← back to Dw Design Bridge
fix: progressive-truncation JSON parser handles qwen3 trailing-junk responses
f660c5cdb2542676edea3df3609403744e195eaa · 2026-05-07 23:37:40 -0700 · Steve
Files touched
A jobs/2026-05-08-06-30-39-4a5075.jsonA jobs/2026-05-08-06-30-39-4a5075/2.svgA jobs/2026-05-08-06-30-40-1d672a.jsonA jobs/2026-05-08-06-30-40-1d672a/0.svgA jobs/2026-05-08-06-30-40-1d672a/1.svgA jobs/2026-05-08-06-30-40-4f2a9b.jsonA jobs/2026-05-08-06-30-40-4f2a9b/0.svgM server.js
Diff
commit f660c5cdb2542676edea3df3609403744e195eaa
Author: Steve <steve@designerwallcoverings.com>
Date: Thu May 7 23:37:40 2026 -0700
fix: progressive-truncation JSON parser handles qwen3 trailing-junk responses
---
jobs/2026-05-08-06-30-39-4a5075.json | 52 ++++++++++++++++++++++++
jobs/2026-05-08-06-30-39-4a5075/2.svg | 15 +++++++
jobs/2026-05-08-06-30-40-1d672a.json | 74 +++++++++++++++++++++++++++++++++++
jobs/2026-05-08-06-30-40-1d672a/0.svg | 9 +++++
jobs/2026-05-08-06-30-40-1d672a/1.svg | 9 +++++
jobs/2026-05-08-06-30-40-4f2a9b.json | 52 ++++++++++++++++++++++++
jobs/2026-05-08-06-30-40-4f2a9b/0.svg | 15 +++++++
server.js | 24 +++++++++---
8 files changed, 244 insertions(+), 6 deletions(-)
diff --git a/jobs/2026-05-08-06-30-39-4a5075.json b/jobs/2026-05-08-06-30-39-4a5075.json
new file mode 100644
index 0000000..6e0c7f7
--- /dev/null
+++ b/jobs/2026-05-08-06-30-39-4a5075.json
@@ -0,0 +1,52 @@
+{
+ "id": "2026-05-08-06-30-39-4a5075",
+ "brief": "Hotel lobby feature wall · trade-grade · jewel-toned · botanical large-scale · evening dramatic · class A vinyl",
+ "count": 3,
+ "status": "done",
+ "created_at": "2026-05-08T06:30:39.640Z",
+ "progress": "3/3",
+ "designs": [
+ {
+ "idx": 0,
+ "ok": false,
+ "error": "no JSON found in response",
+ "generated_at": "2026-05-08T06:32:16.195Z"
+ },
+ {
+ "idx": 1,
+ "ok": false,
+ "error": "no JSON found in response",
+ "generated_at": "2026-05-08T06:34:05.929Z"
+ },
+ {
+ "idx": 2,
+ "ok": true,
+ "design": {
+ "name": "Nocturne Bloom",
+ "pattern_concept": "Majestic night-blooming flowers in dramatic shadow play",
+ "palette": [
+ "#1B4D3E",
+ "#0F3B6B",
+ "#5D2C7C",
+ "#C5A976"
+ ],
+ "scale": "large",
+ "repeat": "drop match 24in vertical",
+ "material_pairing": "silk",
+ "style_tags": [
+ "botanical",
+ "dramatic",
+ "luxury"
+ ],
+ "use_case": "hotel lobby feature wall",
+ "marketing_copy": "Opulent botanical drama in jewel tones, perfect for luxury hotel lobbies",
+ "trade_grade": "class A",
+ "price_band": "$$$$"
+ },
+ "preview_url": "/jobs/2026-05-08-06-30-39-4a5075/2.svg",
+ "generated_at": "2026-05-08T06:35:49.394Z"
+ }
+ ],
+ "started_at": "2026-05-08T06:30:39.646Z",
+ "finished_at": "2026-05-08T06:35:49.396Z"
+}
\ No newline at end of file
diff --git a/jobs/2026-05-08-06-30-39-4a5075/2.svg b/jobs/2026-05-08-06-30-39-4a5075/2.svg
new file mode 100644
index 0000000..47e55aa
--- /dev/null
+++ b/jobs/2026-05-08-06-30-39-4a5075/2.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 440 440" width="100%" height="auto" preserveAspectRatio="xMidYMid slice">
+ <defs>
+ <pattern id="p2026-05-08-06-30-39-4a5075-2" x="0" y="0" width="220" height="220" patternUnits="userSpaceOnUse">
+ <rect width="220" height="220" fill="#1B4D3E"/>
+
+ <ellipse cx="110" cy="70.4" rx="30.800000000000004" ry="70.4" fill="#0F3B6B" opacity="0.85" transform="rotate(-15 110 110)"/>
+ <ellipse cx="110" cy="70.4" rx="30.800000000000004" ry="70.4" fill="#5D2C7C" opacity="0.7" transform="rotate(20 110 110)"/>
+ <circle cx="110" cy="149.6" r="13.2" fill="#C5A976"/>
+ <path d="M 110 121 Q 70.4 92.4 39.599999999999994 149.6" stroke="#0F3B6B" stroke-width="2" fill="none"/>
+ <path d="M 110 121 Q 149.6 92.4 180.4 149.6" stroke="#0F3B6B" stroke-width="2" fill="none"/>
+
+ </pattern>
+ </defs>
+ <rect width="440" height="440" fill="url(#p2026-05-08-06-30-39-4a5075-2)"/>
+ </svg>
\ No newline at end of file
diff --git a/jobs/2026-05-08-06-30-40-1d672a.json b/jobs/2026-05-08-06-30-40-1d672a.json
new file mode 100644
index 0000000..38612af
--- /dev/null
+++ b/jobs/2026-05-08-06-30-40-1d672a.json
@@ -0,0 +1,74 @@
+{
+ "id": "2026-05-08-06-30-40-1d672a",
+ "brief": "Restaurant private dining room · moody · art-deco geometric · gilded accents · silk-luxe",
+ "count": 3,
+ "status": "done",
+ "created_at": "2026-05-08T06:30:40.096Z",
+ "progress": "3/3",
+ "designs": [
+ {
+ "idx": 0,
+ "ok": true,
+ "design": {
+ "name": "Velvet Noir Geometry",
+ "pattern_concept": "Bold art-deco geometric shapes in moody hues with gold foil accents on silk-luxury base",
+ "palette": [
+ "#0A1A2F",
+ "#4B0032",
+ "#D4AF37",
+ "#1A1A1A"
+ ],
+ "scale": "large",
+ "repeat": "drop match 24in vertical",
+ "material_pairing": "silk",
+ "style_tags": [
+ "art-deco",
+ "moody",
+ "luxury"
+ ],
+ "use_case": "Restaurant private dining room feature wall",
+ "marketing_copy": "Opulent art-deco geometrics with gilded silk accents for a moody, luxurious dining experience",
+ "trade_grade": "class A",
+ "price_band": "$$$$"
+ },
+ "preview_url": "/jobs/2026-05-08-06-30-40-1d672a/0.svg",
+ "generated_at": "2026-05-08T06:31:47.341Z"
+ },
+ {
+ "idx": 1,
+ "ok": true,
+ "design": {
+ "name": "Velvet Eclipse",
+ "pattern_concept": "Bold art-deco geometric motifs with gilded leaf accents on moody silk-luxe ground",
+ "palette": [
+ "#0A1A2F",
+ "#2E1B3B",
+ "#C9A05C",
+ "#FFFFFF"
+ ],
+ "scale": "large",
+ "repeat": "drop match 24in vertical",
+ "material_pairing": "silk",
+ "style_tags": [
+ "art deco",
+ "moody",
+ "luxury"
+ ],
+ "use_case": "restaurant private dining room feature wall",
+ "marketing_copy": "Opulent art deco geometrics with gilded silk elegance, casting a moody allure in intimate spaces",
+ "trade_grade": "class A",
+ "price_band": "$$$"
+ },
+ "preview_url": "/jobs/2026-05-08-06-30-40-1d672a/1.svg",
+ "generated_at": "2026-05-08T06:33:34.579Z"
+ },
+ {
+ "idx": 2,
+ "ok": false,
+ "error": "no JSON found in response",
+ "generated_at": "2026-05-08T06:35:19.842Z"
+ }
+ ],
+ "started_at": "2026-05-08T06:30:40.102Z",
+ "finished_at": "2026-05-08T06:35:19.858Z"
+}
\ No newline at end of file
diff --git a/jobs/2026-05-08-06-30-40-1d672a/0.svg b/jobs/2026-05-08-06-30-40-1d672a/0.svg
new file mode 100644
index 0000000..c7d6216
--- /dev/null
+++ b/jobs/2026-05-08-06-30-40-1d672a/0.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 440 440" width="100%" height="auto" preserveAspectRatio="xMidYMid slice">
+ <defs>
+ <pattern id="p2026-05-08-06-30-40-1d672a-0" x="0" y="0" width="220" height="220" patternUnits="userSpaceOnUse">
+ <rect width="220" height="220" fill="#0A1A2F"/>
+ <line x1="110" y1="220" x2="-44.0" y2="220.0" stroke="#1A1A1A" stroke-width="2"/><line x1="110" y1="220" x2="-32.3" y2="161.1" stroke="#4B0032" stroke-width="2"/><line x1="110" y1="220" x2="1.1" y2="111.1" stroke="#1A1A1A" stroke-width="2"/><line x1="110" y1="220" x2="51.1" y2="77.7" stroke="#4B0032" stroke-width="2"/><line x1="110" y1="220" x2="110.0" y2="66.0" stroke="#1A1A1A" stroke-width="2"/><line x1="110" y1="220" x2="168.9" y2="77.7" stroke="#4B0032" stroke-width="2"/><line x1="110" y1="220" x2="218.9" y2="111.1" stroke="#1A1A1A" stroke-width="2"/><line x1="110" y1="220" x2="252.3" y2="161.1" stroke="#4B0032" stroke-width="2"/><line x1="110" y1="220" x2="264.0" y2="220.0" stroke="#1A1A1A" stroke-width="2"/><circle cx="110" cy="220" r="26.4" fill="#D4AF37"/>
+ </pattern>
+ </defs>
+ <rect width="440" height="440" fill="url(#p2026-05-08-06-30-40-1d672a-0)"/>
+ </svg>
\ No newline at end of file
diff --git a/jobs/2026-05-08-06-30-40-1d672a/1.svg b/jobs/2026-05-08-06-30-40-1d672a/1.svg
new file mode 100644
index 0000000..81ebeac
--- /dev/null
+++ b/jobs/2026-05-08-06-30-40-1d672a/1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 440 440" width="100%" height="auto" preserveAspectRatio="xMidYMid slice">
+ <defs>
+ <pattern id="p2026-05-08-06-30-40-1d672a-1" x="0" y="0" width="220" height="220" patternUnits="userSpaceOnUse">
+ <rect width="220" height="220" fill="#0A1A2F"/>
+ <line x1="110" y1="220" x2="-44.0" y2="220.0" stroke="#FFFFFF" stroke-width="2"/><line x1="110" y1="220" x2="-32.3" y2="161.1" stroke="#2E1B3B" stroke-width="2"/><line x1="110" y1="220" x2="1.1" y2="111.1" stroke="#FFFFFF" stroke-width="2"/><line x1="110" y1="220" x2="51.1" y2="77.7" stroke="#2E1B3B" stroke-width="2"/><line x1="110" y1="220" x2="110.0" y2="66.0" stroke="#FFFFFF" stroke-width="2"/><line x1="110" y1="220" x2="168.9" y2="77.7" stroke="#2E1B3B" stroke-width="2"/><line x1="110" y1="220" x2="218.9" y2="111.1" stroke="#FFFFFF" stroke-width="2"/><line x1="110" y1="220" x2="252.3" y2="161.1" stroke="#2E1B3B" stroke-width="2"/><line x1="110" y1="220" x2="264.0" y2="220.0" stroke="#FFFFFF" stroke-width="2"/><circle cx="110" cy="220" r="26.4" fill="#C9A05C"/>
+ </pattern>
+ </defs>
+ <rect width="440" height="440" fill="url(#p2026-05-08-06-30-40-1d672a-1)"/>
+ </svg>
\ No newline at end of file
diff --git a/jobs/2026-05-08-06-30-40-4f2a9b.json b/jobs/2026-05-08-06-30-40-4f2a9b.json
new file mode 100644
index 0000000..f4520e2
--- /dev/null
+++ b/jobs/2026-05-08-06-30-40-4f2a9b.json
@@ -0,0 +1,52 @@
+{
+ "id": "2026-05-08-06-30-40-4f2a9b",
+ "brief": "Modern apartment bedroom · pastel · peel-and-stick · botanical small-scale · serene",
+ "count": 3,
+ "status": "done",
+ "created_at": "2026-05-08T06:30:40.516Z",
+ "progress": "3/3",
+ "designs": [
+ {
+ "idx": 0,
+ "ok": true,
+ "design": {
+ "name": "Whispering Ferns",
+ "pattern_concept": "Delicate botanical ferns in soft, flowing lines with minimalistic pastel shading",
+ "palette": [
+ "#D0E8E0",
+ "#E6D3D3",
+ "#F4D3E5",
+ "#F0E6D2"
+ ],
+ "scale": "small",
+ "repeat": "drop match 21in vertical",
+ "material_pairing": "vinyl",
+ "style_tags": [
+ "botanical",
+ "serene",
+ "modern"
+ ],
+ "use_case": "Modern apartment bedroom accent wall",
+ "marketing_copy": "Subtle botanical elegance in peel-and-stick pastel hues for tranquil spaces",
+ "trade_grade": "residential",
+ "price_band": "$$"
+ },
+ "preview_url": "/jobs/2026-05-08-06-30-40-4f2a9b/0.svg",
+ "generated_at": "2026-05-08T06:31:25.749Z"
+ },
+ {
+ "idx": 1,
+ "ok": false,
+ "error": "no JSON found in response",
+ "generated_at": "2026-05-08T06:33:12.623Z"
+ },
+ {
+ "idx": 2,
+ "ok": false,
+ "error": "no JSON found in response",
+ "generated_at": "2026-05-08T06:34:50.591Z"
+ }
+ ],
+ "started_at": "2026-05-08T06:30:40.577Z",
+ "finished_at": "2026-05-08T06:34:50.599Z"
+}
\ No newline at end of file
diff --git a/jobs/2026-05-08-06-30-40-4f2a9b/0.svg b/jobs/2026-05-08-06-30-40-4f2a9b/0.svg
new file mode 100644
index 0000000..b689c14
--- /dev/null
+++ b/jobs/2026-05-08-06-30-40-4f2a9b/0.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160" width="100%" height="auto" preserveAspectRatio="xMidYMid slice">
+ <defs>
+ <pattern id="p2026-05-08-06-30-40-4f2a9b-0" x="0" y="0" width="80" height="80" patternUnits="userSpaceOnUse">
+ <rect width="80" height="80" fill="#D0E8E0"/>
+
+ <ellipse cx="40" cy="25.6" rx="11.200000000000001" ry="25.6" fill="#E6D3D3" opacity="0.85" transform="rotate(-15 40 40)"/>
+ <ellipse cx="40" cy="25.6" rx="11.200000000000001" ry="25.6" fill="#F4D3E5" opacity="0.7" transform="rotate(20 40 40)"/>
+ <circle cx="40" cy="54.4" r="4.8" fill="#F0E6D2"/>
+ <path d="M 40 44 Q 25.6 33.6 14.399999999999999 54.4" stroke="#E6D3D3" stroke-width="2" fill="none"/>
+ <path d="M 40 44 Q 54.4 33.6 65.6 54.4" stroke="#E6D3D3" stroke-width="2" fill="none"/>
+
+ </pattern>
+ </defs>
+ <rect width="160" height="160" fill="url(#p2026-05-08-06-30-40-4f2a9b-0)"/>
+ </svg>
\ No newline at end of file
diff --git a/server.js b/server.js
index b1d2d22..bc9bece 100644
--- a/server.js
+++ b/server.js
@@ -71,14 +71,26 @@ Output STRICTLY one JSON object on a single line, no prose, no markdown fence:
if (!res.ok) throw new Error(`ollama http ${res.status}`);
const j = await res.json();
let raw = j.response || '';
- raw = raw.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
- const m = raw.match(/\{[\s\S]*\}/);
+ // Strip <think>…</think> (qwen3 reasoning leaks)
+ raw = raw.replace(/<think>[\s\S]*?<\/think>/g, '');
+ // Strip ```json fences if present
+ raw = raw.replace(/```(?:json)?\s*/g, '').replace(/```/g, '');
+ // Find FIRST balanced {...} object — try multiple strategies
+ // 1) greedy match { ... } that contains "name" or "pattern_concept"
+ let m = raw.match(/\{[^{}]*"name"[\s\S]*?\}/);
+ // 2) any first {...}
+ if (!m) m = raw.match(/\{[\s\S]*?\}(?=\s*$|\s*\n)/);
+ if (!m) m = raw.match(/\{[\s\S]*\}/);
if (!m) throw new Error('no JSON found in response');
- try {
- return JSON.parse(m[0]);
- } catch (e) {
- throw new Error(`JSON parse: ${e.message}`);
+ // Try parsing progressively — sometimes there's trailing junk after the JSON
+ let candidate = m[0];
+ let parsed = null;
+ for (let i = candidate.length; i > 50; i--) {
+ if (candidate[i - 1] !== '}') continue;
+ try { parsed = JSON.parse(candidate.slice(0, i)); break; } catch {}
}
+ if (!parsed) throw new Error('JSON parse failed across all candidates');
+ return parsed;
}
// SVG pattern generator — uses the design's palette + style_tags to render a tileable preview.
← c2c19fa feat: SVG pattern preview generator (botanical/deco/damask/p
·
back to Dw Design Bridge
·
gitignore: protect .env* from accidental commits per MEMORY 3741d2a →