@retrivora-ai/rag-engine 1.5.2 → 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 +59 -20
- package/dist/index.mjs +59 -20
- package/dist/server.js +14 -8
- package/dist/server.mjs +1 -1
- package/package.json +1 -1
- package/src/components/MessageBubble.tsx +94 -42
- 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,38 +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
|
-
const
|
|
449
|
-
const
|
|
447
|
+
const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
|
|
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
|
-
const sanitized = sanitizeJson(rawContent);
|
|
453
|
-
console.error("[ChartBlock] Parsing failed.\nError:", err, "\nSanitized:\n", sanitized);
|
|
454
465
|
return { error: String(err) };
|
|
455
466
|
}
|
|
456
467
|
}, [rawContent, isStreaming]);
|
|
457
468
|
if ("loading" in result) {
|
|
458
|
-
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..."));
|
|
459
470
|
}
|
|
460
471
|
if ("error" in result) {
|
|
461
|
-
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));
|
|
462
473
|
}
|
|
463
474
|
const { config } = result;
|
|
464
|
-
|
|
465
|
-
|
|
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;
|
|
466
499
|
}
|
|
467
|
-
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(
|
|
468
|
-
DynamicChart,
|
|
469
|
-
{
|
|
470
|
-
config,
|
|
471
|
-
primaryColor,
|
|
472
|
-
accentColor
|
|
473
|
-
}
|
|
474
|
-
));
|
|
475
500
|
}
|
|
476
501
|
function MessageBubble({
|
|
477
502
|
message,
|
|
@@ -646,9 +671,9 @@ ${match.trim()}
|
|
|
646
671
|
]);
|
|
647
672
|
var _a;
|
|
648
673
|
const lang = (_a = /language-(\w+)/.exec(className != null ? className : "")) == null ? void 0 : _a[1];
|
|
649
|
-
if (!inline && lang === "chart") {
|
|
674
|
+
if (!inline && (lang === "ui" || lang === "chart")) {
|
|
650
675
|
return /* @__PURE__ */ import_react5.default.createElement(
|
|
651
|
-
|
|
676
|
+
UIDispatcher,
|
|
652
677
|
{
|
|
653
678
|
rawContent: String(children != null ? children : "").trim(),
|
|
654
679
|
primaryColor,
|
|
@@ -657,6 +682,20 @@ ${match.trim()}
|
|
|
657
682
|
}
|
|
658
683
|
);
|
|
659
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
|
+
}
|
|
660
699
|
if (!inline && lang === "table-loading") {
|
|
661
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..."));
|
|
662
701
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -403,38 +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
|
-
const
|
|
412
|
-
const
|
|
410
|
+
const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
|
|
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
|
-
const sanitized = sanitizeJson(rawContent);
|
|
416
|
-
console.error("[ChartBlock] Parsing failed.\nError:", err, "\nSanitized:\n", sanitized);
|
|
417
428
|
return { error: String(err) };
|
|
418
429
|
}
|
|
419
430
|
}, [rawContent, isStreaming]);
|
|
420
431
|
if ("loading" in result) {
|
|
421
|
-
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..."));
|
|
422
433
|
}
|
|
423
434
|
if ("error" in result) {
|
|
424
|
-
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));
|
|
425
436
|
}
|
|
426
437
|
const { config } = result;
|
|
427
|
-
|
|
428
|
-
|
|
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;
|
|
429
462
|
}
|
|
430
|
-
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(
|
|
431
|
-
DynamicChart,
|
|
432
|
-
{
|
|
433
|
-
config,
|
|
434
|
-
primaryColor,
|
|
435
|
-
accentColor
|
|
436
|
-
}
|
|
437
|
-
));
|
|
438
463
|
}
|
|
439
464
|
function MessageBubble({
|
|
440
465
|
message,
|
|
@@ -609,9 +634,9 @@ ${match.trim()}
|
|
|
609
634
|
]);
|
|
610
635
|
var _a;
|
|
611
636
|
const lang = (_a = /language-(\w+)/.exec(className != null ? className : "")) == null ? void 0 : _a[1];
|
|
612
|
-
if (!inline && lang === "chart") {
|
|
637
|
+
if (!inline && (lang === "ui" || lang === "chart")) {
|
|
613
638
|
return /* @__PURE__ */ React5.createElement(
|
|
614
|
-
|
|
639
|
+
UIDispatcher,
|
|
615
640
|
{
|
|
616
641
|
rawContent: String(children != null ? children : "").trim(),
|
|
617
642
|
primaryColor,
|
|
@@ -620,6 +645,20 @@ ${match.trim()}
|
|
|
620
645
|
}
|
|
621
646
|
);
|
|
622
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
|
+
}
|
|
623
662
|
if (!inline && lang === "table-loading") {
|
|
624
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..."));
|
|
625
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,70 +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
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
+
const clean = rawContent
|
|
235
|
+
.replace(/^```[a-z]*\s*/i, '')
|
|
236
|
+
.replace(/```\s*$/, '')
|
|
237
|
+
.trim();
|
|
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 };
|
|
234
257
|
} catch (err) {
|
|
235
|
-
const sanitized = sanitizeJson(rawContent);
|
|
236
|
-
console.error('[ChartBlock] Parsing failed.\nError:', err, '\nSanitized:\n', sanitized);
|
|
237
258
|
return { error: String(err) };
|
|
238
259
|
}
|
|
239
260
|
}, [rawContent, isStreaming]);
|
|
240
261
|
|
|
241
262
|
if ('loading' in result) {
|
|
242
263
|
return (
|
|
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
|
|
244
|
-
<div className="w-
|
|
245
|
-
<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>
|
|
246
267
|
</div>
|
|
247
268
|
);
|
|
248
269
|
}
|
|
249
270
|
|
|
250
271
|
if ('error' in result) {
|
|
251
|
-
return
|
|
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">
|
|
253
|
-
<p className="font-medium mb-1">Failed to render visualization</p>
|
|
254
|
-
<p className="opacity-70 text-xs">The generated configuration is invalid. Check the console for details.</p>
|
|
255
|
-
</div>
|
|
256
|
-
);
|
|
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>;
|
|
257
273
|
}
|
|
258
274
|
|
|
259
275
|
const { config } = result;
|
|
260
276
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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;
|
|
264
310
|
}
|
|
265
|
-
|
|
266
|
-
return (
|
|
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">
|
|
268
|
-
<DynamicChart
|
|
269
|
-
config={config as ChartConfig}
|
|
270
|
-
primaryColor={primaryColor}
|
|
271
|
-
accentColor={accentColor}
|
|
272
|
-
/>
|
|
273
|
-
</div>
|
|
274
|
-
);
|
|
275
311
|
}
|
|
276
312
|
|
|
313
|
+
|
|
314
|
+
|
|
277
315
|
// ─── Main component ───────────────────────────────────────────────────────────
|
|
278
316
|
|
|
279
317
|
export function MessageBubble({
|
|
@@ -455,9 +493,9 @@ export function MessageBubble({
|
|
|
455
493
|
}: React.HTMLAttributes<HTMLElement> & { inline?: boolean }) {
|
|
456
494
|
const lang = /language-(\w+)/.exec(className ?? '')?.[1];
|
|
457
495
|
|
|
458
|
-
if (!inline && lang === 'chart') {
|
|
496
|
+
if (!inline && (lang === 'ui' || lang === 'chart')) {
|
|
459
497
|
return (
|
|
460
|
-
<
|
|
498
|
+
<UIDispatcher
|
|
461
499
|
rawContent={String(children ?? '').trim()}
|
|
462
500
|
primaryColor={primaryColor}
|
|
463
501
|
accentColor={accentColor}
|
|
@@ -466,6 +504,20 @@ export function MessageBubble({
|
|
|
466
504
|
);
|
|
467
505
|
}
|
|
468
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
|
+
|
|
469
521
|
if (!inline && lang === 'table-loading') {
|
|
470
522
|
return (
|
|
471
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;
|