[object Object]

← 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

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 →