@retrivora-ai/rag-engine 1.8.0 → 1.8.1

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.
Files changed (46) hide show
  1. package/dist/DocumentChunker-Dh9TvmGG.d.mts +45 -0
  2. package/dist/DocumentChunker-Dh9TvmGG.d.ts +45 -0
  3. package/dist/{index-DPsQodME.d.mts → ILLMProvider-BfRgI1Xh.d.mts} +58 -1
  4. package/dist/{index-DPsQodME.d.ts → ILLMProvider-BfRgI1Xh.d.ts} +58 -1
  5. package/dist/MultiTablePostgresProvider-YY7LPNJK.mjs +8 -0
  6. package/dist/{chunk-PV3MFHWU.mjs → chunk-BFYLQYQU.mjs} +808 -439
  7. package/dist/chunk-R3RGUMHE.mjs +218 -0
  8. package/dist/handlers/index.d.mts +2 -2
  9. package/dist/handlers/index.d.ts +2 -2
  10. package/dist/handlers/index.js +995 -603
  11. package/dist/handlers/index.mjs +1 -1
  12. package/dist/{index-Bb2yEopi.d.mts → index-1Z4GuYBi.d.ts} +7 -1
  13. package/dist/{index-CkbTzj9J.d.ts → index-BV0z5mb6.d.mts} +7 -1
  14. package/dist/index.d.mts +3 -3
  15. package/dist/index.d.ts +3 -3
  16. package/dist/index.js +491 -316
  17. package/dist/index.mjs +411 -217
  18. package/dist/server.d.mts +35 -5
  19. package/dist/server.d.ts +35 -5
  20. package/dist/server.js +1146 -777
  21. package/dist/server.mjs +163 -176
  22. package/package.json +4 -2
  23. package/src/app/page.tsx +1 -2
  24. package/src/components/MessageBubble.tsx +211 -69
  25. package/src/components/ProductCard.tsx +2 -2
  26. package/src/components/VisualizationRenderer.tsx +347 -247
  27. package/src/config/RagConfig.ts +5 -0
  28. package/src/config/serverConfig.ts +3 -1
  29. package/src/core/Pipeline.ts +65 -206
  30. package/src/core/ProviderRegistry.ts +2 -2
  31. package/src/core/VectorPlugin.ts +9 -0
  32. package/src/handlers/index.ts +23 -7
  33. package/src/hooks/useRagChat.ts +2 -2
  34. package/src/llm/LLMFactory.ts +54 -2
  35. package/src/llm/providers/AnthropicProvider.ts +12 -8
  36. package/src/llm/providers/GeminiProvider.ts +188 -143
  37. package/src/llm/providers/OllamaProvider.ts +7 -3
  38. package/src/llm/providers/OpenAIProvider.ts +12 -8
  39. package/src/types/chat.ts +6 -0
  40. package/src/types/index.ts +80 -0
  41. package/src/utils/SchemaMapper.ts +129 -0
  42. package/src/utils/UITransformer.ts +491 -181
  43. package/dist/DocumentChunker-C1GEEosY.d.ts +0 -93
  44. package/dist/DocumentChunker-CFEiRopR.d.mts +0 -93
  45. package/dist/PostgreSQLProvider-BMOETDZA.mjs +0 -8
  46. package/dist/chunk-FLOSGE6A.mjs +0 -202
package/dist/server.js CHANGED
@@ -334,196 +334,205 @@ var init_PineconeProvider = __esm({
334
334
  }
335
335
  });
336
336
 
