@retrivora-ai/rag-engine 1.3.7 → 1.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1705,8 +1705,6 @@ When presenting structured data, statistics, or comparisons, decide if it is bes
1705
1705
  3. dataKeys MUST exactly match the property names in the data objects.
1706
1706
  4. data objects MUST be FLAT (no nested objects or arrays).
1707
1707
  5. DO NOT output naked JSON; it MUST be wrapped in \`\`\`chart ... \`\`\`.
1708
- 6. The UI HAS a built-in charting engine. NEVER provide Python, Plotly, or Matplotlib code.
1709
- 7. NEVER apologize or claim you cannot render charts. Simply use the \`\`\`chart\`\`\` block.
1710
1708
  \`\`\`chart
1711
1709
  {
1712
1710
  "type": "bar" | "line" | "pie",
@@ -2270,8 +2268,6 @@ When presenting structured data, statistics, or comparisons, decide if it is bes
2270
2268
  3. dataKeys MUST exactly match the property names in the data objects.
2271
2269
  4. data objects MUST be FLAT (no nested objects or arrays).
2272
2270
  5. DO NOT output naked JSON; it MUST be wrapped in \`\`\`chart ... \`\`\`.
2273
- 6. The UI HAS a built-in charting engine. NEVER provide Python, Plotly, or Matplotlib code.
2274
- 7. NEVER apologize or claim you cannot render charts. Simply use the \`\`\`chart\`\`\` block.
2275
2271
  \`\`\`chart
2276
2272
  {
2277
2273
  "type": "bar" | "line" | "pie",
@@ -3274,8 +3274,6 @@ When presenting structured data, statistics, or comparisons, decide if it is bes
3274
3274
  3. dataKeys MUST exactly match the property names in the data objects.
3275
3275
  4. data objects MUST be FLAT (no nested objects or arrays).
3276
3276
  5. DO NOT output naked JSON; it MUST be wrapped in \`\`\`chart ... \`\`\`.
3277
- 6. The UI HAS a built-in charting engine. NEVER provide Python, Plotly, or Matplotlib code.
3278
- 7. NEVER apologize or claim you cannot render charts. Simply use the \`\`\`chart\`\`\` block.
3279
3277
  \`\`\`chart
3280
3278
  {
3281
3279
  "type": "bar" | "line" | "pie",
@@ -3833,8 +3831,6 @@ When presenting structured data, statistics, or comparisons, decide if it is bes
3833
3831
  3. dataKeys MUST exactly match the property names in the data objects.
3834
3832
  4. data objects MUST be FLAT (no nested objects or arrays).
3835
3833
  5. DO NOT output naked JSON; it MUST be wrapped in \`\`\`chart ... \`\`\`.
3836
- 6. The UI HAS a built-in charting engine. NEVER provide Python, Plotly, or Matplotlib code.
3837
- 7. NEVER apologize or claim you cannot render charts. Simply use the \`\`\`chart\`\`\` block.
3838
3834
  \`\`\`chart
3839
3835
  {
3840
3836
  "type": "bar" | "line" | "pie",
@@ -9,7 +9,7 @@ import {
9
9
  sseFrame,
10
10
  sseMetaFrame,
11
11
  sseTextFrame
12
- } from "../chunk-NVXFI7HX.mjs";
12
+ } from "../chunk-FQ7OYZLF.mjs";
13
13
  import "../chunk-YLTMFW4M.mjs";
14
14
  import "../chunk-X4TOT24V.mjs";
15
15
  export {
package/dist/index.js CHANGED
@@ -320,10 +320,6 @@ function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6
320
320
  // src/components/MessageBubble.tsx
321
321
  function sanitizeJson(raw) {
322
322
  let s = raw.trim();
323
- const firstOpener = s.search(/[\{\[]/);
324
- if (firstOpener !== -1) {
325
- s = s.substring(firstOpener);
326
- }
327
323
  s = s.replace(
328
324
  /:\s*(-?\d+(?:\.\d+)?)\s*([+\-*/])\s*(-?\d+(?:\.\d+)?)/g,
329
325
  (_, a, op, b) => {
@@ -385,14 +381,65 @@ function resolveImage(data) {
385
381
  }
386
382
  return void 0;
387
383
  }
384
+ function normaliseChild(children) {
385
+ if (typeof children === "object" && children !== null && !Array.isArray(children) && !import_react5.default.isValidElement(children)) {
386
+ return JSON.stringify(children);
387
+ }
388
+ return children;
389
+ }
390
+ function DataTable({ config }) {
391
+ const keys = import_react5.default.useMemo(() => {
392
+ if (Array.isArray(config.columns) && config.columns.length) {
393
+ return config.columns;
394
+ }
395
+ if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
396
+ if (config.xAxisKey && !config.dataKeys.includes(config.xAxisKey)) {
397
+ return [config.xAxisKey, ...config.dataKeys];
398
+ }
399
+ return config.dataKeys;
400
+ }
401
+ if (config.data.length > 0) return Object.keys(config.data[0]);
402
+ return [];
403
+ }, [config]);
404
+ if (!config.data.length || !keys.length) {
405
+ return /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs text-slate-400 italic my-2" }, "No data to display.");
406
+ }
407
+ const formatCell = (val) => {
408
+ if (val === null || val === void 0) return "\u2014";
409
+ if (typeof val === "boolean") return val ? "\u2713" : "\u2717";
410
+ return String(val);
411
+ };
412
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden" }, config.title && /* @__PURE__ */ import_react5.default.createElement("div", { className: "px-4 py-2.5 bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs font-semibold text-slate-600 dark:text-white/70 uppercase tracking-wide" }, config.title)), /* @__PURE__ */ import_react5.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ import_react5.default.createElement("table", { className: "w-full text-left border-collapse min-w-[320px] text-sm" }, /* @__PURE__ */ import_react5.default.createElement("thead", { className: "bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ import_react5.default.createElement("tr", null, keys.map((k) => /* @__PURE__ */ import_react5.default.createElement(
413
+ "th",
414
+ {
415
+ key: k,
416
+ className: "px-4 py-3 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
417
+ },
418
+ k
419
+ )))), /* @__PURE__ */ import_react5.default.createElement("tbody", null, config.data.map((row, ri) => /* @__PURE__ */ import_react5.default.createElement(
420
+ "tr",
421
+ {
422
+ key: ri,
423
+ className: "border-b border-slate-100 dark:border-white/5 last:border-0 hover:bg-slate-50/60 dark:hover:bg-white/5 transition-colors"
424
+ },
425
+ keys.map((k) => /* @__PURE__ */ import_react5.default.createElement(
426
+ "td",
427
+ {
428
+ key: k,
429
+ className: "px-4 py-3 text-slate-600 dark:text-white/70 whitespace-nowrap"
430
+ },
431
+ formatCell(row[k])
432
+ ))
433
+ ))))), /* @__PURE__ */ import_react5.default.createElement("div", { className: "px-4 py-2 bg-slate-50 dark:bg-white/5 border-t border-slate-100 dark:border-white/5" }, /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-[11px] text-slate-400 dark:text-white/30" }, config.data.length, " row", config.data.length !== 1 ? "s" : "")));
434
+ }
388
435
  function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
389
436
  const result = import_react5.default.useMemo(() => {
390
437
  if (isStreaming) return { loading: true };
391
- if (!rawContent || rawContent === "undefined") return { error: "Empty chart config." };
438
+ if (!rawContent || rawContent === "undefined") return { error: "Empty visualization config." };
392
439
  try {
393
440
  const sanitized = sanitizeJson(rawContent);
394
- const config = JSON.parse(sanitized);
395
- return { config };
441
+ const config2 = JSON.parse(sanitized);
442
+ return { config: config2 };
396
443
  } catch (err) {
397
444
  const sanitized = sanitizeJson(rawContent);
398
445
  console.error("[ChartBlock] Parsing failed.\nError:", err, "\nSanitized:\n", sanitized);
@@ -400,12 +447,23 @@ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
400
447
  }
401
448
  }, [rawContent, isStreaming]);
402
449
  if ("loading" in result) {
403
- return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4 p-8 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex flex-col items-center justify-center gap-3 select-none" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-6 h-6 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs text-slate-500 font-medium animate-pulse" }, "Preparing data visualization..."));
450
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4 p-8 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex flex-col items-center justify-center gap-3 select-none" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-6 h-6 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs text-slate-500 font-medium animate-pulse" }, "Preparing data visualization\u2026"));
404
451
  }
405
452
  if ("error" in result) {
406
- return /* @__PURE__ */ import_react5.default.createElement("div", { className: "p-4 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-lg text-sm border border-red-100 dark:border-red-900/30" }, /* @__PURE__ */ import_react5.default.createElement("p", { className: "font-medium mb-1" }, "Failed to render chart"), /* @__PURE__ */ import_react5.default.createElement("p", { className: "opacity-70 text-xs" }, "The generated configuration is invalid. Check the console for details."));
453
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className: "p-4 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-lg text-sm border border-red-100 dark:border-red-900/30" }, /* @__PURE__ */ import_react5.default.createElement("p", { className: "font-medium mb-1" }, "Failed to render visualization"), /* @__PURE__ */ import_react5.default.createElement("p", { className: "opacity-70 text-xs" }, "The generated configuration is invalid. Check the console for details."));
454
+ }
455
+ const { config } = result;
456
+ if (config.type === "table") {
457
+ return /* @__PURE__ */ import_react5.default.createElement(DataTable, { config });
407
458
  }
408
- return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm" }, /* @__PURE__ */ import_react5.default.createElement(DynamicChart, { config: result.config, primaryColor, accentColor }));
459
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm" }, /* @__PURE__ */ import_react5.default.createElement(
460
+ DynamicChart,
461
+ {
462
+ config,
463
+ primaryColor,
464
+ accentColor
465
+ }
466
+ ));
409
467
  }
410
468
  function MessageBubble({
411
469
  message,
@@ -479,6 +537,10 @@ function MessageBubble({
479
537
  return false;
480
538
  });
481
539
  }, [productsFromSources, productsFromContent, message.content]);
540
+ const processedMarkdown = import_react5.default.useMemo(() => {
541
+ const raw = cleanContent || message.content;
542
+ return raw.replace(/([^\n])\n\|/g, "$1\n\n|");
543
+ }, [cleanContent, message.content]);
482
544
  const markdownComponents = import_react5.default.useMemo(
483
545
  () => ({
484
546
  table: (_a) => {
@@ -499,7 +561,7 @@ function MessageBubble({
499
561
  return /* @__PURE__ */ import_react5.default.createElement(
500
562
  "th",
501
563
  __spreadValues({
502
- className: "p-3 font-semibold text-slate-700 dark:text-white/90 first:rounded-tl-xl last:rounded-tr-xl"
564
+ className: "px-4 py-3 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
503
565
  }, props),
504
566
  normaliseChild(children)
505
567
  );
@@ -509,7 +571,7 @@ function MessageBubble({
509
571
  return /* @__PURE__ */ import_react5.default.createElement(
510
572
  "td",
511
573
  __spreadValues({
512
- className: "p-3 border-b border-slate-100 dark:border-white/5 last:border-0 text-slate-600 dark:text-white/70"
574
+ className: "px-4 py-3 border-b border-slate-100 dark:border-white/5 last:border-0 text-slate-600 dark:text-white/70 whitespace-nowrap"
513
575
  }, props),
514
576
  normaliseChild(children)
515
577
  );
@@ -555,7 +617,7 @@ function MessageBubble({
555
617
  className: `relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser ? "text-white rounded-tr-sm" : "bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm"}`,
556
618
  style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
557
619
  },
558
- isStreaming && !message.content ? /* @__PURE__ */ import_react5.default.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ import_react5.default.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ import_react5.default.createElement(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default], components: markdownComponents }, cleanContent || message.content), isStreaming && message.content && /* @__PURE__ */ import_react5.default.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
620
+ isStreaming && !message.content ? /* @__PURE__ */ import_react5.default.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ import_react5.default.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ import_react5.default.createElement(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default], components: markdownComponents }, processedMarkdown), isStreaming && message.content && /* @__PURE__ */ import_react5.default.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
559
621
  ), !isUser && allProducts.length > 0 && /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ import_react5.default.createElement(
560
622
  ProductCarousel,
561
623
  {
@@ -576,12 +638,6 @@ function MessageBubble({
576
638
  " used"
577
639
  ), showSources && /* @__PURE__ */ import_react5.default.createElement("div", { className: "mt-2 flex flex-col gap-2" }, sources.map((src, i) => /* @__PURE__ */ import_react5.default.createElement(SourceCard, { key: src.id, source: src, index: i }))))));
578
640
  }
579
- function normaliseChild(children) {
580
- if (typeof children === "object" && children !== null && !Array.isArray(children) && !import_react5.default.isValidElement(children)) {
581
- return JSON.stringify(children);
582
- }
583
- return children;
584
- }
585
641
 
586
642
  // src/components/ConfigProvider.tsx
587
643
  var import_react6 = __toESM(require("react"));
package/dist/index.mjs CHANGED
@@ -283,10 +283,6 @@ function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6
283
283
  // src/components/MessageBubble.tsx
284
284
  function sanitizeJson(raw) {
285
285
  let s = raw.trim();
286
- const firstOpener = s.search(/[\{\[]/);
287
- if (firstOpener !== -1) {
288
- s = s.substring(firstOpener);
289
- }
290
286
  s = s.replace(
291
287
  /:\s*(-?\d+(?:\.\d+)?)\s*([+\-*/])\s*(-?\d+(?:\.\d+)?)/g,
292
288
  (_, a, op, b) => {
@@ -348,14 +344,65 @@ function resolveImage(data) {
348
344
  }
349
345
  return void 0;
350
346
  }
347
+ function normaliseChild(children) {
348
+ if (typeof children === "object" && children !== null && !Array.isArray(children) && !React5.isValidElement(children)) {
349
+ return JSON.stringify(children);
350
+ }
351
+ return children;
352
+ }
353
+ function DataTable({ config }) {
354
+ const keys = React5.useMemo(() => {
355
+ if (Array.isArray(config.columns) && config.columns.length) {
356
+ return config.columns;
357
+ }
358
+ if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
359
+ if (config.xAxisKey && !config.dataKeys.includes(config.xAxisKey)) {
360
+ return [config.xAxisKey, ...config.dataKeys];
361
+ }
362
+ return config.dataKeys;
363
+ }
364
+ if (config.data.length > 0) return Object.keys(config.data[0]);
365
+ return [];
366
+ }, [config]);
367
+ if (!config.data.length || !keys.length) {
368
+ return /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-400 italic my-2" }, "No data to display.");
369
+ }
370
+ const formatCell = (val) => {
371
+ if (val === null || val === void 0) return "\u2014";
372
+ if (typeof val === "boolean") return val ? "\u2713" : "\u2717";
373
+ return String(val);
374
+ };
375
+ return /* @__PURE__ */ React5.createElement("div", { className: "my-4 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden" }, config.title && /* @__PURE__ */ React5.createElement("div", { className: "px-4 py-2.5 bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ React5.createElement("p", { className: "text-xs font-semibold text-slate-600 dark:text-white/70 uppercase tracking-wide" }, config.title)), /* @__PURE__ */ React5.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React5.createElement("table", { className: "w-full text-left border-collapse min-w-[320px] text-sm" }, /* @__PURE__ */ React5.createElement("thead", { className: "bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ React5.createElement("tr", null, keys.map((k) => /* @__PURE__ */ React5.createElement(
376
+ "th",
377
+ {
378
+ key: k,
379
+ className: "px-4 py-3 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
380
+ },
381
+ k
382
+ )))), /* @__PURE__ */ React5.createElement("tbody", null, config.data.map((row, ri) => /* @__PURE__ */ React5.createElement(
383
+ "tr",
384
+ {
385
+ key: ri,
386
+ className: "border-b border-slate-100 dark:border-white/5 last:border-0 hover:bg-slate-50/60 dark:hover:bg-white/5 transition-colors"
387
+ },
388
+ keys.map((k) => /* @__PURE__ */ React5.createElement(
389
+ "td",
390
+ {
391
+ key: k,
392
+ className: "px-4 py-3 text-slate-600 dark:text-white/70 whitespace-nowrap"
393
+ },
394
+ formatCell(row[k])
395
+ ))
396
+ ))))), /* @__PURE__ */ React5.createElement("div", { className: "px-4 py-2 bg-slate-50 dark:bg-white/5 border-t border-slate-100 dark:border-white/5" }, /* @__PURE__ */ React5.createElement("p", { className: "text-[11px] text-slate-400 dark:text-white/30" }, config.data.length, " row", config.data.length !== 1 ? "s" : "")));
397
+ }
351
398
  function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
352
399
  const result = React5.useMemo(() => {
353
400
  if (isStreaming) return { loading: true };
354
- if (!rawContent || rawContent === "undefined") return { error: "Empty chart config." };
401
+ if (!rawContent || rawContent === "undefined") return { error: "Empty visualization config." };
355
402
  try {
356
403
  const sanitized = sanitizeJson(rawContent);
357
- const config = JSON.parse(sanitized);
358
- return { config };
404
+ const config2 = JSON.parse(sanitized);
405
+ return { config: config2 };
359
406
  } catch (err) {
360
407
  const sanitized = sanitizeJson(rawContent);
361
408
  console.error("[ChartBlock] Parsing failed.\nError:", err, "\nSanitized:\n", sanitized);
@@ -363,12 +410,23 @@ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
363
410
  }
364
411
  }, [rawContent, isStreaming]);
365
412
  if ("loading" in result) {
366
- return /* @__PURE__ */ React5.createElement("div", { className: "my-4 p-8 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex flex-col items-center justify-center gap-3 select-none" }, /* @__PURE__ */ React5.createElement("div", { className: "w-6 h-6 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-500 font-medium animate-pulse" }, "Preparing data visualization..."));
413
+ return /* @__PURE__ */ React5.createElement("div", { className: "my-4 p-8 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex flex-col items-center justify-center gap-3 select-none" }, /* @__PURE__ */ React5.createElement("div", { className: "w-6 h-6 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-500 font-medium animate-pulse" }, "Preparing data visualization\u2026"));
367
414
  }
368
415
  if ("error" in result) {
369
- return /* @__PURE__ */ React5.createElement("div", { className: "p-4 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-lg text-sm border border-red-100 dark:border-red-900/30" }, /* @__PURE__ */ React5.createElement("p", { className: "font-medium mb-1" }, "Failed to render chart"), /* @__PURE__ */ React5.createElement("p", { className: "opacity-70 text-xs" }, "The generated configuration is invalid. Check the console for details."));
416
+ return /* @__PURE__ */ React5.createElement("div", { className: "p-4 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-lg text-sm border border-red-100 dark:border-red-900/30" }, /* @__PURE__ */ React5.createElement("p", { className: "font-medium mb-1" }, "Failed to render visualization"), /* @__PURE__ */ React5.createElement("p", { className: "opacity-70 text-xs" }, "The generated configuration is invalid. Check the console for details."));
417
+ }
418
+ const { config } = result;
419
+ if (config.type === "table") {
420
+ return /* @__PURE__ */ React5.createElement(DataTable, { config });
370
421
  }
371
- return /* @__PURE__ */ React5.createElement("div", { className: "my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm" }, /* @__PURE__ */ React5.createElement(DynamicChart, { config: result.config, primaryColor, accentColor }));
422
+ return /* @__PURE__ */ React5.createElement("div", { className: "my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm" }, /* @__PURE__ */ React5.createElement(
423
+ DynamicChart,
424
+ {
425
+ config,
426
+ primaryColor,
427
+ accentColor
428
+ }
429
+ ));
372
430
  }
373
431
  function MessageBubble({
374
432
  message,
@@ -442,6 +500,10 @@ function MessageBubble({
442
500
  return false;
443
501
  });
444
502
  }, [productsFromSources, productsFromContent, message.content]);
503
+ const processedMarkdown = React5.useMemo(() => {
504
+ const raw = cleanContent || message.content;
505
+ return raw.replace(/([^\n])\n\|/g, "$1\n\n|");
506
+ }, [cleanContent, message.content]);
445
507
  const markdownComponents = React5.useMemo(
446
508
  () => ({
447
509
  table: (_a) => {
@@ -462,7 +524,7 @@ function MessageBubble({
462
524
  return /* @__PURE__ */ React5.createElement(
463
525
  "th",
464
526
  __spreadValues({
465
- className: "p-3 font-semibold text-slate-700 dark:text-white/90 first:rounded-tl-xl last:rounded-tr-xl"
527
+ className: "px-4 py-3 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
466
528
  }, props),
467
529
  normaliseChild(children)
468
530
  );
@@ -472,7 +534,7 @@ function MessageBubble({
472
534
  return /* @__PURE__ */ React5.createElement(
473
535
  "td",
474
536
  __spreadValues({
475
- className: "p-3 border-b border-slate-100 dark:border-white/5 last:border-0 text-slate-600 dark:text-white/70"
537
+ className: "px-4 py-3 border-b border-slate-100 dark:border-white/5 last:border-0 text-slate-600 dark:text-white/70 whitespace-nowrap"
476
538
  }, props),
477
539
  normaliseChild(children)
478
540
  );
@@ -518,7 +580,7 @@ function MessageBubble({
518
580
  className: `relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser ? "text-white rounded-tr-sm" : "bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm"}`,
519
581
  style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
520
582
  },
521
- isStreaming && !message.content ? /* @__PURE__ */ React5.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ React5.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ React5.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm], components: markdownComponents }, cleanContent || message.content), isStreaming && message.content && /* @__PURE__ */ React5.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
583
+ isStreaming && !message.content ? /* @__PURE__ */ React5.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ React5.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ React5.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm], components: markdownComponents }, processedMarkdown), isStreaming && message.content && /* @__PURE__ */ React5.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
522
584
  ), !isUser && allProducts.length > 0 && /* @__PURE__ */ React5.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ React5.createElement(
523
585
  ProductCarousel,
524
586
  {
@@ -539,12 +601,6 @@ function MessageBubble({
539
601
  " used"
540
602
  ), showSources && /* @__PURE__ */ React5.createElement("div", { className: "mt-2 flex flex-col gap-2" }, sources.map((src, i) => /* @__PURE__ */ React5.createElement(SourceCard, { key: src.id, source: src, index: i }))))));
541
603
  }
542
- function normaliseChild(children) {
543
- if (typeof children === "object" && children !== null && !Array.isArray(children) && !React5.isValidElement(children)) {
544
- return JSON.stringify(children);
545
- }
546
- return children;
547
- }
548
604
 
549
605
  // src/components/ConfigProvider.tsx
550
606
  import React6, { createContext, useContext } from "react";
package/dist/server.js CHANGED
@@ -3359,8 +3359,6 @@ When presenting structured data, statistics, or comparisons, decide if it is bes
3359
3359
  3. dataKeys MUST exactly match the property names in the data objects.
3360
3360
  4. data objects MUST be FLAT (no nested objects or arrays).
3361
3361
  5. DO NOT output naked JSON; it MUST be wrapped in \`\`\`chart ... \`\`\`.
3362
- 6. The UI HAS a built-in charting engine. NEVER provide Python, Plotly, or Matplotlib code.
3363
- 7. NEVER apologize or claim you cannot render charts. Simply use the \`\`\`chart\`\`\` block.
3364
3362
  \`\`\`chart
3365
3363
  {
3366
3364
  "type": "bar" | "line" | "pie",
@@ -3924,8 +3922,6 @@ When presenting structured data, statistics, or comparisons, decide if it is bes
3924
3922
  3. dataKeys MUST exactly match the property names in the data objects.
3925
3923
  4. data objects MUST be FLAT (no nested objects or arrays).
3926
3924
  5. DO NOT output naked JSON; it MUST be wrapped in \`\`\`chart ... \`\`\`.
3927
- 6. The UI HAS a built-in charting engine. NEVER provide Python, Plotly, or Matplotlib code.
3928
- 7. NEVER apologize or claim you cannot render charts. Simply use the \`\`\`chart\`\`\` block.
3929
3925
  \`\`\`chart
3930
3926
  {
3931
3927
  "type": "bar" | "line" | "pie",
package/dist/server.mjs CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  sseFrame,
40
40
  sseMetaFrame,
41
41
  sseTextFrame
42
- } from "./chunk-NVXFI7HX.mjs";
42
+ } from "./chunk-FQ7OYZLF.mjs";
43
43
  import "./chunk-YLTMFW4M.mjs";
44
44
  import {
45
45
  PineconeProvider
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
4
4
  "description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
5
5
  "author": "Abhinav Alkuchi",
6
6
  "license": "MIT",
@@ -15,12 +15,6 @@ import { DynamicChart, ChartConfig } from './DynamicChart';
15
15
  function sanitizeJson(raw: string): string {
16
16
  let s = raw.trim();
17
17
 
18
- // 0. Find the first '{' or '[' and trim everything before it (e.g. markdown fences)
19
- const firstOpener = s.search(/[\{\[]/);
20
- if (firstOpener !== -1) {
21
- s = s.substring(firstOpener);
22
- }
23
-
24
18
  // 1. Evaluate simple arithmetic expressions in values e.g. "v": 495 / 1000
25
19
  s = s.replace(
26
20
  /:\s*(-?\d+(?:\.\d+)?)\s*([+\-*/])\s*(-?\d+(?:\.\d+)?)/g,
@@ -55,8 +49,8 @@ function sanitizeJson(raw: string): string {
55
49
  // 7. Stack-based structure balancer.
56
50
  // Walk every character, maintain a stack of openers so we close each
57
51
  // unclosed structure with its exact matching closer — in the right order.
58
- // This is the critical fix: a flat counter approach appends all ]'s then
59
- // all }'s, which corrupts nested structures like {..., [..., {...}]}.
52
+ // A flat counter approach appends all ]'s then all }'s, which corrupts
53
+ // nested structures like {..., [..., {...}]}.
60
54
  {
61
55
  const stack: ('{' | '[')[] = [];
62
56
  let inString = false;
@@ -72,7 +66,6 @@ function sanitizeJson(raw: string): string {
72
66
  stack.push(ch);
73
67
  } else if (ch === '}') {
74
68
  if (stack[stack.length - 1] === '{') stack.pop();
75
- // mismatched closer — leave stack alone (sanitize step 5 handles trailing commas)
76
69
  } else if (ch === ']') {
77
70
  if (stack[stack.length - 1] === '[') stack.pop();
78
71
  }
@@ -110,7 +103,116 @@ function resolveImage(data: Record<string, unknown>): string | undefined {
110
103
  return undefined;
111
104
  }
112
105
 
113
- // ─── Chart code block renderer ────────────────────────────────────────────────
106
+ /** Safely render table cell children that might be plain objects. */
107
+ function normaliseChild(children: React.ReactNode): React.ReactNode {
108
+ if (
109
+ typeof children === 'object' &&
110
+ children !== null &&
111
+ !Array.isArray(children) &&
112
+ !React.isValidElement(children)
113
+ ) {
114
+ return JSON.stringify(children);
115
+ }
116
+ return children;
117
+ }
118
+
119
+ // ─── Inline data table renderer ───────────────────────────────────────────────
120
+ // Handles chart configs where type === "table". The LLM may emit any of:
121
+ // columns, dataKeys (+optional xAxisKey), or no key hints (auto-detect from row).
122
+
123
+ interface TableConfig {
124
+ type: 'table';
125
+ title?: string;
126
+ columns?: string[]; // preferred: explicit ordered column labels
127
+ dataKeys?: string[]; // fallback column keys (may omit xAxisKey)
128
+ xAxisKey?: string; // row-label key to prepend when using dataKeys
129
+ data: Record<string, unknown>[];
130
+ }
131
+
132
+ function DataTable({ config }: { config: TableConfig }) {
133
+ const keys = React.useMemo<string[]>(() => {
134
+ // Priority 1: explicit columns list
135
+ if (Array.isArray(config.columns) && config.columns.length) {
136
+ return config.columns;
137
+ }
138
+ // Priority 2: dataKeys, with xAxisKey prepended if not already present
139
+ if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
140
+ if (config.xAxisKey && !config.dataKeys.includes(config.xAxisKey)) {
141
+ return [config.xAxisKey, ...config.dataKeys];
142
+ }
143
+ return config.dataKeys;
144
+ }
145
+ // Priority 3: auto-detect from first data row
146
+ if (config.data.length > 0) return Object.keys(config.data[0]);
147
+ return [];
148
+ }, [config]);
149
+
150
+ if (!config.data.length || !keys.length) {
151
+ return <p className="text-xs text-slate-400 italic my-2">No data to display.</p>;
152
+ }
153
+
154
+ const formatCell = (val: unknown): string => {
155
+ if (val === null || val === undefined) return '—';
156
+ if (typeof val === 'boolean') return val ? '✓' : '✗';
157
+ return String(val);
158
+ };
159
+
160
+ return (
161
+ <div className="my-4 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden">
162
+ {config.title && (
163
+ <div className="px-4 py-2.5 bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10">
164
+ <p className="text-xs font-semibold text-slate-600 dark:text-white/70 uppercase tracking-wide">
165
+ {config.title}
166
+ </p>
167
+ </div>
168
+ )}
169
+
170
+ <div className="overflow-x-auto">
171
+ <table className="w-full text-left border-collapse min-w-[320px] text-sm">
172
+ <thead className="bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10">
173
+ <tr>
174
+ {keys.map(k => (
175
+ <th
176
+ key={k}
177
+ className="px-4 py-3 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
178
+ >
179
+ {k}
180
+ </th>
181
+ ))}
182
+ </tr>
183
+ </thead>
184
+ <tbody>
185
+ {config.data.map((row, ri) => (
186
+ <tr
187
+ key={ri}
188
+ className="border-b border-slate-100 dark:border-white/5 last:border-0 hover:bg-slate-50/60 dark:hover:bg-white/5 transition-colors"
189
+ >
190
+ {keys.map(k => (
191
+ <td
192
+ key={k}
193
+ className="px-4 py-3 text-slate-600 dark:text-white/70 whitespace-nowrap"
194
+ >
195
+ {formatCell(row[k])}
196
+ </td>
197
+ ))}
198
+ </tr>
199
+ ))}
200
+ </tbody>
201
+ </table>
202
+ </div>
203
+
204
+ <div className="px-4 py-2 bg-slate-50 dark:bg-white/5 border-t border-slate-100 dark:border-white/5">
205
+ <p className="text-[11px] text-slate-400 dark:text-white/30">
206
+ {config.data.length} row{config.data.length !== 1 ? 's' : ''}
207
+ </p>
208
+ </div>
209
+ </div>
210
+ );
211
+ }
212
+
213
+ // ─── Chart / table code block dispatcher ─────────────────────────────────────
214
+
215
+ type ParsedConfig = ChartConfig | TableConfig;
114
216
 
115
217
  interface ChartBlockProps {
116
218
  rawContent: string;
@@ -120,12 +222,14 @@ interface ChartBlockProps {
120
222
  }
121
223
 
122
224
  function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }: ChartBlockProps) {
123
- const result = React.useMemo<{ config: ChartConfig } | { error: string } | { loading: boolean }>(() => {
225
+ const result = React.useMemo<
226
+ { config: ParsedConfig } | { error: string } | { loading: true }
227
+ >(() => {
124
228
  if (isStreaming) return { loading: true };
125
- if (!rawContent || rawContent === 'undefined') return { error: 'Empty chart config.' };
229
+ if (!rawContent || rawContent === 'undefined') return { error: 'Empty visualization config.' };
126
230
  try {
127
231
  const sanitized = sanitizeJson(rawContent);
128
- const config = JSON.parse(sanitized) as ChartConfig;
232
+ const config = JSON.parse(sanitized) as ParsedConfig;
129
233
  return { config };
130
234
  } catch (err) {
131
235
  const sanitized = sanitizeJson(rawContent);
@@ -138,7 +242,7 @@ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }: Char
138
242
  return (
139
243
  <div className="my-4 p-8 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex flex-col items-center justify-center gap-3 select-none">
140
244
  <div className="w-6 h-6 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" />
141
- <p className="text-xs text-slate-500 font-medium animate-pulse">Preparing data visualization...</p>
245
+ <p className="text-xs text-slate-500 font-medium animate-pulse">Preparing data visualization…</p>
142
246
  </div>
143
247
  );
144
248
  }
@@ -146,20 +250,31 @@ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }: Char
146
250
  if ('error' in result) {
147
251
  return (
148
252
  <div className="p-4 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-lg text-sm border border-red-100 dark:border-red-900/30">
149
- <p className="font-medium mb-1">Failed to render chart</p>
253
+ <p className="font-medium mb-1">Failed to render visualization</p>
150
254
  <p className="opacity-70 text-xs">The generated configuration is invalid. Check the console for details.</p>
151
255
  </div>
152
256
  );
153
257
  }
154
258
 
259
+ const { config } = result;
260
+
261
+ // Route table type to dedicated renderer; everything else goes to DynamicChart
262
+ if (config.type === 'table') {
263
+ return <DataTable config={config as TableConfig} />;
264
+ }
265
+
155
266
  return (
156
267
  <div className="my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm">
157
- <DynamicChart config={result.config} primaryColor={primaryColor} accentColor={accentColor} />
268
+ <DynamicChart
269
+ config={config as ChartConfig}
270
+ primaryColor={primaryColor}
271
+ accentColor={accentColor}
272
+ />
158
273
  </div>
159
274
  );
160
275
  }
161
276
 
162
- // ─── Component ────────────────────────────────────────────────────────────────
277
+ // ─── Main component ───────────────────────────────────────────────────────────
163
278
 
164
279
  export function MessageBubble({
165
280
  message,
@@ -242,6 +357,13 @@ export function MessageBubble({
242
357
  });
243
358
  }, [productsFromSources, productsFromContent, message.content]);
244
359
 
360
+ // ── Markdown source fixer ──────────────────────────────────────────────────
361
+ // GFM tables require a blank line before the | row when preceded by text.
362
+ const processedMarkdown = React.useMemo(() => {
363
+ const raw = cleanContent || message.content;
364
+ return raw.replace(/([^\n])\n\|/g, '$1\n\n|');
365
+ }, [cleanContent, message.content]);
366
+
245
367
  // ── Markdown component overrides ───────────────────────────────────────────
246
368
  const markdownComponents = React.useMemo(
247
369
  () => ({
@@ -258,7 +380,7 @@ export function MessageBubble({
258
380
  ),
259
381
  th: ({ children, ...props }: React.ThHTMLAttributes<HTMLTableCellElement>) => (
260
382
  <th
261
- className="p-3 font-semibold text-slate-700 dark:text-white/90 first:rounded-tl-xl last:rounded-tr-xl"
383
+ className="px-4 py-3 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
262
384
  {...props}
263
385
  >
264
386
  {normaliseChild(children)}
@@ -266,7 +388,7 @@ export function MessageBubble({
266
388
  ),
267
389
  td: ({ children, ...props }: React.TdHTMLAttributes<HTMLTableCellElement>) => (
268
390
  <td
269
- className="p-3 border-b border-slate-100 dark:border-white/5 last:border-0 text-slate-600 dark:text-white/70"
391
+ className="px-4 py-3 border-b border-slate-100 dark:border-white/5 last:border-0 text-slate-600 dark:text-white/70 whitespace-nowrap"
270
392
  {...props}
271
393
  >
272
394
  {normaliseChild(children)}
@@ -309,8 +431,8 @@ export function MessageBubble({
309
431
  {/* Avatar */}
310
432
  <div
311
433
  className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center shadow-lg ${isUser
312
- ? 'text-white'
313
- : 'bg-slate-100 dark:bg-white/10 text-slate-700 dark:text-white/80'
434
+ ? 'text-white'
435
+ : 'bg-slate-100 dark:bg-white/10 text-slate-700 dark:text-white/80'
314
436
  }`}
