@retrivora-ai/rag-engine 1.7.9 → 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 (47) hide show
  1. package/dist/DocumentChunker-Dh9TvmGG.d.mts +45 -0
  2. package/dist/DocumentChunker-Dh9TvmGG.d.ts +45 -0
  3. package/dist/{index-wCRqMtdX.d.mts → ILLMProvider-BfRgI1Xh.d.mts} +59 -1
  4. package/dist/{index-wCRqMtdX.d.ts → ILLMProvider-BfRgI1Xh.d.ts} +59 -1
  5. package/dist/MultiTablePostgresProvider-YY7LPNJK.mjs +8 -0
  6. package/dist/{chunk-UHGYLTTY.mjs → chunk-BFYLQYQU.mjs} +846 -458
  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 +1033 -622
  11. package/dist/handlers/index.mjs +1 -1
  12. package/dist/{index-BDqz2_Yu.d.ts → index-1Z4GuYBi.d.ts} +7 -1
  13. package/dist/{index-DhsG2o5q.d.mts → 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 +507 -352
  17. package/dist/index.mjs +427 -253
  18. package/dist/server.d.mts +35 -5
  19. package/dist/server.d.ts +35 -5
  20. package/dist/server.js +1183 -795
  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/DynamicChart.tsx +10 -35
  25. package/src/components/MessageBubble.tsx +223 -74
  26. package/src/components/ProductCard.tsx +2 -2
  27. package/src/components/VisualizationRenderer.tsx +347 -247
  28. package/src/config/RagConfig.ts +5 -0
  29. package/src/config/serverConfig.ts +3 -1
  30. package/src/core/Pipeline.ts +70 -209
  31. package/src/core/ProviderRegistry.ts +2 -2
  32. package/src/core/VectorPlugin.ts +9 -0
  33. package/src/handlers/index.ts +23 -7
  34. package/src/hooks/useRagChat.ts +2 -2
  35. package/src/llm/LLMFactory.ts +54 -2
  36. package/src/llm/providers/AnthropicProvider.ts +12 -8
  37. package/src/llm/providers/GeminiProvider.ts +188 -143
  38. package/src/llm/providers/OllamaProvider.ts +7 -3
  39. package/src/llm/providers/OpenAIProvider.ts +12 -8
  40. package/src/types/chat.ts +6 -0
  41. package/src/types/index.ts +81 -0
  42. package/src/utils/SchemaMapper.ts +129 -0
  43. package/src/utils/UITransformer.ts +524 -194
  44. package/dist/DocumentChunker-Bmscbh-X.d.ts +0 -93
  45. package/dist/DocumentChunker-DCuxrOdM.d.mts +0 -93
  46. package/dist/PostgreSQLProvider-BMOETDZA.mjs +0 -8
  47. package/dist/chunk-FLOSGE6A.mjs +0 -202
@@ -26,6 +26,18 @@ var __spreadValues = (a, b) => {
26
26
  return a;
27
27
  };
28
28
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
29
+ var __objRest = (source, exclude) => {
30
+ var target = {};
31
+ for (var prop in source)
32
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
33
+ target[prop] = source[prop];
34
+ if (source != null && __getOwnPropSymbols)
35
+ for (var prop of __getOwnPropSymbols(source)) {
36
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
37
+ target[prop] = source[prop];
38
+ }
39
+ return target;
40
+ };
29
41
  var __esm = (fn, res) => function __init() {
30
42
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
31
43
  };
@@ -322,196 +334,205 @@ var init_PineconeProvider = __esm({
322
334
  }
323
335
  });
324
336
 
