@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.
- package/dist/{ILLMProvider-BfRgI1Xh.d.mts → ILLMProvider-BOJFz3Na.d.mts} +47 -1
- package/dist/{ILLMProvider-BfRgI1Xh.d.ts → ILLMProvider-BOJFz3Na.d.ts} +47 -1
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +509 -162
- package/dist/handlers/index.mjs +5849 -15
- package/dist/{index-1Z4GuYBi.d.ts → index-BwpcaziY.d.ts} +4 -2
- package/dist/{index-BV0z5mb6.d.mts → index-D3V9Et2M.d.mts} +4 -2
- package/dist/index.d.mts +30 -16
- package/dist/index.d.ts +30 -16
- package/dist/index.js +1999 -1210
- package/dist/index.mjs +2041 -1205
- package/dist/server.d.mts +21 -25
- package/dist/server.d.ts +21 -25
- package/dist/server.js +536 -163
- package/dist/server.mjs +5923 -67
- package/package.json +9 -7
- package/src/app/api/upload/route.ts +4 -0
- package/src/app/constants.tsx +2 -2
- package/src/app/globals.css +35 -11
- package/src/app/page.tsx +12 -321
- package/src/components/AmbientBackground.tsx +29 -0
- package/src/components/ArchitectureCard.tsx +17 -0
- package/src/components/ArchitectureCardsSection.tsx +15 -0
- package/src/components/ChatWidget.tsx +0 -1
- package/src/components/ChatWindow.tsx +32 -0
- package/src/components/CodeViewer.tsx +51 -0
- package/src/components/ConfigProvider.tsx +1 -0
- package/src/components/DocViewer.tsx +37 -0
- package/src/components/DocumentUpload.tsx +44 -1
- package/src/components/Documentation.tsx +58 -0
- package/src/components/DynamicChart.tsx +27 -2
- package/src/components/Hero.tsx +59 -0
- package/src/components/HourglassLoader.tsx +87 -0
- package/src/components/Lifecycle.tsx +37 -0
- package/src/components/MarkdownComponents.tsx +143 -0
- package/src/components/MessageBubble.tsx +91 -1012
- package/src/components/Navbar.tsx +55 -0
- package/src/components/ObservabilityPanel.tsx +374 -0
- package/src/components/ProductCard.tsx +3 -1
- package/src/components/UIDispatcher.tsx +341 -0
- package/src/components/VisualizationRenderer.tsx +48 -26
- package/src/config/ConfigBuilder.ts +38 -1
- package/src/core/LangChainAgent.ts +1 -4
- package/src/core/Pipeline.ts +209 -86
- package/src/core/QueryProcessor.ts +65 -0
- package/src/handlers/index.ts +72 -12
- package/src/hooks/useRagChat.ts +19 -9
- package/src/index.ts +9 -1
- package/src/providers/vectordb/MultiTablePostgresProvider.ts +150 -64
- package/src/rag/Reranker.ts +99 -6
- package/src/types/chat.ts +2 -0
- package/src/types/index.ts +52 -0
- package/src/types/props.ts +9 -1
- package/src/utils/ProductExtractor.ts +347 -0
- package/src/utils/UITransformer.ts +4 -53
- package/src/utils/synonyms.ts +78 -0
- package/dist/ChromaDBProvider-MIDOR4FW.mjs +0 -8
- package/dist/MilvusProvider-U7SKC27V.mjs +0 -8
- package/dist/MongoDBProvider-YNKC7EJ6.mjs +0 -8
- package/dist/MultiTablePostgresProvider-YY7LPNJK.mjs +0 -8
- package/dist/PineconeProvider-QZNRKTN2.mjs +0 -8
- package/dist/QdrantProvider-RLJTNGPY.mjs +0 -8
- package/dist/RedisProvider-SR65SCKV.mjs +0 -8
- package/dist/SimpleGraphProvider-SLOXO4M7.mjs +0 -62
- package/dist/UniversalVectorProvider-IN67OS56.mjs +0 -9
- package/dist/WeaviateProvider-5FWDFITI.mjs +0 -8
- package/dist/chunk-5AJ4XHLW.mjs +0 -201
- package/dist/chunk-5YGUXK7Z.mjs +0 -80
- package/dist/chunk-BFYLQYQU.mjs +0 -3985
- package/dist/chunk-CFVEZTBJ.mjs +0 -102
- package/dist/chunk-IMP6FUCY.mjs +0 -30
- package/dist/chunk-LR3VMDVK.mjs +0 -157
- package/dist/chunk-M6JSPGAR.mjs +0 -117
- package/dist/chunk-PSFPZXHX.mjs +0 -245
- package/dist/chunk-R3RGUMHE.mjs +0 -218
- package/dist/chunk-U55XRW3U.mjs +0 -96
- package/dist/chunk-VUQJVIJT.mjs +0 -148
- package/dist/chunk-X4TOT24V.mjs +0 -89
- 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
|
-
|
|
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:
|
|
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 },
|
|
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');
|