@retrivora-ai/rag-engine 1.3.8 → 1.4.0
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.
- package/dist/index.js +77 -17
- package/dist/index.mjs +77 -17
- package/package.json +1 -1
- package/src/components/MessageBubble.tsx +151 -36
package/dist/index.js
CHANGED
|
@@ -381,14 +381,65 @@ function resolveImage(data) {
|
|
|
381
381
|
}
|
|
382
382
|
return void 0;
|
|
383
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
|
+
}
|
|
384
435
|
function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
|
|
385
436
|
const result = import_react5.default.useMemo(() => {
|
|
386
437
|
if (isStreaming) return { loading: true };
|
|
387
|
-
if (!rawContent || rawContent === "undefined") return { error: "Empty
|
|
438
|
+
if (!rawContent || rawContent === "undefined") return { error: "Empty visualization config." };
|
|
388
439
|
try {
|
|
389
440
|
const sanitized = sanitizeJson(rawContent);
|
|
390
|
-
const
|
|
391
|
-
return { config };
|
|
441
|
+
const config2 = JSON.parse(sanitized);
|
|
442
|
+
return { config: config2 };
|
|
392
443
|
} catch (err) {
|
|
393
444
|
const sanitized = sanitizeJson(rawContent);
|
|
394
445
|
console.error("[ChartBlock] Parsing failed.\nError:", err, "\nSanitized:\n", sanitized);
|
|
@@ -396,12 +447,23 @@ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
|
|
|
396
447
|
}
|
|
397
448
|
}, [rawContent, isStreaming]);
|
|
398
449
|
if ("loading" in result) {
|
|
399
|
-
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"));
|
|
400
451
|
}
|
|
401
452
|
if ("error" in result) {
|
|
402
|
-
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
|
|
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."));
|
|
403
454
|
}
|
|
404
|
-
|
|
455
|
+
const { config } = result;
|
|
456
|
+
if (config.type === "table") {
|
|
457
|
+
return /* @__PURE__ */ import_react5.default.createElement(DataTable, { config });
|
|
458
|
+
}
|
|
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
|
+
));
|
|
405
467
|
}
|
|
406
468
|
function MessageBubble({
|
|
407
469
|
message,
|
|
@@ -475,18 +537,22 @@ function MessageBubble({
|
|
|
475
537
|
return false;
|
|
476
538
|
});
|
|
477
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]);
|
|
478
544
|
const markdownComponents = import_react5.default.useMemo(
|
|
479
545
|
() => ({
|
|
480
546
|
table: (_a) => {
|
|
481
547
|
var props = __objRest(_a, []);
|
|
482
|
-
return /* @__PURE__ */ import_react5.default.createElement("div", { className: "overflow-
|
|
548
|
+
return /* @__PURE__ */ import_react5.default.createElement("div", { className: "overflow-hidden my-6 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm bg-white dark:bg-slate-900/50" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ import_react5.default.createElement("table", __spreadValues({ className: "w-full text-left border-collapse min-w-[400px] text-sm" }, props))));
|
|
483
549
|
},
|
|
484
550
|
thead: (_b) => {
|
|
485
551
|
var props = __objRest(_b, []);
|
|
486
552
|
return /* @__PURE__ */ import_react5.default.createElement(
|
|
487
553
|
"thead",
|
|
488
554
|
__spreadValues({
|
|
489
|
-
className: "bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10"
|
|
555
|
+
className: "bg-slate-50/80 dark:bg-white/5 border-b border-slate-200 dark:border-white/10"
|
|
490
556
|
}, props)
|
|
491
557
|
);
|
|
492
558
|
},
|
|
@@ -495,7 +561,7 @@ function MessageBubble({
|
|
|
495
561
|
return /* @__PURE__ */ import_react5.default.createElement(
|
|
496
562
|
"th",
|
|
497
563
|
__spreadValues({
|
|
498
|
-
className: "
|
|
564
|
+
className: "px-4 py-3.5 font-bold text-slate-800 dark:text-white uppercase text-[11px] tracking-wider"
|
|
499
565
|
}, props),
|
|
500
566
|
normaliseChild(children)
|
|
501
567
|
);
|
|
@@ -505,7 +571,7 @@ function MessageBubble({
|
|
|
505
571
|
return /* @__PURE__ */ import_react5.default.createElement(
|
|
506
572
|
"td",
|
|
507
573
|
__spreadValues({
|
|
508
|
-
className: "
|
|
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 hover:bg-slate-50/50 dark:hover:bg-white/5 transition-colors"
|
|
509
575
|
}, props),
|
|
510
576
|
normaliseChild(children)
|
|
511
577
|
);
|
|
@@ -551,7 +617,7 @@ function MessageBubble({
|
|
|
551
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"}`,
|
|
552
618
|
style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
553
619
|
},
|
|
554
|
-
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 },
|
|
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" }))
|
|
555
621
|
), !isUser && allProducts.length > 0 && /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ import_react5.default.createElement(
|
|
556
622
|
ProductCarousel,
|
|
557
623
|
{
|
|
@@ -572,12 +638,6 @@ function MessageBubble({
|
|
|
572
638
|
" used"
|
|
573
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 }))))));
|
|
574
640
|
}
|
|
575
|
-
function normaliseChild(children) {
|
|
576
|
-
if (typeof children === "object" && children !== null && !Array.isArray(children) && !import_react5.default.isValidElement(children)) {
|
|
577
|
-
return JSON.stringify(children);
|
|
578
|
-
}
|
|
579
|
-
return children;
|
|
580
|
-
}
|
|
581
641
|
|
|
582
642
|
// src/components/ConfigProvider.tsx
|
|
583
643
|
var import_react6 = __toESM(require("react"));
|
package/dist/index.mjs
CHANGED
|
@@ -344,14 +344,65 @@ function resolveImage(data) {
|
|
|
344
344
|
}
|
|
345
345
|
return void 0;
|
|
346
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
|
+
}
|
|
347
398
|
function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
|
|
348
399
|
const result = React5.useMemo(() => {
|
|
349
400
|
if (isStreaming) return { loading: true };
|
|
350
|
-
if (!rawContent || rawContent === "undefined") return { error: "Empty
|
|
401
|
+
if (!rawContent || rawContent === "undefined") return { error: "Empty visualization config." };
|
|
351
402
|
try {
|
|
352
403
|
const sanitized = sanitizeJson(rawContent);
|
|
353
|
-
const
|
|
354
|
-
return { config };
|
|
404
|
+
const config2 = JSON.parse(sanitized);
|
|
405
|
+
return { config: config2 };
|
|
355
406
|
} catch (err) {
|
|
356
407
|
const sanitized = sanitizeJson(rawContent);
|
|
357
408
|
console.error("[ChartBlock] Parsing failed.\nError:", err, "\nSanitized:\n", sanitized);
|
|
@@ -359,12 +410,23 @@ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
|
|
|
359
410
|
}
|
|
360
411
|
}, [rawContent, isStreaming]);
|
|
361
412
|
if ("loading" in result) {
|
|
362
|
-
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"));
|
|
363
414
|
}
|
|
364
415
|
if ("error" in result) {
|
|
365
|
-
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
|
|
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."));
|
|
366
417
|
}
|
|
367
|
-
|
|
418
|
+
const { config } = result;
|
|
419
|
+
if (config.type === "table") {
|
|
420
|
+
return /* @__PURE__ */ React5.createElement(DataTable, { config });
|
|
421
|
+
}
|
|
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
|
+
));
|
|
368
430
|
}
|
|
369
431
|
function MessageBubble({
|
|
370
432
|
message,
|
|
@@ -438,18 +500,22 @@ function MessageBubble({
|
|
|
438
500
|
return false;
|
|
439
501
|
});
|
|
440
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]);
|
|
441
507
|
const markdownComponents = React5.useMemo(
|
|
442
508
|
() => ({
|
|
443
509
|
table: (_a) => {
|
|
444
510
|
var props = __objRest(_a, []);
|
|
445
|
-
return /* @__PURE__ */ React5.createElement("div", { className: "overflow-
|
|
511
|
+
return /* @__PURE__ */ React5.createElement("div", { className: "overflow-hidden my-6 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm bg-white dark:bg-slate-900/50" }, /* @__PURE__ */ React5.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React5.createElement("table", __spreadValues({ className: "w-full text-left border-collapse min-w-[400px] text-sm" }, props))));
|
|
446
512
|
},
|
|
447
513
|
thead: (_b) => {
|
|
448
514
|
var props = __objRest(_b, []);
|
|
449
515
|
return /* @__PURE__ */ React5.createElement(
|
|
450
516
|
"thead",
|
|
451
517
|
__spreadValues({
|
|
452
|
-
className: "bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10"
|
|
518
|
+
className: "bg-slate-50/80 dark:bg-white/5 border-b border-slate-200 dark:border-white/10"
|
|
453
519
|
}, props)
|
|
454
520
|
);
|
|
455
521
|
},
|
|
@@ -458,7 +524,7 @@ function MessageBubble({
|
|
|
458
524
|
return /* @__PURE__ */ React5.createElement(
|
|
459
525
|
"th",
|
|
460
526
|
__spreadValues({
|
|
461
|
-
className: "
|
|
527
|
+
className: "px-4 py-3.5 font-bold text-slate-800 dark:text-white uppercase text-[11px] tracking-wider"
|
|
462
528
|
}, props),
|
|
463
529
|
normaliseChild(children)
|
|
464
530
|
);
|
|
@@ -468,7 +534,7 @@ function MessageBubble({
|
|
|
468
534
|
return /* @__PURE__ */ React5.createElement(
|
|
469
535
|
"td",
|
|
470
536
|
__spreadValues({
|
|
471
|
-
className: "
|
|
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 hover:bg-slate-50/50 dark:hover:bg-white/5 transition-colors"
|
|
472
538
|
}, props),
|
|
473
539
|
normaliseChild(children)
|
|
474
540
|
);
|
|
@@ -514,7 +580,7 @@ function MessageBubble({
|
|
|
514
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"}`,
|
|
515
581
|
style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
516
582
|
},
|
|
517
|
-
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 },
|
|
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" }))
|
|
518
584
|
), !isUser && allProducts.length > 0 && /* @__PURE__ */ React5.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ React5.createElement(
|
|
519
585
|
ProductCarousel,
|
|
520
586
|
{
|
|
@@ -535,12 +601,6 @@ function MessageBubble({
|
|
|
535
601
|
" used"
|
|
536
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 }))))));
|
|
537
603
|
}
|
|
538
|
-
function normaliseChild(children) {
|
|
539
|
-
if (typeof children === "object" && children !== null && !Array.isArray(children) && !React5.isValidElement(children)) {
|
|
540
|
-
return JSON.stringify(children);
|
|
541
|
-
}
|
|
542
|
-
return children;
|
|
543
|
-
}
|
|
544
604
|
|
|
545
605
|
// src/components/ConfigProvider.tsx
|
|
546
606
|
import React6, { createContext, useContext } from "react";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
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",
|
|
@@ -49,8 +49,8 @@ function sanitizeJson(raw: string): string {
|
|
|
49
49
|
// 7. Stack-based structure balancer.
|
|
50
50
|
// Walk every character, maintain a stack of openers so we close each
|
|
51
51
|
// unclosed structure with its exact matching closer — in the right order.
|
|
52
|
-
//
|
|
53
|
-
//
|
|
52
|
+
// A flat counter approach appends all ]'s then all }'s, which corrupts
|
|
53
|
+
// nested structures like {..., [..., {...}]}.
|
|
54
54
|
{
|
|
55
55
|
const stack: ('{' | '[')[] = [];
|
|
56
56
|
let inString = false;
|
|
@@ -66,7 +66,6 @@ function sanitizeJson(raw: string): string {
|
|
|
66
66
|
stack.push(ch);
|
|
67
67
|
} else if (ch === '}') {
|
|
68
68
|
if (stack[stack.length - 1] === '{') stack.pop();
|
|
69
|
-
// mismatched closer — leave stack alone (sanitize step 5 handles trailing commas)
|
|
70
69
|
} else if (ch === ']') {
|
|
71
70
|
if (stack[stack.length - 1] === '[') stack.pop();
|
|
72
71
|
}
|
|
@@ -104,7 +103,116 @@ function resolveImage(data: Record<string, unknown>): string | undefined {
|
|
|
104
103
|
return undefined;
|
|
105
104
|
}
|
|
106
105
|
|
|
107
|
-
|
|
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;
|
|
108
216
|
|
|
109
217
|
interface ChartBlockProps {
|
|
110
218
|
rawContent: string;
|
|
@@ -114,12 +222,14 @@ interface ChartBlockProps {
|
|
|
114
222
|
}
|
|
115
223
|
|
|
116
224
|
function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }: ChartBlockProps) {
|
|
117
|
-
const result = React.useMemo<
|
|
225
|
+
const result = React.useMemo<
|
|
226
|
+
{ config: ParsedConfig } | { error: string } | { loading: true }
|
|
227
|
+
>(() => {
|
|
118
228
|
if (isStreaming) return { loading: true };
|
|
119
|
-
if (!rawContent || rawContent === 'undefined') return { error: 'Empty
|
|
229
|
+
if (!rawContent || rawContent === 'undefined') return { error: 'Empty visualization config.' };
|
|
120
230
|
try {
|
|
121
231
|
const sanitized = sanitizeJson(rawContent);
|
|
122
|
-
const config = JSON.parse(sanitized) as
|
|
232
|
+
const config = JSON.parse(sanitized) as ParsedConfig;
|
|
123
233
|
return { config };
|
|
124
234
|
} catch (err) {
|
|
125
235
|
const sanitized = sanitizeJson(rawContent);
|
|
@@ -132,7 +242,7 @@ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }: Char
|
|
|
132
242
|
return (
|
|
133
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">
|
|
134
244
|
<div className="w-6 h-6 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" />
|
|
135
|
-
<p className="text-xs text-slate-500 font-medium animate-pulse">Preparing data visualization
|
|
245
|
+
<p className="text-xs text-slate-500 font-medium animate-pulse">Preparing data visualization…</p>
|
|
136
246
|
</div>
|
|
137
247
|
);
|
|
138
248
|
}
|
|
@@ -140,20 +250,31 @@ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }: Char
|
|
|
140
250
|
if ('error' in result) {
|
|
141
251
|
return (
|
|
142
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">
|
|
143
|
-
<p className="font-medium mb-1">Failed to render
|
|
253
|
+
<p className="font-medium mb-1">Failed to render visualization</p>
|
|
144
254
|
<p className="opacity-70 text-xs">The generated configuration is invalid. Check the console for details.</p>
|
|
145
255
|
</div>
|
|
146
256
|
);
|
|
147
257
|
}
|
|
148
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
|
+
|
|
149
266
|
return (
|
|
150
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">
|
|
151
|
-
<DynamicChart
|
|
268
|
+
<DynamicChart
|
|
269
|
+
config={config as ChartConfig}
|
|
270
|
+
primaryColor={primaryColor}
|
|
271
|
+
accentColor={accentColor}
|
|
272
|
+
/>
|
|
152
273
|
</div>
|
|
153
274
|
);
|
|
154
275
|
}
|
|
155
276
|
|
|
156
|
-
// ───
|
|
277
|
+
// ─── Main component ───────────────────────────────────────────────────────────
|
|
157
278
|
|
|
158
279
|
export function MessageBubble({
|
|
159
280
|
message,
|
|
@@ -236,23 +357,32 @@ export function MessageBubble({
|
|
|
236
357
|
});
|
|
237
358
|
}, [productsFromSources, productsFromContent, message.content]);
|
|
238
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
|
+
|
|
239
367
|
// ── Markdown component overrides ───────────────────────────────────────────
|
|
240
368
|
const markdownComponents = React.useMemo(
|
|
241
369
|
() => ({
|
|
242
370
|
table: ({ ...props }: React.HTMLAttributes<HTMLTableElement>) => (
|
|
243
|
-
<div className="overflow-
|
|
244
|
-
<
|
|
371
|
+
<div className="overflow-hidden my-6 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm bg-white dark:bg-slate-900/50">
|
|
372
|
+
<div className="overflow-x-auto">
|
|
373
|
+
<table className="w-full text-left border-collapse min-w-[400px] text-sm" {...props} />
|
|
374
|
+
</div>
|
|
245
375
|
</div>
|
|
246
376
|
),
|
|
247
377
|
thead: ({ ...props }: React.HTMLAttributes<HTMLTableSectionElement>) => (
|
|
248
378
|
<thead
|
|
249
|
-
className="bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10"
|
|
379
|
+
className="bg-slate-50/80 dark:bg-white/5 border-b border-slate-200 dark:border-white/10"
|
|
250
380
|
{...props}
|
|
251
381
|
/>
|
|
252
382
|
),
|
|
253
383
|
th: ({ children, ...props }: React.ThHTMLAttributes<HTMLTableCellElement>) => (
|
|
254
384
|
<th
|
|
255
|
-
className="
|
|
385
|
+
className="px-4 py-3.5 font-bold text-slate-800 dark:text-white uppercase text-[11px] tracking-wider"
|
|
256
386
|
{...props}
|
|
257
387
|
>
|
|
258
388
|
{normaliseChild(children)}
|
|
@@ -260,7 +390,7 @@ export function MessageBubble({
|
|
|
260
390
|
),
|
|
261
391
|
td: ({ children, ...props }: React.TdHTMLAttributes<HTMLTableCellElement>) => (
|
|
262
392
|
<td
|
|
263
|
-
className="
|
|
393
|
+
className="px-4 py-3 border-b border-slate-100 dark:border-white/5 last:border-0 text-slate-600 dark:text-white/70 hover:bg-slate-50/50 dark:hover:bg-white/5 transition-colors"
|
|
264
394
|
{...props}
|
|
265
395
|
>
|
|
266
396
|
{normaliseChild(children)}
|
|
@@ -303,8 +433,8 @@ export function MessageBubble({
|
|
|
303
433
|
{/* Avatar */}
|
|
304
434
|
<div
|
|
305
435
|
className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center shadow-lg ${isUser
|
|
306
|
-
|
|
307
|
-
|
|
436
|
+
? 'text-white'
|
|
437
|
+
: 'bg-slate-100 dark:bg-white/10 text-slate-700 dark:text-white/80'
|
|
308
438
|
}`}
|
|
309
439
|
style={isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}}
|
|
310
440
|
>
|
|
@@ -315,8 +445,8 @@ export function MessageBubble({
|
|
|
315
445
|
{/* Bubble */}
|
|
316
446
|
<div
|
|
317
447
|
className={`relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser
|
|
318
|
-
|
|
319
|
-
|
|
448
|
+
? 'text-white rounded-tr-sm'
|
|
449
|
+
: 'bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm'
|
|
320
450
|
}`}
|
|
321
451
|
style={
|
|
322
452
|
isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
@@ -331,7 +461,7 @@ export function MessageBubble({
|
|
|
331
461
|
) : (
|
|
332
462
|
<div className={`prose prose-sm max-w-none ${isUser ? 'prose-invert' : 'dark:prose-invert'}`}>
|
|
333
463
|
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
|
334
|
-
{
|
|
464
|
+
{processedMarkdown}
|
|
335
465
|
</ReactMarkdown>
|
|
336
466
|
|
|
337
467
|
{isStreaming && message.content && (
|
|
@@ -375,19 +505,4 @@ export function MessageBubble({
|
|
|
375
505
|
</div>
|
|
376
506
|
</div>
|
|
377
507
|
);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// ─── Util ─────────────────────────────────────────────────────────────────────
|
|
381
|
-
|
|
382
|
-
/** Safely render table cell children that might be plain objects. */
|
|
383
|
-
function normaliseChild(children: React.ReactNode): React.ReactNode {
|
|
384
|
-
if (
|
|
385
|
-
typeof children === 'object' &&
|
|
386
|
-
children !== null &&
|
|
387
|
-
!Array.isArray(children) &&
|
|
388
|
-
!React.isValidElement(children)
|
|
389
|
-
) {
|
|
390
|
-
return JSON.stringify(children);
|
|
391
|
-
}
|
|
392
|
-
return children;
|
|
393
508
|
}
|