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/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
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 @@
✏️ Drawfast
📐 FreeCAD
🔌 KiCAD
+ 🎨 Scribus
🎨 rSwag
📖 rPubs
@@ -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" });