@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.
- package/dist/{MongoDBProvider-Z6ALOVDN.mjs → MongoDBProvider-BO2Y5DRR.mjs} +1 -1
- package/dist/{chunk-5W2YWFT3.mjs → chunk-3JR3SAWX.mjs} +57 -50
- package/dist/{chunk-CD6TSNL4.mjs → chunk-73I6VWU3.mjs} +1 -1
- package/dist/handlers/index.js +57 -50
- package/dist/handlers/index.mjs +1 -1
- package/dist/index.js +77 -22
- package/dist/index.mjs +77 -22
- package/dist/server.d.mts +1 -20
- package/dist/server.d.ts +1 -20
- package/dist/server.js +30 -33
- package/dist/server.mjs +2 -2
- package/package.json +1 -1
- package/src/components/ChatWindow.tsx +4 -3
- package/src/components/MessageBubble.tsx +5 -2
- package/src/core/Pipeline.ts +1 -0
- package/src/core/VectorPlugin.ts +6 -22
- package/src/handlers/index.ts +18 -10
- package/src/hooks/useRagChat.ts +79 -15
- package/src/providers/vectordb/MongoDBProvider.ts +1 -1
- package/src/rag/EntityExtractor.ts +29 -12
|
@@ -1088,7 +1088,7 @@ var ProviderRegistry = class {
|
|
|
1088
1088
|
return PostgreSQLProvider;
|
|
1089
1089
|
}
|
|
1090
1090
|
case "mongodb": {
|
|
1091
|
-
const { MongoDBProvider } = await import("./MongoDBProvider-
|
|
1091
|
+
const { MongoDBProvider } = await import("./MongoDBProvider-BO2Y5DRR.mjs");
|
|
1092
1092
|
return MongoDBProvider;
|
|
1093
1093
|
}
|
|
1094
1094
|
case "milvus": {
|
|
@@ -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"
|
|
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
|
-
|
|
1398
|
-
|
|
1396
|
+
Nodes: {id, label, properties}
|
|
1397
|
+
Edges: {source, target, type, properties}
|
|
1399
1398
|
|
|
1400
|
-
|
|
1401
|
-
|
|
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
|
-
|
|
1405
|
+
Text to extract from:
|
|
1406
|
+
"${text}"
|
|
1404
1407
|
`;
|
|
1405
1408
|
const response = await this.llm.chat([
|
|
1406
|
-
{ role: "system", content: "You are
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2534
|
-
|
|
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";
|
package/dist/handlers/index.js
CHANGED
|
@@ -546,7 +546,7 @@ var init_MongoDBProvider = __esm({
|
|
|
546
546
|
if (!opts.uri || !opts.database || !opts.collection) {
|
|
547
547
|
throw new Error("[MongoDBProvider] options uri, database, and collection are required.");
|
|
548
548
|
}
|
|
549
|
-
this.client = new import_mongodb.MongoClient(opts.uri);
|
|
549
|
+
this.client = new import_mongodb.MongoClient(opts.uri, { serverSelectionTimeoutMS: 5e3 });
|
|
550
550
|
this.dbName = opts.database;
|
|
551
551
|
this.collectionName = opts.collection;
|
|
552
552
|
this.embeddingKey = opts.embeddingKey || "embedding";
|
|
@@ -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"
|
|
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
|
-
|
|
2861
|
-
|
|
2859
|
+
Nodes: {id, label, properties}
|
|
2860
|
+
Edges: {source, target, type, properties}
|
|
2862
2861
|
|
|
2863
|
-
|
|
2864
|
-
|
|
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
|
-
|
|
2868
|
+
Text to extract from:
|
|
2869
|
+
"${text}"
|
|
2867
2870
|
`;
|
|
2868
2871
|
const response = await this.llm.chat([
|
|
2869
|
-
{ role: "system", content: "You are
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3991
|
-
|
|
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, {
|
package/dist/handlers/index.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -118,7 +118,7 @@ function MessageBubble({
|
|
|
118
118
|
className: `relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser ? "text-white rounded-tr-sm" : "bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm"}`,
|
|
119
119
|
style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
120
120
|
},
|
|
121
|
-
isStreaming ? /* @__PURE__ */ import_react2.default.createElement("span", { className: "flex items-center gap-1" }, /* @__PURE__ */ import_react2.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ import_react2.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ import_react2.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ import_react2.default.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ import_react2.default.createElement(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default] }, message.content))
|
|
121
|
+
isStreaming && !message.content ? /* @__PURE__ */ import_react2.default.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ import_react2.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ import_react2.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ import_react2.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ import_react2.default.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ import_react2.default.createElement(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default] }, message.content), isStreaming && message.content && /* @__PURE__ */ import_react2.default.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
|
|
122
122
|
), !isUser && sources && sources.length > 0 && /* @__PURE__ */ import_react2.default.createElement("div", { className: "w-full" }, /* @__PURE__ */ import_react2.default.createElement(
|
|
123
123
|
"button",
|
|
124
124
|
{
|
|
@@ -186,7 +186,6 @@ function useConfig() {
|
|
|
186
186
|
|
|
187
187
|
// src/hooks/useRagChat.ts
|
|
188
188
|
var import_react4 = require("react");
|
|
189
|
-
var import_axios = __toESM(require("axios"));
|
|
190
189
|
|
|
191
190
|
// src/hooks/useStoredMessages.ts
|
|
192
191
|
var React4 = __toESM(require("react"));
|
|
@@ -240,7 +239,6 @@ function useRagChat(projectId, options = {}) {
|
|
|
240
239
|
}, [messages]);
|
|
241
240
|
const sendMessage = (0, import_react4.useCallback)(
|
|
242
241
|
async (text, options2) => {
|
|
243
|
-
var _a, _b, _c;
|
|
244
242
|
const trimmed = text.trim();
|
|
245
243
|
if (!trimmed || isLoading) return;
|
|
246
244
|
lastInputRef.current = trimmed;
|
|
@@ -257,22 +255,77 @@ function useRagChat(projectId, options = {}) {
|
|
|
257
255
|
setIsLoading(true);
|
|
258
256
|
try {
|
|
259
257
|
const history = messagesRef.current.map(({ role, content }) => ({ role, content }));
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
258
|
+
const response = await fetch(apiUrl, {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: { "Content-Type": "application/json" },
|
|
261
|
+
body: JSON.stringify({
|
|
262
|
+
message: trimmed,
|
|
263
|
+
history,
|
|
264
|
+
namespace: namespace != null ? namespace : projectId
|
|
265
|
+
})
|
|
264
266
|
});
|
|
265
|
-
|
|
266
|
-
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
const errorData = await response.json().catch(() => ({}));
|
|
269
|
+
throw new Error(errorData.error || `Server returned ${response.status}`);
|
|
270
|
+
}
|
|
271
|
+
if (!response.body) {
|
|
272
|
+
throw new Error("No response body received from server");
|
|
273
|
+
}
|
|
274
|
+
const reader = response.body.getReader();
|
|
275
|
+
const decoder = new TextDecoder();
|
|
276
|
+
let assistantContent = "";
|
|
277
|
+
let sources = [];
|
|
278
|
+
const assistantMessageId = `assistant_${Date.now()}`;
|
|
279
|
+
setMessages((prev) => [
|
|
280
|
+
...prev,
|
|
281
|
+
{
|
|
282
|
+
id: assistantMessageId,
|
|
283
|
+
role: "assistant",
|
|
284
|
+
content: "",
|
|
285
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
286
|
+
}
|
|
287
|
+
]);
|
|
288
|
+
while (true) {
|
|
289
|
+
const { done, value } = await reader.read();
|
|
290
|
+
if (done) break;
|
|
291
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
292
|
+
if (chunk.includes("__METADATA__")) {
|
|
293
|
+
const [text2, metadataStr] = chunk.split("__METADATA__");
|
|
294
|
+
if (text2) assistantContent += text2;
|
|
295
|
+
try {
|
|
296
|
+
const meta = JSON.parse(metadataStr);
|
|
297
|
+
sources = meta.sources || [];
|
|
298
|
+
} catch (e) {
|
|
299
|
+
console.warn("[useRagChat] Failed to parse metadata");
|
|
300
|
+
}
|
|
301
|
+
} else if (chunk.includes("__ERROR__")) {
|
|
302
|
+
const [text2, errorStr] = chunk.split("__ERROR__");
|
|
303
|
+
if (text2) assistantContent += text2;
|
|
304
|
+
try {
|
|
305
|
+
const err = JSON.parse(errorStr);
|
|
306
|
+
setError(err.error || "Stream error");
|
|
307
|
+
} catch (e) {
|
|
308
|
+
setError("An error occurred during streaming");
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
assistantContent += chunk;
|
|
312
|
+
}
|
|
313
|
+
setMessages(
|
|
314
|
+
(prev) => prev.map(
|
|
315
|
+
(msg) => msg.id === assistantMessageId ? __spreadProps(__spreadValues({}, msg), { content: assistantContent, sources: sources.length > 0 ? sources : msg.sources }) : msg
|
|
316
|
+
)
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
const finalAssistantMessage = {
|
|
320
|
+
id: assistantMessageId,
|
|
267
321
|
role: "assistant",
|
|
268
|
-
content:
|
|
269
|
-
sources:
|
|
322
|
+
content: assistantContent,
|
|
323
|
+
sources: sources.length > 0 ? sources : void 0,
|
|
270
324
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
271
325
|
};
|
|
272
|
-
|
|
273
|
-
onReply == null ? void 0 : onReply(assistantMessage);
|
|
326
|
+
onReply == null ? void 0 : onReply(finalAssistantMessage);
|
|
274
327
|
} catch (err) {
|
|
275
|
-
const msg =
|
|
328
|
+
const msg = err instanceof Error ? err.message : "Something went wrong. Please try again.";
|
|
276
329
|
setError(msg);
|
|
277
330
|
onError == null ? void 0 : onError(msg);
|
|
278
331
|
} finally {
|
|
@@ -324,6 +377,7 @@ var BORDER_RADIUS_MAP = {
|
|
|
324
377
|
|
|
325
378
|
// src/components/ChatWindow.tsx
|
|
326
379
|
function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
380
|
+
var _a;
|
|
327
381
|
const { ui, projectId } = useConfig();
|
|
328
382
|
const [input, setInput] = (0, import_react6.useState)("");
|
|
329
383
|
const [mounted, setMounted] = (0, import_react6.useState)(false);
|
|
@@ -336,9 +390,9 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
|
336
390
|
setMounted(true);
|
|
337
391
|
}, []);
|
|
338
392
|
(0, import_react6.useEffect)(() => {
|
|
339
|
-
var
|
|
393
|
+
var _a2;
|
|
340
394
|
if (messages.length > 0 || isLoading) {
|
|
341
|
-
(
|
|
395
|
+
(_a2 = bottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
|
|
342
396
|
}
|
|
343
397
|
}, [messages, isLoading]);
|
|
344
398
|
const sendMessage = (0, import_react6.useCallback)(async () => {
|
|
@@ -347,8 +401,8 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
|
347
401
|
setInput("");
|
|
348
402
|
await send(text);
|
|
349
403
|
window.setTimeout(() => {
|
|
350
|
-
var
|
|
351
|
-
return (
|
|
404
|
+
var _a2;
|
|
405
|
+
return (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
352
406
|
}, 50);
|
|
353
407
|
}, [input, isLoading, send]);
|
|
354
408
|
const handleKeyDown = (e) => {
|
|
@@ -419,25 +473,26 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
|
419
473
|
{
|
|
420
474
|
key: suggestion,
|
|
421
475
|
onClick: () => {
|
|
422
|
-
var
|
|
476
|
+
var _a2;
|
|
423
477
|
setInput(suggestion);
|
|
424
|
-
(
|
|
478
|
+
(_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
425
479
|
},
|
|
426
480
|
className: "px-3 py-1.5 rounded-full text-xs border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/50 hover:text-slate-900 dark:hover:text-white/80 hover:border-slate-300 dark:hover:border-white/20 hover:bg-slate-100 dark:hover:bg-white/5 transition-all"
|
|
427
481
|
},
|
|
428
482
|
suggestion
|
|
429
483
|
)
|
|
430
484
|
)))
|
|
431
|
-
) : messages.map((msg) => /* @__PURE__ */ import_react6.default.createElement(
|
|
485
|
+
) : messages.map((msg, index) => /* @__PURE__ */ import_react6.default.createElement(
|
|
432
486
|
MessageBubble,
|
|
433
487
|
{
|
|
434
488
|
key: msg.id,
|
|
435
489
|
message: msg,
|
|
436
490
|
sources: msg.sources,
|
|
491
|
+
isStreaming: isLoading && index === messages.length - 1 && msg.role === "assistant",
|
|
437
492
|
primaryColor: ui.primaryColor,
|
|
438
493
|
accentColor: ui.accentColor
|
|
439
494
|
}
|
|
440
|
-
)), isLoading && /* @__PURE__ */ import_react6.default.createElement(
|
|
495
|
+
)), isLoading && ((_a = messages[messages.length - 1]) == null ? void 0 : _a.role) !== "assistant" && /* @__PURE__ */ import_react6.default.createElement(
|
|
441
496
|
MessageBubble,
|
|
442
497
|
{
|
|
443
498
|
message: { role: "assistant", content: "" },
|
package/dist/index.mjs
CHANGED
|
@@ -73,7 +73,7 @@ function MessageBubble({
|
|
|
73
73
|
className: `relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser ? "text-white rounded-tr-sm" : "bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm"}`,
|
|
74
74
|
style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
75
75
|
},
|
|
76
|
-
isStreaming ? /* @__PURE__ */ React2.createElement("span", { className: "flex items-center gap-1" }, /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ React2.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ React2.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm] }, message.content))
|
|
76
|
+
isStreaming && !message.content ? /* @__PURE__ */ React2.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ React2.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ React2.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm] }, message.content), isStreaming && message.content && /* @__PURE__ */ React2.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
|
|
77
77
|
), !isUser && sources && sources.length > 0 && /* @__PURE__ */ React2.createElement("div", { className: "w-full" }, /* @__PURE__ */ React2.createElement(
|
|
78
78
|
"button",
|
|
79
79
|
{
|
|
@@ -125,7 +125,6 @@ function useConfig() {
|
|
|
125
125
|
|
|
126
126
|
// src/hooks/useRagChat.ts
|
|
127
127
|
import { useState as useState2, useCallback, useRef, useEffect as useEffect2 } from "react";
|
|
128
|
-
import axios from "axios";
|
|
129
128
|
|
|
130
129
|
// src/hooks/useStoredMessages.ts
|
|
131
130
|
import * as React4 from "react";
|
|
@@ -179,7 +178,6 @@ function useRagChat(projectId, options = {}) {
|
|
|
179
178
|
}, [messages]);
|
|
180
179
|
const sendMessage = useCallback(
|
|
181
180
|
async (text, options2) => {
|
|
182
|
-
var _a, _b, _c;
|
|
183
181
|
const trimmed = text.trim();
|
|
184
182
|
if (!trimmed || isLoading) return;
|
|
185
183
|
lastInputRef.current = trimmed;
|
|
@@ -196,22 +194,77 @@ function useRagChat(projectId, options = {}) {
|
|
|
196
194
|
setIsLoading(true);
|
|
197
195
|
try {
|
|
198
196
|
const history = messagesRef.current.map(({ role, content }) => ({ role, content }));
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
197
|
+
const response = await fetch(apiUrl, {
|
|
198
|
+
method: "POST",
|
|
199
|
+
headers: { "Content-Type": "application/json" },
|
|
200
|
+
body: JSON.stringify({
|
|
201
|
+
message: trimmed,
|
|
202
|
+
history,
|
|
203
|
+
namespace: namespace != null ? namespace : projectId
|
|
204
|
+
})
|
|
203
205
|
});
|
|
204
|
-
|
|
205
|
-
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
const errorData = await response.json().catch(() => ({}));
|
|
208
|
+
throw new Error(errorData.error || `Server returned ${response.status}`);
|
|
209
|
+
}
|
|
210
|
+
if (!response.body) {
|
|
211
|
+
throw new Error("No response body received from server");
|
|
212
|
+
}
|
|
213
|
+
const reader = response.body.getReader();
|
|
214
|
+
const decoder = new TextDecoder();
|
|
215
|
+
let assistantContent = "";
|
|
216
|
+
let sources = [];
|
|
217
|
+
const assistantMessageId = `assistant_${Date.now()}`;
|
|
218
|
+
setMessages((prev) => [
|
|
219
|
+
...prev,
|
|
220
|
+
{
|
|
221
|
+
id: assistantMessageId,
|
|
222
|
+
role: "assistant",
|
|
223
|
+
content: "",
|
|
224
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
225
|
+
}
|
|
226
|
+
]);
|
|
227
|
+
while (true) {
|
|
228
|
+
const { done, value } = await reader.read();
|
|
229
|
+
if (done) break;
|
|
230
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
231
|
+
if (chunk.includes("__METADATA__")) {
|
|
232
|
+
const [text2, metadataStr] = chunk.split("__METADATA__");
|
|
233
|
+
if (text2) assistantContent += text2;
|
|
234
|
+
try {
|
|
235
|
+
const meta = JSON.parse(metadataStr);
|
|
236
|
+
sources = meta.sources || [];
|
|
237
|
+
} catch (e) {
|
|
238
|
+
console.warn("[useRagChat] Failed to parse metadata");
|
|
239
|
+
}
|
|
240
|
+
} else if (chunk.includes("__ERROR__")) {
|
|
241
|
+
const [text2, errorStr] = chunk.split("__ERROR__");
|
|
242
|
+
if (text2) assistantContent += text2;
|
|
243
|
+
try {
|
|
244
|
+
const err = JSON.parse(errorStr);
|
|
245
|
+
setError(err.error || "Stream error");
|
|
246
|
+
} catch (e) {
|
|
247
|
+
setError("An error occurred during streaming");
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
assistantContent += chunk;
|
|
251
|
+
}
|
|
252
|
+
setMessages(
|
|
253
|
+
(prev) => prev.map(
|
|
254
|
+
(msg) => msg.id === assistantMessageId ? __spreadProps(__spreadValues({}, msg), { content: assistantContent, sources: sources.length > 0 ? sources : msg.sources }) : msg
|
|
255
|
+
)
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
const finalAssistantMessage = {
|
|
259
|
+
id: assistantMessageId,
|
|
206
260
|
role: "assistant",
|
|
207
|
-
content:
|
|
208
|
-
sources:
|
|
261
|
+
content: assistantContent,
|
|
262
|
+
sources: sources.length > 0 ? sources : void 0,
|
|
209
263
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
210
264
|
};
|
|
211
|
-
|
|
212
|
-
onReply == null ? void 0 : onReply(assistantMessage);
|
|
265
|
+
onReply == null ? void 0 : onReply(finalAssistantMessage);
|
|
213
266
|
} catch (err) {
|
|
214
|
-
const msg =
|
|
267
|
+
const msg = err instanceof Error ? err.message : "Something went wrong. Please try again.";
|
|
215
268
|
setError(msg);
|
|
216
269
|
onError == null ? void 0 : onError(msg);
|
|
217
270
|
} finally {
|
|
@@ -281,6 +334,7 @@ var BORDER_RADIUS_MAP = {
|
|
|
281
334
|
|
|
282
335
|
// src/components/ChatWindow.tsx
|
|
283
336
|
function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
337
|
+
var _a;
|
|
284
338
|
const { ui, projectId } = useConfig();
|
|
285
339
|
const [input, setInput] = useState3("");
|
|
286
340
|
const [mounted, setMounted] = useState3(false);
|
|
@@ -293,9 +347,9 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
|
293
347
|
setMounted(true);
|
|
294
348
|
}, []);
|
|
295
349
|
useEffect3(() => {
|
|
296
|
-
var
|
|
350
|
+
var _a2;
|
|
297
351
|
if (messages.length > 0 || isLoading) {
|
|
298
|
-
(
|
|
352
|
+
(_a2 = bottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
|
|
299
353
|
}
|
|
300
354
|
}, [messages, isLoading]);
|
|
301
355
|
const sendMessage = useCallback2(async () => {
|
|
@@ -304,8 +358,8 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
|
304
358
|
setInput("");
|
|
305
359
|
await send(text);
|
|
306
360
|
window.setTimeout(() => {
|
|
307
|
-
var
|
|
308
|
-
return (
|
|
361
|
+
var _a2;
|
|
362
|
+
return (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
309
363
|
}, 50);
|
|
310
364
|
}, [input, isLoading, send]);
|
|
311
365
|
const handleKeyDown = (e) => {
|
|
@@ -376,25 +430,26 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
|
|
|
376
430
|
{
|
|
377
431
|
key: suggestion,
|
|
378
432
|
onClick: () => {
|
|
379
|
-
var
|
|
433
|
+
var _a2;
|
|
380
434
|
setInput(suggestion);
|
|
381
|
-
(
|
|
435
|
+
(_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
382
436
|
},
|
|
383
437
|
className: "px-3 py-1.5 rounded-full text-xs border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/50 hover:text-slate-900 dark:hover:text-white/80 hover:border-slate-300 dark:hover:border-white/20 hover:bg-slate-100 dark:hover:bg-white/5 transition-all"
|
|
384
438
|
},
|
|
385
439
|
suggestion
|
|
386
440
|
)
|
|
387
441
|
)))
|
|
388
|
-
) : messages.map((msg) => /* @__PURE__ */ React6.createElement(
|
|
442
|
+
) : messages.map((msg, index) => /* @__PURE__ */ React6.createElement(
|
|
389
443
|
MessageBubble,
|
|
390
444
|
{
|
|
391
445
|
key: msg.id,
|
|
392
446
|
message: msg,
|
|
393
447
|
sources: msg.sources,
|
|
448
|
+
isStreaming: isLoading && index === messages.length - 1 && msg.role === "assistant",
|
|
394
449
|
primaryColor: ui.primaryColor,
|
|
395
450
|
accentColor: ui.accentColor
|
|
396
451
|
}
|
|
397
|
-
)), isLoading && /* @__PURE__ */ React6.createElement(
|
|
452
|
+
)), isLoading && ((_a = messages[messages.length - 1]) == null ? void 0 : _a.role) !== "assistant" && /* @__PURE__ */ React6.createElement(
|
|
398
453
|
MessageBubble,
|
|
399
454
|
{
|
|
400
455
|
message: { role: "assistant", content: "" },
|
package/dist/server.d.mts
CHANGED
|
@@ -111,26 +111,7 @@ declare class ProviderHealthCheck {
|
|
|
111
111
|
declare class VectorPlugin {
|
|
112
112
|
private config;
|
|
113
113
|
private pipeline;
|
|
114
|
-
|
|
115
|
-
* Initializes the plugin with the host configuration.
|
|
116
|
-
* @param hostConfig - Configuration object passed from the host application.
|
|
117
|
-
* @throws Error if configuration is invalid or providers are unhealthy
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* const plugin = new VectorPlugin({
|
|
121
|
-
* projectId: 'my-app',
|
|
122
|
-
* vectorDb: {
|
|
123
|
-
* provider: 'pinecone',
|
|
124
|
-
* indexName: 'my-index',
|
|
125
|
-
* options: { apiKey: process.env.PINECONE_API_KEY }
|
|
126
|
-
* },
|
|
127
|
-
* llm: {
|
|
128
|
-
* provider: 'openai',
|
|
129
|
-
* model: 'gpt-4o',
|
|
130
|
-
* apiKey: process.env.OPENAI_API_KEY
|
|
131
|
-
* }
|
|
132
|
-
* });
|
|
133
|
-
*/
|
|
114
|
+
private validationPromise;
|
|
134
115
|
constructor(hostConfig?: Partial<RagConfig>);
|
|
135
116
|
/**
|
|
136
117
|
* Get the current resolved configuration.
|
package/dist/server.d.ts
CHANGED
|
@@ -111,26 +111,7 @@ declare class ProviderHealthCheck {
|
|
|
111
111
|
declare class VectorPlugin {
|
|
112
112
|
private config;
|
|
113
113
|
private pipeline;
|
|
114
|
-
|
|
115
|
-
* Initializes the plugin with the host configuration.
|
|
116
|
-
* @param hostConfig - Configuration object passed from the host application.
|
|
117
|
-
* @throws Error if configuration is invalid or providers are unhealthy
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* const plugin = new VectorPlugin({
|
|
121
|
-
* projectId: 'my-app',
|
|
122
|
-
* vectorDb: {
|
|
123
|
-
* provider: 'pinecone',
|
|
124
|
-
* indexName: 'my-index',
|
|
125
|
-
* options: { apiKey: process.env.PINECONE_API_KEY }
|
|
126
|
-
* },
|
|
127
|
-
* llm: {
|
|
128
|
-
* provider: 'openai',
|
|
129
|
-
* model: 'gpt-4o',
|
|
130
|
-
* apiKey: process.env.OPENAI_API_KEY
|
|
131
|
-
* }
|
|
132
|
-
* });
|
|
133
|
-
*/
|
|
114
|
+
private validationPromise;
|
|
134
115
|
constructor(hostConfig?: Partial<RagConfig>);
|
|
135
116
|
/**
|
|
136
117
|
* Get the current resolved configuration.
|
package/dist/server.js
CHANGED
|
@@ -558,7 +558,7 @@ var init_MongoDBProvider = __esm({
|
|
|
558
558
|
if (!opts.uri || !opts.database || !opts.collection) {
|
|
559
559
|
throw new Error("[MongoDBProvider] options uri, database, and collection are required.");
|
|
560
560
|
}
|
|
561
|
-
this.client = new import_mongodb.MongoClient(opts.uri);
|
|
561
|
+
this.client = new import_mongodb.MongoClient(opts.uri, { serverSelectionTimeoutMS: 5e3 });
|
|
562
562
|
this.dbName = opts.database;
|
|
563
563
|
this.collectionName = opts.collection;
|
|
564
564
|
this.embeddingKey = opts.embeddingKey || "embedding";
|
|
@@ -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"
|
|
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
|
-
|
|
2943
|
-
|
|
2941
|
+
Nodes: {id, label, properties}
|
|
2942
|
+
Edges: {source, target, type, properties}
|
|
2944
2943
|
|
|
2945
|
-
|
|
2946
|
-
|
|
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
|
-
|
|
2950
|
+
Text to extract from:
|
|
2951
|
+
"${text}"
|
|
2949
2952
|
`;
|
|
2950
2953
|
const response = await this.llm.chat([
|
|
2951
|
-
{ role: "system", content: "You are
|
|
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
|
|
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(
|
|
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-
|
|
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-
|
|
47
|
+
} from "./chunk-73I6VWU3.mjs";
|
|
48
48
|
import {
|
|
49
49
|
MilvusProvider
|
|
50
50
|
} from "./chunk-U55XRW3U.mjs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.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>
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -50,6 +50,7 @@ export class Pipeline {
|
|
|
50
50
|
async initialize(): Promise<void> {
|
|
51
51
|
if (this.initialised) return;
|
|
52
52
|
|
|
53
|
+
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
53
54
|
// Resolve vector DB provider
|
|
54
55
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
55
56
|
|
package/src/core/VectorPlugin.ts
CHANGED
|
@@ -20,32 +20,13 @@ import { ChatMessage } from '../llm/ILLMProvider';
|
|
|
20
20
|
export class VectorPlugin {
|
|
21
21
|
private config: RagConfig;
|
|
22
22
|
private pipeline: Pipeline;
|
|
23
|
+
private validationPromise: Promise<void>;
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
-
* Initializes the plugin with the host configuration.
|
|
26
|
-
* @param hostConfig - Configuration object passed from the host application.
|
|
27
|
-
* @throws Error if configuration is invalid or providers are unhealthy
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* const plugin = new VectorPlugin({
|
|
31
|
-
* projectId: 'my-app',
|
|
32
|
-
* vectorDb: {
|
|
33
|
-
* provider: 'pinecone',
|
|
34
|
-
* indexName: 'my-index',
|
|
35
|
-
* options: { apiKey: process.env.PINECONE_API_KEY }
|
|
36
|
-
* },
|
|
37
|
-
* llm: {
|
|
38
|
-
* provider: 'openai',
|
|
39
|
-
* model: 'gpt-4o',
|
|
40
|
-
* apiKey: process.env.OPENAI_API_KEY
|
|
41
|
-
* }
|
|
42
|
-
* });
|
|
43
|
-
*/
|
|
44
25
|
constructor(hostConfig?: Partial<RagConfig>) {
|
|
45
26
|
this.config = ConfigResolver.resolve(hostConfig);
|
|
46
27
|
|
|
47
|
-
//
|
|
48
|
-
ConfigValidator.validateAndThrow(this.config);
|
|
28
|
+
// Start validation early
|
|
29
|
+
this.validationPromise = ConfigValidator.validateAndThrow(this.config);
|
|
49
30
|
|
|
50
31
|
this.pipeline = new Pipeline(this.config);
|
|
51
32
|
}
|
|
@@ -80,6 +61,7 @@ export class VectorPlugin {
|
|
|
80
61
|
* Run a chat query.
|
|
81
62
|
*/
|
|
82
63
|
async chat(message: string, history: ChatMessage[] = [], namespace?: string): Promise<ChatResponse> {
|
|
64
|
+
await this.validationPromise;
|
|
83
65
|
return this.pipeline.ask(message, history, namespace);
|
|
84
66
|
}
|
|
85
67
|
|
|
@@ -87,6 +69,7 @@ export class VectorPlugin {
|
|
|
87
69
|
* Run a streaming chat query.
|
|
88
70
|
*/
|
|
89
71
|
async *chatStream(message: string, history: ChatMessage[] = [], namespace?: string) {
|
|
72
|
+
await this.validationPromise;
|
|
90
73
|
yield* this.pipeline.askStream(message, history, namespace);
|
|
91
74
|
}
|
|
92
75
|
|
|
@@ -94,6 +77,7 @@ export class VectorPlugin {
|
|
|
94
77
|
* Ingest documents into the vector database.
|
|
95
78
|
*/
|
|
96
79
|
async ingest(documents: IngestDocument[], namespace?: string): Promise<Array<{ docId: string | number; chunksIngested: number }>> {
|
|
80
|
+
await this.validationPromise;
|
|
97
81
|
return this.pipeline.ingest(documents, namespace);
|
|
98
82
|
}
|
|
99
83
|
|
package/src/handlers/index.ts
CHANGED
|
@@ -40,22 +40,30 @@ export function createStreamHandler(config?: Partial<RagConfig>) {
|
|
|
40
40
|
|
|
41
41
|
return async function POST(req: NextRequest) {
|
|
42
42
|
try {
|
|
43
|
-
const
|
|
43
|
+
const body = await req.json();
|
|
44
|
+
const { message, history = [], namespace } = body;
|
|
44
45
|
|
|
45
46
|
const encoder = new TextEncoder();
|
|
46
47
|
const stream = new ReadableStream({
|
|
47
48
|
async start(controller) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
try {
|
|
50
|
+
const pipelineStream = plugin.chatStream(message, history, namespace);
|
|
51
|
+
|
|
52
|
+
for await (const chunk of pipelineStream) {
|
|
53
|
+
if (typeof chunk === 'string') {
|
|
54
|
+
controller.enqueue(encoder.encode(chunk));
|
|
55
|
+
} else {
|
|
56
|
+
// Yield metadata/sources at the end
|
|
57
|
+
controller.enqueue(encoder.encode(`\n\n__METADATA__${JSON.stringify(chunk)}`));
|
|
58
|
+
}
|
|
56
59
|
}
|
|
60
|
+
controller.close();
|
|
61
|
+
} catch (streamError) {
|
|
62
|
+
console.error('[createStreamHandler] Stream processing error:', streamError);
|
|
63
|
+
const errorMessage = streamError instanceof Error ? streamError.message : String(streamError);
|
|
64
|
+
controller.enqueue(encoder.encode(`\n\n__ERROR__${JSON.stringify({ error: errorMessage })}`));
|
|
65
|
+
controller.close();
|
|
57
66
|
}
|
|
58
|
-
controller.close();
|
|
59
67
|
},
|
|
60
68
|
});
|
|
61
69
|
|
package/src/hooks/useRagChat.ts
CHANGED
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
17
|
-
import axios from 'axios';
|
|
18
17
|
import { VectorMatch } from '@/types';
|
|
19
18
|
import { useStoredMessages } from './useStoredMessages';
|
|
20
19
|
|
|
@@ -111,26 +110,91 @@ export function useRagChat(
|
|
|
111
110
|
try {
|
|
112
111
|
const history = messagesRef.current.map(({ role, content }) => ({ role, content }));
|
|
113
112
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
const response = await fetch(apiUrl, {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: { 'Content-Type': 'application/json' },
|
|
116
|
+
body: JSON.stringify({
|
|
117
|
+
message: trimmed,
|
|
118
|
+
history,
|
|
119
|
+
namespace: namespace ?? projectId,
|
|
120
|
+
}),
|
|
118
121
|
});
|
|
119
122
|
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
const errorData = await response.json().catch(() => ({}));
|
|
125
|
+
throw new Error(errorData.error || `Server returned ${response.status}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!response.body) {
|
|
129
|
+
throw new Error('No response body received from server');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const reader = response.body.getReader();
|
|
133
|
+
const decoder = new TextDecoder();
|
|
134
|
+
let assistantContent = '';
|
|
135
|
+
let sources: VectorMatch[] = [];
|
|
136
|
+
|
|
137
|
+
// Add a placeholder assistant message that we will update
|
|
138
|
+
const assistantMessageId = `assistant_${Date.now()}`;
|
|
139
|
+
setMessages((prev) => [
|
|
140
|
+
...prev,
|
|
141
|
+
{
|
|
142
|
+
id: assistantMessageId,
|
|
143
|
+
role: 'assistant',
|
|
144
|
+
content: '',
|
|
145
|
+
createdAt: new Date().toISOString(),
|
|
146
|
+
},
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
while (true) {
|
|
150
|
+
const { done, value } = await reader.read();
|
|
151
|
+
if (done) break;
|
|
152
|
+
|
|
153
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
154
|
+
|
|
155
|
+
// Handle protocol tokens
|
|
156
|
+
if (chunk.includes('__METADATA__')) {
|
|
157
|
+
const [text, metadataStr] = chunk.split('__METADATA__');
|
|
158
|
+
if (text) assistantContent += text;
|
|
159
|
+
try {
|
|
160
|
+
const meta = JSON.parse(metadataStr);
|
|
161
|
+
sources = meta.sources || [];
|
|
162
|
+
} catch {
|
|
163
|
+
console.warn('[useRagChat] Failed to parse metadata');
|
|
164
|
+
}
|
|
165
|
+
} else if (chunk.includes('__ERROR__')) {
|
|
166
|
+
const [text, errorStr] = chunk.split('__ERROR__');
|
|
167
|
+
if (text) assistantContent += text;
|
|
168
|
+
try {
|
|
169
|
+
const err = JSON.parse(errorStr);
|
|
170
|
+
setError(err.error || 'Stream error');
|
|
171
|
+
} catch {
|
|
172
|
+
setError('An error occurred during streaming');
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
assistantContent += chunk;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Update the specific message
|
|
179
|
+
setMessages((prev) =>
|
|
180
|
+
prev.map((msg) =>
|
|
181
|
+
msg.id === assistantMessageId
|
|
182
|
+
? { ...msg, content: assistantContent, sources: sources.length > 0 ? sources : msg.sources }
|
|
183
|
+
: msg
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const finalAssistantMessage: RagMessage = {
|
|
189
|
+
id: assistantMessageId,
|
|
122
190
|
role: 'assistant',
|
|
123
|
-
content:
|
|
124
|
-
sources:
|
|
191
|
+
content: assistantContent,
|
|
192
|
+
sources: sources.length > 0 ? sources : undefined,
|
|
125
193
|
createdAt: new Date().toISOString(),
|
|
126
194
|
};
|
|
127
|
-
|
|
128
|
-
setMessages((prev) => [...prev, assistantMessage]);
|
|
129
|
-
onReply?.(assistantMessage);
|
|
195
|
+
onReply?.(finalAssistantMessage);
|
|
130
196
|
} catch (err) {
|
|
131
|
-
const msg =
|
|
132
|
-
? (err.response?.data?.error ?? err.message)
|
|
133
|
-
: 'Something went wrong. Please try again.';
|
|
197
|
+
const msg = err instanceof Error ? err.message : 'Something went wrong. Please try again.';
|
|
134
198
|
setError(msg);
|
|
135
199
|
onError?.(msg);
|
|
136
200
|
} finally {
|
|
@@ -25,7 +25,7 @@ export class MongoDBProvider extends BaseVectorProvider {
|
|
|
25
25
|
if (!opts.uri || !opts.database || !opts.collection) {
|
|
26
26
|
throw new Error('[MongoDBProvider] options uri, database, and collection are required.');
|
|
27
27
|
}
|
|
28
|
-
this.client = new MongoClient(opts.uri as string);
|
|
28
|
+
this.client = new MongoClient(opts.uri as string, { serverSelectionTimeoutMS: 5000 });
|
|
29
29
|
this.dbName = opts.database as string;
|
|
30
30
|
this.collectionName = opts.collection as string;
|
|
31
31
|
this.embeddingKey = (opts.embeddingKey as string) || 'embedding';
|
|
@@ -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"
|
|
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
|
-
|
|
24
|
-
|
|
22
|
+
Nodes: {id, label, properties}
|
|
23
|
+
Edges: {source, target, type, properties}
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
39
|
-
const
|
|
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(
|
|
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
|
}
|