From 4a75c64a1862df05771c2aa1326c734ebcca0b46 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 25 Mar 2026 16:51:31 -0700 Subject: [PATCH 1/2] feat(rdesign): rich landing page, Scribus canvas tool, icon update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace minimal rDesign landing with full rl-* pattern (hero, features, how-it-works, capabilities, open source, data protection, CTA) - Add Scribus button to Create toolbar group in canvas (folk-design-agent) - Export FolkDesignAgent from lib/index.ts, register in canvas.html - Update module icon from 🎯 to 🎨 (matches favicon + MODULE_META) Co-Authored-By: Claude Opus 4.6 --- lib/index.ts | 1 + modules/rdesign/mod.ts | 180 ++++++++++++++++++++++++++++++++++++++--- website/canvas.html | 5 ++ 3 files changed, 176 insertions(+), 10 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index 864dc93..e6011ed 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -49,6 +49,7 @@ export * from "./folk-blender"; export * from "./folk-drawfast"; export * from "./folk-freecad"; export * from "./folk-kicad"; +export * from "./folk-design-agent"; // Advanced Shapes export * from "./folk-video-chat"; diff --git a/modules/rdesign/mod.ts b/modules/rdesign/mod.ts index d3e21bd..0275556 100644 --- a/modules/rdesign/mod.ts +++ b/modules/rdesign/mod.ts @@ -500,19 +500,179 @@ function renderDesignApp(space: string, novncUrl: string): string { } function renderDesignLanding(): string { - return `
-
🎯
-

rDesign

-

AI-powered DTP workspace. Describe what you want and the design agent builds it in Scribus — posters, flyers, brochures, and print-ready documents.

-

Text in, design out. No mouse interaction needed.

- Open rDesign -
`; + return ` + +
+ rDesign +

(You)rDesign, text in — design out.

+

AI-Powered Desktop Publishing with Scribus

+

+ Describe what you want and the design agent builds it — posters, flyers, brochures, zines, and print-ready documents. + No mouse interaction needed. Powered by Gemini tool-calling and Scribus under the hood. +

+ +
+ + +
+
+

What rDesign Handles

+
+
+
📄
+

Posters & Flyers

+

Describe your event, campaign, or announcement and get a print-ready poster with proper typography, layout, and image placement.

+
+
+
📚
+

Brochures & Zines

+

Multi-page layouts with text flow, columns, and image frames. Perfect for newsletters, pamphlets, and small publications.

+
+
+
🎨
+

Brand Assets

+

Business cards, letterheads, social media graphics. Consistent typography and color from a single design brief.

+
+
+
+
+ + +
+
+

How It Works

+
+
+
1
+

Describe

+

Write a natural-language brief — “A3 poster for a jazz night, deep blue background, gold accents, vintage feel.”

+
+
+
2
+

Agent Plans

+

The AI agent decomposes your brief into Scribus operations: document size, text frames, image placement, colors, and fonts.

+
+
+
3
+

Live Preview

+

Watch the design take shape in real time. Each tool call updates the SVG preview so you see progress as it happens.

+
+
+
4
+

Export

+

Open the finished document in Scribus for fine-tuning, or export directly as PDF for print.

+
+
+
+
+ + +
+
+

Design Agent Capabilities

+
+
+
🗎
+

Text Frames

+

Add and position text with control over font, size, color, alignment, and line spacing. The agent handles typography best practices.

+
+
+
🖼
+

Image Frames

+

Place images from URLs or generate them inline with AI. Automatic scaling and positioning within the layout.

+
+
+
+

Shapes & Backgrounds

+

Rectangles, ellipses, decorative elements, and full-page backgrounds. Set colors, borders, and opacity per element.

+
+
+
+ A4/A3/Letter/Custom + PDF Export + AI Image Generation + Multi-page + Print-ready +
+
+
+ + +
+
+

Built on Open Source

+

