@retrivora-ai/rag-engine 1.8.1 → 1.8.3

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.
Files changed (80) hide show
  1. package/dist/{ILLMProvider-BfRgI1Xh.d.mts → ILLMProvider-BOJFz3Na.d.mts} +47 -1
  2. package/dist/{ILLMProvider-BfRgI1Xh.d.ts → ILLMProvider-BOJFz3Na.d.ts} +47 -1
  3. package/dist/handlers/index.d.mts +2 -2
  4. package/dist/handlers/index.d.ts +2 -2
  5. package/dist/handlers/index.js +509 -162
  6. package/dist/handlers/index.mjs +5849 -15
  7. package/dist/{index-1Z4GuYBi.d.ts → index-BwpcaziY.d.ts} +4 -2
  8. package/dist/{index-BV0z5mb6.d.mts → index-D3V9Et2M.d.mts} +4 -2
  9. package/dist/index.d.mts +30 -16
  10. package/dist/index.d.ts +30 -16
  11. package/dist/index.js +1999 -1210
  12. package/dist/index.mjs +2041 -1205
  13. package/dist/server.d.mts +21 -25
  14. package/dist/server.d.ts +21 -25
  15. package/dist/server.js +536 -163
  16. package/dist/server.mjs +5923 -67
  17. package/package.json +9 -7
  18. package/src/app/api/upload/route.ts +4 -0
  19. package/src/app/constants.tsx +2 -2
  20. package/src/app/globals.css +35 -11
  21. package/src/app/page.tsx +12 -321
  22. package/src/components/AmbientBackground.tsx +29 -0
  23. package/src/components/ArchitectureCard.tsx +17 -0
  24. package/src/components/ArchitectureCardsSection.tsx +15 -0
  25. package/src/components/ChatWidget.tsx +0 -1
  26. package/src/components/ChatWindow.tsx +32 -0
  27. package/src/components/CodeViewer.tsx +51 -0
  28. package/src/components/ConfigProvider.tsx +1 -0
  29. package/src/components/DocViewer.tsx +37 -0
  30. package/src/components/DocumentUpload.tsx +44 -1
  31. package/src/components/Documentation.tsx +58 -0
  32. package/src/components/DynamicChart.tsx +27 -2
  33. package/src/components/Hero.tsx +59 -0
  34. package/src/components/HourglassLoader.tsx +87 -0
  35. package/src/components/Lifecycle.tsx +37 -0
  36. package/src/components/MarkdownComponents.tsx +143 -0
  37. package/src/components/MessageBubble.tsx +91 -1012
  38. package/src/components/Navbar.tsx +55 -0
  39. package/src/components/ObservabilityPanel.tsx +374 -0
  40. package/src/components/ProductCard.tsx +3 -1
  41. package/src/components/UIDispatcher.tsx +341 -0
  42. package/src/components/VisualizationRenderer.tsx +48 -26
  43. package/src/config/ConfigBuilder.ts +38 -1
  44. package/src/core/LangChainAgent.ts +1 -4
  45. package/src/core/Pipeline.ts +209 -86
  46. package/src/core/QueryProcessor.ts +65 -0
  47. package/src/handlers/index.ts +72 -12
  48. package/src/hooks/useRagChat.ts +19 -9
  49. package/src/index.ts +9 -1
  50. package/src/providers/vectordb/MultiTablePostgresProvider.ts +150 -64
  51. package/src/rag/Reranker.ts +99 -6
  52. package/src/types/chat.ts +2 -0
  53. package/src/types/index.ts +52 -0
  54. package/src/types/props.ts +9 -1
  55. package/src/utils/ProductExtractor.ts +347 -0
  56. package/src/utils/UITransformer.ts +4 -53
  57. package/src/utils/synonyms.ts +78 -0
  58. package/dist/ChromaDBProvider-MIDOR4FW.mjs +0 -8
  59. package/dist/MilvusProvider-U7SKC27V.mjs +0 -8
  60. package/dist/MongoDBProvider-YNKC7EJ6.mjs +0 -8
  61. package/dist/MultiTablePostgresProvider-YY7LPNJK.mjs +0 -8
  62. package/dist/PineconeProvider-QZNRKTN2.mjs +0 -8
  63. package/dist/QdrantProvider-RLJTNGPY.mjs +0 -8
  64. package/dist/RedisProvider-SR65SCKV.mjs +0 -8
  65. package/dist/SimpleGraphProvider-SLOXO4M7.mjs +0 -62
  66. package/dist/UniversalVectorProvider-IN67OS56.mjs +0 -9
  67. package/dist/WeaviateProvider-5FWDFITI.mjs +0 -8
  68. package/dist/chunk-5AJ4XHLW.mjs +0 -201
  69. package/dist/chunk-5YGUXK7Z.mjs +0 -80
  70. package/dist/chunk-BFYLQYQU.mjs +0 -3985
  71. package/dist/chunk-CFVEZTBJ.mjs +0 -102
  72. package/dist/chunk-IMP6FUCY.mjs +0 -30
  73. package/dist/chunk-LR3VMDVK.mjs +0 -157
  74. package/dist/chunk-M6JSPGAR.mjs +0 -117
  75. package/dist/chunk-PSFPZXHX.mjs +0 -245
  76. package/dist/chunk-R3RGUMHE.mjs +0 -218
  77. package/dist/chunk-U55XRW3U.mjs +0 -96
  78. package/dist/chunk-VUQJVIJT.mjs +0 -148
  79. package/dist/chunk-X4TOT24V.mjs +0 -89
  80. package/dist/chunk-YLTMFW4M.mjs +0 -49
