@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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MongoDBProvider
3
- } from "./chunk-CD6TSNL4.mjs";
3
+ } from "./chunk-73I6VWU3.mjs";
4
4
  import "./chunk-IMP6FUCY.mjs";
5
5
  import "./chunk-X4TOT24V.mjs";
6
6
  export {
@@ -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-Z6ALOVDN.mjs");
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 { message, history = [], namespace } = await req.json();
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
- for (var iter = __forAwait(pipelineStream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
2518
- const chunk = temp.value;
2519
- if (typeof chunk === "string") {
2520
- controller.enqueue(encoder.encode(chunk));
2521
- } else {
2522
- controller.enqueue(encoder.encode(`
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
- } catch (temp) {
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
- if (error)
2534
- throw error[0];
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, {
@@ -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 { message, history = [], namespace } = await req.json();
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
- for (var iter = __forAwait(pipelineStream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
3975
- const chunk = temp.value;
3976
- if (typeof chunk === "string") {
3977
- controller.enqueue(encoder.encode(chunk));
3978
- } else {
3979
- controller.enqueue(encoder.encode(`
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
- } catch (temp) {
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
- if (error)
3991
- throw error[0];
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, {
@@ -4,7 +4,7 @@ import {
4
4
  createIngestHandler,
5
5
  createStreamHandler,
6
6
  createUploadHandler
7
- } from "../chunk-5W2YWFT3.mjs";
7
+ } from "../chunk-OJNAKSQ2.mjs";
8
8
  import "../chunk-YLTMFW4M.mjs";
9
9
  import "../chunk-X4TOT24V.mjs";
10
10
  export {
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 { data } = await import_axios.default.post(apiUrl, {
261
- message: trimmed,
262
- history,
263
- namespace: namespace != null ? namespace : projectId
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
- const assistantMessage = {
266
- id: `assistant_${Date.now()}`,
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: data.reply,
269
- sources: data.sources,
322
+ content: assistantContent,
323
+ sources: sources.length > 0 ? sources : void 0,
270
324
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
271
325
  };
272
- setMessages((prev) => [...prev, assistantMessage]);
273
- onReply == null ? void 0 : onReply(assistantMessage);
326
+ onReply == null ? void 0 : onReply(finalAssistantMessage);
274
327
  } catch (err) {
275
- const msg = import_axios.default.isAxiosError(err) ? (_c = (_b = (_a = err.response) == null ? void 0 : _a.data) == null ? void 0 : _b.error) != null ? _c : err.message : "Something went wrong. Please try again.";
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 _a;
393
+ var _a2;
340
394
  if (messages.length > 0 || isLoading) {
341
- (_a = bottomRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
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 _a;
351
- return (_a = inputRef.current) == null ? void 0 : _a.focus();
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 _a;
476
+ var _a2;
423
477
  setInput(suggestion);
424
- (_a = inputRef.current) == null ? void 0 : _a.focus();
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 { data } = await axios.post(apiUrl, {
200
- message: trimmed,
201
- history,
202
- namespace: namespace != null ? namespace : projectId
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
- const assistantMessage = {
205
- id: `assistant_${Date.now()}`,
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: data.reply,
208
- sources: data.sources,
261
+ content: assistantContent,
262
+ sources: sources.length > 0 ? sources : void 0,
209
263
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
210
264
  };
211
- setMessages((prev) => [...prev, assistantMessage]);
212
- onReply == null ? void 0 : onReply(assistantMessage);
265
+ onReply == null ? void 0 : onReply(finalAssistantMessage);
213
266
  } catch (err) {
214
- const msg = axios.isAxiosError(err) ? (_c = (_b = (_a = err.response) == null ? void 0 : _a.data) == null ? void 0 : _b.error) != null ? _c : err.message : "Something went wrong. Please try again.";
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 _a;
350
+ var _a2;
297
351
  if (messages.length > 0 || isLoading) {
298
- (_a = bottomRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
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 _a;
308
- return (_a = inputRef.current) == null ? void 0 : _a.focus();
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 _a;
433
+ var _a2;
380
434
  setInput(suggestion);
381
- (_a = inputRef.current) == null ? void 0 : _a.focus();
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-5W2YWFT3.mjs";
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-CD6TSNL4.mjs";
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.1",
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>
@@ -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
 
@@ -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
- // Validate configuration early with detailed error messages
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
 
@@ -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 { message, history = [], namespace } = await req.json();
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
- const pipelineStream = plugin.chatStream(message, history, namespace);
49
-
50
- for await (const chunk of pipelineStream) {
51
- if (typeof chunk === 'string') {
52
- controller.enqueue(encoder.encode(chunk));
53
- } else {
54
- // Yield metadata/sources at the end
55
- controller.enqueue(encoder.encode(`\n\n__METADATA__${JSON.stringify(chunk)}`));
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
 
@@ -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 { data } = await axios.post(apiUrl, {
115
- message: trimmed,
116
- history,
117
- namespace: namespace ?? projectId,
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
- const assistantMessage: RagMessage = {
121
- id: `assistant_${Date.now()}`,
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: data.reply,
124
- sources: data.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 = axios.isAxiosError(err)
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';