The tools that power rDesign.

+
+
+

Scribus

+

Professional desktop publishing — the open-source alternative to InDesign. Runs headless in Docker via Xvfb + noVNC.

+
+
+

Gemini

+

Google’s multimodal AI with native tool-calling. Powers the design agent’s planning and execution loop.

+
+
+

noVNC

+

Browser-based VNC client for direct Scribus access. Open the full desktop when you need pixel-perfect manual edits.

+
+
+

Hono

+

Ultra-fast API framework powering the bridge between the design agent and Scribus.

+
+
+
+
+ + +
+
+

Your Designs, Protected

+

All processing happens on your server — designs never leave your infrastructure.

+
+
+
🏠
+

Self-Hosted

+

Scribus runs in a Docker container on your own server. No cloud DTP service, no vendor lock-in.

+
+
+
💾
+

Persistent Storage

+

Design files are stored in a Docker volume and persist across container restarts and updates.

+
+
+
🔧
+

Full Source Access

+

Open the .sla file in Scribus anytime. Native format, no proprietary lock-in. Export to PDF, SVG, or PNG.

+
+
+
+
+ + +
+
+

(You)rDesign, text in — design out.

+

Describe it and the agent builds it. Try the demo or create a space to get started.

+ +
+
+ + +`; } export const designModule: RSpaceModule = { id: "rdesign", name: "rDesign", - icon: "\u{1f3af}", + icon: "🎨", description: "AI-powered DTP workspace — text in, design out", scoping: { defaultScope: 'global', userConfigurable: false }, publicWrite: true, @@ -523,7 +683,7 @@ export const designModule: RSpaceModule = { ], acceptsFeeds: ["data", "resource"], outputPaths: [ - { path: "designs", name: "Designs", icon: "\u{1f3af}", description: "Design files and layouts" }, - { path: "templates", name: "Templates", icon: "\u{1f4d0}", description: "Reusable design templates" }, + { path: "designs", name: "Designs", icon: "🎨", description: "Design files and layouts" }, + { path: "templates", name: "Templates", icon: "📐", description: "Reusable design templates" }, ], }; diff --git a/website/canvas.html b/website/canvas.html index bd84ec7..caf15e7 100644 --- a/website/canvas.html +++ b/website/canvas.html @@ -2076,6 +2076,7 @@ + @@ -2460,6 +2461,7 @@ FolkDrawfast, FolkFreeCAD, FolkKiCAD, + FolkDesignAgent, FolkCanvas, FolkRApp, FolkFeed, @@ -2707,6 +2709,7 @@ FolkDrawfast.define(); FolkFreeCAD.define(); FolkKiCAD.define(); + FolkDesignAgent.define(); FolkCanvas.define(); FolkRApp.define(); FolkFeed.define(); @@ -2753,6 +2756,7 @@ shapeRegistry.register("folk-drawfast", FolkDrawfast); shapeRegistry.register("folk-freecad", FolkFreeCAD); shapeRegistry.register("folk-kicad", FolkKiCAD); + shapeRegistry.register("folk-design-agent", FolkDesignAgent); shapeRegistry.register("folk-canvas", FolkCanvas); shapeRegistry.register("folk-rapp", FolkRApp); shapeRegistry.register("folk-feed", FolkFeed); @@ -4464,6 +4468,7 @@ Use real coordinates, YYYY-MM-DD dates, ISO currency codes. Ask clarifying quest document.getElementById("new-drawfast").addEventListener("click", () => setPendingTool("folk-drawfast")); document.getElementById("new-freecad").addEventListener("click", () => setPendingTool("folk-freecad")); document.getElementById("new-kicad").addEventListener("click", () => setPendingTool("folk-kicad")); + document.getElementById("new-scribus").addEventListener("click", () => setPendingTool("folk-design-agent")); document.getElementById("new-tx-builder").addEventListener("click", () => setPendingTool("folk-transaction-builder")); document.getElementById("new-google-item").addEventListener("click", () => { setPendingTool("folk-google-item", { service: "drive", title: "New Google Item" }); From c046acf9ce21a20c3cebec00dd6d8aef60805995 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 25 Mar 2026 16:51:35 -0700 Subject: [PATCH 2/2] fix(blender): switch from Ollama to Gemini Flash for script generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ollama runs CPU-only on Netcup — 7B models take minutes, exceeding Cloudflare's 100s timeout (524). Gemini 2.0 Flash responds in seconds. Co-Authored-By: Claude Opus 4.6 --- server/index.ts | 52 +++++++++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/server/index.ts b/server/index.ts index 93b02f5..e453264 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1556,52 +1556,44 @@ const RUNPOD_API_KEY = process.env.RUNPOD_API_KEY || ""; app.get("/api/blender-gen/health", async (c) => { const issues: string[] = []; const warnings: string[] = []; - const OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434"; - try { - const res = await fetch(`${OLLAMA_URL}/api/tags`, { signal: AbortSignal.timeout(3000) }); - if (!res.ok) issues.push("Ollama not responding"); - } catch { - issues.push("Ollama unreachable"); - } + if (!GEMINI_API_KEY) issues.push("GEMINI_API_KEY not configured"); if (!RUNPOD_API_KEY) warnings.push("RunPod not configured — script-only mode"); return c.json({ available: issues.length === 0, issues, warnings }); }); app.post("/api/blender-gen", async (c) => { + if (!GEMINI_API_KEY) return c.json({ error: "GEMINI_API_KEY not configured" }, 503); + const { prompt } = await c.req.json(); if (!prompt) return c.json({ error: "prompt required" }, 400); - // Step 1: Generate Blender Python script via LLM - const OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434"; + // Step 1: Generate Blender Python script via Gemini Flash let script = ""; try { - const llmRes = await fetch(`${OLLAMA_URL}/api/generate`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - model: process.env.OLLAMA_MODEL || "qwen2.5-coder:7b", - prompt: `Generate a Blender Python script that creates: ${prompt}\n\nThe script should:\n- Import bpy\n- Clear the default scene\n- Create the described objects with materials\n- Set up basic lighting and camera\n- Render to /tmp/render.png at 1024x1024\n\nOnly output the Python code, no explanations.`, - stream: false, - }), - }); + const { GoogleGenerativeAI } = await import("@google/generative-ai"); + const genAI = new GoogleGenerativeAI(GEMINI_API_KEY); + const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" }); + const result = await model.generateContent(`Generate a Blender Python script that creates: ${prompt} - if (llmRes.ok) { - const llmData = await llmRes.json(); - script = llmData.response || ""; - // Extract code block if wrapped in markdown - const codeMatch = script.match(/```(?:python)?\n([\s\S]*?)```/); - if (codeMatch) script = codeMatch[1].trim(); - } else { - const errText = await llmRes.text().catch(() => ""); - console.error(`[blender-gen] Ollama ${llmRes.status}: ${errText}`); - } +The script should: +- Import bpy +- Clear the default scene (delete all default objects) +- Create the described objects with materials and colors +- Set up basic lighting (sun + area light) and camera positioned to frame the scene +- Render to /tmp/render.png at 1024x1024 + +Output ONLY the Python code, no explanations or comments outside the code.`); + + const text = result.response.text(); + const codeMatch = text.match(/```(?:python)?\n([\s\S]*?)```/); + script = codeMatch ? codeMatch[1].trim() : text.trim(); } catch (e) { - console.error("[blender-gen] LLM error:", e); + console.error("[blender-gen] Gemini error:", e); } if (!script) { - return c.json({ error: "Failed to generate Blender script — is Ollama running?" }, 502); + return c.json({ error: "Failed to generate Blender script" }, 502); } // Step 2: Execute on RunPod (headless Blender) — optional