@@ -0,0 +1,341 @@
1
+ import React from 'react';
2
+ import { Product } from '../types';
3
+ import { ChatViewportSize } from '../types/props';
4
+ import { DynamicChart } from './DynamicChart';
5
+ import { ProductCarousel } from './ProductCarousel';
6
+ import { VisualizationRenderer } from './VisualizationRenderer';
7
+ import { extractLikelyJsonBlock, sanitizeJson, resolveImage } from '../utils/ProductExtractor';
8
+
9
+ // ─── Universal UI Dispatcher ────────────────────────────────────────────────
10
+
11
+ interface UIConfig {
12
+ view: 'chart' | 'carousel' | 'table' | 'none';
13
+ chartType?: 'pie' | 'bar' | 'line';
14
+ xAxisKey?: string;
15
+ dataKeys?: string[];
16
+ columns?: string[];
17
+ colors?: string[];
18
+ data: Record<string, unknown>[];
19
+ title?: string;
20
+ description?: string;
21
+ insights?: string[];
22
+ type?: string;
23
+ items?: Record<string, unknown>[];
24
+ rows?: Record<string, unknown>[];
25
+ products?: Record<string, unknown>[];
26
+ results?: Record<string, unknown>[];
27
+
28
+ // V7 Protocol Fields
29
+ carousel?: {
30
+ items: Record<string, unknown>[];
31
+ };
32
+ chart?: {
33
+ type: 'pie' | 'bar' | 'line';
34
+ xKey: string;
35
+ yKeys: string[];
36
+ data: Record<string, unknown>[];
37
+ };
38
+ table?: {
39
+ columns: string[];
40
+ rows?: Record<string, unknown>[];
41
+ data?: Record<string, unknown>[];
42
+ };
43
+ }
44
+
45
+ interface RawUIConfig extends Omit<UIConfig, 'data' | 'items' | 'rows' | 'products' | 'results' | 'carousel' | 'chart' | 'table'> {
46
+ data: Array<Record<string, unknown> | unknown[]>;
47
+ items?: Array<Record<string, unknown> | unknown[]>;
48
+ rows?: Array<Record<string, unknown> | unknown[]>;
49
+ products?: Array<Record<string, unknown> | unknown[]>;
50
+ results?: Array<Record<string, unknown> | unknown[]>;
51
+
52
+ // V7 Protocol Fields (Raw)
53
+ carousel?: {
54
+ items: Array<Record<string, unknown> | unknown[]>;
55
+ };
56
+ chart?: {
57
+ type: 'pie' | 'bar' | 'line';
58
+ xKey: string;
59
+ yKeys: string[];
60
+ data: Array<Record<string, unknown> | unknown[]>;
61
+ };
62
+ table?: {
63
+ columns: string[];
64
+ rows?: Array<Record<string, unknown> | unknown[]>;
65
+ data?: Array<Record<string, unknown> | unknown[]>;
66
+ };
67
+ }
68
+
69
+ function normalizeTabularRows(
70
+ rows: Array<Record<string, unknown> | unknown[]> | undefined,
71
+ columns?: string[],
72
+ ): Record<string, unknown>[] {
73
+ if (!Array.isArray(rows)) return [];
74
+
75
+ // Common synonyms for mapping natural language headers to technical keys
76
+ const SYNONYMS: Record<string, string[]> = {
77
+ name: ['product', 'item', 'title', 'label', 'heading', 'product name', 'item name'],
78
+ price: ['cost', 'amount', 'msrp', 'price', 'rate', 'value', 'price_usd'],
79
+ brand: ['manufacturer', 'vendor', 'make', 'company', 'brand_name', 'supplier'],
80
+ image: ['imageUrl', 'thumbnail', 'img', 'url', 'photo', 'picture', 'media'],
81
+ stock: ['inventory', 'quantity', 'count', 'availability', 'stock_level', 'in stock', 'status'],
82
+ };
83
+
84
+ return rows
85
+ .map((row) => {
86
+ if (Array.isArray(row)) {
87
+ const keys = Array.isArray(columns) && columns.length > 0
88
+ ? columns
89
+ : row.map((_, index) => `column_${index + 1}`);
90
+
91
+ return keys.reduce<Record<string, unknown>>((acc, key, index) => {
92
+ acc[key] = row[index];
93
+ return acc;
94
+ }, {});
95
+ }
96
+
97
+ if (row && typeof row === 'object') {
98
+ const result: Record<string, unknown> = { ...row };
99
+
100
+ // Self-Healing: If columns are provided, ensure the row has keys matching those columns
101
+ if (Array.isArray(columns)) {
102
+ columns.forEach(col => {
103
+ if (result[col] !== undefined) return; // Already matches
104
+
105
+ // Try to find a match among row keys
106
+ const rowKeys = Object.keys(row);
107
+ const colLower = col.toLowerCase().replace(/_/g, ' ');
108
+
109
+ // 1. Direct case-insensitive / space-to-underscore match
110
+ const foundKey = rowKeys.find(rk => {
111
+ const rkLower = rk.toLowerCase().replace(/_/g, ' ');
112
+ return rkLower === colLower || rkLower.includes(colLower) || colLower.includes(rkLower);
113
+ });
114
+
115
+ if (foundKey) {
116
+ result[col] = row[foundKey];
117
+ return;
118
+ }
119
+
120
+ // 2. Synonym match
121
+ for (const [target, aliases] of Object.entries(SYNONYMS)) {
122
+ const isMatch = target === colLower || aliases.some(a => colLower.includes(a) || a.includes(colLower));
123
+ if (isMatch) {
124
+ const mappedKey = rowKeys.find(rk => {
125
+ const rkL = rk.toLowerCase();
126
+ return rkL === target || aliases.some(a => rkL.includes(a) || a.includes(rkL));
127
+ });
128
+ if (mappedKey) {
129
+ result[col] = row[mappedKey];
130
+ break;
131
+ }
132
+ }
133
+ }
134
+ });
135
+ }
136
+
137
+ return result;
138
+ }
139
+
140
+ return { value: row };
141
+ })
142
+ .filter((row) => Object.keys(row).length > 0);
143
+ }
144
+
145
+ function resolveStructuredRows(config: Partial<RawUIConfig> & Record<string, unknown>) {
146
+ // V7 Nested Structures
147
+ if (config.carousel?.items && Array.isArray(config.carousel.items)) return config.carousel.items;
148
+ if (config.chart?.data && Array.isArray(config.chart.data)) return config.chart.data;
149
+ if (config.table?.rows && Array.isArray(config.table.rows)) return config.table.rows;
150
+ if (config.table?.data && Array.isArray(config.table.data)) return config.table.data;
151
+
152
+ // V6 Flat Structures
153
+ if (Array.isArray(config.data) && config.data.length > 0) return config.data;
154
+ if (Array.isArray(config.items) && config.items.length > 0) return config.items;
155
+ if (Array.isArray(config.products) && config.products.length > 0) return config.products;
156
+ if (Array.isArray(config.rows) && config.rows.length > 0) return config.rows;
157
+ if (Array.isArray(config.results) && config.results.length > 0) return config.results;
158
+ return [];
159
+ }
160
+
161
+ export function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart, viewportSize = 'large' }: {
162
+ rawContent: string;
163
+ primaryColor?: string;
164
+ accentColor?: string;
165
+ isStreaming?: boolean;
166
+ onAddToCart?: (product: Product) => void;
167
+ viewportSize?: ChatViewportSize;
168
+ }) {
169
+ const result = React.useMemo<{ config: UIConfig } | { error: string } | { loading: true }>(() => {
170
+ try {
171
+ const clean = rawContent
172
+ .replace(/^```[a-z]*\s*/i, '')
173
+ .replace(/```\s*$/, '')
174
+ .trim();
175
+ const parsed = JSON.parse(sanitizeJson(extractLikelyJsonBlock(clean)));
176
+ const config = { ...parsed } as Partial<RawUIConfig> & Record<string, unknown>;
177
+
178
+ // V7 Field Mapping
179
+ if (config.chart) {
180
+ if (!config.chartType) config.chartType = config.chart.type;
181
+ if (!config.xAxisKey) config.xAxisKey = config.chart.xKey;
182
+ if (!config.dataKeys) config.dataKeys = config.chart.yKeys;
183
+ }
184
+ if (config.table) {
185
+ if (!config.columns) config.columns = config.table.columns;
186
+ }
187
+
188
+ const resolvedRows = resolveStructuredRows(config);
189
+
190
+ // ─── Intent Healing & Auto-Detection ───────────────────────────────────
191
+
192
+ const hasProductLikeData = resolvedRows.length > 0 &&
193
+ typeof resolvedRows[0] === 'object' && resolvedRows[0] !== null &&
194
+ !Array.isArray(resolvedRows[0]) &&
195
+ (['price', 'brand', 'image', 'link', 'thumbnail'].some(key => key in resolvedRows[0]) ||
196
+ Object.keys(resolvedRows[0]).length >= 4);
197
+
198
+ const hasAggregationLikeData = resolvedRows.length > 0 &&
199
+ typeof resolvedRows[0] === 'object' && resolvedRows[0] !== null &&
200
+ !Array.isArray(resolvedRows[0]) &&
201
+ (['value', 'count', 'total', 'percentage'].some(key => key in resolvedRows[0]) ||
202
+ (Object.keys(resolvedRows[0]).length <= 3 && Object.values(resolvedRows[0]).some(v => typeof v === 'number')));
203
+
204
+ // 1. Healing: Override explicit view if it contradicts the data shape
205
+ if (config.view === 'chart' && hasProductLikeData && !hasAggregationLikeData) {
206
+ config.view = 'carousel';
207
+ } else if (config.view === 'carousel' && hasAggregationLikeData && !hasProductLikeData) {
208
+ config.view = 'chart';
209
+ config.chartType = (config.chartType as 'pie' | 'bar' | 'line') || 'bar';
210
+ }
211
+
212
+ // 2. Auto-Detection: Fill in missing or dynamic view
213
+ if (!config.view || ['pie', 'bar', 'line', 'pie_chart', 'bar_chart', 'line_chart'].includes(String(config.view))) {
214
+ const viewStr = String(config.view || '').toLowerCase();
215
+
216
+ if (viewStr.includes('carousel') || config.type === 'products' || Array.isArray(config.items) || hasProductLikeData) {
217
+ config.view = 'carousel';
218
+ } else if (
219
+ viewStr.includes('chart') ||
220
+ viewStr.includes('pie') || viewStr.includes('bar') || viewStr.includes('line') ||
221
+ hasAggregationLikeData ||
222
+ (typeof config.type === 'string' && ['pie', 'bar', 'line'].includes(config.type)) ||
223
+ (typeof config.chartType === 'string' && ['pie', 'bar', 'line'].includes(config.chartType))
224
+ ) {
225
+ config.view = 'chart';
226
+ config.chartType = (config.chartType as 'pie' | 'bar' | 'line') ||
227
+ (viewStr.replace('_chart', '') as 'pie' | 'bar' | 'line') ||
228
+ (config.type as 'pie' | 'bar' | 'line') || 'bar';
229
+ } else if (viewStr.includes('table') || config.type === 'table') {
230
+ config.view = 'table';
231
+ } else {
232
+ // Do not forcefully default to a table if it is not explicitly requested or strongly implied
233
+ config.view = 'none';
234
+ }
235
+ }
236
+
237
+ const normalizedConfig: UIConfig = {
238
+ ...(config as Omit<UIConfig, 'data'>),
239
+ data: normalizeTabularRows(resolvedRows, config.columns),
240
+ };
241
+
242
+ return { config: normalizedConfig };
243
+ } catch (err) {
244
+ if (isStreaming) return { loading: true };
245
+ return { error: String(err) };
246
+ }
247
+ }, [rawContent, isStreaming]);
248
+
249
+ if ('loading' in result) {
250
+ return (
251
+ <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">
252
+ <div className="w-5 h-5 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" />
253
+ <p className="text-xs text-slate-500 font-medium italic">Preparing view...</p>
254
+ </div>
255
+ );
256
+ }
257
+
258
+ if ('error' in result) {
259
+ 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>;
260
+ }
261
+
262
+ const { config } = result;
263
+ const isCompact = viewportSize === 'compact';
264
+ const hasInsights = Array.isArray(config.insights) && config.insights.length > 0;
265
+ const hasDescription = typeof config.description === 'string' && config.description.trim().length > 0;
266
+
267
+ switch (config.view) {
268
+ case 'chart':
269
+ return (
270
+ <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`}>
271
+ {config.title && <h4 className={`${isCompact ? 'text-[11px] mb-3' : 'text-xs mb-4'} font-semibold text-slate-500 px-2`}>{config.title}</h4>}
272
+ {hasDescription && (
273
+ <p className={`px-2 ${isCompact ? 'text-xs' : 'text-sm'} text-slate-500 dark:text-white/60`}>{config.description}</p>
274
+ )}
275
+ <DynamicChart
276
+ config={{
277
+ type: (config.chartType || 'bar') as 'bar' | 'line' | 'pie',
278
+ data: config.data as unknown as Record<string, string | number>[],
279
+ xAxisKey: config.xAxisKey,
280
+ dataKeys: config.dataKeys,
281
+ colors: config.colors,
282
+ }}
283
+ primaryColor={primaryColor}
284
+ accentColor={accentColor}
285
+ viewportSize={viewportSize}
286
+ isStreaming={isStreaming}
287
+ />
288
+ {hasInsights && (
289
+ <div className="mt-4 flex flex-wrap gap-2 px-2">
290
+ {config.insights?.map((insight, i) => (
291
+ <span
292
+ key={`insight-${i}`}
293
+ 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`}
294
+ >
295
+ {String(insight)}
296
+ </span>
297
+ ))}
298
+ </div>
299
+ )}
300
+ </div>
301
+ );
302
+ case 'carousel':
303
+ return (
304
+ <div className="my-4">
305
+ {config.title && <h4 className={`${isCompact ? 'mb-1.5 text-[11px]' : 'mb-2 text-xs'} font-semibold text-slate-500`}>{config.title}</h4>}
306
+ {hasDescription && (
307
+ <p className={`mb-3 ${isCompact ? 'text-xs' : 'text-sm'} text-slate-500 dark:text-white/60`}>{config.description}</p>
308
+ )}
309
+ <ProductCarousel products={(config.data as unknown as Product[]).map(item => ({
310
+ ...item,
311
+ image: item.image ?? (typeof resolveImage === 'function' ? resolveImage(item as unknown as Record<string, unknown>) : undefined)
312
+ }))} primaryColor={primaryColor} onAddToCart={onAddToCart} />
313
+ </div>
314
+ );
315
+ case 'table':
316
+ return (
317
+ <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`}>
318
+ {config.title && <h4 className={`${isCompact ? 'text-[11px]' : 'text-xs'} font-semibold text-slate-500 mb-2`}>{config.title}</h4>}
319
+ {hasDescription && (
320
+ <p className={`mb-3 ${isCompact ? 'text-xs' : 'text-sm'} text-slate-500 dark:text-white/60`}>{config.description}</p>
321
+ )}
322
+ {/* Use VisualizationRenderer for Table data */}
323
+ <VisualizationRenderer
324
+ data={{
325
+ type: 'table',
326
+ title: config.title || '',
327
+ description: config.description,
328
+ data: {
329
+ columns: config.columns || Object.keys(config.data[0] || {}),
330
+ rows: config.data.map(row => Object.values(row)) as (string | number | boolean)[][]
331
+ }
332
+ }}
333
+ primaryColor={primaryColor}
334
+ isStreaming={isStreaming}
335
+ />
336
+ </div>
337
+ );
338
+ default:
339
+ return null;
340
+ }
341
+ }
@@ -24,18 +24,7 @@ import {
24
24
  import { ProductCarousel } from './ProductCarousel';
25
25
  import { Product, UITransformationResponse, VisualizationType, PieChartData, BarChartData, LineChartDataPoint, TableData, CarouselProduct } from '../types';
26
26
 
27
- // ─── Shared palette (mirrors DynamicChart.tsx) ───────────────────────────────
28
-
29
- const CHART_COLORS = [
30
- '#6366f1', // indigo
31
- '#10b981', // emerald
32
- '#f59e0b', // amber
33
- '#ef4444', // red
34
- '#8b5cf6', // purple
35
- '#ec4899', // pink
36
- '#06b6d4', // cyan
37
- '#f97316', // orange
38
- ];
27
+ import { CHART_COLORS } from './DynamicChart';
39
28
 
40
29
  function getColor(index: number): string {
41
30
  return CHART_COLORS[index % CHART_COLORS.length];
@@ -85,14 +74,12 @@ function CustomTooltip({
85
74
 
86
75
  // ─── Props ────────────────────────────────────────────────────────────────────
87
76
 
88
- /**
89
- * Props for the VisualizationRenderer component
90
- */
91
77
  interface VisualizationRendererProps {
92
78
  data: UITransformationResponse;
93
79
  className?: string;
94
80
  primaryColor?: string;
95
81
  onAddToCart?: (product: Product) => void;
82
+ isStreaming?: boolean;
96
83
  }
97
84
 
98
85
  // ─── Main component ───────────────────────────────────────────────────────────
@@ -115,6 +102,7 @@ export function VisualizationRenderer({
115
102
  className = '',
116
103
  primaryColor,
117
104
  onAddToCart,
105
+ isStreaming,
118
106
  }: VisualizationRendererProps) {
119
107
  if (!data) {
120
108
  return (
@@ -137,7 +125,7 @@ export function VisualizationRenderer({
137
125
  )}
138
126
  </div>
139
127
 
140
- {renderVisualization(data.type, data.data, onAddToCart, primaryColor)}
128
+ {renderVisualization(data.type, data.data, onAddToCart, primaryColor, isStreaming)}
141
129
  </div>
142
130
  );
143
131
  }
@@ -149,19 +137,20 @@ function renderVisualization(
149
137
  data: unknown,
150
138
  onAddToCart?: (product: Product) => void,
151
139
  primaryColor?: string,
140
+ isStreaming?: boolean,
152
141
  ): React.ReactNode {
153
142
  try {
154
143
  switch (type) {
155
144
  case 'pie_chart':
156
- return <PieChartView data={data as PieChartData[]} />;
145
+ return <PieChartView data={data as PieChartData[]} isStreaming={isStreaming} />;
157
146
  case 'bar_chart':
158
- return <BarChartView data={data as BarChartData[]} primaryColor={primaryColor} />;
147
+ return <BarChartView data={data as BarChartData[]} primaryColor={primaryColor} isStreaming={isStreaming} />;
159
148
  case 'line_chart':
160
- return <LineChartView data={data as LineChartDataPoint[]} primaryColor={primaryColor} />;
149
+ return <LineChartView data={data as LineChartDataPoint[]} primaryColor={primaryColor} isStreaming={isStreaming} />;
161
150
  case 'radar_chart':
162
- return <RadarChartView data={data as Record<string, unknown>[]} />;
151
+ return <RadarChartView data={data as Record<string, unknown>[]} isStreaming={isStreaming} />;
163
152
  case 'table':
164
- return <TableView data={data as TableData} />;
153
+ return <TableView data={data as TableData} isStreaming={isStreaming} />;
165
154
  case 'product_carousel':
166
155
  case 'carousel':
167
156
  return <CarouselView data={data as CarouselProduct[]} onAddToCart={onAddToCart} primaryColor={primaryColor} />;
@@ -175,9 +164,38 @@ function renderVisualization(
175
164
  }
176
165
  }
177
166
 
167
+ // ─── Chart Skeleton ────────────────────────────────────────────────────────────
168
+
169
+ function ChartSkeleton({ type }: { type: 'pie' | 'bar' | 'table' }) {
170
+ if (type === 'pie') {
171
+ return (
172
+ <div className="w-full mt-4 mb-2 animate-pulse">
173
+ <div className="mx-auto w-40 h-40 rounded-full bg-slate-100 dark:bg-white/10" />
174
+ </div>
175
+ );
176
+ }
177
+ if (type === 'table') {
178
+ return (
179
+ <div className="w-full mt-4 mb-2 animate-pulse space-y-2">
180
+ <div className="h-8 bg-slate-100 dark:bg-white/10 rounded" />
181
+ <div className="h-8 bg-slate-100 dark:bg-white/10 rounded" />
182
+ <div className="h-8 bg-slate-100 dark:bg-white/10 rounded" />
183
+ </div>
184
+ );
185
+ }
186
+ return (
187
+ <div className="w-full mt-4 mb-2 animate-pulse flex items-end gap-2 h-32 px-2">
188
+ {Array.from({ length: 5 }).map((_, i) => (
189
+ <div key={i} className="flex-1 rounded-t-md bg-slate-100 dark:bg-white/10" style={{ height: `${40 + (i % 3) * 25}%` }} />
190
+ ))}
191
+ </div>
192
+ );
193
+ }
194
+
178
195
  // ─── Pie Chart ────────────────────────────────────────────────────────────────
179
196
 
180
- function PieChartView({ data }: { data: PieChartData[] }) {
197
+ function PieChartView({ data, isStreaming }: { data: PieChartData[]; isStreaming?: boolean }) {
198
+ if (isStreaming) return <ChartSkeleton type="pie" />;
181
199
  if (!Array.isArray(data) || data.length === 0) {
182
200
  return <div className="text-xs text-red-500">Invalid pie chart data</div>;
183
201
  }
@@ -216,7 +234,8 @@ function PieChartView({ data }: { data: PieChartData[] }) {
216
234
 
217
235
  // ─── Bar Chart ────────────────────────────────────────────────────────────────
218
236
 
219
- function BarChartView({ data, primaryColor }: { data: BarChartData[]; primaryColor?: string }) {
237
+ function BarChartView({ data, primaryColor, isStreaming }: { data: BarChartData[]; primaryColor?: string; isStreaming?: boolean }) {
238
+ if (isStreaming) return <ChartSkeleton type="bar" />;
220
239
  if (!Array.isArray(data) || data.length === 0) {
221
240
  return <div className="text-xs text-red-500">Invalid bar chart data</div>;
222
241
  }
@@ -261,7 +280,8 @@ function BarChartView({ data, primaryColor }: { data: BarChartData[]; primaryCol
261
280
 
262
281
  // ─── Line Chart ───────────────────────────────────────────────────────────────
263
282
 
264
- function LineChartView({ data, primaryColor }: { data: LineChartDataPoint[]; primaryColor?: string }) {
283
+ function LineChartView({ data, primaryColor, isStreaming }: { data: LineChartDataPoint[]; primaryColor?: string; isStreaming?: boolean }) {
284
+ if (isStreaming) return <ChartSkeleton type="bar" />;
265
285
  if (!Array.isArray(data) || data.length === 0) {
266
286
  return <div className="text-xs text-red-500">Invalid line chart data</div>;
267
287
  }
@@ -303,7 +323,8 @@ function LineChartView({ data, primaryColor }: { data: LineChartDataPoint[]; pri
303
323
 
304
324
  // ─── Radar Chart ──────────────────────────────────────────────────────────────
305
325
 
306
- function RadarChartView({ data }: { data: Record<string, unknown>[] }) {
326
+ function RadarChartView({ data, isStreaming }: { data: Record<string, unknown>[]; isStreaming?: boolean }) {
327
+ if (isStreaming) return <ChartSkeleton type="pie" />;
307
328
  if (!Array.isArray(data) || data.length === 0) {
308
329
  return <div className="text-xs text-red-500">Invalid radar chart data</div>;
309
330
  }
@@ -340,7 +361,8 @@ function RadarChartView({ data }: { data: Record<string, unknown>[] }) {
340
361
 
341
362
  // ─── Table ────────────────────────────────────────────────────────────────────
342
363
 
343
- function TableView({ data }: { data: TableData }) {
364
+ function TableView({ data, isStreaming }: { data: TableData; isStreaming?: boolean }) {
365
+ if (isStreaming) return <ChartSkeleton type="table" />;
344
366
  if (!data?.columns || !data?.rows) {
345
367
  return <div className="text-xs text-red-500">Invalid table data</div>;
346
368
  }
@@ -32,6 +32,8 @@ import {
32
32
  VectorDBProvider,
33
33
  LLMProvider,
34
34
  EmbeddingProvider,
35
+ GraphDBConfig,
36
+ GraphDBProvider,
35
37
  } from './RagConfig';
36
38
 
37
39
  export class ConfigBuilder {
@@ -41,6 +43,7 @@ export class ConfigBuilder {
41
43
  private _embedding?: EmbeddingConfig;
42
44
  private _ui?: UIConfig;
43
45
  private _rag?: RAGConfig;
46
+ private _graphDb?: GraphDBConfig;
44
47
 
45
48
  /**
46
49
  * Set the project/application ID for namespacing
@@ -118,6 +121,24 @@ export class ConfigBuilder {
118
121
  return this;
119
122
  }
120
123
 
124
+ /**
125
+ * Configure the graph database provider
126
+ */
127
+ graphDb(
128
+ provider: string | 'auto',
129
+ options?: Record<string, unknown>
130
+ ): this {
131
+ if (provider === 'auto') {
132
+ this._graphDb = this._autoDetectGraphDb();
133
+ } else {
134
+ this._graphDb = {
135
+ provider: provider as GraphDBProvider,
136
+ options: options ?? {},
137
+ };
138
+ }
139
+ return this;
140
+ }
141
+
121
142
  /**
122
143
  * Set RAG-specific pipeline parameters
123
144
  */
@@ -152,13 +173,19 @@ export class ConfigBuilder {
152
173
  );
153
174
  }
154
175
 
176
+ const ragConfig = this._rag ?? { chunkSize: 1000, chunkOverlap: 200, topK: 5 };
177
+ if (process.env.RAG_USE_GRAPH_RETRIEVAL === 'true') {
178
+ ragConfig.useGraphRetrieval = true;
179
+ }
180
+
155
181
  return {
156
182
  projectId: this._projectId!,
157
183
  vectorDb: this._vectorDb!,
158
184
  llm: this._llm!,
159
185
  embedding: this._embedding!,
160
186
  ui: this._rag ? this._ui : undefined,
161
- rag: this._rag ?? { chunkSize: 1000, chunkOverlap: 200, topK: 5 },
187
+ rag: ragConfig,
188
+ graphDb: this._graphDb,
162
189
  ...(this._ui ? { ui: this._ui } : {}),
163
190
  };
164
191
  }
@@ -212,6 +239,16 @@ export class ConfigBuilder {
212
239
  }
213
240
  return { provider: 'openai', model: 'text-embedding-3-small', apiKey: process.env.OPENAI_API_KEY };
214
241
  }
242
+
243
+ private _autoDetectGraphDb(): GraphDBConfig | undefined {
244
+ if (process.env.GRAPH_DB_PROVIDER) {
245
+ return {
246
+ provider: process.env.GRAPH_DB_PROVIDER as GraphDBProvider,
247
+ options: {},
248
+ };
249
+ }
250
+ return undefined;
251
+ }
215
252
  }
216
253
 
217
254
  /**
@@ -25,7 +25,7 @@ export class LangChainAgent {
25
25
  */
26
26
  async initialize(chatModel: unknown) {
27
27
  try {
28
- const [{ DynamicTool }, { HumanMessage }, { createAgent }] = await Promise.all([
28
+ const [{ DynamicTool }, , { createAgent }] = await Promise.all([
29
29
  import('@langchain/core/tools'),
30
30
  import('@langchain/core/messages'),
31
31
  import('langchain'),
@@ -80,9 +80,6 @@ export class LangChainAgent {
80
80
  tools: [searchTool],
81
81
  systemPrompt: finalSystemPrompt,
82
82
  });
83
-
84
- // Suppress unused import warning — HumanMessage is used in run()
85
- void HumanMessage;
86
83
  } catch (error) {
87
84
  const isMissing =
88
85
  error instanceof Error && error.message.includes('Cannot find module');