@retrivora-ai/rag-engine 1.8.0 → 1.8.2
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/DocumentChunker-Dh9TvmGG.d.mts +45 -0
- package/dist/DocumentChunker-Dh9TvmGG.d.ts +45 -0
- package/dist/{index-DPsQodME.d.mts → ILLMProvider-BOJFz3Na.d.mts} +104 -1
- package/dist/{index-DPsQodME.d.ts → ILLMProvider-BOJFz3Na.d.ts} +104 -1
- package/dist/MultiTablePostgresProvider-ZLGSKTJR.mjs +8 -0
- package/dist/chunk-ICKRMZQK.mjs +76 -0
- package/dist/{chunk-PV3MFHWU.mjs → chunk-LZVVLSDN.mjs} +977 -516
- package/dist/chunk-OZFBG4BA.mjs +291 -0
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +1269 -656
- package/dist/handlers/index.mjs +4 -1
- package/dist/{index-Bb2yEopi.d.mts → index-BwpcaziY.d.ts} +10 -2
- package/dist/{index-CkbTzj9J.d.ts → index-D3V9Et2M.d.mts} +10 -2
- package/dist/index.d.mts +24 -4
- package/dist/index.d.ts +24 -4
- package/dist/index.js +1354 -826
- package/dist/index.mjs +1284 -795
- package/dist/server.d.mts +47 -27
- package/dist/server.d.ts +47 -27
- package/dist/server.js +1417 -829
- package/dist/server.mjs +164 -176
- package/package.json +6 -2
- package/src/app/api/upload/route.ts +4 -0
- package/src/app/constants.tsx +2 -2
- package/src/app/page.tsx +12 -322
- package/src/components/AmbientBackground.tsx +29 -0
- package/src/components/ArchitectureCard.tsx +17 -0
- package/src/components/ArchitectureCardsSection.tsx +15 -0
- 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 +140 -0
- package/src/components/MessageBubble.tsx +124 -904
- package/src/components/Navbar.tsx +55 -0
- package/src/components/ObservabilityPanel.tsx +374 -0
- package/src/components/ProductCard.tsx +5 -3
- package/src/components/UIDispatcher.tsx +344 -0
- package/src/components/VisualizationRenderer.tsx +372 -250
- package/src/config/RagConfig.ts +5 -0
- package/src/config/serverConfig.ts +3 -1
- package/src/core/Pipeline.ts +240 -271
- package/src/core/ProviderRegistry.ts +2 -2
- package/src/core/VectorPlugin.ts +9 -0
- package/src/handlers/index.ts +91 -15
- package/src/hooks/useRagChat.ts +21 -11
- package/src/index.ts +9 -1
- package/src/llm/LLMFactory.ts +54 -2
- package/src/llm/providers/AnthropicProvider.ts +12 -8
- package/src/llm/providers/GeminiProvider.ts +188 -143
- package/src/llm/providers/OllamaProvider.ts +7 -3
- package/src/llm/providers/OpenAIProvider.ts +12 -8
- package/src/providers/vectordb/MultiTablePostgresProvider.ts +150 -64
- package/src/types/chat.ts +8 -0
- package/src/types/index.ts +132 -0
- package/src/types/props.ts +9 -1
- package/src/utils/ProductExtractor.ts +347 -0
- package/src/utils/SchemaMapper.ts +129 -0
- package/src/utils/UITransformer.ts +470 -209
- package/src/utils/synonyms.ts +78 -0
- package/dist/DocumentChunker-C1GEEosY.d.ts +0 -93
- package/dist/DocumentChunker-CFEiRopR.d.mts +0 -93
- package/dist/PostgreSQLProvider-BMOETDZA.mjs +0 -8
- package/dist/chunk-FLOSGE6A.mjs +0 -202
|
@@ -0,0 +1,344 @@
|
|
|
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
|
+
console.log("🚀 ~ UIDispatcher ~ description:", config.description);
|
|
268
|
+
console.log("🚀 ~ UIDispatcher ~ hasDescription:", hasDescription);
|
|
269
|
+
|
|
270
|
+
switch (config.view) {
|
|
271
|
+
case 'chart':
|
|
272
|
+
return (
|
|
273
|
+
<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`}>
|
|
274
|
+
{config.title && <h4 className={`${isCompact ? 'text-[11px] mb-3' : 'text-xs mb-4'} font-semibold text-slate-500 px-2`}>{config.title}</h4>}
|
|
275
|
+
{hasDescription && (
|
|
276
|
+
<p className={`px-2 ${isCompact ? 'text-xs' : 'text-sm'} text-slate-500 dark:text-white/60`}>{config.description}</p>
|
|
277
|
+
)}
|
|
278
|
+
<DynamicChart
|
|
279
|
+
config={{
|
|
280
|
+
type: (config.chartType || 'bar') as 'bar' | 'line' | 'pie',
|
|
281
|
+
data: config.data as unknown as Record<string, string | number>[],
|
|
282
|
+
xAxisKey: config.xAxisKey,
|
|
283
|
+
dataKeys: config.dataKeys,
|
|
284
|
+
colors: config.colors,
|
|
285
|
+
}}
|
|
286
|
+
primaryColor={primaryColor}
|
|
287
|
+
accentColor={accentColor}
|
|
288
|
+
viewportSize={viewportSize}
|
|
289
|
+
isStreaming={isStreaming}
|
|
290
|
+
/>
|
|
291
|
+
{hasInsights && (
|
|
292
|
+
<div className="mt-4 flex flex-wrap gap-2 px-2">
|
|
293
|
+
{config.insights?.map((insight, i) => (
|
|
294
|
+
<span
|
|
295
|
+
key={`insight-${i}`}
|
|
296
|
+
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`}
|
|
297
|
+
>
|
|
298
|
+
{String(insight)}
|
|
299
|
+
</span>
|
|
300
|
+
))}
|
|
301
|
+
</div>
|
|
302
|
+
)}
|
|
303
|
+
</div>
|
|
304
|
+
);
|
|
305
|
+
case 'carousel':
|
|
306
|
+
return (
|
|
307
|
+
<div className="my-4">
|
|
308
|
+
{config.title && <h4 className={`${isCompact ? 'mb-1.5 text-[11px]' : 'mb-2 text-xs'} font-semibold text-slate-500`}>{config.title}</h4>}
|
|
309
|
+
{hasDescription && (
|
|
310
|
+
<p className={`mb-3 ${isCompact ? 'text-xs' : 'text-sm'} text-slate-500 dark:text-white/60`}>{config.description}</p>
|
|
311
|
+
)}
|
|
312
|
+
<ProductCarousel products={(config.data as unknown as Product[]).map(item => ({
|
|
313
|
+
...item,
|
|
314
|
+
image: item.image ?? (typeof resolveImage === 'function' ? resolveImage(item as unknown as Record<string, unknown>) : undefined)
|
|
315
|
+
}))} primaryColor={primaryColor} onAddToCart={onAddToCart} />
|
|
316
|
+
</div>
|
|
317
|
+
);
|
|
318
|
+
case 'table':
|
|
319
|
+
return (
|
|
320
|
+
<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`}>
|
|
321
|
+
{config.title && <h4 className={`${isCompact ? 'text-[11px]' : 'text-xs'} font-semibold text-slate-500 mb-2`}>{config.title}</h4>}
|
|
322
|
+
{hasDescription && (
|
|
323
|
+
<p className={`mb-3 ${isCompact ? 'text-xs' : 'text-sm'} text-slate-500 dark:text-white/60`}>{config.description}</p>
|
|
324
|
+
)}
|
|
325
|
+
{/* Use VisualizationRenderer for Table data */}
|
|
326
|
+
<VisualizationRenderer
|
|
327
|
+
data={{
|
|
328
|
+
type: 'table',
|
|
329
|
+
title: config.title || '',
|
|
330
|
+
description: config.description,
|
|
331
|
+
data: {
|
|
332
|
+
columns: config.columns || Object.keys(config.data[0] || {}),
|
|
333
|
+
rows: config.data.map(row => Object.values(row)) as (string | number | boolean)[][]
|
|
334
|
+
}
|
|
335
|
+
}}
|
|
336
|
+
primaryColor={primaryColor}
|
|
337
|
+
isStreaming={isStreaming}
|
|
338
|
+
/>
|
|
339
|
+
</div>
|
|
340
|
+
);
|
|
341
|
+
default:
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
}
|