@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
|
@@ -1,37 +1,108 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import React from 'react';
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
PieChart,
|
|
6
|
+
Pie,
|
|
7
|
+
Cell,
|
|
8
|
+
BarChart,
|
|
9
|
+
Bar,
|
|
10
|
+
LineChart,
|
|
11
|
+
Line,
|
|
12
|
+
XAxis,
|
|
13
|
+
YAxis,
|
|
14
|
+
CartesianGrid,
|
|
15
|
+
Tooltip,
|
|
16
|
+
Legend,
|
|
17
|
+
ResponsiveContainer,
|
|
18
|
+
RadarChart,
|
|
19
|
+
Radar,
|
|
20
|
+
PolarGrid,
|
|
21
|
+
PolarAngleAxis,
|
|
22
|
+
PolarRadiusAxis,
|
|
23
|
+
} from 'recharts';
|
|
24
|
+
import { ProductCarousel } from './ProductCarousel';
|
|
25
|
+
import { Product, UITransformationResponse, VisualizationType, PieChartData, BarChartData, LineChartDataPoint, TableData, CarouselProduct } from '../types';
|
|
26
|
+
|
|
27
|
+
import { CHART_COLORS } from './DynamicChart';
|
|
28
|
+
|
|
29
|
+
function getColor(index: number): string {
|
|
30
|
+
return CHART_COLORS[index % CHART_COLORS.length];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ─── Custom Tooltip ───────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
interface TooltipPayloadItem {
|
|
36
|
+
name: string;
|
|
37
|
+
value: string | number;
|
|
38
|
+
color?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function CustomTooltip({
|
|
42
|
+
active,
|
|
43
|
+
payload,
|
|
44
|
+
label,
|
|
45
|
+
}: {
|
|
46
|
+
active?: boolean;
|
|
47
|
+
payload?: TooltipPayloadItem[];
|
|
48
|
+
label?: string;
|
|
49
|
+
}) {
|
|
50
|
+
if (!active || !payload || payload.length === 0) return null;
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className="rounded-xl border border-slate-200 bg-white px-3 py-2 text-xs shadow-xl dark:border-white/10 dark:bg-slate-950">
|
|
54
|
+
{label && (
|
|
55
|
+
<p className="mb-2 font-semibold text-slate-800 dark:text-white/90">{label}</p>
|
|
56
|
+
)}
|
|
57
|
+
{payload.map((entry, i) => (
|
|
58
|
+
<div key={i} className="flex items-center justify-between gap-3 text-slate-600 dark:text-white/70">
|
|
59
|
+
<span className="flex items-center gap-1.5">
|
|
60
|
+
{entry.color && (
|
|
61
|
+
<span
|
|
62
|
+
className="inline-block w-2 h-2 rounded-full flex-shrink-0"
|
|
63
|
+
style={{ background: entry.color }}
|
|
64
|
+
/>
|
|
65
|
+
)}
|
|
66
|
+
{entry.name}
|
|
67
|
+
</span>
|
|
68
|
+
<span className="font-medium text-slate-900 dark:text-white/90">{entry.value}</span>
|
|
69
|
+
</div>
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─── Props ────────────────────────────────────────────────────────────────────
|
|
13
76
|
|
|
14
|
-
/**
|
|
15
|
-
* Props for the VisualizationRenderer component
|
|
16
|
-
*/
|
|
17
77
|
interface VisualizationRendererProps {
|
|
18
78
|
data: UITransformationResponse;
|
|
19
79
|
className?: string;
|
|
80
|
+
primaryColor?: string;
|
|
81
|
+
onAddToCart?: (product: Product) => void;
|
|
82
|
+
isStreaming?: boolean;
|
|
20
83
|
}
|
|
21
84
|
|
|
85
|
+
// ─── Main component ───────────────────────────────────────────────────────────
|
|
86
|
+
|
|
22
87
|
/**
|
|
23
|
-
* VisualizationRenderer — Renders UI transformations as React components
|
|
88
|
+
* VisualizationRenderer — Renders UI transformations as React components.
|
|
24
89
|
*
|
|
25
90
|
* Dynamically selects the appropriate visualization component based on the
|
|
26
|
-
* transformation type returned by UITransformer.
|
|
91
|
+
* transformation type returned by UITransformer. All charts are rendered via
|
|
92
|
+
* Recharts (chart.js has been removed).
|
|
93
|
+
*
|
|
94
|
+
* Supported types: bar_chart · line_chart · pie_chart · table · product_carousel · text
|
|
27
95
|
*
|
|
28
96
|
* @example
|
|
29
|
-
* const transformation = UITransformer.
|
|
97
|
+
* const transformation = await UITransformer.analyzeAndDecide(query, sources, llm);
|
|
30
98
|
* return <VisualizationRenderer data={transformation} />;
|
|
31
99
|
*/
|
|
32
100
|
export function VisualizationRenderer({
|
|
33
101
|
data,
|
|
34
102
|
className = '',
|
|
103
|
+
primaryColor,
|
|
104
|
+
onAddToCart,
|
|
105
|
+
isStreaming,
|
|
35
106
|
}: VisualizationRendererProps) {
|
|
36
107
|
if (!data) {
|
|
37
108
|
return (
|
|
@@ -43,299 +114,350 @@ export function VisualizationRenderer({
|
|
|
43
114
|
|
|
44
115
|
return (
|
|
45
116
|
<div className={`space-y-4 ${className}`}>
|
|
46
|
-
<div className="space-y-
|
|
47
|
-
<h3 className="text-
|
|
117
|
+
<div className="space-y-1">
|
|
118
|
+
<h3 className="text-sm font-semibold text-slate-900 dark:text-white">
|
|
48
119
|
{data.title}
|
|
49
120
|
</h3>
|
|
50
|
-
{data.description && (
|
|
51
|
-
<p className="text-
|
|
121
|
+
{data.description && data.description !== (data.data as { content?: string })?.content && (
|
|
122
|
+
<p className="text-xs text-slate-500 dark:text-slate-400">
|
|
52
123
|
{data.description}
|
|
53
124
|
</p>
|
|
54
125
|
)}
|
|
55
126
|
</div>
|
|
56
127
|
|
|
57
|
-
{renderVisualization(data.type, data.data)}
|
|
128
|
+
{renderVisualization(data.type, data.data, onAddToCart, primaryColor, isStreaming)}
|
|
58
129
|
</div>
|
|
59
130
|
);
|
|
60
131
|
}
|
|
61
132
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
133
|
+
// ─── Visualization dispatcher ─────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
function renderVisualization(
|
|
136
|
+
type: VisualizationType,
|
|
137
|
+
data: unknown,
|
|
138
|
+
onAddToCart?: (product: Product) => void,
|
|
139
|
+
primaryColor?: string,
|
|
140
|
+
isStreaming?: boolean,
|
|
141
|
+
): React.ReactNode {
|
|
142
|
+
try {
|
|
143
|
+
switch (type) {
|
|
144
|
+
case 'pie_chart':
|
|
145
|
+
return <PieChartView data={data as PieChartData[]} isStreaming={isStreaming} />;
|
|
146
|
+
case 'bar_chart':
|
|
147
|
+
return <BarChartView data={data as BarChartData[]} primaryColor={primaryColor} isStreaming={isStreaming} />;
|
|
148
|
+
case 'line_chart':
|
|
149
|
+
return <LineChartView data={data as LineChartDataPoint[]} primaryColor={primaryColor} isStreaming={isStreaming} />;
|
|
150
|
+
case 'radar_chart':
|
|
151
|
+
return <RadarChartView data={data as Record<string, unknown>[]} isStreaming={isStreaming} />;
|
|
152
|
+
case 'table':
|
|
153
|
+
return <TableView data={data as TableData} isStreaming={isStreaming} />;
|
|
154
|
+
case 'product_carousel':
|
|
155
|
+
case 'carousel':
|
|
156
|
+
return <CarouselView data={data as CarouselProduct[]} onAddToCart={onAddToCart} primaryColor={primaryColor} />;
|
|
157
|
+
case 'text':
|
|
158
|
+
default:
|
|
159
|
+
return <TextView data={data} />;
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.warn('[VisualizationRenderer] Rendering failed, falling back to text:', error);
|
|
163
|
+
return <TextView data={{ content: 'Unable to render the requested visualization.' }} />;
|
|
80
164
|
}
|
|
81
165
|
}
|
|
82
166
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
|
|
195
|
+
// ─── Pie Chart ────────────────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
function PieChartView({ data, isStreaming }: { data: PieChartData[]; isStreaming?: boolean }) {
|
|
198
|
+
if (isStreaming) return <ChartSkeleton type="pie" />;
|
|
199
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
200
|
+
return <div className="text-xs text-red-500">Invalid pie chart data</div>;
|
|
89
201
|
}
|
|
90
202
|
|
|
203
|
+
// Recharts Pie uses flat key/value, so map to { name, value }
|
|
204
|
+
const chartData = data.map((item) => ({
|
|
205
|
+
name: item.label,
|
|
206
|
+
value: item.value,
|
|
207
|
+
}));
|
|
208
|
+
|
|
91
209
|
return (
|
|
92
|
-
<div className="bg-white dark:bg-
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
210
|
+
<div className="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 p-4 shadow-sm">
|
|
211
|
+
<ResponsiveContainer width="100%" height={280}>
|
|
212
|
+
<PieChart>
|
|
213
|
+
<Pie
|
|
214
|
+
data={chartData}
|
|
215
|
+
dataKey="value"
|
|
216
|
+
nameKey="name"
|
|
217
|
+
cx="50%"
|
|
218
|
+
cy="50%"
|
|
219
|
+
outerRadius="68%"
|
|
220
|
+
label={false}
|
|
221
|
+
labelLine={false}
|
|
98
222
|
>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
<span
|
|
108
|
-
className={`text-xs px-2 py-1 rounded ${
|
|
109
|
-
item.inStock
|
|
110
|
-
? 'bg-green-100 text-green-800'
|
|
111
|
-
: 'bg-red-100 text-red-800'
|
|
112
|
-
}`}
|
|
113
|
-
>
|
|
114
|
-
{item.inStock ? 'In Stock' : 'Out of Stock'}
|
|
115
|
-
</span>
|
|
116
|
-
)}
|
|
117
|
-
</div>
|
|
118
|
-
))}
|
|
119
|
-
</div>
|
|
223
|
+
{chartData.map((_entry, index) => (
|
|
224
|
+
<Cell key={`cell-${index}`} fill={getColor(index)} />
|
|
225
|
+
))}
|
|
226
|
+
</Pie>
|
|
227
|
+
<Tooltip content={<CustomTooltip />} />
|
|
228
|
+
<Legend wrapperStyle={{ fontSize: 12 }} verticalAlign="bottom" />
|
|
229
|
+
</PieChart>
|
|
230
|
+
</ResponsiveContainer>
|
|
120
231
|
</div>
|
|
121
232
|
);
|
|
122
233
|
}
|
|
123
234
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (!Array.isArray(data)) {
|
|
129
|
-
return <div className="text-red-500">Invalid bar chart data</div>;
|
|
235
|
+
// ─── Bar Chart ────────────────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
function BarChartView({ data, primaryColor, isStreaming }: { data: BarChartData[]; primaryColor?: string; isStreaming?: boolean }) {
|
|
238
|
+
if (isStreaming) return <ChartSkeleton type="bar" />;
|
|
239
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
240
|
+
return <div className="text-xs text-red-500">Invalid bar chart data</div>;
|
|
130
241
|
}
|
|
131
242
|
|
|
243
|
+
const chartData = data.map((item) => ({
|
|
244
|
+
category: item.category,
|
|
245
|
+
value: item.value,
|
|
246
|
+
...(item.inStockCount !== undefined && { inStockCount: item.inStockCount }),
|
|
247
|
+
...(item.outOfStockCount !== undefined && { outOfStockCount: item.outOfStockCount }),
|
|
248
|
+
}));
|
|
249
|
+
|
|
250
|
+
const barColor = primaryColor ?? getColor(0);
|
|
251
|
+
const hasStock = chartData.some((d) => 'inStockCount' in d);
|
|
252
|
+
|
|
132
253
|
return (
|
|
133
|
-
<div className="bg-white dark:bg-
|
|
134
|
-
<
|
|
135
|
-
{
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
className={`text-xs px-2 py-1 rounded inline-block ${
|
|
157
|
-
item.inStock
|
|
158
|
-
? 'bg-green-100 text-green-800'
|
|
159
|
-
: 'bg-red-100 text-red-800'
|
|
160
|
-
}`}
|
|
161
|
-
>
|
|
162
|
-
{item.inStock ? 'In Stock' : 'Out of Stock'}
|
|
163
|
-
</span>
|
|
164
|
-
)}
|
|
165
|
-
</div>
|
|
166
|
-
))}
|
|
167
|
-
</div>
|
|
254
|
+
<div className="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 p-4 shadow-sm overflow-x-auto">
|
|
255
|
+
<ResponsiveContainer width="100%" height={260}>
|
|
256
|
+
<BarChart data={chartData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
|
257
|
+
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e2e8f0" />
|
|
258
|
+
<XAxis
|
|
259
|
+
dataKey="category"
|
|
260
|
+
tick={{ fontSize: 11, fill: '#64748b' }}
|
|
261
|
+
tickLine={false}
|
|
262
|
+
axisLine={{ stroke: '#cbd5e1' }}
|
|
263
|
+
/>
|
|
264
|
+
<YAxis tick={{ fontSize: 11, fill: '#64748b' }} tickLine={false} axisLine={false} />
|
|
265
|
+
<Tooltip content={<CustomTooltip />} cursor={{ fill: '#f1f5f9' }} />
|
|
266
|
+
<Legend wrapperStyle={{ fontSize: 12 }} />
|
|
267
|
+
{hasStock ? (
|
|
268
|
+
<>
|
|
269
|
+
<Bar dataKey="inStockCount" name="In Stock" fill={getColor(1)} radius={[4, 4, 0, 0]} />
|
|
270
|
+
<Bar dataKey="outOfStockCount" name="Out of Stock" fill={getColor(3)} radius={[4, 4, 0, 0]} />
|
|
271
|
+
</>
|
|
272
|
+
) : (
|
|
273
|
+
<Bar dataKey="value" name="Value" fill={barColor} radius={[4, 4, 0, 0]} />
|
|
274
|
+
)}
|
|
275
|
+
</BarChart>
|
|
276
|
+
</ResponsiveContainer>
|
|
168
277
|
</div>
|
|
169
278
|
);
|
|
170
279
|
}
|
|
171
280
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (!Array.isArray(data)) {
|
|
177
|
-
return <div className="text-red-500">Invalid line chart data</div>;
|
|
281
|
+
// ─── Line Chart ───────────────────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
function LineChartView({ data, primaryColor, isStreaming }: { data: LineChartDataPoint[]; primaryColor?: string; isStreaming?: boolean }) {
|
|
284
|
+
if (isStreaming) return <ChartSkeleton type="bar" />;
|
|
285
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
286
|
+
return <div className="text-xs text-red-500">Invalid line chart data</div>;
|
|
178
287
|
}
|
|
179
288
|
|
|
289
|
+
const chartData = data.map((point) => ({
|
|
290
|
+
label: String(point.label ?? point.timestamp),
|
|
291
|
+
value: Number(point.value),
|
|
292
|
+
}));
|
|
293
|
+
|
|
294
|
+
const lineColor = primaryColor ?? getColor(0);
|
|
295
|
+
|
|
180
296
|
return (
|
|
181
|
-
<div className="bg-white dark:bg-
|
|
182
|
-
<
|
|
183
|
-
{
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
297
|
+
<div className="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 p-4 shadow-sm overflow-x-auto">
|
|
298
|
+
<ResponsiveContainer width="100%" height={260}>
|
|
299
|
+
<LineChart data={chartData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
|
300
|
+
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e2e8f0" />
|
|
301
|
+
<XAxis
|
|
302
|
+
dataKey="label"
|
|
303
|
+
tick={{ fontSize: 11, fill: '#64748b' }}
|
|
304
|
+
tickLine={false}
|
|
305
|
+
axisLine={{ stroke: '#cbd5e1' }}
|
|
306
|
+
/>
|
|
307
|
+
<YAxis tick={{ fontSize: 11, fill: '#64748b' }} tickLine={false} axisLine={false} />
|
|
308
|
+
<Tooltip content={<CustomTooltip />} />
|
|
309
|
+
<Line
|
|
310
|
+
type="monotone"
|
|
311
|
+
dataKey="value"
|
|
312
|
+
name="Value"
|
|
313
|
+
stroke={lineColor}
|
|
314
|
+
strokeWidth={2.5}
|
|
315
|
+
dot={{ r: 3.5, strokeWidth: 2, fill: lineColor }}
|
|
316
|
+
activeDot={{ r: 5 }}
|
|
317
|
+
/>
|
|
318
|
+
</LineChart>
|
|
319
|
+
</ResponsiveContainer>
|
|
197
320
|
</div>
|
|
198
321
|
);
|
|
199
322
|
}
|
|
200
323
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (!data
|
|
206
|
-
return <div className="text-red-500">Invalid
|
|
324
|
+
// ─── Radar Chart ──────────────────────────────────────────────────────────────
|
|
325
|
+
|
|
326
|
+
function RadarChartView({ data, isStreaming }: { data: Record<string, unknown>[]; isStreaming?: boolean }) {
|
|
327
|
+
if (isStreaming) return <ChartSkeleton type="pie" />;
|
|
328
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
329
|
+
return <div className="text-xs text-red-500">Invalid radar chart data</div>;
|
|
207
330
|
}
|
|
208
331
|
|
|
332
|
+
// Find all keys that are not 'attribute' to use as radar data keys (product names)
|
|
333
|
+
const productNames = Array.from(
|
|
334
|
+
new Set(data.flatMap((item) => Object.keys(item).filter((key) => key !== 'attribute')))
|
|
335
|
+
);
|
|
336
|
+
|
|
209
337
|
return (
|
|
210
|
-
<div className="bg-white dark:bg-
|
|
211
|
-
<
|
|
212
|
-
<
|
|
213
|
-
<
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
338
|
+
<div className="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 p-4 shadow-sm">
|
|
339
|
+
<ResponsiveContainer width="100%" height={300}>
|
|
340
|
+
<RadarChart cx="50%" cy="50%" outerRadius="80%" data={data}>
|
|
341
|
+
<PolarGrid stroke="#e2e8f0" />
|
|
342
|
+
<PolarAngleAxis dataKey="attribute" tick={{ fontSize: 11, fill: '#64748b' }} />
|
|
343
|
+
<PolarRadiusAxis tick={{ fontSize: 10, fill: '#64748b' }} />
|
|
344
|
+
{productNames.map((productName, index) => (
|
|
345
|
+
<Radar
|
|
346
|
+
key={productName}
|
|
347
|
+
name={productName}
|
|
348
|
+
dataKey={productName}
|
|
349
|
+
stroke={getColor(index)}
|
|
350
|
+
fill={getColor(index)}
|
|
351
|
+
fillOpacity={0.4}
|
|
352
|
+
/>
|
|
353
|
+
))}
|
|
354
|
+
<Tooltip content={<CustomTooltip />} />
|
|
355
|
+
<Legend wrapperStyle={{ fontSize: 12 }} />
|
|
356
|
+
</RadarChart>
|
|
357
|
+
</ResponsiveContainer>
|
|
358
|
+
</div>
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ─── Table ────────────────────────────────────────────────────────────────────
|
|
363
|
+
|
|
364
|
+
function TableView({ data, isStreaming }: { data: TableData; isStreaming?: boolean }) {
|
|
365
|
+
if (isStreaming) return <ChartSkeleton type="table" />;
|
|
366
|
+
if (!data?.columns || !data?.rows) {
|
|
367
|
+
return <div className="text-xs text-red-500">Invalid table data</div>;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return (
|
|
371
|
+
<div className="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden">
|
|
372
|
+
<div className="overflow-x-auto">
|
|
373
|
+
<table className="w-full text-sm border-collapse">
|
|
374
|
+
<thead className="bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10">
|
|
375
|
+
<tr>
|
|
376
|
+
{data.columns.map((col: string, idx: number) => (
|
|
377
|
+
<th
|
|
378
|
+
key={idx}
|
|
379
|
+
className="px-4 py-3 text-left text-xs font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap uppercase tracking-wider"
|
|
234
380
|
>
|
|
235
|
-
{
|
|
236
|
-
</
|
|
381
|
+
{col}
|
|
382
|
+
</th>
|
|
237
383
|
))}
|
|
238
384
|
</tr>
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
385
|
+
</thead>
|
|
386
|
+
<tbody className="divide-y divide-slate-100 dark:divide-white/5">
|
|
387
|
+
{data.rows.map((row: (string | number | boolean)[], rowIdx: number) => (
|
|
388
|
+
<tr
|
|
389
|
+
key={rowIdx}
|
|
390
|
+
className="hover:bg-slate-50/60 dark:hover:bg-white/5 transition-colors"
|
|
391
|
+
>
|
|
392
|
+
{row.map((cell: string | number | boolean, cellIdx: number) => (
|
|
393
|
+
<td
|
|
394
|
+
key={cellIdx}
|
|
395
|
+
className="px-4 py-3 text-slate-600 dark:text-white/70 text-sm"
|
|
396
|
+
>
|
|
397
|
+
{String(cell)}
|
|
398
|
+
</td>
|
|
399
|
+
))}
|
|
400
|
+
</tr>
|
|
401
|
+
))}
|
|
402
|
+
</tbody>
|
|
403
|
+
</table>
|
|
404
|
+
</div>
|
|
405
|
+
<div className="px-4 py-2 bg-slate-50 dark:bg-white/5 border-t border-slate-100 dark:border-white/5">
|
|
406
|
+
<p className="text-[11px] text-slate-400 dark:text-white/30">
|
|
407
|
+
{data.rows.length} row{data.rows.length !== 1 ? 's' : ''}
|
|
408
|
+
</p>
|
|
409
|
+
</div>
|
|
242
410
|
</div>
|
|
243
411
|
);
|
|
244
412
|
}
|
|
245
413
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
414
|
+
// ─── Product Carousel ─────────────────────────────────────────────────────────
|
|
415
|
+
|
|
416
|
+
function CarouselView({
|
|
417
|
+
data,
|
|
418
|
+
onAddToCart,
|
|
419
|
+
primaryColor,
|
|
420
|
+
}: {
|
|
421
|
+
data: CarouselProduct[];
|
|
422
|
+
onAddToCart?: (product: Product) => void;
|
|
423
|
+
primaryColor?: string;
|
|
424
|
+
}) {
|
|
250
425
|
if (!Array.isArray(data)) {
|
|
251
|
-
return <div className="text-red-500">Invalid carousel data</div>;
|
|
426
|
+
return <div className="text-xs text-red-500">Invalid carousel data</div>;
|
|
252
427
|
}
|
|
253
428
|
|
|
429
|
+
const products: Product[] = data.map((product) => ({
|
|
430
|
+
id: product.id,
|
|
431
|
+
name: product.name,
|
|
432
|
+
brand: product.brand,
|
|
433
|
+
price: product.price,
|
|
434
|
+
image: product.image,
|
|
435
|
+
link: typeof product.link === 'string' ? product.link : undefined,
|
|
436
|
+
description: product.description,
|
|
437
|
+
inStock: product.inStock,
|
|
438
|
+
}));
|
|
439
|
+
|
|
254
440
|
return (
|
|
255
|
-
<div className="
|
|
256
|
-
<
|
|
257
|
-
{
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
>
|
|
262
|
-
{product.image && (
|
|
263
|
-
<div className="relative w-full h-40 bg-gray-200">
|
|
264
|
-
<Image
|
|
265
|
-
src={product.image}
|
|
266
|
-
alt={product.name}
|
|
267
|
-
fill
|
|
268
|
-
className="object-cover"
|
|
269
|
-
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
|
270
|
-
/>
|
|
271
|
-
</div>
|
|
272
|
-
)}
|
|
273
|
-
<div className="p-4 space-y-2">
|
|
274
|
-
<h4 className="font-semibold text-gray-900 dark:text-white line-clamp-2">
|
|
275
|
-
{product.name}
|
|
276
|
-
</h4>
|
|
277
|
-
{product.brand && (
|
|
278
|
-
<p className="text-xs text-gray-500 dark:text-gray-400">
|
|
279
|
-
Brand: {product.brand}
|
|
280
|
-
</p>
|
|
281
|
-
)}
|
|
282
|
-
{product.price && (
|
|
283
|
-
<p className="text-lg font-bold text-gray-900 dark:text-white">
|
|
284
|
-
${typeof product.price === 'number' ? product.price.toFixed(2) : product.price}
|
|
285
|
-
</p>
|
|
286
|
-
)}
|
|
287
|
-
{product.description && (
|
|
288
|
-
<p className="text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
|
|
289
|
-
{product.description}
|
|
290
|
-
</p>
|
|
291
|
-
)}
|
|
292
|
-
<div className="flex gap-2 pt-2">
|
|
293
|
-
<span
|
|
294
|
-
className={`text-xs px-2 py-1 rounded flex-1 text-center ${
|
|
295
|
-
product.inStock
|
|
296
|
-
? 'bg-green-100 text-green-800'
|
|
297
|
-
: 'bg-red-100 text-red-800'
|
|
298
|
-
}`}
|
|
299
|
-
>
|
|
300
|
-
{product.inStock ? 'In Stock' : 'Out of Stock'}
|
|
301
|
-
</span>
|
|
302
|
-
</div>
|
|
303
|
-
</div>
|
|
304
|
-
</div>
|
|
305
|
-
))}
|
|
306
|
-
</div>
|
|
441
|
+
<div className="w-full">
|
|
442
|
+
<ProductCarousel
|
|
443
|
+
products={products}
|
|
444
|
+
primaryColor={primaryColor ?? '#6366f1'}
|
|
445
|
+
onAddToCart={(p) => onAddToCart?.(p)}
|
|
446
|
+
/>
|
|
307
447
|
</div>
|
|
308
448
|
);
|
|
309
449
|
}
|
|
310
450
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const content = (data as { content?: string })?.content || String(data);
|
|
451
|
+
// ─── Text / Prose ─────────────────────────────────────────────────────────────
|
|
452
|
+
|
|
453
|
+
function TextView({ data }: { data: unknown }) {
|
|
454
|
+
const content = (data as { content?: string })?.content || String(data ?? '');
|
|
316
455
|
|
|
317
456
|
return (
|
|
318
|
-
<div className="bg-white dark:bg-
|
|
319
|
-
<p className="text-
|
|
457
|
+
<div className="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 p-4 shadow-sm">
|
|
458
|
+
<p className="text-sm text-slate-700 dark:text-slate-300 whitespace-pre-wrap leading-relaxed">
|
|
320
459
|
{content}
|
|
321
460
|
</p>
|
|
322
461
|
</div>
|
|
323
462
|
);
|
|
324
463
|
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Utility to generate consistent colors for charts
|
|
328
|
-
*/
|
|
329
|
-
function generateColor(index: number): string {
|
|
330
|
-
const colors = [
|
|
331
|
-
'#3B82F6', // blue
|
|
332
|
-
'#10B981', // green
|
|
333
|
-
'#F59E0B', // amber
|
|
334
|
-
'#EF4444', // red
|
|
335
|
-
'#8B5CF6', // purple
|
|
336
|
-
'#EC4899', // pink
|
|
337
|
-
'#06B6D4', // cyan
|
|
338
|
-
'#F97316', // orange
|
|
339
|
-
];
|
|
340
|
-
return colors[index % colors.length];
|
|
341
|
-
}
|