fix(blender): make RunPod optional, fix script-only generation
RunPod was hard-gated — returned 503 when RUNPOD_API_KEY missing, blocking even LLM script generation. Now generates script via Ollama regardless, only attempts RunPod render if key is configured. Health check returns warnings (non-blocking) vs issues (blocking). Default model switched to qwen2.5:14b (available on server). Regex also handles non-python-tagged code blocks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
59d2cc9933
commit
2f3a4a13dc
|
|
@ -377,6 +377,8 @@ export class FolkBlender extends FolkShape {
|
||||||
if (!h.available && this.#generateBtn) {
|
if (!h.available && this.#generateBtn) {
|
||||||
this.#generateBtn.disabled = true;
|
this.#generateBtn.disabled = true;
|
||||||
this.#generateBtn.title = (h.issues || []).join(", ") || "Blender service unavailable";
|
this.#generateBtn.title = (h.issues || []).join(", ") || "Blender service unavailable";
|
||||||
|
} else if (h.warnings?.length && this.#generateBtn) {
|
||||||
|
this.#generateBtn.title = h.warnings.join(", ");
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
if (this.#generateBtn) {
|
if (this.#generateBtn) {
|
||||||
|
|
|
||||||
|
|
@ -1569,7 +1569,7 @@ const RUNPOD_API_KEY = process.env.RUNPOD_API_KEY || "";
|
||||||
|
|
||||||
app.get("/api/blender-gen/health", async (c) => {
|
app.get("/api/blender-gen/health", async (c) => {
|
||||||
const issues: string[] = [];
|
const issues: string[] = [];
|
||||||
if (!RUNPOD_API_KEY) issues.push("RUNPOD_API_KEY not configured");
|
const warnings: string[] = [];
|
||||||
const OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434";
|
const OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434";
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${OLLAMA_URL}/api/tags`, { signal: AbortSignal.timeout(3000) });
|
const res = await fetch(`${OLLAMA_URL}/api/tags`, { signal: AbortSignal.timeout(3000) });
|
||||||
|
|
@ -1577,12 +1577,11 @@ app.get("/api/blender-gen/health", async (c) => {
|
||||||
} catch {
|
} catch {
|
||||||
issues.push("Ollama unreachable");
|
issues.push("Ollama unreachable");
|
||||||
}
|
}
|
||||||
return c.json({ available: issues.length === 0, issues });
|
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) => {
|
app.post("/api/blender-gen", async (c) => {
|
||||||
if (!RUNPOD_API_KEY) return c.json({ error: "RUNPOD_API_KEY not configured" }, 503);
|
|
||||||
|
|
||||||
const { prompt } = await c.req.json();
|
const { prompt } = await c.req.json();
|
||||||
if (!prompt) return c.json({ error: "prompt required" }, 400);
|
if (!prompt) return c.json({ error: "prompt required" }, 400);
|
||||||
|
|
||||||
|
|
@ -1595,7 +1594,7 @@ app.post("/api/blender-gen", async (c) => {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: process.env.OLLAMA_MODEL || "llama3.1",
|
model: process.env.OLLAMA_MODEL || "qwen2.5:14b",
|
||||||
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.`,
|
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,
|
stream: false,
|
||||||
}),
|
}),
|
||||||
|
|
@ -1605,18 +1604,22 @@ app.post("/api/blender-gen", async (c) => {
|
||||||
const llmData = await llmRes.json();
|
const llmData = await llmRes.json();
|
||||||
script = llmData.response || "";
|
script = llmData.response || "";
|
||||||
// Extract code block if wrapped in markdown
|
// Extract code block if wrapped in markdown
|
||||||
const codeMatch = script.match(/```python\n([\s\S]*?)```/);
|
const codeMatch = script.match(/```(?:python)?\n([\s\S]*?)```/);
|
||||||
if (codeMatch) script = codeMatch[1];
|
if (codeMatch) script = codeMatch[1].trim();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[blender-gen] LLM error:", e);
|
console.error("[blender-gen] LLM error:", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!script) {
|
if (!script) {
|
||||||
return c.json({ error: "Failed to generate Blender script" }, 502);
|
return c.json({ error: "Failed to generate Blender script — is Ollama running?" }, 502);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Execute on RunPod (headless Blender) — optional
|
||||||
|
if (!RUNPOD_API_KEY) {
|
||||||
|
return c.json({ script, render_url: null, blend_url: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Execute on RunPod (headless Blender)
|
|
||||||
try {
|
try {
|
||||||
const runpodRes = await fetch("https://api.runpod.ai/v2/blender/runsync", {
|
const runpodRes = await fetch("https://api.runpod.ai/v2/blender/runsync", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -1633,7 +1636,6 @@ app.post("/api/blender-gen", async (c) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!runpodRes.ok) {
|
if (!runpodRes.ok) {
|
||||||
// Return just the script if RunPod fails
|
|
||||||
return c.json({ script, error_detail: "RunPod execution failed" });
|
return c.json({ script, error_detail: "RunPod execution failed" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1644,7 +1646,6 @@ app.post("/api/blender-gen", async (c) => {
|
||||||
blend_url: runpodData.output?.blend_url || null,
|
blend_url: runpodData.output?.blend_url || null,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Return the script even if RunPod is unavailable
|
|
||||||
return c.json({ script, error_detail: "RunPod unavailable" });
|
return c.json({ script, error_detail: "RunPod unavailable" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue