saivivek6 commited on
Commit
16cd24d
Β·
1 Parent(s): 5cd2c94

Deploy: drop JSON-as-text blocks (final JSON-in-UI fix)

Browse files
vivek/backend/combined_prompt.py CHANGED
@@ -219,12 +219,27 @@ def _chart_has_data(chart: dict[str, Any]) -> bool:
219
  _NUMERIC_ARRAY_RE = re.compile(r"^\s*\[\s*-?\d+(\.\d+)?(\s*,\s*-?\d+(\.\d+)?)*\s*\]\s*$")
220
 
221
 
 
 
 
 
 
 
 
222
  def _block_is_renderable(b: dict[str, Any]) -> bool:
223
  t = str(b.get("type") or "").lower()
224
  if t == "text":
225
  content = str(b.get("content") or "").strip()
226
- # Drop bare numeric arrays (e.g. tic-tac-toe win lines "[0,1,2]") β€” that's not prose.
227
- return bool(content) and not _NUMERIC_ARRAY_RE.match(content)
 
 
 
 
 
 
 
 
228
  if t == "kpi_row":
229
  items = b.get("items")
230
  return isinstance(items, list) and any(
 
219
  _NUMERIC_ARRAY_RE = re.compile(r"^\s*\[\s*-?\d+(\.\d+)?(\s*,\s*-?\d+(\.\d+)?)*\s*\]\s*$")
220
 
221
 
222
+ def _looks_like_json(s: str) -> bool:
223
+ try:
224
+ return isinstance(json.loads(s), (dict, list))
225
+ except Exception:
226
+ return s.strip()[:1] in "{[" # truncated JSON-ish
227
+
228
+
229
  def _block_is_renderable(b: dict[str, Any]) -> bool:
230
  t = str(b.get("type") or "").lower()
231
  if t == "text":
232
  content = str(b.get("content") or "").strip()
233
+ if not content or _NUMERIC_ARRAY_RE.match(content):
234
+ return False # bare numeric arrays (e.g. tic-tac-toe "[0,1,2]") are not prose
235
+ # Drop text whose content is actually JSON (a block/layout the model stuffed in) β€”
236
+ # it must never render as raw text in the UI.
237
+ if content[:1] in "{[" and (
238
+ re.search(r'"(?:type|layout|items|tone|label|series|chart)"\s*:', content)
239
+ or _looks_like_json(content)
240
+ ):
241
+ return False
242
+ return True
243
  if t == "kpi_row":
244
  items = b.get("items")
245
  return isinstance(items, list) and any(
vivek/frontend-vue/dist/assets/{ActionRow-BcmZQovL.js β†’ ActionRow-ByJHOpPb.js} RENAMED
@@ -1 +1 @@
1
- import{G as e,M as t,P as n,f as r,h as i,i as a,jt as o,u as s,v as c}from"./runtime-core.esm-bundler-olIhRSij.js";import{tr as l}from"./index-1ugYrWkO.js";var u={class:`flex flex-wrap gap-2`},d=c({__name:`ActionRow`,props:{block:{}},emits:[`action`],setup(c,{emit:d}){let f=d;function p(e){let t=String(e.label||e.intent||``).trim();t&&f(`action`,t)}return(d,f)=>(t(),r(`div`,u,[(t(!0),r(a,null,n(c.block.buttons||[],(n,r)=>(t(),s(l,{key:r,type:`button`,variant:`outline`,size:`sm`,title:n.intent?`Ask: ${n.label||n.intent}`:void 0,onClick:e=>p(n)},{default:e(()=>[i(o(n.label||`Action`),1)]),_:2},1032,[`title`,`onClick`]))),128))]))}});export{d as default};
 
1
+ import{G as e,M as t,P as n,f as r,h as i,i as a,jt as o,u as s,v as c}from"./runtime-core.esm-bundler-olIhRSij.js";import{tr as l}from"./index-0cd4ouHe.js";var u={class:`flex flex-wrap gap-2`},d=c({__name:`ActionRow`,props:{block:{}},emits:[`action`],setup(c,{emit:d}){let f=d;function p(e){let t=String(e.label||e.intent||``).trim();t&&f(`action`,t)}return(d,f)=>(t(),r(`div`,u,[(t(!0),r(a,null,n(c.block.buttons||[],(n,r)=>(t(),s(l,{key:r,type:`button`,variant:`outline`,size:`sm`,title:n.intent?`Ask: ${n.label||n.intent}`:void 0,onClick:e=>p(n)},{default:e(()=>[i(o(n.label||`Action`),1)]),_:2},1032,[`title`,`onClick`]))),128))]))}});export{d as default};
vivek/frontend-vue/dist/assets/{ChartBlock-V9FzCExg.js β†’ ChartBlock-D02q0rey.js} RENAMED
The diff for this file is too large to render. See raw diff
 
vivek/frontend-vue/dist/assets/{index-1ugYrWkO.js β†’ index-0cd4ouHe.js} RENAMED
The diff for this file is too large to render. See raw diff
 
vivek/frontend-vue/dist/index.html CHANGED
@@ -5,7 +5,7 @@
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
  <title>frontend-vue</title>
8
- <script type="module" crossorigin src="/assets/index-1ugYrWkO.js"></script>
9
  <link rel="modulepreload" crossorigin href="/assets/runtime-core.esm-bundler-olIhRSij.js">
10
  <link rel="stylesheet" crossorigin href="/assets/index-yy7aehFb.css">
11
  </head>
 
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
  <title>frontend-vue</title>
8
+ <script type="module" crossorigin src="/assets/index-0cd4ouHe.js"></script>
9
  <link rel="modulepreload" crossorigin href="/assets/runtime-core.esm-bundler-olIhRSij.js">
10
  <link rel="stylesheet" crossorigin href="/assets/index-yy7aehFb.css">
11
  </head>
vivek/frontend-vue/src/components/WidgetRegistryRenderer.vue CHANGED
@@ -84,15 +84,33 @@ function salvageBlocks(s: string): Block[] {
84
  return out
85
  }
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  function finalizeBlocks(layout: unknown[]): Block[] {
88
  const normalized = layout.map((b) => normalizeWidgetBlock(b)) as Block[]
89
- // Drop bare numeric-array text blocks (e.g. tic-tac-toe win lines "[0,1,2]") β€” not real content.
90
- const NUMERIC_ARRAY_RE = /^\s*\[\s*-?\d+(\.\d+)?(\s*,\s*-?\d+(\.\d+)?)*\s*\]\s*$/
91
  return normalized.filter((b) => {
92
  const type = String(b.type || '').toLowerCase()
93
  // Never show an empty chart card β€” drop charts with no renderable data outright.
94
  if (type === 'chart' && !chartHasRenderableData((b as { chart?: any }).chart || {})) return false
95
- if (type === 'text' && NUMERIC_ARRAY_RE.test(String((b as { content?: string }).content ?? ''))) return false
 
 
 
 
96
  return true
97
  })
98
  }
 
84
  return out
85
  }
86
 
87
+ // A text block's content that is actually JSON (a block/layout the model stuffed into
88
+ // content) β€” must NEVER render as raw text. Detect and drop.
89
+ const NUMERIC_ARRAY_RE = /^\s*\[\s*-?\d+(\.\d+)?(\s*,\s*-?\d+(\.\d+)?)*\s*\]\s*$/
90
+ function textContentIsJson(content: string): boolean {
91
+ const t = String(content || '').trim()
92
+ if (!(t.startsWith('{') || t.startsWith('['))) return false
93
+ // looks like a widget block/array, or parses as a JSON object/array
94
+ if (/"(?:type|layout|items|tone|label|series|chart|value)"\s*:/.test(t)) return true
95
+ try {
96
+ const o = JSON.parse(t)
97
+ return o && typeof o === 'object'
98
+ } catch {
99
+ return /^[{[]/.test(t) // truncated JSON-ish β†’ still drop
100
+ }
101
+ }
102
+
103
  function finalizeBlocks(layout: unknown[]): Block[] {
104
  const normalized = layout.map((b) => normalizeWidgetBlock(b)) as Block[]
 
 
105
  return normalized.filter((b) => {
106
  const type = String(b.type || '').toLowerCase()
107
  // Never show an empty chart card β€” drop charts with no renderable data outright.
108
  if (type === 'chart' && !chartHasRenderableData((b as { chart?: any }).chart || {})) return false
109
+ if (type === 'text') {
110
+ const c = String((b as { content?: string }).content ?? '')
111
+ // Drop numeric-array junk AND any text block whose content is raw JSON.
112
+ if (NUMERIC_ARRAY_RE.test(c) || textContentIsJson(c)) return false
113
+ }
114
  return true
115
  })
116
  }