@retrivora-ai/rag-engine 1.3.8 → 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.
- package/dist/index.js +75 -15
- package/dist/index.mjs +75 -15
- package/package.json +1 -1
- package/src/components/MessageBubble.tsx +146 -33
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,6 +537,10 @@ 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) => {
|
|
@@ -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 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
|
|
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 whitespace-nowrap"
|
|
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,6 +500,10 @@ 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) => {
|
|
@@ -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 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
|
|
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 whitespace-nowrap"
|
|
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.
|
|
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",
|
|
@@ -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,6 +357,13 @@ 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
|
() => ({
|
|
@@ -252,7 +380,7 @@ export function MessageBubble({
|
|
|
252
380
|
),
|
|
253
381
|
th: ({ children, ...props }: React.ThHTMLAttributes<HTMLTableCellElement>) => (
|
|
254
382
|
<th
|
|
255
|
-
className="
|
|
383
|
+
className="px-4 py-3 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
|
|
256
384
|
{...props}
|
|
257
385
|
>
|
|
258
386
|
{normaliseChild(children)}
|
|
@@ -260,7 +388,7 @@ export function MessageBubble({
|
|
|
260
388
|
),
|
|
261
389
|
td: ({ children, ...props }: React.TdHTMLAttributes<HTMLTableCellElement>) => (
|
|
262
390
|
<td
|
|
263
|
-
className="
|
|
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"
|
|
264
392
|
{...props}
|
|
265
393
|
>
|
|
266
394
|
{normaliseChild(children)}
|
|
@@ -303,8 +431,8 @@ export function MessageBubble({
|
|
|
303
431
|
{/* Avatar */}
|
|
304
432
|
<div
|
|
305
433
|
className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center shadow-lg ${isUser
|
|
306
|
-
|
|
307
|
-
|
|
434
|
+
? 'text-white'
|
|
435
|
+
: 'bg-slate-100 dark:bg-white/10 text-slate-700 dark:text-white/80'
|
|
308
436
|
}`}
|
|
309
437
|
style={isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}}
|
|
310
438
|
>
|
|
@@ -315,8 +443,8 @@ export function MessageBubble({
|
|
|
315
443
|
{/* Bubble */}
|
|
316
444
|
<div
|
|
317
445
|
className={`relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser
|
|
318
|
-
|
|
319
|
-
|
|
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'
|
|
320
448
|
}`}
|
|
321
449
|
style={
|
|
322
450
|
isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
@@ -331,7 +459,7 @@ export function MessageBubble({
|
|
|
331
459
|
) : (
|
|
332
460
|
<div className={`prose prose-sm max-w-none ${isUser ? 'prose-invert' : 'dark:prose-invert'}`}>
|
|
333
461
|
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
|
334
|
-
{
|
|
462
|
+
{processedMarkdown}
|
|
335
463
|
</ReactMarkdown>
|
|
336
464
|
|
|
337
465
|
{isStreaming && message.content && (
|
|
@@ -375,19 +503,4 @@ export function MessageBubble({
|
|
|
375
503
|
</div>
|
|
376
504
|
</div>
|
|
377
505
|
);
|
|
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
506
|
}
|