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