325
- // src/providers/vectordb/PostgreSQLProvider.ts
326
- var PostgreSQLProvider_exports = {};
327
- __export(PostgreSQLProvider_exports, {
328
- PostgreSQLProvider: () => PostgreSQLProvider
337
+ // src/providers/vectordb/MultiTablePostgresProvider.ts
338
+ var MultiTablePostgresProvider_exports = {};
339
+ __export(MultiTablePostgresProvider_exports, {
340
+ MultiTablePostgresProvider: () => MultiTablePostgresProvider
329
341
  });
330
- var import_pg, PostgreSQLProvider;
331
- var init_PostgreSQLProvider = __esm({
332
- "src/providers/vectordb/PostgreSQLProvider.ts"() {
342
+ var import_pg, MultiTablePostgresProvider;
343
+ var init_MultiTablePostgresProvider = __esm({
344
+ "src/providers/vectordb/MultiTablePostgresProvider.ts"() {
333
345
  "use strict";
334
346
  import_pg = require("pg");
335
347
  init_BaseVectorProvider();
336
- PostgreSQLProvider = class extends BaseVectorProvider {
348
+ MultiTablePostgresProvider = class extends BaseVectorProvider {
337
349
  constructor(config) {
338
- var _a;
350
+ var _a, _b, _c, _d, _e;
339
351
  super(config);
340
- this.tableName = this.indexName.replace(/[^a-z0-9_]/gi, "_");
341
- const opts = config.options;
342
- 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
+ }
343
356
  this.connectionString = opts.connectionString;
344
- this.dimensions = (_a = opts.dimensions) != null ? _a : 1536;
345
- }
346
- static getValidator() {
347
- return {
348
- validate(config) {
349
- const errors = [];
350
- const opts = config.options || {};
351
- if (!opts.connectionString) {
352
- errors.push({
353
- field: "vectorDb.options.connectionString",
354
- message: "PostgreSQL connection string is required",
355
- suggestion: "Set PGVECTOR_CONNECTION_STRING environment variable",
356
- severity: "error"
357
- });
358
- }
359
- if (opts.tables && typeof opts.tables !== "string" && !Array.isArray(opts.tables)) {
360
- errors.push({
361
- field: "vectorDb.options.tables",
362
- message: "PostgreSQL tables must be a string or a string array",
363
- severity: "error"
364
- });
365
- }
366
- return errors;
367
- }
368
- };
369
- }
370
- static getHealthChecker() {
371
- return {
372
- async check(config) {
373
- const opts = config.options || {};
374
- const timestamp = Date.now();
375
- try {
376
- const { Client } = await import("pg");
377
- const client = new Client({ connectionString: opts.connectionString });
378
- await client.connect();
379
- const result = await client.query(`
380
- SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'vector');
381
- `);
382
- const hasVector = result.rows[0].exists;
383
- await client.end();
384
- return {
385
- healthy: true,
386
- provider: "postgresql",
387
- capabilities: { pgvectorInstalled: hasVector },
388
- timestamp
389
- };
390
- } catch (error) {
391
- return {
392
- healthy: false,
393
- provider: "postgresql",
394
- error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
395
- timestamp
396
- };
397
- }
398
- }
399
- };
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;
400
367
  }
401
368
  async initialize() {
402
369
  this.pool = new import_pg.Pool({ connectionString: this.connectionString });
403
370
  const client = await this.pool.connect();
404
371
  try {
405
- await client.query("CREATE EXTENSION IF NOT EXISTS vector");
406
- await client.query(`
407
- CREATE TABLE IF NOT EXISTS ${this.tableName} (
408
- id TEXT PRIMARY KEY,
409
- namespace TEXT NOT NULL DEFAULT '',
410
- content TEXT NOT NULL,
411
- metadata JSONB,
412
- embedding VECTOR(${this.dimensions})
413
- )
414
- `);
415
- await client.query(`
416
- CREATE INDEX IF NOT EXISTS ${this.tableName}_embedding_idx
417
- ON ${this.tableName}
418
- 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'
419
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
+ }
420
396
  } finally {
421
397
  client.release();
422
398
  }
423
399
  }
424
- async upsert(doc, namespace = "") {
425
- var _a;
426
- const vectorLiteral = `[${doc.vector.join(",")}]`;
427
- await this.pool.query(
428
- `INSERT INTO ${this.tableName} (id, namespace, content, metadata, embedding)
429
- VALUES ($1, $2, $3, $4, $5::vector)
430
- ON CONFLICT (id) DO UPDATE
431
- SET namespace = EXCLUDED.namespace,
432
- content = EXCLUDED.content,
433
- metadata = EXCLUDED.metadata,
434
- embedding = EXCLUDED.embedding`,
435
- [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."
436
407
  );
437
408
  }
438
- async batchUpsert(docs, namespace = "") {
439
- if (docs.length === 0) return;
440
- const client = await this.pool.connect();
441
- try {
442
- await client.query("BEGIN");
443
- const BATCH_SIZE = 50;
444
- for (let i = 0; i < docs.length; i += BATCH_SIZE) {
445
- const batch = docs.slice(i, i + BATCH_SIZE);
446
- const values = [];
447
- const valuePlaceholders = batch.map((doc, idx) => {
448
- var _a;
449
- const offset = idx * 5;
450
- values.push(doc.id, namespace, doc.content, JSON.stringify((_a = doc.metadata) != null ? _a : {}), `[${doc.vector.join(",")}]`);
451
- return `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}::vector)`;
452
- }).join(", ");
453
- const query = `
454
- INSERT INTO ${this.tableName} (id, namespace, content, metadata, embedding)
455
- VALUES ${valuePlaceholders}
456
- ON CONFLICT (id) DO UPDATE
457
- SET namespace = EXCLUDED.namespace,
458
- content = EXCLUDED.content,
459
- metadata = EXCLUDED.metadata,
460
- embedding = EXCLUDED.embedding
461
- `;
462
- await client.query(query, values);
463
- }
464
- await client.query("COMMIT");
465
- } catch (error) {
466
- await client.query("ROLLBACK");
467
- throw error;
468
- } finally {
469
- client.release();
470
- }
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
+ );
471
416
  }
472
- 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
+ }
473
425
  const vectorLiteral = `[${vector.join(",")}]`;
474
- let whereClause = namespace ? `WHERE namespace = $3` : "";
475
- const params = [vectorLiteral, topK];
476
- if (namespace) params.push(namespace);
477
- const publicFilter = this.sanitizeFilter(filter);
478
- if (Object.keys(publicFilter).length > 0) {
479
- const filterConditions = Object.entries(publicFilter).map(([key, val]) => {
480
- const paramIdx = params.length + 1;
481
- params.push(JSON.stringify(val));
482
- return `metadata->>'${key}' = $${paramIdx}`;
483
- }).join(" AND ");
484
- 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);
485
512
  }
486
- const client = await this.pool.connect();
487
- try {
488
- const efSearch = this.config.options.efSearch || Math.max(topK * 10, 40);
489
- await client.query(`SET LOCAL hnsw.ef_search = ${efSearch}`);
490
- const result = await client.query(
491
- `SELECT id, content, metadata, 1 - (embedding <=> $1::vector) AS score
492
- FROM ${this.tableName}
493
- ${whereClause}
494
- ORDER BY embedding <=> $1::vector
495
- LIMIT $2`,
496
- params
497
- );
498
- return result.rows.map((row) => ({
499
- id: String(row["id"]),
500
- score: parseFloat(String(row["score"])),
501
- content: String(row["content"]),
502
- metadata: row["metadata"]
503
- }));
504
- } finally {
505
- 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));
506
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)}`);
528
+ }
529
+ return finalSorted.slice(0, Math.max(topK, 15));
507
530
  }
508
- async delete(id, namespace) {
509
- const where = namespace ? "WHERE id = $1 AND namespace = $2" : "WHERE id = $1";
510
- const params = namespace ? [id, namespace] : [id];
511
- 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.");
512
533
  }
513
- async deleteNamespace(namespace) {
514
- 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.");
515
536
  }
516
537
  async ping() {
517
538
  try {
@@ -522,7 +543,9 @@ var init_PostgreSQLProvider = __esm({
522
543
  }
523
544
  }
524
545
  async disconnect() {
525
- await this.pool.end();
546
+ if (this.pool) {
547
+ await this.pool.end();
548
+ }
526
549
  }
527
550
  };
528
551
  }
@@ -1691,7 +1714,7 @@ function readEnum(env, name, fallback, allowed) {
1691
1714
  throw new Error(`[getRagConfig] ${name} must be one of: ${allowed.join(", ")}`);
1692
1715
  }
1693
1716
  function getEnvConfig(env = process.env, base) {
1694
- 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;
1717
+ 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;
1695
1718
  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__";
1696
1719
  const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
1697
1720
  const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
@@ -1702,33 +1725,35 @@ function getEnvConfig(env = process.env, base) {
1702
1725
  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 : "";
1703
1726
  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;
1704
1727
  } else if (vectorProvider === "pgvector" || vectorProvider === "postgresql") {
1705
- 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 : "";
1728
+ 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 : "";
1729
+ 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;
1730
+ 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;
1706
1731
  vectorDbOptions.dimensions = embeddingDimensions;
1707
1732
  } else if (vectorProvider === "mongodb") {
1708
- 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 : "";
1709
- 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 : "";
1710
- 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 : "";
1711
- 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";
1733
+ 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 : "";
1734
+ 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 : "";
1735
+ 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 : "";
1736
+ 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";
1712
1737
  } else if (vectorProvider === "qdrant") {
1713
- 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";
1714
- 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;
1738
+ 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";
1739
+ 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;
1715
1740
  vectorDbOptions.dimensions = embeddingDimensions;
1716
1741
  } else if (vectorProvider === "milvus") {
1717
- vectorDbOptions.baseUrl = (_P = readString(env, "MILVUS_URL")) != null ? _P : "http://localhost:19530";
1742
+ vectorDbOptions.baseUrl = (_Z = readString(env, "MILVUS_URL")) != null ? _Z : "http://localhost:19530";
1718
1743
  vectorDbOptions.apiKey = readString(env, "MILVUS_API_KEY");
1719
1744
  } else if (vectorProvider === "chromadb") {
1720
- vectorDbOptions.baseUrl = (_Q = readString(env, "CHROMADB_URL")) != null ? _Q : "http://localhost:8000";
1745
+ vectorDbOptions.baseUrl = (__ = readString(env, "CHROMADB_URL")) != null ? __ : "http://localhost:8000";
1721
1746
  } else if (vectorProvider === "weaviate") {
1722
- vectorDbOptions.baseUrl = (_R = readString(env, "WEAVIATE_URL")) != null ? _R : "http://localhost:8080";
1747
+ vectorDbOptions.baseUrl = (_$ = readString(env, "WEAVIATE_URL")) != null ? _$ : "http://localhost:8080";
1723
1748
  vectorDbOptions.apiKey = readString(env, "WEAVIATE_API_KEY");
1724
1749
  } else if (vectorProvider === "redis") {
1725
- vectorDbOptions.baseUrl = (_S = readString(env, "REDIS_URL")) != null ? _S : "";
1750
+ vectorDbOptions.baseUrl = (_aa = readString(env, "REDIS_URL")) != null ? _aa : "";
1726
1751
  vectorDbOptions.apiKey = readString(env, "REDIS_API_KEY");
1727
1752
  } else if (vectorProvider === "rest") {
1728
- vectorDbOptions.baseUrl = (_T = readString(env, "VECTOR_DB_REST_URL")) != null ? _T : "";
1753
+ vectorDbOptions.baseUrl = (_ba = readString(env, "VECTOR_DB_REST_URL")) != null ? _ba : "";
1729
1754
  vectorDbOptions.headers = readString(env, "VECTOR_DB_REST_API_KEY") ? { "api-key": readString(env, "VECTOR_DB_REST_API_KEY") } : {};
1730
1755
  } else if (vectorProvider === "universal_rest") {
1731
- vectorDbOptions.baseUrl = (_V = (_U = readString(env, "VECTOR_BASE_URL")) != null ? _U : readString(env, "VECTOR_DB_REST_URL")) != null ? _V : "";
1756
+ vectorDbOptions.baseUrl = (_da = (_ca = readString(env, "VECTOR_BASE_URL")) != null ? _ca : readString(env, "VECTOR_DB_REST_URL")) != null ? _da : "";
1732
1757
  vectorDbOptions.profile = readString(env, "VECTOR_UNIVERSAL_PROFILE");
1733
1758
  vectorDbOptions.headers = readString(env, "VECTOR_DB_REST_API_KEY") ? { Authorization: `Bearer ${readString(env, "VECTOR_DB_REST_API_KEY")}` } : {};
1734
1759
  }
@@ -1745,20 +1770,20 @@ function getEnvConfig(env = process.env, base) {
1745
1770
  openai: readString(env, "OPENAI_API_KEY"),
1746
1771
  gemini: readString(env, "GEMINI_API_KEY"),
1747
1772
  ollama: void 0,
1748
- universal_rest: (_W = readString(env, "EMBEDDING_API_KEY")) != null ? _W : readString(env, "OPENAI_API_KEY"),
1749
- custom: (_X = readString(env, "EMBEDDING_API_KEY")) != null ? _X : readString(env, "OPENAI_API_KEY")
1773
+ universal_rest: (_ea = readString(env, "EMBEDDING_API_KEY")) != null ? _ea : readString(env, "OPENAI_API_KEY"),
1774
+ custom: (_fa = readString(env, "EMBEDDING_API_KEY")) != null ? _fa : readString(env, "OPENAI_API_KEY")
1750
1775
  };
1751
1776
  return {
1752
1777
  projectId,
1753
1778
  vectorDb: {
1754
1779
  provider: vectorProvider,
1755
- indexName: (_Z = (_Y = readString(env, "VECTOR_DB_INDEX")) != null ? _Y : vectorDbOptions.indexName) != null ? _Z : "rag-index",
1780
+ indexName: (_ha = (_ga = readString(env, "VECTOR_DB_INDEX")) != null ? _ga : vectorDbOptions.indexName) != null ? _ha : "rag-index",
1756
1781
  options: vectorDbOptions
1757
1782
  },
1758
1783
  llm: {
1759
1784
  provider: llmProvider,
1760
- model: (__ = readString(env, "LLM_MODEL")) != null ? __ : "gpt-4o",
1761
- apiKey: (_$ = llmApiKeyByProvider[llmProvider]) != null ? _$ : "",
1785
+ model: (_ia = readString(env, "LLM_MODEL")) != null ? _ia : "gpt-4o",
1786
+ apiKey: (_ja = llmApiKeyByProvider[llmProvider]) != null ? _ja : "",
1762
1787
  baseUrl: readString(env, "LLM_BASE_URL"),
1763
1788
  systemPrompt: readString(env, "LLM_SYSTEM_PROMPT"),
1764
1789
  maxTokens: readNumber(env, "LLM_MAX_TOKENS", 4096),
@@ -1769,7 +1794,7 @@ function getEnvConfig(env = process.env, base) {
1769
1794
  },
1770
1795
  embedding: {
1771
1796
  provider: embeddingProvider,
1772
- model: (_aa = readString(env, "EMBEDDING_MODEL")) != null ? _aa : "text-embedding-3-small",
1797
+ model: (_ka = readString(env, "EMBEDDING_MODEL")) != null ? _ka : "text-embedding-3-small",
1773
1798
  apiKey: embeddingApiKeyByProvider[embeddingProvider],
1774
1799
  baseUrl: readString(env, "EMBEDDING_BASE_URL"),
1775
1800
  dimensions: embeddingDimensions,
@@ -1780,17 +1805,17 @@ function getEnvConfig(env = process.env, base) {
1780
1805
  }
1781
1806
  },
1782
1807
  ui: {
1783
- title: (_ca = (_ba = readString(env, "NEXT_PUBLIC_UI_TITLE")) != null ? _ba : readString(env, "UI_TITLE")) != null ? _ca : "AI Assistant",
1784
- subtitle: (_ea = (_da = readString(env, "NEXT_PUBLIC_UI_SUBTITLE")) != null ? _da : readString(env, "UI_SUBTITLE")) != null ? _ea : "Powered by RAG",
1785
- primaryColor: (_ga = (_fa = readString(env, "NEXT_PUBLIC_PRIMARY_COLOR")) != null ? _fa : readString(env, "UI_PRIMARY_COLOR")) != null ? _ga : "#10b981",
1786
- accentColor: (_ia = (_ha = readString(env, "NEXT_PUBLIC_ACCENT_COLOR")) != null ? _ha : readString(env, "UI_ACCENT_COLOR")) != null ? _ia : "#3b82f6",
1787
- logoUrl: (_ja = readString(env, "NEXT_PUBLIC_LOGO_URL")) != null ? _ja : readString(env, "UI_LOGO_URL"),
1788
- placeholder: (_la = (_ka = readString(env, "NEXT_PUBLIC_PLACEHOLDER")) != null ? _ka : readString(env, "UI_PLACEHOLDER")) != null ? _la : "Ask me anything\u2026",
1789
- showSources: ((_na = (_ma = readString(env, "NEXT_PUBLIC_SHOW_SOURCES")) != null ? _ma : readString(env, "UI_SHOW_SOURCES")) != null ? _na : "true") !== "false",
1790
- 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.",
1791
- visualStyle: (_ra = (_qa = readString(env, "NEXT_PUBLIC_UI_VISUAL_STYLE")) != null ? _qa : readString(env, "UI_VISUAL_STYLE")) != null ? _ra : "glass",
1792
- borderRadius: (_ta = (_sa = readString(env, "NEXT_PUBLIC_UI_BORDER_RADIUS")) != null ? _sa : readString(env, "UI_BORDER_RADIUS")) != null ? _ta : "xl",
1793
- allowUpload: ((_va = (_ua = readString(env, "NEXT_PUBLIC_ALLOW_UPLOAD")) != null ? _ua : readString(env, "UI_ALLOW_UPLOAD")) != null ? _va : "false") === "true"
1808
+ title: (_ma = (_la = readString(env, "NEXT_PUBLIC_UI_TITLE")) != null ? _la : readString(env, "UI_TITLE")) != null ? _ma : "AI Assistant",
1809
+ subtitle: (_oa = (_na = readString(env, "NEXT_PUBLIC_UI_SUBTITLE")) != null ? _na : readString(env, "UI_SUBTITLE")) != null ? _oa : "Powered by RAG",
1810
+ primaryColor: (_qa = (_pa = readString(env, "NEXT_PUBLIC_PRIMARY_COLOR")) != null ? _pa : readString(env, "UI_PRIMARY_COLOR")) != null ? _qa : "#10b981",
1811
+ accentColor: (_sa = (_ra = readString(env, "NEXT_PUBLIC_ACCENT_COLOR")) != null ? _ra : readString(env, "UI_ACCENT_COLOR")) != null ? _sa : "#3b82f6",
1812
+ logoUrl: (_ta = readString(env, "NEXT_PUBLIC_LOGO_URL")) != null ? _ta : readString(env, "UI_LOGO_URL"),
1813
+ placeholder: (_va = (_ua = readString(env, "NEXT_PUBLIC_PLACEHOLDER")) != null ? _ua : readString(env, "UI_PLACEHOLDER")) != null ? _va : "Ask me anything\u2026",
1814
+ showSources: ((_xa = (_wa = readString(env, "NEXT_PUBLIC_SHOW_SOURCES")) != null ? _wa : readString(env, "UI_SHOW_SOURCES")) != null ? _xa : "true") !== "false",
1815
+ 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.",
1816
+ visualStyle: (_Ba = (_Aa = readString(env, "NEXT_PUBLIC_UI_VISUAL_STYLE")) != null ? _Aa : readString(env, "UI_VISUAL_STYLE")) != null ? _Ba : "glass",
1817
+ borderRadius: (_Da = (_Ca = readString(env, "NEXT_PUBLIC_UI_BORDER_RADIUS")) != null ? _Ca : readString(env, "UI_BORDER_RADIUS")) != null ? _Da : "xl",
1818
+ allowUpload: ((_Fa = (_Ea = readString(env, "NEXT_PUBLIC_ALLOW_UPLOAD")) != null ? _Ea : readString(env, "UI_ALLOW_UPLOAD")) != null ? _Fa : "false") === "true"
1794
1819
  },
1795
1820
  rag: {
1796
1821
  topK: readNumber(env, "RAG_TOP_K", 5),
@@ -1907,14 +1932,14 @@ var OpenAIProvider = class {
1907
1932
  };
1908
1933
  }
1909
1934
  async chat(messages, context, options) {
1910
- var _a, _b, _c, _d, _e, _f, _g, _h;
1911
- const systemContent = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
1935
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1936
+ 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.
1912
1937
 
1913
1938
  Context:
1914
1939
  ${context}`;
1915
1940
  const systemMessage = {
1916
1941
  role: "system",
1917
- content: systemContent.includes("{{context}}") ? systemContent.replace("{{context}}", context) : `${systemContent}
1942
+ content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
1918
1943
 
1919
1944
  Context:
1920
1945
  ${context}`
@@ -1929,22 +1954,22 @@ ${context}`
1929
1954
  const completion = await this.client.chat.completions.create({
1930
1955
  model: this.llmConfig.model,
1931
1956
  messages: formattedMessages,
1932
- max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
1933
- temperature: (_e = (_d = options == null ? void 0 : options.temperature) != null ? _d : this.llmConfig.temperature) != null ? _e : 0.7,
1957
+ max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
1958
+ temperature: (_f = (_e = options == null ? void 0 : options.temperature) != null ? _e : this.llmConfig.temperature) != null ? _f : 0.7,
1934
1959
  stop: options == null ? void 0 : options.stop
1935
1960
  });
1936
- return (_h = (_g = (_f = completion.choices[0]) == null ? void 0 : _f.message) == null ? void 0 : _g.content) != null ? _h : "";
1961
+ return (_i = (_h = (_g = completion.choices[0]) == null ? void 0 : _g.message) == null ? void 0 : _h.content) != null ? _i : "";
1937
1962
  }
1938
1963
  chatStream(messages, context, options) {
1939
1964
  return __asyncGenerator(this, null, function* () {
1940
- var _a, _b, _c, _d, _e, _f, _g;
1941
- const systemContent = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
1965
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1966
+ 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.
1942
1967
 
1943
1968
  Context:
1944
1969
  ${context}`;
1945
1970
  const systemMessage = {
1946
1971
  role: "system",
1947
- content: systemContent.includes("{{context}}") ? systemContent.replace("{{context}}", context) : `${systemContent}
1972
+ content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
1948
1973
 
1949
1974
  Context:
1950
1975
  ${context}`
@@ -1959,8 +1984,8 @@ ${context}`
1959
1984
  const stream = yield new __await(this.client.chat.completions.create({
1960
1985
  model: this.llmConfig.model,
1961
1986
  messages: formattedMessages,
1962
- max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
1963
- temperature: (_e = (_d = options == null ? void 0 : options.temperature) != null ? _d : this.llmConfig.temperature) != null ? _e : 0.7,
1987
+ max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
1988
+ temperature: (_f = (_e = options == null ? void 0 : options.temperature) != null ? _e : this.llmConfig.temperature) != null ? _f : 0.7,
1964
1989
  stop: options == null ? void 0 : options.stop,
1965
1990
  stream: true
1966
1991
  }));
@@ -1970,7 +1995,7 @@ ${context}`
1970
1995
  try {
1971
1996
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
1972
1997
  const chunk = temp.value;
1973
- const content = ((_g = (_f = chunk.choices[0]) == null ? void 0 : _f.delta) == null ? void 0 : _g.content) || "";
1998
+ const content = ((_h = (_g = chunk.choices[0]) == null ? void 0 : _g.delta) == null ? void 0 : _h.content) || "";
1974
1999
  if (content) yield content;
1975
2000
  }
1976
2001
  } catch (temp) {
@@ -2068,12 +2093,12 @@ var AnthropicProvider = class {
2068
2093
  };
2069
2094
  }
2070
2095
  async chat(messages, context, options) {
2071
- var _a, _b, _c;
2072
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the context below to answer the user's question accurately.
2096
+ var _a, _b, _c, _d;
2097
+ 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.
2073
2098
 
2074
2099
  Context:
2075
2100
  ${context}`;
2076
- const system = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
2101
+ const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
2077
2102
 
2078
2103
  Context:
2079
2104
  ${context}`;
@@ -2083,7 +2108,7 @@ ${context}`;
2083
2108
  }));
2084
2109
  const response = await this.client.messages.create({
2085
2110
  model: this.llmConfig.model,
2086
- max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
2111
+ max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
2087
2112
  system,
2088
2113
  messages: anthropicMessages
2089
2114
  });
@@ -2092,12 +2117,12 @@ ${context}`;
2092
2117
  }
2093
2118
  chatStream(messages, context, options) {
2094
2119
  return __asyncGenerator(this, null, function* () {
2095
- var _a, _b, _c;
2096
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the context below to answer the user's question accurately.
2120
+ var _a, _b, _c, _d;
2121
+ 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.
2097
2122
 
2098
2123
  Context:
2099
2124
  ${context}`;
2100
- const system = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
2125
+ const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
2101
2126
 
2102
2127
  Context:
2103
2128
  ${context}`;
@@ -2107,7 +2132,7 @@ ${context}`;
2107
2132
  }));
2108
2133
  const stream = yield new __await(this.client.messages.create({
2109
2134
  model: this.llmConfig.model,
2110
- max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
2135
+ max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
2111
2136
  system,
2112
2137
  messages: anthropicMessages,
2113
2138
  stream: true
@@ -2222,7 +2247,7 @@ var OllamaProvider = class {
2222
2247
  }
2223
2248
  async chat(messages, context, options) {
2224
2249
  var _a, _b, _c, _d;
2225
- const system = this.buildSystemPrompt(context);
2250
+ const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
2226
2251
  const { data } = await this.http.post("/api/chat", {
2227
2252
  model: this.llmConfig.model,
2228
2253
  stream: false,
@@ -2240,7 +2265,7 @@ var OllamaProvider = class {
2240
2265
  chatStream(messages, context, options) {
2241
2266
  return __asyncGenerator(this, null, function* () {
2242
2267
  var _a, _b, _c, _d, _e, _f;
2243
- const system = this.buildSystemPrompt(context);
2268
+ const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
2244
2269
  const response = yield new __await(this.http.post("/api/chat", {
2245
2270
  model: this.llmConfig.model,
2246
2271
  stream: true,
@@ -2296,8 +2321,11 @@ var OllamaProvider = class {
2296
2321
  }
2297
2322
  });
2298
2323
  }
2299
- buildSystemPrompt(context) {
2324
+ buildSystemPrompt(context, overridePrompt) {
2300
2325
  var _a;
2326
+ if (overridePrompt) {
2327
+ return overridePrompt;
2328
+ }
2301
2329
  const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
2302
2330
 
2303
2331
  Context:
@@ -2349,20 +2377,41 @@ ${context}`;
2349
2377
  };
2350
2378
 
2351
2379
  // src/llm/providers/GeminiProvider.ts
2352
- var import_genai = require("@google/genai");
2380
+ var import_generative_ai = require("@google/generative-ai");
2381
+ var DEFAULT_EMBEDDING_MODEL = "text-embedding-004";
2382
+ var DEFAULT_TEMPERATURE = 0.7;
2383
+ var DEFAULT_MAX_TOKENS = 1024;
2384
+ function sanitizeModel(model) {
2385
+ return model ? model.split(":")[0] : model;
2386
+ }
2387
+ function buildClient(apiKey) {
2388
+ return new import_generative_ai.GoogleGenerativeAI(apiKey);
2389
+ }
2390
+ function applyPrefix(text, taskType, queryPrefix, docPrefix) {
2391
+ if (taskType === "query" && queryPrefix && !text.startsWith(queryPrefix)) {
2392
+ return `${queryPrefix}${text}`;
2393
+ }
2394
+ if (taskType === "document" && docPrefix && !text.startsWith(docPrefix)) {
2395
+ return `${docPrefix}${text}`;
2396
+ }
2397
+ return text;
2398
+ }
2353
2399
  var GeminiProvider = class {
2354
2400
  constructor(llmConfig, embeddingConfig) {
2355
- if (!llmConfig.apiKey) throw new Error("[GeminiProvider] llmConfig.apiKey is required");
2356
- this.client = new import_genai.GoogleGenAI({ apiKey: llmConfig.apiKey });
2357
- this.llmConfig = __spreadProps(__spreadValues({}, llmConfig), {
2358
- model: this.sanitizeModel(llmConfig.model)
2359
- });
2401
+ if (!llmConfig.apiKey) {
2402
+ throw new Error("[GeminiProvider] llmConfig.apiKey is required");
2403
+ }
2404
+ this.llmConfig = __spreadProps(__spreadValues({}, llmConfig), { model: sanitizeModel(llmConfig.model) });
2405
+ this.client = buildClient(this.llmConfig.apiKey);
2360
2406
  if (embeddingConfig) {
2361
2407
  this.embeddingConfig = __spreadProps(__spreadValues({}, embeddingConfig), {
2362
- model: this.sanitizeModel(embeddingConfig.model)
2408
+ model: sanitizeModel(embeddingConfig.model)
2363
2409
  });
2364
2410
  }
2365
2411
  }
2412
+ // -------------------------------------------------------------------------
2413
+ // Static factory helpers
2414
+ // -------------------------------------------------------------------------
2366
2415
  static getValidator() {
2367
2416
  return {
2368
2417
  validate(config) {
@@ -2376,7 +2425,11 @@ var GeminiProvider = class {
2376
2425
  });
2377
2426
  }
2378
2427
  if (!config.model) {
2379
- errors.push({ field: "llm.model", message: "Gemini model name is required", severity: "error" });
2428
+ errors.push({
2429
+ field: "llm.model",
2430
+ message: "Gemini model name is required",
2431
+ severity: "error"
2432
+ });
2380
2433
  }
2381
2434
  return errors;
2382
2435
  }
@@ -2385,13 +2438,15 @@ var GeminiProvider = class {
2385
2438
  static getHealthChecker() {
2386
2439
  return {
2387
2440
  async check(config) {
2441
+ var _a, _b;
2388
2442
  const timestamp = Date.now();
2389
- const apiKey = config.apiKey || process.env.GOOGLE_GENAI_API_KEY || "";
2390
- const modelName = config.model;
2443
+ const apiKey = (_b = (_a = config.apiKey) != null ? _a : process.env.GOOGLE_GENAI_API_KEY) != null ? _b : "";
2444
+ const modelName = sanitizeModel(config.model);
2391
2445
  try {
2392
- const { GoogleGenAI: GoogleGenAI2 } = await import("@google/genai");
2393
- const genAI = new GoogleGenAI2({ apiKey });
2394
- await genAI.models.get({ model: modelName });
2446
+ const { GoogleGenerativeAI: GoogleGenerativeAI2 } = await import("@google/generative-ai");
2447
+ const ai = new GoogleGenerativeAI2(apiKey);
2448
+ const model = ai.getGenerativeModel({ model: DEFAULT_EMBEDDING_MODEL });
2449
+ await model.embedContent("health-check");
2395
2450
  return {
2396
2451
  healthy: true,
2397
2452
  provider: "gemini",
@@ -2409,70 +2464,90 @@ var GeminiProvider = class {
2409
2464
  }
2410
2465
  };
2411
2466
  }
2412
- sanitizeModel(model) {
2413
- if (!model) return model;
2414
- return model.split(":")[0];
2467
+ // -------------------------------------------------------------------------
2468
+ // Private utilities
2469
+ // -------------------------------------------------------------------------
2470
+ /** Resolve the embedding client — uses a separate client when the embedding
2471
+ * API key differs from the LLM API key. */
2472
+ get embeddingClient() {
2473
+ var _a;
2474
+ if (((_a = this.embeddingConfig) == null ? void 0 : _a.apiKey) && this.embeddingConfig.apiKey !== this.llmConfig.apiKey) {
2475
+ return buildClient(this.embeddingConfig.apiKey);
2476
+ }
2477
+ return this.client;
2478
+ }
2479
+ /** Resolve the embedding model to use, in order of specificity. */
2480
+ resolveEmbeddingModel(optionsModel) {
2481
+ var _a, _b;
2482
+ return sanitizeModel((_b = optionsModel != null ? optionsModel : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _b : DEFAULT_EMBEDDING_MODEL);
2415
2483
  }
2416
- async chat(messages, context, options) {
2417
- var _a, _b, _c, _d, _e, _f;
2418
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
2484
+ /**
2485
+ * Build the system instruction string, inserting the RAG context either via
2486
+ * the `{{context}}` placeholder or by appending it.
2487
+ */
2488
+ buildSystemInstruction(context) {
2489
+ var _a;
2490
+ const base = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
2419
2491
 
2420
2492
  Context:
2421
2493
  ${context}`;
2422
- const systemInstruction = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
2494
+ return base.includes("{{context}}") ? base.replace("{{context}}", context) : `${base}
2423
2495
 
2424
2496
  Context:
2425
2497
  ${context}`;
2426
- const geminiMessages = messages.map((m) => ({
2498
+ }
2499
+ /**
2500
+ * Convert ChatMessage[] to the Gemini contents format.
2501
+ *
2502
+ * Rules enforced by the Gemini API:
2503
+ * - Only `user` and `model` roles are allowed in `contents`.
2504
+ * - System messages must be passed via `systemInstruction`, not here.
2505
+ * - Messages must strictly alternate between `user` and `model`.
2506
+ */
2507
+ buildGeminiContents(messages) {
2508
+ return messages.filter((m) => m.role !== "system").map((m) => ({
2427
2509
  role: m.role === "assistant" ? "model" : "user",
2428
2510
  parts: [{ text: m.content }]
2429
2511
  }));
2430
- const response = await this.client.models.generateContent({
2512
+ }
2513
+ // -------------------------------------------------------------------------
2514
+ // ILLMProvider — chat
2515
+ // -------------------------------------------------------------------------
2516
+ async chat(messages, context, options) {
2517
+ var _a, _b, _c, _d;
2518
+ const model = this.client.getGenerativeModel({
2431
2519
  model: this.llmConfig.model,
2432
- contents: geminiMessages,
2433
- config: {
2434
- systemInstruction,
2435
- temperature: (_c = (_b = options == null ? void 0 : options.temperature) != null ? _b : this.llmConfig.temperature) != null ? _c : 0.7,
2436
- maxOutputTokens: (_e = (_d = options == null ? void 0 : options.maxTokens) != null ? _d : this.llmConfig.maxTokens) != null ? _e : 1024,
2520
+ systemInstruction: this.buildSystemInstruction(context)
2521
+ });
2522
+ const result = await model.generateContent({
2523
+ contents: this.buildGeminiContents(messages),
2524
+ generationConfig: {
2525
+ temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
2526
+ maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
2437
2527
  stopSequences: options == null ? void 0 : options.stop
2438
2528
  }
2439
2529
  });
2440
- const text = typeof response.text === "function" ? response.text() : (_f = response.text) != null ? _f : "";
2441
- return text;
2530
+ return result.response.text();
2442
2531
  }
2443
2532
  chatStream(messages, context, options) {
2444
2533
  return __asyncGenerator(this, null, function* () {
2445
- var _a, _b, _c, _d, _e, _f;
2446
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
2447
-
2448
- Context:
2449
- ${context}`;
2450
- const systemInstruction = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
2451
-
2452
- Context:
2453
- ${context}`;
2454
- const geminiMessages = messages.map((m) => ({
2455
- role: m.role === "assistant" ? "model" : "user",
2456
- parts: [{ text: m.content }]
2457
- }));
2458
- const result = yield new __await(this.client.models.generateContentStream({
2534
+ var _a, _b, _c, _d;
2535
+ const model = this.client.getGenerativeModel({
2459
2536
  model: this.llmConfig.model,
2460
- contents: geminiMessages,
2461
- config: {
2462
- systemInstruction,
2463
- temperature: (_c = (_b = options == null ? void 0 : options.temperature) != null ? _b : this.llmConfig.temperature) != null ? _c : 0.7,
2464
- maxOutputTokens: (_e = (_d = options == null ? void 0 : options.maxTokens) != null ? _d : this.llmConfig.maxTokens) != null ? _e : 1024,
2537
+ systemInstruction: this.buildSystemInstruction(context)
2538
+ });
2539
+ const result = yield new __await(model.generateContentStream({
2540
+ contents: this.buildGeminiContents(messages),
2541
+ generationConfig: {
2542
+ temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
2543
+ maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
2465
2544
  stopSequences: options == null ? void 0 : options.stop
2466
2545
  }
2467
2546
  }));
2468
- const stream = (_f = result == null ? void 0 : result.stream) != null ? _f : result;
2469
- if (!stream) {
2470
- throw new Error("[GeminiProvider] generateContentStream returned undefined");
2471
- }
2472
2547
  try {
2473
- for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
2548
+ for (var iter = __forAwait(result.stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
2474
2549
  const chunk = temp.value;
2475
- const text = typeof chunk.text === "function" ? chunk.text() : chunk.text;
2550
+ const text = chunk.text();
2476
2551
  if (text) yield text;
2477
2552
  }
2478
2553
  } catch (temp) {
@@ -2487,69 +2562,69 @@ ${context}`;
2487
2562
  }
2488
2563
  });
2489
2564
  }
2565
+ // -------------------------------------------------------------------------
2566
+ // ILLMProvider — embeddings
2567
+ // -------------------------------------------------------------------------
2490
2568
  async embed(text, options) {
2491
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2492
- const model = this.sanitizeModel(
2493
- (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "text-embedding-004"
2569
+ var _a, _b;
2570
+ const content = applyPrefix(
2571
+ text,
2572
+ options == null ? void 0 : options.taskType,
2573
+ (_a = this.embeddingConfig) == null ? void 0 : _a.queryPrefix,
2574
+ (_b = this.embeddingConfig) == null ? void 0 : _b.documentPrefix
2494
2575
  );
2495
- const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
2496
- const client = apiKey !== this.llmConfig.apiKey ? new import_genai.GoogleGenAI({ apiKey }) : this.client;
2497
- let content = text;
2498
- const queryPrefix = (_f = this.embeddingConfig) == null ? void 0 : _f.queryPrefix;
2499
- const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
2500
- if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
2501
- if (!content.startsWith(queryPrefix)) {
2502
- content = `${queryPrefix}${text}`;
2503
- }
2504
- } else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
2505
- if (!content.startsWith(docPrefix)) {
2506
- content = `${docPrefix}${text}`;
2507
- }
2508
- }
2509
- const response = await client.models.embedContent({
2510
- model,
2511
- contents: content,
2512
- config: ((_h = this.embeddingConfig) == null ? void 0 : _h.dimensions) ? { outputDimensionality: this.embeddingConfig.dimensions } : void 0
2576
+ const modelName = this.resolveEmbeddingModel(options == null ? void 0 : options.model);
2577
+ const model = this.embeddingClient.getGenerativeModel({ model: modelName });
2578
+ const response = await model.embedContent({
2579
+ content: { role: "user", parts: [{ text: content }] }
2513
2580
  }).catch((err) => {
2514
- console.error(`[GeminiProvider] Embedding failed for model "${model}":`, err.message);
2581
+ console.error(`[GeminiProvider] Embedding failed for model "${modelName}":`, err.message);
2515
2582
  throw err;
2516
2583
  });
2517
- return (_k = (_j = (_i = response.embeddings) == null ? void 0 : _i[0]) == null ? void 0 : _j.values) != null ? _k : [];
2584
+ return response.embedding.values;
2518
2585
  }
2519
2586
  async batchEmbed(texts, options) {
2520
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
2521
- const model = this.sanitizeModel(
2522
- (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "text-embedding-004"
2523
- );
2524
- const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
2525
- const client = apiKey !== this.llmConfig.apiKey ? new import_genai.GoogleGenAI({ apiKey }) : this.client;
2526
- let contents = texts;
2527
- const queryPrefix = (_f = this.embeddingConfig) == null ? void 0 : _f.queryPrefix;
2528
- const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
2529
- if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
2530
- contents = texts.map((text) => text.startsWith(queryPrefix) ? text : `${queryPrefix}${text}`);
2531
- } else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
2532
- contents = texts.map((text) => text.startsWith(docPrefix) ? text : `${docPrefix}${text}`);
2533
- }
2534
- const response = await client.models.embedContent({
2535
- model,
2536
- contents,
2537
- config: ((_h = this.embeddingConfig) == null ? void 0 : _h.dimensions) ? { outputDimensionality: this.embeddingConfig.dimensions } : void 0
2587
+ const modelName = this.resolveEmbeddingModel(options == null ? void 0 : options.model);
2588
+ const model = this.embeddingClient.getGenerativeModel({ model: modelName });
2589
+ const requests = texts.map((text) => {
2590
+ var _a, _b;
2591
+ return {
2592
+ content: {
2593
+ role: "user",
2594
+ parts: [{
2595
+ text: applyPrefix(
2596
+ text,
2597
+ options == null ? void 0 : options.taskType,
2598
+ (_a = this.embeddingConfig) == null ? void 0 : _a.queryPrefix,
2599
+ (_b = this.embeddingConfig) == null ? void 0 : _b.documentPrefix
2600
+ )
2601
+ }]
2602
+ }
2603
+ };
2604
+ });
2605
+ const response = await model.batchEmbedContents({
2606
+ requests: requests.map((r) => ({
2607
+ model: `models/${modelName}`,
2608
+ content: r.content
2609
+ }))
2538
2610
  }).catch((err) => {
2539
- console.error(`[GeminiProvider] Batch embedding failed for model "${model}":`, err.message);
2611
+ console.error(`[GeminiProvider] Batch embedding failed for model "${modelName}":`, err.message);
2540
2612
  throw err;
2541
2613
  });
2542
- return (_j = (_i = response.embeddings) == null ? void 0 : _i.map((e) => {
2543
- var _a2;
2544
- return (_a2 = e.values) != null ? _a2 : [];
2545
- })) != null ? _j : [];
2614
+ return response.embeddings.map((e) => {
2615
+ var _a;
2616
+ return (_a = e.values) != null ? _a : [];
2617
+ });
2546
2618
  }
2619
+ // -------------------------------------------------------------------------
2620
+ // ILLMProvider — health
2621
+ // -------------------------------------------------------------------------
2547
2622
  async ping() {
2548
2623
  try {
2549
- await this.client.models.embedContent({
2550
- model: "text-embedding-004",
2551
- contents: "ping"
2624
+ const model = this.embeddingClient.getGenerativeModel({
2625
+ model: this.resolveEmbeddingModel()
2552
2626
  });
2627
+ await model.embedContent("ping");
2553
2628
  return true;
2554
2629
  } catch (err) {
2555
2630
  console.error("[GeminiProvider] Ping failed:", err);
@@ -2706,9 +2781,51 @@ ${context != null ? context : "None"}` },
2706
2781
  };
2707
2782
 
2708
2783
  // src/llm/LLMFactory.ts
2784
+ var customProviders = /* @__PURE__ */ new Map();
2709
2785
  var LLMFactory = class _LLMFactory {
2786
+ /**
2787
+ * Register a custom LLM provider factory at runtime.
2788
+ *
2789
+ * Use this to add support for any LLM backend (Azure OpenAI, Cohere, Mistral,
2790
+ * Bedrock, etc.) without modifying this library's source code.
2791
+ *
2792
+ * @example
2793
+ * // In your Next.js app initialization:
2794
+ * import { LLMFactory } from '@retrivora-ai/rag-engine/server';
2795
+ * import { MyCustomProvider } from './providers/MyCustomProvider';
2796
+ *
2797
+ * LLMFactory.register('my-provider', (config) => new MyCustomProvider(config));
2798
+ *
2799
+ * // Then set in your .env.local:
2800
+ * // LLM_PROVIDER=my-provider
2801
+ */
2802
+ static register(name, factory) {
2803
+ customProviders.set(name.toLowerCase(), factory);
2804
+ console.log(`[LLMFactory] Registered custom provider: "${name}"`);
2805
+ }
2806
+ /**
2807
+ * Unregister a previously registered custom provider.
2808
+ */
2809
+ static unregister(name) {
2810
+ customProviders.delete(name.toLowerCase());
2811
+ }
2812
+ /**
2813
+ * List all registered provider names (built-in + custom).
2814
+ */
2815
+ static listProviders() {
2816
+ return [
2817
+ "openai",
2818
+ "anthropic",
2819
+ "ollama",
2820
+ "gemini",
2821
+ "rest",
2822
+ "universal_rest",
2823
+ "custom",
2824
+ ...Array.from(customProviders.keys())
2825
+ ];
2826
+ }
2710
2827
  static create(llmConfig, embeddingConfig) {
2711
- var _a;
2828
+ var _a, _b;
2712
2829
  switch (llmConfig.provider) {
2713
2830
  case "openai":
2714
2831
  return new OpenAIProvider(llmConfig, embeddingConfig);
@@ -2722,11 +2839,19 @@ var LLMFactory = class _LLMFactory {
2722
2839
  case "universal_rest":
2723
2840
  case "custom":
2724
2841
  return new UniversalLLMAdapter(llmConfig);
2725
- default:
2726
- if (llmConfig.baseUrl || ((_a = llmConfig.options) == null ? void 0 : _a.baseUrl)) {
2842
+ default: {
2843
+ const providerName = String((_a = llmConfig.provider) != null ? _a : "").toLowerCase();
2844
+ const customFactory = customProviders.get(providerName);
2845
+ if (customFactory) {
2846
+ return customFactory(llmConfig);
2847
+ }
2848
+ if (llmConfig.baseUrl || ((_b = llmConfig.options) == null ? void 0 : _b.baseUrl)) {
2727
2849
  return new UniversalLLMAdapter(llmConfig);
2728
2850
  }
2729
- throw new Error(`[LLMFactory] Unknown provider "${llmConfig.provider}"`);
2851
+ throw new Error(
2852
+ `[LLMFactory] Unknown provider "${llmConfig.provider}". Built-in providers: ${_LLMFactory.listProviders().join(", ")}. Register a custom provider with LLMFactory.register().`
2853
+ );
2854
+ }
2730
2855
  }
2731
2856
  }
2732
2857
  static getValidator(provider) {
@@ -2816,8 +2941,8 @@ var ProviderRegistry = class {
2816
2941
  }
2817
2942
  case "pgvector":
2818
2943
  case "postgresql": {
2819
- const { PostgreSQLProvider: PostgreSQLProvider2 } = await Promise.resolve().then(() => (init_PostgreSQLProvider(), PostgreSQLProvider_exports));
2820
- return PostgreSQLProvider2;
2944
+ const { MultiTablePostgresProvider: MultiTablePostgresProvider2 } = await Promise.resolve().then(() => (init_MultiTablePostgresProvider(), MultiTablePostgresProvider_exports));
2945
+ return MultiTablePostgresProvider2;
2821
2946
  }
2822
2947
  case "mongodb": {
2823
2948
  const { MongoDBProvider: MongoDBProvider2 } = await Promise.resolve().then(() => (init_MongoDBProvider(), MongoDBProvider_exports));
@@ -3779,69 +3904,40 @@ var QueryProcessor = class {
3779
3904
  var UITransformer = class {
3780
3905
  /**
3781
3906
  * Main transformation method
3782
- * Analyzes user query and retrieved data to determine the best visualization format
3783
- *
3784
- * @param userQuery - The original user question/query
3785
- * @param retrievedData - Vector database retrieval results
3786
- * @returns Structured JSON response for UI rendering
3907
+ * Analyzes user query and retrieved data to determine if a product carousel is needed.
3787
3908
  */
3788
- static transform(userQuery, retrievedData) {
3909
+ static transform(userQuery, retrievedData, config, trainedSchema) {
3789
3910
  if (!retrievedData || retrievedData.length === 0) {
3790
3911
  return this.createTextResponse("No data available", "No relevant data found for your query.");
3791
3912
  }
3792
- const analysis = this.analyzeData(userQuery, retrievedData);
3793
- switch (analysis.suggestedType) {
3794
- case "product_carousel":
3795
- return this.transformToProductCarousel(retrievedData);
3796
- case "pie_chart":
3797
- return this.transformToPieChart(retrievedData);
3798
- case "bar_chart":
3799
- return this.transformToBarChart(retrievedData);
3800
- case "line_chart":
3801
- return this.transformToLineChart(retrievedData);
3802
- case "table":
3803
- return this.transformToTable(retrievedData);
3804
- default:
3805
- return this.transformToText(retrievedData);
3806
- }
3807
- }
3808
- /**
3809
- * Analyzes the data and query to suggest the best visualization type
3810
- */
3811
- static analyzeData(query, data) {
3812
- const queryLower = query.toLowerCase();
3813
- const hasProducts = data.some(
3814
- (item) => this.isProductData(item)
3815
- );
3816
- const hasTimeSeries = data.some(
3817
- (item) => this.isTimeSeriesData(item)
3818
- );
3819
- const hasCategories = this.detectCategories(data).length > 0;
3820
- const isDistributionQuery = queryLower.includes("distribution") || queryLower.includes("breakdown") || queryLower.includes("percentage") || queryLower.includes("proportion");
3821
- const isComparisonQuery = queryLower.includes("compare") || queryLower.includes("vs") || queryLower.includes("difference");
3822
- const isTrendQuery = queryLower.includes("trend") || queryLower.includes("over time") || queryLower.includes("growth") || queryLower.includes("decline");
3823
- if (hasProducts && !hasTimeSeries && !isDistributionQuery) {
3824
- return { suggestedType: "product_carousel", confidence: 0.95, hasProducts, hasTimeSeries, hasCategories };
3913
+ const isStockRequest = this.isStockQuery(userQuery);
3914
+ const filteredData = isStockRequest ? retrievedData.filter((item) => this.determineStockStatus(item)) : retrievedData;
3915
+ const categories = this.detectCategories(filteredData);
3916
+ const hasProducts = filteredData.some((item) => this.isProductData(item));
3917
+ const isTimeSeries = filteredData.some((item) => this.isTimeSeriesData(item));
3918
+ const isTrendQuery = this.isTrendQuery(userQuery);
3919
+ if (isTrendQuery && isTimeSeries) {
3920
+ return this.transformToLineChart(filteredData);
3825
3921
  }
3826
- if (isTrendQuery && hasTimeSeries) {
3827
- return { suggestedType: "line_chart", confidence: 0.9, hasProducts, hasTimeSeries, hasCategories };
3922
+ if (hasProducts && !this.shouldShowCategoryChart(userQuery, categories)) {
3923
+ return this.transformToProductCarousel(filteredData, config, trainedSchema);
3828
3924
  }
3829
- if (isDistributionQuery && hasCategories) {
3830
- return { suggestedType: "pie_chart", confidence: 0.85, hasProducts, hasTimeSeries, hasCategories };
3925
+ if (categories.length > 1 && this.shouldShowCategoryChart(userQuery, categories)) {
3926
+ return this.transformToPieChart(filteredData);
3831
3927
  }
3832
- if (isComparisonQuery && hasCategories && data.length > 2) {
3833
- return { suggestedType: "bar_chart", confidence: 0.8, hasProducts, hasTimeSeries, hasCategories };
3928
+ if (hasProducts) {
3929
+ return this.transformToProductCarousel(filteredData, config, trainedSchema);
3834
3930
  }
3835
- if (data.length > 5 && this.hasMultipleFields(data)) {
3836
- return { suggestedType: "table", confidence: 0.75, hasProducts, hasTimeSeries, hasCategories };
3931
+ if (this.hasMultipleFields(filteredData)) {
3932
+ return this.transformToTable(filteredData);
3837
3933
  }
3838
- return { suggestedType: "text", confidence: 0.5, hasProducts, hasTimeSeries, hasCategories };
3934
+ return this.transformToText(filteredData);
3839
3935
  }
3840
3936
  /**
3841
3937
  * Transform data to product carousel format
3842
3938
  */
3843
- static transformToProductCarousel(data) {
3844
- const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item)).filter((p) => p !== null);
3939
+ static transformToProductCarousel(data, config, trainedSchema) {
3940
+ const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item, config, trainedSchema)).filter((p) => p !== null);
3845
3941
  return {
3846
3942
  type: "product_carousel",
3847
3943
  title: "Recommended Products",
@@ -3855,11 +3951,15 @@ var UITransformer = class {
3855
3951
  static transformToPieChart(data) {
3856
3952
  const categories = this.detectCategories(data);
3857
3953
  const categoryData = this.aggregateByCategory(data, categories);
3858
- const pieData = Object.entries(categoryData).map(([label, count]) => ({
3859
- label,
3860
- value: count,
3861
- inStock: this.checkStockStatus(label, data)
3862
- }));
3954
+ const pieData = Object.entries(categoryData).map(([label, count]) => {
3955
+ const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data);
3956
+ return {
3957
+ label,
3958
+ value: count,
3959
+ inStockCount,
3960
+ outOfStockCount
3961
+ };
3962
+ });
3863
3963
  return {
3864
3964
  type: "pie_chart",
3865
3965
  title: "Distribution by Category",
@@ -3873,11 +3973,15 @@ var UITransformer = class {
3873
3973
  static transformToBarChart(data) {
3874
3974
  const categories = this.detectCategories(data);
3875
3975
  const categoryData = this.aggregateByCategory(data, categories);
3876
- const barData = Object.entries(categoryData).map(([category, value]) => ({
3877
- category,
3878
- value,
3879
- inStock: this.checkStockStatus(category, data)
3880
- }));
3976
+ const barData = Object.entries(categoryData).map(([category, value]) => {
3977
+ const { inStockCount, outOfStockCount } = this.calculateStockCounts(category, data);
3978
+ return {
3979
+ category,
3980
+ value,
3981
+ inStockCount,
3982
+ outOfStockCount
3983
+ };
3984
+ });
3881
3985
  return {
3882
3986
  type: "bar_chart",
3883
3987
  title: "Comparison by Category",
@@ -3941,49 +4045,261 @@ var UITransformer = class {
3941
4045
  data: { content }
3942
4046
  };
3943
4047
  }
4048
+ static parseTransformationResponse(raw) {
4049
+ const payloadText = this.extractJsonCandidate(raw);
4050
+ if (!payloadText) return null;
4051
+ try {
4052
+ const parsed = JSON.parse(payloadText);
4053
+ return this.normalizeTransformation(parsed);
4054
+ } catch (e) {
4055
+ return null;
4056
+ }
4057
+ }
4058
+ static extractJsonCandidate(raw) {
4059
+ if (!raw) return null;
4060
+ const cleaned = raw.replace(/```(?:json|chart|ui)?\s*/gi, "").replace(/```/g, "").trim();
4061
+ const start = cleaned.indexOf("{");
4062
+ if (start === -1) return null;
4063
+ let depth = 0;
4064
+ let inString = false;
4065
+ let escape = false;
4066
+ for (let i = start; i < cleaned.length; i += 1) {
4067
+ const char = cleaned[i];
4068
+ if (escape) {
4069
+ escape = false;
4070
+ continue;
4071
+ }
4072
+ if (char === "\\") {
4073
+ escape = true;
4074
+ continue;
4075
+ }
4076
+ if (char === '"') {
4077
+ inString = !inString;
4078
+ continue;
4079
+ }
4080
+ if (inString) continue;
4081
+ if (char === "{") depth += 1;
4082
+ if (char === "}") {
4083
+ depth -= 1;
4084
+ if (depth === 0) {
4085
+ return cleaned.slice(start, i + 1);
4086
+ }
4087
+ }
4088
+ }
4089
+ return null;
4090
+ }
4091
+ static normalizeTransformation(payload) {
4092
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
4093
+ if (!payload || typeof payload !== "object") return null;
4094
+ const payloadObj = payload;
4095
+ const type = this.normalizeVisualizationType(String((_c = (_b = (_a = payloadObj.type) != null ? _a : payloadObj.view) != null ? _b : payloadObj.chartType) != null ? _c : ""));
4096
+ if (!type) return null;
4097
+ const title = String((_e = (_d = payloadObj.title) != null ? _d : payloadObj.heading) != null ? _e : "Visualization");
4098
+ const description = payloadObj.description ? String(payloadObj.description) : void 0;
4099
+ 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;
4100
+ const data = type === "text" && typeof rawData === "string" ? { content: rawData } : rawData;
4101
+ const transformation = {
4102
+ type,
4103
+ title,
4104
+ description,
4105
+ data
4106
+ };
4107
+ return this.validateTransformation(transformation) ? transformation : null;
4108
+ }
4109
+ static normalizeVisualizationType(type) {
4110
+ var _a;
4111
+ const mapping = {
4112
+ pie: "pie_chart",
4113
+ pie_chart: "pie_chart",
4114
+ bar: "bar_chart",
4115
+ bar_chart: "bar_chart",
4116
+ line: "line_chart",
4117
+ line_chart: "line_chart",
4118
+ radar: "radar_chart",
4119
+ radar_chart: "radar_chart",
4120
+ table: "table",
4121
+ text: "text",
4122
+ product_carousel: "product_carousel",
4123
+ carousel: "carousel"
4124
+ };
4125
+ return (_a = mapping[type.toLowerCase()]) != null ? _a : null;
4126
+ }
4127
+ static validateTransformation(transformation) {
4128
+ const { type, data } = transformation;
4129
+ switch (type) {
4130
+ case "pie_chart":
4131
+ case "bar_chart":
4132
+ return Array.isArray(data) && data.every(
4133
+ (item) => item !== null && typeof item === "object" && (typeof item.value === "number" || !Number.isNaN(Number(item.value)))
4134
+ );
4135
+ case "line_chart":
4136
+ return Array.isArray(data) && data.every(
4137
+ (item) => item !== null && typeof item === "object" && "timestamp" in item && (typeof item.value === "number" || !Number.isNaN(Number(item.value)))
4138
+ );
4139
+ case "radar_chart":
4140
+ return Array.isArray(data) && data.every(
4141
+ (item) => item !== null && typeof item === "object" && "attribute" in item
4142
+ );
4143
+ case "table":
4144
+ return typeof data === "object" && data !== null && Array.isArray(data.columns) && Array.isArray(data.rows);
4145
+ case "text":
4146
+ return typeof data === "object" && data !== null && typeof data.content === "string";
4147
+ case "product_carousel":
4148
+ case "carousel":
4149
+ return Array.isArray(data) && data.every(
4150
+ (item) => item !== null && typeof item === "object" && ("id" in item || "name" in item)
4151
+ );
4152
+ default:
4153
+ return false;
4154
+ }
4155
+ }
3944
4156
  /**
3945
4157
  * Helper: Check if data item is product-related
3946
4158
  */
3947
4159
  static isProductData(item) {
3948
4160
  const content = (item.content || "").toLowerCase();
3949
- const productKeywords = ["product", "price", "stock", "item", "sku", "brand", "model"];
3950
- return productKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
3951
- (k) => ["name", "price", "product", "sku"].includes(k.toLowerCase())
4161
+ const productKeywords = [
4162
+ "product",
4163
+ "price",
4164
+ "stock",
4165
+ "item",
4166
+ "sku",
4167
+ "brand",
4168
+ "model",
4169
+ "msrp",
4170
+ "inventory",
4171
+ "buy",
4172
+ "shop",
4173
+ "beauty",
4174
+ "care",
4175
+ "cosmetic",
4176
+ "facial",
4177
+ "cream",
4178
+ "serum",
4179
+ "mask",
4180
+ "makeup",
4181
+ "fragrance"
4182
+ ];
4183
+ const hasKeywords = productKeywords.some((kw) => content.includes(kw));
4184
+ const hasMetadataKey = Object.keys(item.metadata || {}).some(
4185
+ (k) => ["name", "price", "product", "sku", "brand", "model", "cost", "item"].includes(k.toLowerCase())
3952
4186
  );
4187
+ const hasPricePattern = /\$\s*\d+/.test(content);
4188
+ return hasKeywords || hasMetadataKey || hasPricePattern;
3953
4189
  }
3954
4190
  /**
3955
4191
  * Helper: Check if data contains time series
3956
4192
  */
3957
4193
  static isTimeSeriesData(item) {
3958
4194
  const content = (item.content || "").toLowerCase();
3959
- const timeKeywords = ["date", "time", "year", "month", "week", "day", "hour", "trend", "historical"];
3960
- return timeKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
4195
+ const timeKeywords = ["trend", "historical", "growth", "decline", "change", "increase", "decrease"];
4196
+ const hasTimeKeyword = timeKeywords.some((kw) => content.includes(kw));
4197
+ const metadata = item.metadata || {};
4198
+ const maybeDateKeys = Object.keys(metadata).filter(
3961
4199
  (k) => ["date", "timestamp", "time", "period"].includes(k.toLowerCase())
3962
4200
  );
4201
+ const hasValidDateValue = maybeDateKeys.some((key) => {
4202
+ const value = metadata[key];
4203
+ if (typeof value === "string") {
4204
+ return !Number.isNaN(Date.parse(value)) && value.trim().length > 0;
4205
+ }
4206
+ return typeof value === "number";
4207
+ });
4208
+ return hasTimeKeyword || hasValidDateValue;
4209
+ }
4210
+ static shouldShowCategoryChart(query, categories) {
4211
+ if (categories.length < 2) {
4212
+ return false;
4213
+ }
4214
+ const normalized = query.toLowerCase();
4215
+ const chartKeywords = [
4216
+ "distribution",
4217
+ "breakdown",
4218
+ "by category",
4219
+ "by type",
4220
+ "compare",
4221
+ "share",
4222
+ "percentage",
4223
+ "segmentation",
4224
+ "split",
4225
+ "category breakdown",
4226
+ "category distribution"
4227
+ ];
4228
+ return chartKeywords.some((keyword) => normalized.includes(keyword));
4229
+ }
4230
+ static isTrendQuery(query) {
4231
+ const normalized = query.toLowerCase();
4232
+ const trendKeywords = [
4233
+ "trend",
4234
+ "over time",
4235
+ "historical",
4236
+ "growth",
4237
+ "decline",
4238
+ "increase",
4239
+ "decrease",
4240
+ "year",
4241
+ "month",
4242
+ "week",
4243
+ "day",
4244
+ "comparison",
4245
+ "compare",
4246
+ "changes",
4247
+ "timeline"
4248
+ ];
4249
+ return trendKeywords.some((keyword) => normalized.includes(keyword));
4250
+ }
4251
+ static isStockQuery(query) {
4252
+ const normalized = query.toLowerCase();
4253
+ return normalized.includes("in stock") || normalized.includes("available") || normalized.includes("availability") || normalized.includes("inventory") || normalized.includes("stock status");
3963
4254
  }
3964
4255
  /**
3965
- * Helper: Extract product information from vector match
4256
+ * Helper: Extract property from metadata using mapping, AI training, case-insensitivity, and synonyms.
3966
4257
  */
3967
- static extractProductInfo(item) {
4258
+ static getDynamicVal(meta, uiKey, config, trainedSchema) {
4259
+ var _a;
4260
+ if (!meta) return void 0;
4261
+ const mapping = (_a = config == null ? void 0 : config.rag) == null ? void 0 : _a.uiMapping;
4262
+ if (mapping && mapping[uiKey]) {
4263
+ const mappedKey = mapping[uiKey];
4264
+ if (meta[mappedKey] !== void 0) return meta[mappedKey];
4265
+ }
4266
+ if (trainedSchema && typeof trainedSchema === "object" && trainedSchema !== null) {
4267
+ const trainedKey = trainedSchema[uiKey];
4268
+ if (trainedKey && meta[trainedKey] !== void 0) return meta[trainedKey];
4269
+ }
4270
+ const synonyms = this.SYNONYMS[uiKey] || [];
4271
+ const searchKeys = [uiKey, ...synonyms].map((k) => k.toLowerCase());
4272
+ const foundDirectKey = Object.keys(meta).find((k) => searchKeys.includes(k.toLowerCase()));
4273
+ if (foundDirectKey) return meta[foundDirectKey];
4274
+ const foundPartialKey = Object.keys(meta).find((k) => {
4275
+ const keyLow = k.toLowerCase();
4276
+ return searchKeys.some((sk) => keyLow.includes(sk) || sk.includes(keyLow));
4277
+ });
4278
+ return foundPartialKey ? meta[foundPartialKey] : void 0;
4279
+ }
4280
+ static extractProductInfo(item, config, trainedSchema) {
3968
4281
  const meta = item.metadata || {};
3969
- if (meta.name || meta.product) {
3970
- return {
3971
- id: item.id,
3972
- name: meta.name || meta.product,
3973
- price: meta.price,
3974
- image: meta.image || meta.imageUrl,
3975
- brand: meta.brand,
3976
- description: item.content,
3977
- inStock: this.determineStockStatus(item)
3978
- };
3979
- }
3980
- const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
3981
- const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d.]+)/i);
3982
- if (nameMatch) {
4282
+ const name = this.getDynamicVal(meta, "name", config, trainedSchema);
4283
+ const price = this.getDynamicVal(meta, "price", config, trainedSchema);
4284
+ const brand = this.getDynamicVal(meta, "brand", config, trainedSchema);
4285
+ if (name || this.isProductData(item)) {
4286
+ let finalName = name ? String(name) : void 0;
4287
+ if (!finalName) {
4288
+ const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
4289
+ finalName = nameMatch ? nameMatch[1].trim() : item.content.split("\n")[0].substring(0, 60);
4290
+ }
4291
+ let finalPrice = typeof price === "number" || typeof price === "string" ? price : void 0;
4292
+ if (!finalPrice) {
4293
+ const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d,.]+)/i) || item.content.match(/\$\s*([\d,.]+)/);
4294
+ if (priceMatch) finalPrice = priceMatch[1].replace(/,/g, "");
4295
+ }
4296
+ const imageValue = this.getDynamicVal(meta, "image", config, trainedSchema);
3983
4297
  return {
3984
4298
  id: item.id,
3985
- name: nameMatch[1],
3986
- price: priceMatch ? parseFloat(priceMatch[1]) : void 0,
4299
+ name: finalName,
4300
+ price: finalPrice,
4301
+ image: typeof imageValue === "string" ? imageValue : void 0,
4302
+ brand: brand ? String(brand) : void 0,
3987
4303
  description: item.content,
3988
4304
  inStock: this.determineStockStatus(item)
3989
4305
  };
@@ -4007,6 +4323,10 @@ var UITransformer = class {
4007
4323
  const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
4008
4324
  tags.forEach((t) => categories.add(String(t)));
4009
4325
  }
4326
+ const contentCategories = Array.from(new Set(
4327
+ Array.from(item.content.matchAll(/^\s*([^:\n]+):\s*(?:•|\*|-)*/gm)).map((match) => match[1].trim()).filter(Boolean)
4328
+ ));
4329
+ contentCategories.forEach((category) => categories.add(category));
4010
4330
  const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
4011
4331
  if (categoryMatch) {
4012
4332
  categories.add(categoryMatch[1].trim());
@@ -4074,14 +4394,23 @@ var UITransformer = class {
4074
4394
  });
4075
4395
  }
4076
4396
  /**
4077
- * Helper: Check stock status
4397
+ * Helper: Calculate stock counts by category
4078
4398
  */
4079
- static checkStockStatus(category, data) {
4080
- const item = data.find((d) => {
4399
+ static calculateStockCounts(category, data) {
4400
+ let inStock = 0;
4401
+ let outOfStock = 0;
4402
+ data.forEach((d) => {
4081
4403
  const meta = d.metadata || {};
4082
- return meta.category === category || meta.type === category;
4404
+ const itemCategory = meta.category || meta.type || "Other";
4405
+ if (itemCategory === category) {
4406
+ if (this.determineStockStatus(d)) {
4407
+ inStock++;
4408
+ } else {
4409
+ outOfStock++;
4410
+ }
4411
+ }
4083
4412
  });
4084
- return this.determineStockStatus(item || {});
4413
+ return { inStockCount: inStock, outOfStockCount: outOfStock };
4085
4414
  }
4086
4415
  /**
4087
4416
  * Helper: Determine if item is in stock
@@ -4118,6 +4447,225 @@ var UITransformer = class {
4118
4447
  });
4119
4448
  return fieldCount.size > 2;
4120
4449
  }
4450
+ // ─── LLM-Driven Visualization Decision ────────────────────────────────────
4451
+ /**
4452
+ * analyzeAndDecide — sends user question + RAG data to the LLM with a
4453
+ * structured system prompt and parses the JSON response into a
4454
+ * UITransformationResponse.
4455
+ *
4456
+ * This is the recommended entry point for production use. The heuristic
4457
+ * `transform()` method is used as a fallback if the LLM call fails.
4458
+ *
4459
+ * System prompt instructs the LLM to:
4460
+ * - Analyze the question and retrieved data
4461
+ * - Choose the best visualization: bar_chart | line_chart | pie_chart | table | text
4462
+ * - Return a strict JSON object — no prose, no markdown fences
4463
+ *
4464
+ * @param query - the original user question
4465
+ * @param sources - vector DB matches returned by RAG retrieval
4466
+ * @param llm - any ILLMProvider instance (OpenAI, Anthropic, Ollama, Gemini, REST…)
4467
+ * @returns - a validated UITransformationResponse (type + title + description + data)
4468
+ */
4469
+ static async analyzeAndDecide(query, sources, llm) {
4470
+ try {
4471
+ const context = this.buildContextSummary(sources);
4472
+ const systemPrompt = this.buildVisualizationSystemPrompt();
4473
+ const userPrompt = [
4474
+ `USER QUESTION: ${query}`,
4475
+ "",
4476
+ "RETRIEVED DATA (JSON):",
4477
+ context
4478
+ ].join("\n");
4479
+ const rawResponse = await llm.chat(
4480
+ [{ role: "user", content: userPrompt }],
4481
+ "",
4482
+ { systemPrompt, temperature: 0 }
4483
+ );
4484
+ const parsed = this.parseTransformationResponse(rawResponse);
4485
+ if (parsed) {
4486
+ console.debug("[UITransformer] LLM chose visualization type:", parsed.type);
4487
+ return parsed;
4488
+ }
4489
+ console.warn("[UITransformer] LLM returned unparseable response; falling back to heuristic.");
4490
+ } catch (err) {
4491
+ console.warn("[UITransformer] analyzeAndDecide LLM call failed; falling back to heuristic.", err);
4492
+ }
4493
+ return this.transform(query, sources);
4494
+ }
4495
+ /**
4496
+ * Build the system prompt that instructs the LLM to return a visualization JSON.
4497
+ */
4498
+ static buildVisualizationSystemPrompt() {
4499
+ return `You are a data visualization expert embedded in a RAG chat system.
4500
+ You will receive a user question and structured data retrieved from a vector database.
4501
+ Your ONLY job is to analyze this information and return a single JSON object that tells
4502
+ the frontend how to visualize it.
4503
+
4504
+ Return ONLY a valid JSON object \u2014 no markdown code fences, no explanation, no prose.
4505
+
4506
+ The JSON must have this exact shape:
4507
+ {
4508
+ "type": "bar_chart" | "line_chart" | "pie_chart" | "radar_chart" | "table" | "text",
4509
+ "title": "A concise, descriptive title for the visualization",
4510
+ "description": "One sentence describing what the visualization shows",
4511
+ "data": <structured data \u2014 see rules below>
4512
+ }
4513
+
4514
+ DATA SHAPE per type:
4515
+ - bar_chart: array of { "category": string, "value": number }
4516
+ - line_chart: array of { "timestamp": string, "value": number, "label": string }
4517
+ - pie_chart: array of { "label": string, "value": number }
4518
+ - radar_chart: array of { "attribute": string, "[series1]": number, "[series2]": number, ... }
4519
+ Example for radar_chart:
4520
+ [
4521
+ { "attribute": "Longevity", "Dolce Shine": 4, "CK One": 3 },
4522
+ { "attribute": "Sillage", "Dolce Shine": 3, "CK One": 4 },
4523
+ { "attribute": "Freshness", "Dolce Shine": 5, "CK One": 4 }
4524
+ ]
4525
+ - table: { "columns": string[], "rows": (string|number)[][] }
4526
+ - text: { "content": "<prose answer>" }
4527
+
4528
+ DECISION RULES (follow strictly):
4529
+ 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.
4530
+ 2. line_chart \u2192 trends or changes over time (dates, months, years, sequential events)
4531
+ 3. pie_chart \u2192 proportional breakdown or percentage distribution
4532
+ 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.
4533
+ 5. table \u2192 multi-field structured records where each item has \u2265 3 attributes
4534
+ 6. text \u2192 conversational or free-form answers where no chart adds value
4535
+
4536
+ IMPORTANT:
4537
+ - Aggregate numeric values from the raw data \u2014 do not pass raw object arrays as data.
4538
+ - Ensure all "value" fields are numbers, not strings.
4539
+ - For bar/line/pie, keep at most 12 data points for readability.
4540
+ - Never include nested objects or arrays inside bar_chart / line_chart / pie_chart data items.`;
4541
+ }
4542
+ /**
4543
+ * Serialize retrieved vector matches into a compact JSON context string.
4544
+ * Limits the total character count to avoid exceeding LLM context windows.
4545
+ */
4546
+ static buildContextSummary(sources, maxChars = 6e3) {
4547
+ const items = sources.map((s, i) => {
4548
+ var _a, _b, _c, _d;
4549
+ return {
4550
+ index: i + 1,
4551
+ content: (_b = (_a = s.content) == null ? void 0 : _a.substring(0, 400)) != null ? _b : "",
4552
+ metadata: (_c = s.metadata) != null ? _c : {},
4553
+ score: (_d = s.score) != null ? _d : 0
4554
+ };
4555
+ });
4556
+ const full = JSON.stringify(items, null, 2);
4557
+ if (full.length <= maxChars) return full;
4558
+ const partial = [];
4559
+ let chars = 2;
4560
+ for (const item of items) {
4561
+ const chunk = JSON.stringify(item);
4562
+ if (chars + chunk.length + 2 > maxChars) break;
4563
+ partial.push(item);
4564
+ chars += chunk.length + 2;
4565
+ }
4566
+ return JSON.stringify(partial, null, 2) + "\n// ... truncated";
4567
+ }
4568
+ };
4569
+ /**
4570
+ * Central dictionary of common synonyms for UI properties.
4571
+ * This allows the system to be schema-agnostic by guessing field names.
4572
+ */
4573
+ UITransformer.SYNONYMS = {
4574
+ name: ["product", "item", "title", "label", "heading", "subject"],
4575
+ price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd"],
4576
+ brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
4577
+ image: ["imageUrl", "thumbnail", "img", "url", "photo", "picture", "media", "image_url", "main_image", "product_image", "thumb"],
4578
+ stock: ["inventory", "quantity", "count", "availability", "stock_level", "inStock", "is_available"],
4579
+ description: ["summary", "content", "body", "text", "info", "details"]
4580
+ };
4581
+
4582
+ // src/utils/SchemaMapper.ts
4583
+ var SchemaMapper = class {
4584
+ /**
4585
+ * Trains the plugin on a set of keys.
4586
+ * This is done once per schema and cached.
4587
+ */
4588
+ static async train(llm, projectId, keys) {
4589
+ const cacheKey = `${projectId}:${keys.sort().join(",")}`;
4590
+ if (this.cache.has(cacheKey)) {
4591
+ return this.cache.get(cacheKey);
4592
+ }
4593
+ console.log(`[SchemaMapper] \u{1F9E0} Training AI on new schema keys: ${keys.join(", ")}`);
4594
+ const propertyList = Object.entries(this.TARGET_PROPERTIES).map(([prop, desc]) => `- ${prop} (${desc})`).join("\n");
4595
+ const prompt = `
4596
+ Given these metadata keys from a database: [${keys.join(", ")}]
4597
+
4598
+ Identify which keys best correspond to these standard UI properties:
4599
+ ${propertyList}
4600
+
4601
+ Return ONLY a JSON object where the keys are the UI properties and the values are the matching database keys.
4602
+ If no good match is found for a property, omit it.
4603
+
4604
+ Example:
4605
+ {
4606
+ "name": "Product_Title",
4607
+ "price": "MSRP_USD",
4608
+ "brand": "VendorName"
4609
+ }
4610
+ `;
4611
+ try {
4612
+ const response = await llm.chat(
4613
+ [
4614
+ { role: "system", content: "You are a database schema expert. You ONLY respond with valid JSON." },
4615
+ { role: "user", content: prompt }
4616
+ ],
4617
+ ""
4618
+ );
4619
+ const startIdx = response.indexOf("{");
4620
+ if (startIdx !== -1) {
4621
+ let braceCount = 0;
4622
+ let endIdx = -1;
4623
+ for (let i = startIdx; i < response.length; i++) {
4624
+ if (response[i] === "{") braceCount++;
4625
+ else if (response[i] === "}") {
4626
+ braceCount--;
4627
+ if (braceCount === 0) {
4628
+ endIdx = i;
4629
+ break;
4630
+ }
4631
+ }
4632
+ }
4633
+ if (endIdx !== -1) {
4634
+ const jsonContent = response.substring(startIdx, endIdx + 1);
4635
+ const cleanJson = this.sanitizeJson(jsonContent);
4636
+ const mapping = JSON.parse(cleanJson);
4637
+ this.cache.set(cacheKey, mapping);
4638
+ return mapping;
4639
+ }
4640
+ }
4641
+ } catch (error) {
4642
+ console.warn("[SchemaMapper] AI training failed, falling back to heuristics:", error);
4643
+ }
4644
+ return {};
4645
+ }
4646
+ /**
4647
+ * Forgiving JSON parser that fixes common AI formatting mistakes.
4648
+ */
4649
+ static sanitizeJson(s) {
4650
+ 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();
4651
+ }
4652
+ static getCached(projectId, keys) {
4653
+ const cacheKey = `${projectId}:${keys.sort().join(",")}`;
4654
+ return this.cache.get(cacheKey);
4655
+ }
4656
+ };
4657
+ SchemaMapper.cache = /* @__PURE__ */ new Map();
4658
+ /**
4659
+ * Descriptions of standard UI properties to help the AI map fields accurately.
4660
+ */
4661
+ SchemaMapper.TARGET_PROPERTIES = {
4662
+ name: "The primary title, name, or label of the item",
4663
+ price: "The numeric cost, price, MSRP, or amount",
4664
+ brand: "The manufacturer, vendor, brand, or maker",
4665
+ image: "The URL to an image, thumbnail, photo, or picture",
4666
+ stock: "The availability, inventory count, quantity, or stock level",
4667
+ description: "The detailed text content, summary, or body info",
4668
+ category: "The group, department, type, or category name"
4121
4669
  };
4122
4670
 
4123
4671
  // src/core/Pipeline.ts
@@ -4166,205 +4714,31 @@ var Pipeline = class {
4166
4714
  }
4167
4715
  this.reranker = new Reranker();
4168
4716
  }
4717
+ /**
4718
+ * Expose the underlying LLM provider (set after initialize()).
4719
+ * Used by the stream handler to pass to UITransformer.analyzeAndDecide().
4720
+ */
4721
+ getLLMProvider() {
4722
+ return this.initialised ? this.llmProvider : void 0;
4723
+ }
4169
4724
  async initialize() {
4170
- var _a, _b;
4725
+ var _a;
4171
4726
  if (this.initialised) return;
4172
- const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
4173
4727
  const chartInstruction = `
4728
+ You are a helpful product assistant. Use the provided context to answer questions accurately.
4174
4729
 
4175
- ${CHART_MARKER}
4176
- ### UNIVERSAL UI PROTOCOL V7 (STRICT MODE)
4177
-
4178
- You are responsible for returning a SINGLE structured UI block when visualization is required.
4179
-
4180
- ---
4181
-
4182
- ## 1. INTENT CLASSIFICATION (MANDATORY)
4183
-
4184
- Classify the query into ONE of the following intents:
4185
-
4186
- - "explore" \u2192 user wants to browse items (e.g., "show me products")
4187
- - "analyze" \u2192 user wants aggregation/distribution (e.g., "distribution of products")
4188
- - "compare" \u2192 user wants side-by-side comparison
4189
- - "detail" \u2192 user wants structured/tabular data
4190
-
4191
- ---
4192
-
4193
- ## 2. VIEW MAPPING (STRICT)
4194
-
4195
- | Intent | View |
4196
- |----------|-----------|
4197
- | explore | carousel |
4198
- | analyze | chart |
4199
- | compare | table |
4200
- | detail | table |
4201
-
4202
- \u26A0\uFE0F Override Rules:
4203
- - If query contains "distribution", "percentage", "ratio" \u2192 ALWAYS chart
4204
- - If query contains "show products", "list items" \u2192 ALWAYS carousel
4205
- - NEVER mix views
4206
-
4207
- ---
4208
-
4209
- ## 3. RESPONSE FORMAT (STRICT JSON ONLY INSIDE BLOCK)
4210
-
4211
- \`\`\`ui
4212
- {
4213
- "view": "carousel" | "chart" | "table",
4214
- "title": "string",
4215
- "description": "string",
4216
- "metadata": {
4217
- "intent": "explore" | "analyze" | "compare" | "detail",
4218
- "confidence": 0.0-1.0
4219
- },
4220
-
4221
- // CHART ONLY
4222
- "chart": {
4223
- "type": "pie" | "bar" | "line",
4224
- "xKey": "label",
4225
- "yKeys": ["value"],
4226
- "data": []
4227
- },
4228
-
4229
- // TABLE ONLY
4230
- "table": {
4231
- "columns": [],
4232
- "rows": []
4233
- },
4234
-
4235
- // CAROUSEL ONLY
4236
- "carousel": {
4237
- "items": []
4238
- },
4239
-
4240
- "insights": []
4241
- }
4242
- \`\`\`
4243
-
4244
- ---
4245
-
4246
- ## 4. DATA CONTRACT (CRITICAL)
4247
-
4248
- ### \u{1F539} CAROUSEL ITEM FORMAT
4249
- {
4250
- "id": "string",
4251
- "name": "string",
4252
- "brand": "string",
4253
- "price": number,
4254
- "image": "url",
4255
- "link": "url",
4256
- "inStock": boolean
4257
- }
4258
-
4259
- ### \u{1F539} CHART DATA FORMAT
4260
- {
4261
- "label": "string",
4262
- "value": number,
4263
- "inStock": boolean (optional)
4264
- }
4265
-
4266
- ### \u{1F539} TABLE FORMAT
4267
- - columns MUST match row keys
4268
- - rows MUST be flat objects
4269
-
4270
- ---
4271
-
4272
- ## 5. DATA RULES
4273
-
4274
- - NEVER hallucinate fields or data
4275
- - ONLY use retrieved vector DB data
4276
- - NO placeholders ("...", "N/A")
4277
- - If image/link missing \u2192 REMOVE FIELD
4278
- - Aggregate BEFORE rendering chart
4279
- - IF NO DATA IS FOUND in the retrieved context:
4280
- \u2192 DO NOT output a \`\`\`ui\`\`\` block
4281
- \u2192 Instead, state clearly that no relevant products or data were found.
4282
-
4283
- ---
4284
-
4285
- ## 6. SMART RULES (IMPORTANT)
4286
-
4287
- - If user asks BOTH:
4288
- "show products AND distribution"
4289
- \u2192 PRIORITIZE "explore" \u2192 carousel
4290
-
4291
- - If dataset size > 20:
4292
- \u2192 aggregate \u2192 chart or table
4293
-
4294
- ---
4295
-
4296
- ## 7. OUTPUT RULES
4297
-
4298
- - ALWAYS return EXACTLY ONE \`\`\`ui\`\`\` block
4299
- - JSON must be VALID
4300
- - No explanation inside block
4301
- - NEVER use ASCII charts, Markdown tables, or text-based drawings for data.
4302
- - If data is tabular or statistical, ALWAYS use the \`\`\`ui\`\`\` block.
4730
+ ### UI STYLE RULES (CRITICAL):
4731
+ - NEVER generate markdown tables. If you do, the UI will break.
4732
+ - NEVER generate HTML tags like <figure>, <tbody>, <tr>, etc.
4733
+ - NEVER generate text-based charts or graphs.
4734
+ - ONLY use plain text and bullet points.
4303
4735
 
4304
- ---
4305
-
4306
- ## 8. EXAMPLES
4307
-
4308
- User: "show me beauty products"
4309
-
4310
- \`\`\`ui
4311
- {
4312
- "view": "carousel",
4313
- "title": "Beauty Products",
4314
- "description": "Browse available beauty items",
4315
- "metadata": {
4316
- "intent": "explore",
4317
- "confidence": 0.95
4318
- },
4319
- "carousel": {
4320
- "items": [
4321
- {
4322
- "id": "1",
4323
- "name": "Mascara",
4324
- "brand": "Essence",
4325
- "price": 9.99,
4326
- "image": "https://example.com/mascara.jpg",
4327
- "link": "https://example.com/p1",
4328
- "inStock": true
4329
- }
4330
- ]
4331
- },
4332
- "insights": ["Most products are affordable"]
4333
- }
4334
- \`\`\`
4335
-
4336
- ---
4337
-
4338
- User: "distribution of products across categories"
4339
-
4340
- \`\`\`ui
4341
- {
4342
- "view": "chart",
4343
- "title": "Product Distribution",
4344
- "description": "Category-wise product distribution",
4345
- "metadata": {
4346
- "intent": "analyze",
4347
- "confidence": 0.98
4348
- },
4349
- "chart": {
4350
- "type": "pie",
4351
- "xKey": "label",
4352
- "yKeys": ["value"],
4353
- "data": [
4354
- { "label": "Beauty", "value": 15 },
4355
- { "label": "Electronics", "value": 10 }
4356
- ]
4357
- },
4358
- "insights": ["Beauty dominates inventory"]
4359
- }
4360
- \`\`\`
4736
+ ### PRODUCT DISPLAY:
4737
+ - When recommending products, simply list their names, prices, and features in a friendly, conversational manner.
4738
+ - The UI will automatically detect these products and show high-quality product cards in a carousel below your message.
4739
+ - Do NOT try to format product lists as tables.
4361
4740
  `;
4362
- if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
4363
- let cleanPrompt = this.config.llm.systemPrompt || "";
4364
- cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, "");
4365
- cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, "");
4366
- this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
4367
- }
4741
+ this.config.llm.systemPrompt = chartInstruction;
4368
4742
  console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
4369
4743
  this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
4370
4744
  const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
@@ -4379,7 +4753,7 @@ User: "distribution of products across categories"
4379
4753
  this.entityExtractor = new EntityExtractor(this.llmProvider);
4380
4754
  }
4381
4755
  await this.vectorDB.initialize();
4382
- if (((_b = this.config.rag) == null ? void 0 : _b.architecture) === "agentic") {
4756
+ if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic") {
4383
4757
  this.agent = new LangChainAgent(this, this.config);
4384
4758
  await this.agent.initialize(this.llmProvider);
4385
4759
  }
@@ -4516,14 +4890,16 @@ User: "distribution of products across categories"
4516
4890
  let reply = "";
4517
4891
  let sources = [];
4518
4892
  let graphData;
4893
+ let uiTransformation;
4519
4894
  try {
4520
4895
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
4521
4896
  const chunk = temp.value;
4522
4897
  if (typeof chunk === "string") {
4523
4898
  reply += chunk;
4524
- } else if ("sources" in chunk) {
4525
- sources = chunk.sources;
4526
- graphData = chunk.graphData;
4899
+ } else if (typeof chunk === "object" && chunk !== null) {
4900
+ if ("sources" in chunk) sources = chunk.sources;
4901
+ if ("graphData" in chunk) graphData = chunk.graphData;
4902
+ if ("ui_transformation" in chunk) uiTransformation = chunk.ui_transformation;
4527
4903
  }
4528
4904
  }
4529
4905
  } catch (temp) {
@@ -4536,7 +4912,7 @@ User: "distribution of products across categories"
4536
4912
  throw error[0];
4537
4913
  }
4538
4914
  }
4539
- return { reply, sources, graphData };
4915
+ return { reply, sources, graphData, ui_transformation: uiTransformation };
4540
4916
  }
4541
4917
  /**
4542
4918
  * High-performance streaming RAG flow.
@@ -4580,7 +4956,12 @@ ${graphContext}
4580
4956
  VECTOR CONTEXT:
4581
4957
  ${context}`;
4582
4958
  }
4583
- const messages = [...history, { role: "user", content: question }];
4959
+ const allMetadataKeys = Array.from(new Set(sources.flatMap((s) => Object.keys(s.metadata || {}))));
4960
+ const trainingPromise = allMetadataKeys.length > 0 ? SchemaMapper.train(this.llmProvider, ns, allMetadataKeys) : Promise.resolve(void 0);
4961
+ const restrictionSuffix = "\n\n(IMPORTANT: Use plain text only. NEVER generate tables, HTML figures, or text charts. If listing products, use simple bullet points.)";
4962
+ const hardenedHistory = [...history];
4963
+ const userQuestion = { role: "user", content: question + restrictionSuffix };
4964
+ const messages = [...hardenedHistory, userQuestion];
4584
4965
  if (this.llmProvider.chatStream) {
4585
4966
  const stream = this.llmProvider.chatStream(messages, context);
4586
4967
  if (!stream) {
@@ -4605,7 +4986,8 @@ ${context}`;
4605
4986
  const reply = yield new __await(this.llmProvider.chat(messages, context));
4606
4987
  yield reply;
4607
4988
  }
4608
- const uiTransformation = UITransformer.transform(question, sources);
4989
+ const trainedSchema = yield new __await(trainingPromise);
4990
+ const uiTransformation = yield new __await(this.generateUiTransformation(question, sources, context, trainedSchema));
4609
4991
  yield {
4610
4992
  reply: "",
4611
4993
  sources,
@@ -4621,6 +5003,17 @@ ${context}`;
4621
5003
  * Universal retrieval method combining all enabled providers.
4622
5004
  * Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
4623
5005
  */
5006
+ async generateUiTransformation(question, sources, _context, trainedSchema) {
5007
+ if (!sources || sources.length === 0) {
5008
+ return UITransformer.transform(question, sources, this.config, trainedSchema);
5009
+ }
5010
+ try {
5011
+ return await UITransformer.analyzeAndDecide(question, sources, this.llmProvider);
5012
+ } catch (err) {
5013
+ console.warn("[Pipeline] generateUiTransformation failed, using heuristic fallback:", err);
5014
+ return UITransformer.transform(question, sources, this.config, trainedSchema);
5015
+ }
5016
+ }
4624
5017
  async retrieve(query, options) {
4625
5018
  var _a, _b, _c;
4626
5019
  const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
@@ -4823,6 +5216,14 @@ var VectorPlugin = class {
4823
5216
  getConfig() {
4824
5217
  return this.config;
4825
5218
  }
5219
+ /**
5220
+ * Get the initialized LLM provider (available after the first request).
5221
+ * Used by handlers to pass to UITransformer.analyzeAndDecide() for
5222
+ * LLM-driven visualization decisions.
5223
+ */
5224
+ getLLMProvider() {
5225
+ return this.pipeline.getLLMProvider();
5226
+ }
4826
5227
  /**
4827
5228
  * Perform pre-flight health checks on all configured providers.
4828
5229
  * Useful to verify connectivity before running operations.
@@ -4944,7 +5345,7 @@ function sseMetaFrame(meta) {
4944
5345
  `;
4945
5346
  }
4946
5347
  function sseUIFrame(uiTransformation) {
4947
- return `data: ${JSON.stringify(__spreadValues({ type: "ui_transformation" }, uiTransformation != null ? uiTransformation : {}))}
5348
+ return `data: ${JSON.stringify({ type: "ui_transformation", data: uiTransformation })}
4948
5349
 
4949
5350
  `;
4950
5351
  }
@@ -4999,6 +5400,7 @@ function createStreamHandler(configOrPlugin) {
4999
5400
  const encoder = new TextEncoder();
5000
5401
  const stream = new ReadableStream({
5001
5402
  async start(controller) {
5403
+ var _a, _b;
5002
5404
  const enqueue = (text) => controller.enqueue(encoder.encode(text));
5003
5405
  try {
5004
5406
  const pipelineStream = plugin.chatStream(message, history, namespace);
@@ -5009,13 +5411,22 @@ function createStreamHandler(configOrPlugin) {
5009
5411
  enqueue(sseTextFrame(chunk));
5010
5412
  } else {
5011
5413
  enqueue(sseMetaFrame(chunk));
5012
- const sources = (chunk == null ? void 0 : chunk.sources) || [];
5013
- if (sources && sources.length > 0) {
5414
+ const responseChunk = chunk;
5415
+ const sources = (responseChunk == null ? void 0 : responseChunk.sources) || [];
5416
+ if (sources.length > 0) {
5014
5417
  try {
5015
- const uiTransformation = UITransformer.transform(message, sources);
5016
- enqueue(sseUIFrame(uiTransformation));
5418
+ const llmProvider = (_a = plugin.getLLMProvider) == null ? void 0 : _a.call(plugin);
5419
+ 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());
5420
+ if (uiTransformation) {
5421
+ enqueue(sseUIFrame(uiTransformation));
5422
+ }
5017
5423
  } catch (transformError) {
5018
5424
  console.warn("[createStreamHandler] UI transformation warning:", transformError);
5425
+ try {
5426
+ const fallback = UITransformer.transform(message, sources, plugin.getConfig());
5427
+ if (fallback) enqueue(sseUIFrame(fallback));
5428
+ } catch (e) {
5429
+ }
5019
5430
  }
5020
5431
  }
5021
5432
  }