@retrivora-ai/rag-engine 1.5.3 → 1.5.4
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/{chunk-C4UPQX3E.mjs → chunk-BLLMNP77.mjs} +14 -8
- package/dist/handlers/index.js +14 -8
- package/dist/handlers/index.mjs +1 -1
- package/dist/index.js +58 -18
- package/dist/index.mjs +58 -18
- package/dist/server.js +14 -8
- package/dist/server.mjs +1 -1
- package/package.json +1 -1
- package/src/components/MessageBubble.tsx +90 -40
- package/src/core/Pipeline.ts +14 -8
|
@@ -2259,17 +2259,23 @@ var Pipeline = class {
|
|
|
2259
2259
|
async initialize() {
|
|
2260
2260
|
var _a, _b;
|
|
2261
2261
|
if (this.initialised) return;
|
|
2262
|
-
const CHART_MARKER = "<!--
|
|
2262
|
+
const CHART_MARKER = "<!-- UI_PROTOCOL_V3 -->";
|
|
2263
2263
|
const chartInstruction = `
|
|
2264
2264
|
|
|
2265
2265
|
${CHART_MARKER}
|
|
2266
|
-
###
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2266
|
+
### UNIVERSAL UI PROTOCOL:
|
|
2267
|
+
You have a specialized UI system. For any structured data (charts, product lists, or tables), you MUST use a single \`\`\`ui\`\`\` block.
|
|
2268
|
+
1. DECIDE the best "view" for the user's query:
|
|
2269
|
+
- "chart": For statistics, distributions, or comparisons. (Requires "chartType": "pie" | "bar" | "line")
|
|
2270
|
+
- "carousel": For browsing specific product items.
|
|
2271
|
+
- "table": For detailed raw data comparisons.
|
|
2272
|
+
2. OUTPUT a \`\`\`ui\`\`\` block with this schema:
|
|
2273
|
+
{
|
|
2274
|
+
"view": "chart" | "carousel" | "table",
|
|
2275
|
+
"chartType": "pie" | "bar" | "line" (only for view: "chart"),
|
|
2276
|
+
"data": [ ... objects ... ]
|
|
2277
|
+
}
|
|
2278
|
+
3. RULES: Valid JSON only. Data must be FLAT. Never draw charts/tables with text. Choose ONLY the most useful view.`;
|
|
2273
2279
|
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
2274
2280
|
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
|
|
2275
2281
|
}
|
package/dist/handlers/index.js
CHANGED
|
@@ -3822,17 +3822,23 @@ var Pipeline = class {
|
|
|
3822
3822
|
async initialize() {
|
|
3823
3823
|
var _a, _b;
|
|
3824
3824
|
if (this.initialised) return;
|
|
3825
|
-
const CHART_MARKER = "<!--
|
|
3825
|
+
const CHART_MARKER = "<!-- UI_PROTOCOL_V3 -->";
|
|
3826
3826
|
const chartInstruction = `
|
|
3827
3827
|
|
|
3828
3828
|
${CHART_MARKER}
|
|
3829
|
-
###
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3829
|
+
### UNIVERSAL UI PROTOCOL:
|
|
3830
|
+
You have a specialized UI system. For any structured data (charts, product lists, or tables), you MUST use a single \`\`\`ui\`\`\` block.
|
|
3831
|
+
1. DECIDE the best "view" for the user's query:
|
|
3832
|
+
- "chart": For statistics, distributions, or comparisons. (Requires "chartType": "pie" | "bar" | "line")
|
|
3833
|
+
- "carousel": For browsing specific product items.
|
|
3834
|
+
- "table": For detailed raw data comparisons.
|
|
3835
|
+
2. OUTPUT a \`\`\`ui\`\`\` block with this schema:
|
|
3836
|
+
{
|
|
3837
|
+
"view": "chart" | "carousel" | "table",
|
|
3838
|
+
"chartType": "pie" | "bar" | "line" (only for view: "chart"),
|
|
3839
|
+
"data": [ ... objects ... ]
|
|
3840
|
+
}
|
|
3841
|
+
3. RULES: Valid JSON only. Data must be FLAT. Never draw charts/tables with text. Choose ONLY the most useful view.`;
|
|
3836
3842
|
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
3837
3843
|
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
|
|
3838
3844
|
}
|
package/dist/handlers/index.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -440,37 +440,63 @@ function DataTable({ config }) {
|
|
|
440
440
|
))
|
|
441
441
|
))))), /* @__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" : "")));
|
|
442
442
|
}
|
|
443
|
-
function
|
|
443
|
+
function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming }) {
|
|
444
444
|
const result = import_react5.default.useMemo(() => {
|
|
445
445
|
if (isStreaming) return { loading: true };
|
|
446
|
-
if (!rawContent || rawContent === "undefined") return { error: "Empty visualization config." };
|
|
447
446
|
try {
|
|
448
447
|
const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
|
|
449
|
-
const
|
|
448
|
+
const parsed = JSON.parse(sanitizeJson(clean));
|
|
449
|
+
const config2 = __spreadValues({}, parsed);
|
|
450
|
+
if (!config2.view) {
|
|
451
|
+
if (config2.type === "products" || Array.isArray(config2.items)) {
|
|
452
|
+
config2.view = "carousel";
|
|
453
|
+
config2.data = config2.data || config2.items || [];
|
|
454
|
+
} else if (["pie", "bar", "line"].includes(config2.type) || ["pie", "bar", "line"].includes(config2.chartType)) {
|
|
455
|
+
config2.view = "chart";
|
|
456
|
+
config2.chartType = config2.chartType || config2.type || "bar";
|
|
457
|
+
config2.data = config2.data || [];
|
|
458
|
+
} else {
|
|
459
|
+
config2.view = "table";
|
|
460
|
+
config2.data = Array.isArray(config2.data) ? config2.data : Array.isArray(config2) ? config2 : [];
|
|
461
|
+
}
|
|
462
|
+
}
|
|
450
463
|
return { config: config2 };
|
|
451
464
|
} catch (err) {
|
|
452
|
-
console.error("[ChartBlock] Parsing failed.\nError:", err);
|
|
453
465
|
return { error: String(err) };
|
|
454
466
|
}
|
|
455
467
|
}, [rawContent, isStreaming]);
|
|
456
468
|
if ("loading" in result) {
|
|
457
|
-
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
|
|
469
|
+
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 animate-pulse" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-5 h-5 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 italic" }, "Preparing view..."));
|
|
458
470
|
}
|
|
459
471
|
if ("error" in result) {
|
|
460
|
-
return /* @__PURE__ */ import_react5.default.createElement("
|
|
472
|
+
return /* @__PURE__ */ import_react5.default.createElement("pre", { className: "p-4 my-2 bg-slate-900 text-slate-100 rounded-lg text-[10px] overflow-auto max-h-40" }, /* @__PURE__ */ import_react5.default.createElement("code", null, rawContent));
|
|
461
473
|
}
|
|
462
474
|
const { config } = result;
|
|
463
|
-
|
|
464
|
-
|
|
475
|
+
switch (config.view) {
|
|
476
|
+
case "chart":
|
|
477
|
+
return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-6 p-4 bg-white dark:bg-slate-900 rounded-2xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden" }, config.title && /* @__PURE__ */ import_react5.default.createElement("h4", { className: "text-xs font-semibold text-slate-500 mb-4 px-2" }, config.title), /* @__PURE__ */ import_react5.default.createElement(
|
|
478
|
+
DynamicChart,
|
|
479
|
+
{
|
|
480
|
+
config: {
|
|
481
|
+
type: config.chartType || "bar",
|
|
482
|
+
data: config.data
|
|
483
|
+
},
|
|
484
|
+
primaryColor,
|
|
485
|
+
accentColor
|
|
486
|
+
}
|
|
487
|
+
));
|
|
488
|
+
case "carousel":
|
|
489
|
+
return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4" }, /* @__PURE__ */ import_react5.default.createElement(ProductCarousel, { products: config.data.map((item) => {
|
|
490
|
+
var _a;
|
|
491
|
+
return __spreadProps(__spreadValues({}, item), {
|
|
492
|
+
image: (_a = item.image) != null ? _a : typeof resolveImage === "function" ? resolveImage(item) : void 0
|
|
493
|
+
});
|
|
494
|
+
}) }));
|
|
495
|
+
case "table":
|
|
496
|
+
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 overflow-auto max-h-[400px]" }, config.title && /* @__PURE__ */ import_react5.default.createElement("h4", { className: "text-xs font-semibold text-slate-500 mb-2" }, config.title), /* @__PURE__ */ import_react5.default.createElement(DataTable, { config: { type: "table", data: config.data } }));
|
|
497
|
+
default:
|
|
498
|
+
return null;
|
|
465
499
|
}
|
|
466
|
-
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(
|
|
467
|
-
DynamicChart,
|
|
468
|
-
{
|
|
469
|
-
config,
|
|
470
|
-
primaryColor,
|
|
471
|
-
accentColor
|
|
472
|
-
}
|
|
473
|
-
));
|
|
474
500
|
}
|
|
475
501
|
function MessageBubble({
|
|
476
502
|
message,
|
|
@@ -645,9 +671,9 @@ ${match.trim()}
|
|
|
645
671
|
]);
|
|
646
672
|
var _a;
|
|
647
673
|
const lang = (_a = /language-(\w+)/.exec(className != null ? className : "")) == null ? void 0 : _a[1];
|
|
648
|
-
if (!inline && lang === "chart") {
|
|
674
|
+
if (!inline && (lang === "ui" || lang === "chart")) {
|
|
649
675
|
return /* @__PURE__ */ import_react5.default.createElement(
|
|
650
|
-
|
|
676
|
+
UIDispatcher,
|
|
651
677
|
{
|
|
652
678
|
rawContent: String(children != null ? children : "").trim(),
|
|
653
679
|
primaryColor,
|
|
@@ -656,6 +682,20 @@ ${match.trim()}
|
|
|
656
682
|
}
|
|
657
683
|
);
|
|
658
684
|
}
|
|
685
|
+
if (!inline && lang === "json") {
|
|
686
|
+
const content = String(children != null ? children : "").trim();
|
|
687
|
+
if (content.includes('"type": "products"') || content.includes('"type":"products"')) {
|
|
688
|
+
return /* @__PURE__ */ import_react5.default.createElement(
|
|
689
|
+
UIDispatcher,
|
|
690
|
+
{
|
|
691
|
+
rawContent: content,
|
|
692
|
+
primaryColor,
|
|
693
|
+
accentColor,
|
|
694
|
+
isStreaming
|
|
695
|
+
}
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
659
699
|
if (!inline && lang === "table-loading") {
|
|
660
700
|
return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-5 p-6 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex items-center justify-center gap-3 select-none" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-4 h-4 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 italic animate-pulse" }, "Generating data table..."));
|
|
661
701
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -403,37 +403,63 @@ function DataTable({ config }) {
|
|
|
403
403
|
))
|
|
404
404
|
))))), /* @__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" : "")));
|
|
405
405
|
}
|
|
406
|
-
function
|
|
406
|
+
function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming }) {
|
|
407
407
|
const result = React5.useMemo(() => {
|
|
408
408
|
if (isStreaming) return { loading: true };
|
|
409
|
-
if (!rawContent || rawContent === "undefined") return { error: "Empty visualization config." };
|
|
410
409
|
try {
|
|
411
410
|
const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
|
|
412
|
-
const
|
|
411
|
+
const parsed = JSON.parse(sanitizeJson(clean));
|
|
412
|
+
const config2 = __spreadValues({}, parsed);
|
|
413
|
+
if (!config2.view) {
|
|
414
|
+
if (config2.type === "products" || Array.isArray(config2.items)) {
|
|
415
|
+
config2.view = "carousel";
|
|
416
|
+
config2.data = config2.data || config2.items || [];
|
|
417
|
+
} else if (["pie", "bar", "line"].includes(config2.type) || ["pie", "bar", "line"].includes(config2.chartType)) {
|
|
418
|
+
config2.view = "chart";
|
|
419
|
+
config2.chartType = config2.chartType || config2.type || "bar";
|
|
420
|
+
config2.data = config2.data || [];
|
|
421
|
+
} else {
|
|
422
|
+
config2.view = "table";
|
|
423
|
+
config2.data = Array.isArray(config2.data) ? config2.data : Array.isArray(config2) ? config2 : [];
|
|
424
|
+
}
|
|
425
|
+
}
|
|
413
426
|
return { config: config2 };
|
|
414
427
|
} catch (err) {
|
|
415
|
-
console.error("[ChartBlock] Parsing failed.\nError:", err);
|
|
416
428
|
return { error: String(err) };
|
|
417
429
|
}
|
|
418
430
|
}, [rawContent, isStreaming]);
|
|
419
431
|
if ("loading" in result) {
|
|
420
|
-
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
|
|
432
|
+
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 animate-pulse" }, /* @__PURE__ */ React5.createElement("div", { className: "w-5 h-5 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-500 font-medium italic" }, "Preparing view..."));
|
|
421
433
|
}
|
|
422
434
|
if ("error" in result) {
|
|
423
|
-
return /* @__PURE__ */ React5.createElement("
|
|
435
|
+
return /* @__PURE__ */ React5.createElement("pre", { className: "p-4 my-2 bg-slate-900 text-slate-100 rounded-lg text-[10px] overflow-auto max-h-40" }, /* @__PURE__ */ React5.createElement("code", null, rawContent));
|
|
424
436
|
}
|
|
425
437
|
const { config } = result;
|
|
426
|
-
|
|
427
|
-
|
|
438
|
+
switch (config.view) {
|
|
439
|
+
case "chart":
|
|
440
|
+
return /* @__PURE__ */ React5.createElement("div", { className: "my-6 p-4 bg-white dark:bg-slate-900 rounded-2xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden" }, config.title && /* @__PURE__ */ React5.createElement("h4", { className: "text-xs font-semibold text-slate-500 mb-4 px-2" }, config.title), /* @__PURE__ */ React5.createElement(
|
|
441
|
+
DynamicChart,
|
|
442
|
+
{
|
|
443
|
+
config: {
|
|
444
|
+
type: config.chartType || "bar",
|
|
445
|
+
data: config.data
|
|
446
|
+
},
|
|
447
|
+
primaryColor,
|
|
448
|
+
accentColor
|
|
449
|
+
}
|
|
450
|
+
));
|
|
451
|
+
case "carousel":
|
|
452
|
+
return /* @__PURE__ */ React5.createElement("div", { className: "my-4" }, /* @__PURE__ */ React5.createElement(ProductCarousel, { products: config.data.map((item) => {
|
|
453
|
+
var _a;
|
|
454
|
+
return __spreadProps(__spreadValues({}, item), {
|
|
455
|
+
image: (_a = item.image) != null ? _a : typeof resolveImage === "function" ? resolveImage(item) : void 0
|
|
456
|
+
});
|
|
457
|
+
}) }));
|
|
458
|
+
case "table":
|
|
459
|
+
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 overflow-auto max-h-[400px]" }, config.title && /* @__PURE__ */ React5.createElement("h4", { className: "text-xs font-semibold text-slate-500 mb-2" }, config.title), /* @__PURE__ */ React5.createElement(DataTable, { config: { type: "table", data: config.data } }));
|
|
460
|
+
default:
|
|
461
|
+
return null;
|
|
428
462
|
}
|
|
429
|
-
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(
|
|
430
|
-
DynamicChart,
|
|
431
|
-
{
|
|
432
|
-
config,
|
|
433
|
-
primaryColor,
|
|
434
|
-
accentColor
|
|
435
|
-
}
|
|
436
|
-
));
|
|
437
463
|
}
|
|
438
464
|
function MessageBubble({
|
|
439
465
|
message,
|
|
@@ -608,9 +634,9 @@ ${match.trim()}
|
|
|
608
634
|
]);
|
|
609
635
|
var _a;
|
|
610
636
|
const lang = (_a = /language-(\w+)/.exec(className != null ? className : "")) == null ? void 0 : _a[1];
|
|
611
|
-
if (!inline && lang === "chart") {
|
|
637
|
+
if (!inline && (lang === "ui" || lang === "chart")) {
|
|
612
638
|
return /* @__PURE__ */ React5.createElement(
|
|
613
|
-
|
|
639
|
+
UIDispatcher,
|
|
614
640
|
{
|
|
615
641
|
rawContent: String(children != null ? children : "").trim(),
|
|
616
642
|
primaryColor,
|
|
@@ -619,6 +645,20 @@ ${match.trim()}
|
|
|
619
645
|
}
|
|
620
646
|
);
|
|
621
647
|
}
|
|
648
|
+
if (!inline && lang === "json") {
|
|
649
|
+
const content = String(children != null ? children : "").trim();
|
|
650
|
+
if (content.includes('"type": "products"') || content.includes('"type":"products"')) {
|
|
651
|
+
return /* @__PURE__ */ React5.createElement(
|
|
652
|
+
UIDispatcher,
|
|
653
|
+
{
|
|
654
|
+
rawContent: content,
|
|
655
|
+
primaryColor,
|
|
656
|
+
accentColor,
|
|
657
|
+
isStreaming
|
|
658
|
+
}
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
622
662
|
if (!inline && lang === "table-loading") {
|
|
623
663
|
return /* @__PURE__ */ React5.createElement("div", { className: "my-5 p-6 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex items-center justify-center gap-3 select-none" }, /* @__PURE__ */ React5.createElement("div", { className: "w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-500 font-medium italic animate-pulse" }, "Generating data table..."));
|
|
624
664
|
}
|
package/dist/server.js
CHANGED
|
@@ -3913,17 +3913,23 @@ var Pipeline = class {
|
|
|
3913
3913
|
async initialize() {
|
|
3914
3914
|
var _a, _b;
|
|
3915
3915
|
if (this.initialised) return;
|
|
3916
|
-
const CHART_MARKER = "<!--
|
|
3916
|
+
const CHART_MARKER = "<!-- UI_PROTOCOL_V3 -->";
|
|
3917
3917
|
const chartInstruction = `
|
|
3918
3918
|
|
|
3919
3919
|
${CHART_MARKER}
|
|
3920
|
-
###
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3920
|
+
### UNIVERSAL UI PROTOCOL:
|
|
3921
|
+
You have a specialized UI system. For any structured data (charts, product lists, or tables), you MUST use a single \`\`\`ui\`\`\` block.
|
|
3922
|
+
1. DECIDE the best "view" for the user's query:
|
|
3923
|
+
- "chart": For statistics, distributions, or comparisons. (Requires "chartType": "pie" | "bar" | "line")
|
|
3924
|
+
- "carousel": For browsing specific product items.
|
|
3925
|
+
- "table": For detailed raw data comparisons.
|
|
3926
|
+
2. OUTPUT a \`\`\`ui\`\`\` block with this schema:
|
|
3927
|
+
{
|
|
3928
|
+
"view": "chart" | "carousel" | "table",
|
|
3929
|
+
"chartType": "pie" | "bar" | "line" (only for view: "chart"),
|
|
3930
|
+
"data": [ ... objects ... ]
|
|
3931
|
+
}
|
|
3932
|
+
3. RULES: Valid JSON only. Data must be FLAT. Never draw charts/tables with text. Choose ONLY the most useful view.`;
|
|
3927
3933
|
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
3928
3934
|
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
|
|
3929
3935
|
}
|
package/dist/server.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
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",
|
|
@@ -7,7 +7,7 @@ import remarkGfm from 'remark-gfm';
|
|
|
7
7
|
import { MessageBubbleProps, Product } from '../types';
|
|
8
8
|
import { SourceCard } from './SourceCard';
|
|
9
9
|
import { ProductCarousel } from './ProductCarousel';
|
|
10
|
-
import { DynamicChart
|
|
10
|
+
import { DynamicChart } from './DynamicChart';
|
|
11
11
|
|
|
12
12
|
// ─── JSON sanitization ────────────────────────────────────────────────────────
|
|
13
13
|
// Order matters: each step is a targeted fix, no step undoes a previous one.
|
|
@@ -210,72 +210,108 @@ function DataTable({ config }: { config: TableConfig }) {
|
|
|
210
210
|
);
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
// ───
|
|
213
|
+
// ─── Universal UI Dispatcher ────────────────────────────────────────────────
|
|
214
|
+
// Handles charts, product carousels, and data tables via a single protocol.
|
|
214
215
|
|
|
215
|
-
|
|
216
|
+
interface UIConfig {
|
|
217
|
+
view: 'chart' | 'carousel' | 'table';
|
|
218
|
+
chartType?: 'pie' | 'bar' | 'line';
|
|
219
|
+
data: Record<string, unknown>[];
|
|
220
|
+
title?: string;
|
|
221
|
+
type?: string;
|
|
222
|
+
items?: Record<string, unknown>[];
|
|
223
|
+
}
|
|
216
224
|
|
|
217
|
-
|
|
225
|
+
function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming }: {
|
|
218
226
|
rawContent: string;
|
|
219
|
-
primaryColor
|
|
220
|
-
accentColor
|
|
227
|
+
primaryColor?: string;
|
|
228
|
+
accentColor?: string;
|
|
221
229
|
isStreaming?: boolean;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }: ChartBlockProps) {
|
|
225
|
-
const result = React.useMemo<
|
|
226
|
-
{ config: ParsedConfig } | { error: string } | { loading: true }
|
|
227
|
-
>(() => {
|
|
230
|
+
}) {
|
|
231
|
+
const result = React.useMemo<{ config: UIConfig } | { error: string } | { loading: true }>(() => {
|
|
228
232
|
if (isStreaming) return { loading: true };
|
|
229
|
-
if (!rawContent || rawContent === 'undefined') return { error: 'Empty visualization config.' };
|
|
230
233
|
try {
|
|
231
234
|
const clean = rawContent
|
|
232
235
|
.replace(/^```[a-z]*\s*/i, '')
|
|
233
236
|
.replace(/```\s*$/, '')
|
|
234
237
|
.trim();
|
|
235
|
-
const
|
|
236
|
-
|
|
238
|
+
const parsed = JSON.parse(sanitizeJson(clean));
|
|
239
|
+
const config = { ...parsed };
|
|
240
|
+
|
|
241
|
+
// Smart Auto-Detection for legacy or simpler formats
|
|
242
|
+
if (!config.view) {
|
|
243
|
+
if (config.type === 'products' || Array.isArray(config.items)) {
|
|
244
|
+
config.view = 'carousel';
|
|
245
|
+
config.data = config.data || config.items || [];
|
|
246
|
+
} else if (['pie', 'bar', 'line'].includes(config.type) || ['pie', 'bar', 'line'].includes(config.chartType)) {
|
|
247
|
+
config.view = 'chart';
|
|
248
|
+
config.chartType = config.chartType || config.type || 'bar';
|
|
249
|
+
config.data = config.data || [];
|
|
250
|
+
} else {
|
|
251
|
+
config.view = 'table';
|
|
252
|
+
config.data = Array.isArray(config.data) ? config.data : (Array.isArray(config) ? config : []);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { config: config as UIConfig };
|
|
237
257
|
} catch (err) {
|
|
238
|
-
console.error('[ChartBlock] Parsing failed.\nError:', err);
|
|
239
258
|
return { error: String(err) };
|
|
240
259
|
}
|
|
241
260
|
}, [rawContent, isStreaming]);
|
|
242
261
|
|
|
243
262
|
if ('loading' in result) {
|
|
244
263
|
return (
|
|
245
|
-
<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
|
|
246
|
-
<div className="w-
|
|
247
|
-
<p className="text-xs text-slate-500 font-medium
|
|
264
|
+
<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 animate-pulse">
|
|
265
|
+
<div className="w-5 h-5 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" />
|
|
266
|
+
<p className="text-xs text-slate-500 font-medium italic">Preparing view...</p>
|
|
248
267
|
</div>
|
|
249
268
|
);
|
|
250
269
|
}
|
|
251
270
|
|
|
252
271
|
if ('error' in result) {
|
|
253
|
-
return
|
|
254
|
-
<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">
|
|
255
|
-
<p className="font-medium mb-1">Failed to render visualization</p>
|
|
256
|
-
<p className="opacity-70 text-xs">The generated configuration is invalid. Check the console for details.</p>
|
|
257
|
-
</div>
|
|
258
|
-
);
|
|
272
|
+
return <pre className="p-4 my-2 bg-slate-900 text-slate-100 rounded-lg text-[10px] overflow-auto max-h-40"><code>{rawContent}</code></pre>;
|
|
259
273
|
}
|
|
260
274
|
|
|
261
275
|
const { config } = result;
|
|
262
276
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
277
|
+
switch (config.view) {
|
|
278
|
+
case 'chart':
|
|
279
|
+
return (
|
|
280
|
+
<div className="my-6 p-4 bg-white dark:bg-slate-900 rounded-2xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden">
|
|
281
|
+
{config.title && <h4 className="text-xs font-semibold text-slate-500 mb-4 px-2">{config.title}</h4>}
|
|
282
|
+
<DynamicChart
|
|
283
|
+
config={{
|
|
284
|
+
type: (config.chartType || 'bar') as 'bar' | 'line' | 'pie',
|
|
285
|
+
data: config.data as unknown as Record<string, string | number>[]
|
|
286
|
+
}}
|
|
287
|
+
primaryColor={primaryColor}
|
|
288
|
+
accentColor={accentColor}
|
|
289
|
+
/>
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
case 'carousel':
|
|
293
|
+
return (
|
|
294
|
+
<div className="my-4">
|
|
295
|
+
<ProductCarousel products={(config.data as unknown as Product[]).map(item => ({
|
|
296
|
+
...item,
|
|
297
|
+
image: item.image ?? (typeof resolveImage === 'function' ? resolveImage(item as unknown as Record<string, unknown>) : undefined)
|
|
298
|
+
}))} />
|
|
299
|
+
</div>
|
|
300
|
+
);
|
|
301
|
+
case 'table':
|
|
302
|
+
return (
|
|
303
|
+
<div className="my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-auto max-h-[400px]">
|
|
304
|
+
{config.title && <h4 className="text-xs font-semibold text-slate-500 mb-2">{config.title}</h4>}
|
|
305
|
+
<DataTable config={{ type: 'table', data: config.data } as TableConfig} />
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
default:
|
|
309
|
+
return null;
|
|
266
310
|
}
|
|
267
|
-
|
|
268
|
-
return (
|
|
269
|
-
<div className="my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm">
|
|
270
|
-
<DynamicChart
|
|
271
|
-
config={config as ChartConfig}
|
|
272
|
-
primaryColor={primaryColor}
|
|
273
|
-
accentColor={accentColor}
|
|
274
|
-
/>
|
|
275
|
-
</div>
|
|
276
|
-
);
|
|
277
311
|
}
|
|
278
312
|
|
|
313
|
+
|
|
314
|
+
|
|
279
315
|
// ─── Main component ───────────────────────────────────────────────────────────
|
|
280
316
|
|
|
281
317
|
export function MessageBubble({
|
|
@@ -457,9 +493,9 @@ export function MessageBubble({
|
|
|
457
493
|
}: React.HTMLAttributes<HTMLElement> & { inline?: boolean }) {
|
|
458
494
|
const lang = /language-(\w+)/.exec(className ?? '')?.[1];
|
|
459
495
|
|
|
460
|
-
if (!inline && lang === 'chart') {
|
|
496
|
+
if (!inline && (lang === 'ui' || lang === 'chart')) {
|
|
461
497
|
return (
|
|
462
|
-
<
|
|
498
|
+
<UIDispatcher
|
|
463
499
|
rawContent={String(children ?? '').trim()}
|
|
464
500
|
primaryColor={primaryColor}
|
|
465
501
|
accentColor={accentColor}
|
|
@@ -468,6 +504,20 @@ export function MessageBubble({
|
|
|
468
504
|
);
|
|
469
505
|
}
|
|
470
506
|
|
|
507
|
+
if (!inline && lang === 'json') {
|
|
508
|
+
const content = String(children ?? '').trim();
|
|
509
|
+
if (content.includes('"type": "products"') || content.includes('"type":"products"')) {
|
|
510
|
+
return (
|
|
511
|
+
<UIDispatcher
|
|
512
|
+
rawContent={content}
|
|
513
|
+
primaryColor={primaryColor}
|
|
514
|
+
accentColor={accentColor}
|
|
515
|
+
isStreaming={isStreaming}
|
|
516
|
+
/>
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
471
521
|
if (!inline && lang === 'table-loading') {
|
|
472
522
|
return (
|
|
473
523
|
<div className="my-5 p-6 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex items-center justify-center gap-3 select-none">
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -101,15 +101,21 @@ export class Pipeline {
|
|
|
101
101
|
|
|
102
102
|
// Augment system prompt with a Universal Visualization Protocol
|
|
103
103
|
// We use a unique marker to ensure this is only injected once but is ALWAYS present.
|
|
104
|
-
const CHART_MARKER = '<!--
|
|
104
|
+
const CHART_MARKER = '<!-- UI_PROTOCOL_V3 -->';
|
|
105
105
|
const chartInstruction = `\n\n${CHART_MARKER}
|
|
106
|
-
###
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
### UNIVERSAL UI PROTOCOL:
|
|
107
|
+
You have a specialized UI system. For any structured data (charts, product lists, or tables), you MUST use a single \`\`\`ui\`\`\` block.
|
|
108
|
+
1. DECIDE the best "view" for the user's query:
|
|
109
|
+
- "chart": For statistics, distributions, or comparisons. (Requires "chartType": "pie" | "bar" | "line")
|
|
110
|
+
- "carousel": For browsing specific product items.
|
|
111
|
+
- "table": For detailed raw data comparisons.
|
|
112
|
+
2. OUTPUT a \`\`\`ui\`\`\` block with this schema:
|
|
113
|
+
{
|
|
114
|
+
"view": "chart" | "carousel" | "table",
|
|
115
|
+
"chartType": "pie" | "bar" | "line" (only for view: "chart"),
|
|
116
|
+
"data": [ ... objects ... ]
|
|
117
|
+
}
|
|
118
|
+
3. RULES: Valid JSON only. Data must be FLAT. Never draw charts/tables with text. Choose ONLY the most useful view.`;
|
|
113
119
|
|
|
114
120
|
if (!this.config.llm.systemPrompt?.includes(CHART_MARKER)) {
|
|
115
121
|
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || '') + chartInstruction;
|