337
- // src/providers/vectordb/PostgreSQLProvider.ts
338
- var PostgreSQLProvider_exports = {};
339
- __export(PostgreSQLProvider_exports, {
340
- PostgreSQLProvider: () => PostgreSQLProvider
337
+ // src/providers/vectordb/MultiTablePostgresProvider.ts
338
+ var MultiTablePostgresProvider_exports = {};
339
+ __export(MultiTablePostgresProvider_exports, {
340
+ MultiTablePostgresProvider: () => MultiTablePostgresProvider
341
341
  });
342
- var import_pg, PostgreSQLProvider;
343
- var init_PostgreSQLProvider = __esm({
344
- "src/providers/vectordb/PostgreSQLProvider.ts"() {
342
+ var import_pg, MultiTablePostgresProvider;
343
+ var init_MultiTablePostgresProvider = __esm({
344
+ "src/providers/vectordb/MultiTablePostgresProvider.ts"() {
345
345
  "use strict";
346
346
  import_pg = require("pg");
347
347
  init_BaseVectorProvider();
348
- PostgreSQLProvider = class extends BaseVectorProvider {
348
+ MultiTablePostgresProvider = class extends BaseVectorProvider {
349
349
  constructor(config) {
350
- var _a;
350
+ var _a, _b, _c, _d, _e;
351
351
  super(config);
352
- this.tableName = this.indexName.replace(/[^a-z0-9_]/gi, "_");
353
- const opts = config.options;
354
- if (!opts.connectionString) throw new Error("[PostgreSQLProvider] options.connectionString is required");
352
+ const opts = config.options || {};
353
+ if (!opts.connectionString) {
354
+ throw new Error("[MultiTablePostgresProvider] options.connectionString is required");
355
+ }
355
356
  this.connectionString = opts.connectionString;
356
- this.dimensions = (_a = opts.dimensions) != null ? _a : 1536;
357
- }
358
- static getValidator() {
359
- return {
360
- validate(config) {
361
- const errors = [];
362
- const opts = config.options || {};
363
- if (!opts.connectionString) {
364
- errors.push({
365
- field: "vectorDb.options.connectionString",
366
- message: "PostgreSQL connection string is required",
367
- suggestion: "Set PGVECTOR_CONNECTION_STRING environment variable",
368
- severity: "error"
369
- });
370
- }
371
- if (opts.tables && typeof opts.tables !== "string" && !Array.isArray(opts.tables)) {
372
- errors.push({
373
- field: "vectorDb.options.tables",
374
- message: "PostgreSQL tables must be a string or a string array",
375
- severity: "error"
376
- });
377
- }
378
- return errors;
379
- }
380
- };
381
- }
382
- static getHealthChecker() {
383
- return {
384
- async check(config) {
385
- const opts = config.options || {};
386
- const timestamp = Date.now();
387
- try {
388
- const { Client } = await import("pg");
389
- const client = new Client({ connectionString: opts.connectionString });
390
- await client.connect();
391
- const result = await client.query(`
392
- SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'vector');
393
- `);
394
- const hasVector = result.rows[0].exists;
395
- await client.end();
396
- return {
397
- healthy: true,
398
- provider: "postgresql",
399
- capabilities: { pgvectorInstalled: hasVector },
400
- timestamp
401
- };
402
- } catch (error) {
403
- return {
404
- healthy: false,
405
- provider: "postgresql",
406
- error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
407
- timestamp
408
- };
409
- }
410
- }
411
- };
357
+ this.dimensions = (_a = opts.dimensions) != null ? _a : 768;
358
+ const rawTables = (_c = (_b = opts.tables) != null ? _b : process.env.VECTOR_DB_TABLES) != null ? _c : "";
359
+ this.tables = typeof rawTables === "string" ? rawTables.split(",").map((t) => t.trim()).filter(Boolean) : rawTables;
360
+ if (this.tables.length === 0) {
361
+ console.warn(
362
+ "[MultiTablePostgresProvider] No tables configured. Set VECTOR_DB_TABLES as a comma-separated list of table names or pass options.tables."
363
+ );
364
+ }
365
+ const rawSearchFields = (_e = (_d = opts.searchFields) != null ? _d : process.env.VECTOR_DB_SEARCH_FIELDS) != null ? _e : ["name", "product_name", "productname", "title"];
366
+ this.searchFields = typeof rawSearchFields === "string" ? rawSearchFields.split(",").map((f) => f.trim()).filter(Boolean) : rawSearchFields;
412
367
  }
413
368
  async initialize() {
414
369
  this.pool = new import_pg.Pool({ connectionString: this.connectionString });
415
370
  const client = await this.pool.connect();
416
371
  try {
417
- await client.query("CREATE EXTENSION IF NOT EXISTS vector");
418
- await client.query(`
419
- CREATE TABLE IF NOT EXISTS ${this.tableName} (
420
- id TEXT PRIMARY KEY,
421
- namespace TEXT NOT NULL DEFAULT '',
422
- content TEXT NOT NULL,
423
- metadata JSONB,
424
- embedding VECTOR(${this.dimensions})
425
- )
426
- `);
427
- await client.query(`
428
- CREATE INDEX IF NOT EXISTS ${this.tableName}_embedding_idx
429
- ON ${this.tableName}
430
- USING hnsw (embedding vector_cosine_ops)
372
+ const res = await client.query(`
373
+ SELECT table_name
374
+ FROM information_schema.columns
375
+ WHERE column_name = 'embedding'
376
+ AND table_schema = 'public'
431
377
  `);
378
+ const discoveredTables = res.rows.map((r) => r.table_name);
379
+ if (this.tables.length === 0) {
380
+ this.tables = discoveredTables;
381
+ } else {
382
+ const staticTables = [...this.tables];
383
+ this.tables = staticTables.filter((t) => discoveredTables.includes(t));
384
+ if (this.tables.length < staticTables.length) {
385
+ const missing = staticTables.filter((t) => !discoveredTables.includes(t));
386
+ console.warn(`[MultiTablePostgresProvider] Some configured tables were skipped (missing 'embedding' column): ${missing.join(", ")}`);
387
+ }
388
+ }
389
+ if (this.tables.length === 0) {
390
+ console.warn('[MultiTablePostgresProvider] No tables with "embedding" columns found in the database.');
391
+ } else {
392
+ console.log(
393
+ `[MultiTablePostgresProvider] Connected. Searching across ${this.tables.length} table(s): ${this.tables.join(", ")}`
394
+ );
395
+ }
432
396
  } finally {
433
397
  client.release();
434
398
  }
435
399
  }
436
- async upsert(doc, namespace = "") {
437
- var _a;
438
- const vectorLiteral = `[${doc.vector.join(",")}]`;
439
- await this.pool.query(
440
- `INSERT INTO ${this.tableName} (id, namespace, content, metadata, embedding)
441
- VALUES ($1, $2, $3, $4, $5::vector)
442
- ON CONFLICT (id) DO UPDATE
443
- SET namespace = EXCLUDED.namespace,
444
- content = EXCLUDED.content,
445
- metadata = EXCLUDED.metadata,
446
- embedding = EXCLUDED.embedding`,
447
- [doc.id, namespace, doc.content, JSON.stringify((_a = doc.metadata) != null ? _a : {}), vectorLiteral]
400
+ /**
401
+ * Upsert is not supported for MultiTablePostgresProvider as it's designed for
402
+ * searching across pre-existing tables with varying schemas.
403
+ */
404
+ async upsert(_doc, _namespace) {
405
+ throw new Error(
406
+ "[MultiTablePostgresProvider] upsert() is not supported in multi-table mode. Please use the standard PostgreSQLProvider for single-table managed indices."
448
407
  );
449
408
  }
450
- async batchUpsert(docs, namespace = "") {
451
- if (docs.length === 0) return;
452
- const client = await this.pool.connect();
453
- try {
454
- await client.query("BEGIN");
455
- const BATCH_SIZE = 50;
456
- for (let i = 0; i < docs.length; i += BATCH_SIZE) {
457
- const batch = docs.slice(i, i + BATCH_SIZE);
458
- const values = [];
459
- const valuePlaceholders = batch.map((doc, idx) => {
460
- var _a;
461
- const offset = idx * 5;
462
- values.push(doc.id, namespace, doc.content, JSON.stringify((_a = doc.metadata) != null ? _a : {}), `[${doc.vector.join(",")}]`);
463
- return `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}::vector)`;
464
- }).join(", ");
465
- const query = `
466
- INSERT INTO ${this.tableName} (id, namespace, content, metadata, embedding)
467
- VALUES ${valuePlaceholders}
468
- ON CONFLICT (id) DO UPDATE
469
- SET namespace = EXCLUDED.namespace,
470
- content = EXCLUDED.content,
471
- metadata = EXCLUDED.metadata,
472
- embedding = EXCLUDED.embedding
473
- `;
474
- await client.query(query, values);
475
- }
476
- await client.query("COMMIT");
477
- } catch (error) {
478
- await client.query("ROLLBACK");
479
- throw error;
480
- } finally {
481
- client.release();
482
- }
409
+ /**
410
+ * Batch upsert is not supported for MultiTablePostgresProvider.
411
+ */
412
+ async batchUpsert(_docs, _namespace) {
413
+ throw new Error(
414
+ "[MultiTablePostgresProvider] batchUpsert() is not supported in multi-table mode."
415
+ );
483
416
  }
484
- async query(vector, topK, namespace, filter) {
417
+ /**
418
+ * Query all configured tables and merge results, sorted by cosine similarity score.
419
+ */
420
+ async query(vector, topK, _namespace, _filter) {
421
+ var _a, _b, _c;
422
+ if (!this.pool) {
423
+ throw new Error("[MultiTablePostgresProvider] Provider not initialized. Call initialize() first.");
424
+ }
485
425
  const vectorLiteral = `[${vector.join(",")}]`;
486
- let whereClause = namespace ? `WHERE namespace = $3` : "";
487
- const params = [vectorLiteral, topK];
488
- if (namespace) params.push(namespace);
489
- const publicFilter = this.sanitizeFilter(filter);
490
- if (Object.keys(publicFilter).length > 0) {
491
- const filterConditions = Object.entries(publicFilter).map(([key, val]) => {
492
- const paramIdx = params.length + 1;
493
- params.push(JSON.stringify(val));
494
- return `metadata->>'${key}' = $${paramIdx}`;
495
- }).join(" AND ");
496
- whereClause = whereClause ? `${whereClause} AND ${filterConditions}` : `WHERE ${filterConditions}`;
426
+ const allResults = [];
427
+ console.log(`[MultiTablePostgresProvider] --- Starting Multi-Table Search ---`);
428
+ const queryText = _filter == null ? void 0 : _filter.queryText;
429
+ const entityHints = Array.isArray(_filter == null ? void 0 : _filter.__entityHints) ? _filter.__entityHints.filter(
430
+ (hint) => typeof hint === "object" && hint !== null && typeof hint.value === "string"
431
+ ).map((hint) => hint.value.trim().toLowerCase()).filter(Boolean) : [];
432
+ console.log(`[MultiTablePostgresProvider] queryText: "${queryText}"`);
433
+ console.log(`[MultiTablePostgresProvider] entityHints: [${entityHints.join(", ")}]`);
434
+ const getDynamicKeywordQuery = () => {
435
+ if (entityHints.length > 0) {
436
+ return entityHints.map((h) => h.includes(" ") ? `"${h}"` : h).join(" & ");
437
+ }
438
+ if (queryText) {
439
+ return queryText.toLowerCase().replace(/\b(show|me|the|product[s]?|item[s]?|card[s]?|of|category|find|list|browse|what|are|display|all|under|for|in|with|about)\b/g, "").trim().replace(/\s+/g, " & ");
440
+ }
441
+ return "";
442
+ };
443
+ const dynamicKeywordQuery = getDynamicKeywordQuery();
444
+ const queryPromises = this.tables.map(async (table) => {
445
+ try {
446
+ let sqlQuery = "";
447
+ let params = [];
448
+ if (queryText) {
449
+ const hasEntityHints = entityHints.length > 0;
450
+ const exactNameScoreExpr = hasEntityHints ? `+ (
451
+ SELECT COALESCE(MAX(
452
+ CASE WHEN LOWER(val) IN (${entityHints.map((h) => `'${h.replace(/'/g, "''")}'`).join(", ")})
453
+ THEN 1.0 ELSE 0.0 END
454
+ ), 0)
455
+ FROM jsonb_each_text(to_jsonb(t)) AS kv(key, val)
456
+ WHERE key IN (${this.searchFields.map((f) => `'${f}'`).join(", ")})
457
+ ) * 3.0` : "";
458
+ sqlQuery = `
459
+ SELECT *,
460
+ (1 - (embedding <=> $1::vector)) AS vector_score,
461
+ COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
462
+ ((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
463
+ FROM "${table}" t
464
+ ORDER BY hybrid_score DESC
465
+ LIMIT 50
466
+ `;
467
+ params = [vectorLiteral, dynamicKeywordQuery];
468
+ } else {
469
+ sqlQuery = `
470
+ SELECT *,
471
+ (1 - (embedding <=> $1::vector)) AS hybrid_score
472
+ FROM "${table}" t
473
+ ORDER BY hybrid_score DESC
474
+ LIMIT 50
475
+ `;
476
+ params = [vectorLiteral];
477
+ }
478
+ const result = await this.pool.query(sqlQuery, params);
479
+ if (result.rowCount && result.rowCount > 0) {
480
+ console.log(` \u2705 Table "${table}": Found ${result.rowCount} potential matches. Top score: ${result.rows[0].hybrid_score.toFixed(4)}`);
481
+ } else {
482
+ console.log(` \u26AA Table "${table}": No relevant matches found.`);
483
+ }
484
+ const tableResults = [];
485
+ for (const row of result.rows) {
486
+ const _a2 = row, { hybrid_score, id } = _a2, rest = __objRest(_a2, ["hybrid_score", "id"]);
487
+ delete rest.embedding;
488
+ delete rest.vector_score;
489
+ delete rest.keyword_score;
490
+ delete rest.exact_name_score;
491
+ const content = `[TYPE: ${table.replace(/s$/, "").toUpperCase()}]
492
+ ` + Object.entries(rest).filter(([k, v]) => v !== null && typeof v !== "object" && k !== "id").map(([k, v]) => `${k}: ${v}`).join("\n");
493
+ tableResults.push({
494
+ id: `${table}-${id}`,
495
+ score: parseFloat(String(hybrid_score)),
496
+ content,
497
+ metadata: __spreadProps(__spreadValues({}, rest), { source_table: table })
498
+ });
499
+ }
500
+ return tableResults;
501
+ } catch (err) {
502
+ console.error(
503
+ `[MultiTablePostgresProvider] Error querying table "${table}":`,
504
+ err.message
505
+ );
506
+ return [];
507
+ }
508
+ });
509
+ const resultsArray = await Promise.all(queryPromises);
510
+ for (const tableResults of resultsArray) {
511
+ allResults.push(...tableResults);
497
512
  }
498
- const client = await this.pool.connect();
499
- try {
500
- const efSearch = this.config.options.efSearch || Math.max(topK * 10, 40);
501
- await client.query(`SET LOCAL hnsw.ef_search = ${efSearch}`);
502
- const result = await client.query(
503
- `SELECT id, content, metadata, 1 - (embedding <=> $1::vector) AS score
504
- FROM ${this.tableName}
505
- ${whereClause}
506
- ORDER BY embedding <=> $1::vector
507
- LIMIT $2`,
508
- params
509
- );
510
- return result.rows.map((row) => ({
511
- id: String(row["id"]),
512
- score: parseFloat(String(row["score"])),
513
- content: String(row["content"]),
514
- metadata: row["metadata"]
515
- }));
516
- } finally {
517
- client.release();
513
+ const resultsByTable = {};
514
+ for (const res of allResults) {
515
+ const table = (_a = res.metadata) == null ? void 0 : _a.source_table;
516
+ if (!resultsByTable[table]) resultsByTable[table] = [];
517
+ resultsByTable[table].push(res);
518
+ }
519
+ const balancedResults = [];
520
+ const tables = Object.keys(resultsByTable);
521
+ for (const table of tables) {
522
+ balancedResults.push(...resultsByTable[table].slice(0, 3));
523
+ }
524
+ const finalSorted = balancedResults.sort((a, b) => b.score - a.score);
525
+ if (finalSorted.length > 0) {
526
+ console.log(`[MultiTablePostgresProvider] --- Search Complete ---`);
527
+ console.log(`[MultiTablePostgresProvider] Final top match from "${(_c = (_b = finalSorted[0].metadata) == null ? void 0 : _b.source_table) != null ? _c : "unknown"}" with score ${finalSorted[0].score.toFixed(4)}`);
518
528
  }
529
+ return finalSorted.slice(0, Math.max(topK, 15));
519
530
  }
520
- async delete(id, namespace) {
521
- const where = namespace ? "WHERE id = $1 AND namespace = $2" : "WHERE id = $1";
522
- const params = namespace ? [id, namespace] : [id];
523
- await this.pool.query(`DELETE FROM ${this.tableName} ${where}`, params);
531
+ async delete(_id, _namespace) {
532
+ console.warn("[MultiTablePostgresProvider] delete() is a no-op for multi-table mode.");
524
533
  }
525
- async deleteNamespace(namespace) {
526
- await this.pool.query(`DELETE FROM ${this.tableName} WHERE namespace = $1`, [namespace]);
534
+ async deleteNamespace(_namespace) {
535
+ console.warn("[MultiTablePostgresProvider] deleteNamespace() is a no-op for multi-table mode.");
527
536
  }
528
537
  async ping() {
529
538
  try {
@@ -534,7 +543,9 @@ var init_PostgreSQLProvider = __esm({
534
543
  }
535
544
  }
536
545
  async disconnect() {
537
- await this.pool.end();
546
+ if (this.pool) {
547
+ await this.pool.end();
548
+ }
538
549
  }
539
550
  };
540
551
  }
@@ -1736,7 +1747,7 @@ function getRagConfig(baseConfig, env = process.env) {
1736
1747
  return getEnvConfig(env, baseConfig);
1737
1748
  }
1738
1749
  function getEnvConfig(env = process.env, base) {
1739
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, __, _$, _aa, _ba, _ca, _da, _ea, _fa, _ga, _ha, _ia, _ja, _ka, _la, _ma, _na, _oa, _pa, _qa, _ra, _sa, _ta, _ua, _va;
1750
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, __, _$, _aa, _ba, _ca, _da, _ea, _fa, _ga, _ha, _ia, _ja, _ka, _la, _ma, _na, _oa, _pa, _qa, _ra, _sa, _ta, _ua, _va, _wa, _xa, _ya, _za, _Aa, _Ba, _Ca, _Da, _Ea, _Fa;
1740
1751
  const projectId = (_c = (_b = (_a = readString(env, "RAG_PROJECT_ID")) != null ? _a : readString(env, "NEXT_PUBLIC_PROJECT_ID")) != null ? _b : base == null ? void 0 : base.projectId) != null ? _c : "__default__";
1741
1752
  const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
1742
1753
  const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
@@ -1747,33 +1758,35 @@ function getEnvConfig(env = process.env, base) {
1747
1758
  vectorDbOptions.apiKey = (_g = (_f = readString(env, "PINECONE_API_KEY")) != null ? _f : (_e = (_d = base == null ? void 0 : base.vectorDb) == null ? void 0 : _d.options) == null ? void 0 : _e.apiKey) != null ? _g : "";
1748
1759
  vectorDbOptions.indexName = (_l = (_i = readString(env, "PINECONE_INDEX")) != null ? _i : (_h = base == null ? void 0 : base.vectorDb) == null ? void 0 : _h.indexName) != null ? _l : (_k = (_j = base == null ? void 0 : base.vectorDb) == null ? void 0 : _j.options) == null ? void 0 : _k.indexName;
1749
1760
  } else if (vectorProvider === "pgvector" || vectorProvider === "postgresql") {
1750
- vectorDbOptions.connectionString = (_p = (_o = readString(env, "PGVECTOR_CONNECTION_STRING")) != null ? _o : (_n = (_m = base == null ? void 0 : base.vectorDb) == null ? void 0 : _m.options) == null ? void 0 : _n.connectionString) != null ? _p : "";
1761
+ vectorDbOptions.connectionString = (_q = (_p = (_m = readString(env, "PGVECTOR_CONNECTION_STRING")) != null ? _m : readString(env, "POSTGRES_URL")) != null ? _p : (_o = (_n = base == null ? void 0 : base.vectorDb) == null ? void 0 : _n.options) == null ? void 0 : _o.connectionString) != null ? _q : "";
1762
+ vectorDbOptions.tables = (_v = (_s = (_r = readString(env, "VECTOR_DB_TABLES")) != null ? _r : readString(env, "POSTGRES_TABLES")) == null ? void 0 : _s.split(",").map((t) => t.trim())) != null ? _v : (_u = (_t = base == null ? void 0 : base.vectorDb) == null ? void 0 : _t.options) == null ? void 0 : _u.tables;
1763
+ vectorDbOptions.searchFields = (_z = (_w = readString(env, "POSTGRES_SEARCH_FIELDS")) == null ? void 0 : _w.split(",").map((f) => f.trim())) != null ? _z : (_y = (_x = base == null ? void 0 : base.vectorDb) == null ? void 0 : _x.options) == null ? void 0 : _y.searchFields;
1751
1764
  vectorDbOptions.dimensions = embeddingDimensions;
1752
1765
  } else if (vectorProvider === "mongodb") {
1753
- vectorDbOptions.uri = (_t = (_s = readString(env, "MONGODB_URI")) != null ? _s : (_r = (_q = base == null ? void 0 : base.vectorDb) == null ? void 0 : _q.options) == null ? void 0 : _r.uri) != null ? _t : "";
1754
- vectorDbOptions.database = (_x = (_w = readString(env, "MONGODB_DB")) != null ? _w : (_v = (_u = base == null ? void 0 : base.vectorDb) == null ? void 0 : _u.options) == null ? void 0 : _v.database) != null ? _x : "";
1755
- vectorDbOptions.collection = (_B = (_A = readString(env, "MONGODB_COLLECTION")) != null ? _A : (_z = (_y = base == null ? void 0 : base.vectorDb) == null ? void 0 : _y.options) == null ? void 0 : _z.collection) != null ? _B : "";
1756
- vectorDbOptions.indexName = (_H = (_G = (_D = readString(env, "MONGODB_INDEX_NAME")) != null ? _D : (_C = base == null ? void 0 : base.vectorDb) == null ? void 0 : _C.indexName) != null ? _G : (_F = (_E = base == null ? void 0 : base.vectorDb) == null ? void 0 : _E.options) == null ? void 0 : _F.indexName) != null ? _H : "vector_index";
1766
+ vectorDbOptions.uri = (_D = (_C = readString(env, "MONGODB_URI")) != null ? _C : (_B = (_A = base == null ? void 0 : base.vectorDb) == null ? void 0 : _A.options) == null ? void 0 : _B.uri) != null ? _D : "";
1767
+ vectorDbOptions.database = (_H = (_G = readString(env, "MONGODB_DB")) != null ? _G : (_F = (_E = base == null ? void 0 : base.vectorDb) == null ? void 0 : _E.options) == null ? void 0 : _F.database) != null ? _H : "";
1768
+ vectorDbOptions.collection = (_L = (_K = readString(env, "MONGODB_COLLECTION")) != null ? _K : (_J = (_I = base == null ? void 0 : base.vectorDb) == null ? void 0 : _I.options) == null ? void 0 : _J.collection) != null ? _L : "";
1769
+ vectorDbOptions.indexName = (_R = (_Q = (_N = readString(env, "MONGODB_INDEX_NAME")) != null ? _N : (_M = base == null ? void 0 : base.vectorDb) == null ? void 0 : _M.indexName) != null ? _Q : (_P = (_O = base == null ? void 0 : base.vectorDb) == null ? void 0 : _O.options) == null ? void 0 : _P.indexName) != null ? _R : "vector_index";
1757
1770
  } else if (vectorProvider === "qdrant") {
1758
- vectorDbOptions.baseUrl = (_L = (_K = readString(env, "QDRANT_URL")) != null ? _K : (_J = (_I = base == null ? void 0 : base.vectorDb) == null ? void 0 : _I.options) == null ? void 0 : _J.baseUrl) != null ? _L : "http://localhost:6333";
1759
- vectorDbOptions.apiKey = (_O = readString(env, "QDRANT_API_KEY")) != null ? _O : (_N = (_M = base == null ? void 0 : base.vectorDb) == null ? void 0 : _M.options) == null ? void 0 : _N.apiKey;
1771
+ vectorDbOptions.baseUrl = (_V = (_U = readString(env, "QDRANT_URL")) != null ? _U : (_T = (_S = base == null ? void 0 : base.vectorDb) == null ? void 0 : _S.options) == null ? void 0 : _T.baseUrl) != null ? _V : "http://localhost:6333";
1772
+ vectorDbOptions.apiKey = (_Y = readString(env, "QDRANT_API_KEY")) != null ? _Y : (_X = (_W = base == null ? void 0 : base.vectorDb) == null ? void 0 : _W.options) == null ? void 0 : _X.apiKey;
1760
1773
  vectorDbOptions.dimensions = embeddingDimensions;
1761
1774
  } else if (vectorProvider === "milvus") {
1762
- vectorDbOptions.baseUrl = (_P = readString(env, "MILVUS_URL")) != null ? _P : "http://localhost:19530";
1775
+ vectorDbOptions.baseUrl = (_Z = readString(env, "MILVUS_URL")) != null ? _Z : "http://localhost:19530";
1763
1776
  vectorDbOptions.apiKey = readString(env, "MILVUS_API_KEY");
1764
1777
  } else if (vectorProvider === "chromadb") {
1765
- vectorDbOptions.baseUrl = (_Q = readString(env, "CHROMADB_URL")) != null ? _Q : "http://localhost:8000";
1778
+ vectorDbOptions.baseUrl = (__ = readString(env, "CHROMADB_URL")) != null ? __ : "http://localhost:8000";
1766
1779
  } else if (vectorProvider === "weaviate") {
1767
- vectorDbOptions.baseUrl = (_R = readString(env, "WEAVIATE_URL")) != null ? _R : "http://localhost:8080";
1780
+ vectorDbOptions.baseUrl = (_$ = readString(env, "WEAVIATE_URL")) != null ? _$ : "http://localhost:8080";
1768
1781
  vectorDbOptions.apiKey = readString(env, "WEAVIATE_API_KEY");
1769
1782
  } else if (vectorProvider === "redis") {
1770
- vectorDbOptions.baseUrl = (_S = readString(env, "REDIS_URL")) != null ? _S : "";
1783
+ vectorDbOptions.baseUrl = (_aa = readString(env, "REDIS_URL")) != null ? _aa : "";
1771
1784
  vectorDbOptions.apiKey = readString(env, "REDIS_API_KEY");
1772
1785
  } else if (vectorProvider === "rest") {
1773
- vectorDbOptions.baseUrl = (_T = readString(env, "VECTOR_DB_REST_URL")) != null ? _T : "";
1786
+ vectorDbOptions.baseUrl = (_ba = readString(env, "VECTOR_DB_REST_URL")) != null ? _ba : "";
1774
1787
  vectorDbOptions.headers = readString(env, "VECTOR_DB_REST_API_KEY") ? { "api-key": readString(env, "VECTOR_DB_REST_API_KEY") } : {};
1775
1788
  } else if (vectorProvider === "universal_rest") {
1776
- vectorDbOptions.baseUrl = (_V = (_U = readString(env, "VECTOR_BASE_URL")) != null ? _U : readString(env, "VECTOR_DB_REST_URL")) != null ? _V : "";
1789
+ vectorDbOptions.baseUrl = (_da = (_ca = readString(env, "VECTOR_BASE_URL")) != null ? _ca : readString(env, "VECTOR_DB_REST_URL")) != null ? _da : "";
1777
1790
  vectorDbOptions.profile = readString(env, "VECTOR_UNIVERSAL_PROFILE");
1778
1791
  vectorDbOptions.headers = readString(env, "VECTOR_DB_REST_API_KEY") ? { Authorization: `Bearer ${readString(env, "VECTOR_DB_REST_API_KEY")}` } : {};
1779
1792
  }
@@ -1790,20 +1803,20 @@ function getEnvConfig(env = process.env, base) {
1790
1803
  openai: readString(env, "OPENAI_API_KEY"),
1791
1804
  gemini: readString(env, "GEMINI_API_KEY"),
1792
1805
  ollama: void 0,
1793
- universal_rest: (_W = readString(env, "EMBEDDING_API_KEY")) != null ? _W : readString(env, "OPENAI_API_KEY"),
1794
- custom: (_X = readString(env, "EMBEDDING_API_KEY")) != null ? _X : readString(env, "OPENAI_API_KEY")
1806
+ universal_rest: (_ea = readString(env, "EMBEDDING_API_KEY")) != null ? _ea : readString(env, "OPENAI_API_KEY"),
1807
+ custom: (_fa = readString(env, "EMBEDDING_API_KEY")) != null ? _fa : readString(env, "OPENAI_API_KEY")
1795
1808
  };
1796
1809
  return {
1797
1810
  projectId,
1798
1811
  vectorDb: {
1799
1812
  provider: vectorProvider,
1800
- indexName: (_Z = (_Y = readString(env, "VECTOR_DB_INDEX")) != null ? _Y : vectorDbOptions.indexName) != null ? _Z : "rag-index",
1813
+ indexName: (_ha = (_ga = readString(env, "VECTOR_DB_INDEX")) != null ? _ga : vectorDbOptions.indexName) != null ? _ha : "rag-index",
1801
1814
  options: vectorDbOptions
1802
1815
  },
1803
1816
  llm: {
1804
1817
  provider: llmProvider,
1805
- model: (__ = readString(env, "LLM_MODEL")) != null ? __ : "gpt-4o",
1806
- apiKey: (_$ = llmApiKeyByProvider[llmProvider]) != null ? _$ : "",
1818
+ model: (_ia = readString(env, "LLM_MODEL")) != null ? _ia : "gpt-4o",
1819
+ apiKey: (_ja = llmApiKeyByProvider[llmProvider]) != null ? _ja : "",
1807
1820
  baseUrl: readString(env, "LLM_BASE_URL"),
1808
1821
  systemPrompt: readString(env, "LLM_SYSTEM_PROMPT"),
1809
1822
  maxTokens: readNumber(env, "LLM_MAX_TOKENS", 4096),
@@ -1814,7 +1827,7 @@ function getEnvConfig(env = process.env, base) {
1814
1827
  },
1815
1828
  embedding: {
1816
1829
  provider: embeddingProvider,
1817
- model: (_aa = readString(env, "EMBEDDING_MODEL")) != null ? _aa : "text-embedding-3-small",
1830
+ model: (_ka = readString(env, "EMBEDDING_MODEL")) != null ? _ka : "text-embedding-3-small",
1818
1831
  apiKey: embeddingApiKeyByProvider[embeddingProvider],
1819
1832
  baseUrl: readString(env, "EMBEDDING_BASE_URL"),
1820
1833
  dimensions: embeddingDimensions,
@@ -1825,17 +1838,17 @@ function getEnvConfig(env = process.env, base) {
1825
1838
  }
1826
1839
  },
1827
1840
  ui: {
1828
- title: (_ca = (_ba = readString(env, "NEXT_PUBLIC_UI_TITLE")) != null ? _ba : readString(env, "UI_TITLE")) != null ? _ca : "AI Assistant",
1829
- subtitle: (_ea = (_da = readString(env, "NEXT_PUBLIC_UI_SUBTITLE")) != null ? _da : readString(env, "UI_SUBTITLE")) != null ? _ea : "Powered by RAG",
1830
- primaryColor: (_ga = (_fa = readString(env, "NEXT_PUBLIC_PRIMARY_COLOR")) != null ? _fa : readString(env, "UI_PRIMARY_COLOR")) != null ? _ga : "#10b981",
1831
- accentColor: (_ia = (_ha = readString(env, "NEXT_PUBLIC_ACCENT_COLOR")) != null ? _ha : readString(env, "UI_ACCENT_COLOR")) != null ? _ia : "#3b82f6",
1832
- logoUrl: (_ja = readString(env, "NEXT_PUBLIC_LOGO_URL")) != null ? _ja : readString(env, "UI_LOGO_URL"),
1833
- placeholder: (_la = (_ka = readString(env, "NEXT_PUBLIC_PLACEHOLDER")) != null ? _ka : readString(env, "UI_PLACEHOLDER")) != null ? _la : "Ask me anything\u2026",
1834
- showSources: ((_na = (_ma = readString(env, "NEXT_PUBLIC_SHOW_SOURCES")) != null ? _ma : readString(env, "UI_SHOW_SOURCES")) != null ? _na : "true") !== "false",
1835
- welcomeMessage: (_pa = (_oa = readString(env, "NEXT_PUBLIC_WELCOME_MESSAGE")) != null ? _oa : readString(env, "UI_WELCOME_MESSAGE")) != null ? _pa : "Hello! I'm your AI assistant. Ask me anything about your documents.",
1836
- visualStyle: (_ra = (_qa = readString(env, "NEXT_PUBLIC_UI_VISUAL_STYLE")) != null ? _qa : readString(env, "UI_VISUAL_STYLE")) != null ? _ra : "glass",
1837
- borderRadius: (_ta = (_sa = readString(env, "NEXT_PUBLIC_UI_BORDER_RADIUS")) != null ? _sa : readString(env, "UI_BORDER_RADIUS")) != null ? _ta : "xl",
1838
- allowUpload: ((_va = (_ua = readString(env, "NEXT_PUBLIC_ALLOW_UPLOAD")) != null ? _ua : readString(env, "UI_ALLOW_UPLOAD")) != null ? _va : "false") === "true"
1841
+ title: (_ma = (_la = readString(env, "NEXT_PUBLIC_UI_TITLE")) != null ? _la : readString(env, "UI_TITLE")) != null ? _ma : "AI Assistant",
1842
+ subtitle: (_oa = (_na = readString(env, "NEXT_PUBLIC_UI_SUBTITLE")) != null ? _na : readString(env, "UI_SUBTITLE")) != null ? _oa : "Powered by RAG",
1843
+ primaryColor: (_qa = (_pa = readString(env, "NEXT_PUBLIC_PRIMARY_COLOR")) != null ? _pa : readString(env, "UI_PRIMARY_COLOR")) != null ? _qa : "#10b981",
1844
+ accentColor: (_sa = (_ra = readString(env, "NEXT_PUBLIC_ACCENT_COLOR")) != null ? _ra : readString(env, "UI_ACCENT_COLOR")) != null ? _sa : "#3b82f6",
1845
+ logoUrl: (_ta = readString(env, "NEXT_PUBLIC_LOGO_URL")) != null ? _ta : readString(env, "UI_LOGO_URL"),
1846
+ placeholder: (_va = (_ua = readString(env, "NEXT_PUBLIC_PLACEHOLDER")) != null ? _ua : readString(env, "UI_PLACEHOLDER")) != null ? _va : "Ask me anything\u2026",
1847
+ showSources: ((_xa = (_wa = readString(env, "NEXT_PUBLIC_SHOW_SOURCES")) != null ? _wa : readString(env, "UI_SHOW_SOURCES")) != null ? _xa : "true") !== "false",
1848
+ welcomeMessage: (_za = (_ya = readString(env, "NEXT_PUBLIC_WELCOME_MESSAGE")) != null ? _ya : readString(env, "UI_WELCOME_MESSAGE")) != null ? _za : "Hello! I'm your AI assistant. Ask me anything about your documents.",
1849
+ visualStyle: (_Ba = (_Aa = readString(env, "NEXT_PUBLIC_UI_VISUAL_STYLE")) != null ? _Aa : readString(env, "UI_VISUAL_STYLE")) != null ? _Ba : "glass",
1850
+ borderRadius: (_Da = (_Ca = readString(env, "NEXT_PUBLIC_UI_BORDER_RADIUS")) != null ? _Ca : readString(env, "UI_BORDER_RADIUS")) != null ? _Da : "xl",
1851
+ allowUpload: ((_Fa = (_Ea = readString(env, "NEXT_PUBLIC_ALLOW_UPLOAD")) != null ? _Ea : readString(env, "UI_ALLOW_UPLOAD")) != null ? _Fa : "false") === "true"
1839
1852
  },
1840
1853
  rag: {
1841
1854
  topK: readNumber(env, "RAG_TOP_K", 5),
@@ -1952,14 +1965,14 @@ var OpenAIProvider = class {
1952
1965
  };
1953
1966
  }
1954
1967
  async chat(messages, context, options) {
1955
- var _a, _b, _c, _d, _e, _f, _g, _h;
1956
- const systemContent = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
1968
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1969
+ const resolvedSystemPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Answer questions based on the provided context.
1957
1970
 
1958
1971
  Context:
1959
1972
  ${context}`;
1960
1973
  const systemMessage = {
1961
1974
  role: "system",
1962
- content: systemContent.includes("{{context}}") ? systemContent.replace("{{context}}", context) : `${systemContent}
1975
+ content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
1963
1976
 
1964
1977
  Context:
1965
1978
  ${context}`
@@ -1974,22 +1987,22 @@ ${context}`
1974
1987
  const completion = await this.client.chat.completions.create({
1975
1988
  model: this.llmConfig.model,
1976
1989
  messages: formattedMessages,
1977
- max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
1978
- temperature: (_e = (_d = options == null ? void 0 : options.temperature) != null ? _d : this.llmConfig.temperature) != null ? _e : 0.7,
1990
+ max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
1991
+ temperature: (_f = (_e = options == null ? void 0 : options.temperature) != null ? _e : this.llmConfig.temperature) != null ? _f : 0.7,
1979
1992
  stop: options == null ? void 0 : options.stop
1980
1993
  });
1981
- return (_h = (_g = (_f = completion.choices[0]) == null ? void 0 : _f.message) == null ? void 0 : _g.content) != null ? _h : "";
1994
+ return (_i = (_h = (_g = completion.choices[0]) == null ? void 0 : _g.message) == null ? void 0 : _h.content) != null ? _i : "";
1982
1995
  }
1983
1996
  chatStream(messages, context, options) {
1984
1997
  return __asyncGenerator(this, null, function* () {
1985
- var _a, _b, _c, _d, _e, _f, _g;
1986
- const systemContent = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
1998
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1999
+ const resolvedSystemPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Answer questions based on the provided context.
1987
2000
 
1988
2001
  Context:
1989
2002
  ${context}`;
1990
2003
  const systemMessage = {
1991
2004
  role: "system",
1992
- content: systemContent.includes("{{context}}") ? systemContent.replace("{{context}}", context) : `${systemContent}
2005
+ content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
1993
2006
 
1994
2007
  Context:
1995
2008
  ${context}`
@@ -2004,8 +2017,8 @@ ${context}`
2004
2017
  const stream = yield new __await(this.client.chat.completions.create({
2005
2018
  model: this.llmConfig.model,
2006
2019
  messages: formattedMessages,
2007
- max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
2008
- temperature: (_e = (_d = options == null ? void 0 : options.temperature) != null ? _d : this.llmConfig.temperature) != null ? _e : 0.7,
2020
+ max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
2021
+ temperature: (_f = (_e = options == null ? void 0 : options.temperature) != null ? _e : this.llmConfig.temperature) != null ? _f : 0.7,
2009
2022
  stop: options == null ? void 0 : options.stop,
2010
2023
  stream: true
2011
2024
  }));
@@ -2015,7 +2028,7 @@ ${context}`
2015
2028
  try {
2016
2029
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
2017
2030
  const chunk = temp.value;
2018
- const content = ((_g = (_f = chunk.choices[0]) == null ? void 0 : _f.delta) == null ? void 0 : _g.content) || "";
2031
+ const content = ((_h = (_g = chunk.choices[0]) == null ? void 0 : _g.delta) == null ? void 0 : _h.content) || "";
2019
2032
  if (content) yield content;
2020
2033
  }
2021
2034
  } catch (temp) {
@@ -2113,12 +2126,12 @@ var AnthropicProvider = class {
2113
2126
  };
2114
2127
  }
2115
2128
  async chat(messages, context, options) {
2116
- var _a, _b, _c;
2117
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the context below to answer the user's question accurately.
2129
+ var _a, _b, _c, _d;
2130
+ const resolvedPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Use the context below to answer the user's question accurately.
2118
2131
 
2119
2132
  Context:
2120
2133
  ${context}`;
2121
- const system = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
2134
+ const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
2122
2135
 
2123
2136
  Context:
2124
2137
  ${context}`;
@@ -2128,7 +2141,7 @@ ${context}`;
2128
2141
  }));
2129
2142
  const response = await this.client.messages.create({
2130
2143
  model: this.llmConfig.model,
2131
- max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
2144
+ max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
2132
2145
  system,
2133
2146
  messages: anthropicMessages
2134
2147
  });
@@ -2137,12 +2150,12 @@ ${context}`;
2137
2150
  }
2138
2151
  chatStream(messages, context, options) {
2139
2152
  return __asyncGenerator(this, null, function* () {
2140
- var _a, _b, _c;
2141
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the context below to answer the user's question accurately.
2153
+ var _a, _b, _c, _d;
2154
+ const resolvedPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Use the context below to answer the user's question accurately.
2142
2155
 
2143
2156
  Context:
2144
2157
  ${context}`;
2145
- const system = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
2158
+ const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
2146
2159
 
2147
2160
  Context:
2148
2161
  ${context}`;
@@ -2152,7 +2165,7 @@ ${context}`;
2152
2165
  }));
2153
2166
  const stream = yield new __await(this.client.messages.create({
2154
2167
  model: this.llmConfig.model,
2155
- max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
2168
+ max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
2156
2169
  system,
2157
2170
  messages: anthropicMessages,
2158
2171
  stream: true
@@ -2267,7 +2280,7 @@ var OllamaProvider = class {
2267
2280
  }
2268
2281
  async chat(messages, context, options) {
2269
2282
  var _a, _b, _c, _d;
2270
- const system = this.buildSystemPrompt(context);
2283
+ const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
2271
2284
  const { data } = await this.http.post("/api/chat", {
2272
2285
  model: this.llmConfig.model,
2273
2286
  stream: false,
@@ -2285,7 +2298,7 @@ var OllamaProvider = class {
2285
2298
  chatStream(messages, context, options) {
2286
2299
  return __asyncGenerator(this, null, function* () {
2287
2300
  var _a, _b, _c, _d, _e, _f;
2288
- const system = this.buildSystemPrompt(context);
2301
+ const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
2289
2302
  const response = yield new __await(this.http.post("/api/chat", {
2290
2303
  model: this.llmConfig.model,
2291
2304
  stream: true,
@@ -2341,8 +2354,11 @@ var OllamaProvider = class {
2341
2354
  }
2342
2355
  });
2343
2356
  }
2344
- buildSystemPrompt(context) {
2357
+ buildSystemPrompt(context, overridePrompt) {
2345
2358
  var _a;
2359
+ if (overridePrompt) {
2360
+ return overridePrompt;
2361
+ }
2346
2362
  const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
2347
2363
 
2348
2364
  Context:
@@ -2394,20 +2410,41 @@ ${context}`;
2394
2410
  };
2395
2411
 
2396
2412
  // src/llm/providers/GeminiProvider.ts
2397
- var import_genai = require("@google/genai");
2413
+ var import_generative_ai = require("@google/generative-ai");
2414
+ var DEFAULT_EMBEDDING_MODEL = "text-embedding-004";
2415
+ var DEFAULT_TEMPERATURE = 0.7;
2416
+ var DEFAULT_MAX_TOKENS = 1024;
2417
+ function sanitizeModel(model) {
2418
+ return model ? model.split(":")[0] : model;
2419
+ }
2420
+ function buildClient(apiKey) {
2421
+ return new import_generative_ai.GoogleGenerativeAI(apiKey);
2422
+ }
2423
+ function applyPrefix(text, taskType, queryPrefix, docPrefix) {
2424
+ if (taskType === "query" && queryPrefix && !text.startsWith(queryPrefix)) {
2425
+ return `${queryPrefix}${text}`;
2426
+ }
2427
+ if (taskType === "document" && docPrefix && !text.startsWith(docPrefix)) {
2428
+ return `${docPrefix}${text}`;
2429
+ }
2430
+ return text;
2431
+ }
2398
2432
  var GeminiProvider = class {
2399
2433
  constructor(llmConfig, embeddingConfig) {
2400
- if (!llmConfig.apiKey) throw new Error("[GeminiProvider] llmConfig.apiKey is required");
2401
- this.client = new import_genai.GoogleGenAI({ apiKey: llmConfig.apiKey });
2402
- this.llmConfig = __spreadProps(__spreadValues({}, llmConfig), {
2403
- model: this.sanitizeModel(llmConfig.model)
2404
- });
2434
+ if (!llmConfig.apiKey) {
2435
+ throw new Error("[GeminiProvider] llmConfig.apiKey is required");
2436
+ }
2437
+ this.llmConfig = __spreadProps(__spreadValues({}, llmConfig), { model: sanitizeModel(llmConfig.model) });
2438
+ this.client = buildClient(this.llmConfig.apiKey);
2405
2439
  if (embeddingConfig) {
2406
2440
  this.embeddingConfig = __spreadProps(__spreadValues({}, embeddingConfig), {
2407
- model: this.sanitizeModel(embeddingConfig.model)
2441
+ model: sanitizeModel(embeddingConfig.model)
2408
2442
  });
2409
2443
  }
2410
2444
  }
2445
+ // -------------------------------------------------------------------------
2446
+ // Static factory helpers
2447
+ // -------------------------------------------------------------------------
2411
2448
  static getValidator() {
2412
2449
  return {
2413
2450
  validate(config) {
@@ -2421,7 +2458,11 @@ var GeminiProvider = class {
2421
2458
  });
2422
2459
  }
2423
2460
  if (!config.model) {
2424
- errors.push({ field: "llm.model", message: "Gemini model name is required", severity: "error" });
2461
+ errors.push({
2462
+ field: "llm.model",
2463
+ message: "Gemini model name is required",
2464
+ severity: "error"
2465
+ });
2425
2466
  }
2426
2467
  return errors;
2427
2468
  }
@@ -2430,13 +2471,15 @@ var GeminiProvider = class {
2430
2471
  static getHealthChecker() {
2431
2472
  return {
2432
2473
  async check(config) {
2474
+ var _a, _b;
2433
2475
  const timestamp = Date.now();
2434
- const apiKey = config.apiKey || process.env.GOOGLE_GENAI_API_KEY || "";
2435
- const modelName = config.model;
2476
+ const apiKey = (_b = (_a = config.apiKey) != null ? _a : process.env.GOOGLE_GENAI_API_KEY) != null ? _b : "";
2477
+ const modelName = sanitizeModel(config.model);
2436
2478
  try {
2437
- const { GoogleGenAI: GoogleGenAI2 } = await import("@google/genai");
2438
- const genAI = new GoogleGenAI2({ apiKey });
2439
- await genAI.models.get({ model: modelName });
2479
+ const { GoogleGenerativeAI: GoogleGenerativeAI2 } = await import("@google/generative-ai");
2480
+ const ai = new GoogleGenerativeAI2(apiKey);
2481
+ const model = ai.getGenerativeModel({ model: DEFAULT_EMBEDDING_MODEL });
2482
+ await model.embedContent("health-check");
2440
2483
  return {
2441
2484
  healthy: true,
2442
2485
  provider: "gemini",
@@ -2454,70 +2497,90 @@ var GeminiProvider = class {
2454
2497
  }
2455
2498
  };
2456
2499
  }
2457
- sanitizeModel(model) {
2458
- if (!model) return model;
2459
- return model.split(":")[0];
2500
+ // -------------------------------------------------------------------------
2501
+ // Private utilities
2502
+ // -------------------------------------------------------------------------
2503
+ /** Resolve the embedding client — uses a separate client when the embedding
2504
+ * API key differs from the LLM API key. */
2505
+ get embeddingClient() {
2506
+ var _a;
2507
+ if (((_a = this.embeddingConfig) == null ? void 0 : _a.apiKey) && this.embeddingConfig.apiKey !== this.llmConfig.apiKey) {
2508
+ return buildClient(this.embeddingConfig.apiKey);
2509
+ }
2510
+ return this.client;
2460
2511
  }
2461
- async chat(messages, context, options) {
2462
- var _a, _b, _c, _d, _e, _f;
2463
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
2512
+ /** Resolve the embedding model to use, in order of specificity. */
2513
+ resolveEmbeddingModel(optionsModel) {
2514
+ var _a, _b;
2515
+ return sanitizeModel((_b = optionsModel != null ? optionsModel : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _b : DEFAULT_EMBEDDING_MODEL);
2516
+ }
2517
+ /**
2518
+ * Build the system instruction string, inserting the RAG context either via
2519
+ * the `{{context}}` placeholder or by appending it.
2520
+ */
2521
+ buildSystemInstruction(context) {
2522
+ var _a;
2523
+ const base = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
2464
2524
 
2465
2525
  Context:
2466
2526
  ${context}`;
2467
- const systemInstruction = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
2527
+ return base.includes("{{context}}") ? base.replace("{{context}}", context) : `${base}
2468
2528
 
2469
2529
  Context:
2470
2530
  ${context}`;
2471
- const geminiMessages = messages.map((m) => ({
2531
+ }
2532
+ /**
2533
+ * Convert ChatMessage[] to the Gemini contents format.
2534
+ *
2535
+ * Rules enforced by the Gemini API:
2536
+ * - Only `user` and `model` roles are allowed in `contents`.
2537
+ * - System messages must be passed via `systemInstruction`, not here.
2538
+ * - Messages must strictly alternate between `user` and `model`.
2539
+ */
2540
+ buildGeminiContents(messages) {
2541
+ return messages.filter((m) => m.role !== "system").map((m) => ({
2472
2542
  role: m.role === "assistant" ? "model" : "user",
2473
2543
  parts: [{ text: m.content }]
2474
2544
  }));
2475
- const response = await this.client.models.generateContent({
2545
+ }
2546
+ // -------------------------------------------------------------------------
2547
+ // ILLMProvider — chat
2548
+ // -------------------------------------------------------------------------
2549
+ async chat(messages, context, options) {
2550
+ var _a, _b, _c, _d;
2551
+ const model = this.client.getGenerativeModel({
2476
2552
  model: this.llmConfig.model,
2477
- contents: geminiMessages,
2478
- config: {
2479
- systemInstruction,
2480
- temperature: (_c = (_b = options == null ? void 0 : options.temperature) != null ? _b : this.llmConfig.temperature) != null ? _c : 0.7,
2481
- maxOutputTokens: (_e = (_d = options == null ? void 0 : options.maxTokens) != null ? _d : this.llmConfig.maxTokens) != null ? _e : 1024,
2553
+ systemInstruction: this.buildSystemInstruction(context)
2554
+ });
2555
+ const result = await model.generateContent({
2556
+ contents: this.buildGeminiContents(messages),
2557
+ generationConfig: {
2558
+ temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
2559
+ maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
2482
2560
  stopSequences: options == null ? void 0 : options.stop
2483
2561
  }
2484
2562
  });
2485
- const text = typeof response.text === "function" ? response.text() : (_f = response.text) != null ? _f : "";
2486
- return text;
2563
+ return result.response.text();
2487
2564
  }
2488
2565
  chatStream(messages, context, options) {
2489
2566
  return __asyncGenerator(this, null, function* () {
2490
- var _a, _b, _c, _d, _e, _f;
2491
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
2492
-
2493
- Context:
2494
- ${context}`;
2495
- const systemInstruction = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
2496
-
2497
- Context:
2498
- ${context}`;
2499
- const geminiMessages = messages.map((m) => ({
2500
- role: m.role === "assistant" ? "model" : "user",
2501
- parts: [{ text: m.content }]
2502
- }));
2503
- const result = yield new __await(this.client.models.generateContentStream({
2567
+ var _a, _b, _c, _d;
2568
+ const model = this.client.getGenerativeModel({
2504
2569
  model: this.llmConfig.model,
2505
- contents: geminiMessages,
2506
- config: {
2507
- systemInstruction,
2508
- temperature: (_c = (_b = options == null ? void 0 : options.temperature) != null ? _b : this.llmConfig.temperature) != null ? _c : 0.7,
2509
- maxOutputTokens: (_e = (_d = options == null ? void 0 : options.maxTokens) != null ? _d : this.llmConfig.maxTokens) != null ? _e : 1024,
2570
+ systemInstruction: this.buildSystemInstruction(context)
2571
+ });
2572
+ const result = yield new __await(model.generateContentStream({
2573
+ contents: this.buildGeminiContents(messages),
2574
+ generationConfig: {
2575
+ temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
2576
+ maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
2510
2577
  stopSequences: options == null ? void 0 : options.stop
2511
2578
  }
2512
2579
  }));
2513
- const stream = (_f = result == null ? void 0 : result.stream) != null ? _f : result;
2514
- if (!stream) {
2515
- throw new Error("[GeminiProvider] generateContentStream returned undefined");
2516
- }
2517
2580
  try {
2518
- for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
2581
+ for (var iter = __forAwait(result.stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
2519
2582
  const chunk = temp.value;
2520
- const text = typeof chunk.text === "function" ? chunk.text() : chunk.text;
2583
+ const text = chunk.text();
2521
2584
  if (text) yield text;
2522
2585
  }
2523
2586
  } catch (temp) {
@@ -2532,69 +2595,69 @@ ${context}`;
2532
2595
  }
2533
2596
  });
2534
2597
  }
2598
+ // -------------------------------------------------------------------------
2599
+ // ILLMProvider — embeddings
2600
+ // -------------------------------------------------------------------------
2535
2601
  async embed(text, options) {
2536
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2537
- const model = this.sanitizeModel(
2538
- (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "text-embedding-004"
2602
+ var _a, _b;
2603
+ const content = applyPrefix(
2604
+ text,
2605
+ options == null ? void 0 : options.taskType,
2606
+ (_a = this.embeddingConfig) == null ? void 0 : _a.queryPrefix,
2607
+ (_b = this.embeddingConfig) == null ? void 0 : _b.documentPrefix
2539
2608
  );
2540
- const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
2541
- const client = apiKey !== this.llmConfig.apiKey ? new import_genai.GoogleGenAI({ apiKey }) : this.client;
2542
- let content = text;
2543
- const queryPrefix = (_f = this.embeddingConfig) == null ? void 0 : _f.queryPrefix;
2544
- const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
2545
- if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
2546
- if (!content.startsWith(queryPrefix)) {
2547
- content = `${queryPrefix}${text}`;
2548
- }
2549
- } else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
2550
- if (!content.startsWith(docPrefix)) {
2551
- content = `${docPrefix}${text}`;
2552
- }
2553
- }
2554
- const response = await client.models.embedContent({
2555
- model,
2556
- contents: content,
2557
- config: ((_h = this.embeddingConfig) == null ? void 0 : _h.dimensions) ? { outputDimensionality: this.embeddingConfig.dimensions } : void 0
2609
+ const modelName = this.resolveEmbeddingModel(options == null ? void 0 : options.model);
2610
+ const model = this.embeddingClient.getGenerativeModel({ model: modelName });
2611
+ const response = await model.embedContent({
2612
+ content: { role: "user", parts: [{ text: content }] }
2558
2613
  }).catch((err) => {
2559
- console.error(`[GeminiProvider] Embedding failed for model "${model}":`, err.message);
2614
+ console.error(`[GeminiProvider] Embedding failed for model "${modelName}":`, err.message);
2560
2615
  throw err;
2561
2616
  });
2562
- return (_k = (_j = (_i = response.embeddings) == null ? void 0 : _i[0]) == null ? void 0 : _j.values) != null ? _k : [];
2617
+ return response.embedding.values;
2563
2618
  }
2564
2619
  async batchEmbed(texts, options) {
2565
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
2566
- const model = this.sanitizeModel(
2567
- (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "text-embedding-004"
2568
- );
2569
- const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
2570
- const client = apiKey !== this.llmConfig.apiKey ? new import_genai.GoogleGenAI({ apiKey }) : this.client;
2571
- let contents = texts;
2572
- const queryPrefix = (_f = this.embeddingConfig) == null ? void 0 : _f.queryPrefix;
2573
- const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
2574
- if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
2575
- contents = texts.map((text) => text.startsWith(queryPrefix) ? text : `${queryPrefix}${text}`);
2576
- } else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
2577
- contents = texts.map((text) => text.startsWith(docPrefix) ? text : `${docPrefix}${text}`);
2578
- }
2579
- const response = await client.models.embedContent({
2580
- model,
2581
- contents,
2582
- config: ((_h = this.embeddingConfig) == null ? void 0 : _h.dimensions) ? { outputDimensionality: this.embeddingConfig.dimensions } : void 0
2620
+ const modelName = this.resolveEmbeddingModel(options == null ? void 0 : options.model);
2621
+ const model = this.embeddingClient.getGenerativeModel({ model: modelName });
2622
+ const requests = texts.map((text) => {
2623
+ var _a, _b;
2624
+ return {
2625
+ content: {
2626
+ role: "user",
2627
+ parts: [{
2628
+ text: applyPrefix(
2629
+ text,
2630
+ options == null ? void 0 : options.taskType,
2631
+ (_a = this.embeddingConfig) == null ? void 0 : _a.queryPrefix,
2632
+ (_b = this.embeddingConfig) == null ? void 0 : _b.documentPrefix
2633
+ )
2634
+ }]
2635
+ }
2636
+ };
2637
+ });
2638
+ const response = await model.batchEmbedContents({
2639
+ requests: requests.map((r) => ({
2640
+ model: `models/${modelName}`,
2641
+ content: r.content
2642
+ }))
2583
2643
  }).catch((err) => {
2584
- console.error(`[GeminiProvider] Batch embedding failed for model "${model}":`, err.message);
2644
+ console.error(`[GeminiProvider] Batch embedding failed for model "${modelName}":`, err.message);
2585
2645
  throw err;
2586
2646
  });
2587
- return (_j = (_i = response.embeddings) == null ? void 0 : _i.map((e) => {
2588
- var _a2;
2589
- return (_a2 = e.values) != null ? _a2 : [];
2590
- })) != null ? _j : [];
2647
+ return response.embeddings.map((e) => {
2648
+ var _a;
2649
+ return (_a = e.values) != null ? _a : [];
2650
+ });
2591
2651
  }
2652
+ // -------------------------------------------------------------------------
2653
+ // ILLMProvider — health
2654
+ // -------------------------------------------------------------------------
2592
2655
  async ping() {
2593
2656
  try {
2594
- await this.client.models.embedContent({
2595
- model: "text-embedding-004",
2596
- contents: "ping"
2657
+ const model = this.embeddingClient.getGenerativeModel({
2658
+ model: this.resolveEmbeddingModel()
2597
2659
  });
2660
+ await model.embedContent("ping");
2598
2661
  return true;
2599
2662
  } catch (err) {
2600
2663
  console.error("[GeminiProvider] Ping failed:", err);
@@ -2790,9 +2853,51 @@ ${context != null ? context : "None"}` },
2790
2853
  };
2791
2854
 
2792
2855
  // src/llm/LLMFactory.ts
2856
+ var customProviders = /* @__PURE__ */ new Map();
2793
2857
  var LLMFactory = class _LLMFactory {
2858
+ /**
2859
+ * Register a custom LLM provider factory at runtime.
2860
+ *
2861
+ * Use this to add support for any LLM backend (Azure OpenAI, Cohere, Mistral,
2862
+ * Bedrock, etc.) without modifying this library's source code.
2863
+ *
2864
+ * @example
2865
+ * // In your Next.js app initialization:
2866
+ * import { LLMFactory } from '@retrivora-ai/rag-engine/server';
2867
+ * import { MyCustomProvider } from './providers/MyCustomProvider';
2868
+ *
2869
+ * LLMFactory.register('my-provider', (config) => new MyCustomProvider(config));
2870
+ *
2871
+ * // Then set in your .env.local:
2872
+ * // LLM_PROVIDER=my-provider
2873
+ */
2874
+ static register(name, factory) {
2875
+ customProviders.set(name.toLowerCase(), factory);
2876
+ console.log(`[LLMFactory] Registered custom provider: "${name}"`);
2877
+ }
2878
+ /**
2879
+ * Unregister a previously registered custom provider.
2880
+ */
2881
+ static unregister(name) {
2882
+ customProviders.delete(name.toLowerCase());
2883
+ }
2884
+ /**
2885
+ * List all registered provider names (built-in + custom).
2886
+ */
2887
+ static listProviders() {
2888
+ return [
2889
+ "openai",
2890
+ "anthropic",
2891
+ "ollama",
2892
+ "gemini",
2893
+ "rest",
2894
+ "universal_rest",
2895
+ "custom",
2896
+ ...Array.from(customProviders.keys())
2897
+ ];
2898
+ }
2794
2899
  static create(llmConfig, embeddingConfig) {
2795
- var _a;
2900
+ var _a, _b;
2796
2901
  switch (llmConfig.provider) {
2797
2902
  case "openai":
2798
2903
  return new OpenAIProvider(llmConfig, embeddingConfig);
@@ -2806,11 +2911,19 @@ var LLMFactory = class _LLMFactory {
2806
2911
  case "universal_rest":
2807
2912
  case "custom":
2808
2913
  return new UniversalLLMAdapter(llmConfig);
2809
- default:
2810
- if (llmConfig.baseUrl || ((_a = llmConfig.options) == null ? void 0 : _a.baseUrl)) {
2914
+ default: {
2915
+ const providerName = String((_a = llmConfig.provider) != null ? _a : "").toLowerCase();
2916
+ const customFactory = customProviders.get(providerName);
2917
+ if (customFactory) {
2918
+ return customFactory(llmConfig);
2919
+ }
2920
+ if (llmConfig.baseUrl || ((_b = llmConfig.options) == null ? void 0 : _b.baseUrl)) {
2811
2921
  return new UniversalLLMAdapter(llmConfig);
2812
2922
  }
2813
- throw new Error(`[LLMFactory] Unknown provider "${llmConfig.provider}"`);
2923
+ throw new Error(
2924
+ `[LLMFactory] Unknown provider "${llmConfig.provider}". Built-in providers: ${_LLMFactory.listProviders().join(", ")}. Register a custom provider with LLMFactory.register().`
2925
+ );
2926
+ }
2814
2927
  }
2815
2928
  }
2816
2929
  static getValidator(provider) {
@@ -2900,8 +3013,8 @@ var ProviderRegistry = class {
2900
3013
  }
2901
3014
  case "pgvector":
2902
3015
  case "postgresql": {
2903
- const { PostgreSQLProvider: PostgreSQLProvider2 } = await Promise.resolve().then(() => (init_PostgreSQLProvider(), PostgreSQLProvider_exports));
2904
- return PostgreSQLProvider2;
3016
+ const { MultiTablePostgresProvider: MultiTablePostgresProvider2 } = await Promise.resolve().then(() => (init_MultiTablePostgresProvider(), MultiTablePostgresProvider_exports));
3017
+ return MultiTablePostgresProvider2;
2905
3018
  }
2906
3019
  case "mongodb": {
2907
3020
  const { MongoDBProvider: MongoDBProvider2 } = await Promise.resolve().then(() => (init_MongoDBProvider(), MongoDBProvider_exports));
@@ -3869,69 +3982,40 @@ var QueryProcessor = class {
3869
3982
  var UITransformer = class {
3870
3983
  /**
3871
3984
  * Main transformation method
3872
- * Analyzes user query and retrieved data to determine the best visualization format
3873
- *
3874
- * @param userQuery - The original user question/query
3875
- * @param retrievedData - Vector database retrieval results
3876
- * @returns Structured JSON response for UI rendering
3985
+ * Analyzes user query and retrieved data to determine if a product carousel is needed.
3877
3986
  */
3878
- static transform(userQuery, retrievedData) {
3987
+ static transform(userQuery, retrievedData, config, trainedSchema) {
3879
3988
  if (!retrievedData || retrievedData.length === 0) {
3880
3989
  return this.createTextResponse("No data available", "No relevant data found for your query.");
3881
3990
  }
3882
- const analysis = this.analyzeData(userQuery, retrievedData);
3883
- switch (analysis.suggestedType) {
3884
- case "product_carousel":
3885
- return this.transformToProductCarousel(retrievedData);
3886
- case "pie_chart":
3887
- return this.transformToPieChart(retrievedData);
3888
- case "bar_chart":
3889
- return this.transformToBarChart(retrievedData);
3890
- case "line_chart":
3891
- return this.transformToLineChart(retrievedData);
3892
- case "table":
3893
- return this.transformToTable(retrievedData);
3894
- default:
3895
- return this.transformToText(retrievedData);
3896
- }
3897
- }
3898
- /**
3899
- * Analyzes the data and query to suggest the best visualization type
3900
- */
3901
- static analyzeData(query, data) {
3902
- const queryLower = query.toLowerCase();
3903
- const hasProducts = data.some(
3904
- (item) => this.isProductData(item)
3905
- );
3906
- const hasTimeSeries = data.some(
3907
- (item) => this.isTimeSeriesData(item)
3908
- );
3909
- const hasCategories = this.detectCategories(data).length > 0;
3910
- const isDistributionQuery = queryLower.includes("distribution") || queryLower.includes("breakdown") || queryLower.includes("percentage") || queryLower.includes("proportion");
3911
- const isComparisonQuery = queryLower.includes("compare") || queryLower.includes("vs") || queryLower.includes("difference");
3912
- const isTrendQuery = queryLower.includes("trend") || queryLower.includes("over time") || queryLower.includes("growth") || queryLower.includes("decline");
3913
- if (hasProducts && !hasTimeSeries && !isDistributionQuery) {
3914
- return { suggestedType: "product_carousel", confidence: 0.95, hasProducts, hasTimeSeries, hasCategories };
3991
+ const isStockRequest = this.isStockQuery(userQuery);
3992
+ const filteredData = isStockRequest ? retrievedData.filter((item) => this.determineStockStatus(item)) : retrievedData;
3993
+ const categories = this.detectCategories(filteredData);
3994
+ const hasProducts = filteredData.some((item) => this.isProductData(item));
3995
+ const isTimeSeries = filteredData.some((item) => this.isTimeSeriesData(item));
3996
+ const isTrendQuery = this.isTrendQuery(userQuery);
3997
+ if (isTrendQuery && isTimeSeries) {
3998
+ return this.transformToLineChart(filteredData);
3915
3999
  }
3916
- if (isTrendQuery && hasTimeSeries) {
3917
- return { suggestedType: "line_chart", confidence: 0.9, hasProducts, hasTimeSeries, hasCategories };
4000
+ if (hasProducts && !this.shouldShowCategoryChart(userQuery, categories)) {
4001
+ return this.transformToProductCarousel(filteredData, config, trainedSchema);
3918
4002
  }
3919
- if (isDistributionQuery && hasCategories) {
3920
- return { suggestedType: "pie_chart", confidence: 0.85, hasProducts, hasTimeSeries, hasCategories };
4003
+ if (categories.length > 1 && this.shouldShowCategoryChart(userQuery, categories)) {
4004
+ return this.transformToPieChart(filteredData);
3921
4005
  }
3922
- if (isComparisonQuery && hasCategories && data.length > 2) {
3923
- return { suggestedType: "bar_chart", confidence: 0.8, hasProducts, hasTimeSeries, hasCategories };
4006
+ if (hasProducts) {
4007
+ return this.transformToProductCarousel(filteredData, config, trainedSchema);
3924
4008
  }
3925
- if (data.length > 5 && this.hasMultipleFields(data)) {
3926
- return { suggestedType: "table", confidence: 0.75, hasProducts, hasTimeSeries, hasCategories };
4009
+ if (this.hasMultipleFields(filteredData)) {
4010
+ return this.transformToTable(filteredData);
3927
4011
  }
3928
- return { suggestedType: "text", confidence: 0.5, hasProducts, hasTimeSeries, hasCategories };
4012
+ return this.transformToText(filteredData);
3929
4013
  }
3930
4014
  /**
3931
4015
  * Transform data to product carousel format
3932
4016
  */
3933
- static transformToProductCarousel(data) {
3934
- const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item)).filter((p) => p !== null);
4017
+ static transformToProductCarousel(data, config, trainedSchema) {
4018
+ const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item, config, trainedSchema)).filter((p) => p !== null);
3935
4019
  return {
3936
4020
  type: "product_carousel",
3937
4021
  title: "Recommended Products",
@@ -4039,49 +4123,261 @@ var UITransformer = class {
4039
4123
  data: { content }
4040
4124
  };
4041
4125
  }
4042
- /**
4043
- * Helper: Check if data item is product-related
4126
+ static parseTransformationResponse(raw) {
4127
+ const payloadText = this.extractJsonCandidate(raw);
4128
+ if (!payloadText) return null;
4129
+ try {
4130
+ const parsed = JSON.parse(payloadText);
4131
+ return this.normalizeTransformation(parsed);
4132
+ } catch (e) {
4133
+ return null;
4134
+ }
4135
+ }
4136
+ static extractJsonCandidate(raw) {
4137
+ if (!raw) return null;
4138
+ const cleaned = raw.replace(/```(?:json|chart|ui)?\s*/gi, "").replace(/```/g, "").trim();
4139
+ const start = cleaned.indexOf("{");
4140
+ if (start === -1) return null;
4141
+ let depth = 0;
4142
+ let inString = false;
4143
+ let escape = false;
4144
+ for (let i = start; i < cleaned.length; i += 1) {
4145
+ const char = cleaned[i];
4146
+ if (escape) {
4147
+ escape = false;
4148
+ continue;
4149
+ }
4150
+ if (char === "\\") {
4151
+ escape = true;
4152
+ continue;
4153
+ }
4154
+ if (char === '"') {
4155
+ inString = !inString;
4156
+ continue;
4157
+ }
4158
+ if (inString) continue;
4159
+ if (char === "{") depth += 1;
4160
+ if (char === "}") {
4161
+ depth -= 1;
4162
+ if (depth === 0) {
4163
+ return cleaned.slice(start, i + 1);
4164
+ }
4165
+ }
4166
+ }
4167
+ return null;
4168
+ }
4169
+ static normalizeTransformation(payload) {
4170
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
4171
+ if (!payload || typeof payload !== "object") return null;
4172
+ const payloadObj = payload;
4173
+ const type = this.normalizeVisualizationType(String((_c = (_b = (_a = payloadObj.type) != null ? _a : payloadObj.view) != null ? _b : payloadObj.chartType) != null ? _c : ""));
4174
+ if (!type) return null;
4175
+ const title = String((_e = (_d = payloadObj.title) != null ? _d : payloadObj.heading) != null ? _e : "Visualization");
4176
+ const description = payloadObj.description ? String(payloadObj.description) : void 0;
4177
+ const rawData = (_j = (_i = (_h = (_g = (_f = payloadObj.data) != null ? _f : payloadObj.table) != null ? _g : payloadObj.rows) != null ? _h : payloadObj.items) != null ? _i : payloadObj.content) != null ? _j : null;
4178
+ const data = type === "text" && typeof rawData === "string" ? { content: rawData } : rawData;
4179
+ const transformation = {
4180
+ type,
4181
+ title,
4182
+ description,
4183
+ data
4184
+ };
4185
+ return this.validateTransformation(transformation) ? transformation : null;
4186
+ }
4187
+ static normalizeVisualizationType(type) {
4188
+ var _a;
4189
+ const mapping = {
4190
+ pie: "pie_chart",
4191
+ pie_chart: "pie_chart",
4192
+ bar: "bar_chart",
4193
+ bar_chart: "bar_chart",
4194
+ line: "line_chart",
4195
+ line_chart: "line_chart",
4196
+ radar: "radar_chart",
4197
+ radar_chart: "radar_chart",
4198
+ table: "table",
4199
+ text: "text",
4200
+ product_carousel: "product_carousel",
4201
+ carousel: "carousel"
4202
+ };
4203
+ return (_a = mapping[type.toLowerCase()]) != null ? _a : null;
4204
+ }
4205
+ static validateTransformation(transformation) {
4206
+ const { type, data } = transformation;
4207
+ switch (type) {
4208
+ case "pie_chart":
4209
+ case "bar_chart":
4210
+ return Array.isArray(data) && data.every(
4211
+ (item) => item !== null && typeof item === "object" && (typeof item.value === "number" || !Number.isNaN(Number(item.value)))
4212
+ );
4213
+ case "line_chart":
4214
+ return Array.isArray(data) && data.every(
4215
+ (item) => item !== null && typeof item === "object" && "timestamp" in item && (typeof item.value === "number" || !Number.isNaN(Number(item.value)))
4216
+ );
4217
+ case "radar_chart":
4218
+ return Array.isArray(data) && data.every(
4219
+ (item) => item !== null && typeof item === "object" && "attribute" in item
4220
+ );
4221
+ case "table":
4222
+ return typeof data === "object" && data !== null && Array.isArray(data.columns) && Array.isArray(data.rows);
4223
+ case "text":
4224
+ return typeof data === "object" && data !== null && typeof data.content === "string";
4225
+ case "product_carousel":
4226
+ case "carousel":
4227
+ return Array.isArray(data) && data.every(
4228
+ (item) => item !== null && typeof item === "object" && ("id" in item || "name" in item)
4229
+ );
4230
+ default:
4231
+ return false;
4232
+ }
4233
+ }
4234
+ /**
4235
+ * Helper: Check if data item is product-related
4044
4236
  */
4045
4237
  static isProductData(item) {
4046
4238
  const content = (item.content || "").toLowerCase();
4047
- const productKeywords = ["product", "price", "stock", "item", "sku", "brand", "model"];
4048
- return productKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
4049
- (k) => ["name", "price", "product", "sku"].includes(k.toLowerCase())
4239
+ const productKeywords = [
4240
+ "product",
4241
+ "price",
4242
+ "stock",
4243
+ "item",
4244
+ "sku",
4245
+ "brand",
4246
+ "model",
4247
+ "msrp",
4248
+ "inventory",
4249
+ "buy",
4250
+ "shop",
4251
+ "beauty",
4252
+ "care",
4253
+ "cosmetic",
4254
+ "facial",
4255
+ "cream",
4256
+ "serum",
4257
+ "mask",
4258
+ "makeup",
4259
+ "fragrance"
4260
+ ];
4261
+ const hasKeywords = productKeywords.some((kw) => content.includes(kw));
4262
+ const hasMetadataKey = Object.keys(item.metadata || {}).some(
4263
+ (k) => ["name", "price", "product", "sku", "brand", "model", "cost", "item"].includes(k.toLowerCase())
4050
4264
  );
4265
+ const hasPricePattern = /\$\s*\d+/.test(content);
4266
+ return hasKeywords || hasMetadataKey || hasPricePattern;
4051
4267
  }
4052
4268
  /**
4053
4269
  * Helper: Check if data contains time series
4054
4270
  */
4055
4271
  static isTimeSeriesData(item) {
4056
4272
  const content = (item.content || "").toLowerCase();
4057
- const timeKeywords = ["date", "time", "year", "month", "week", "day", "hour", "trend", "historical"];
4058
- return timeKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
4273
+ const timeKeywords = ["trend", "historical", "growth", "decline", "change", "increase", "decrease"];
4274
+ const hasTimeKeyword = timeKeywords.some((kw) => content.includes(kw));
4275
+ const metadata = item.metadata || {};
4276
+ const maybeDateKeys = Object.keys(metadata).filter(
4059
4277
  (k) => ["date", "timestamp", "time", "period"].includes(k.toLowerCase())
4060
4278
  );
4279
+ const hasValidDateValue = maybeDateKeys.some((key) => {
4280
+ const value = metadata[key];
4281
+ if (typeof value === "string") {
4282
+ return !Number.isNaN(Date.parse(value)) && value.trim().length > 0;
4283
+ }
4284
+ return typeof value === "number";
4285
+ });
4286
+ return hasTimeKeyword || hasValidDateValue;
4287
+ }
4288
+ static shouldShowCategoryChart(query, categories) {
4289
+ if (categories.length < 2) {
4290
+ return false;
4291
+ }
4292
+ const normalized = query.toLowerCase();
4293
+ const chartKeywords = [
4294
+ "distribution",
4295
+ "breakdown",
4296
+ "by category",
4297
+ "by type",
4298
+ "compare",
4299
+ "share",
4300
+ "percentage",
4301
+ "segmentation",
4302
+ "split",
4303
+ "category breakdown",
4304
+ "category distribution"
4305
+ ];
4306
+ return chartKeywords.some((keyword) => normalized.includes(keyword));
4307
+ }
4308
+ static isTrendQuery(query) {
4309
+ const normalized = query.toLowerCase();
4310
+ const trendKeywords = [
4311
+ "trend",
4312
+ "over time",
4313
+ "historical",
4314
+ "growth",
4315
+ "decline",
4316
+ "increase",
4317
+ "decrease",
4318
+ "year",
4319
+ "month",
4320
+ "week",
4321
+ "day",
4322
+ "comparison",
4323
+ "compare",
4324
+ "changes",
4325
+ "timeline"
4326
+ ];
4327
+ return trendKeywords.some((keyword) => normalized.includes(keyword));
4328
+ }
4329
+ static isStockQuery(query) {
4330
+ const normalized = query.toLowerCase();
4331
+ return normalized.includes("in stock") || normalized.includes("available") || normalized.includes("availability") || normalized.includes("inventory") || normalized.includes("stock status");
4061
4332
  }
4062
4333
  /**
4063
- * Helper: Extract product information from vector match
4334
+ * Helper: Extract property from metadata using mapping, AI training, case-insensitivity, and synonyms.
4064
4335
  */
4065
- static extractProductInfo(item) {
4336
+ static getDynamicVal(meta, uiKey, config, trainedSchema) {
4337
+ var _a;
4338
+ if (!meta) return void 0;
4339
+ const mapping = (_a = config == null ? void 0 : config.rag) == null ? void 0 : _a.uiMapping;
4340
+ if (mapping && mapping[uiKey]) {
4341
+ const mappedKey = mapping[uiKey];
4342
+ if (meta[mappedKey] !== void 0) return meta[mappedKey];
4343
+ }
4344
+ if (trainedSchema && typeof trainedSchema === "object" && trainedSchema !== null) {
4345
+ const trainedKey = trainedSchema[uiKey];
4346
+ if (trainedKey && meta[trainedKey] !== void 0) return meta[trainedKey];
4347
+ }
4348
+ const synonyms = this.SYNONYMS[uiKey] || [];
4349
+ const searchKeys = [uiKey, ...synonyms].map((k) => k.toLowerCase());
4350
+ const foundDirectKey = Object.keys(meta).find((k) => searchKeys.includes(k.toLowerCase()));
4351
+ if (foundDirectKey) return meta[foundDirectKey];
4352
+ const foundPartialKey = Object.keys(meta).find((k) => {
4353
+ const keyLow = k.toLowerCase();
4354
+ return searchKeys.some((sk) => keyLow.includes(sk) || sk.includes(keyLow));
4355
+ });
4356
+ return foundPartialKey ? meta[foundPartialKey] : void 0;
4357
+ }
4358
+ static extractProductInfo(item, config, trainedSchema) {
4066
4359
  const meta = item.metadata || {};
4067
- if (meta.name || meta.product) {
4360
+ const name = this.getDynamicVal(meta, "name", config, trainedSchema);
4361
+ const price = this.getDynamicVal(meta, "price", config, trainedSchema);
4362
+ const brand = this.getDynamicVal(meta, "brand", config, trainedSchema);
4363
+ if (name || this.isProductData(item)) {
4364
+ let finalName = name ? String(name) : void 0;
4365
+ if (!finalName) {
4366
+ const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
4367
+ finalName = nameMatch ? nameMatch[1].trim() : item.content.split("\n")[0].substring(0, 60);
4368
+ }
4369
+ let finalPrice = typeof price === "number" || typeof price === "string" ? price : void 0;
4370
+ if (!finalPrice) {
4371
+ const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d,.]+)/i) || item.content.match(/\$\s*([\d,.]+)/);
4372
+ if (priceMatch) finalPrice = priceMatch[1].replace(/,/g, "");
4373
+ }
4374
+ const imageValue = this.getDynamicVal(meta, "image", config, trainedSchema);
4068
4375
  return {
4069
4376
  id: item.id,
4070
- name: meta.name || meta.product,
4071
- price: meta.price,
4072
- image: meta.image || meta.imageUrl,
4073
- brand: meta.brand,
4074
- description: item.content,
4075
- inStock: this.determineStockStatus(item)
4076
- };
4077
- }
4078
- const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
4079
- const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d.]+)/i);
4080
- if (nameMatch) {
4081
- return {
4082
- id: item.id,
4083
- name: nameMatch[1],
4084
- price: priceMatch ? parseFloat(priceMatch[1]) : void 0,
4377
+ name: finalName,
4378
+ price: finalPrice,
4379
+ image: typeof imageValue === "string" ? imageValue : void 0,
4380
+ brand: brand ? String(brand) : void 0,
4085
4381
  description: item.content,
4086
4382
  inStock: this.determineStockStatus(item)
4087
4383
  };
@@ -4105,6 +4401,10 @@ var UITransformer = class {
4105
4401
  const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
4106
4402
  tags.forEach((t) => categories.add(String(t)));
4107
4403
  }
4404
+ const contentCategories = Array.from(new Set(
4405
+ Array.from(item.content.matchAll(/^\s*([^:\n]+):\s*(?:•|\*|-)*/gm)).map((match) => match[1].trim()).filter(Boolean)
4406
+ ));
4407
+ contentCategories.forEach((category) => categories.add(category));
4108
4408
  const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
4109
4409
  if (categoryMatch) {
4110
4410
  categories.add(categoryMatch[1].trim());
@@ -4225,6 +4525,225 @@ var UITransformer = class {
4225
4525
  });
4226
4526
  return fieldCount.size > 2;
4227
4527
  }
4528
+ // ─── LLM-Driven Visualization Decision ────────────────────────────────────
4529
+ /**
4530
+ * analyzeAndDecide — sends user question + RAG data to the LLM with a
4531
+ * structured system prompt and parses the JSON response into a
4532
+ * UITransformationResponse.
4533
+ *
4534
+ * This is the recommended entry point for production use. The heuristic
4535
+ * `transform()` method is used as a fallback if the LLM call fails.
4536
+ *
4537
+ * System prompt instructs the LLM to:
4538
+ * - Analyze the question and retrieved data
4539
+ * - Choose the best visualization: bar_chart | line_chart | pie_chart | table | text
4540
+ * - Return a strict JSON object — no prose, no markdown fences
4541
+ *
4542
+ * @param query - the original user question
4543
+ * @param sources - vector DB matches returned by RAG retrieval
4544
+ * @param llm - any ILLMProvider instance (OpenAI, Anthropic, Ollama, Gemini, REST…)
4545
+ * @returns - a validated UITransformationResponse (type + title + description + data)
4546
+ */
4547
+ static async analyzeAndDecide(query, sources, llm) {
4548
+ try {
4549
+ const context = this.buildContextSummary(sources);
4550
+ const systemPrompt = this.buildVisualizationSystemPrompt();
4551
+ const userPrompt = [
4552
+ `USER QUESTION: ${query}`,
4553
+ "",
4554
+ "RETRIEVED DATA (JSON):",
4555
+ context
4556
+ ].join("\n");
4557
+ const rawResponse = await llm.chat(
4558
+ [{ role: "user", content: userPrompt }],
4559
+ "",
4560
+ { systemPrompt, temperature: 0 }
4561
+ );
4562
+ const parsed = this.parseTransformationResponse(rawResponse);
4563
+ if (parsed) {
4564
+ console.debug("[UITransformer] LLM chose visualization type:", parsed.type);
4565
+ return parsed;
4566
+ }
4567
+ console.warn("[UITransformer] LLM returned unparseable response; falling back to heuristic.");
4568
+ } catch (err) {
4569
+ console.warn("[UITransformer] analyzeAndDecide LLM call failed; falling back to heuristic.", err);
4570
+ }
4571
+ return this.transform(query, sources);
4572
+ }
4573
+ /**
4574
+ * Build the system prompt that instructs the LLM to return a visualization JSON.
4575
+ */
4576
+ static buildVisualizationSystemPrompt() {
4577
+ return `You are a data visualization expert embedded in a RAG chat system.
4578
+ You will receive a user question and structured data retrieved from a vector database.
4579
+ Your ONLY job is to analyze this information and return a single JSON object that tells
4580
+ the frontend how to visualize it.
4581
+
4582
+ Return ONLY a valid JSON object \u2014 no markdown code fences, no explanation, no prose.
4583
+
4584
+ The JSON must have this exact shape:
4585
+ {
4586
+ "type": "bar_chart" | "line_chart" | "pie_chart" | "radar_chart" | "table" | "text",
4587
+ "title": "A concise, descriptive title for the visualization",
4588
+ "description": "One sentence describing what the visualization shows",
4589
+ "data": <structured data \u2014 see rules below>
4590
+ }
4591
+
4592
+ DATA SHAPE per type:
4593
+ - bar_chart: array of { "category": string, "value": number }
4594
+ - line_chart: array of { "timestamp": string, "value": number, "label": string }
4595
+ - pie_chart: array of { "label": string, "value": number }
4596
+ - radar_chart: array of { "attribute": string, "[series1]": number, "[series2]": number, ... }
4597
+ Example for radar_chart:
4598
+ [
4599
+ { "attribute": "Longevity", "Dolce Shine": 4, "CK One": 3 },
4600
+ { "attribute": "Sillage", "Dolce Shine": 3, "CK One": 4 },
4601
+ { "attribute": "Freshness", "Dolce Shine": 5, "CK One": 4 }
4602
+ ]
4603
+ - table: { "columns": string[], "rows": (string|number)[][] }
4604
+ - text: { "content": "<prose answer>" }
4605
+
4606
+ DECISION RULES (follow strictly):
4607
+ 1. bar_chart \u2192 comparing quantities across categories (e.g. sales by region, price by product). Use this when there is only ONE value per category.
4608
+ 2. line_chart \u2192 trends or changes over time (dates, months, years, sequential events)
4609
+ 3. pie_chart \u2192 proportional breakdown or percentage distribution
4610
+ 4. radar_chart \u2192 comparing 2 or more products across MULTIPLE attributes (e.g. comparing features, ratings, or characteristics of 2 specific products). If the user asks to "compare" products and the data contains multiple dimensions or ratings for them, you MUST use this.
4611
+ 5. table \u2192 multi-field structured records where each item has \u2265 3 attributes
4612
+ 6. text \u2192 conversational or free-form answers where no chart adds value
4613
+
4614
+ IMPORTANT:
4615
+ - Aggregate numeric values from the raw data \u2014 do not pass raw object arrays as data.
4616
+ - Ensure all "value" fields are numbers, not strings.
4617
+ - For bar/line/pie, keep at most 12 data points for readability.
4618
+ - Never include nested objects or arrays inside bar_chart / line_chart / pie_chart data items.`;
4619
+ }
4620
+ /**
4621
+ * Serialize retrieved vector matches into a compact JSON context string.
4622
+ * Limits the total character count to avoid exceeding LLM context windows.
4623
+ */
4624
+ static buildContextSummary(sources, maxChars = 6e3) {
4625
+ const items = sources.map((s, i) => {
4626
+ var _a, _b, _c, _d;
4627
+ return {
4628
+ index: i + 1,
4629
+ content: (_b = (_a = s.content) == null ? void 0 : _a.substring(0, 400)) != null ? _b : "",
4630
+ metadata: (_c = s.metadata) != null ? _c : {},
4631
+ score: (_d = s.score) != null ? _d : 0
4632
+ };
4633
+ });
4634
+ const full = JSON.stringify(items, null, 2);
4635
+ if (full.length <= maxChars) return full;
4636
+ const partial = [];
4637
+ let chars = 2;
4638
+ for (const item of items) {
4639
+ const chunk = JSON.stringify(item);
4640
+ if (chars + chunk.length + 2 > maxChars) break;
4641
+ partial.push(item);
4642
+ chars += chunk.length + 2;
4643
+ }
4644
+ return JSON.stringify(partial, null, 2) + "\n// ... truncated";
4645
+ }
4646
+ };
4647
+ /**
4648
+ * Central dictionary of common synonyms for UI properties.
4649
+ * This allows the system to be schema-agnostic by guessing field names.
4650
+ */
4651
+ UITransformer.SYNONYMS = {
4652
+ name: ["product", "item", "title", "label", "heading", "subject"],
4653
+ price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd"],
4654
+ brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
4655
+ image: ["imageUrl", "thumbnail", "img", "url", "photo", "picture", "media", "image_url", "main_image", "product_image", "thumb"],
4656
+ stock: ["inventory", "quantity", "count", "availability", "stock_level", "inStock", "is_available"],
4657
+ description: ["summary", "content", "body", "text", "info", "details"]
4658
+ };
4659
+
4660
+ // src/utils/SchemaMapper.ts
4661
+ var SchemaMapper = class {
4662
+ /**
4663
+ * Trains the plugin on a set of keys.
4664
+ * This is done once per schema and cached.
4665
+ */
4666
+ static async train(llm, projectId, keys) {
4667
+ const cacheKey = `${projectId}:${keys.sort().join(",")}`;
4668
+ if (this.cache.has(cacheKey)) {
4669
+ return this.cache.get(cacheKey);
4670
+ }
4671
+ console.log(`[SchemaMapper] \u{1F9E0} Training AI on new schema keys: ${keys.join(", ")}`);
4672
+ const propertyList = Object.entries(this.TARGET_PROPERTIES).map(([prop, desc]) => `- ${prop} (${desc})`).join("\n");
4673
+ const prompt = `
4674
+ Given these metadata keys from a database: [${keys.join(", ")}]
4675
+
4676
+ Identify which keys best correspond to these standard UI properties:
4677
+ ${propertyList}
4678
+
4679
+ Return ONLY a JSON object where the keys are the UI properties and the values are the matching database keys.
4680
+ If no good match is found for a property, omit it.
4681
+
4682
+ Example:
4683
+ {
4684
+ "name": "Product_Title",
4685
+ "price": "MSRP_USD",
4686
+ "brand": "VendorName"
4687
+ }
4688
+ `;
4689
+ try {
4690
+ const response = await llm.chat(
4691
+ [
4692
+ { role: "system", content: "You are a database schema expert. You ONLY respond with valid JSON." },
4693
+ { role: "user", content: prompt }
4694
+ ],
4695
+ ""
4696
+ );
4697
+ const startIdx = response.indexOf("{");
4698
+ if (startIdx !== -1) {
4699
+ let braceCount = 0;
4700
+ let endIdx = -1;
4701
+ for (let i = startIdx; i < response.length; i++) {
4702
+ if (response[i] === "{") braceCount++;
4703
+ else if (response[i] === "}") {
4704
+ braceCount--;
4705
+ if (braceCount === 0) {
4706
+ endIdx = i;
4707
+ break;
4708
+ }
4709
+ }
4710
+ }
4711
+ if (endIdx !== -1) {
4712
+ const jsonContent = response.substring(startIdx, endIdx + 1);
4713
+ const cleanJson = this.sanitizeJson(jsonContent);
4714
+ const mapping = JSON.parse(cleanJson);
4715
+ this.cache.set(cacheKey, mapping);
4716
+ return mapping;
4717
+ }
4718
+ }
4719
+ } catch (error) {
4720
+ console.warn("[SchemaMapper] AI training failed, falling back to heuristics:", error);
4721
+ }
4722
+ return {};
4723
+ }
4724
+ /**
4725
+ * Forgiving JSON parser that fixes common AI formatting mistakes.
4726
+ */
4727
+ static sanitizeJson(s) {
4728
+ return s.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "").replace(/[\u201C\u201D\u2018\u2019]/g, '"').replace(/'/g, '"').replace(/([{,]\s*)([a-zA-Z0-9_]+?)\s*:/g, '$1"$2":').replace(/,\s*([}\]])/g, "$1").trim();
4729
+ }
4730
+ static getCached(projectId, keys) {
4731
+ const cacheKey = `${projectId}:${keys.sort().join(",")}`;
4732
+ return this.cache.get(cacheKey);
4733
+ }
4734
+ };
4735
+ SchemaMapper.cache = /* @__PURE__ */ new Map();
4736
+ /**
4737
+ * Descriptions of standard UI properties to help the AI map fields accurately.
4738
+ */
4739
+ SchemaMapper.TARGET_PROPERTIES = {
4740
+ name: "The primary title, name, or label of the item",
4741
+ price: "The numeric cost, price, MSRP, or amount",
4742
+ brand: "The manufacturer, vendor, brand, or maker",
4743
+ image: "The URL to an image, thumbnail, photo, or picture",
4744
+ stock: "The availability, inventory count, quantity, or stock level",
4745
+ description: "The detailed text content, summary, or body info",
4746
+ category: "The group, department, type, or category name"
4228
4747
  };
4229
4748
 
4230
4749
  // src/core/Pipeline.ts
@@ -4273,205 +4792,31 @@ var Pipeline = class {
4273
4792
  }
4274
4793
  this.reranker = new Reranker();
4275
4794
  }
4795
+ /**
4796
+ * Expose the underlying LLM provider (set after initialize()).
4797
+ * Used by the stream handler to pass to UITransformer.analyzeAndDecide().
4798
+ */
4799
+ getLLMProvider() {
4800
+ return this.initialised ? this.llmProvider : void 0;
4801
+ }
4276
4802
  async initialize() {
4277
- var _a, _b;
4803
+ var _a;
4278
4804
  if (this.initialised) return;
4279
- const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
4280
4805
  const chartInstruction = `
4806
+ You are a helpful product assistant. Use the provided context to answer questions accurately.
4281
4807
 
4282
- ${CHART_MARKER}
4283
- ### UNIVERSAL UI PROTOCOL V7 (STRICT MODE)
4284
-
4285
- You are responsible for returning a SINGLE structured UI block when visualization is required.
4286
-
4287
- ---
4288
-
4289
- ## 1. INTENT CLASSIFICATION (MANDATORY)
4290
-
4291
- Classify the query into ONE of the following intents:
4292
-
4293
- - "explore" \u2192 user wants to browse items (e.g., "show me products")
4294
- - "analyze" \u2192 user wants aggregation/distribution (e.g., "distribution of products")
4295
- - "compare" \u2192 user wants side-by-side comparison
4296
- - "detail" \u2192 user wants structured/tabular data
4297
-
4298
- ---
4299
-
4300
- ## 2. VIEW SELECTION (DYNAMIC)
4301
-
4302
- Choose the best primary "view" ("carousel", "chart", or "table") based on the data:
4303
- - TRENDS/GROWTH \u2192 "chart" (type: "line")
4304
- - DISTRIBUTIONS/RATIOS \u2192 "chart" (type: "pie")
4305
- - COMPARISONS \u2192 "chart" (type: "bar")
4306
- - PRODUCT LISTS \u2192 "carousel"
4307
- - RAW DATA/SPECS \u2192 "table"
4308
-
4309
- \u26A0\uFE0F Dynamic Overrides:
4310
- - If query is "show products", ALWAYS use carousel.
4311
- - If data is statistical, ALWAYS use a chart.
4312
- - NEVER explain why you chose a view.
4313
-
4314
- ---
4315
-
4316
- ## 3. RESPONSE FORMAT (STRICT JSON ONLY INSIDE BLOCK)
4317
-
4318
- \`\`\`ui
4319
- {
4320
- "view": "carousel" | "chart" | "table",
4321
- "title": "string",
4322
- "description": "string",
4323
- "metadata": {
4324
- "intent": "explore" | "analyze" | "compare" | "detail",
4325
- "confidence": 0.0-1.0
4326
- },
4327
-
4328
- // CHART ONLY
4329
- "chart": {
4330
- "type": "pie" | "bar" | "line",
4331
- "xKey": "label",
4332
- "yKeys": ["value"],
4333
- "data": []
4334
- },
4335
-
4336
- // TABLE ONLY
4337
- "table": {
4338
- "columns": [],
4339
- "rows": []
4340
- },
4341
-
4342
- // CAROUSEL ONLY
4343
- "carousel": {
4344
- "items": []
4345
- },
4346
-
4347
- "insights": []
4348
- }
4349
- \`\`\`
4350
-
4351
- ---
4352
-
4353
- ## 4. DATA CONTRACT (CRITICAL)
4354
-
4355
- ### \u{1F539} CAROUSEL ITEM FORMAT
4356
- {
4357
- "id": "string",
4358
- "name": "string",
4359
- "brand": "string",
4360
- "price": number,
4361
- "image": "url",
4362
- "link": "url",
4363
- "inStock": boolean
4364
- }
4365
-
4366
- ### \u{1F539} CHART DATA FORMAT
4367
- {
4368
- "label": "string",
4369
- "value": number,
4370
- "inStockCount": number (optional),
4371
- "outOfStockCount": number (optional)
4372
- }
4373
-
4374
- ### \u{1F539} TABLE FORMAT
4375
- - columns MUST match row keys
4376
- - rows MUST be flat objects
4377
-
4378
- ---
4379
-
4380
- ## 5. DATA RULES
4381
-
4382
- - NEVER hallucinate fields or data.
4383
- - ONLY use data provided in the retrieved context.
4384
- - STRICT DATA ISOLATION: The labels and numbers in the EXAMPLES section below are placeholders. NEVER use them.
4385
- - IF NO DATA IS FOUND: You MUST NOT generate a chart. You MUST NOT say "This example assumes" or provide hypothetical numbers. Simply state "No relevant data found in the database." and STOP.
4386
- - NO META-COMMENTARY: NEVER talk about the task itself (e.g., "I've used JSON", "In a real-world app", "Dedicated library").
4387
- - NEVER explain YOUR formatting choice.
4388
- - Aggregate BEFORE rendering chart.
4389
- - DYNAMIC INTELLIGENCE: Autonomously extract relevant categories/values from the context and calculate totals for the "data" array based on the user query.
4390
-
4391
- ---
4392
-
4393
- ## 6. SMART RULES (IMPORTANT)
4394
-
4395
- - If user asks BOTH:
4396
- "show products AND distribution"
4397
- \u2192 PRIORITIZE "explore" \u2192 carousel
4398
-
4399
- - If dataset size > 20:
4400
- \u2192 aggregate \u2192 chart or table
4401
-
4402
- ---
4403
-
4404
- ## 7. OUTPUT RULES
4405
-
4406
- - ALWAYS return EXACTLY ONE \`\`\`ui\`\`\` block.
4407
- - JSON must be VALID.
4408
- - FIELD MAPPING (MANDATORY): Always use "label" for the name/category and "value" for the numeric count.
4409
- - NO xKey or yKeys: Do not include these fields; the UI uses "label" and "value" by default.
4410
- - SILENT DATA: If a chart is provided, DO NOT say "Here is the JSON" or "Below is the data". Simply output the block.
4411
- - TEXT OUTSIDE THE BLOCK: One brief sentence maximum.
4412
-
4413
- ---
4414
-
4415
- ## 8. EXAMPLES
4416
-
4417
- User: "show me beauty products"
4418
-
4419
- \`\`\`ui
4420
- {
4421
- "view": "carousel",
4422
- "title": "TITLE",
4423
- "description": "DESCRIPTION",
4424
- "metadata": {
4425
- "intent": "explore",
4426
- "confidence": 0.95
4427
- },
4428
- "carousel": {
4429
- "items": [
4430
- {
4431
- "id": "ID",
4432
- "name": "PRODUCT_NAME",
4433
- "brand": "BRAND",
4434
- "price": 0,
4435
- "image": "URL",
4436
- "link": "URL",
4437
- "inStock": true
4438
- }
4439
- ]
4440
- },
4441
- "insights": ["INSIGHT"]
4442
- }
4443
- \`\`\`
4444
-
4445
- ---
4446
-
4447
-
4808
+ ### UI STYLE RULES (CRITICAL):
4809
+ - NEVER generate markdown tables. If you do, the UI will break.
4810
+ - NEVER generate HTML tags like <figure>, <tbody>, <tr>, etc.
4811
+ - NEVER generate text-based charts or graphs.
4812
+ - ONLY use plain text and bullet points.
4448
4813
 
4449
- \`\`\`ui
4450
- {
4451
- "view": "chart",
4452
- "title": "TITLE",
4453
- "description": "DESCRIPTION",
4454
- "metadata": {
4455
- "intent": "analyze",
4456
- "confidence": 0.98
4457
- },
4458
- "chart": {
4459
- "type": "pie",
4460
- "data": [
4461
- { "label": "LABEL_A", "value": 0, "inStockCount": 0, "outOfStockCount": 0 },
4462
- { "label": "LABEL_B", "value": 0, "inStockCount": 0, "outOfStockCount": 0 }
4463
- ]
4464
- },
4465
- "insights": ["INSIGHT"]
4466
- }
4467
- \`\`\`
4814
+ ### PRODUCT DISPLAY:
4815
+ - When recommending products, simply list their names, prices, and features in a friendly, conversational manner.
4816
+ - The UI will automatically detect these products and show high-quality product cards in a carousel below your message.
4817
+ - Do NOT try to format product lists as tables.
4468
4818
  `;
4469
- if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
4470
- let cleanPrompt = this.config.llm.systemPrompt || "";
4471
- cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, "");
4472
- cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, "");
4473
- this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
4474
- }
4819
+ this.config.llm.systemPrompt = chartInstruction;
4475
4820
  console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
4476
4821
  this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
4477
4822
  const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
@@ -4486,7 +4831,7 @@ User: "show me beauty products"
4486
4831
  this.entityExtractor = new EntityExtractor(this.llmProvider);
4487
4832
  }
4488
4833
  await this.vectorDB.initialize();
4489
- if (((_b = this.config.rag) == null ? void 0 : _b.architecture) === "agentic") {
4834
+ if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic") {
4490
4835
  this.agent = new LangChainAgent(this, this.config);
4491
4836
  await this.agent.initialize(this.llmProvider);
4492
4837
  }
@@ -4689,7 +5034,12 @@ ${graphContext}
4689
5034
  VECTOR CONTEXT:
4690
5035
  ${context}`;
4691
5036
  }
4692
- const messages = [...history, { role: "user", content: question }];
5037
+ const allMetadataKeys = Array.from(new Set(sources.flatMap((s) => Object.keys(s.metadata || {}))));
5038
+ const trainingPromise = allMetadataKeys.length > 0 ? SchemaMapper.train(this.llmProvider, ns, allMetadataKeys) : Promise.resolve(void 0);
5039
+ const restrictionSuffix = "\n\n(IMPORTANT: Use plain text only. NEVER generate tables, HTML figures, or text charts. If listing products, use simple bullet points.)";
5040
+ const hardenedHistory = [...history];
5041
+ const userQuestion = { role: "user", content: question + restrictionSuffix };
5042
+ const messages = [...hardenedHistory, userQuestion];
4693
5043
  if (this.llmProvider.chatStream) {
4694
5044
  const stream = this.llmProvider.chatStream(messages, context);
4695
5045
  if (!stream) {
@@ -4714,7 +5064,8 @@ ${context}`;
4714
5064
  const reply = yield new __await(this.llmProvider.chat(messages, context));
4715
5065
  yield reply;
4716
5066
  }
4717
- const uiTransformation = UITransformer.transform(question, sources);
5067
+ const trainedSchema = yield new __await(trainingPromise);
5068
+ const uiTransformation = yield new __await(this.generateUiTransformation(question, sources, context, trainedSchema));
4718
5069
  yield {
4719
5070
  reply: "",
4720
5071
  sources,
@@ -4730,6 +5081,17 @@ ${context}`;
4730
5081
  * Universal retrieval method combining all enabled providers.
4731
5082
  * Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
4732
5083
  */
5084
+ async generateUiTransformation(question, sources, _context, trainedSchema) {
5085
+ if (!sources || sources.length === 0) {
5086
+ return UITransformer.transform(question, sources, this.config, trainedSchema);
5087
+ }
5088
+ try {
5089
+ return await UITransformer.analyzeAndDecide(question, sources, this.llmProvider);
5090
+ } catch (err) {
5091
+ console.warn("[Pipeline] generateUiTransformation failed, using heuristic fallback:", err);
5092
+ return UITransformer.transform(question, sources, this.config, trainedSchema);
5093
+ }
5094
+ }
4733
5095
  async retrieve(query, options) {
4734
5096
  var _a, _b, _c;
4735
5097
  const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
@@ -4932,6 +5294,14 @@ var VectorPlugin = class {
4932
5294
  getConfig() {
4933
5295
  return this.config;
4934
5296
  }
5297
+ /**
5298
+ * Get the initialized LLM provider (available after the first request).
5299
+ * Used by handlers to pass to UITransformer.analyzeAndDecide() for
5300
+ * LLM-driven visualization decisions.
5301
+ */
5302
+ getLLMProvider() {
5303
+ return this.pipeline.getLLMProvider();
5304
+ }
4935
5305
  /**
4936
5306
  * Perform pre-flight health checks on all configured providers.
4937
5307
  * Useful to verify connectivity before running operations.
@@ -5211,199 +5581,189 @@ var DocumentParser = class {
5211
5581
  // src/server.ts
5212
5582
  init_BaseVectorProvider();
5213
5583
  init_PineconeProvider();
5214
- init_PostgreSQLProvider();
5215
5584
 
5216
- // src/providers/vectordb/MultiTablePostgresProvider.ts
5585
+ // src/providers/vectordb/PostgreSQLProvider.ts
5217
5586
  var import_pg2 = require("pg");
5218
5587
  init_BaseVectorProvider();
5219
- var MultiTablePostgresProvider = class extends BaseVectorProvider {
5588
+ var PostgreSQLProvider = class extends BaseVectorProvider {
5220
5589
  constructor(config) {
5221
- var _a, _b, _c, _d, _e;
5590
+ var _a;
5222
5591
  super(config);
5223
- const opts = config.options || {};
5224
- if (!opts.connectionString) {
5225
- throw new Error("[MultiTablePostgresProvider] options.connectionString is required");
5226
- }
5592
+ this.tableName = this.indexName.replace(/[^a-z0-9_]/gi, "_");
5593
+ const opts = config.options;
5594
+ if (!opts.connectionString) throw new Error("[PostgreSQLProvider] options.connectionString is required");
5227
5595
  this.connectionString = opts.connectionString;
5228
- this.dimensions = (_a = opts.dimensions) != null ? _a : 768;
5229
- const rawTables = (_c = (_b = opts.tables) != null ? _b : process.env.VECTOR_DB_TABLES) != null ? _c : "";
5230
- this.tables = typeof rawTables === "string" ? rawTables.split(",").map((t) => t.trim()).filter(Boolean) : rawTables;
5231
- if (this.tables.length === 0) {
5232
- console.warn(
5233
- "[MultiTablePostgresProvider] No tables configured. Set VECTOR_DB_TABLES as a comma-separated list of table names or pass options.tables."
5234
- );
5235
- }
5236
- const rawSearchFields = (_e = (_d = opts.searchFields) != null ? _d : process.env.VECTOR_DB_SEARCH_FIELDS) != null ? _e : ["name", "product_name", "productname", "title"];
5237
- this.searchFields = typeof rawSearchFields === "string" ? rawSearchFields.split(",").map((f) => f.trim()).filter(Boolean) : rawSearchFields;
5596
+ this.dimensions = (_a = opts.dimensions) != null ? _a : 1536;
5597
+ }
5598
+ static getValidator() {
5599
+ return {
5600
+ validate(config) {
5601
+ const errors = [];
5602
+ const opts = config.options || {};
5603
+ if (!opts.connectionString) {
5604
+ errors.push({
5605
+ field: "vectorDb.options.connectionString",
5606
+ message: "PostgreSQL connection string is required",
5607
+ suggestion: "Set PGVECTOR_CONNECTION_STRING environment variable",
5608
+ severity: "error"
5609
+ });
5610
+ }
5611
+ if (opts.tables && typeof opts.tables !== "string" && !Array.isArray(opts.tables)) {
5612
+ errors.push({
5613
+ field: "vectorDb.options.tables",
5614
+ message: "PostgreSQL tables must be a string or a string array",
5615
+ severity: "error"
5616
+ });
5617
+ }
5618
+ return errors;
5619
+ }
5620
+ };
5621
+ }
5622
+ static getHealthChecker() {
5623
+ return {
5624
+ async check(config) {
5625
+ const opts = config.options || {};
5626
+ const timestamp = Date.now();
5627
+ try {
5628
+ const { Client } = await import("pg");
5629
+ const client = new Client({ connectionString: opts.connectionString });
5630
+ await client.connect();
5631
+ const result = await client.query(`
5632
+ SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'vector');
5633
+ `);
5634
+ const hasVector = result.rows[0].exists;
5635
+ await client.end();
5636
+ return {
5637
+ healthy: true,
5638
+ provider: "postgresql",
5639
+ capabilities: { pgvectorInstalled: hasVector },
5640
+ timestamp
5641
+ };
5642
+ } catch (error) {
5643
+ return {
5644
+ healthy: false,
5645
+ provider: "postgresql",
5646
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
5647
+ timestamp
5648
+ };
5649
+ }
5650
+ }
5651
+ };
5238
5652
  }
5239
5653
  async initialize() {
5240
5654
  this.pool = new import_pg2.Pool({ connectionString: this.connectionString });
5241
5655
  const client = await this.pool.connect();
5242
5656
  try {
5243
- const res = await client.query(`
5244
- SELECT table_name
5245
- FROM information_schema.columns
5246
- WHERE column_name = 'embedding'
5247
- AND table_schema = 'public'
5657
+ await client.query("CREATE EXTENSION IF NOT EXISTS vector");
5658
+ await client.query(`
5659
+ CREATE TABLE IF NOT EXISTS ${this.tableName} (
5660
+ id TEXT PRIMARY KEY,
5661
+ namespace TEXT NOT NULL DEFAULT '',
5662
+ content TEXT NOT NULL,
5663
+ metadata JSONB,
5664
+ embedding VECTOR(${this.dimensions})
5665
+ )
5666
+ `);
5667
+ await client.query(`
5668
+ CREATE INDEX IF NOT EXISTS ${this.tableName}_embedding_idx
5669
+ ON ${this.tableName}
5670
+ USING hnsw (embedding vector_cosine_ops)
5248
5671
  `);
5249
- const discoveredTables = res.rows.map((r) => r.table_name);
5250
- if (this.tables.length === 0) {
5251
- this.tables = discoveredTables;
5252
- } else {
5253
- const staticTables = [...this.tables];
5254
- this.tables = staticTables.filter((t) => discoveredTables.includes(t));
5255
- if (this.tables.length < staticTables.length) {
5256
- const missing = staticTables.filter((t) => !discoveredTables.includes(t));
5257
- console.warn(`[MultiTablePostgresProvider] Some configured tables were skipped (missing 'embedding' column): ${missing.join(", ")}`);
5258
- }
5259
- }
5260
- if (this.tables.length === 0) {
5261
- console.warn('[MultiTablePostgresProvider] No tables with "embedding" columns found in the database.');
5262
- } else {
5263
- console.log(
5264
- `[MultiTablePostgresProvider] Connected. Searching across ${this.tables.length} table(s): ${this.tables.join(", ")}`
5265
- );
5266
- }
5267
5672
  } finally {
5268
5673
  client.release();
5269
5674
  }
5270
5675
  }
5271
- /**
5272
- * Upsert is not supported for MultiTablePostgresProvider as it's designed for
5273
- * searching across pre-existing tables with varying schemas.
5274
- */
5275
- async upsert(_doc, _namespace) {
5276
- throw new Error(
5277
- "[MultiTablePostgresProvider] upsert() is not supported in multi-table mode. Please use the standard PostgreSQLProvider for single-table managed indices."
5278
- );
5279
- }
5280
- /**
5281
- * Batch upsert is not supported for MultiTablePostgresProvider.
5282
- */
5283
- async batchUpsert(_docs, _namespace) {
5284
- throw new Error(
5285
- "[MultiTablePostgresProvider] batchUpsert() is not supported in multi-table mode."
5676
+ async upsert(doc, namespace = "") {
5677
+ var _a;
5678
+ const vectorLiteral = `[${doc.vector.join(",")}]`;
5679
+ await this.pool.query(
5680
+ `INSERT INTO ${this.tableName} (id, namespace, content, metadata, embedding)
5681
+ VALUES ($1, $2, $3, $4, $5::vector)
5682
+ ON CONFLICT (id) DO UPDATE
5683
+ SET namespace = EXCLUDED.namespace,
5684
+ content = EXCLUDED.content,
5685
+ metadata = EXCLUDED.metadata,
5686
+ embedding = EXCLUDED.embedding`,
5687
+ [doc.id, namespace, doc.content, JSON.stringify((_a = doc.metadata) != null ? _a : {}), vectorLiteral]
5286
5688
  );
5287
5689
  }
5288
- /**
5289
- * Query all configured tables and merge results, sorted by cosine similarity score.
5290
- */
5291
- async query(vector, topK, _namespace, _filter) {
5292
- var _a, _b, _c;
5293
- if (!this.pool) {
5294
- throw new Error("[MultiTablePostgresProvider] Provider not initialized. Call initialize() first.");
5295
- }
5296
- const vectorLiteral = `[${vector.join(",")}]`;
5297
- const allResults = [];
5298
- console.log(`[MultiTablePostgresProvider] --- Starting Multi-Table Search ---`);
5299
- const queryText = _filter == null ? void 0 : _filter.queryText;
5300
- const entityHints = Array.isArray(_filter == null ? void 0 : _filter.__entityHints) ? _filter.__entityHints.filter(
5301
- (hint) => typeof hint === "object" && hint !== null && typeof hint.value === "string"
5302
- ).map((hint) => hint.value.trim().toLowerCase()).filter(Boolean) : [];
5303
- console.log(`[MultiTablePostgresProvider] queryText: "${queryText}"`);
5304
- console.log(`[MultiTablePostgresProvider] entityHints: [${entityHints.join(", ")}]`);
5305
- const getDynamicKeywordQuery = () => {
5306
- if (entityHints.length > 0) {
5307
- return entityHints.map((h) => h.includes(" ") ? `"${h}"` : h).join(" & ");
5308
- }
5309
- if (queryText) {
5310
- return queryText.toLowerCase().replace(/\b(show|me|the|product[s]?|item[s]?|card[s]?|of|category|find|list|browse|what|are|display|all|under|for|in|with|about)\b/g, "").trim().replace(/\s+/g, " & ");
5311
- }
5312
- return "";
5313
- };
5314
- const dynamicKeywordQuery = getDynamicKeywordQuery();
5315
- const queryPromises = this.tables.map(async (table) => {
5316
- try {
5317
- let sqlQuery = "";
5318
- let params = [];
5319
- if (queryText) {
5320
- const hasEntityHints = entityHints.length > 0;
5321
- const exactNameScoreExpr = hasEntityHints ? `+ (
5322
- SELECT COALESCE(MAX(
5323
- CASE WHEN LOWER(val) IN (${entityHints.map((h) => `'${h.replace(/'/g, "''")}'`).join(", ")})
5324
- THEN 1.0 ELSE 0.0 END
5325
- ), 0)
5326
- FROM jsonb_each_text(to_jsonb(t)) AS kv(key, val)
5327
- WHERE key IN (${this.searchFields.map((f) => `'${f}'`).join(", ")})
5328
- ) * 3.0` : "";
5329
- sqlQuery = `
5330
- SELECT *,
5331
- (1 - (embedding <=> $1::vector)) AS vector_score,
5332
- COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
5333
- ((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
5334
- FROM "${table}" t
5335
- ORDER BY hybrid_score DESC
5336
- LIMIT 50
5337
- `;
5338
- params = [vectorLiteral, dynamicKeywordQuery];
5339
- } else {
5340
- sqlQuery = `
5341
- SELECT *,
5342
- (1 - (embedding <=> $1::vector)) AS hybrid_score
5343
- FROM "${table}" t
5344
- ORDER BY hybrid_score DESC
5345
- LIMIT 50
5346
- `;
5347
- params = [vectorLiteral];
5348
- }
5349
- const result = await this.pool.query(sqlQuery, params);
5350
- if (result.rowCount && result.rowCount > 0) {
5351
- console.log(` \u2705 Table "${table}": Found ${result.rowCount} potential matches. Top score: ${result.rows[0].hybrid_score.toFixed(4)}`);
5352
- } else {
5353
- console.log(` \u26AA Table "${table}": No relevant matches found.`);
5354
- }
5355
- const tableResults = [];
5356
- for (const row of result.rows) {
5357
- const _a2 = row, { hybrid_score, id } = _a2, rest = __objRest(_a2, ["hybrid_score", "id"]);
5358
- delete rest.embedding;
5359
- delete rest.vector_score;
5360
- delete rest.keyword_score;
5361
- delete rest.exact_name_score;
5362
- const content = `[TYPE: ${table.replace(/s$/, "").toUpperCase()}]
5363
- ` + Object.entries(rest).filter(([k, v]) => v !== null && typeof v !== "object" && k !== "id").map(([k, v]) => `${k}: ${v}`).join("\n");
5364
- tableResults.push({
5365
- id: `${table}-${id}`,
5366
- score: parseFloat(String(hybrid_score)),
5367
- content,
5368
- metadata: __spreadProps(__spreadValues({}, rest), { source_table: table })
5369
- });
5370
- }
5371
- return tableResults;
5372
- } catch (err) {
5373
- console.error(
5374
- `[MultiTablePostgresProvider] Error querying table "${table}":`,
5375
- err.message
5376
- );
5377
- return [];
5690
+ async batchUpsert(docs, namespace = "") {
5691
+ if (docs.length === 0) return;
5692
+ const client = await this.pool.connect();
5693
+ try {
5694
+ await client.query("BEGIN");
5695
+ const BATCH_SIZE = 50;
5696
+ for (let i = 0; i < docs.length; i += BATCH_SIZE) {
5697
+ const batch = docs.slice(i, i + BATCH_SIZE);
5698
+ const values = [];
5699
+ const valuePlaceholders = batch.map((doc, idx) => {
5700
+ var _a;
5701
+ const offset = idx * 5;
5702
+ values.push(doc.id, namespace, doc.content, JSON.stringify((_a = doc.metadata) != null ? _a : {}), `[${doc.vector.join(",")}]`);
5703
+ return `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}::vector)`;
5704
+ }).join(", ");
5705
+ const query = `
5706
+ INSERT INTO ${this.tableName} (id, namespace, content, metadata, embedding)
5707
+ VALUES ${valuePlaceholders}
5708
+ ON CONFLICT (id) DO UPDATE
5709
+ SET namespace = EXCLUDED.namespace,
5710
+ content = EXCLUDED.content,
5711
+ metadata = EXCLUDED.metadata,
5712
+ embedding = EXCLUDED.embedding
5713
+ `;
5714
+ await client.query(query, values);
5378
5715
  }
5379
- });
5380
- const resultsArray = await Promise.all(queryPromises);
5381
- for (const tableResults of resultsArray) {
5382
- allResults.push(...tableResults);
5383
- }
5384
- const resultsByTable = {};
5385
- for (const res of allResults) {
5386
- const table = (_a = res.metadata) == null ? void 0 : _a.source_table;
5387
- if (!resultsByTable[table]) resultsByTable[table] = [];
5388
- resultsByTable[table].push(res);
5716
+ await client.query("COMMIT");
5717
+ } catch (error) {
5718
+ await client.query("ROLLBACK");
5719
+ throw error;
5720
+ } finally {
5721
+ client.release();
5389
5722
  }
5390
- const balancedResults = [];
5391
- const tables = Object.keys(resultsByTable);
5392
- for (const table of tables) {
5393
- balancedResults.push(...resultsByTable[table].slice(0, 3));
5723
+ }
5724
+ async query(vector, topK, namespace, filter) {
5725
+ const vectorLiteral = `[${vector.join(",")}]`;
5726
+ let whereClause = namespace ? `WHERE namespace = $3` : "";
5727
+ const params = [vectorLiteral, topK];
5728
+ if (namespace) params.push(namespace);
5729
+ const publicFilter = this.sanitizeFilter(filter);
5730
+ if (Object.keys(publicFilter).length > 0) {
5731
+ const filterConditions = Object.entries(publicFilter).map(([key, val]) => {
5732
+ const paramIdx = params.length + 1;
5733
+ params.push(JSON.stringify(val));
5734
+ return `metadata->>'${key}' = $${paramIdx}`;
5735
+ }).join(" AND ");
5736
+ whereClause = whereClause ? `${whereClause} AND ${filterConditions}` : `WHERE ${filterConditions}`;
5394
5737
  }
5395
- const finalSorted = balancedResults.sort((a, b) => b.score - a.score);
5396
- if (finalSorted.length > 0) {
5397
- console.log(`[MultiTablePostgresProvider] --- Search Complete ---`);
5398
- console.log(`[MultiTablePostgresProvider] Final top match from "${(_c = (_b = finalSorted[0].metadata) == null ? void 0 : _b.source_table) != null ? _c : "unknown"}" with score ${finalSorted[0].score.toFixed(4)}`);
5738
+ const client = await this.pool.connect();
5739
+ try {
5740
+ const efSearch = this.config.options.efSearch || Math.max(topK * 10, 40);
5741
+ await client.query(`SET LOCAL hnsw.ef_search = ${efSearch}`);
5742
+ const result = await client.query(
5743
+ `SELECT id, content, metadata, 1 - (embedding <=> $1::vector) AS score
5744
+ FROM ${this.tableName}
5745
+ ${whereClause}
5746
+ ORDER BY embedding <=> $1::vector
5747
+ LIMIT $2`,
5748
+ params
5749
+ );
5750
+ return result.rows.map((row) => ({
5751
+ id: String(row["id"]),
5752
+ score: parseFloat(String(row["score"])),
5753
+ content: String(row["content"]),
5754
+ metadata: row["metadata"]
5755
+ }));
5756
+ } finally {
5757
+ client.release();
5399
5758
  }
5400
- return finalSorted.slice(0, Math.max(topK, 15));
5401
5759
  }
5402
- async delete(_id, _namespace) {
5403
- console.warn("[MultiTablePostgresProvider] delete() is a no-op for multi-table mode.");
5760
+ async delete(id, namespace) {
5761
+ const where = namespace ? "WHERE id = $1 AND namespace = $2" : "WHERE id = $1";
5762
+ const params = namespace ? [id, namespace] : [id];
5763
+ await this.pool.query(`DELETE FROM ${this.tableName} ${where}`, params);
5404
5764
  }
5405
- async deleteNamespace(_namespace) {
5406
- console.warn("[MultiTablePostgresProvider] deleteNamespace() is a no-op for multi-table mode.");
5765
+ async deleteNamespace(namespace) {
5766
+ await this.pool.query(`DELETE FROM ${this.tableName} WHERE namespace = $1`, [namespace]);
5407
5767
  }
5408
5768
  async ping() {
5409
5769
  try {
@@ -5414,13 +5774,12 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
5414
5774
  }
5415
5775
  }
5416
5776
  async disconnect() {
5417
- if (this.pool) {
5418
- await this.pool.end();
5419
- }
5777
+ await this.pool.end();
5420
5778
  }
5421
5779
  };
5422
5780
 
5423
5781
  // src/server.ts
5782
+ init_MultiTablePostgresProvider();
5424
5783
  init_MongoDBProvider();
5425
5784
  init_MilvusProvider();
5426
5785
  init_QdrantProvider();
@@ -5447,7 +5806,7 @@ function sseMetaFrame(meta) {
5447
5806
  `;
5448
5807
  }
5449
5808
  function sseUIFrame(uiTransformation) {
5450
- return `data: ${JSON.stringify(__spreadValues({ type: "ui_transformation" }, uiTransformation != null ? uiTransformation : {}))}
5809
+ return `data: ${JSON.stringify({ type: "ui_transformation", data: uiTransformation })}
5451
5810
 
5452
5811
  `;
5453
5812
  }
@@ -5502,6 +5861,7 @@ function createStreamHandler(configOrPlugin) {
5502
5861
  const encoder = new TextEncoder();
5503
5862
  const stream = new ReadableStream({
5504
5863
  async start(controller) {
5864
+ var _a, _b;
5505
5865
  const enqueue = (text) => controller.enqueue(encoder.encode(text));
5506
5866
  try {
5507
5867
  const pipelineStream = plugin.chatStream(message, history, namespace);
@@ -5512,13 +5872,22 @@ function createStreamHandler(configOrPlugin) {
5512
5872
  enqueue(sseTextFrame(chunk));
5513
5873
  } else {
5514
5874
  enqueue(sseMetaFrame(chunk));
5515
- const sources = (chunk == null ? void 0 : chunk.sources) || [];
5516
- if (sources && sources.length > 0) {
5875
+ const responseChunk = chunk;
5876
+ const sources = (responseChunk == null ? void 0 : responseChunk.sources) || [];
5877
+ if (sources.length > 0) {
5517
5878
  try {
5518
- const uiTransformation = UITransformer.transform(message, sources);
5519
- enqueue(sseUIFrame(uiTransformation));
5879
+ const llmProvider = (_a = plugin.getLLMProvider) == null ? void 0 : _a.call(plugin);
5880
+ const uiTransformation = (_b = responseChunk == null ? void 0 : responseChunk.ui_transformation) != null ? _b : llmProvider ? await UITransformer.analyzeAndDecide(message, sources, llmProvider) : UITransformer.transform(message, sources, plugin.getConfig());
5881
+ if (uiTransformation) {
5882
+ enqueue(sseUIFrame(uiTransformation));
5883
+ }
5520
5884
  } catch (transformError) {
5521
5885
  console.warn("[createStreamHandler] UI transformation warning:", transformError);
5886
+ try {
5887
+ const fallback = UITransformer.transform(message, sources, plugin.getConfig());
5888
+ if (fallback) enqueue(sseUIFrame(fallback));
5889
+ } catch (e) {
5890
+ }
5522
5891
  }
5523
5892
  }
5524
5893
  }