315
437
  style={isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}}
316
438
  >
@@ -321,8 +443,8 @@ export function MessageBubble({
321
443
  {/* Bubble */}
322
444
  <div
323
445
  className={`relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser
324
- ? 'text-white rounded-tr-sm'
325
- : 'bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm'
446
+ ? 'text-white rounded-tr-sm'
447
+ : 'bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm'
326
448
  }`}
327
449
  style={
328
450
  isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
@@ -337,7 +459,7 @@ export function MessageBubble({
337
459
  ) : (
338
460
  <div className={`prose prose-sm max-w-none ${isUser ? 'prose-invert' : 'dark:prose-invert'}`}>
339
461
  <ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
340
- {cleanContent || message.content}
462
+ {processedMarkdown}
341
463
  </ReactMarkdown>
342
464
 
343
465
  {isStreaming && message.content && (
@@ -381,19 +503,4 @@ export function MessageBubble({
381
503
  </div>
382
504
  </div>
383
505
  );
384
- }
385
-
386
- // ─── Util ─────────────────────────────────────────────────────────────────────
387
-
388
- /** Safely render table cell children that might be plain objects. */
389
- function normaliseChild(children: React.ReactNode): React.ReactNode {
390
- if (
391
- typeof children === 'object' &&
392
- children !== null &&
393
- !Array.isArray(children) &&
394
- !React.isValidElement(children)
395
- ) {
396
- return JSON.stringify(children);
397
- }
398
- return children;
399
506
  }
@@ -59,8 +59,6 @@ export class LangChainAgent {
59
59
  3. dataKeys MUST exactly match the property names in the data objects.
60
60
  4. data objects MUST be FLAT (no nested objects or arrays).
61
61
  5. DO NOT output naked JSON; it MUST be wrapped in \`\`\`chart ... \`\`\`.
62
- 6. The UI HAS a built-in charting engine. NEVER provide Python, Plotly, or Matplotlib code.
63
- 7. NEVER apologize or claim you cannot render charts. Simply use the \`\`\`chart\`\`\` block.
64
62
  \`\`\`chart
65
63
  {
66
64
  "type": "bar" | "line" | "pie",
@@ -109,8 +109,6 @@ export class Pipeline {
109
109
  3. dataKeys MUST exactly match the property names in the data objects.
110
110
  4. data objects MUST be FLAT (no nested objects or arrays).
111
111
  5. DO NOT output naked JSON; it MUST be wrapped in \`\`\`chart ... \`\`\`.
112
- 6. The UI HAS a built-in charting engine. NEVER provide Python, Plotly, or Matplotlib code.
113
- 7. NEVER apologize or claim you cannot render charts. Simply use the \`\`\`chart\`\`\` block.
114
112
  \`\`\`chart
115
113
  {
116
114
  "type": "bar" | "line" | "pie",