@retrivora-ai/rag-engine 1.2.1 → 1.2.3
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-BXOUMKoP.d.ts +93 -0
- package/dist/DocumentChunker-D1dg5iCi.d.mts +93 -0
- package/dist/{chunk-GCPPRD2G.mjs → chunk-WGPNEAK3.mjs} +37 -3
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +37 -3
- package/dist/handlers/index.mjs +1 -1
- package/dist/{index-DbtE8wLM.d.ts → index-B67KQ9NN.d.ts} +1 -1
- package/dist/{RagConfig-BOLOz0_O.d.mts → index-Cti1u0y1.d.mts} +86 -94
- package/dist/{RagConfig-BOLOz0_O.d.ts → index-Cti1u0y1.d.ts} +86 -94
- package/dist/{index-64BDupW3.d.mts → index-kUXnRvuI.d.mts} +1 -1
- package/dist/index.d.mts +52 -78
- package/dist/index.d.ts +52 -78
- package/dist/index.js +313 -124
- package/dist/index.mjs +293 -97
- package/dist/server.d.mts +5 -4
- package/dist/server.d.ts +5 -4
- package/dist/server.js +37 -3
- package/dist/server.mjs +1 -1
- package/package.json +2 -1
- package/src/components/ChatWidget.tsx +1 -8
- package/src/components/ChatWindow.tsx +120 -29
- package/src/components/ConfigProvider.tsx +7 -33
- package/src/components/DocumentUpload.tsx +1 -8
- package/src/components/DynamicChart.tsx +113 -0
- package/src/components/MessageBubble.tsx +51 -13
- package/src/components/ProductCard.tsx +1 -7
- package/src/components/ProductCarousel.tsx +1 -7
- package/src/components/SourceCard.tsx +1 -6
- package/src/config/RagConfig.ts +2 -0
- package/src/config/uiConstants.ts +23 -0
- package/src/core/LangChainAgent.ts +20 -2
- package/src/core/Pipeline.ts +19 -1
- package/src/core/VectorPlugin.ts +1 -1
- package/src/handlers/index.ts +1 -1
- package/src/hooks/useRagChat.ts +1 -45
- package/src/hooks/useStoredMessages.ts +1 -1
- package/src/index.ts +20 -5
- package/src/llm/ILLMProvider.ts +1 -13
- package/src/llm/providers/AnthropicProvider.ts +2 -1
- package/src/llm/providers/GeminiProvider.ts +2 -1
- package/src/llm/providers/OllamaProvider.ts +2 -1
- package/src/llm/providers/OpenAIProvider.ts +2 -1
- package/src/llm/providers/UniversalLLMAdapter.ts +2 -1
- package/src/server.ts +2 -2
- package/src/types/chat.ts +53 -0
- package/src/types/index.ts +3 -0
- package/src/types/props.ts +79 -0
- package/dist/DocumentChunker-Dh9TvmGG.d.mts +0 -45
- package/dist/DocumentChunker-Dh9TvmGG.d.ts +0 -45
package/dist/server.d.mts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { j as VectorDBConfig, L as LLMConfig, E as EmbeddingConfig, h as RagConfig, I as IngestDocument, C as ChatMessage, d as ChatResponse, l as RetrievalResult, i as UpsertDocument, V as VectorMatch, G as GraphDBConfig, m as GraphNode, n as Edge, o as GraphSearchResult, k as VectorDBProvider, f as LLMProvider, e as EmbeddingProvider, g as RAGConfig, U as UIConfig, c as ChatOptions } from './index-Cti1u0y1.mjs';
|
|
2
|
+
import { I as ILLMProvider, E as EmbedOptions } from './DocumentChunker-D1dg5iCi.mjs';
|
|
3
|
+
export { C as Chunk, a as ChunkOptions, D as DocumentChunker } from './DocumentChunker-D1dg5iCi.mjs';
|
|
4
|
+
import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-kUXnRvuI.mjs';
|
|
5
|
+
export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-kUXnRvuI.mjs';
|
|
5
6
|
import 'next/server';
|
|
6
7
|
|
|
7
8
|
/**
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { j as VectorDBConfig, L as LLMConfig, E as EmbeddingConfig, h as RagConfig, I as IngestDocument, C as ChatMessage, d as ChatResponse, l as RetrievalResult, i as UpsertDocument, V as VectorMatch, G as GraphDBConfig, m as GraphNode, n as Edge, o as GraphSearchResult, k as VectorDBProvider, f as LLMProvider, e as EmbeddingProvider, g as RAGConfig, U as UIConfig, c as ChatOptions } from './index-Cti1u0y1.js';
|
|
2
|
+
import { I as ILLMProvider, E as EmbedOptions } from './DocumentChunker-BXOUMKoP.js';
|
|
3
|
+
export { C as Chunk, a as ChunkOptions, D as DocumentChunker } from './DocumentChunker-BXOUMKoP.js';
|
|
4
|
+
import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-B67KQ9NN.js';
|
|
5
|
+
export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-B67KQ9NN.js';
|
|
5
6
|
import 'next/server';
|
|
6
7
|
|
|
7
8
|
/**
|
package/dist/server.js
CHANGED
|
@@ -3346,11 +3346,29 @@ Sources Used: ${JSON.stringify(
|
|
|
3346
3346
|
)}`;
|
|
3347
3347
|
}
|
|
3348
3348
|
});
|
|
3349
|
+
const defaultAgentPrompt = "You are a helpful AI assistant with access to a document search tool.";
|
|
3350
|
+
let finalSystemPrompt = this.config.llm.systemPrompt || defaultAgentPrompt;
|
|
3351
|
+
const chartInstruction = `
|
|
3352
|
+
|
|
3353
|
+
When presenting structured data, statistics, or comparisons, decide if it is best presented as a table or a chart.
|
|
3354
|
+
- For tables: use standard Markdown tables.
|
|
3355
|
+
- For charts (bar, line, pie): Output a JSON code block with language 'chart' matching this schema:
|
|
3356
|
+
\`\`\`chart
|
|
3357
|
+
{
|
|
3358
|
+
"type": "bar" | "line" | "pie",
|
|
3359
|
+
"xAxisKey": "name",
|
|
3360
|
+
"dataKeys": ["value"],
|
|
3361
|
+
"data": [{"name": "A", "value": 10}]
|
|
3362
|
+
}
|
|
3363
|
+
\`\`\``;
|
|
3364
|
+
if (!finalSystemPrompt.includes("chart")) {
|
|
3365
|
+
finalSystemPrompt += chartInstruction;
|
|
3366
|
+
}
|
|
3349
3367
|
this.agent = createAgent({
|
|
3350
3368
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3351
3369
|
model: chatModel,
|
|
3352
3370
|
tools: [searchTool],
|
|
3353
|
-
systemPrompt:
|
|
3371
|
+
systemPrompt: finalSystemPrompt
|
|
3354
3372
|
});
|
|
3355
3373
|
void HumanMessage;
|
|
3356
3374
|
} catch (error) {
|
|
@@ -3885,8 +3903,24 @@ var Pipeline = class {
|
|
|
3885
3903
|
this.reranker = new Reranker();
|
|
3886
3904
|
}
|
|
3887
3905
|
async initialize() {
|
|
3888
|
-
var _a;
|
|
3906
|
+
var _a, _b;
|
|
3889
3907
|
if (this.initialised) return;
|
|
3908
|
+
const chartInstruction = `
|
|
3909
|
+
|
|
3910
|
+
When presenting structured data, statistics, or comparisons, decide if it is best presented as a table or a chart.
|
|
3911
|
+
- For tables: use standard Markdown tables.
|
|
3912
|
+
- For charts (bar, line, pie): Output a JSON code block with language 'chart' matching this schema:
|
|
3913
|
+
\`\`\`chart
|
|
3914
|
+
{
|
|
3915
|
+
"type": "bar" | "line" | "pie",
|
|
3916
|
+
"xAxisKey": "name",
|
|
3917
|
+
"dataKeys": ["value"],
|
|
3918
|
+
"data": [{"name": "A", "value": 10}]
|
|
3919
|
+
}
|
|
3920
|
+
\`\`\``;
|
|
3921
|
+
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes("chart"))) {
|
|
3922
|
+
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
|
|
3923
|
+
}
|
|
3890
3924
|
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
3891
3925
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
3892
3926
|
const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
|
|
@@ -3901,7 +3935,7 @@ var Pipeline = class {
|
|
|
3901
3935
|
this.entityExtractor = new EntityExtractor(this.llmProvider);
|
|
3902
3936
|
}
|
|
3903
3937
|
await this.vectorDB.initialize();
|
|
3904
|
-
if (((
|
|
3938
|
+
if (((_b = this.config.rag) == null ? void 0 : _b.architecture) === "agentic") {
|
|
3905
3939
|
this.agent = new LangChainAgent(this, this.config);
|
|
3906
3940
|
await this.agent.initialize(this.llmProvider);
|
|
3907
3941
|
}
|
package/dist/server.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
|
|
5
5
|
"author": "Abhinav Alkuchi",
|
|
6
6
|
"license": "MIT",
|
|
@@ -95,6 +95,7 @@
|
|
|
95
95
|
"react": ">=18.0.0",
|
|
96
96
|
"react-dom": ">=18.0.0",
|
|
97
97
|
"react-markdown": "^10.1.0",
|
|
98
|
+
"recharts": "^3.8.1",
|
|
98
99
|
"remark-gfm": "^4.0.1"
|
|
99
100
|
},
|
|
100
101
|
"optionalDependencies": {
|
|
@@ -4,14 +4,7 @@ import React, { useState } from 'react';
|
|
|
4
4
|
import { MessageSquare, X } from 'lucide-react';
|
|
5
5
|
import { ChatWindow } from './ChatWindow';
|
|
6
6
|
import { useConfig } from './ConfigProvider';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
interface ChatWidgetProps {
|
|
10
|
-
/** Position of the floating button. Defaults to bottom-right. */
|
|
11
|
-
position?: 'bottom-right' | 'bottom-left';
|
|
12
|
-
/** Called when the user clicks 'Add to Cart' on a product */
|
|
13
|
-
onAddToCart?: (product: Product) => void;
|
|
14
|
-
}
|
|
7
|
+
import { ChatWidgetProps } from '../types';
|
|
15
8
|
|
|
16
9
|
const DEFAULT_DIMENSIONS = { width: 380, height: 600 };
|
|
17
10
|
|
|
@@ -11,34 +11,43 @@ import {
|
|
|
11
11
|
RotateCcw,
|
|
12
12
|
Maximize2,
|
|
13
13
|
Minimize2,
|
|
14
|
+
Mic,
|
|
15
|
+
MicOff,
|
|
14
16
|
} from 'lucide-react';
|
|
15
17
|
import { MessageBubble } from './MessageBubble';
|
|
16
18
|
import { useConfig } from './ConfigProvider';
|
|
17
19
|
import { useRagChat } from '../hooks/useRagChat';
|
|
18
20
|
import { BORDER_RADIUS_MAP, CHAT_SUGGESTIONS } from '../config/uiConstants';
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
interface
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
21
|
+
import { ChatWindowProps } from '../types';
|
|
22
|
+
|
|
23
|
+
interface SpeechRecognitionEvent {
|
|
24
|
+
resultIndex: number;
|
|
25
|
+
results: {
|
|
26
|
+
length: number;
|
|
27
|
+
[index: number]: {
|
|
28
|
+
isFinal: boolean;
|
|
29
|
+
[index: number]: { transcript: string };
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface SpeechRecognitionErrorEvent {
|
|
35
|
+
error: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ISpeechRecognition {
|
|
39
|
+
continuous: boolean;
|
|
40
|
+
interimResults: boolean;
|
|
41
|
+
onresult: ((event: SpeechRecognitionEvent) => void) | null;
|
|
42
|
+
onerror: ((event: SpeechRecognitionErrorEvent) => void) | null;
|
|
43
|
+
onend: (() => void) | null;
|
|
44
|
+
start: () => void;
|
|
45
|
+
stop: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface WindowWithSpeech extends Window {
|
|
49
|
+
SpeechRecognition?: new () => ISpeechRecognition;
|
|
50
|
+
webkitSpeechRecognition?: new () => ISpeechRecognition;
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
export function ChatWindow({
|
|
@@ -62,6 +71,66 @@ export function ChatWindow({
|
|
|
62
71
|
namespace: projectId,
|
|
63
72
|
});
|
|
64
73
|
|
|
74
|
+
const [suggestions, setSuggestions] = useState<string[]>([]);
|
|
75
|
+
const [isSuggesting, setIsSuggesting] = useState(false);
|
|
76
|
+
|
|
77
|
+
const [isListening, setIsListening] = useState(false);
|
|
78
|
+
const recognitionRef = useRef<ISpeechRecognition | null>(null);
|
|
79
|
+
|
|
80
|
+
// Initialize SpeechRecognition
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (typeof window !== 'undefined') {
|
|
83
|
+
const win = window as unknown as WindowWithSpeech;
|
|
84
|
+
const SpeechRecognition = win.SpeechRecognition || win.webkitSpeechRecognition;
|
|
85
|
+
if (SpeechRecognition) {
|
|
86
|
+
const recognition = new SpeechRecognition();
|
|
87
|
+
recognition.continuous = true;
|
|
88
|
+
recognition.interimResults = true;
|
|
89
|
+
|
|
90
|
+
recognition.onresult = (event: SpeechRecognitionEvent) => {
|
|
91
|
+
let finalTranscript = '';
|
|
92
|
+
for (let i = event.resultIndex; i < event.results.length; ++i) {
|
|
93
|
+
if (event.results[i].isFinal) {
|
|
94
|
+
finalTranscript += event.results[i][0].transcript;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (finalTranscript) {
|
|
98
|
+
setInput((prev) => (prev ? prev + ' ' : '') + finalTranscript.trim());
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
recognition.onerror = (event: SpeechRecognitionErrorEvent) => {
|
|
103
|
+
console.error('Speech recognition error:', event.error);
|
|
104
|
+
setIsListening(false);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
recognition.onend = () => {
|
|
108
|
+
setIsListening(false);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
recognitionRef.current = recognition;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
const toggleListening = () => {
|
|
117
|
+
if (!recognitionRef.current) {
|
|
118
|
+
alert('Speech recognition is not supported in your browser.');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (isListening) {
|
|
123
|
+
recognitionRef.current.stop();
|
|
124
|
+
} else {
|
|
125
|
+
try {
|
|
126
|
+
recognitionRef.current.start();
|
|
127
|
+
setIsListening(true);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
console.error('Failed to start recognition:', err);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
65
134
|
useEffect(() => {
|
|
66
135
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
67
136
|
setMounted(true);
|
|
@@ -77,10 +146,17 @@ export function ChatWindow({
|
|
|
77
146
|
const sendMessage = useCallback(async () => {
|
|
78
147
|
const text = input.trim();
|
|
79
148
|
if (!text || isLoading) return;
|
|
149
|
+
|
|
150
|
+
if (isListening) {
|
|
151
|
+
recognitionRef.current?.stop();
|
|
152
|
+
}
|
|
153
|
+
|
|
80
154
|
setInput('');
|
|
155
|
+
setSuggestions([]);
|
|
156
|
+
setIsSuggesting(false);
|
|
81
157
|
await send(text);
|
|
82
158
|
window.setTimeout(() => inputRef.current?.focus(), 50);
|
|
83
|
-
}, [input, isLoading, send]);
|
|
159
|
+
}, [input, isLoading, send, isListening]);
|
|
84
160
|
|
|
85
161
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
86
162
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
@@ -91,16 +167,15 @@ export function ChatWindow({
|
|
|
91
167
|
|
|
92
168
|
const clearHistory = () => {
|
|
93
169
|
clear();
|
|
170
|
+
setSuggestions([]);
|
|
171
|
+
setIsSuggesting(false);
|
|
94
172
|
};
|
|
95
173
|
|
|
96
174
|
const isEmpty = messages.length === 0;
|
|
97
175
|
|
|
98
|
-
|
|
176
|
+
const currentRadius = BORDER_RADIUS_MAP[ui.borderRadius || 'xl'];
|
|
99
177
|
const isGlass = ui.visualStyle !== 'solid';
|
|
100
178
|
|
|
101
|
-
const [suggestions, setSuggestions] = useState<string[]>([]);
|
|
102
|
-
const [isSuggesting, setIsSuggesting] = useState(false);
|
|
103
|
-
|
|
104
179
|
// Auto-suggestions fetcher (debounced)
|
|
105
180
|
useEffect(() => {
|
|
106
181
|
if (input.trim().length < 3) {
|
|
@@ -276,7 +351,7 @@ export function ChatWindow({
|
|
|
276
351
|
{/* Typing indicator (only if assistant hasn't started replying yet) */}
|
|
277
352
|
{isLoading && messages[messages.length - 1]?.role !== 'assistant' && (
|
|
278
353
|
<MessageBubble
|
|
279
|
-
message={{ role: 'assistant', content: '' }}
|
|
354
|
+
message={{ id: 'loading', role: 'assistant', content: '', createdAt: new Date().toISOString() }}
|
|
280
355
|
isStreaming
|
|
281
356
|
primaryColor={ui.primaryColor}
|
|
282
357
|
accentColor={ui.accentColor}
|
|
@@ -343,6 +418,22 @@ export function ChatWindow({
|
|
|
343
418
|
style={{ scrollbarWidth: 'none' }}
|
|
344
419
|
/>
|
|
345
420
|
|
|
421
|
+
{ui.enableVoiceInput !== false && (
|
|
422
|
+
<button
|
|
423
|
+
type="button"
|
|
424
|
+
onClick={toggleListening}
|
|
425
|
+
disabled={isLoading}
|
|
426
|
+
className={`flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 disabled:active:scale-100 ${
|
|
427
|
+
isListening
|
|
428
|
+
? 'bg-rose-500 text-white animate-pulse shadow-md shadow-rose-500/20'
|
|
429
|
+
: 'text-slate-400 dark:text-white/40 hover:bg-slate-200 dark:hover:bg-white/10'
|
|
430
|
+
}`}
|
|
431
|
+
title={isListening ? 'Stop listening' : 'Start voice input'}
|
|
432
|
+
>
|
|
433
|
+
{isListening ? <MicOff className="w-4 h-4" /> : <Mic className="w-4 h-4" />}
|
|
434
|
+
</button>
|
|
435
|
+
)}
|
|
436
|
+
|
|
346
437
|
<button
|
|
347
438
|
onClick={sendMessage}
|
|
348
439
|
disabled={!input.trim() || isLoading}
|
|
@@ -7,47 +7,21 @@
|
|
|
7
7
|
* Only the UI-safe portions of RagConfig are exposed to the client.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import React, { createContext, useContext
|
|
10
|
+
import React, { createContext, useContext } from 'react';
|
|
11
11
|
import { UIConfig } from '../config/RagConfig';
|
|
12
12
|
import { mergeDefined } from '../utils/templateUtils';
|
|
13
|
+
import { ClientConfig, ConfigProviderProps } from '../types';
|
|
14
|
+
import { DEFAULT_CONFIG } from '../config/uiConstants';
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
projectId: string;
|
|
16
|
-
ui: Required<UIConfig>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const defaultConfig: ClientConfig = {
|
|
20
|
-
projectId: 'default',
|
|
21
|
-
ui: {
|
|
22
|
-
title: 'AI Assistant',
|
|
23
|
-
subtitle: 'Powered by RAG',
|
|
24
|
-
primaryColor: '#6366f1',
|
|
25
|
-
accentColor: '#8b5cf6',
|
|
26
|
-
logoUrl: '',
|
|
27
|
-
placeholder: 'Ask me anything…',
|
|
28
|
-
showSources: true,
|
|
29
|
-
welcomeMessage: "Hello! I'm your AI assistant. Ask me anything about your documents.",
|
|
30
|
-
showWidget: true,
|
|
31
|
-
poweredBy: 'RAG',
|
|
32
|
-
visualStyle: 'glass',
|
|
33
|
-
borderRadius: 'xl',
|
|
34
|
-
allowUpload: true,
|
|
35
|
-
allowResize: true,
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const ConfigContext = createContext<ClientConfig>(defaultConfig);
|
|
16
|
+
const ConfigContext = createContext<ClientConfig>(DEFAULT_CONFIG);
|
|
40
17
|
|
|
41
18
|
export function ConfigProvider({
|
|
42
19
|
config,
|
|
43
20
|
children,
|
|
44
|
-
}: {
|
|
45
|
-
config?: { projectId?: string; ui?: Partial<UIConfig> };
|
|
46
|
-
children: ReactNode;
|
|
47
|
-
}) {
|
|
21
|
+
}: ConfigProviderProps) {
|
|
48
22
|
const merged: ClientConfig = {
|
|
49
|
-
projectId: config?.projectId ||
|
|
50
|
-
ui: mergeDefined(
|
|
23
|
+
projectId: config?.projectId || DEFAULT_CONFIG.projectId,
|
|
24
|
+
ui: mergeDefined(DEFAULT_CONFIG.ui, config?.ui) as Required<UIConfig>,
|
|
51
25
|
};
|
|
52
26
|
|
|
53
27
|
return <ConfigContext.Provider value={merged}>{children}</ConfigContext.Provider>;
|
|
@@ -4,14 +4,7 @@ import React, { useState, useRef } from 'react';
|
|
|
4
4
|
import { Upload, File, X, CheckCircle, AlertCircle, Loader2 } from 'lucide-react';
|
|
5
5
|
import { useConfig } from './ConfigProvider';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
/** Optional namespace for the upload */
|
|
9
|
-
namespace?: string;
|
|
10
|
-
/** Callback when upload completes */
|
|
11
|
-
onUploadComplete?: (results: unknown) => void;
|
|
12
|
-
/** Additional className */
|
|
13
|
-
className?: string;
|
|
14
|
-
}
|
|
7
|
+
import { DocumentUploadProps } from '../types';
|
|
15
8
|
|
|
16
9
|
interface FileState {
|
|
17
10
|
file: File;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import {
|
|
5
|
+
BarChart, Bar, LineChart, Line, PieChart, Pie, Cell,
|
|
6
|
+
XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer
|
|
7
|
+
} from 'recharts';
|
|
8
|
+
|
|
9
|
+
export interface ChartConfig {
|
|
10
|
+
type: 'bar' | 'line' | 'pie';
|
|
11
|
+
data: Array<Record<string, string | number>>;
|
|
12
|
+
xAxisKey?: string;
|
|
13
|
+
dataKeys?: string[];
|
|
14
|
+
colors?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface DynamicChartProps {
|
|
18
|
+
config: ChartConfig;
|
|
19
|
+
primaryColor?: string;
|
|
20
|
+
accentColor?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const DEFAULT_COLORS = ['#6366f1', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899'];
|
|
24
|
+
|
|
25
|
+
export function DynamicChart({ config, primaryColor = '#6366f1', accentColor = '#8b5cf6' }: DynamicChartProps) {
|
|
26
|
+
const { type, data, xAxisKey = 'name', dataKeys = [] } = config;
|
|
27
|
+
const colors = config.colors || [primaryColor, accentColor, ...DEFAULT_COLORS];
|
|
28
|
+
|
|
29
|
+
if (!data || data.length === 0) {
|
|
30
|
+
return <div className="p-4 text-sm text-slate-500">No data available for chart.</div>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Handle Pie Chart separately as it has a different structure
|
|
34
|
+
if (type === 'pie') {
|
|
35
|
+
const pieDataKey = dataKeys[0] || Object.keys(data[0]).find(k => k !== xAxisKey) || 'value';
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="w-full h-64 mt-4 mb-2 select-none">
|
|
39
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
40
|
+
<PieChart>
|
|
41
|
+
<Pie
|
|
42
|
+
data={data}
|
|
43
|
+
dataKey={pieDataKey}
|
|
44
|
+
nameKey={xAxisKey}
|
|
45
|
+
cx="50%"
|
|
46
|
+
cy="50%"
|
|
47
|
+
outerRadius={80}
|
|
48
|
+
label={({ name, percent }) => `${name} ${((percent ?? 0) * 100).toFixed(0)}%`}
|
|
49
|
+
>
|
|
50
|
+
{data.map((entry, index) => (
|
|
51
|
+
<Cell key={`cell-${index}`} fill={colors[index % colors.length]} />
|
|
52
|
+
))}
|
|
53
|
+
</Pie>
|
|
54
|
+
<Tooltip
|
|
55
|
+
contentStyle={{ borderRadius: '8px', border: 'none', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' }}
|
|
56
|
+
/>
|
|
57
|
+
<Legend />
|
|
58
|
+
</PieChart>
|
|
59
|
+
</ResponsiveContainer>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Bar and Line charts
|
|
65
|
+
return (
|
|
66
|
+
<div className="w-full h-64 mt-4 mb-2 select-none">
|
|
67
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
68
|
+
{type === 'bar' ? (
|
|
69
|
+
<BarChart data={data} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
|
70
|
+
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e2e8f0" />
|
|
71
|
+
<XAxis dataKey={xAxisKey} tick={{ fontSize: 12, fill: '#64748b' }} tickLine={false} axisLine={{ stroke: '#cbd5e1' }} />
|
|
72
|
+
<YAxis tick={{ fontSize: 12, fill: '#64748b' }} tickLine={false} axisLine={false} />
|
|
73
|
+
<Tooltip
|
|
74
|
+
contentStyle={{ borderRadius: '8px', border: 'none', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' }}
|
|
75
|
+
cursor={{ fill: '#f1f5f9' }}
|
|
76
|
+
/>
|
|
77
|
+
<Legend wrapperStyle={{ fontSize: 12 }} />
|
|
78
|
+
{dataKeys.map((key, index) => (
|
|
79
|
+
<Bar
|
|
80
|
+
key={key}
|
|
81
|
+
dataKey={key}
|
|
82
|
+
fill={colors[index % colors.length]}
|
|
83
|
+
radius={[4, 4, 0, 0]}
|
|
84
|
+
barSize={Math.max(20, 60 / data.length)}
|
|
85
|
+
/>
|
|
86
|
+
))}
|
|
87
|
+
</BarChart>
|
|
88
|
+
) : (
|
|
89
|
+
<LineChart data={data} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
|
90
|
+
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e2e8f0" />
|
|
91
|
+
<XAxis dataKey={xAxisKey} tick={{ fontSize: 12, fill: '#64748b' }} tickLine={false} axisLine={{ stroke: '#cbd5e1' }} />
|
|
92
|
+
<YAxis tick={{ fontSize: 12, fill: '#64748b' }} tickLine={false} axisLine={false} />
|
|
93
|
+
<Tooltip
|
|
94
|
+
contentStyle={{ borderRadius: '8px', border: 'none', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' }}
|
|
95
|
+
/>
|
|
96
|
+
<Legend wrapperStyle={{ fontSize: 12 }} />
|
|
97
|
+
{dataKeys.map((key, index) => (
|
|
98
|
+
<Line
|
|
99
|
+
key={key}
|
|
100
|
+
type="monotone"
|
|
101
|
+
dataKey={key}
|
|
102
|
+
stroke={colors[index % colors.length]}
|
|
103
|
+
strokeWidth={3}
|
|
104
|
+
dot={{ r: 4, strokeWidth: 2 }}
|
|
105
|
+
activeDot={{ r: 6 }}
|
|
106
|
+
/>
|
|
107
|
+
))}
|
|
108
|
+
</LineChart>
|
|
109
|
+
)}
|
|
110
|
+
</ResponsiveContainer>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -4,20 +4,10 @@ import React from 'react';
|
|
|
4
4
|
import { Bot, User, ChevronDown, ChevronRight } from 'lucide-react';
|
|
5
5
|
import ReactMarkdown from 'react-markdown';
|
|
6
6
|
import remarkGfm from 'remark-gfm';
|
|
7
|
-
import {
|
|
8
|
-
import { VectorMatch } from '../types';
|
|
7
|
+
import { MessageBubbleProps, Product } from '../types';
|
|
9
8
|
import { SourceCard } from './SourceCard';
|
|
10
9
|
import { ProductCarousel } from './ProductCarousel';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
interface MessageBubbleProps {
|
|
14
|
-
message: ChatMessage;
|
|
15
|
-
sources?: VectorMatch[];
|
|
16
|
-
isStreaming?: boolean;
|
|
17
|
-
primaryColor?: string;
|
|
18
|
-
accentColor?: string;
|
|
19
|
-
onAddToCart?: (product: Product) => void;
|
|
20
|
-
}
|
|
10
|
+
import { DynamicChart, ChartConfig } from './DynamicChart';
|
|
21
11
|
|
|
22
12
|
export function MessageBubble({
|
|
23
13
|
message,
|
|
@@ -166,7 +156,55 @@ export function MessageBubble({
|
|
|
166
156
|
</span>
|
|
167
157
|
) : (
|
|
168
158
|
<div className={`prose prose-sm max-w-none ${isUser ? 'prose-invert' : 'dark:prose-invert'}`}>
|
|
169
|
-
<ReactMarkdown
|
|
159
|
+
<ReactMarkdown
|
|
160
|
+
remarkPlugins={[remarkGfm]}
|
|
161
|
+
components={{
|
|
162
|
+
table: ({...props}) => (
|
|
163
|
+
<div className="overflow-x-auto my-4 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm">
|
|
164
|
+
<table className="w-full text-left border-collapse min-w-[400px]" {...props} />
|
|
165
|
+
</div>
|
|
166
|
+
),
|
|
167
|
+
thead: ({...props}) => (
|
|
168
|
+
<thead className="bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" {...props} />
|
|
169
|
+
),
|
|
170
|
+
th: ({...props}) => (
|
|
171
|
+
<th className="p-3 font-semibold text-slate-700 dark:text-white/90 first:rounded-tl-xl last:rounded-tr-xl" {...props} />
|
|
172
|
+
),
|
|
173
|
+
td: ({...props}) => (
|
|
174
|
+
<td className="p-3 border-b border-slate-100 dark:border-white/5 last:border-0 text-slate-600 dark:text-white/70" {...props} />
|
|
175
|
+
),
|
|
176
|
+
code({ inline, className, children, ...props }: React.HTMLAttributes<HTMLElement> & { inline?: boolean }) {
|
|
177
|
+
const match = /language-(\w+)/.exec(className || '');
|
|
178
|
+
const isChart = match && match[1] === 'chart';
|
|
179
|
+
|
|
180
|
+
if (!inline && isChart) {
|
|
181
|
+
try {
|
|
182
|
+
const config = JSON.parse(String(children).replace(/\n$/, '')) as ChartConfig;
|
|
183
|
+
return (
|
|
184
|
+
<div className="my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm">
|
|
185
|
+
<DynamicChart
|
|
186
|
+
config={config}
|
|
187
|
+
primaryColor={primaryColor}
|
|
188
|
+
accentColor={accentColor}
|
|
189
|
+
/>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
} catch {
|
|
193
|
+
return (
|
|
194
|
+
<div className="p-4 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-lg text-sm">
|
|
195
|
+
Failed to render chart: Invalid configuration.
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return (
|
|
201
|
+
<code className={className} {...props}>
|
|
202
|
+
{children}
|
|
203
|
+
</code>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
170
208
|
{cleanContent || message.content}
|
|
171
209
|
</ReactMarkdown>
|
|
172
210
|
|
|
@@ -4,13 +4,7 @@ import React from 'react';
|
|
|
4
4
|
import Image from 'next/image';
|
|
5
5
|
import { ShoppingCart, ExternalLink } from 'lucide-react';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
interface ProductCardProps {
|
|
10
|
-
product: Product;
|
|
11
|
-
primaryColor?: string;
|
|
12
|
-
onAddToCart?: (product: Product) => void;
|
|
13
|
-
}
|
|
7
|
+
import { ProductCardProps } from '../types';
|
|
14
8
|
|
|
15
9
|
export function ProductCard({ product, primaryColor = '#6366f1', onAddToCart }: ProductCardProps) {
|
|
16
10
|
return (
|
|
@@ -3,13 +3,7 @@
|
|
|
3
3
|
import React, { useRef, useState, useEffect } from 'react';
|
|
4
4
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
|
5
5
|
import { ProductCard } from './ProductCard';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
interface ProductCarouselProps {
|
|
9
|
-
products: Product[];
|
|
10
|
-
primaryColor?: string;
|
|
11
|
-
onAddToCart?: (product: Product) => void;
|
|
12
|
-
}
|
|
6
|
+
import { ProductCarouselProps } from '../types';
|
|
13
7
|
|
|
14
8
|
export function ProductCarousel({ products, primaryColor = '#6366f1', onAddToCart }: ProductCarouselProps) {
|
|
15
9
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
@@ -2,12 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { ChevronDown, ChevronUp } from 'lucide-react';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
interface SourceCardProps {
|
|
8
|
-
source: VectorMatch;
|
|
9
|
-
index: number;
|
|
10
|
-
}
|
|
5
|
+
import { SourceCardProps } from '../types';
|
|
11
6
|
|
|
12
7
|
export function SourceCard({ source, index }: SourceCardProps) {
|
|
13
8
|
const [expanded, setExpanded] = React.useState(false);
|