Spaces:
Running
Running
Deploy: drop JSON-as-text blocks (final JSON-in-UI fix)
Browse files- vivek/backend/combined_prompt.py +17 -2
- vivek/frontend-vue/dist/assets/{ActionRow-BcmZQovL.js β ActionRow-ByJHOpPb.js} +1 -1
- vivek/frontend-vue/dist/assets/{ChartBlock-V9FzCExg.js β ChartBlock-D02q0rey.js} +0 -0
- vivek/frontend-vue/dist/assets/{index-1ugYrWkO.js β index-0cd4ouHe.js} +0 -0
- vivek/frontend-vue/dist/index.html +1 -1
- vivek/frontend-vue/src/components/WidgetRegistryRenderer.vue +21 -3
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 |
-
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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-
|
|
|
|
| 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-
|
| 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'
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
}
|