@retrivora-ai/rag-engine 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 {
@@ -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": {
@@ -1391,26 +1391,39 @@ var EntityExtractor = class {
1391
1391
  async extract(text) {
1392
1392
  const prompt = `
1393
1393
  Extract entities and relationships from the following text.
1394
- Format the output as JSON with two keys: "nodes" (array of {id, label, properties}) and "edges" (array of {source, target, type, properties}).
1395
- Use the same ID for the same entity.
1394
+ Format the output as a JSON object with exactly two keys: "nodes" and "edges".
1396
1395
 
1397
- IMPORTANT: Ensure all property values are valid JSON types (strings, numbers, booleans, or null).
1398
- DO NOT include mathematical expressions like "4.5/5" as numbers; use strings instead.
1396
+ Nodes: {id, label, properties}
1397
+ Edges: {source, target, type, properties}
1399
1398
 
1400
- Text:
1401
- "${text}"
1399
+ IMPORTANT:
1400
+ - Ensure all property values are simple JSON types (string, number, boolean).
1401
+ - Do not use mathematical expressions or complex nested objects in properties.
1402
+ - If no entities are found, return empty arrays.
1403
+ - RESPOND ONLY WITH THE JSON OBJECT.
1402
1404
 
1403
- Output JSON:
1405
+ Text to extract from:
1406
+ "${text}"
1404
1407
  `;
1405
1408
  const response = await this.llm.chat([
1406
- { role: "system", content: "You are an expert at knowledge graph extraction. Respond only with valid JSON." },
1409
+ { role: "system", content: "You are a precise knowledge graph extraction engine. You always output valid JSON and nothing else." },
1407
1410
  { role: "user", content: prompt }
1408
- ], "");
1411
+ ], "", { maxTokens: 2048, temperature: 0.1 });
1409
1412
  try {
1410
- const cleanJson = response.replace(/```json\n?|\n?```/g, "").trim();
1413
+ const jsonMatch = response.match(/\{[\s\S]*\}/);
1414
+ const cleanJson = jsonMatch ? jsonMatch[0] : response.trim();
1411
1415
  return JSON.parse(cleanJson);
1412
1416
  } catch (e) {
1413
- console.warn("[EntityExtractor] Failed to parse LLM response as JSON:", response);
1417
+ console.warn(`[EntityExtractor] Failed to parse LLM response. Length: ${response.length} chars.`);
1418
+ if (response.trim().endsWith("}") === false && response.includes('"nodes"')) {
1419
+ try {
1420
+ const partialResponse = response.trim() + (response.includes("[") ? "]}" : "}");
1421
+ const jsonMatch = partialResponse.match(/\{[\s\S]*\}/);
1422
+ if (jsonMatch) return JSON.parse(jsonMatch[0]);
1423
+ } catch (e2) {
1424
+ }
1425
+ }
1426
+ console.warn("[EntityExtractor] Full failing response:", response.substring(0, 200) + "...");
1414
1427
  return { nodes: [], edges: [] };
1415
1428
  }
1416
1429
  }
