@retrivora-ai/rag-engine 1.0.1 → 1.0.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/{MongoDBProvider-Z6ALOVDN.mjs → MongoDBProvider-BO2Y5DRR.mjs} +1 -1
- package/dist/{chunk-CD6TSNL4.mjs → chunk-73I6VWU3.mjs} +1 -1
- package/dist/{chunk-5W2YWFT3.mjs → chunk-OJNAKSQ2.mjs} +33 -39
- package/dist/handlers/index.js +33 -39
- package/dist/handlers/index.mjs +1 -1
- package/dist/index.js +77 -22
- package/dist/index.mjs +77 -22
- package/dist/server.d.mts +1 -20
- package/dist/server.d.ts +1 -20
- package/dist/server.js +6 -22
- package/dist/server.mjs +2 -2
- package/package.json +1 -1
- package/src/components/ChatWindow.tsx +4 -3
- package/src/components/MessageBubble.tsx +5 -2
- package/src/core/Pipeline.ts +1 -0
- package/src/core/VectorPlugin.ts +6 -22
- package/src/handlers/index.ts +18 -10
- package/src/hooks/useRagChat.ts +79 -15
- package/src/providers/vectordb/MongoDBProvider.ts +1 -1
|
@@ -14,7 +14,7 @@ var MongoDBProvider = class extends BaseVectorProvider {
|
|
|
14
14
|
if (!opts.uri || !opts.database || !opts.collection) {
|
|
15
15
|
throw new Error("[MongoDBProvider] options uri, database, and collection are required.");
|
|
16
16
|
}
|
|
17
|
-
this.client = new MongoClient(opts.uri);
|
|
17
|
+
this.client = new MongoClient(opts.uri, { serverSelectionTimeoutMS: 5e3 });
|
|
18
18
|
this.dbName = opts.database;
|
|
19
19
|
this.collectionName = opts.collection;
|
|
20
20
|
this.embeddingKey = opts.embeddingKey || "embedding";
|
|
@@ -1088,7 +1088,7 @@ var ProviderRegistry = class {
|
|
|
1088
1088
|
return PostgreSQLProvider;
|
|
1089
1089
|
}
|
|
1090
1090
|
case "mongodb": {
|
|
1091
|
-
const { MongoDBProvider } = await import("./MongoDBProvider-
|
|
1091
|
+
const { MongoDBProvider } = await import("./MongoDBProvider-BO2Y5DRR.mjs");
|
|
1092
1092
|
return MongoDBProvider;
|
|
1093
1093
|
}
|
|
1094
1094
|
case "milvus": {
|
|
@@ -1977,6 +1977,7 @@ var Pipeline = class {
|
|
|
1977
1977
|
async initialize() {
|
|
1978
1978
|
var _a;
|
|
1979
1979
|
if (this.initialised) return;
|
|
1980
|
+
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
1980
1981
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
1981
1982
|
const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
|
|
1982
1983
|
this.config.llm,
|
|
@@ -2361,29 +2362,9 @@ var ProviderHealthCheck = class {
|
|
|
2361
2362
|
|
|
2362
2363
|
// src/core/VectorPlugin.ts
|
|
2363
2364
|
var VectorPlugin = class {
|
|
2364
|
-
/**
|
|
2365
|
-
* Initializes the plugin with the host configuration.
|
|
2366
|
-
* @param hostConfig - Configuration object passed from the host application.
|
|
2367
|
-
* @throws Error if configuration is invalid or providers are unhealthy
|
|
2368
|
-
*
|
|
2369
|
-
* @example
|
|
2370
|
-
* const plugin = new VectorPlugin({
|
|
2371
|
-
* projectId: 'my-app',
|
|
2372
|
-
* vectorDb: {
|
|
2373
|
-
* provider: 'pinecone',
|
|
2374
|
-
* indexName: 'my-index',
|
|
2375
|
-
* options: { apiKey: process.env.PINECONE_API_KEY }
|
|
2376
|
-
* },
|
|
2377
|
-
* llm: {
|
|
2378
|
-
* provider: 'openai',
|
|
2379
|
-
* model: 'gpt-4o',
|
|
2380
|
-
* apiKey: process.env.OPENAI_API_KEY
|
|
2381
|
-
* }
|
|
2382
|
-
* });
|
|
2383
|
-
*/
|
|
2384
2365
|
constructor(hostConfig) {
|
|
2385
2366
|
this.config = ConfigResolver.resolve(hostConfig);
|
|
2386
|
-
ConfigValidator.validateAndThrow(this.config);
|
|
2367
|
+
this.validationPromise = ConfigValidator.validateAndThrow(this.config);
|
|
2387
2368
|
this.pipeline = new Pipeline(this.config);
|
|
2388
2369
|
}
|
|
2389
2370
|
/**
|
|
@@ -2409,6 +2390,7 @@ var VectorPlugin = class {
|
|
|
2409
2390
|
* Run a chat query.
|
|
2410
2391
|
*/
|
|
2411
2392
|
async chat(message, history = [], namespace) {
|
|
2393
|
+
await this.validationPromise;
|
|
2412
2394
|
return this.pipeline.ask(message, history, namespace);
|
|
2413
2395
|
}
|
|
2414
2396
|
/**
|
|
@@ -2416,6 +2398,7 @@ var VectorPlugin = class {
|
|
|
2416
2398
|
*/
|
|
2417
2399
|
chatStream(_0) {
|
|
2418
2400
|
return __asyncGenerator(this, arguments, function* (message, history = [], namespace) {
|
|
2401
|
+
yield new __await(this.validationPromise);
|
|
2419
2402
|
yield* __yieldStar(this.pipeline.askStream(message, history, namespace));
|
|
2420
2403
|
});
|
|
2421
2404
|
}
|
|
@@ -2423,6 +2406,7 @@ var VectorPlugin = class {
|
|
|
2423
2406
|
* Ingest documents into the vector database.
|
|
2424
2407
|
*/
|
|
2425
2408
|
async ingest(documents, namespace) {
|
|
2409
|
+
await this.validationPromise;
|
|
2426
2410
|
return this.pipeline.ingest(documents, namespace);
|
|
2427
2411
|
}
|
|
2428
2412
|
};
|
|
@@ -2508,33 +2492,43 @@ function createStreamHandler(config) {
|
|
|
2508
2492
|
const plugin = new VectorPlugin(config);
|
|
2509
2493
|
return async function POST(req) {
|
|
2510
2494
|
try {
|
|
2511
|
-
const
|
|
2495
|
+
const body = await req.json();
|
|
2496
|
+
const { message, history = [], namespace } = body;
|
|
2512
2497
|
const encoder = new TextEncoder();
|
|
2513
2498
|
const stream = new ReadableStream({
|
|
2514
2499
|
async start(controller) {
|
|
2515
|
-
const pipelineStream = plugin.chatStream(message, history, namespace);
|
|
2516
2500
|
try {
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2501
|
+
const pipelineStream = plugin.chatStream(message, history, namespace);
|
|
2502
|
+
try {
|
|
2503
|
+
for (var iter = __forAwait(pipelineStream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
|
|
2504
|
+
const chunk = temp.value;
|
|
2505
|
+
if (typeof chunk === "string") {
|
|
2506
|
+
controller.enqueue(encoder.encode(chunk));
|
|
2507
|
+
} else {
|
|
2508
|
+
controller.enqueue(encoder.encode(`
|
|
2523
2509
|
|
|
2524
2510
|
__METADATA__${JSON.stringify(chunk)}`));
|
|
2511
|
+
}
|
|
2525
2512
|
}
|
|
2526
|
-
}
|
|
2527
|
-
|
|
2528
|
-
error = [temp];
|
|
2529
|
-
} finally {
|
|
2530
|
-
try {
|
|
2531
|
-
more && (temp = iter.return) && await temp.call(iter);
|
|
2513
|
+
} catch (temp) {
|
|
2514
|
+
error = [temp];
|
|
2532
2515
|
} finally {
|
|
2533
|
-
|
|
2534
|
-
|
|
2516
|
+
try {
|
|
2517
|
+
more && (temp = iter.return) && await temp.call(iter);
|
|
2518
|
+
} finally {
|
|
2519
|
+
if (error)
|
|
2520
|
+
throw error[0];
|
|
2521
|
+
}
|
|
2535
2522
|
}
|
|
2523
|
+
controller.close();
|
|
2524
|
+
} catch (streamError) {
|
|
2525
|
+
console.error("[createStreamHandler] Stream processing error:", streamError);
|
|
2526
|
+
const errorMessage = streamError instanceof Error ? streamError.message : String(streamError);
|
|
2527
|
+
controller.enqueue(encoder.encode(`
|
|
2528
|
+
|
|
2529
|
+
__ERROR__${JSON.stringify({ error: errorMessage })}`));
|
|
2530
|
+
controller.close();
|
|
2536
2531
|
}
|
|
2537
|
-
controller.close();
|
|
2538
2532
|
}
|
|
2539
2533
|
});
|
|
2540
2534
|
return new Response(stream, {
|
package/dist/handlers/index.js
CHANGED
|
@@ -546,7 +546,7 @@ var init_MongoDBProvider = __esm({
|
|
|
546
546
|
if (!opts.uri || !opts.database || !opts.collection) {
|
|
547
547
|
throw new Error("[MongoDBProvider] options uri, database, and collection are required.");
|
|
548
548
|
}
|
|
549
|
-
this.client = new import_mongodb.MongoClient(opts.uri);
|
|
549
|
+
this.client = new import_mongodb.MongoClient(opts.uri, { serverSelectionTimeoutMS: 5e3 });
|
|
550
550
|
this.dbName = opts.database;
|
|
551
551
|
this.collectionName = opts.collection;
|
|
552
552
|
this.embeddingKey = opts.embeddingKey || "embedding";
|
|
@@ -3434,6 +3434,7 @@ var Pipeline = class {
|
|
|
3434
3434
|
async initialize() {
|
|
3435
3435
|
var _a;
|
|
3436
3436
|
if (this.initialised) return;
|
|
3437
|
+
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
3437
3438
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
3438
3439
|
const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
|
|
3439
3440
|
this.config.llm,
|
|
@@ -3818,29 +3819,9 @@ var ProviderHealthCheck = class {
|
|
|
3818
3819
|
|
|
3819
3820
|
// src/core/VectorPlugin.ts
|
|
3820
3821
|
var VectorPlugin = class {
|
|
3821
|
-
/**
|
|
3822
|
-
* Initializes the plugin with the host configuration.
|
|
3823
|
-
* @param hostConfig - Configuration object passed from the host application.
|
|
3824
|
-
* @throws Error if configuration is invalid or providers are unhealthy
|
|
3825
|
-
*
|
|
3826
|
-
* @example
|
|
3827
|
-
* const plugin = new VectorPlugin({
|
|
3828
|
-
* projectId: 'my-app',
|
|
3829
|
-
* vectorDb: {
|
|
3830
|
-
* provider: 'pinecone',
|
|
3831
|
-
* indexName: 'my-index',
|
|
3832
|
-
* options: { apiKey: process.env.PINECONE_API_KEY }
|
|
3833
|
-
* },
|
|
3834
|
-
* llm: {
|
|
3835
|
-
* provider: 'openai',
|
|
3836
|
-
* model: 'gpt-4o',
|
|
3837
|
-
* apiKey: process.env.OPENAI_API_KEY
|
|
3838
|
-
* }
|
|
3839
|
-
* });
|
|
3840
|
-
*/
|
|
3841
3822
|
constructor(hostConfig) {
|
|
3842
3823
|
this.config = ConfigResolver.resolve(hostConfig);
|
|
3843
|
-
ConfigValidator.validateAndThrow(this.config);
|
|
3824
|
+
this.validationPromise = ConfigValidator.validateAndThrow(this.config);
|
|
3844
3825
|
this.pipeline = new Pipeline(this.config);
|
|
3845
3826
|
}
|
|
3846
3827
|
/**
|
|
@@ -3866,6 +3847,7 @@ var VectorPlugin = class {
|
|
|
3866
3847
|
* Run a chat query.
|
|
3867
3848
|
*/
|
|
3868
3849
|
async chat(message, history = [], namespace) {
|
|
3850
|
+
await this.validationPromise;
|
|
3869
3851
|
return this.pipeline.ask(message, history, namespace);
|
|
3870
3852
|
}
|
|
3871
3853
|
/**
|
|
@@ -3873,6 +3855,7 @@ var VectorPlugin = class {
|
|
|
3873
3855
|
*/
|
|
3874
3856
|
chatStream(_0) {
|
|
3875
3857
|
return __asyncGenerator(this, arguments, function* (message, history = [], namespace) {
|
|
3858
|
+
yield new __await(this.validationPromise);
|
|
3876
3859
|
yield* __yieldStar(this.pipeline.askStream(message, history, namespace));
|
|
3877
3860
|
});
|
|
3878
3861
|
}
|
|
@@ -3880,6 +3863,7 @@ var VectorPlugin = class {
|
|
|
3880
3863
|
* Ingest documents into the vector database.
|
|
3881
3864
|
*/
|
|
3882
3865
|
async ingest(documents, namespace) {
|
|
3866
|
+
await this.validationPromise;
|
|
3883
3867
|
return this.pipeline.ingest(documents, namespace);
|
|
3884
3868
|
}
|
|
3885
3869
|
};
|
|
@@ -3965,33 +3949,43 @@ function createStreamHandler(config) {
|
|
|
3965
3949
|
const plugin = new VectorPlugin(config);
|
|
3966
3950
|
return async function POST(req) {
|
|
3967
3951
|
try {
|
|
3968
|
-
const
|
|
3952
|
+
const body = await req.json();
|
|
3953
|
+
const { message, history = [], namespace } = body;
|
|
3969
3954
|
const encoder = new TextEncoder();
|
|
3970
3955
|
const stream = new ReadableStream({
|
|
3971
3956
|
async start(controller) {
|
|
3972
|
-
const pipelineStream = plugin.chatStream(message, history, namespace);
|
|
3973
3957
|
try {
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3958
|
+
const pipelineStream = plugin.chatStream(message, history, namespace);
|
|
3959
|
+
try {
|
|
3960
|
+
for (var iter = __forAwait(pipelineStream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
|
|
3961
|
+
const chunk = temp.value;
|
|
3962
|
+
if (typeof chunk === "string") {
|
|
3963
|
+
controller.enqueue(encoder.encode(chunk));
|
|
3964
|
+
} else {
|
|
3965
|
+
controller.enqueue(encoder.encode(`
|
|
3980
3966
|
|
|
3981
3967
|
__METADATA__${JSON.stringify(chunk)}`));
|
|
3968
|
+
}
|
|
3982
3969
|
}
|
|
3983
|
-
}
|
|
3984
|
-
|
|
3985
|
-
error = [temp];
|
|
3986
|
-
} finally {
|
|
3987
|
-
try {
|
|
3988
|
-
more && (temp = iter.return) && await temp.call(iter);
|
|
3970
|
+
} catch (temp) {
|
|
3971
|
+
error = [temp];
|
|
3989
3972
|
} finally {
|
|
3990
|
-
|
|
3991
|
-
|
|
3973
|
+
try {
|
|
3974
|
+
more && (temp = iter.return) && await temp.call(iter);
|
|
3975
|
+
} finally {
|
|
3976
|
+
if (error)
|
|
3977
|
+
throw error[0];
|
|
3978
|
+
}
|
|
3992
3979
|
}
|
|
3980
|
+
controller.close();
|
|
3981
|
+
} catch (streamError) {
|
|
3982
|
+
console.error("[createStreamHandler] Stream processing error:", streamError);
|
|
3983
|
+
const errorMessage = streamError instanceof Error ? streamError.message : String(streamError);
|
|
3984
|
+
controller.enqueue(encoder.encode(`
|
|
3985
|
+
|
|
3986
|
+
__ERROR__${JSON.stringify({ error: errorMessage })}`));
|
|
3987
|
+
controller.close();
|
|
3993
3988
|
}
|
|
3994
|
-
controller.close();
|
|
3995
3989
|
}
|
|
3996
3990
|
});
|
|
3997
3991
|
return new Response(stream, {
|
package/dist/handlers/index.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -118,7 +118,7 @@ function MessageBubble({
|
|
|
118
118
|
className: `relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser ? "text-white rounded-tr-sm" : "bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm"}`,
|
|
119
119
|
style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
120
120
|
},
|
|
121
|
-
isStreaming ? /* @__PURE__ */ import_react2.default.createElement("span", { className: "flex items-center gap-1" }, /* @__PURE__ */ import_react2.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ import_react2.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ import_react2.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ import_react2.default.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ import_react2.default.createElement(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default] }, message.content))
|
|
121
|
+
isStreaming && !message.content ? /* @__PURE__ */ import_react2.default.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ import_react2.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ import_react2.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ import_react2.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ import_react2.default.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ import_react2.default.createElement(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default] }, message.content), isStreaming && message.content && /* @__PURE__ */ import_react2.default.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
|
|
122
122
|
), !isUser && sources && sources.length > 0 && /* @__PURE__ */ import_react2.default.createElement("div", { className: "w-full" }, /* @__PURE__ */ import_react2.default.createElement(
|
|
123
123
|
"button",
|
|
124
124
|
{
|
|
@@ -186,7 +186,6 @@ function useConfig() {
|
|
|
186
186
|
|
|
187
187
|
// src/hooks/useRagChat.ts
|
|
188
188
|
var import_react4 = require("react");
|
|
189
|
-
var import_axios = __toESM(require("axios"));
|
|
190
189
|
|
|
191
190
|
// src/hooks/useStoredMessages.ts
|
|
192
191
|
var React4 = __toESM(require("react"));
|
|
@@ -240,7 +239,6 @@ function useRagChat(projectId, options = {}) {
|
|
|
240
239
|
}, [messages]);
|
|
241
240
|
const sendMessage = (0, import_react4.useCallback)(
|
|
242
241
|
async (text, options2) => {
|
|
243
|
-
var _a, _b, _c;
|
|
244
242
|
const trimmed = text.trim();
|
|
245
243
|
if (!trimmed || isLoading) return;
|
|
246
244
|
lastInputRef.current = trimmed;
|
|
@@ -257,22 +255,77 @@ function useRagChat(projectId, options = {}) {
|
|
|
257
255
|
setIsLoading(true);
|
|
258
256
|
try {
|
|
259
257
|
const history = messagesRef.current.map(({ role, content }) => ({ role, content }));
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
258
|
+
const response = await fetch(apiUrl, {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: { "Content-Type": "application/json" },
|
|
261
|
+
body: JSON.stringify({
|
|
262
|
+
message: trimmed,
|
|
263
|
+
history,
|
|
264
|
+
namespace: namespace != null ? namespace : projectId
|
|
265
|
+
})
|
|
264
266
|
});
|
|
265
|
-
|
|
266
|
-
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
const errorData = await response.json().catch(() => ({}));
|
|
269
|
+
throw new Error(errorData.error || `Server returned ${response.status}`);
|
|
270
|
+
}
|
|
271
|
+
if (!response.body) {
|
|
272
|
+
throw new Error("No response body received from server");
|
|
273
|
+
}
|
|
274
|
+
const reader = response.body.getReader();
|
|
275
|
+
const decoder = new TextDecoder();
|
|
276
|
+
let assistantContent = "";
|
|
277
|
+
let sources = [];
|
|
278
|
+
const assistantMessageId = `assistant_${Date.now()}`;
|
|
279
|
+
setMessages((prev) => [
|
|
280
|
+
...prev,
|
|
281
|
+
{
|
|
282
|
+
id: assistantMessageId,
|
|
283
|
+
role: "assistant",
|
|
284
|
+
content: "",
|
|
285
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
286
|
+
}
|
|
287
|
+
]);
|
|
288
|
+
while (true) {
|
|
289
|
+
const { done, value } = await reader.read();
|
|
290
|
+
if (done) break;
|
|
291
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
292
|
+
if (chunk.includes("__METADATA__")) {
|
|
293
|
+
const [text2, metadataStr] = chunk.split("__METADATA__");
|
|
294
|
+
if (text2) assistantContent += text2;
|
|
295
|
+
try {
|
|
296
|
+
const meta = JSON.parse(metadataStr);
|
|
297
|
+
sources = meta.sources || [];
|
|
298
|
+
} catch (e) {
|
|
299
|
+
console.warn("[useRagChat] Failed to parse metadata");
|
|
300
|
+
}
|
|
301
|
+
} else if (chunk.includes("__ERROR__")) {
|
|
302
|
+
const [text2, errorStr] = chunk.split("__ERROR__");
|
|
303
|
+
if (text2) assistantContent += text2;
|
|
304
|
+
try {
|
|
305
|
+
const err = JSON.parse(errorStr);
|
|
306
|
+
setError(err.error || "Stream error");
|
|
307
|
+
} catch (e) {
|
|
308
|
+
setError("An error occurred during streaming");
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
assistantContent += chunk;
|
|
312
|
+
}
|
|
313
|
+
setMessages(
|
|
314
|
+
(prev) => prev.map(
|
|
315
|
+
(msg) => msg.id === assistantMessageId ? __spreadProps(__spreadValues({}, msg), { content: assistantContent, sources: sources.length > 0 ? sources : msg.sources }) : msg
|
|
316
|
+
)
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
const finalAssistantMessage = {
|
|
320
|
+
id: assistantMessageId,
|
|
267
321
|
role: "assistant",
|
|
268
|
-
content:
|
|
269
|
-
sources:
|
|
322
|
+
content: assistantContent,
|
|
323
|
+
sources: sources.length > 0 ? sources : void 0,
|
|
270
324
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
271
325
|
};
|
|
272
|
-
|
|
273
|
-
onReply == null ? void 0 : onReply(assistantMessage);
|
|
326
|
+
onReply == null ? void 0 : onReply(finalAssistantMessage);
|
|
274
327
|
} catch (err) {
|
|
275
|
-
const msg =
|
|
328
|
+
const msg = err instanceof Error ? err.message : "Something went wrong. Please try again.";
|
|
276
329
|
setError(msg);
|
|
277
330
|
onError == null ? void 0 : onError(msg);
|
|
278
331
|
} finally {
|
|
@@ -324,6 +377,7 @@ var BORDER_RADIUS_MAP = {
|
|
|
324
377
|
|
|
325
378
|
// src/components/ChatWindow.tsx
|
|
326
379
|
function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
380
|
+
var _a;
|
|
327
381
|
const { ui, projectId } = useConfig();
|
|
328
382
|
const [input, setInput] = (0, import_react6.useState)("");
|
|
329
383
|
const [mounted, setMounted] = (0, import_react6.useState)(false);
|
|
@@ -336,9 +390,9 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
|
336
390
|
setMounted(true);
|
|
337
391
|
}, []);
|
|
338
392
|
(0, import_react6.useEffect)(() => {
|
|
339
|
-
var
|
|
393
|
+
var _a2;
|
|
340
394
|
if (messages.length > 0 || isLoading) {
|
|
341
|
-
(
|
|
395
|
+
(_a2 = bottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
|
|
342
396
|
}
|
|
343
397
|
}, [messages, isLoading]);
|
|
344
398
|
const sendMessage = (0, import_react6.useCallback)(async () => {
|
|
@@ -347,8 +401,8 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
|
347
401
|
setInput("");
|
|
348
402
|
await send(text);
|
|
349
403
|
window.setTimeout(() => {
|
|
350
|
-
var
|
|
351
|
-
return (
|
|
404
|
+
var _a2;
|
|
405
|
+
return (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
352
406
|
}, 50);
|
|
353
407
|
}, [input, isLoading, send]);
|
|
354
408
|
const handleKeyDown = (e) => {
|
|
@@ -419,25 +473,26 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
|
419
473
|
{
|
|
420
474
|
key: suggestion,
|
|
421
475
|
onClick: () => {
|
|
422
|
-
var
|
|
476
|
+
var _a2;
|
|
423
477
|
setInput(suggestion);
|
|
424
|
-
(
|
|
478
|
+
(_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
425
479
|
},
|
|
426
480
|
className: "px-3 py-1.5 rounded-full 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:border-slate-300 dark:hover:border-white/20 hover:bg-slate-100 dark:hover:bg-white/5 transition-all"
|
|
427
481
|
},
|
|
428
482
|
suggestion
|
|
429
483
|
)
|
|
430
484
|
)))
|
|
431
|
-
) : messages.map((msg) => /* @__PURE__ */ import_react6.default.createElement(
|
|
485
|
+
) : messages.map((msg, index) => /* @__PURE__ */ import_react6.default.createElement(
|
|
432
486
|
MessageBubble,
|
|
433
487
|
{
|
|
434
488
|
key: msg.id,
|
|
435
489
|
message: msg,
|
|
436
490
|
sources: msg.sources,
|
|
491
|
+
isStreaming: isLoading && index === messages.length - 1 && msg.role === "assistant",
|
|
437
492
|
primaryColor: ui.primaryColor,
|
|
438
493
|
accentColor: ui.accentColor
|
|
439
494
|
}
|
|
440
|
-
)), isLoading && /* @__PURE__ */ import_react6.default.createElement(
|
|
495
|
+
)), isLoading && ((_a = messages[messages.length - 1]) == null ? void 0 : _a.role) !== "assistant" && /* @__PURE__ */ import_react6.default.createElement(
|
|
441
496
|
MessageBubble,
|
|
442
497
|
{
|
|
443
498
|
message: { role: "assistant", content: "" },
|
package/dist/index.mjs
CHANGED
|
@@ -73,7 +73,7 @@ function MessageBubble({
|
|
|
73
73
|
className: `relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser ? "text-white rounded-tr-sm" : "bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm"}`,
|
|
74
74
|
style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
75
75
|
},
|
|
76
|
-
isStreaming ? /* @__PURE__ */ React2.createElement("span", { className: "flex items-center gap-1" }, /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ React2.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ React2.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm] }, message.content))
|
|
76
|
+
isStreaming && !message.content ? /* @__PURE__ */ React2.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ React2.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ React2.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm] }, message.content), isStreaming && message.content && /* @__PURE__ */ React2.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
|
|
77
77
|
), !isUser && sources && sources.length > 0 && /* @__PURE__ */ React2.createElement("div", { className: "w-full" }, /* @__PURE__ */ React2.createElement(
|
|
78
78
|
"button",
|
|
79
79
|
{
|
|
@@ -125,7 +125,6 @@ function useConfig() {
|
|
|
125
125
|
|
|
126
126
|
// src/hooks/useRagChat.ts
|
|
127
127
|
import { useState as useState2, useCallback, useRef, useEffect as useEffect2 } from "react";
|
|
128
|
-
import axios from "axios";
|
|
129
128
|
|
|
130
129
|
// src/hooks/useStoredMessages.ts
|
|
131
130
|
import * as React4 from "react";
|
|
@@ -179,7 +178,6 @@ function useRagChat(projectId, options = {}) {
|
|
|
179
178
|
}, [messages]);
|
|
180
179
|
const sendMessage = useCallback(
|
|
181
180
|
async (text, options2) => {
|
|
182
|
-
var _a, _b, _c;
|
|
183
181
|
const trimmed = text.trim();
|
|
184
182
|
if (!trimmed || isLoading) return;
|
|
185
183
|
lastInputRef.current = trimmed;
|
|
@@ -196,22 +194,77 @@ function useRagChat(projectId, options = {}) {
|
|
|
196
194
|
setIsLoading(true);
|
|
197
195
|
try {
|
|
198
196
|
const history = messagesRef.current.map(({ role, content }) => ({ role, content }));
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
197
|
+
const response = await fetch(apiUrl, {
|
|
198
|
+
method: "POST",
|
|
199
|
+
headers: { "Content-Type": "application/json" },
|
|
200
|
+
body: JSON.stringify({
|
|
201
|
+
message: trimmed,
|
|
202
|
+
history,
|
|
203
|
+
namespace: namespace != null ? namespace : projectId
|
|
204
|
+
})
|
|
203
205
|
});
|
|
204
|
-
|
|
205
|
-
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
const errorData = await response.json().catch(() => ({}));
|
|
208
|
+
throw new Error(errorData.error || `Server returned ${response.status}`);
|
|
209
|
+
}
|
|
210
|
+
if (!response.body) {
|
|
211
|
+
throw new Error("No response body received from server");
|
|
212
|
+
}
|
|
213
|
+
const reader = response.body.getReader();
|
|
214
|
+
const decoder = new TextDecoder();
|
|
215
|
+
let assistantContent = "";
|
|
216
|
+
let sources = [];
|
|
217
|
+
const assistantMessageId = `assistant_${Date.now()}`;
|
|
218
|
+
setMessages((prev) => [
|
|
219
|
+
...prev,
|
|
220
|
+
{
|
|
221
|
+
id: assistantMessageId,
|
|
222
|
+
role: "assistant",
|
|
223
|
+
content: "",
|
|
224
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
225
|
+
}
|
|
226
|
+
]);
|
|
227
|
+
while (true) {
|
|
228
|
+
const { done, value } = await reader.read();
|
|
229
|
+
if (done) break;
|
|
230
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
231
|
+
if (chunk.includes("__METADATA__")) {
|
|
232
|
+
const [text2, metadataStr] = chunk.split("__METADATA__");
|
|
233
|
+
if (text2) assistantContent += text2;
|
|
234
|
+
try {
|
|
235
|
+
const meta = JSON.parse(metadataStr);
|
|
236
|
+
sources = meta.sources || [];
|
|
237
|
+
} catch (e) {
|
|
238
|
+
console.warn("[useRagChat] Failed to parse metadata");
|
|
239
|
+
}
|
|
240
|
+
} else if (chunk.includes("__ERROR__")) {
|
|
241
|
+
const [text2, errorStr] = chunk.split("__ERROR__");
|
|
242
|
+
if (text2) assistantContent += text2;
|
|
243
|
+
try {
|
|
244
|
+
const err = JSON.parse(errorStr);
|
|
245
|
+
setError(err.error || "Stream error");
|
|
246
|
+
} catch (e) {
|
|
247
|
+
setError("An error occurred during streaming");
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
assistantContent += chunk;
|
|
251
|
+
}
|
|
252
|
+
setMessages(
|
|
253
|
+
(prev) => prev.map(
|
|
254
|
+
(msg) => msg.id === assistantMessageId ? __spreadProps(__spreadValues({}, msg), { content: assistantContent, sources: sources.length > 0 ? sources : msg.sources }) : msg
|
|
255
|
+
)
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
const finalAssistantMessage = {
|
|
259
|
+
id: assistantMessageId,
|
|
206
260
|
role: "assistant",
|
|
207
|
-
content:
|
|
208
|
-
sources:
|
|
261
|
+
content: assistantContent,
|
|
262
|
+
sources: sources.length > 0 ? sources : void 0,
|
|
209
263
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
210
264
|
};
|
|
211
|
-
|
|
212
|
-
onReply == null ? void 0 : onReply(assistantMessage);
|
|
265
|
+
onReply == null ? void 0 : onReply(finalAssistantMessage);
|
|
213
266
|
} catch (err) {
|
|
214
|
-
const msg =
|
|
267
|
+
const msg = err instanceof Error ? err.message : "Something went wrong. Please try again.";
|
|
215
268
|
setError(msg);
|
|
216
269
|
onError == null ? void 0 : onError(msg);
|
|
217
270
|
} finally {
|
|
@@ -281,6 +334,7 @@ var BORDER_RADIUS_MAP = {
|
|
|
281
334
|
|
|
282
335
|
// src/components/ChatWindow.tsx
|
|
283
336
|
function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
337
|
+
var _a;
|
|
284
338
|
const { ui, projectId } = useConfig();
|
|
285
339
|
const [input, setInput] = useState3("");
|
|
286
340
|
const [mounted, setMounted] = useState3(false);
|
|
@@ -293,9 +347,9 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
|
293
347
|
setMounted(true);
|
|
294
348
|
}, []);
|
|
295
349
|
useEffect3(() => {
|
|
296
|
-
var
|
|
350
|
+
var _a2;
|
|
297
351
|
if (messages.length > 0 || isLoading) {
|
|
298
|
-
(
|
|
352
|
+
(_a2 = bottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
|
|
299
353
|
}
|
|
300
354
|
}, [messages, isLoading]);
|
|
301
355
|
const sendMessage = useCallback2(async () => {
|
|
@@ -304,8 +358,8 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
|
304
358
|
setInput("");
|
|
305
359
|
await send(text);
|
|
306
360
|
window.setTimeout(() => {
|
|
307
|
-
var
|
|
308
|
-
return (
|
|
361
|
+
var _a2;
|
|
362
|
+
return (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
309
363
|
}, 50);
|
|
310
364
|
}, [input, isLoading, send]);
|
|
311
365
|
const handleKeyDown = (e) => {
|
|
@@ -376,25 +430,26 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
|
376
430
|
{
|
|
377
431
|
key: suggestion,
|
|
378
432
|
onClick: () => {
|
|
379
|
-
var
|
|
433
|
+
var _a2;
|
|
380
434
|
setInput(suggestion);
|
|
381
|
-
(
|
|
435
|
+
(_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
382
436
|
},
|
|
383
437
|
className: "px-3 py-1.5 rounded-full 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:border-slate-300 dark:hover:border-white/20 hover:bg-slate-100 dark:hover:bg-white/5 transition-all"
|
|
384
438
|
},
|
|
385
439
|
suggestion
|
|
386
440
|
)
|
|
387
441
|
)))
|
|
388
|
-
) : messages.map((msg) => /* @__PURE__ */ React6.createElement(
|
|
442
|
+
) : messages.map((msg, index) => /* @__PURE__ */ React6.createElement(
|
|
389
443
|
MessageBubble,
|
|
390
444
|
{
|
|
391
445
|
key: msg.id,
|
|
392
446
|
message: msg,
|
|
393
447
|
sources: msg.sources,
|
|
448
|
+
isStreaming: isLoading && index === messages.length - 1 && msg.role === "assistant",
|
|
394
449
|
primaryColor: ui.primaryColor,
|
|
395
450
|
accentColor: ui.accentColor
|
|
396
451
|
}
|
|
397
|
-
)), isLoading && /* @__PURE__ */ React6.createElement(
|
|
452
|
+
)), isLoading && ((_a = messages[messages.length - 1]) == null ? void 0 : _a.role) !== "assistant" && /* @__PURE__ */ React6.createElement(
|
|
398
453
|
MessageBubble,
|
|
399
454
|
{
|
|
400
455
|
message: { role: "assistant", content: "" },
|
package/dist/server.d.mts
CHANGED
|
@@ -111,26 +111,7 @@ declare class ProviderHealthCheck {
|
|
|
111
111
|
declare class VectorPlugin {
|
|
112
112
|
private config;
|
|
113
113
|
private pipeline;
|
|
114
|
-
|
|
115
|
-
* Initializes the plugin with the host configuration.
|
|
116
|
-
* @param hostConfig - Configuration object passed from the host application.
|
|
117
|
-
* @throws Error if configuration is invalid or providers are unhealthy
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* const plugin = new VectorPlugin({
|
|
121
|
-
* projectId: 'my-app',
|
|
122
|
-
* vectorDb: {
|
|
123
|
-
* provider: 'pinecone',
|
|
124
|
-
* indexName: 'my-index',
|
|
125
|
-
* options: { apiKey: process.env.PINECONE_API_KEY }
|
|
126
|
-
* },
|
|
127
|
-
* llm: {
|
|
128
|
-
* provider: 'openai',
|
|
129
|
-
* model: 'gpt-4o',
|
|
130
|
-
* apiKey: process.env.OPENAI_API_KEY
|
|
131
|
-
* }
|
|
132
|
-
* });
|
|
133
|
-
*/
|
|
114
|
+
private validationPromise;
|
|
134
115
|
constructor(hostConfig?: Partial<RagConfig>);
|
|
135
116
|
/**
|
|
136
117
|
* Get the current resolved configuration.
|
package/dist/server.d.ts
CHANGED
|
@@ -111,26 +111,7 @@ declare class ProviderHealthCheck {
|
|
|
111
111
|
declare class VectorPlugin {
|
|
112
112
|
private config;
|
|
113
113
|
private pipeline;
|
|
114
|
-
|
|
115
|
-
* Initializes the plugin with the host configuration.
|
|
116
|
-
* @param hostConfig - Configuration object passed from the host application.
|
|
117
|
-
* @throws Error if configuration is invalid or providers are unhealthy
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* const plugin = new VectorPlugin({
|
|
121
|
-
* projectId: 'my-app',
|
|
122
|
-
* vectorDb: {
|
|
123
|
-
* provider: 'pinecone',
|
|
124
|
-
* indexName: 'my-index',
|
|
125
|
-
* options: { apiKey: process.env.PINECONE_API_KEY }
|
|
126
|
-
* },
|
|
127
|
-
* llm: {
|
|
128
|
-
* provider: 'openai',
|
|
129
|
-
* model: 'gpt-4o',
|
|
130
|
-
* apiKey: process.env.OPENAI_API_KEY
|
|
131
|
-
* }
|
|
132
|
-
* });
|
|
133
|
-
*/
|
|
114
|
+
private validationPromise;
|
|
134
115
|
constructor(hostConfig?: Partial<RagConfig>);
|
|
135
116
|
/**
|
|
136
117
|
* Get the current resolved configuration.
|
package/dist/server.js
CHANGED
|
@@ -558,7 +558,7 @@ var init_MongoDBProvider = __esm({
|
|
|
558
558
|
if (!opts.uri || !opts.database || !opts.collection) {
|
|
559
559
|
throw new Error("[MongoDBProvider] options uri, database, and collection are required.");
|
|
560
560
|
}
|
|
561
|
-
this.client = new import_mongodb.MongoClient(opts.uri);
|
|
561
|
+
this.client = new import_mongodb.MongoClient(opts.uri, { serverSelectionTimeoutMS: 5e3 });
|
|
562
562
|
this.dbName = opts.database;
|
|
563
563
|
this.collectionName = opts.collection;
|
|
564
564
|
this.embeddingKey = opts.embeddingKey || "embedding";
|
|
@@ -3522,6 +3522,7 @@ var Pipeline = class {
|
|
|
3522
3522
|
async initialize() {
|
|
3523
3523
|
var _a;
|
|
3524
3524
|
if (this.initialised) return;
|
|
3525
|
+
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
3525
3526
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
3526
3527
|
const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
|
|
3527
3528
|
this.config.llm,
|
|
@@ -3906,29 +3907,9 @@ var ProviderHealthCheck = class {
|
|
|
3906
3907
|
|
|
3907
3908
|
// src/core/VectorPlugin.ts
|
|
3908
3909
|
var VectorPlugin = class {
|
|
3909
|
-
/**
|
|
3910
|
-
* Initializes the plugin with the host configuration.
|
|
3911
|
-
* @param hostConfig - Configuration object passed from the host application.
|
|
3912
|
-
* @throws Error if configuration is invalid or providers are unhealthy
|
|
3913
|
-
*
|
|
3914
|
-
* @example
|
|
3915
|
-
* const plugin = new VectorPlugin({
|
|
3916
|
-
* projectId: 'my-app',
|
|
3917
|
-
* vectorDb: {
|
|
3918
|
-
* provider: 'pinecone',
|
|
3919
|
-
* indexName: 'my-index',
|
|
3920
|
-
* options: { apiKey: process.env.PINECONE_API_KEY }
|
|
3921
|
-
* },
|
|
3922
|
-
* llm: {
|
|
3923
|
-
* provider: 'openai',
|
|
3924
|
-
* model: 'gpt-4o',
|
|
3925
|
-
* apiKey: process.env.OPENAI_API_KEY
|
|
3926
|
-
* }
|
|
3927
|
-
* });
|
|
3928
|
-
*/
|
|
3929
3910
|
constructor(hostConfig) {
|
|
3930
3911
|
this.config = ConfigResolver.resolve(hostConfig);
|
|
3931
|
-
ConfigValidator.validateAndThrow(this.config);
|
|
3912
|
+
this.validationPromise = ConfigValidator.validateAndThrow(this.config);
|
|
3932
3913
|
this.pipeline = new Pipeline(this.config);
|
|
3933
3914
|
}
|
|
3934
3915
|
/**
|
|
@@ -3954,6 +3935,7 @@ var VectorPlugin = class {
|
|
|
3954
3935
|
* Run a chat query.
|
|
3955
3936
|
*/
|
|
3956
3937
|
async chat(message, history = [], namespace) {
|
|
3938
|
+
await this.validationPromise;
|
|
3957
3939
|
return this.pipeline.ask(message, history, namespace);
|
|
3958
3940
|
}
|
|
3959
3941
|
/**
|
|
@@ -3961,6 +3943,7 @@ var VectorPlugin = class {
|
|
|
3961
3943
|
*/
|
|
3962
3944
|
chatStream(_0) {
|
|
3963
3945
|
return __asyncGenerator(this, arguments, function* (message, history = [], namespace) {
|
|
3946
|
+
yield new __await(this.validationPromise);
|
|
3964
3947
|
yield* __yieldStar(this.pipeline.askStream(message, history, namespace));
|
|
3965
3948
|
});
|
|
3966
3949
|
}
|
|
@@ -3968,6 +3951,7 @@ var VectorPlugin = class {
|
|
|
3968
3951
|
* Ingest documents into the vector database.
|
|
3969
3952
|
*/
|
|
3970
3953
|
async ingest(documents, namespace) {
|
|
3954
|
+
await this.validationPromise;
|
|
3971
3955
|
return this.pipeline.ingest(documents, namespace);
|
|
3972
3956
|
}
|
|
3973
3957
|
};
|
package/dist/server.mjs
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
createIngestHandler,
|
|
35
35
|
createUploadHandler,
|
|
36
36
|
getRagConfig
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-OJNAKSQ2.mjs";
|
|
38
38
|
import "./chunk-YLTMFW4M.mjs";
|
|
39
39
|
import {
|
|
40
40
|
PineconeProvider
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
} from "./chunk-FLOSGE6A.mjs";
|
|
45
45
|
import {
|
|
46
46
|
MongoDBProvider
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-73I6VWU3.mjs";
|
|
48
48
|
import {
|
|
49
49
|
MilvusProvider
|
|
50
50
|
} from "./chunk-U55XRW3U.mjs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.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",
|
|
@@ -170,19 +170,20 @@ export function ChatWindow({ className = '', style, onClose, showClose = false }
|
|
|
170
170
|
</div>
|
|
171
171
|
</div>
|
|
172
172
|
) : (
|
|
173
|
-
messages.map((msg) => (
|
|
173
|
+
messages.map((msg, index) => (
|
|
174
174
|
<MessageBubble
|
|
175
175
|
key={msg.id}
|
|
176
176
|
message={msg}
|
|
177
177
|
sources={msg.sources}
|
|
178
|
+
isStreaming={isLoading && index === messages.length - 1 && msg.role === 'assistant'}
|
|
178
179
|
primaryColor={ui.primaryColor}
|
|
179
180
|
accentColor={ui.accentColor}
|
|
180
181
|
/>
|
|
181
182
|
))
|
|
182
183
|
)}
|
|
183
184
|
|
|
184
|
-
{/* Typing indicator */}
|
|
185
|
-
{isLoading && (
|
|
185
|
+
{/* Typing indicator (only if assistant hasn't started replying yet) */}
|
|
186
|
+
{isLoading && messages[messages.length - 1]?.role !== 'assistant' && (
|
|
186
187
|
<MessageBubble
|
|
187
188
|
message={{ role: 'assistant', content: '' }}
|
|
188
189
|
isStreaming
|
|
@@ -55,8 +55,8 @@ export function MessageBubble({
|
|
|
55
55
|
: {}
|
|
56
56
|
}
|
|
57
57
|
>
|
|
58
|
-
{isStreaming ? (
|
|
59
|
-
<span className="flex items-center gap-1">
|
|
58
|
+
{isStreaming && !message.content ? (
|
|
59
|
+
<span className="flex items-center gap-1 py-1">
|
|
60
60
|
<span className="w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" />
|
|
61
61
|
<span className="w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" />
|
|
62
62
|
<span className="w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" />
|
|
@@ -66,6 +66,9 @@ export function MessageBubble({
|
|
|
66
66
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
|
67
67
|
{message.content}
|
|
68
68
|
</ReactMarkdown>
|
|
69
|
+
{isStreaming && message.content && (
|
|
70
|
+
<span className="inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" />
|
|
71
|
+
)}
|
|
69
72
|
</div>
|
|
70
73
|
)}
|
|
71
74
|
</div>
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -50,6 +50,7 @@ export class Pipeline {
|
|
|
50
50
|
async initialize(): Promise<void> {
|
|
51
51
|
if (this.initialised) return;
|
|
52
52
|
|
|
53
|
+
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
53
54
|
// Resolve vector DB provider
|
|
54
55
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
55
56
|
|
package/src/core/VectorPlugin.ts
CHANGED
|
@@ -20,32 +20,13 @@ import { ChatMessage } from '../llm/ILLMProvider';
|
|
|
20
20
|
export class VectorPlugin {
|
|
21
21
|
private config: RagConfig;
|
|
22
22
|
private pipeline: Pipeline;
|
|
23
|
+
private validationPromise: Promise<void>;
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
-
* Initializes the plugin with the host configuration.
|
|
26
|
-
* @param hostConfig - Configuration object passed from the host application.
|
|
27
|
-
* @throws Error if configuration is invalid or providers are unhealthy
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* const plugin = new VectorPlugin({
|
|
31
|
-
* projectId: 'my-app',
|
|
32
|
-
* vectorDb: {
|
|
33
|
-
* provider: 'pinecone',
|
|
34
|
-
* indexName: 'my-index',
|
|
35
|
-
* options: { apiKey: process.env.PINECONE_API_KEY }
|
|
36
|
-
* },
|
|
37
|
-
* llm: {
|
|
38
|
-
* provider: 'openai',
|
|
39
|
-
* model: 'gpt-4o',
|
|
40
|
-
* apiKey: process.env.OPENAI_API_KEY
|
|
41
|
-
* }
|
|
42
|
-
* });
|
|
43
|
-
*/
|
|
44
25
|
constructor(hostConfig?: Partial<RagConfig>) {
|
|
45
26
|
this.config = ConfigResolver.resolve(hostConfig);
|
|
46
27
|
|
|
47
|
-
//
|
|
48
|
-
ConfigValidator.validateAndThrow(this.config);
|
|
28
|
+
// Start validation early
|
|
29
|
+
this.validationPromise = ConfigValidator.validateAndThrow(this.config);
|
|
49
30
|
|
|
50
31
|
this.pipeline = new Pipeline(this.config);
|
|
51
32
|
}
|
|
@@ -80,6 +61,7 @@ export class VectorPlugin {
|
|
|
80
61
|
* Run a chat query.
|
|
81
62
|
*/
|
|
82
63
|
async chat(message: string, history: ChatMessage[] = [], namespace?: string): Promise<ChatResponse> {
|
|
64
|
+
await this.validationPromise;
|
|
83
65
|
return this.pipeline.ask(message, history, namespace);
|
|
84
66
|
}
|
|
85
67
|
|
|
@@ -87,6 +69,7 @@ export class VectorPlugin {
|
|
|
87
69
|
* Run a streaming chat query.
|
|
88
70
|
*/
|
|
89
71
|
async *chatStream(message: string, history: ChatMessage[] = [], namespace?: string) {
|
|
72
|
+
await this.validationPromise;
|
|
90
73
|
yield* this.pipeline.askStream(message, history, namespace);
|
|
91
74
|
}
|
|
92
75
|
|
|
@@ -94,6 +77,7 @@ export class VectorPlugin {
|
|
|
94
77
|
* Ingest documents into the vector database.
|
|
95
78
|
*/
|
|
96
79
|
async ingest(documents: IngestDocument[], namespace?: string): Promise<Array<{ docId: string | number; chunksIngested: number }>> {
|
|
80
|
+
await this.validationPromise;
|
|
97
81
|
return this.pipeline.ingest(documents, namespace);
|
|
98
82
|
}
|
|
99
83
|
|
package/src/handlers/index.ts
CHANGED
|
@@ -40,22 +40,30 @@ export function createStreamHandler(config?: Partial<RagConfig>) {
|
|
|
40
40
|
|
|
41
41
|
return async function POST(req: NextRequest) {
|
|
42
42
|
try {
|
|
43
|
-
const
|
|
43
|
+
const body = await req.json();
|
|
44
|
+
const { message, history = [], namespace } = body;
|
|
44
45
|
|
|
45
46
|
const encoder = new TextEncoder();
|
|
46
47
|
const stream = new ReadableStream({
|
|
47
48
|
async start(controller) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
try {
|
|
50
|
+
const pipelineStream = plugin.chatStream(message, history, namespace);
|
|
51
|
+
|
|
52
|
+
for await (const chunk of pipelineStream) {
|
|
53
|
+
if (typeof chunk === 'string') {
|
|
54
|
+
controller.enqueue(encoder.encode(chunk));
|
|
55
|
+
} else {
|
|
56
|
+
// Yield metadata/sources at the end
|
|
57
|
+
controller.enqueue(encoder.encode(`\n\n__METADATA__${JSON.stringify(chunk)}`));
|
|
58
|
+
}
|
|
56
59
|
}
|
|
60
|
+
controller.close();
|
|
61
|
+
} catch (streamError) {
|
|
62
|
+
console.error('[createStreamHandler] Stream processing error:', streamError);
|
|
63
|
+
const errorMessage = streamError instanceof Error ? streamError.message : String(streamError);
|
|
64
|
+
controller.enqueue(encoder.encode(`\n\n__ERROR__${JSON.stringify({ error: errorMessage })}`));
|
|
65
|
+
controller.close();
|
|
57
66
|
}
|
|
58
|
-
controller.close();
|
|
59
67
|
},
|
|
60
68
|
});
|
|
61
69
|
|
package/src/hooks/useRagChat.ts
CHANGED
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
17
|
-
import axios from 'axios';
|
|
18
17
|
import { VectorMatch } from '@/types';
|
|
19
18
|
import { useStoredMessages } from './useStoredMessages';
|
|
20
19
|
|
|
@@ -111,26 +110,91 @@ export function useRagChat(
|
|
|
111
110
|
try {
|
|
112
111
|
const history = messagesRef.current.map(({ role, content }) => ({ role, content }));
|
|
113
112
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
const response = await fetch(apiUrl, {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: { 'Content-Type': 'application/json' },
|
|
116
|
+
body: JSON.stringify({
|
|
117
|
+
message: trimmed,
|
|
118
|
+
history,
|
|
119
|
+
namespace: namespace ?? projectId,
|
|
120
|
+
}),
|
|
118
121
|
});
|
|
119
122
|
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
const errorData = await response.json().catch(() => ({}));
|
|
125
|
+
throw new Error(errorData.error || `Server returned ${response.status}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!response.body) {
|
|
129
|
+
throw new Error('No response body received from server');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const reader = response.body.getReader();
|
|
133
|
+
const decoder = new TextDecoder();
|
|
134
|
+
let assistantContent = '';
|
|
135
|
+
let sources: VectorMatch[] = [];
|
|
136
|
+
|
|
137
|
+
// Add a placeholder assistant message that we will update
|
|
138
|
+
const assistantMessageId = `assistant_${Date.now()}`;
|
|
139
|
+
setMessages((prev) => [
|
|
140
|
+
...prev,
|
|
141
|
+
{
|
|
142
|
+
id: assistantMessageId,
|
|
143
|
+
role: 'assistant',
|
|
144
|
+
content: '',
|
|
145
|
+
createdAt: new Date().toISOString(),
|
|
146
|
+
},
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
while (true) {
|
|
150
|
+
const { done, value } = await reader.read();
|
|
151
|
+
if (done) break;
|
|
152
|
+
|
|
153
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
154
|
+
|
|
155
|
+
// Handle protocol tokens
|
|
156
|
+
if (chunk.includes('__METADATA__')) {
|
|
157
|
+
const [text, metadataStr] = chunk.split('__METADATA__');
|
|
158
|
+
if (text) assistantContent += text;
|
|
159
|
+
try {
|
|
160
|
+
const meta = JSON.parse(metadataStr);
|
|
161
|
+
sources = meta.sources || [];
|
|
162
|
+
} catch {
|
|
163
|
+
console.warn('[useRagChat] Failed to parse metadata');
|
|
164
|
+
}
|
|
165
|
+
} else if (chunk.includes('__ERROR__')) {
|
|
166
|
+
const [text, errorStr] = chunk.split('__ERROR__');
|
|
167
|
+
if (text) assistantContent += text;
|
|
168
|
+
try {
|
|
169
|
+
const err = JSON.parse(errorStr);
|
|
170
|
+
setError(err.error || 'Stream error');
|
|
171
|
+
} catch {
|
|
172
|
+
setError('An error occurred during streaming');
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
assistantContent += chunk;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Update the specific message
|
|
179
|
+
setMessages((prev) =>
|
|
180
|
+
prev.map((msg) =>
|
|
181
|
+
msg.id === assistantMessageId
|
|
182
|
+
? { ...msg, content: assistantContent, sources: sources.length > 0 ? sources : msg.sources }
|
|
183
|
+
: msg
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const finalAssistantMessage: RagMessage = {
|
|
189
|
+
id: assistantMessageId,
|
|
122
190
|
role: 'assistant',
|
|
123
|
-
content:
|
|
124
|
-
sources:
|
|
191
|
+
content: assistantContent,
|
|
192
|
+
sources: sources.length > 0 ? sources : undefined,
|
|
125
193
|
createdAt: new Date().toISOString(),
|
|
126
194
|
};
|
|
127
|
-
|
|
128
|
-
setMessages((prev) => [...prev, assistantMessage]);
|
|
129
|
-
onReply?.(assistantMessage);
|
|
195
|
+
onReply?.(finalAssistantMessage);
|
|
130
196
|
} catch (err) {
|
|
131
|
-
const msg =
|
|
132
|
-
? (err.response?.data?.error ?? err.message)
|
|
133
|
-
: 'Something went wrong. Please try again.';
|
|
197
|
+
const msg = err instanceof Error ? err.message : 'Something went wrong. Please try again.';
|
|
134
198
|
setError(msg);
|
|
135
199
|
onError?.(msg);
|
|
136
200
|
} finally {
|
|
@@ -25,7 +25,7 @@ export class MongoDBProvider extends BaseVectorProvider {
|
|
|
25
25
|
if (!opts.uri || !opts.database || !opts.collection) {
|
|
26
26
|
throw new Error('[MongoDBProvider] options uri, database, and collection are required.');
|
|
27
27
|
}
|
|
28
|
-
this.client = new MongoClient(opts.uri as string);
|
|
28
|
+
this.client = new MongoClient(opts.uri as string, { serverSelectionTimeoutMS: 5000 });
|
|
29
29
|
this.dbName = opts.database as string;
|
|
30
30
|
this.collectionName = opts.collection as string;
|
|
31
31
|
this.embeddingKey = (opts.embeddingKey as string) || 'embedding';
|