@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,55 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { Zap, Package, GitBranch, HeartPulse } from 'lucide-react';
|
|
5
|
+
import { ThemeToggle } from '@/components/ThemeToggle';
|
|
6
|
+
import { useConfig } from '@/components/ConfigProvider';
|
|
7
|
+
import pkg from '../../package.json';
|
|
8
|
+
|
|
9
|
+
export function Navbar() {
|
|
10
|
+
const { ui } = useConfig();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<nav className="sticky top-0 z-50 flex items-center justify-between px-8 py-5 border-b border-slate-200 dark:border-white/5 bg-white/80 dark:bg-[#080811]/80 backdrop-blur-md">
|
|
14
|
+
<div className="flex items-center gap-3 group cursor-default">
|
|
15
|
+
<div className="relative">
|
|
16
|
+
<div
|
|
17
|
+
className="w-10 h-10 rounded-xl flex items-center justify-center shadow-lg relative z-10 overflow-hidden transition-transform group-hover:scale-110 duration-500"
|
|
18
|
+
style={{ background: `linear-gradient(135deg, ${ui.primaryColor}, ${ui.accentColor})` }}
|
|
19
|
+
>
|
|
20
|
+
<Zap className="w-5 h-5 text-white animate-pulse" />
|
|
21
|
+
<div className="absolute inset-0 bg-white/20 opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
22
|
+
</div>
|
|
23
|
+
<div
|
|
24
|
+
className="absolute inset-0 blur-lg opacity-40 group-hover:opacity-80 transition-opacity"
|
|
25
|
+
style={{ background: ui.primaryColor }}
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="flex flex-col -gap-0.5">
|
|
29
|
+
<span className="font-black text-transparent bg-clip-text text-xl tracking-tight leading-none"
|
|
30
|
+
style={{ backgroundImage: `linear-gradient(to right, ${ui.primaryColor}, ${ui.accentColor})` }}
|
|
31
|
+
>
|
|
32
|
+
Retrivora AI
|
|
33
|
+
</span>
|
|
34
|
+
<div className="flex items-center gap-1.5">
|
|
35
|
+
<span className="font-bold text-slate-900 dark:text-white text-[10px] tracking-[0.15em] uppercase opacity-70">Accelerator</span>
|
|
36
|
+
<span className="text-[9px] px-1.5 py-0.5 rounded-full bg-slate-100 dark:bg-white/5 text-slate-500 dark:text-white/40 font-mono border border-slate-200/50 dark:border-white/10">v{pkg?.version}</span>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div className="flex items-center gap-3">
|
|
42
|
+
<ThemeToggle />
|
|
43
|
+
<a href="https://www.npmjs.com/package/@retrivora-ai/rag-engine" target="_blank" rel="noreferrer" className="text-slate-500 hover:text-[#CB3837] dark:text-white/40 dark:hover:text-[#CB3837] text-sm transition-colors flex items-center gap-1.5">
|
|
44
|
+
<Package className="w-4 h-4" /> NPM
|
|
45
|
+
</a>
|
|
46
|
+
<a href="https://github.com/abhinav1201/ai-accelerator" target="_blank" rel="noreferrer" className="text-slate-500 hover:text-slate-900 dark:text-white/40 dark:hover:text-white/70 text-sm transition-colors flex items-center gap-1.5">
|
|
47
|
+
<GitBranch className="w-4 h-4" /> GitHub
|
|
48
|
+
</a>
|
|
49
|
+
<a href="/api/health" target="_blank" rel="noreferrer" className="px-3 py-1.5 rounded-lg text-xs border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/50 hover:text-slate-900 dark:hover:text-white/80 hover:bg-slate-100 dark:hover:bg-white/5 transition-all flex items-center gap-1.5">
|
|
50
|
+
<HeartPulse className="w-3.5 h-3.5" /> Health
|
|
51
|
+
</a>
|
|
52
|
+
</div>
|
|
53
|
+
</nav>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import {
|
|
5
|
+
Clock, Cpu, Zap, AlertTriangle, FileText, Layers,
|
|
6
|
+
ChevronDown, ChevronRight, CheckCircle, AlertCircle,
|
|
7
|
+
} from 'lucide-react';
|
|
8
|
+
import { ObservabilityTrace } from '../types';
|
|
9
|
+
|
|
10
|
+
// ─── Props ────────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
interface ObservabilityPanelProps {
|
|
13
|
+
trace: ObservabilityTrace;
|
|
14
|
+
primaryColor?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
function fmtMs(ms: number): string {
|
|
20
|
+
return ms >= 1000 ? `${(ms / 1000).toFixed(2)}s` : `${Math.round(ms)}ms`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function fmtTokens(n: number): string {
|
|
24
|
+
return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function ScoreBadge({ score }: { score: number }) {
|
|
28
|
+
const pct = Math.round(score * 100);
|
|
29
|
+
const color =
|
|
30
|
+
pct <= 20 ? 'text-emerald-600 bg-emerald-50 border-emerald-200 dark:text-emerald-400 dark:bg-emerald-500/10 dark:border-emerald-500/20'
|
|
31
|
+
: pct <= 50 ? 'text-amber-600 bg-amber-50 border-amber-200 dark:text-amber-400 dark:bg-amber-500/10 dark:border-amber-500/20'
|
|
32
|
+
: 'text-red-600 bg-red-50 border-red-200 dark:text-red-400 dark:bg-red-500/10 dark:border-red-500/20';
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<span className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-semibold border ${color}`}>
|
|
36
|
+
{pct <= 20 ? <CheckCircle className="w-3 h-3" /> : <AlertCircle className="w-3 h-3" />}
|
|
37
|
+
{pct}% hallucination risk
|
|
38
|
+
</span>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── Tab content renderers ────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
function RetrievalTab({ trace }: { trace: ObservabilityTrace }) {
|
|
45
|
+
return (
|
|
46
|
+
<div className="space-y-2">
|
|
47
|
+
<p className="text-[11px] text-slate-500 dark:text-white/40">
|
|
48
|
+
{trace.chunks.length} chunk{trace.chunks.length !== 1 ? 's' : ''} retrieved
|
|
49
|
+
{trace.rewrittenQuery && (
|
|
50
|
+
<span className="ml-2 italic">
|
|
51
|
+
(query rewritten: <em className="text-indigo-500">{trace.rewrittenQuery}</em>)
|
|
52
|
+
</span>
|
|
53
|
+
)}
|
|
54
|
+
</p>
|
|
55
|
+
<div className="space-y-1.5 max-h-56 overflow-y-auto pr-1 custom-scrollbar">
|
|
56
|
+
{trace.chunks.map((chunk, i) => (
|
|
57
|
+
<div
|
|
58
|
+
key={String(chunk.id)}
|
|
59
|
+
className="rounded-lg border border-slate-100 dark:border-white/8 bg-slate-50 dark:bg-white/3 p-2.5"
|
|
60
|
+
>
|
|
61
|
+
<div className="flex items-center justify-between mb-1">
|
|
62
|
+
<span className="text-[10px] font-mono text-slate-400 dark:text-white/30">
|
|
63
|
+
[{i + 1}] {String(chunk.id).slice(0, 20)}
|
|
64
|
+
</span>
|
|
65
|
+
<span className="text-[10px] font-semibold text-indigo-600 dark:text-indigo-400">
|
|
66
|
+
score: {chunk.score.toFixed(3)}
|
|
67
|
+
</span>
|
|
68
|
+
</div>
|
|
69
|
+
{/* Score bar */}
|
|
70
|
+
<div className="h-1 rounded-full bg-slate-200 dark:bg-white/10 mb-2 overflow-hidden">
|
|
71
|
+
<div
|
|
72
|
+
className="h-full rounded-full bg-indigo-500 dark:bg-indigo-400 transition-all"
|
|
73
|
+
style={{ width: `${Math.min(100, chunk.score * 100)}%` }}
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
<p className="text-[11px] text-slate-600 dark:text-white/60 line-clamp-2 leading-relaxed">
|
|
77
|
+
{chunk.content}
|
|
78
|
+
</p>
|
|
79
|
+
</div>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function LatencyTab({ trace }: { trace: ObservabilityTrace }) {
|
|
87
|
+
const { latency } = trace;
|
|
88
|
+
const bars = [
|
|
89
|
+
{ label: 'Embed', ms: latency.embedMs, color: '#6366f1' },
|
|
90
|
+
{ label: 'Retrieve', ms: latency.retrieveMs, color: '#10b981' },
|
|
91
|
+
...(latency.rerankMs != null ? [{ label: 'Rerank', ms: latency.rerankMs, color: '#f59e0b' }] : []),
|
|
92
|
+
{ label: 'Generate', ms: latency.generateMs, color: '#8b5cf6' },
|
|
93
|
+
];
|
|
94
|
+
const maxMs = Math.max(...bars.map(b => b.ms), 1);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className="space-y-3">
|
|
98
|
+
<div className="flex items-center justify-between">
|
|
99
|
+
<span className="text-[11px] text-slate-500 dark:text-white/40">Pipeline stages</span>
|
|
100
|
+
<span className="text-[11px] font-semibold text-slate-700 dark:text-white/70">
|
|
101
|
+
Total: {fmtMs(latency.totalMs)}
|
|
102
|
+
</span>
|
|
103
|
+
</div>
|
|
104
|
+
<div className="space-y-2">
|
|
105
|
+
{bars.map((bar) => (
|
|
106
|
+
<div key={bar.label} className="space-y-0.5">
|
|
107
|
+
<div className="flex items-center justify-between">
|
|
108
|
+
<span className="text-[11px] text-slate-600 dark:text-white/60">{bar.label}</span>
|
|
109
|
+
<span className="text-[11px] font-mono text-slate-500 dark:text-white/40">{fmtMs(bar.ms)}</span>
|
|
110
|
+
</div>
|
|
111
|
+
<div className="h-2 rounded-full bg-slate-100 dark:bg-white/8 overflow-hidden">
|
|
112
|
+
<div
|
|
113
|
+
className="h-full rounded-full transition-all duration-700"
|
|
114
|
+
style={{
|
|
115
|
+
width: `${(bar.ms / maxMs) * 100}%`,
|
|
116
|
+
background: bar.color,
|
|
117
|
+
opacity: 0.85,
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function TokensTab({ trace }: { trace: ObservabilityTrace }) {
|
|
129
|
+
const { tokens } = trace;
|
|
130
|
+
if (!tokens) {
|
|
131
|
+
return <p className="text-[11px] text-slate-400 dark:text-white/30 italic">Token data not available.</p>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const stats = [
|
|
135
|
+
{ label: 'Prompt Tokens', value: fmtTokens(tokens.promptTokens), color: 'text-indigo-600 dark:text-indigo-400' },
|
|
136
|
+
{ label: 'Completion Tokens', value: fmtTokens(tokens.completionTokens), color: 'text-emerald-600 dark:text-emerald-400' },
|
|
137
|
+
{ label: 'Total Tokens', value: fmtTokens(tokens.totalTokens), color: 'text-slate-700 dark:text-white/80' },
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<div className="space-y-3">
|
|
142
|
+
<div className="grid grid-cols-3 gap-2">
|
|
143
|
+
{stats.map((s) => (
|
|
144
|
+
<div key={s.label} className="rounded-lg border border-slate-100 dark:border-white/8 bg-slate-50 dark:bg-white/3 p-2.5 text-center">
|
|
145
|
+
<p className={`text-lg font-bold ${s.color}`}>{s.value}</p>
|
|
146
|
+
<p className="text-[10px] text-slate-400 dark:text-white/30 mt-0.5">{s.label}</p>
|
|
147
|
+
</div>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
{tokens.estimatedCostUsd != null && (
|
|
151
|
+
<div className="flex items-center justify-between rounded-lg border border-emerald-100 dark:border-emerald-500/20 bg-emerald-50 dark:bg-emerald-500/10 px-3 py-2">
|
|
152
|
+
<span className="text-[11px] text-emerald-700 dark:text-emerald-400 font-medium">Estimated Cost</span>
|
|
153
|
+
<span className="text-[11px] font-bold text-emerald-700 dark:text-emerald-400">
|
|
154
|
+
${tokens.estimatedCostUsd < 0.0001
|
|
155
|
+
? tokens.estimatedCostUsd.toExponential(2)
|
|
156
|
+
: tokens.estimatedCostUsd.toFixed(5)} USD
|
|
157
|
+
</span>
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
{/* Visual token proportion bar */}
|
|
161
|
+
<div className="h-2 rounded-full overflow-hidden flex">
|
|
162
|
+
<div
|
|
163
|
+
className="h-full bg-indigo-500 dark:bg-indigo-400"
|
|
164
|
+
style={{ width: `${(tokens.promptTokens / tokens.totalTokens) * 100}%` }}
|
|
165
|
+
title="Prompt tokens"
|
|
166
|
+
/>
|
|
167
|
+
<div
|
|
168
|
+
className="h-full bg-emerald-500 dark:bg-emerald-400"
|
|
169
|
+
style={{ width: `${(tokens.completionTokens / tokens.totalTokens) * 100}%` }}
|
|
170
|
+
title="Completion tokens"
|
|
171
|
+
/>
|
|
172
|
+
</div>
|
|
173
|
+
<div className="flex gap-3 text-[10px] text-slate-400 dark:text-white/30">
|
|
174
|
+
<span className="flex items-center gap-1"><span className="inline-block w-2 h-2 rounded-full bg-indigo-500" /> Prompt</span>
|
|
175
|
+
<span className="flex items-center gap-1"><span className="inline-block w-2 h-2 rounded-full bg-emerald-500" /> Completion</span>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function HallucinationTab({ trace }: { trace: ObservabilityTrace }) {
|
|
182
|
+
if (trace.hallucinationScore == null) {
|
|
183
|
+
return <p className="text-[11px] text-slate-400 dark:text-white/30 italic">Hallucination scoring not available for this response.</p>;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const score = trace.hallucinationScore;
|
|
187
|
+
const pct = Math.round(score * 100);
|
|
188
|
+
const barColor = pct <= 20 ? '#10b981' : pct <= 50 ? '#f59e0b' : '#ef4444';
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div className="space-y-3">
|
|
192
|
+
<div className="flex items-center justify-between">
|
|
193
|
+
<ScoreBadge score={score} />
|
|
194
|
+
<span className="text-[11px] font-mono text-slate-500 dark:text-white/40">{pct}/100</span>
|
|
195
|
+
</div>
|
|
196
|
+
<div className="h-3 rounded-full bg-slate-100 dark:bg-white/8 overflow-hidden">
|
|
197
|
+
<div
|
|
198
|
+
className="h-full rounded-full transition-all duration-700"
|
|
199
|
+
style={{ width: `${pct}%`, background: barColor }}
|
|
200
|
+
/>
|
|
201
|
+
</div>
|
|
202
|
+
{trace.hallucinationReason && (
|
|
203
|
+
<div className="rounded-lg border border-slate-100 dark:border-white/8 bg-slate-50 dark:bg-white/3 p-2.5">
|
|
204
|
+
<p className="text-[11px] text-slate-500 dark:text-white/40 font-medium mb-0.5">AI self-assessment</p>
|
|
205
|
+
<p className="text-[11px] text-slate-700 dark:text-white/70 leading-relaxed">{trace.hallucinationReason}</p>
|
|
206
|
+
</div>
|
|
207
|
+
)}
|
|
208
|
+
<div className="flex gap-2 text-[10px] text-slate-400 dark:text-white/30">
|
|
209
|
+
<span className="flex items-center gap-1"><span className="inline-block w-2 h-2 rounded-full bg-emerald-500" /> 0–20%: Grounded</span>
|
|
210
|
+
<span className="flex items-center gap-1"><span className="inline-block w-2 h-2 rounded-full bg-amber-500" /> 21–50%: Uncertain</span>
|
|
211
|
+
<span className="flex items-center gap-1"><span className="inline-block w-2 h-2 rounded-full bg-red-500" /> 51–100%: Risk</span>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function PromptTab({ trace }: { trace: ObservabilityTrace }) {
|
|
218
|
+
const [showSystem, setShowSystem] = React.useState(false);
|
|
219
|
+
return (
|
|
220
|
+
<div className="space-y-3">
|
|
221
|
+
<div>
|
|
222
|
+
<button
|
|
223
|
+
onClick={() => setShowSystem(s => !s)}
|
|
224
|
+
className="flex items-center gap-1.5 text-[11px] font-semibold text-slate-600 dark:text-white/60 hover:text-slate-900 dark:hover:text-white/80 transition-colors mb-1.5"
|
|
225
|
+
>
|
|
226
|
+
{showSystem ? <ChevronDown className="w-3 h-3" /> : <ChevronRight className="w-3 h-3" />}
|
|
227
|
+
System Prompt
|
|
228
|
+
</button>
|
|
229
|
+
{showSystem && (
|
|
230
|
+
<pre className="text-[10px] font-mono text-slate-600 dark:text-white/60 bg-slate-900 dark:bg-black/30 rounded-lg p-3 overflow-auto max-h-40 whitespace-pre-wrap leading-relaxed">
|
|
231
|
+
{trace.systemPrompt || '(empty)'}
|
|
232
|
+
</pre>
|
|
233
|
+
)}
|
|
234
|
+
</div>
|
|
235
|
+
<div>
|
|
236
|
+
<p className="text-[11px] font-semibold text-slate-600 dark:text-white/60 mb-1.5">User Prompt</p>
|
|
237
|
+
<pre className="text-[10px] font-mono text-slate-600 dark:text-white/60 bg-slate-900 dark:bg-black/30 rounded-lg p-3 overflow-auto max-h-40 whitespace-pre-wrap leading-relaxed">
|
|
238
|
+
{trace.userPrompt}
|
|
239
|
+
</pre>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function ChunksTab({ trace }: { trace: ObservabilityTrace }) {
|
|
246
|
+
const [expanded, setExpanded] = React.useState<Set<number>>(new Set());
|
|
247
|
+
const toggle = (i: number) => setExpanded(prev => {
|
|
248
|
+
const next = new Set(prev);
|
|
249
|
+
if (next.has(i)) {
|
|
250
|
+
next.delete(i);
|
|
251
|
+
} else {
|
|
252
|
+
next.add(i);
|
|
253
|
+
}
|
|
254
|
+
return next;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<div className="space-y-1.5 max-h-64 overflow-y-auto pr-1 custom-scrollbar">
|
|
259
|
+
{trace.chunks.map((chunk, i) => {
|
|
260
|
+
const isOpen = expanded.has(i);
|
|
261
|
+
return (
|
|
262
|
+
<div
|
|
263
|
+
key={String(chunk.id)}
|
|
264
|
+
className="rounded-lg border border-slate-100 dark:border-white/8 bg-slate-50 dark:bg-white/3 overflow-hidden"
|
|
265
|
+
>
|
|
266
|
+
<button
|
|
267
|
+
onClick={() => toggle(i)}
|
|
268
|
+
className="w-full flex items-center justify-between px-3 py-2 text-left hover:bg-slate-100 dark:hover:bg-white/5 transition-colors"
|
|
269
|
+
>
|
|
270
|
+
<div className="flex items-center gap-2">
|
|
271
|
+
{isOpen ? <ChevronDown className="w-3 h-3 text-slate-400" /> : <ChevronRight className="w-3 h-3 text-slate-400" />}
|
|
272
|
+
<span className="text-[11px] font-medium text-slate-700 dark:text-white/70">
|
|
273
|
+
Chunk {i + 1}
|
|
274
|
+
</span>
|
|
275
|
+
<span className="text-[10px] font-mono text-slate-400 dark:text-white/30">
|
|
276
|
+
{String(chunk.id).slice(0, 16)}
|
|
277
|
+
</span>
|
|
278
|
+
</div>
|
|
279
|
+
<span className="text-[10px] font-semibold text-indigo-600 dark:text-indigo-400">
|
|
280
|
+
{chunk.score.toFixed(3)}
|
|
281
|
+
</span>
|
|
282
|
+
</button>
|
|
283
|
+
|
|
284
|
+
{isOpen && (
|
|
285
|
+
<div className="px-3 pb-3 space-y-2 border-t border-slate-100 dark:border-white/5 pt-2">
|
|
286
|
+
<div>
|
|
287
|
+
<p className="text-[10px] text-slate-400 dark:text-white/30 font-semibold uppercase tracking-wider mb-1">Content</p>
|
|
288
|
+
<p className="text-[11px] text-slate-600 dark:text-white/60 leading-relaxed whitespace-pre-wrap">
|
|
289
|
+
{chunk.content}
|
|
290
|
+
</p>
|
|
291
|
+
</div>
|
|
292
|
+
{Object.keys(chunk.metadata).length > 0 && (
|
|
293
|
+
<div>
|
|
294
|
+
<p className="text-[10px] text-slate-400 dark:text-white/30 font-semibold uppercase tracking-wider mb-1">Metadata</p>
|
|
295
|
+
<pre className="text-[10px] font-mono text-slate-500 dark:text-white/40 bg-slate-900 dark:bg-black/30 rounded-md p-2 overflow-auto max-h-24">
|
|
296
|
+
{JSON.stringify(chunk.metadata, null, 2)}
|
|
297
|
+
</pre>
|
|
298
|
+
</div>
|
|
299
|
+
)}
|
|
300
|
+
</div>
|
|
301
|
+
)}
|
|
302
|
+
</div>
|
|
303
|
+
);
|
|
304
|
+
})}
|
|
305
|
+
</div>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ─── Main component ───────────────────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
type TabId = 'retrieval' | 'latency' | 'tokens' | 'hallucination' | 'prompt' | 'chunks';
|
|
312
|
+
|
|
313
|
+
const TABS: { id: TabId; label: string; icon: React.ReactNode }[] = [
|
|
314
|
+
{ id: 'retrieval', label: 'Retrieval', icon: <Layers className="w-3 h-3" /> },
|
|
315
|
+
{ id: 'latency', label: 'Latency', icon: <Clock className="w-3 h-3" /> },
|
|
316
|
+
{ id: 'tokens', label: 'Tokens', icon: <Cpu className="w-3 h-3" /> },
|
|
317
|
+
{ id: 'hallucination',label: 'Grounding', icon: <AlertTriangle className="w-3 h-3" /> },
|
|
318
|
+
{ id: 'prompt', label: 'Prompt', icon: <FileText className="w-3 h-3" /> },
|
|
319
|
+
{ id: 'chunks', label: 'Chunks', icon: <Zap className="w-3 h-3" /> },
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
export function ObservabilityPanel({ trace, primaryColor = '#6366f1' }: ObservabilityPanelProps) {
|
|
323
|
+
const [activeTab, setActiveTab] = React.useState<TabId>('retrieval');
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<div className="mt-2 w-full rounded-xl border border-slate-200 dark:border-white/8 bg-white dark:bg-slate-900/60 shadow-sm overflow-hidden text-[12px]">
|
|
327
|
+
{/* Header */}
|
|
328
|
+
<div
|
|
329
|
+
className="flex items-center gap-2 px-3 py-2 border-b border-slate-100 dark:border-white/8"
|
|
330
|
+
style={{ background: `${primaryColor}08` }}
|
|
331
|
+
>
|
|
332
|
+
<span
|
|
333
|
+
className="inline-flex items-center gap-1 text-[10px] font-bold uppercase tracking-wider"
|
|
334
|
+
style={{ color: primaryColor }}
|
|
335
|
+
>
|
|
336
|
+
<Zap className="w-3 h-3" />
|
|
337
|
+
Observability Trace
|
|
338
|
+
</span>
|
|
339
|
+
<span className="text-[10px] text-slate-400 dark:text-white/30 ml-auto font-mono">
|
|
340
|
+
{trace.requestId}
|
|
341
|
+
</span>
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
{/* Tabs */}
|
|
345
|
+
<div className="flex border-b border-slate-100 dark:border-white/8 overflow-x-auto">
|
|
346
|
+
{TABS.map((tab) => (
|
|
347
|
+
<button
|
|
348
|
+
key={tab.id}
|
|
349
|
+
onClick={() => setActiveTab(tab.id)}
|
|
350
|
+
className={`flex items-center gap-1.5 px-3 py-2 text-[10px] font-semibold whitespace-nowrap transition-colors border-b-2 ${
|
|
351
|
+
activeTab === tab.id
|
|
352
|
+
? 'border-b-2 text-indigo-600 dark:text-indigo-400'
|
|
353
|
+
: 'border-transparent text-slate-400 dark:text-white/30 hover:text-slate-600 dark:hover:text-white/60'
|
|
354
|
+
}`}
|
|
355
|
+
style={activeTab === tab.id ? { borderBottomColor: primaryColor, color: primaryColor } : {}}
|
|
356
|
+
>
|
|
357
|
+
{tab.icon}
|
|
358
|
+
{tab.label}
|
|
359
|
+
</button>
|
|
360
|
+
))}
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
{/* Tab body */}
|
|
364
|
+
<div className="p-3">
|
|
365
|
+
{activeTab === 'retrieval' && <RetrievalTab trace={trace} />}
|
|
366
|
+
{activeTab === 'latency' && <LatencyTab trace={trace} />}
|
|
367
|
+
{activeTab === 'tokens' && <TokensTab trace={trace} />}
|
|
368
|
+
{activeTab === 'hallucination'&& <HallucinationTab trace={trace} />}
|
|
369
|
+
{activeTab === 'prompt' && <PromptTab trace={trace} />}
|
|
370
|
+
{activeTab === 'chunks' && <ChunksTab trace={trace} />}
|
|
371
|
+
</div>
|
|
372
|
+
</div>
|
|
373
|
+
);
|
|
374
|
+
}
|
|
@@ -24,7 +24,7 @@ export function ProductCard({ product, primaryColor = '#6366f1', onAddToCart }:
|
|
|
24
24
|
<ShoppingCart className="w-12 h-12" />
|
|
25
25
|
</div>
|
|
26
26
|
)}
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
{/* Brand Badge */}
|
|
29
29
|
{product.brand && (
|
|
30
30
|
<div className="absolute top-2 left-2 px-2 py-1 bg-white/90 dark:bg-black/60 backdrop-blur-md rounded-md text-[10px] font-bold uppercase tracking-wider text-slate-700 dark:text-white/90 shadow-sm">
|
|
@@ -41,11 +41,13 @@ export function ProductCard({ product, primaryColor = '#6366f1', onAddToCart }:
|
|
|
41
41
|
</h3>
|
|
42
42
|
{product.price && (
|
|
43
43
|
<span className="text-sm font-bold" style={{ color: primaryColor }}>
|
|
44
|
-
{typeof product.price === 'number'
|
|
44
|
+
{typeof product.price === 'number'
|
|
45
|
+
? `$${product.price}`
|
|
46
|
+
: (String(product.price).match(/^[\d.]/) ? `$${product.price}` : product.price)}
|
|
45
47
|
</span>
|
|
46
48
|
)}
|
|
47
49
|
</div>
|
|
48
|
-
|
|
50
|
+
|
|
49
51
|
{product.description && (
|
|
50
52
|
<p className="text-[11px] text-slate-500 dark:text-white/50 line-clamp-2 leading-relaxed">
|
|
51
53
|
{product.description}
|