diff --git a/.gitignore b/.gitignore index 5b350f4..1ab5018 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,11 @@ src/ui/node_modules/ src/ui/electron/node_modules/ src/compiler/ast/ src/compiler/bad.json +src/compiler/build/ +src/compiler/compiler.pdb +src/compiler/yaml-cppd.dll +vcpkg/ .vscode/ *.exe node_modules/ +src\compiler\compiler.exe \ No newline at end of file diff --git a/HOWTO_RUN.txt b/HOWTO_RUN.txt new file mode 100644 index 0000000..1e6c0b5 --- /dev/null +++ b/HOWTO_RUN.txt @@ -0,0 +1,207 @@ +=============================================================================== + SIMRUN - How to Run +=============================================================================== + +This project is a simulation system with: + - UI: React/TypeScript frontend with Electron desktop shell (Vite) + - Compiler: C++ backend for IR processing and compilation + +Follow the steps below for your platform. + +=============================================================================== + PREREQUISITES (BOTH PLATFORMS) +=============================================================================== + +1. Install Git + - Download from https://git-scm.com/ + +2. Install Node.js (v16 or higher) and npm + - Download from https://nodejs.org/ + - npm is included with Node.js + - Verify: `node --version` and `npm --version` + +3. Install CMake (v3.18+) + - Download from https://cmake.org/download/ + - Verify: `cmake --version` + +4. Install a C++ compiler + + WINDOWS: + - Visual Studio Community 2022 (free) + - Download from https://visualstudio.microsoft.com/downloads/ + - Install with "Desktop development with C++" workload + + LINUX: + - Ubuntu/Debian: `sudo apt-get install build-essential g++ cmake` + - Fedora: `sudo dnf install gcc g++ make cmake` + - Arch: `sudo pacman -S base-devel cmake` + +5. Bootstrap vcpkg (C++ dependency manager) + + WINDOWS (PowerShell): + - Open PowerShell and navigate to the repo root + - .\vcpkg\bootstrap-vcpkg.bat + + LINUX (Bash): + - Open terminal and navigate to the repo root + - ./vcpkg/bootstrap-vcpkg.sh + +=============================================================================== + OPTION 1: RUN UI ONLY (No C++ compilation) +=============================================================================== + +Great if you just want to develop the UI and don't need to rebuild the compiler. + +Steps (Both Windows & Linux): + +1. Navigate to UI directory + cd src/ui + +2. Install Node dependencies + npm install + +3. Start development server (with hot reload) + npm run electron:dev + + This will: + - Start the Vite dev server + - Launch the Electron app + - Auto-reload when you make changes + +4. Build for production (if needed) + npm run build + npm run electron + - Creates optimized build and launches Electron desktop app + +=============================================================================== + OPTION 2: FULL SETUP (UI + C++ Compiler) +=============================================================================== + +If you need to rebuild the C++ compiler component, follow this section. + +--- +WINDOWS (Visual Studio 2022 + CMake) +--- + +1. Open a "Developer PowerShell for VS 2022" (search in Windows Start menu) + +2. Navigate to the compiler directory + cd src\compiler + +3. Create build directory and configure with CMake + mkdir build + cd build + cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_TOOLCHAIN_FILE=..\..\vcpkg\scripts\buildsystems\vcpkg.cmake .. + +4. Build the project + cmake --build . --config Release + +5. The compiled executable will be in: + build\Release\ + +6. Now run the UI (from repo root) + cd ..\..\src\ui + npm install + npm run electron:dev + +--- +LINUX (CMake + GCC/Clang) +--- + +1. Open a terminal and navigate to the repo root + +2. Navigate to the compiler directory + cd src/compiler + +3. Create build directory and configure with CMake + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=../../vcpkg/scripts/buildsystems/vcpkg.cmake .. + +4. Build the project + cmake --build . -- -j$(nproc) + + (This uses all available CPU cores for faster compilation) + +5. The compiled binary will be in: + ./ + +6. Now run the UI (from repo root) + cd ../../src/ui + npm install + npm run electron:dev + +=============================================================================== + TROUBLESHOOTING +=============================================================================== + +Issue: CMake can't find dependencies + Solution: Make sure vcpkg is bootstrapped (see Prerequisites step 5) + Ensure you're using the correct path to vcpkg.cmake + +Issue: Cannot find C++ compiler + WINDOWS: Install Visual Studio 2022 with C++ workload + LINUX: Run `sudo apt-get install build-essential` (Ubuntu/Debian) + +Issue: npm install fails + Solution: Clear npm cache and retry + npm cache clean --force + rm -rf node_modules + npm install + +Issue: Electron won't start + Solution: Try running from the repo root in a new terminal + Make sure all npm packages installed successfully + Check console output for specific error messages + +Issue: Port 8081 already in use (compiler server) + Solution: Change the compiler port in src/compiler/src/server.cpp + Or stop the process using port 8081 + +=============================================================================== + PROJECT STRUCTURE +=============================================================================== + +simrun/ +├── src/ +│ ├── ui/ ← React/TypeScript frontend + Electron +│ ├── compiler/ ← C++ compiler backend +│ ├── sim/ ← C++ simulation engine +│ └── analysis/ ← Analysis tools +├── vcpkg/ ← C++ dependency manager +├── HOWTO_RUN.txt ← This file +└── README.md ← Project overview + +=============================================================================== + QUICK REFERENCE +=============================================================================== + +UI Development (Hot Reload): + cd src/ui + npm run electron:dev + +UI Production Build: + cd src/ui + npm run build + npm run electron + +Compiler Build (Windows): + cd src\compiler && mkdir build && cd build + cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_TOOLCHAIN_FILE=..\..\vcpkg\scripts\buildsystems\vcpkg.cmake .. + cmake --build . --config Release + +Compiler Build (Linux): + cd src/compiler && mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=../../vcpkg/scripts/buildsystems/vcpkg.cmake .. + cmake --build . -- -j$(nproc) + +=============================================================================== + GETTING HELP +=============================================================================== + +- Check README.md for project overview +- Check project issues/wiki on GitHub +- Review compiler logs: look for errors in cmake output +- Check browser console in Electron app (press Ctrl+Shift+I) + +=============================================================================== diff --git a/profiles/components/api/service/default.yaml b/profiles/components/api/service/default.yaml deleted file mode 100644 index d071a69..0000000 --- a/profiles/components/api/service/default.yaml +++ /dev/null @@ -1,6 +0,0 @@ -defaults: - dist_latency: lognormal - base_median_latency: 30 - base_variance_latency: 0.8 - max_concurrency: 100 - queue_capacity: 300 diff --git a/profiles/components/cache/default.yaml b/profiles/components/cache/default.yaml deleted file mode 100644 index 0592367..0000000 --- a/profiles/components/cache/default.yaml +++ /dev/null @@ -1,5 +0,0 @@ -defaults: - base_cache_hit_probability: 0.85 - base_cache_hit_latency: 0.2 - base_cache_miss_latency: 1.0 - max_concurrency: 500 diff --git a/profiles/components/database/default.yaml b/profiles/components/database/default.yaml deleted file mode 100644 index afa0a1f..0000000 --- a/profiles/components/database/default.yaml +++ /dev/null @@ -1,9 +0,0 @@ -defaults: - seek_model: lognormal - base_median_seek_ms: 5.0 - base_variance_seek_ms: 0.6 - max_iops: 4000 - bucket_capacity: 8000 - initial_tokens: 8000 - max_concurrency: 1500 - queue_capacity: 5000 diff --git a/profiles/networks/ethernet.yaml b/profiles/networks/ethernet.yaml deleted file mode 100644 index e7cfc3a..0000000 --- a/profiles/networks/ethernet.yaml +++ /dev/null @@ -1,6 +0,0 @@ -defaults: - base_median_latency: 1 - base_variance_latency: 0.5 - base_bandwidth_mbps: 1000 - base_packet_size_bytes: 1500 - queue_capacity: 2048 diff --git a/src/compiler/CMakeLists.txt b/src/compiler/CMakeLists.txt new file mode 100644 index 0000000..ecea795 --- /dev/null +++ b/src/compiler/CMakeLists.txt @@ -0,0 +1,74 @@ +cmake_minimum_required(VERSION 3.15) +project(compiler) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# --------------------------- +# Find packages (vcpkg CONFIG mode) +# --------------------------- +find_package(nlohmann_json CONFIG REQUIRED) +find_package(yaml-cpp CONFIG REQUIRED) +# Uncomment if this project also uses Crow +# find_package(Crow CONFIG REQUIRED) + +# --------------------------- +# Source files +# --------------------------- +set(SOURCES + src/server.cpp + src/compiler_driver.cpp + src/ir_parser.cpp + src/validator.cpp + src/profile_repository.cpp + src/profile_resolver.cpp + src/ir_serializer.cpp +) + +# --------------------------- +# Create executable +# --------------------------- +add_executable(compiler ${SOURCES}) +# Put the final .exe in the project source/compiler folder +set_target_properties(compiler PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_SOURCE_DIR}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_SOURCE_DIR}" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_SOURCE_DIR}" + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_SOURCE_DIR}" +) + + +# --------------------------- +# Link libraries +# --------------------------- +target_link_libraries(compiler + PRIVATE + nlohmann_json::nlohmann_json + yaml-cpp + # Crow::Crow # Uncomment if using Crow +) + +# --------------------------- +# Include directories +# --------------------------- +target_include_directories(compiler + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +# --------------------------- +# Output directory +# --------------------------- +set_target_properties(compiler PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) + +# --------------------------- +# Compiler warnings +# --------------------------- +if(MSVC) + target_compile_options(compiler PRIVATE /W4) +else() + target_compile_options(compiler PRIVATE -Wall -Wextra -Wpedantic) +endif() diff --git a/src/compiler/final_ir.json b/src/compiler/final_ir.json new file mode 100644 index 0000000..8108ae8 --- /dev/null +++ b/src/compiler/final_ir.json @@ -0,0 +1,27 @@ +{ + "context": { + "components": [ + { + "config": { + "base_median_seek_ms": 5.0, + "base_seek_time": 10, + "base_variance_seek_ms": 0.6, + "bucket_capacity": 8000, + "initial_tokens": 8000, + "max_concurrency": 100, + "max_iops": 3000, + "queue_capacity": 3000, + "seek_model": "lognormal" + }, + "id": 1631572, + "type": "database" + } + ] + }, + "header": { + "engine_version": "simrun-0.1", + "ir_version": "1.0", + "seed": 42, + "time_unit": "milliseconds" + } +} \ No newline at end of file diff --git a/src/compiler/src/compiler_driver.cpp b/src/compiler/src/compiler_driver.cpp index 5af2057..272424f 100644 --- a/src/compiler/src/compiler_driver.cpp +++ b/src/compiler/src/compiler_driver.cpp @@ -4,21 +4,32 @@ #include "profile_resolver.h" #include "ir_serializer.h" #include +#include +#include +namespace fs = std::filesystem; string compileIR(IR& ir) { - // 🔴 VALIDATION STAGE + // VALIDATION STAGE string err = validateIR(ir); if (!err.empty()) { throw runtime_error(err); // propagate to server } - // 🟢 PROFILE RESOLUTION - ProfileRepository repo( - "/home/ishita-tyagi/Desktop/Compiler-Simrun/profiles" - ); + // PROFILE RESOLUTION + std::cout << "Current working directory: " << fs::current_path().string() << std::endl; + + string profilesPath = "./profiles"; + if (!fs::exists(profilesPath)) { + std::cerr << "Profiles directory not found at: " << fs::absolute(profilesPath).string() << std::endl; + throw runtime_error("Profiles directory not found at: " + fs::absolute(profilesPath).string()); + } + + std::cout << "Profiles directory found at: " << fs::absolute(profilesPath).string() << std::endl; + + ProfileRepository repo(profilesPath); ProfileResolver resolver(repo); resolver.resolve(ir); - return writeIRToJsonFile(ir, "/tmp/final_ir.json"); + return writeIRToJsonFile(ir, "./final_ir.json"); } diff --git a/src/compiler/src/profile_repository.cpp b/src/compiler/src/profile_repository.cpp index 21ff5fc..bfed55b 100644 --- a/src/compiler/src/profile_repository.cpp +++ b/src/compiler/src/profile_repository.cpp @@ -1,14 +1,61 @@ #include "profile_repository.h" +#include +#include +#include + +namespace fs = std::filesystem; ProfileRepository::ProfileRepository(const string& root) : root(root) {} YAML::Node ProfileRepository::getComponentProfile(const string& type) { - string path = root + "/components/" + type + "/default.yaml"; - return YAML::LoadFile(path); + string path; + + // Handle special cases for component types + if (type == "api" || type == "api/service") { + path = root + "/components/api/service/default.yaml"; + } else if (type == "database") { + path = root + "/components/database/default.yaml"; + } else if (type == "cache") { + path = root + "/components/cache/default.yaml"; + } else { + // Default to type/default.yaml + path = root + "/components/" + type + "/default.yaml"; + } + + std::cout << "Loading component profile for type '" << type << "': " << path << std::endl; + + if (!fs::exists(path)) { + std::cerr << "Profile file not found: " << path << std::endl; + throw std::runtime_error("bad file: " + path); + } + + try { + YAML::Node profile = YAML::LoadFile(path); + std::cout << "Loaded profile successfully" << std::endl; + return profile; + } catch (const YAML::Exception& e) { + std::cerr << "Error loading YAML: " << e.what() << std::endl; + throw std::runtime_error(string("YAML error in ") + path + ": " + e.what()); + } } YAML::Node ProfileRepository::getNetworkProfile(const string& type) { string path = root + "/networks/" + type + ".yaml"; - return YAML::LoadFile(path); + + std::cout << "Loading network profile: " << path << std::endl; + + if (!fs::exists(path)) { + std::cerr << "Profile file not found: " << path << std::endl; + throw std::runtime_error("bad file: " + path); + } + + try { + YAML::Node profile = YAML::LoadFile(path); + std::cout << "Loaded profile successfully" << std::endl; + return profile; + } catch (const YAML::Exception& e) { + std::cerr << "Error loading YAML: " << e.what() << std::endl; + throw std::runtime_error(string("YAML error in ") + path + ": " + e.what()); + } } diff --git a/src/compiler/src/server.cpp b/src/compiler/src/server.cpp index 7bf48a3..d940c05 100644 --- a/src/compiler/src/server.cpp +++ b/src/compiler/src/server.cpp @@ -1,17 +1,39 @@ #include #include #include +#include +#include #include "ir_parser.h" #include "compiler_driver.h" +using json = nlohmann::json; + int main() { crow::SimpleApp app; CROW_ROUTE(app, "/compile").methods("POST"_method) ([](const crow::request& req) { try { - IR ir = parseIR(req.body); + std::cout << "\n=== COMPILER REQUEST RECEIVED ===" << std::endl; + std::cout << "Raw body: " << req.body << std::endl; + + json requestBody = json::parse(req.body); + std::cout << "Parsed request successfully" << std::endl; + + string irJson = requestBody["project"].dump(); + std::cout << "Extracted project field: " << irJson << std::endl; + + IR ir = parseIR(irJson); + std::cout << "Parsed IR - Components count: " << ir.context.components.size() << std::endl; + std::cout << "Parsed IR - Links count: " << ir.context.links.size() << std::endl; + + for (size_t i = 0; i < ir.context.components.size(); i++) { + std::cout << " Component " << i << ": id=" << ir.context.components[i].id + << ", type=" << ir.context.components[i].type << std::endl; + } + string path = compileIR(ir); + std::cout << "Compilation successful! Output: " << path << std::endl; ifstream in(path); stringstream ss; @@ -21,10 +43,12 @@ int main() { res.code = 200; res.set_header("Content-Type", "application/json"); res.body = ss.str(); + std::cout << "Sending response (HTTP 200)" << std::endl; return res; } catch (const runtime_error& e) { - // ALIDATION / USER ERROR + // VALIDATION / USER ERROR + std::cerr << "VALIDATION ERROR: " << e.what() << std::endl; return crow::response( 400, string("Validation error: ") + e.what() @@ -32,6 +56,7 @@ int main() { } catch (const exception& e) { // INTERNAL ERROR + std::cerr << "INTERNAL ERROR: " << e.what() << std::endl; return crow::response( 500, string("Internal compiler error: ") + e.what() @@ -40,5 +65,5 @@ int main() { }); - app.port(8080).run(); + app.port(8081).run(); } diff --git a/src/compiler/src/validator.cpp b/src/compiler/src/validator.cpp index c857c36..8d0097c 100644 --- a/src/compiler/src/validator.cpp +++ b/src/compiler/src/validator.cpp @@ -1,4 +1,5 @@ #include "validator.h" +#include string validateIR(const IR& ir) { @@ -7,12 +8,18 @@ string validateIR(const IR& ir) { } for (const auto& c : ir.context.components) { + std::cout << " Validating component: id=" << c.id << ", type=" << c.type << std::endl; + if (c.id <= 0) { + std::cerr << " Invalid component ID: " << c.id << " (must be > 0)" << std::endl; return "Component id must be positive"; } if (c.type.empty()) { + std::cerr << " Invalid component type (empty)" << std::endl; return "Component type cannot be empty"; } + + std::cout << " Component valid" << std::endl; } return ""; // empty = no errors diff --git a/src/compiler/yaml-cpp.dll b/src/compiler/yaml-cpp.dll new file mode 100644 index 0000000..d7458ab Binary files /dev/null and b/src/compiler/yaml-cpp.dll differ diff --git a/src/ui/electron/main.cjs b/src/ui/electron/main.cjs index 6c984a3..9af4fe3 100644 --- a/src/ui/electron/main.cjs +++ b/src/ui/electron/main.cjs @@ -7,9 +7,15 @@ let compilerProcess = null; function startCompiler() { const compilerPath = path.resolve(__dirname, '../../compiler/compiler.exe'); + const compilerDir = path.dirname(compilerPath); + + console.log("Launching compiler at:", compilerPath); + console.log("Working directory:", compilerDir); compilerProcess = spawn(compilerPath, [], { - stdio: 'inherit' + stdio: 'inherit', + windowsHide: true, + cwd: compilerDir // Set working directory to compiler folder }); compilerProcess.on('error', (err) => { @@ -21,6 +27,7 @@ function startCompiler() { }); } + function createWindow() { const win = new BrowserWindow({ width: 1200, @@ -30,7 +37,7 @@ function createWindow() { } }); - win.loadURL('http://localhost:8080'); + win.loadURL('http://localhost:5173'); } /* ---------------- IPC HANDLERS ---------------- */ @@ -39,14 +46,18 @@ ipcMain.handle('ping-electron', async () => { return 'Pong from Electron main process'; }); -function postToCompiler(pathname, data) { +function postToCompiler(data) { return new Promise((resolve, reject) => { const jsonData = JSON.stringify(data); + console.log("\n[Electron Main] ➡ Sending POST to http://127.0.0.1:8081/compile"); + console.log("[Electron Main] Request body:", JSON.stringify(data, null, 2)); + console.log("[Electron Main] Payload size:", jsonData.length, "bytes"); + const options = { hostname: '127.0.0.1', - port: 18080, - path: pathname, + port: 8081, + path: '/compile', method: 'POST', headers: { 'Content-Type': 'application/json', @@ -55,46 +66,52 @@ function postToCompiler(pathname, data) { }; const req = http.request(options, (res) => { + console.log("[Electron Main] ⬅ Response status:", res.statusCode); + let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { + console.log("[Electron Main] ⬅ Response received"); + console.log("[Electron Main] Raw response body:", body); try { - resolve(JSON.parse(body)); - } catch (e) { - reject(e); + const parsed = JSON.parse(body); + console.log("[Electron Main] Response parsed successfully"); + resolve(parsed); + } catch { + console.error("[Electron Main] Failed to parse response as JSON"); + resolve({ status: "error", raw: body }); } }); }); - req.on('error', reject); + req.on('error', (err) => { + console.error("[Electron Main] HTTP request failed:", err.message); + reject(err); + }); + req.write(jsonData); req.end(); }); } + ipcMain.handle('simulate-project', async (_, projectJson) => { + console.log("IPC simulate-project received in Electron"); try { - const response = await postToCompiler('/api/v1/simulate', { - project: projectJson, - options: {} - }); - + const response = await postToCompiler(projectJson); return response; } catch (err) { + console.error("Compiler request failed:", err.message); return { status: "error", phase: "system", valid: false, - errors: [{ - code: "COMPILER_UNAVAILABLE", - message: "Compiler server not reachable" - }], - warnings: [], - results: null + errors: [{ message: "Compiler server not reachable" }] }; } }); + /* ---------------- APP LIFECYCLE ---------------- */ app.whenReady().then(() => { diff --git a/src/ui/package.json b/src/ui/package.json index 4e7d0a5..f509c72 100644 --- a/src/ui/package.json +++ b/src/ui/package.json @@ -11,7 +11,7 @@ "lint": "eslint .", "preview": "vite preview", "electron": "electron .", - "electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:8080 && electron .\"" + "electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:5173 && electron .\"" }, "dependencies": { "@fontsource/inter": "^5.2.8", diff --git a/src/ui/src/components/toolbar/ComponentToolbar.tsx b/src/ui/src/components/toolbar/ComponentToolbar.tsx index b084404..aba7a0e 100644 --- a/src/ui/src/components/toolbar/ComponentToolbar.tsx +++ b/src/ui/src/components/toolbar/ComponentToolbar.tsx @@ -14,10 +14,10 @@ interface ToolbarSectionProps { } const categoryIcons: Record = { - database: '💾', - cache: '⚡', - api: '🌐', - network: '🔗', + database: '', + cache: '', + api: '', + network: '', }; const categoryGradients: Record = { diff --git a/src/ui/src/lib/irSerializer.ts b/src/ui/src/lib/irSerializer.ts new file mode 100644 index 0000000..56d5efe --- /dev/null +++ b/src/ui/src/lib/irSerializer.ts @@ -0,0 +1,180 @@ +/** + * Serializes architecture canvas data to IR (Intermediate Representation) format + * that the compiler expects + */ + +import { SimulationExport } from '@/types/simulation'; + +export interface IRHeader { + ir_version: string; + engine_version: string; + seed: number; + time_unit: string; +} + +export interface ComponentConfig { + [key: string]: number | string | boolean | undefined; +} + +export interface Component { + id: number; + type: string; + config: ComponentConfig; +} + +export interface Link { + id: number; + from: number; + to: number; + config: ComponentConfig; +} + +export interface Context { + components: Component[]; + links: Link[]; +} + +export interface IR { + header: IRHeader; + context: Context; +} + +/** + * Convert SimulationExport from canvas to IR format expected by compiler + */ +export function serializeToIR(exportData: SimulationExport): IR { + // Build header + const header: IRHeader = { + ir_version: '1.0', + engine_version: 'simrun-0.1', + seed: 42, + time_unit: 'milliseconds', + }; + + // Build components - convert id from string to positive number + const components: Component[] = exportData.components.map((comp, index) => { + // Generate a positive numeric ID from the string ID + // Use a simple hash: just take last 8 digits if available, or use index + let numericId = 0; + + // Try to extract digits and use the last portion (more unique per component) + const digitsOnly = comp.id.replace(/\D/g, ''); + if (digitsOnly && digitsOnly.length > 0) { + // Take the last 8 characters to get a manageable number + const lastDigits = digitsOnly.slice(-8); + numericId = parseInt(lastDigits, 10); + } + + // If still invalid or 0, use index + 1 (always > 0) + if (!numericId || numericId <= 0 || Number.isNaN(numericId)) { + numericId = index + 1; + console.warn(`[irSerializer] Component "${comp.id}" → ID generated from index: ${numericId}`); + } else { + console.log(`[irSerializer] Component "${comp.id}" → numeric ID: ${numericId}`); + } + + // Build config based on component type + const config: ComponentConfig = {}; + + if (comp.parameters) { + const params = comp.parameters as any; + + switch (comp.type) { + case 'api': + case 'api/service': + config.dist_latency = 'lognormal'; + config.base_median_latency = params.processing_latency_ms || 30; + config.base_variance_latency = 0.8; + config.max_concurrency = params.max_concurrency || 100; + config.queue_capacity = 300; + break; + + case 'database': + config.max_iops = 3000; + config.base_seek_time = params.base_latency_ms || 4; + config.max_concurrency = params.max_concurrency || 1000; + config.queue_capacity = 3000; + break; + + case 'cache': + config.cache_hit_probability = params.hit_rate || 0.7; + config.cache_hit_latency = 0.3; + config.cache_miss_latency = 0.1; + break; + + default: + // Generic parameters + Object.assign(config, params); + } + } + + return { + id: numericId, + type: comp.type, + config, + }; + }); + + // Build links - convert ids to numbers + const links: Link[] = exportData.links.map((link, index) => { + // Extract numeric IDs from source and target + let fromId = 0; + let toId = 0; + + // Helper function to extract numeric ID + const extractNumericId = (nodeId: string): number => { + const digitsOnly = nodeId.replace(/\D/g, ''); + if (digitsOnly && digitsOnly.length > 0) { + const lastDigits = digitsOnly.slice(-8); + return parseInt(lastDigits, 10); + } + return 0; + }; + + fromId = extractNumericId(link.source); + toId = extractNumericId(link.target); + + // Fallback to component 1 if extraction failed + if (!fromId || fromId <= 0 || Number.isNaN(fromId)) { + console.warn(`[irSerializer] Link source "${link.source}" → using default: 1`); + fromId = 1; + } + + if (!toId || toId <= 0 || Number.isNaN(toId)) { + console.warn(`[irSerializer] Link target "${link.target}" → using default: 1`); + toId = 1; + } + + console.log(`[irSerializer] Link: "${link.source}" → "${link.target}" | IDs: ${fromId} → ${toId}`); + + const config: ComponentConfig = {}; + if (link.parameters) { + const params = link.parameters as any; + config.base_latency = params.latency_ms || 1; + config.base_bandwidth_mbps = params.bandwidth_limit || 1000; + config.packet_size_bytes = 1024; + } + + return { + id: index + 100, // Start link IDs from 100 to avoid collision with components + from: fromId, + to: toId, + config, + }; + }); + + return { + header, + context: { + components, + links, + }, + }; +} + +/** + * Wraps IR in the format expected by the compiler server + */ +export function wrapForCompiler(ir: IR): { project: IR } { + return { project: ir }; +} diff --git a/src/ui/src/pages/Index.tsx b/src/ui/src/pages/Index.tsx index 9434503..205cecb 100644 --- a/src/ui/src/pages/Index.tsx +++ b/src/ui/src/pages/Index.tsx @@ -4,18 +4,105 @@ import { InspectorPanel } from '@/components/inspector/InspectorPanel'; import { CanvasHeader } from '@/components/header/CanvasHeader'; import { ConfigTabs } from '@/components/panels/ConfigTabs'; import { TooltipProvider } from '@/components/ui/tooltip'; +import { useArchitectureStore } from '@/store/architectureStore'; +import { useSimulationStore } from '@/store/simulationStore'; +import { useState } from "react"; +import { toast } from "sonner"; +import { serializeToIR, wrapForCompiler } from '@/lib/irSerializer'; +import { ComponentCategory } from '@/types/architecture'; +import { ComponentParameters, NetworkParameters, DEFAULT_DATABASE_PARAMS, DEFAULT_CACHE_PARAMS, DEFAULT_API_PARAMS, DEFAULT_NETWORK_PARAMS } from '@/types/simulation'; const Index = () => { + const [simulationResult, setSimulationResult] = useState(null); + const [isOutputMinimized, setIsOutputMinimized] = useState(false); + const [isOutputClosed, setIsOutputClosed] = useState(false); + const { nodes, edges } = useArchitectureStore(); + const { routes, workload, faults } = useSimulationStore(); + + const getDefaultParams = (category: ComponentCategory): ComponentParameters => { + switch (category) { + case 'database': + return DEFAULT_DATABASE_PARAMS; + case 'cache': + return DEFAULT_CACHE_PARAMS; + case 'api': + return DEFAULT_API_PARAMS; + case 'network': + return DEFAULT_NETWORK_PARAMS; + default: + return DEFAULT_API_PARAMS; + } + }; + const handleRunSimulation = async () => { - const projectJson = { - test: "demo" // replace later with real canvas export - }; + if (nodes.length === 0) { + toast.error("Please add components to the canvas"); + return; + } try { - const result = await window.api.simulate(projectJson); - console.log("Simulation response:", result); + // Build the architecture export from canvas state + const exportData = { + components: nodes.map((node) => ({ + id: node.id, + type: node.data.category as string, + profile: node.data.profile as string, + position: node.position, + label: node.data.label as string, + parameters: { + ...getDefaultParams(node.data.category as ComponentCategory), + ...(node.data.parameters as ComponentParameters), + }, + })), + links: edges.map((edge) => ({ + id: edge.id, + source: edge.source, + target: edge.target, + parameters: { + ...DEFAULT_NETWORK_PARAMS, + ...((edge.data?.parameters as NetworkParameters) || {}), + }, + })), + routes, + workload, + faults, + metadata: { + version: '1.0.0', + createdAt: new Date().toISOString(), + }, + }; + + console.log("[UI] Export data:", exportData); + console.log("[UI] Components count:", exportData.components.length); + + // Serialize to IR format + const ir = serializeToIR(exportData); + console.log("[UI] Serialized IR:", ir); + console.log("[UI] IR Components:", ir.context.components); + + const compilerRequest = wrapForCompiler(ir); + console.log("[UI] Final request to compiler:", compilerRequest); + + toast.promise( + window.api.simulate(compilerRequest), + { + loading: 'Running simulation...', + success: (result) => { + console.log("[UI] Response from compiler:", result); + setSimulationResult(result); + setIsOutputClosed(false); + setIsOutputMinimized(false); + return 'Simulation completed'; + }, + error: (err) => { + console.error("[UI] Error from compiler:", err); + return 'Compiler error - check console'; + } + } + ); } catch (err) { - console.error("Simulation failed:", err); + console.error("[UI] Failed to prepare simulation:", err); + toast.error("Failed to prepare simulation"); } }; @@ -39,6 +126,46 @@ const Index = () => { + + {/* Simulation Result Overlay */} + {simulationResult && !isOutputClosed && ( +
+
+

Simulation Output

+
+ + +
+
+ {!isOutputMinimized && ( +
+
{JSON.stringify(simulationResult, null, 2)}
+
+ )} +
+ )} + + {/* Floating button to reopen output */} + {simulationResult && isOutputClosed && ( + + )} ); diff --git a/src/ui/vite.config.ts b/src/ui/vite.config.ts index da25c6d..7ba85d8 100644 --- a/src/ui/vite.config.ts +++ b/src/ui/vite.config.ts @@ -7,7 +7,7 @@ import { componentTagger } from "lovable-tagger"; export default defineConfig(({ mode }) => ({ server: { host: "::", - port: 8080, + port: 5173, }, plugins: [react(), mode === "development" && componentTagger()].filter(Boolean), resolve: {