diff --git a/docker-compose.yml b/docker-compose.yml index 48af458..8e26af2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -300,6 +300,7 @@ services: restart: unless-stopped volumes: - scribus-designs:/data/designs + - rspace-files:/data/files environment: - BRIDGE_SECRET=${SCRIBUS_BRIDGE_SECRET} - BRIDGE_PORT=8765 diff --git a/lib/folk-design-agent.ts b/lib/folk-design-agent.ts index 1be18bd..b99ac7a 100644 --- a/lib/folk-design-agent.ts +++ b/lib/folk-design-agent.ts @@ -59,10 +59,18 @@ const styles = css` background: rgba(255,255,255,0.2); } + .wrapper { + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + } + .content { display: flex; flex-direction: column; - height: calc(100% - 36px); + flex: 1; + min-height: 0; overflow: hidden; } @@ -123,6 +131,7 @@ const styles = css` overflow-y: auto; padding: 12px; font-size: 12px; + min-height: 0; } .step { @@ -234,6 +243,7 @@ export class FolkDesignAgent extends FolkShape { const root = super.createRenderRoot(); const wrapper = document.createElement("div"); + wrapper.className = "wrapper"; wrapper.innerHTML = html`
diff --git a/lib/folk-freecad.ts b/lib/folk-freecad.ts index 95e85a7..b2ad8d5 100644 --- a/lib/folk-freecad.ts +++ b/lib/folk-freecad.ts @@ -49,10 +49,18 @@ const styles = css` background: rgba(255, 255, 255, 0.2); } + .wrapper { + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + } + .content { display: flex; flex-direction: column; - height: calc(100% - 36px); + flex: 1; + min-height: 0; overflow: hidden; } @@ -111,11 +119,13 @@ const styles = css` justify-content: center; padding: 12px; overflow: hidden; + min-height: 0; } .preview-area img { max-width: 100%; max-height: 100%; + object-fit: contain; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } @@ -234,6 +244,7 @@ export class FolkFreeCAD extends FolkShape { const root = super.createRenderRoot(); const wrapper = document.createElement("div"); + wrapper.className = "wrapper"; wrapper.innerHTML = html`
diff --git a/lib/folk-kicad.ts b/lib/folk-kicad.ts index a1542d3..12baaab 100644 --- a/lib/folk-kicad.ts +++ b/lib/folk-kicad.ts @@ -49,10 +49,18 @@ const styles = css` background: rgba(255, 255, 255, 0.2); } + .wrapper { + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + } + .content { display: flex; flex-direction: column; - height: calc(100% - 36px); + flex: 1; + min-height: 0; overflow: hidden; } @@ -151,10 +159,15 @@ const styles = css` flex: 1; overflow: auto; padding: 12px; + min-height: 0; + display: flex; + flex-direction: column; } .preview-area img { max-width: 100%; + max-height: 100%; + object-fit: contain; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } @@ -294,6 +307,7 @@ export class FolkKiCAD extends FolkShape { const root = super.createRenderRoot(); const wrapper = document.createElement("div"); + wrapper.className = "wrapper"; wrapper.innerHTML = html`
diff --git a/modules/rdesign/design-agent-route.ts b/modules/rdesign/design-agent-route.ts index ffc7c73..642f28f 100644 --- a/modules/rdesign/design-agent-route.ts +++ b/modules/rdesign/design-agent-route.ts @@ -118,16 +118,21 @@ async function generateAndPlaceImage(args: Record): Promise { const data = await res.json() as any; if (!data.url) return { error: "Image generation failed", details: data }; - // Download the image to a local path inside the Scribus container + // Download the image and save to shared volume (rspace-files, mounted in both containers) const imageUrl = data.url; const downloadRes = await fetch(imageUrl, { signal: AbortSignal.timeout(30_000) }); if (!downloadRes.ok) return { error: "Failed to download generated image" }; + const { writeFile, mkdir } = await import("node:fs/promises"); const imageName = `gen_${Date.now()}.png`; - const imagePath = `/data/designs/_generated/${imageName}`; + const imageDir = "/data/files/generated"; + const imagePath = `${imageDir}/${imageName}`; - // Write image to bridge container via a bridge command - // For now, place the frame with the URL reference + await mkdir(imageDir, { recursive: true }); + const imageBytes = Buffer.from(await downloadRes.arrayBuffer()); + await writeFile(imagePath, imageBytes); + + // Place the image frame in Scribus — path is accessible via shared rspace-files volume const placeResult = await bridgeCommand("add_image_frame", { x: args.x, y: args.y, diff --git a/server/cad-orchestrator.ts b/server/cad-orchestrator.ts index 5728f72..9909746 100644 --- a/server/cad-orchestrator.ts +++ b/server/cad-orchestrator.ts @@ -176,34 +176,44 @@ export function assembleKicadResult(orch: OrchestrationResult): KicadResult { let drcResults: { violations: string[] } | null = null; for (const entry of orch.toolCallLog) { - try { - const parsed = JSON.parse(entry.result); - switch (entry.tool) { - case "export_svg": - // Could be schematic or board SVG — check args or content + const text = entry.result; + + // Try JSON parse first, then fall back to regex path extraction + let parsed: any = null; + try { parsed = JSON.parse(text); } catch {} + + switch (entry.tool) { + case "export_svg": { + const path = parsed?.svg_path || parsed?.path || parsed?.url + || extractPathFromText(text, [".svg"]); + if (path) { if (entry.args.type === "board" || entry.args.board) { - boardSvg = parsed.svg_path || parsed.path || parsed.url || null; + boardSvg = path; } else { - schematicSvg = parsed.svg_path || parsed.path || parsed.url || null; + schematicSvg = path; } - break; - case "run_drc": + } + break; + } + case "run_drc": + if (parsed) { drcResults = { violations: parsed.violations || parsed.errors || [], }; - break; - case "export_gerber": - gerberUrl = parsed.gerber_path || parsed.path || parsed.url || null; - break; - case "export_bom": - bomUrl = parsed.bom_path || parsed.path || parsed.url || null; - break; - case "export_pdf": - pdfUrl = parsed.pdf_path || parsed.path || parsed.url || null; - break; - } - } catch { - // Non-JSON results are fine (intermediate steps) + } + break; + case "export_gerber": + gerberUrl = parsed?.gerber_path || parsed?.path || parsed?.url + || extractPathFromText(text, [".zip", ".gbr"]); + break; + case "export_bom": + bomUrl = parsed?.bom_path || parsed?.path || parsed?.url + || extractPathFromText(text, [".csv", ".json", ".xml"]); + break; + case "export_pdf": + pdfUrl = parsed?.pdf_path || parsed?.path || parsed?.url + || extractPathFromText(text, [".pdf"]); + break; } }