@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,58 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { FileText, Zap, Package, ExternalLink } from 'lucide-react';
|
|
5
|
+
import { DocViewer } from '@/components/DocViewer';
|
|
6
|
+
import { LANDING_PAGE_CONTENT, QUICK_START_STEPS, API_ENDPOINTS, SNIPPETS } from '@/app/constants';
|
|
7
|
+
import { Snippet } from '@/app/types';
|
|
8
|
+
|
|
9
|
+
export function Documentation() {
|
|
10
|
+
const [activeSnippet, setActiveSnippet] = React.useState<Snippet>(SNIPPETS[0]);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="mt-32 max-w-6xl mx-auto mb-24 relative">
|
|
14
|
+
<div className="text-center mb-16">
|
|
15
|
+
<h2 className="text-3xl font-bold text-slate-900 dark:text-white mb-4">{LANDING_PAGE_CONTENT.guide.title}</h2>
|
|
16
|
+
<p className="text-slate-600 dark:text-white/40 max-w-2xl mx-auto">{LANDING_PAGE_CONTENT.guide.subtitle}</p>
|
|
17
|
+
</div>
|
|
18
|
+
<div className="relative z-10 w-full grid gap-16 lg:grid-cols-[280px_1fr] items-start">
|
|
19
|
+
<aside className="flex flex-col gap-10">
|
|
20
|
+
<section>
|
|
21
|
+
<h2 className="mb-6 text-[10px] font-black text-slate-400 dark:text-white/30 uppercase tracking-[0.3em] flex items-center gap-3">
|
|
22
|
+
<FileText size={14} className="text-indigo-500" /> Quick Start
|
|
23
|
+
</h2>
|
|
24
|
+
<ol className="space-y-6">
|
|
25
|
+
{QUICK_START_STEPS.map((step, i) => (
|
|
26
|
+
<li key={i} className="group flex items-start gap-4">
|
|
27
|
+
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg bg-white dark:bg-white/5 text-slate-600 dark:text-white/40 font-mono text-[10px] border border-slate-200 dark:border-white/10 shadow-sm">{i + 1}</span>
|
|
28
|
+
<span className="text-xs text-slate-500 dark:text-white/50 font-medium leading-relaxed pt-1">{step}</span>
|
|
29
|
+
</li>
|
|
30
|
+
))}
|
|
31
|
+
</ol>
|
|
32
|
+
</section>
|
|
33
|
+
<section className="rounded-2xl border border-slate-200 dark:border-white/10 bg-white dark:bg-white/3 p-6 shadow-sm">
|
|
34
|
+
<h2 className="mb-6 text-[10px] font-black text-slate-400 dark:text-white/30 uppercase tracking-[0.3em] flex items-center gap-3">
|
|
35
|
+
<Zap size={14} className="text-indigo-500" /> API Endpoints
|
|
36
|
+
</h2>
|
|
37
|
+
<div className="grid gap-2.5">
|
|
38
|
+
{API_ENDPOINTS.map((slug) => (
|
|
39
|
+
<div key={slug} className="group flex items-center justify-between p-2 rounded-lg bg-slate-50 dark:bg-white/5 border border-slate-100 dark:border-white/5 hover:border-indigo-200 dark:hover:border-indigo-500/30">
|
|
40
|
+
<span className="text-[10px] font-mono text-slate-500 dark:text-white/30 group-hover:text-indigo-600">/api/{slug}</span>
|
|
41
|
+
<span className="text-[8px] font-bold text-indigo-600 dark:text-indigo-400 bg-indigo-50 dark:bg-indigo-500/10 px-1.5 py-0.5 rounded border border-indigo-100 dark:border-indigo-500/20">POST</span>
|
|
42
|
+
</div>
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
</section>
|
|
46
|
+
</aside>
|
|
47
|
+
<section className="flex flex-col h-full min-h-[560px]">
|
|
48
|
+
<DocViewer activeSnippet={activeSnippet} setActiveSnippet={setActiveSnippet} />
|
|
49
|
+
</section>
|
|
50
|
+
</div>
|
|
51
|
+
<div className="mt-16 text-center">
|
|
52
|
+
<a href="https://www.npmjs.com/package/@retrivora-ai/rag-engine" className="inline-flex items-center gap-3 px-8 py-4 rounded-2xl bg-slate-900 dark:bg-white text-white dark:text-slate-900 font-bold shadow-xl shadow-indigo-500/20">
|
|
53
|
+
<Package className="w-5 h-5" /> Get Started on NPM <ExternalLink className="w-4 h-4" />
|
|
54
|
+
</a>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -20,18 +20,22 @@ interface DynamicChartProps {
|
|
|
20
20
|
primaryColor?: string;
|
|
21
21
|
accentColor?: string;
|
|
22
22
|
viewportSize?: ChatViewportSize;
|
|
23
|
+
/** When true, renders a shimmer skeleton instead of the chart. */
|
|
24
|
+
isStreaming?: boolean;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
/** Shared palette — imported by VisualizationRenderer to avoid duplication. */
|
|
28
|
+
export const CHART_COLORS = ['#6366f1', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899'];
|
|
26
29
|
|
|
27
30
|
export function DynamicChart({
|
|
28
31
|
config,
|
|
29
32
|
primaryColor = '#6366f1',
|
|
30
33
|
accentColor = '#8b5cf6',
|
|
31
34
|
viewportSize = 'large',
|
|
35
|
+
isStreaming = false,
|
|
32
36
|
}: DynamicChartProps) {
|
|
33
37
|
const { type, data } = config;
|
|
34
|
-
const colors = config.colors || [primaryColor, accentColor, ...
|
|
38
|
+
const colors = config.colors || [primaryColor, accentColor, ...CHART_COLORS];
|
|
35
39
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
36
40
|
const [containerWidth, setContainerWidth] = React.useState(0);
|
|
37
41
|
|
|
@@ -111,6 +115,27 @@ export function DynamicChart({
|
|
|
111
115
|
});
|
|
112
116
|
}, [data]);
|
|
113
117
|
|
|
118
|
+
if (isStreaming) {
|
|
119
|
+
const bars = type === 'pie' ? 6 : 5;
|
|
120
|
+
return (
|
|
121
|
+
<div className="w-full mt-4 mb-2 animate-pulse">
|
|
122
|
+
{type === 'pie' ? (
|
|
123
|
+
<div className="mx-auto w-40 h-40 rounded-full bg-slate-100 dark:bg-white/10" />
|
|
124
|
+
) : (
|
|
125
|
+
<div className="flex items-end gap-2 h-32 px-2">
|
|
126
|
+
{Array.from({ length: bars }).map((_, i) => (
|
|
127
|
+
<div
|
|
128
|
+
key={i}
|
|
129
|
+
className="flex-1 rounded-t-md bg-slate-100 dark:bg-white/10"
|
|
130
|
+
style={{ height: `${40 + (i % 3) * 25}%` }}
|
|
131
|
+
/>
|
|
132
|
+
))}
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
114
139
|
if (!data || data.length === 0) {
|
|
115
140
|
return <div className="p-4 text-sm text-slate-500">No data available for chart.</div>;
|
|
116
141
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { useConfig } from '@/components/ConfigProvider';
|
|
5
|
+
import { ChatWindow } from '@/components/ChatWindow';
|
|
6
|
+
import { LANDING_PAGE_CONTENT, VECTOR_DATABASES, AI_MODELS } from '@/app/constants';
|
|
7
|
+
|
|
8
|
+
export function Hero() {
|
|
9
|
+
const { ui } = useConfig();
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="grid lg:grid-cols-2 gap-12 items-start">
|
|
13
|
+
<div className="flex flex-col gap-6 pt-4">
|
|
14
|
+
<div
|
|
15
|
+
className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full text-xs font-medium border w-fit"
|
|
16
|
+
style={{ borderColor: `${ui.primaryColor}40`, color: ui.primaryColor, background: `${ui.primaryColor}10` }}
|
|
17
|
+
>
|
|
18
|
+
<span className="w-1.5 h-1.5 rounded-full animate-pulse" style={{ background: ui.primaryColor }} />
|
|
19
|
+
{LANDING_PAGE_CONTENT.hero.badge}
|
|
20
|
+
</div>
|
|
21
|
+
<h1 className="text-5xl md:text-7xl font-black text-slate-900 dark:text-white mb-6 tracking-tight">
|
|
22
|
+
{LANDING_PAGE_CONTENT.hero.title.split('for')[0]} <br />
|
|
23
|
+
<span className="text-transparent bg-clip-text" style={{ backgroundImage: `linear-gradient(to right, ${ui.primaryColor}, ${ui.accentColor})` }}>
|
|
24
|
+
for {LANDING_PAGE_CONTENT.hero.title.split('for')[1]}
|
|
25
|
+
</span>
|
|
26
|
+
</h1>
|
|
27
|
+
<p className="text-lg md:text-xl text-slate-600 dark:text-white/40 max-w-2xl leading-relaxed">
|
|
28
|
+
{LANDING_PAGE_CONTENT.hero.subtitle}
|
|
29
|
+
</p>
|
|
30
|
+
<div className="flex flex-col gap-4 mt-4">
|
|
31
|
+
<div className="flex flex-col gap-2">
|
|
32
|
+
<span className="text-[10px] font-black text-slate-400 dark:text-white/30 uppercase tracking-[0.2em] ml-1">Vector Ecosystem</span>
|
|
33
|
+
<div className="flex flex-wrap gap-2">
|
|
34
|
+
{VECTOR_DATABASES.map(({ Icon, label }) => (
|
|
35
|
+
<span key={label} className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/60 text-[11px] font-semibold hover:bg-slate-50 dark:hover:bg-white/8 transition-all shadow-sm group">
|
|
36
|
+
<Icon className="w-3 h-3 transition-transform group-hover:scale-110" style={{ color: ui.primaryColor }} /> {label}
|
|
37
|
+
</span>
|
|
38
|
+
))}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div className="flex flex-col gap-2">
|
|
43
|
+
<span className="text-[10px] font-black text-slate-400 dark:text-white/30 uppercase tracking-[0.2em] ml-1">AI Model Garden</span>
|
|
44
|
+
<div className="flex flex-wrap gap-2">
|
|
45
|
+
{AI_MODELS.map(({ Icon, label }) => (
|
|
46
|
+
<span key={label} className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/60 text-[11px] font-semibold hover:bg-slate-50 dark:hover:bg-white/8 transition-all shadow-sm group">
|
|
47
|
+
<Icon className="w-3 h-3 transition-transform group-hover:scale-110" style={{ color: ui.accentColor }} /> {label}
|
|
48
|
+
</span>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="lg:sticky lg:top-6 h-[calc(100vh-3rem)] min-h-[500px] max-h-[800px]">
|
|
55
|
+
<ChatWindow className="w-full h-full" />
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface HourglassLoaderProps {
|
|
4
|
+
size?: number;
|
|
5
|
+
primaryColor?: string;
|
|
6
|
+
accentColor?: string;
|
|
7
|
+
glow?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const HourglassLoader: React.FC<HourglassLoaderProps> = ({
|
|
11
|
+
size = 16,
|
|
12
|
+
primaryColor = '#10b981',
|
|
13
|
+
glow = false,
|
|
14
|
+
}) => {
|
|
15
|
+
return (
|
|
16
|
+
<svg
|
|
17
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
18
|
+
viewBox="0 0 100 100"
|
|
19
|
+
preserveAspectRatio="xMidYMid"
|
|
20
|
+
style={{
|
|
21
|
+
width: size,
|
|
22
|
+
height: size,
|
|
23
|
+
background: 'transparent',
|
|
24
|
+
filter: glow ? `drop-shadow(0 0 4px ${primaryColor})` : 'none',
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
<g>
|
|
28
|
+
{/* Rotation Animation */}
|
|
29
|
+
<animateTransform
|
|
30
|
+
attributeName="transform"
|
|
31
|
+
type="rotate"
|
|
32
|
+
calcMode="spline"
|
|
33
|
+
values="0 50 50; 0 50 50; 180 50 50"
|
|
34
|
+
keyTimes="0; 0.8; 1"
|
|
35
|
+
keySplines="0 0 1 1; 0.4 0 0.2 1"
|
|
36
|
+
dur="2s"
|
|
37
|
+
repeatCount="indefinite"
|
|
38
|
+
/>
|
|
39
|
+
|
|
40
|
+
{/* Glass Outline */}
|
|
41
|
+
<path
|
|
42
|
+
d="M 25 15 L 75 15 L 75 20 L 55 50 L 75 80 L 75 85 L 25 85 L 25 80 L 45 50 L 25 20 Z"
|
|
43
|
+
fill="none"
|
|
44
|
+
stroke={primaryColor}
|
|
45
|
+
strokeWidth="5"
|
|
46
|
+
strokeLinejoin="round"
|
|
47
|
+
strokeLinecap="round"
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
{/* Top Sand */}
|
|
51
|
+
<path fill={primaryColor}>
|
|
52
|
+
<animate
|
|
53
|
+
attributeName="d"
|
|
54
|
+
values="M 30 20 L 70 20 L 50 50 L 50 50 Z; M 50 50 L 50 50 L 50 50 L 50 50 Z; M 50 50 L 50 50 L 50 50 L 50 50 Z"
|
|
55
|
+
keyTimes="0; 0.8; 1"
|
|
56
|
+
dur="2s"
|
|
57
|
+
repeatCount="indefinite"
|
|
58
|
+
/>
|
|
59
|
+
</path>
|
|
60
|
+
|
|
61
|
+
{/* Bottom Sand */}
|
|
62
|
+
<path fill={primaryColor}>
|
|
63
|
+
<animate
|
|
64
|
+
attributeName="d"
|
|
65
|
+
values="M 30 80 L 70 80 L 70 80 L 30 80 Z; M 30 80 L 70 80 L 50 50 L 50 50 Z; M 30 80 L 70 80 L 50 50 L 50 50 Z"
|
|
66
|
+
keyTimes="0; 0.8; 1"
|
|
67
|
+
dur="2s"
|
|
68
|
+
repeatCount="indefinite"
|
|
69
|
+
/>
|
|
70
|
+
</path>
|
|
71
|
+
|
|
72
|
+
{/* Falling Sand Stream */}
|
|
73
|
+
<line x1="50" y1="50" x2="50" y2="80" stroke={primaryColor} strokeWidth="3">
|
|
74
|
+
<animate
|
|
75
|
+
attributeName="opacity"
|
|
76
|
+
values="0; 1; 1; 0; 0"
|
|
77
|
+
keyTimes="0; 0.1; 0.7; 0.8; 1"
|
|
78
|
+
dur="2s"
|
|
79
|
+
repeatCount="indefinite"
|
|
80
|
+
/>
|
|
81
|
+
</line>
|
|
82
|
+
</g>
|
|
83
|
+
</svg>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default HourglassLoader;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { LANDING_PAGE_CONTENT, PIPELINE_STEPS } from '@/app/constants';
|
|
5
|
+
|
|
6
|
+
export function Lifecycle() {
|
|
7
|
+
return (
|
|
8
|
+
<div className="mt-24 mb-12">
|
|
9
|
+
<h2 className="text-2xl font-bold text-slate-900 dark:text-white text-center mb-2">{LANDING_PAGE_CONTENT.lifecycle.title}</h2>
|
|
10
|
+
<div className="relative max-w-5xl mx-auto py-12">
|
|
11
|
+
<div className="absolute left-1/2 top-0 bottom-0 w-0.5 bg-slate-200 dark:bg-white/10 -translate-x-1/2 hidden md:block" />
|
|
12
|
+
<div className="space-y-12 md:space-y-0">
|
|
13
|
+
{PIPELINE_STEPS.map(({ step, Icon, title, desc, colors }, index) => {
|
|
14
|
+
const isLeft = index % 2 === 0;
|
|
15
|
+
return (
|
|
16
|
+
<div key={step} className={`relative flex flex-col md:flex-row items-center gap-8 ${isLeft ? 'md:flex-row' : 'md:flex-row-reverse'}`}>
|
|
17
|
+
<div className={`w-full md:w-1/2 relative flex ${isLeft ? 'justify-end' : 'justify-start'}`}>
|
|
18
|
+
<div className="relative p-6 rounded-2xl border border-slate-200 dark:border-white/10 bg-white dark:bg-white/3 max-w-md text-left overflow-hidden group">
|
|
19
|
+
<div className="absolute -right-2 -bottom-4 text-7xl font-bold opacity-[0.1] italic" style={{ color: colors.from }}>{step}</div>
|
|
20
|
+
<h3 className="font-semibold text-slate-900 dark:text-white mb-2 text-lg flex items-center gap-2">
|
|
21
|
+
<Icon className="w-5 h-5" style={{ color: colors.from }} /> {title}
|
|
22
|
+
</h3>
|
|
23
|
+
<p className="text-slate-600 dark:text-white/40 text-sm leading-relaxed">{desc}</p>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<div className="relative z-10 w-12 h-12 rounded-2xl flex items-center justify-center shadow-xl border-4 border-slate-50 dark:border-[#080811]" style={{ background: `linear-gradient(135deg, ${colors.from}, ${colors.to})` }}>
|
|
27
|
+
<Icon className="w-6 h-6 text-white" />
|
|
28
|
+
</div>
|
|
29
|
+
<div className="hidden md:block md:w-1/2" />
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
})}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Components } from 'react-markdown';
|
|
3
|
+
import { UIDispatcher } from './UIDispatcher';
|
|
4
|
+
import { Product } from '../types';
|
|
5
|
+
import { ChatViewportSize } from '../types/props';
|
|
6
|
+
|
|
7
|
+
interface MarkdownComponentsProps {
|
|
8
|
+
primaryColor: string;
|
|
9
|
+
accentColor: string;
|
|
10
|
+
isStreaming: boolean;
|
|
11
|
+
onAddToCart?: (product: Product) => void;
|
|
12
|
+
viewportSize: ChatViewportSize;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createMarkdownComponents({
|
|
16
|
+
primaryColor,
|
|
17
|
+
accentColor,
|
|
18
|
+
isStreaming,
|
|
19
|
+
onAddToCart,
|
|
20
|
+
viewportSize,
|
|
21
|
+
}: MarkdownComponentsProps): Components {
|
|
22
|
+
return {
|
|
23
|
+
table: ({ ...props }: React.HTMLAttributes<HTMLTableElement>) => {
|
|
24
|
+
// Fix: intercept table rendering during streaming instead of using sentinel
|
|
25
|
+
if (isStreaming) {
|
|
26
|
+
return (
|
|
27
|
+
<div className="my-5 p-6 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex items-center justify-center gap-3 select-none">
|
|
28
|
+
<div className="w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" />
|
|
29
|
+
<p className="text-xs text-slate-500 font-medium italic animate-pulse">
|
|
30
|
+
Generating data table...
|
|
31
|
+
</p>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="not-prose overflow-hidden my-5 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm bg-white dark:bg-slate-900/50">
|
|
38
|
+
<div className="overflow-x-auto">
|
|
39
|
+
<table
|
|
40
|
+
className="!table w-full text-left border-collapse min-w-[400px] text-sm"
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
},
|
|
47
|
+
thead: ({ ...props }: React.HTMLAttributes<HTMLTableSectionElement>) => (
|
|
48
|
+
<thead
|
|
49
|
+
className="!table-header-group bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10"
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
),
|
|
53
|
+
tbody: ({ ...props }: React.HTMLAttributes<HTMLTableSectionElement>) => (
|
|
54
|
+
<tbody className="!table-row-group divide-y divide-slate-100 dark:divide-white/5" {...props} />
|
|
55
|
+
),
|
|
56
|
+
tr: ({ ...props }: React.HTMLAttributes<HTMLTableRowElement>) => (
|
|
57
|
+
<tr
|
|
58
|
+
className="!table-row hover:bg-slate-50/70 dark:hover:bg-white/5 transition-colors"
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
),
|
|
62
|
+
th: ({ children, ...props }: React.ThHTMLAttributes<HTMLTableCellElement>) => (
|
|
63
|
+
<th
|
|
64
|
+
className="!table-cell px-4 py-3 font-bold text-slate-700 dark:text-white/90 whitespace-nowrap text-[11px] uppercase tracking-wider text-left"
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
{normaliseChild(children)}
|
|
68
|
+
</th>
|
|
69
|
+
),
|
|
70
|
+
td: ({ children, ...props }: React.TdHTMLAttributes<HTMLTableCellElement>) => (
|
|
71
|
+
<td
|
|
72
|
+
className="!table-cell px-4 py-3 text-slate-600 dark:text-white/70 whitespace-nowrap text-sm"
|
|
73
|
+
{...props}
|
|
74
|
+
>
|
|
75
|
+
{normaliseChild(children)}
|
|
76
|
+
</td>
|
|
77
|
+
),
|
|
78
|
+
code({
|
|
79
|
+
inline,
|
|
80
|
+
className,
|
|
81
|
+
children,
|
|
82
|
+
...props
|
|
83
|
+
}: React.HTMLAttributes<HTMLElement> & { inline?: boolean }) {
|
|
84
|
+
const lang = /language-(\w+)/.exec(className ?? '')?.[1];
|
|
85
|
+
|
|
86
|
+
if (!inline && (lang === 'ui' || lang === 'chart' || lang === 'json')) {
|
|
87
|
+
const content = String(children ?? '').trim();
|
|
88
|
+
// Since we extract pure components, UIDispatcher handles these formats
|
|
89
|
+
return (
|
|
90
|
+
<UIDispatcher
|
|
91
|
+
rawContent={content}
|
|
92
|
+
primaryColor={primaryColor}
|
|
93
|
+
accentColor={accentColor}
|
|
94
|
+
isStreaming={isStreaming}
|
|
95
|
+
onAddToCart={onAddToCart}
|
|
96
|
+
viewportSize={viewportSize}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!inline) {
|
|
102
|
+
const content = String(children ?? '').trim();
|
|
103
|
+
// Fallback catch for json payload without lang block
|
|
104
|
+
if (content.startsWith('{') && content.endsWith('}')) {
|
|
105
|
+
return (
|
|
106
|
+
<UIDispatcher
|
|
107
|
+
rawContent={content}
|
|
108
|
+
primaryColor={primaryColor}
|
|
109
|
+
accentColor={accentColor}
|
|
110
|
+
isStreaming={isStreaming}
|
|
111
|
+
onAddToCart={onAddToCart}
|
|
112
|
+
viewportSize={viewportSize}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<code className={className} {...props}>
|
|
120
|
+
{typeof children === 'string' || typeof children === 'number'
|
|
121
|
+
? children
|
|
122
|
+
: JSON.stringify(children)}
|
|
123
|
+
</code>
|
|
124
|
+
);
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Safely render table cell children that might be plain objects. */
|
|
130
|
+
function normaliseChild(children: unknown): React.ReactNode {
|
|
131
|
+
if (
|
|
132
|
+
typeof children === 'object' &&
|
|
133
|
+
children !== null &&
|
|
134
|
+
!Array.isArray(children) &&
|
|
135
|
+
!React.isValidElement(children)
|
|
136
|
+
) {
|
|
137
|
+
return JSON.stringify(children);
|
|
138
|
+
}
|
|
139
|
+
return children as React.ReactNode;
|
|
140
|
+
}
|