@retrivora-ai/rag-engine 1.2.1 → 1.2.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-BXOUMKoP.d.ts +93 -0
- package/dist/DocumentChunker-D1dg5iCi.d.mts +93 -0
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- 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 +93 -27
- package/dist/index.mjs +97 -27
- package/dist/server.d.mts +5 -4
- package/dist/server.d.ts +5 -4
- package/package.json +1 -1
- package/src/components/ChatWidget.tsx +1 -8
- package/src/components/ChatWindow.tsx +119 -29
- package/src/components/ConfigProvider.tsx +7 -33
- package/src/components/DocumentUpload.tsx +1 -8
- package/src/components/MessageBubble.tsx +1 -12
- 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/Pipeline.ts +2 -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/index.mjs
CHANGED
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
TriangleAlert,
|
|
22
22
|
RotateCcw,
|
|
23
23
|
Maximize2,
|
|
24
|
-
Minimize2
|
|
24
|
+
Minimize2,
|
|
25
|
+
Mic,
|
|
26
|
+
MicOff
|
|
25
27
|
} from "lucide-react";
|
|
26
28
|
|
|
27
29
|
// src/components/MessageBubble.tsx
|
|
@@ -281,7 +283,9 @@ function MessageBubble({
|
|
|
281
283
|
|
|
282
284
|
// src/components/ConfigProvider.tsx
|
|
283
285
|
import React5, { createContext, useContext } from "react";
|
|
284
|
-
|
|
286
|
+
|
|
287
|
+
// src/config/uiConstants.ts
|
|
288
|
+
var DEFAULT_CONFIG = {
|
|
285
289
|
projectId: "default",
|
|
286
290
|
ui: {
|
|
287
291
|
title: "AI Assistant",
|
|
@@ -297,17 +301,33 @@ var defaultConfig = {
|
|
|
297
301
|
visualStyle: "glass",
|
|
298
302
|
borderRadius: "xl",
|
|
299
303
|
allowUpload: true,
|
|
300
|
-
allowResize: true
|
|
304
|
+
allowResize: true,
|
|
305
|
+
enableVoiceInput: true
|
|
301
306
|
}
|
|
302
307
|
};
|
|
303
|
-
var
|
|
308
|
+
var BORDER_RADIUS_MAP = {
|
|
309
|
+
none: "rounded-none",
|
|
310
|
+
sm: "rounded-sm",
|
|
311
|
+
md: "rounded-md",
|
|
312
|
+
lg: "rounded-lg",
|
|
313
|
+
xl: "rounded-xl",
|
|
314
|
+
full: "rounded-3xl"
|
|
315
|
+
};
|
|
316
|
+
var CHAT_SUGGESTIONS = [
|
|
317
|
+
"What can you help me with?",
|
|
318
|
+
"Summarise the key topics",
|
|
319
|
+
"Show me an example"
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
// src/components/ConfigProvider.tsx
|
|
323
|
+
var ConfigContext = createContext(DEFAULT_CONFIG);
|
|
304
324
|
function ConfigProvider({
|
|
305
325
|
config,
|
|
306
326
|
children
|
|
307
327
|
}) {
|
|
308
328
|
const merged = {
|
|
309
|
-
projectId: (config == null ? void 0 : config.projectId) ||
|
|
310
|
-
ui: mergeDefined(
|
|
329
|
+
projectId: (config == null ? void 0 : config.projectId) || DEFAULT_CONFIG.projectId,
|
|
330
|
+
ui: mergeDefined(DEFAULT_CONFIG.ui, config == null ? void 0 : config.ui)
|
|
311
331
|
};
|
|
312
332
|
return /* @__PURE__ */ React5.createElement(ConfigContext.Provider, { value: merged }, children);
|
|
313
333
|
}
|
|
@@ -505,21 +525,6 @@ function useRagChat(projectId, options = {}) {
|
|
|
505
525
|
};
|
|
506
526
|
}
|
|
507
527
|
|
|
508
|
-
// src/config/uiConstants.ts
|
|
509
|
-
var BORDER_RADIUS_MAP = {
|
|
510
|
-
none: "rounded-none",
|
|
511
|
-
sm: "rounded-sm",
|
|
512
|
-
md: "rounded-md",
|
|
513
|
-
lg: "rounded-lg",
|
|
514
|
-
xl: "rounded-xl",
|
|
515
|
-
full: "rounded-3xl"
|
|
516
|
-
};
|
|
517
|
-
var CHAT_SUGGESTIONS = [
|
|
518
|
-
"What can you help me with?",
|
|
519
|
-
"Summarise the key topics",
|
|
520
|
-
"Show me an example"
|
|
521
|
-
];
|
|
522
|
-
|
|
523
528
|
// src/components/ChatWindow.tsx
|
|
524
529
|
function ChatWindow({
|
|
525
530
|
className = "",
|
|
@@ -542,6 +547,56 @@ function ChatWindow({
|
|
|
542
547
|
const { messages, send, clear, isLoading, error } = useRagChat(projectId, {
|
|
543
548
|
namespace: projectId
|
|
544
549
|
});
|
|
550
|
+
const [suggestions, setSuggestions] = useState4([]);
|
|
551
|
+
const [isSuggesting, setIsSuggesting] = useState4(false);
|
|
552
|
+
const [isListening, setIsListening] = useState4(false);
|
|
553
|
+
const recognitionRef = useRef3(null);
|
|
554
|
+
useEffect4(() => {
|
|
555
|
+
if (typeof window !== "undefined") {
|
|
556
|
+
const win = window;
|
|
557
|
+
const SpeechRecognition = win.SpeechRecognition || win.webkitSpeechRecognition;
|
|
558
|
+
if (SpeechRecognition) {
|
|
559
|
+
const recognition = new SpeechRecognition();
|
|
560
|
+
recognition.continuous = true;
|
|
561
|
+
recognition.interimResults = true;
|
|
562
|
+
recognition.onresult = (event) => {
|
|
563
|
+
let finalTranscript = "";
|
|
564
|
+
for (let i = event.resultIndex; i < event.results.length; ++i) {
|
|
565
|
+
if (event.results[i].isFinal) {
|
|
566
|
+
finalTranscript += event.results[i][0].transcript;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (finalTranscript) {
|
|
570
|
+
setInput((prev) => (prev ? prev + " " : "") + finalTranscript.trim());
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
recognition.onerror = (event) => {
|
|
574
|
+
console.error("Speech recognition error:", event.error);
|
|
575
|
+
setIsListening(false);
|
|
576
|
+
};
|
|
577
|
+
recognition.onend = () => {
|
|
578
|
+
setIsListening(false);
|
|
579
|
+
};
|
|
580
|
+
recognitionRef.current = recognition;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}, []);
|
|
584
|
+
const toggleListening = () => {
|
|
585
|
+
if (!recognitionRef.current) {
|
|
586
|
+
alert("Speech recognition is not supported in your browser.");
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
if (isListening) {
|
|
590
|
+
recognitionRef.current.stop();
|
|
591
|
+
} else {
|
|
592
|
+
try {
|
|
593
|
+
recognitionRef.current.start();
|
|
594
|
+
setIsListening(true);
|
|
595
|
+
} catch (err) {
|
|
596
|
+
console.error("Failed to start recognition:", err);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
};
|
|
545
600
|
useEffect4(() => {
|
|
546
601
|
setMounted(true);
|
|
547
602
|
}, []);
|
|
@@ -552,15 +607,21 @@ function ChatWindow({
|
|
|
552
607
|
}
|
|
553
608
|
}, [messages, isLoading]);
|
|
554
609
|
const sendMessage = useCallback2(async () => {
|
|
610
|
+
var _a2;
|
|
555
611
|
const text = input.trim();
|
|
556
612
|
if (!text || isLoading) return;
|
|
613
|
+
if (isListening) {
|
|
614
|
+
(_a2 = recognitionRef.current) == null ? void 0 : _a2.stop();
|
|
615
|
+
}
|
|
557
616
|
setInput("");
|
|
617
|
+
setSuggestions([]);
|
|
618
|
+
setIsSuggesting(false);
|
|
558
619
|
await send(text);
|
|
559
620
|
window.setTimeout(() => {
|
|
560
|
-
var
|
|
561
|
-
return (
|
|
621
|
+
var _a3;
|
|
622
|
+
return (_a3 = inputRef.current) == null ? void 0 : _a3.focus();
|
|
562
623
|
}, 50);
|
|
563
|
-
}, [input, isLoading, send]);
|
|
624
|
+
}, [input, isLoading, send, isListening]);
|
|
564
625
|
const handleKeyDown = (e) => {
|
|
565
626
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
566
627
|
e.preventDefault();
|
|
@@ -569,12 +630,12 @@ function ChatWindow({
|
|
|
569
630
|
};
|
|
570
631
|
const clearHistory = () => {
|
|
571
632
|
clear();
|
|
633
|
+
setSuggestions([]);
|
|
634
|
+
setIsSuggesting(false);
|
|
572
635
|
};
|
|
573
636
|
const isEmpty = messages.length === 0;
|
|
574
637
|
const currentRadius = BORDER_RADIUS_MAP[ui.borderRadius || "xl"];
|
|
575
638
|
const isGlass = ui.visualStyle !== "solid";
|
|
576
|
-
const [suggestions, setSuggestions] = useState4([]);
|
|
577
|
-
const [isSuggesting, setIsSuggesting] = useState4(false);
|
|
578
639
|
useEffect4(() => {
|
|
579
640
|
if (input.trim().length < 3) {
|
|
580
641
|
return;
|
|
@@ -703,7 +764,7 @@ function ChatWindow({
|
|
|
703
764
|
)), isLoading && ((_a = messages[messages.length - 1]) == null ? void 0 : _a.role) !== "assistant" && /* @__PURE__ */ React7.createElement(
|
|
704
765
|
MessageBubble,
|
|
705
766
|
{
|
|
706
|
-
message: { role: "assistant", content: "" },
|
|
767
|
+
message: { id: "loading", role: "assistant", content: "", createdAt: (/* @__PURE__ */ new Date()).toISOString() },
|
|
707
768
|
isStreaming: true,
|
|
708
769
|
primaryColor: ui.primaryColor,
|
|
709
770
|
accentColor: ui.accentColor,
|
|
@@ -743,6 +804,15 @@ function ChatWindow({
|
|
|
743
804
|
className: "flex-1 bg-transparent text-slate-900 dark:text-white/90 placeholder-slate-400 dark:placeholder-white/30 text-sm resize-none outline-none py-1.5 px-2 max-h-32 leading-relaxed disabled:opacity-50",
|
|
744
805
|
style: { scrollbarWidth: "none" }
|
|
745
806
|
}
|
|
807
|
+
), ui.enableVoiceInput !== false && /* @__PURE__ */ React7.createElement(
|
|
808
|
+
"button",
|
|
809
|
+
{
|
|
810
|
+
type: "button",
|
|
811
|
+
onClick: toggleListening,
|
|
812
|
+
className: `flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all hover:scale-105 active:scale-95 ${isListening ? "bg-rose-500 text-white animate-pulse shadow-md shadow-rose-500/20" : "text-slate-400 dark:text-white/40 hover:bg-slate-200 dark:hover:bg-white/10"}`,
|
|
813
|
+
title: isListening ? "Stop listening" : "Start voice input"
|
|
814
|
+
},
|
|
815
|
+
isListening ? /* @__PURE__ */ React7.createElement(MicOff, { className: "w-4 h-4" }) : /* @__PURE__ */ React7.createElement(Mic, { className: "w-4 h-4" })
|
|
746
816
|
), /* @__PURE__ */ React7.createElement(
|
|
747
817
|
"button",
|
|
748
818
|
{
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
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",
|
|
@@ -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,21 @@ 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
|
+
className={`flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all hover:scale-105 active:scale-95 ${
|
|
426
|
+
isListening
|
|
427
|
+
? 'bg-rose-500 text-white animate-pulse shadow-md shadow-rose-500/20'
|
|
428
|
+
: 'text-slate-400 dark:text-white/40 hover:bg-slate-200 dark:hover:bg-white/10'
|
|
429
|
+
}`}
|
|
430
|
+
title={isListening ? 'Stop listening' : 'Start voice input'}
|
|
431
|
+
>
|
|
432
|
+
{isListening ? <MicOff className="w-4 h-4" /> : <Mic className="w-4 h-4" />}
|
|
433
|
+
</button>
|
|
434
|
+
)}
|
|
435
|
+
|
|
346
436
|
<button
|
|
347
437
|
onClick={sendMessage}
|
|
348
438
|
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;
|
|
@@ -4,20 +4,9 @@ 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 { Product } from '../types';
|
|
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
|
-
}
|
|
21
10
|
|
|
22
11
|
export function MessageBubble({
|
|
23
12
|
message,
|
|
@@ -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);
|
package/src/config/RagConfig.ts
CHANGED
|
@@ -149,6 +149,8 @@ export interface UIConfig {
|
|
|
149
149
|
allowUpload?: boolean;
|
|
150
150
|
/** Whether to allow manual resizing of the chat window. Defaults to true. */
|
|
151
151
|
allowResize?: boolean;
|
|
152
|
+
/** Whether to enable voice search input. Defaults to true. */
|
|
153
|
+
enableVoiceInput?: boolean;
|
|
152
154
|
}
|
|
153
155
|
|
|
154
156
|
// ---------------------------------------------------------------------------
|
|
@@ -5,6 +5,29 @@
|
|
|
5
5
|
* is consumed via npm without Next.js path-alias configuration.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { ClientConfig } from '../types';
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_CONFIG: ClientConfig = {
|
|
11
|
+
projectId: 'default',
|
|
12
|
+
ui: {
|
|
13
|
+
title: 'AI Assistant',
|
|
14
|
+
subtitle: 'Powered by RAG',
|
|
15
|
+
primaryColor: '#6366f1',
|
|
16
|
+
accentColor: '#8b5cf6',
|
|
17
|
+
logoUrl: '',
|
|
18
|
+
placeholder: 'Ask me anything…',
|
|
19
|
+
showSources: true,
|
|
20
|
+
welcomeMessage: "Hello! I'm your AI assistant. Ask me anything about your documents.",
|
|
21
|
+
showWidget: true,
|
|
22
|
+
poweredBy: 'RAG',
|
|
23
|
+
visualStyle: 'glass',
|
|
24
|
+
borderRadius: 'xl',
|
|
25
|
+
allowUpload: true,
|
|
26
|
+
allowResize: true,
|
|
27
|
+
enableVoiceInput: true,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
8
31
|
/** Maps the `borderRadius` config value to a Tailwind class. */
|
|
9
32
|
export const BORDER_RADIUS_MAP: Record<string, string> = {
|
|
10
33
|
none: 'rounded-none',
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BaseVectorProvider } from '../providers/vectordb/BaseVectorProvider';
|
|
2
2
|
import { BaseGraphProvider } from '../providers/graphdb/BaseGraphProvider';
|
|
3
|
-
import { ILLMProvider
|
|
3
|
+
import { ILLMProvider } from '../llm/ILLMProvider';
|
|
4
|
+
import { ChatMessage } from '../types';
|
|
4
5
|
import { DocumentChunker, Chunk } from '../rag/DocumentChunker';
|
|
5
6
|
import { EntityExtractor } from '../rag/EntityExtractor';
|
|
6
7
|
import { Reranker } from '../rag/Reranker';
|
package/src/core/VectorPlugin.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { ConfigValidator } from './ConfigValidator';
|
|
|
4
4
|
import { Pipeline } from './Pipeline';
|
|
5
5
|
import { ProviderHealthCheck, HealthCheckResult } from './ProviderHealthCheck';
|
|
6
6
|
import { IngestDocument, ChatResponse } from '../types';
|
|
7
|
-
import { ChatMessage } from '../
|
|
7
|
+
import { ChatMessage } from '../types';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* VectorPlugin — main orchestrator class.
|