@@ -1977,6 +1990,7 @@ var Pipeline = class {
1977
1990
  async initialize() {
1978
1991
  var _a;
1979
1992
  if (this.initialised) return;
1993
+ console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
1980
1994
  this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
1981
1995
  const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
1982
1996
  this.config.llm,
@@ -2361,29 +2375,9 @@ var ProviderHealthCheck = class {
2361
2375
 
2362
2376
  // src/core/VectorPlugin.ts
2363
2377
  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
2378
  constructor(hostConfig) {
2385
2379
  this.config = ConfigResolver.resolve(hostConfig);
2386
- ConfigValidator.validateAndThrow(this.config);
2380
+ this.validationPromise = ConfigValidator.validateAndThrow(this.config);
2387
2381
  this.pipeline = new Pipeline(this.config);
2388
2382
  }
2389
2383
  /**
@@ -2409,6 +2403,7 @@ var VectorPlugin = class {
2409
2403
  * Run a chat query.
2410
2404
  */
2411
2405
  async chat(message, history = [], namespace) {
2406
+ await this.validationPromise;
2412
2407
  return this.pipeline.ask(message, history, namespace);
2413
2408
  }
2414
2409
  /**
@@ -2416,6 +2411,7 @@ var VectorPlugin = class {
2416
2411
  */
2417
2412
  chatStream(_0) {
2418
2413
  return __asyncGenerator(this, arguments, function* (message, history = [], namespace) {
2414
+ yield new __await(this.validationPromise);
2419
2415
  yield* __yieldStar(this.pipeline.askStream(message, history, namespace));
2420
2416
  });
2421
2417
  }
@@ -2423,6 +2419,7 @@ var VectorPlugin = class {
2423
2419
  * Ingest documents into the vector database.
2424
2420
  */
2425
2421
  async ingest(documents, namespace) {
2422
+ await this.validationPromise;
2426
2423
  return this.pipeline.ingest(documents, namespace);
2427
2424
  }
2428
2425
  };
@@ -2508,33 +2505,43 @@ function createStreamHandler(config) {
2508
2505
  const plugin = new VectorPlugin(config);
2509
2506
  return async function POST(req) {
2510
2507
  try {
2511
- const { message, history = [], namespace } = await req.json();
2508
+ const body = await req.json();
2509
+ const { message, history = [], namespace } = body;
2512
2510
  const encoder = new TextEncoder();
2513
2511
  const stream = new ReadableStream({
2514
2512
  async start(controller) {
2515
- const pipelineStream = plugin.chatStream(message, history, namespace);
2516
2513
  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(`
2514
+ const pipelineStream = plugin.chatStream(message, history, namespace);
2515
+ try {
2516
+ for (var iter = __forAwait(pipelineStream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
2517
+ const chunk = temp.value;
2518
+ if (typeof chunk === "string") {
2519
+ controller.enqueue(encoder.encode(chunk));
2520
+ } else {
2521
+ controller.enqueue(encoder.encode(`
2523
2522
 
2524
2523
  __METADATA__${JSON.stringify(chunk)}`));
2524
+ }
2525
2525
  }
2526
- }
2527
- } catch (temp) {
2528
- error = [temp];
2529
- } finally {
2530
- try {
2531
- more && (temp = iter.return) && await temp.call(iter);
2526
+ } catch (temp) {
2527
+ error = [temp];
2532
2528
  } finally {
2533
- if (error)
2534
- throw error[0];
2529
+ try {
2530
+ more && (temp = iter.return) && await temp.call(iter);
2531
+ } finally {
2532
+ if (error)
2533
+ throw error[0];
2534
+ }
2535
2535
  }
2536
+ controller.close();
2537
+ } catch (streamError) {
2538
+ console.error("[createStreamHandler] Stream processing error:", streamError);
2539
+ const errorMessage = streamError instanceof Error ? streamError.message : String(streamError);
2540
+ controller.enqueue(encoder.encode(`
2541
+
2542
+ __ERROR__${JSON.stringify({ error: errorMessage })}`));
2543
+ controller.close();
2536
2544
  }
2537
- controller.close();
2538
2545
  }
2539
2546
  });
2540
2547
  return new Response(stream, {
@@ -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";
@@ -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";
@@ -2854,26 +2854,39 @@ var EntityExtractor = class {
2854
2854
  async extract(text) {
2855
2855
  const prompt = `
2856
2856
  Extract entities and relationships from the following text.
2857
- Format the output as JSON with two keys: "nodes" (array of {id, label, properties}) and "edges" (array of {source, target, type, properties}).
2858
- Use the same ID for the same entity.
2857
+ Format the output as a JSON object with exactly two keys: "nodes" and "edges".
2859
2858
 
2860
- IMPORTANT: Ensure all property values are valid JSON types (strings, numbers, booleans, or null).
2861
- DO NOT include mathematical expressions like "4.5/5" as numbers; use strings instead.
2859
+ Nodes: {id, label, properties}
2860
+ Edges: {source, target, type, properties}
2862
2861
 
2863
- Text:
2864
- "${text}"
2862
+ IMPORTANT:
2863
+ - Ensure all property values are simple JSON types (string, number, boolean).
2864
+ - Do not use mathematical expressions or complex nested objects in properties.
2865
+ - If no entities are found, return empty arrays.
2866
+ - RESPOND ONLY WITH THE JSON OBJECT.
2865
2867
 
2866
- Output JSON:
2868
+ Text to extract from:
2869
+ "${text}"
2867
2870
  `;
2868
2871
  const response = await this.llm.chat([
2869
- { role: "system", content: "You are an expert at knowledge graph extraction. Respond only with valid JSON." },
2872
+ { role: "system", content: "You are a precise knowledge graph extraction engine. You always output valid JSON and nothing else." },
2870
2873
  { role: "user", content: prompt }
2871
- ], "");
2874
+ ], "", { maxTokens: 2048, temperature: 0.1 });
2872
2875
  try {
2873
- const cleanJson = response.replace(/```json\n?|\n?```/g, "").trim();
2876
+ const jsonMatch = response.match(/\{[\s\S]*\}/);
2877
+ const cleanJson = jsonMatch ? jsonMatch[0] : response.trim();
2874
2878
  return JSON.parse(cleanJson);
2875
2879
  } catch (e) {
2876
- console.warn("[EntityExtractor] Failed to parse LLM response as JSON:", response);
2880
+ console.warn(`[EntityExtractor] Failed to parse LLM response. Length: ${response.length} chars.`);
2881
+ if (response.trim().endsWith("}") === false && response.includes('"nodes"')) {
2882
+ try {
2883
+ const partialResponse = response.trim() + (response.includes("[") ? "]}" : "}");
2884
+ const jsonMatch = partialResponse.match(/\{[\s\S]*\}/);
2885
+ if (jsonMatch) return JSON.parse(jsonMatch[0]);
2886
+ } catch (e2) {
2887
+ }
2888
+ }
2889
+ console.warn("[EntityExtractor] Full failing response:", response.substring(0, 200) + "...");
2877
2890
  return { nodes: [], edges: [] };
2878
2891
  }
2879
2892
  }
@@ -3434,6 +3447,7 @@ var Pipeline = class {
3434
3447
  async initialize() {
3435
3448
  var _a;
3436
3449
  if (this.initialised) return;
3450
+ console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
3437
3451
  this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
3438
3452
  const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
3439
3453
  this.config.llm,
@@ -3818,29 +3832,9 @@ var ProviderHealthCheck = class {
3818
3832
 
3819
3833
  // src/core/VectorPlugin.ts
3820
3834
  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
3835
  constructor(hostConfig) {
3842
3836
  this.config = ConfigResolver.resolve(hostConfig);
3843
- ConfigValidator.validateAndThrow(this.config);
3837
+ this.validationPromise = ConfigValidator.validateAndThrow(this.config);
3844
3838
  this.pipeline = new Pipeline(this.config);
3845
3839
  }
3846
3840
  /**
@@ -3866,6 +3860,7 @@ var VectorPlugin = class {
3866
3860
  * Run a chat query.
3867
3861
  */
3868
3862
  async chat(message, history = [], namespace) {
3863
+ await this.validationPromise;
3869
3864
  return this.pipeline.ask(message, history, namespace);
3870
3865
  }
3871
3866
  /**
@@ -3873,6 +3868,7 @@ var VectorPlugin = class {
3873
3868
  */
3874
3869
  chatStream(_0) {
3875
3870
  return __asyncGenerator(this, arguments, function* (message, history = [], namespace) {
3871
+ yield new __await(this.validationPromise);
3876
3872
  yield* __yieldStar(this.pipeline.askStream(message, history, namespace));
3877
3873
  });
3878
3874
  }
@@ -3880,6 +3876,7 @@ var VectorPlugin = class {
3880
3876
  * Ingest documents into the vector database.
3881
3877
  */
3882
3878
  async ingest(documents, namespace) {
3879
+ await this.validationPromise;
3883
3880
  return this.pipeline.ingest(documents, namespace);
3884
3881
  }
3885
3882
  };
@@ -3965,33 +3962,43 @@ function createStreamHandler(config) {
3965
3962
  const plugin = new VectorPlugin(config);
3966
3963
  return async function POST(req) {
3967
3964
  try {
3968
- const { message, history = [], namespace } = await req.json();
3965
+ const body = await req.json();
3966
+ const { message, history = [], namespace } = body;
3969
3967
  const encoder = new TextEncoder();
3970
3968
  const stream = new ReadableStream({
3971
3969
  async start(controller) {
3972
- const pipelineStream = plugin.chatStream(message, history, namespace);
3973
3970
  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(`
3971
+ const pipelineStream = plugin.chatStream(message, history, namespace);
3972
+ try {
3973
+ for (var iter = __forAwait(pipelineStream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
3974
+ const chunk = temp.value;
3975
+ if (typeof chunk === "string") {
3976
+ controller.enqueue(encoder.encode(chunk));
3977
+ } else {
3978
+ controller.enqueue(encoder.encode(`
3980
3979
 
3981
3980
  __METADATA__${JSON.stringify(chunk)}`));
3981
+ }
3982
3982
  }
3983
- }
3984
- } catch (temp) {
3985
- error = [temp];
3986
- } finally {
3987
- try {
3988
- more && (temp = iter.return) && await temp.call(iter);
3983
+ } catch (temp) {
3984
+ error = [temp];
3989
3985
  } finally {
3990
- if (error)
3991
- throw error[0];
3986
+ try {
3987
+ more && (temp = iter.return) && await temp.call(iter);
3988
+ } finally {
3989
+ if (error)
3990
+ throw error[0];
3991
+ }
3992
3992
  }
3993
+ controller.close();
3994
+ } catch (streamError) {
3995
+ console.error("[createStreamHandler] Stream processing error:", streamError);
3996
+ const errorMessage = streamError instanceof Error ? streamError.message : String(streamError);
3997
+ controller.enqueue(encoder.encode(`
3998
+
3999
+ __ERROR__${JSON.stringify({ error: errorMessage })}`));
4000
+ controller.close();
3993
4001
  }
3994
- controller.close();
3995
4002
  }
3996
4003
  });
3997
4004
  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-3JR3SAWX.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";
@@ -2936,26 +2936,39 @@ var EntityExtractor = class {
2936
2936
  async extract(text) {
2937
2937
  const prompt = `
2938
2938
  Extract entities and relationships from the following text.
2939
- Format the output as JSON with two keys: "nodes" (array of {id, label, properties}) and "edges" (array of {source, target, type, properties}).
2940
- Use the same ID for the same entity.
2939
+ Format the output as a JSON object with exactly two keys: "nodes" and "edges".
2941
2940
 
2942
- IMPORTANT: Ensure all property values are valid JSON types (strings, numbers, booleans, or null).
2943
- DO NOT include mathematical expressions like "4.5/5" as numbers; use strings instead.
2941
+ Nodes: {id, label, properties}
2942
+ Edges: {source, target, type, properties}
2944
2943
 
2945
- Text:
2946
- "${text}"
2944
+ IMPORTANT:
2945
+ - Ensure all property values are simple JSON types (string, number, boolean).
2946
+ - Do not use mathematical expressions or complex nested objects in properties.
2947
+ - If no entities are found, return empty arrays.
2948
+ - RESPOND ONLY WITH THE JSON OBJECT.
2947
2949
 
2948
- Output JSON:
2950
+ Text to extract from:
2951
+ "${text}"
2949
2952
  `;
2950
2953
  const response = await this.llm.chat([
2951
- { role: "system", content: "You are an expert at knowledge graph extraction. Respond only with valid JSON." },
2954
+ { role: "system", content: "You are a precise knowledge graph extraction engine. You always output valid JSON and nothing else." },
2952
2955
  { role: "user", content: prompt }
2953
- ], "");
2956
+ ], "", { maxTokens: 2048, temperature: 0.1 });
2954
2957
  try {
2955
- const cleanJson = response.replace(/```json\n?|\n?```/g, "").trim();
2958
+ const jsonMatch = response.match(/\{[\s\S]*\}/);
2959
+ const cleanJson = jsonMatch ? jsonMatch[0] : response.trim();
2956
2960
  return JSON.parse(cleanJson);
2957
2961
  } catch (e) {
2958
- console.warn("[EntityExtractor] Failed to parse LLM response as JSON:", response);
2962
+ console.warn(`[EntityExtractor] Failed to parse LLM response. Length: ${response.length} chars.`);
2963
+ if (response.trim().endsWith("}") === false && response.includes('"nodes"')) {
2964
+ try {
2965
+ const partialResponse = response.trim() + (response.includes("[") ? "]}" : "}");
2966
+ const jsonMatch = partialResponse.match(/\{[\s\S]*\}/);
2967
+ if (jsonMatch) return JSON.parse(jsonMatch[0]);
2968
+ } catch (e2) {
2969
+ }
2970
+ }
2971
+ console.warn("[EntityExtractor] Full failing response:", response.substring(0, 200) + "...");
2959
2972
  return { nodes: [], edges: [] };
2960
2973
  }
2961
2974
  }
@@ -3522,6 +3535,7 @@ var Pipeline = class {
3522
3535
  async initialize() {
3523
3536
  var _a;
3524
3537
  if (this.initialised) return;
3538
+ console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
3525
3539
  this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
3526
3540
  const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
3527
3541
  this.config.llm,
@@ -3906,29 +3920,9 @@ var ProviderHealthCheck = class {
3906
3920
 
3907
3921
  // src/core/VectorPlugin.ts
3908
3922
  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
3923
  constructor(hostConfig) {
3930
3924
  this.config = ConfigResolver.resolve(hostConfig);
3931
- ConfigValidator.validateAndThrow(this.config);
3925
+ this.validationPromise = ConfigValidator.validateAndThrow(this.config);
3932
3926
  this.pipeline = new Pipeline(this.config);
3933
3927
  }
3934
3928
  /**
@@ -3954,6 +3948,7 @@ var VectorPlugin = class {
3954
3948
  * Run a chat query.
3955
3949
  */
3956
3950
  async chat(message, history = [], namespace) {
3951
+ await this.validationPromise;
3957
3952
  return this.pipeline.ask(message, history, namespace);
3958
3953
  }
3959
3954
  /**
@@ -3961,6 +3956,7 @@ var VectorPlugin = class {
3961
3956
  */
3962
3957
  chatStream(_0) {
3963
3958
  return __asyncGenerator(this, arguments, function* (message, history = [], namespace) {
3959
+ yield new __await(this.validationPromise);
3964
3960
  yield* __yieldStar(this.pipeline.askStream(message, history, namespace));
3965
3961
  });
3966
3962
  }
@@ -3968,6 +3964,7 @@ var VectorPlugin = class {
3968
3964
  * Ingest documents into the vector database.
3969
3965
  */
3970
3966
  async ingest(documents, namespace) {
3967
+ await this.validationPromise;
3971
3968
  return this.pipeline.ingest(documents, namespace);
3972
3969
  }
3973
3970
  };
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-3JR3SAWX.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.3",
4
4
  "description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
5
5
  "author": "Abhinav Alkuchi",
6
6
  "license": "MIT",
@@ -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';
@@ -17,29 +17,46 @@ export class EntityExtractor {
17
17
  async extract(text: string): Promise<{ nodes: GraphNode[]; edges: Edge[] }> {
18
18
  const prompt = `
19
19
  Extract entities and relationships from the following text.
20
- Format the output as JSON with two keys: "nodes" (array of {id, label, properties}) and "edges" (array of {source, target, type, properties}).
21
- Use the same ID for the same entity.
20
+ Format the output as a JSON object with exactly two keys: "nodes" and "edges".
22
21
 
23
- IMPORTANT: Ensure all property values are valid JSON types (strings, numbers, booleans, or null).
24
- DO NOT include mathematical expressions like "4.5/5" as numbers; use strings instead.
22
+ Nodes: {id, label, properties}
23
+ Edges: {source, target, type, properties}
25
24
 
26
- Text:
27
- "${text}"
25
+ IMPORTANT:
26
+ - Ensure all property values are simple JSON types (string, number, boolean).
27
+ - Do not use mathematical expressions or complex nested objects in properties.
28
+ - If no entities are found, return empty arrays.
29
+ - RESPOND ONLY WITH THE JSON OBJECT.
28
30
 
29
- Output JSON:
31
+ Text to extract from:
32
+ "${text}"
30
33
  `;
31
34
 
32
35
  const response = await this.llm.chat([
33
- { role: 'system', content: 'You are an expert at knowledge graph extraction. Respond only with valid JSON.' },
36
+ { role: 'system', content: 'You are a precise knowledge graph extraction engine. You always output valid JSON and nothing else.' },
34
37
  { role: 'user', content: prompt }
35
- ], '');
38
+ ], '', { maxTokens: 2048, temperature: 0.1 });
36
39
 
37
40
  try {
38
- // Clean up LLM response (sometimes it adds markdown blocks)
39
- const cleanJson = response.replace(/```json\n?|\n?```/g, '').trim();
41
+ // 1. Try to find the JSON block using a regex
42
+ const jsonMatch = response.match(/\{[\s\S]*\}/);
43
+ const cleanJson = jsonMatch ? jsonMatch[0] : response.trim();
44
+
40
45
  return JSON.parse(cleanJson);
41
46
  } catch {
42
- console.warn('[EntityExtractor] Failed to parse LLM response as JSON:', response);
47
+ console.warn(`[EntityExtractor] Failed to parse LLM response. Length: ${response.length} chars.`);
48
+
49
+ // Attempt very basic "fix" if it looks like it was truncated
50
+ if (response.trim().endsWith('}') === false && response.includes('"nodes"')) {
51
+ try {
52
+ // This is a last-resort attempt to see if we can get anything out of a truncated response
53
+ const partialResponse = response.trim() + (response.includes('[') ? ']}' : '}');
54
+ const jsonMatch = partialResponse.match(/\{[\s\S]*\}/);
55
+ if (jsonMatch) return JSON.parse(jsonMatch[0]);
56
+ } catch { /* ignore */ }
57
+ }
58
+
59
+ console.warn('[EntityExtractor] Full failing response:', response.substring(0, 200) + '...');
43
60
  return { nodes: [], edges: [] };
44
61
  }
45
62
  }