@retrivora-ai/rag-engine 1.5.5 → 1.5.6
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-Q4MDH6C4.mjs → chunk-ID2Q4CF7.mjs} +2 -0
- package/dist/handlers/index.js +2 -0
- package/dist/handlers/index.mjs +1 -1
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +109 -40
- package/dist/index.mjs +109 -40
- package/dist/server.js +2 -0
- package/dist/server.mjs +1 -1
- package/package.json +1 -1
- package/src/components/ChatWindow.tsx +26 -0
- package/src/components/DynamicChart.tsx +32 -16
- package/src/components/MessageBubble.tsx +95 -27
- package/src/components/ProductCarousel.tsx +2 -2
- package/src/core/Pipeline.ts +2 -0
- package/src/types/props.ts +3 -0
|
@@ -5,6 +5,7 @@ import { Bot, User, ChevronDown, ChevronRight } from 'lucide-react';
|
|
|
5
5
|
import ReactMarkdown from 'react-markdown';
|
|
6
6
|
import remarkGfm from 'remark-gfm';
|
|
7
7
|
import { MessageBubbleProps, Product } from '../types';
|
|
8
|
+
import { ChatViewportSize } from '../types/props';
|
|
8
9
|
import { SourceCard } from './SourceCard';
|
|
9
10
|
import { ProductCarousel } from './ProductCarousel';
|
|
10
11
|
import { DynamicChart } from './DynamicChart';
|
|
@@ -90,6 +91,17 @@ function sanitizeJson(raw: string): string {
|
|
|
90
91
|
return s;
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
function extractLikelyJsonBlock(raw: string): string {
|
|
95
|
+
const trimmed = raw.trim();
|
|
96
|
+
const objectStart = trimmed.indexOf('{');
|
|
97
|
+
const arrayStart = trimmed.indexOf('[');
|
|
98
|
+
|
|
99
|
+
const starts = [objectStart, arrayStart].filter((index) => index >= 0);
|
|
100
|
+
if (starts.length === 0) return trimmed;
|
|
101
|
+
|
|
102
|
+
return trimmed.slice(Math.min(...starts));
|
|
103
|
+
}
|
|
104
|
+
|
|
93
105
|
// ─── Tiny helpers ─────────────────────────────────────────────────────────────
|
|
94
106
|
|
|
95
107
|
function resolveImage(data: Record<string, unknown>): string | undefined {
|
|
@@ -130,7 +142,9 @@ interface TableConfig {
|
|
|
130
142
|
data: Record<string, unknown>[];
|
|
131
143
|
}
|
|
132
144
|
|
|
133
|
-
function DataTable({ config }: { config: TableConfig }) {
|
|
145
|
+
function DataTable({ config, viewportSize = 'large' }: { config: TableConfig; viewportSize?: ChatViewportSize }) {
|
|
146
|
+
const isCompact = viewportSize === 'compact';
|
|
147
|
+
const isMedium = viewportSize === 'medium';
|
|
134
148
|
const keys = React.useMemo<string[]>(() => {
|
|
135
149
|
// Priority 1: explicit columns list
|
|
136
150
|
if (Array.isArray(config.columns) && config.columns.length) {
|
|
@@ -169,13 +183,13 @@ function DataTable({ config }: { config: TableConfig }) {
|
|
|
169
183
|
)}
|
|
170
184
|
|
|
171
185
|
<div className="overflow-x-auto">
|
|
172
|
-
<table className=
|
|
186
|
+
<table className={`w-full text-left border-collapse ${isCompact ? 'min-w-[240px] text-xs' : isMedium ? 'min-w-[280px] text-[13px]' : 'min-w-[320px] text-sm'}`}>
|
|
173
187
|
<thead className="bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10">
|
|
174
188
|
<tr>
|
|
175
189
|
{keys.map(k => (
|
|
176
190
|
<th
|
|
177
191
|
key={k}
|
|
178
|
-
className=
|
|
192
|
+
className={`${isCompact ? 'px-2.5 py-2' : 'px-4 py-3'} font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap`}
|
|
179
193
|
>
|
|
180
194
|
{k}
|
|
181
195
|
</th>
|
|
@@ -191,7 +205,7 @@ function DataTable({ config }: { config: TableConfig }) {
|
|
|
191
205
|
{keys.map(k => (
|
|
192
206
|
<td
|
|
193
207
|
key={k}
|
|
194
|
-
className=
|
|
208
|
+
className={`${isCompact ? 'px-2.5 py-2' : 'px-4 py-3'} text-slate-600 dark:text-white/70 whitespace-nowrap`}
|
|
195
209
|
>
|
|
196
210
|
{formatCell(row[k])}
|
|
197
211
|
</td>
|
|
@@ -229,12 +243,45 @@ interface UIConfig {
|
|
|
229
243
|
items?: Record<string, unknown>[];
|
|
230
244
|
}
|
|
231
245
|
|
|
232
|
-
|
|
246
|
+
interface RawUIConfig extends Omit<UIConfig, 'data'> {
|
|
247
|
+
data: Array<Record<string, unknown> | unknown[]>;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function normalizeTabularRows(
|
|
251
|
+
rows: Array<Record<string, unknown> | unknown[]> | undefined,
|
|
252
|
+
columns?: string[],
|
|
253
|
+
): Record<string, unknown>[] {
|
|
254
|
+
if (!Array.isArray(rows)) return [];
|
|
255
|
+
|
|
256
|
+
return rows
|
|
257
|
+
.map((row) => {
|
|
258
|
+
if (Array.isArray(row)) {
|
|
259
|
+
const keys = Array.isArray(columns) && columns.length > 0
|
|
260
|
+
? columns
|
|
261
|
+
: row.map((_, index) => `column_${index + 1}`);
|
|
262
|
+
|
|
263
|
+
return keys.reduce<Record<string, unknown>>((acc, key, index) => {
|
|
264
|
+
acc[key] = row[index];
|
|
265
|
+
return acc;
|
|
266
|
+
}, {});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (row && typeof row === 'object') {
|
|
270
|
+
return row as Record<string, unknown>;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return { value: row };
|
|
274
|
+
})
|
|
275
|
+
.filter((row) => Object.keys(row).length > 0);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart, viewportSize = 'large' }: {
|
|
233
279
|
rawContent: string;
|
|
234
280
|
primaryColor?: string;
|
|
235
281
|
accentColor?: string;
|
|
236
282
|
isStreaming?: boolean;
|
|
237
283
|
onAddToCart?: (product: Product) => void;
|
|
284
|
+
viewportSize?: ChatViewportSize;
|
|
238
285
|
}) {
|
|
239
286
|
const result = React.useMemo<{ config: UIConfig } | { error: string } | { loading: true }>(() => {
|
|
240
287
|
if (isStreaming) return { loading: true };
|
|
@@ -243,17 +290,26 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
243
290
|
.replace(/^```[a-z]*\s*/i, '')
|
|
244
291
|
.replace(/```\s*$/, '')
|
|
245
292
|
.trim();
|
|
246
|
-
const parsed = JSON.parse(sanitizeJson(clean));
|
|
247
|
-
const config = { ...parsed }
|
|
293
|
+
const parsed = JSON.parse(sanitizeJson(extractLikelyJsonBlock(clean)));
|
|
294
|
+
const config = { ...parsed } as Partial<RawUIConfig> & Record<string, unknown>;
|
|
248
295
|
|
|
249
296
|
// Smart Auto-Detection for legacy or simpler formats
|
|
250
297
|
if (!config.view) {
|
|
251
298
|
if (config.type === 'products' || Array.isArray(config.items)) {
|
|
252
299
|
config.view = 'carousel';
|
|
253
300
|
config.data = config.data || config.items || [];
|
|
254
|
-
} else if (
|
|
301
|
+
} else if (
|
|
302
|
+
(typeof config.type === 'string' && ['pie', 'bar', 'line'].includes(config.type)) ||
|
|
303
|
+
(typeof config.chartType === 'string' && ['pie', 'bar', 'line'].includes(config.chartType))
|
|
304
|
+
) {
|
|
305
|
+
const resolvedChartType =
|
|
306
|
+
config.chartType === 'pie' || config.chartType === 'bar' || config.chartType === 'line'
|
|
307
|
+
? config.chartType
|
|
308
|
+
: config.type === 'pie' || config.type === 'bar' || config.type === 'line'
|
|
309
|
+
? config.type
|
|
310
|
+
: 'bar';
|
|
255
311
|
config.view = 'chart';
|
|
256
|
-
config.chartType =
|
|
312
|
+
config.chartType = resolvedChartType;
|
|
257
313
|
config.data = config.data || [];
|
|
258
314
|
} else {
|
|
259
315
|
config.view = 'table';
|
|
@@ -261,7 +317,12 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
261
317
|
}
|
|
262
318
|
}
|
|
263
319
|
|
|
264
|
-
|
|
320
|
+
const normalizedConfig: UIConfig = {
|
|
321
|
+
...(config as Omit<UIConfig, 'data'>),
|
|
322
|
+
data: normalizeTabularRows(config.data, config.columns),
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
return { config: normalizedConfig };
|
|
265
326
|
} catch (err) {
|
|
266
327
|
return { error: String(err) };
|
|
267
328
|
}
|
|
@@ -281,16 +342,17 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
281
342
|
}
|
|
282
343
|
|
|
283
344
|
const { config } = result;
|
|
345
|
+
const isCompact = viewportSize === 'compact';
|
|
284
346
|
const hasInsights = Array.isArray(config.insights) && config.insights.length > 0;
|
|
285
347
|
const hasDescription = typeof config.description === 'string' && config.description.trim().length > 0;
|
|
286
348
|
|
|
287
349
|
switch (config.view) {
|
|
288
350
|
case 'chart':
|
|
289
351
|
return (
|
|
290
|
-
<div className=
|
|
291
|
-
{config.title && <h4 className=
|
|
352
|
+
<div className={`${isCompact ? 'my-4 p-3' : 'my-6 p-4'} bg-white dark:bg-slate-900 rounded-2xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden`}>
|
|
353
|
+
{config.title && <h4 className={`${isCompact ? 'text-[11px] mb-3' : 'text-xs mb-4'} font-semibold text-slate-500 px-2`}>{config.title}</h4>}
|
|
292
354
|
{hasDescription && (
|
|
293
|
-
<p className=
|
|
355
|
+
<p className={`px-2 ${isCompact ? 'text-xs' : 'text-sm'} text-slate-500 dark:text-white/60`}>{config.description}</p>
|
|
294
356
|
)}
|
|
295
357
|
<DynamicChart
|
|
296
358
|
config={{
|
|
@@ -302,13 +364,14 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
302
364
|
}}
|
|
303
365
|
primaryColor={primaryColor}
|
|
304
366
|
accentColor={accentColor}
|
|
367
|
+
viewportSize={viewportSize}
|
|
305
368
|
/>
|
|
306
369
|
{hasInsights && (
|
|
307
370
|
<div className="mt-4 flex flex-wrap gap-2 px-2">
|
|
308
371
|
{config.insights?.map((insight) => (
|
|
309
372
|
<span
|
|
310
373
|
key={insight}
|
|
311
|
-
className=
|
|
374
|
+
className={`rounded-full border border-emerald-200 bg-emerald-50 ${isCompact ? 'px-2.5 py-1 text-[10px]' : 'px-3 py-1 text-[11px]'} font-medium text-emerald-700 dark:border-emerald-500/20 dark:bg-emerald-500/10 dark:text-emerald-300`}
|
|
312
375
|
>
|
|
313
376
|
{insight}
|
|
314
377
|
</span>
|
|
@@ -320,9 +383,9 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
320
383
|
case 'carousel':
|
|
321
384
|
return (
|
|
322
385
|
<div className="my-4">
|
|
323
|
-
{config.title && <h4 className=
|
|
386
|
+
{config.title && <h4 className={`${isCompact ? 'mb-1.5 text-[11px]' : 'mb-2 text-xs'} font-semibold text-slate-500`}>{config.title}</h4>}
|
|
324
387
|
{hasDescription && (
|
|
325
|
-
<p className=
|
|
388
|
+
<p className={`mb-3 ${isCompact ? 'text-xs' : 'text-sm'} text-slate-500 dark:text-white/60`}>{config.description}</p>
|
|
326
389
|
)}
|
|
327
390
|
<ProductCarousel products={(config.data as unknown as Product[]).map(item => ({
|
|
328
391
|
...item,
|
|
@@ -332,10 +395,10 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
332
395
|
);
|
|
333
396
|
case 'table':
|
|
334
397
|
return (
|
|
335
|
-
<div className=
|
|
336
|
-
{config.title && <h4 className=
|
|
398
|
+
<div className={`${isCompact ? 'my-3 p-3 max-h-[320px]' : 'my-4 p-4 max-h-[400px]'} bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-auto`}>
|
|
399
|
+
{config.title && <h4 className={`${isCompact ? 'text-[11px]' : 'text-xs'} font-semibold text-slate-500 mb-2`}>{config.title}</h4>}
|
|
337
400
|
{hasDescription && (
|
|
338
|
-
<p className=
|
|
401
|
+
<p className={`mb-3 ${isCompact ? 'text-xs' : 'text-sm'} text-slate-500 dark:text-white/60`}>{config.description}</p>
|
|
339
402
|
)}
|
|
340
403
|
<DataTable config={{
|
|
341
404
|
type: 'table',
|
|
@@ -345,7 +408,7 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
345
408
|
dataKeys: config.dataKeys,
|
|
346
409
|
xAxisKey: config.xAxisKey,
|
|
347
410
|
data: config.data,
|
|
348
|
-
}} />
|
|
411
|
+
}} viewportSize={viewportSize} />
|
|
349
412
|
</div>
|
|
350
413
|
);
|
|
351
414
|
default:
|
|
@@ -364,8 +427,11 @@ export function MessageBubble({
|
|
|
364
427
|
primaryColor = '#6366f1',
|
|
365
428
|
accentColor = '#8b5cf6',
|
|
366
429
|
onAddToCart,
|
|
430
|
+
viewportSize = 'large',
|
|
367
431
|
}: MessageBubbleProps) {
|
|
368
432
|
const isUser = message.role === 'user';
|
|
433
|
+
const isCompact = viewportSize === 'compact';
|
|
434
|
+
const isMedium = viewportSize === 'medium';
|
|
369
435
|
const [showSources, setShowSources] = React.useState(false);
|
|
370
436
|
const hasStructuredProductBlock = React.useMemo(
|
|
371
437
|
() => /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|type)"\s*:\s*"(?:carousel|products)"/i.test(message.content),
|
|
@@ -548,6 +614,7 @@ export function MessageBubble({
|
|
|
548
614
|
accentColor={accentColor}
|
|
549
615
|
isStreaming={isStreaming}
|
|
550
616
|
onAddToCart={onAddToCart}
|
|
617
|
+
viewportSize={viewportSize}
|
|
551
618
|
/>
|
|
552
619
|
);
|
|
553
620
|
}
|
|
@@ -562,6 +629,7 @@ export function MessageBubble({
|
|
|
562
629
|
accentColor={accentColor}
|
|
563
630
|
isStreaming={isStreaming}
|
|
564
631
|
onAddToCart={onAddToCart}
|
|
632
|
+
viewportSize={viewportSize}
|
|
565
633
|
/>
|
|
566
634
|
);
|
|
567
635
|
}
|
|
@@ -587,27 +655,27 @@ export function MessageBubble({
|
|
|
587
655
|
);
|
|
588
656
|
},
|
|
589
657
|
}),
|
|
590
|
-
[primaryColor, accentColor, isStreaming, onAddToCart],
|
|
658
|
+
[primaryColor, accentColor, isStreaming, onAddToCart, viewportSize],
|
|
591
659
|
);
|
|
592
660
|
|
|
593
661
|
// ── Render ─────────────────────────────────────────────────────────────────
|
|
594
662
|
return (
|
|
595
|
-
<div className={`flex gap-3 ${isUser ? 'flex-row-reverse' : 'flex-row'} items-start`}>
|
|
663
|
+
<div className={`flex ${isCompact ? 'gap-2' : 'gap-3'} ${isUser ? 'flex-row-reverse' : 'flex-row'} items-start`}>
|
|
596
664
|
{/* Avatar */}
|
|
597
665
|
<div
|
|
598
|
-
className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center shadow-lg ${isUser
|
|
666
|
+
className={`flex-shrink-0 ${isCompact ? 'w-7 h-7' : 'w-8 h-8'} rounded-full flex items-center justify-center shadow-lg ${isUser
|
|
599
667
|
? 'text-white'
|
|
600
668
|
: 'bg-slate-100 dark:bg-white/10 text-slate-700 dark:text-white/80'
|
|
601
669
|
}`}
|
|
602
670
|
style={isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}}
|
|
603
671
|
>
|
|
604
|
-
{isUser ? <User className=
|
|
672
|
+
{isUser ? <User className={`${isCompact ? 'w-3.5 h-3.5' : 'w-4 h-4'} text-white`} /> : <Bot className={`${isCompact ? 'w-3.5 h-3.5' : 'w-4 h-4'}`} />}
|
|
605
673
|
</div>
|
|
606
674
|
|
|
607
|
-
<div className={`flex flex-col gap-1 max-w-[90%] ${isUser ? 'items-end' : 'items-start'}`}>
|
|
675
|
+
<div className={`flex flex-col gap-1 ${isCompact ? 'max-w-[94%]' : isMedium ? 'max-w-[92%]' : 'max-w-[90%]'} ${isUser ? 'items-end' : 'items-start'}`}>
|
|
608
676
|
{/* Bubble */}
|
|
609
677
|
<div
|
|
610
|
-
className={`relative px-4 py-3 rounded-2xl
|
|
678
|
+
className={`relative ${isCompact ? 'px-3 py-2.5 text-[13px]' : 'px-4 py-3 text-sm'} rounded-2xl leading-relaxed shadow-sm dark:shadow-lg ${isUser
|
|
611
679
|
? 'text-white rounded-tr-sm'
|
|
612
680
|
: 'bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm'
|
|
613
681
|
}`}
|
|
@@ -622,7 +690,7 @@ export function MessageBubble({
|
|
|
622
690
|
<span className="w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" />
|
|
623
691
|
</span>
|
|
624
692
|
) : (
|
|
625
|
-
<div className={`prose prose-sm max-w-none ${isUser ? 'prose-invert' : 'dark:prose-invert'}`}>
|
|
693
|
+
<div className={`prose ${isCompact ? 'prose-xs' : 'prose-sm'} max-w-none break-words ${isUser ? 'prose-invert' : 'dark:prose-invert'}`}>
|
|
626
694
|
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
|
627
695
|
{processedMarkdown}
|
|
628
696
|
</ReactMarkdown>
|
|
@@ -44,7 +44,7 @@ export function ProductCarousel({ products, primaryColor = '#6366f1', onAddToCar
|
|
|
44
44
|
// Single product: No carousel needed
|
|
45
45
|
if (products.length === 1) {
|
|
46
46
|
return (
|
|
47
|
-
<div className="my-4 w-[
|
|
47
|
+
<div className="my-4 w-[min(88%,18rem)] min-w-0 max-w-full animate-fade-in-up">
|
|
48
48
|
<ProductCard product={products[0]} primaryColor={primaryColor} onAddToCart={onAddToCart} />
|
|
49
49
|
</div>
|
|
50
50
|
);
|
|
@@ -86,7 +86,7 @@ export function ProductCarousel({ products, primaryColor = '#6366f1', onAddToCar
|
|
|
86
86
|
{products.map((product) => (
|
|
87
87
|
<div
|
|
88
88
|
key={product.id}
|
|
89
|
-
className="snap-start flex-shrink-0 w-[
|
|
89
|
+
className="snap-start flex-shrink-0 w-[min(88%,18rem)] min-w-[11rem] max-w-full"
|
|
90
90
|
>
|
|
91
91
|
<ProductCard product={product} primaryColor={primaryColor} onAddToCart={onAddToCart} />
|
|
92
92
|
</div>
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -138,6 +138,8 @@ When the user asks for a visual representation, comparison, distribution, breakd
|
|
|
138
138
|
- Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
|
|
139
139
|
- Put any short natural-language explanation before or after the \`\`\`ui\`\`\` block, but keep it concise.
|
|
140
140
|
- Return valid JSON inside the \`\`\`ui\`\`\` block.
|
|
141
|
+
- If the user explicitly asks for a pie chart, return "view": "chart" with "chartType": "pie", not a table fallback.
|
|
142
|
+
- Do not prefix the JSON with labels like "Table View", "Alternative", or explanatory text inside the code block.
|
|
141
143
|
|
|
142
144
|
5. EXAMPLE
|
|
143
145
|
User: "provide me the distribution of products across categories and highlight which ones are in stock in a pie chart"
|
package/src/types/props.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { Product, VectorMatch } from './index';
|
|
|
3
3
|
import { RagMessage } from './chat';
|
|
4
4
|
import { UIConfig } from '../config/RagConfig';
|
|
5
5
|
|
|
6
|
+
export type ChatViewportSize = 'compact' | 'medium' | 'large';
|
|
7
|
+
|
|
6
8
|
export interface ChatWindowProps {
|
|
7
9
|
/** Additional className for the wrapper div */
|
|
8
10
|
className?: string;
|
|
@@ -40,6 +42,7 @@ export interface MessageBubbleProps {
|
|
|
40
42
|
primaryColor: string;
|
|
41
43
|
accentColor: string;
|
|
42
44
|
onAddToCart?: (product: Product) => void;
|
|
45
|
+
viewportSize?: ChatViewportSize;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
export interface ProductCardProps {
|