@retrivora-ai/rag-engine 1.2.0 → 1.2.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.
@@ -1,3 +1,54 @@
1
+ interface VectorMatch {
2
+ id: string | number;
3
+ score: number;
4
+ content: string;
5
+ metadata?: Record<string, unknown>;
6
+ }
7
+ interface UpsertDocument {
8
+ id: string | number;
9
+ vector: number[];
10
+ content: string;
11
+ metadata?: Record<string, unknown>;
12
+ }
13
+ interface IngestDocument {
14
+ docId: string | number;
15
+ content: string;
16
+ metadata?: Record<string, unknown>;
17
+ }
18
+ interface ChatResponse {
19
+ reply: string;
20
+ sources: VectorMatch[];
21
+ graphData?: GraphSearchResult;
22
+ }
23
+ interface GraphNode {
24
+ id: string;
25
+ label: string;
26
+ properties?: Record<string, unknown>;
27
+ }
28
+ interface Edge {
29
+ source: string;
30
+ target: string;
31
+ type: string;
32
+ properties?: Record<string, unknown>;
33
+ }
34
+ interface GraphSearchResult {
35
+ nodes: GraphNode[];
36
+ edges: Edge[];
37
+ }
38
+ interface RetrievalResult {
39
+ sources: VectorMatch[];
40
+ graphData?: GraphSearchResult;
41
+ }
42
+ interface Product {
43
+ id: string | number;
44
+ name: string;
45
+ brand?: string;
46
+ price?: string | number;
47
+ image?: string;
48
+ link?: string;
49
+ description?: string;
50
+ }
51
+
1
52
  /**
2
53
  * Generic LLM Provider interface.
3
54
  * Covers both chat completion and embedding generation so a single
@@ -55,48 +106,6 @@ interface ILLMProvider {
55
106
  ping(): Promise<boolean>;
56
107
  }
57
108
 
58
- interface VectorMatch {
59
- id: string | number;
60
- score: number;
61
- content: string;
62
- metadata?: Record<string, unknown>;
63
- }
64
- interface UpsertDocument {
65
- id: string | number;
66
- vector: number[];
67
- content: string;
68
- metadata?: Record<string, unknown>;
69
- }
70
- interface IngestDocument {
71
- docId: string | number;
72
- content: string;
73
- metadata?: Record<string, unknown>;
74
- }
75
- interface ChatResponse {
76
- reply: string;
77
- sources: VectorMatch[];
78
- graphData?: GraphSearchResult;
79
- }
80
- interface GraphNode {
81
- id: string;
82
- label: string;
83
- properties?: Record<string, unknown>;
84
- }
85
- interface Edge {
86
- source: string;
87
- target: string;
88
- type: string;
89
- properties?: Record<string, unknown>;
90
- }
91
- interface GraphSearchResult {
92
- nodes: GraphNode[];
93
- edges: Edge[];
94
- }
95
- interface RetrievalResult {
96
- sources: VectorMatch[];
97
- graphData?: GraphSearchResult;
98
- }
99
-
100
109
  /**
101
110
  * constants.ts — Centralized definitions of acceptable configuration values.
102
111
  *
@@ -271,4 +280,4 @@ interface RagConfig {
271
280
  graphDb?: GraphDBConfig;
272
281
  }
273
282
 
274
- export type { ChatMessage as C, EmbedOptions as E, GraphDBConfig as G, ILLMProvider as I, LLMConfig as L, RAGConfig as R, UIConfig as U, VectorMatch as V, ChatOptions as a, ChatResponse as b, EmbeddingConfig as c, EmbeddingProvider as d, IngestDocument as e, LLMProvider as f, RagConfig as g, UpsertDocument as h, VectorDBConfig as i, VectorDBProvider as j, RetrievalResult as k, GraphNode as l, Edge as m, GraphSearchResult as n };
283
+ export type { ChatMessage as C, EmbedOptions as E, GraphDBConfig as G, ILLMProvider as I, LLMConfig as L, Product as P, RAGConfig as R, UIConfig as U, VectorMatch as V, ChatOptions as a, ChatResponse as b, EmbeddingConfig as c, EmbeddingProvider as d, IngestDocument as e, LLMProvider as f, RagConfig as g, UpsertDocument as h, VectorDBConfig as i, VectorDBProvider as j, RetrievalResult as k, GraphNode as l, Edge as m, GraphSearchResult as n };
@@ -1,3 +1,54 @@
1
+ interface VectorMatch {
2
+ id: string | number;
3
+ score: number;
4
+ content: string;
5
+ metadata?: Record<string, unknown>;
6
+ }
7
+ interface UpsertDocument {
8
+ id: string | number;
9
+ vector: number[];
10
+ content: string;
11
+ metadata?: Record<string, unknown>;
12
+ }
13
+ interface IngestDocument {
14
+ docId: string | number;
15
+ content: string;
16
+ metadata?: Record<string, unknown>;
17
+ }
18
+ interface ChatResponse {
19
+ reply: string;
20
+ sources: VectorMatch[];
21
+ graphData?: GraphSearchResult;
22
+ }
23
+ interface GraphNode {
24
+ id: string;
25
+ label: string;
26
+ properties?: Record<string, unknown>;
27
+ }
28
+ interface Edge {
29
+ source: string;
30
+ target: string;
31
+ type: string;
32
+ properties?: Record<string, unknown>;
33
+ }
34
+ interface GraphSearchResult {
35
+ nodes: GraphNode[];
36
+ edges: Edge[];
37
+ }
38
+ interface RetrievalResult {
39
+ sources: VectorMatch[];
40
+ graphData?: GraphSearchResult;
41
+ }
42
+ interface Product {
43
+ id: string | number;
44
+ name: string;
45
+ brand?: string;
46
+ price?: string | number;
47
+ image?: string;
48
+ link?: string;
49
+ description?: string;
50
+ }
51
+
1
52
  /**
2
53
  * Generic LLM Provider interface.
3
54
  * Covers both chat completion and embedding generation so a single
@@ -55,48 +106,6 @@ interface ILLMProvider {
55
106
  ping(): Promise<boolean>;
56
107
  }
57
108
 
58
- interface VectorMatch {
59
- id: string | number;
60
- score: number;
61
- content: string;
62
- metadata?: Record<string, unknown>;
63
- }
64
- interface UpsertDocument {
65
- id: string | number;
66
- vector: number[];
67
- content: string;
68
- metadata?: Record<string, unknown>;
69
- }
70
- interface IngestDocument {
71
- docId: string | number;
72
- content: string;
73
- metadata?: Record<string, unknown>;
74
- }
75
- interface ChatResponse {
76
- reply: string;
77
- sources: VectorMatch[];
78
- graphData?: GraphSearchResult;
79
- }
80
- interface GraphNode {
81
- id: string;
82
- label: string;
83
- properties?: Record<string, unknown>;
84
- }
85
- interface Edge {
86
- source: string;
87
- target: string;
88
- type: string;
89
- properties?: Record<string, unknown>;
90
- }
91
- interface GraphSearchResult {
92
- nodes: GraphNode[];
93
- edges: Edge[];
94
- }
95
- interface RetrievalResult {
96
- sources: VectorMatch[];
97
- graphData?: GraphSearchResult;
98
- }
99
-
100
109
  /**
101
110
  * constants.ts — Centralized definitions of acceptable configuration values.
102
111
  *
@@ -271,4 +280,4 @@ interface RagConfig {
271
280
  graphDb?: GraphDBConfig;
272
281
  }
273
282
 
274
- export type { ChatMessage as C, EmbedOptions as E, GraphDBConfig as G, ILLMProvider as I, LLMConfig as L, RAGConfig as R, UIConfig as U, VectorMatch as V, ChatOptions as a, ChatResponse as b, EmbeddingConfig as c, EmbeddingProvider as d, IngestDocument as e, LLMProvider as f, RagConfig as g, UpsertDocument as h, VectorDBConfig as i, VectorDBProvider as j, RetrievalResult as k, GraphNode as l, Edge as m, GraphSearchResult as n };
283
+ export type { ChatMessage as C, EmbedOptions as E, GraphDBConfig as G, ILLMProvider as I, LLMConfig as L, Product as P, RAGConfig as R, UIConfig as U, VectorMatch as V, ChatOptions as a, ChatResponse as b, EmbeddingConfig as c, EmbeddingProvider as d, IngestDocument as e, LLMProvider as f, RagConfig as g, UpsertDocument as h, VectorDBConfig as i, VectorDBProvider as j, RetrievalResult as k, GraphNode as l, Edge as m, GraphSearchResult as n };
@@ -2523,6 +2523,50 @@ Optimized Search Query:`;
2523
2523
  );
2524
2524
  return rewrite.trim() || question;
2525
2525
  }
2526
+ /**
2527
+ * Generate 3-5 short, relevant questions based on the vector database content.
2528
+ */
2529
+ async getSuggestions(query, namespace) {
2530
+ if (!query || query.trim().length < 3) {
2531
+ return [];
2532
+ }
2533
+ await this.initialize();
2534
+ const ns = namespace != null ? namespace : this.config.projectId;
2535
+ try {
2536
+ const { sources } = await this.retrieve(query, { namespace: ns, topK: 3 });
2537
+ if (sources.length === 0) {
2538
+ return [];
2539
+ }
2540
+ const context = sources.map((s) => s.content).join("\n\n---\n\n");
2541
+ const prompt = `
2542
+ Based on the following snippets from a document, what are 3 short, relevant questions a user might ask?
2543
+ Focus on questions that can be answered by the context.
2544
+ Keep each question under 10 words and make them very specific to the content.
2545
+ Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3"].
2546
+
2547
+ Context:
2548
+ ${context}
2549
+
2550
+ Suggestions:`;
2551
+ const response = await this.llmProvider.chat(
2552
+ [
2553
+ { role: "system", content: "You are a helpful assistant that generates search suggestions." },
2554
+ { role: "user", content: prompt }
2555
+ ],
2556
+ ""
2557
+ );
2558
+ const match = response.match(/\[[\s\S]*\]/);
2559
+ if (match) {
2560
+ const suggestions = JSON.parse(match[0]);
2561
+ if (Array.isArray(suggestions)) {
2562
+ return suggestions.map((s) => String(s)).slice(0, 3);
2563
+ }
2564
+ }
2565
+ } catch (error) {
2566
+ console.error("[Pipeline] Failed to generate suggestions:", error);
2567
+ }
2568
+ return [];
2569
+ }
2526
2570
  };
2527
2571
 
2528
2572
  // src/core/ProviderHealthCheck.ts
@@ -2677,6 +2721,13 @@ var VectorPlugin = class {
2677
2721
  await this.validationPromise;
2678
2722
  return this.pipeline.ingest(documents, namespace);
2679
2723
  }
2724
+ /**
2725
+ * Get auto-suggestions based on a query prefix.
2726
+ */
2727
+ async getSuggestions(query, namespace) {
2728
+ await this.validationPromise;
2729
+ return this.pipeline.getSuggestions(query, namespace);
2730
+ }
2680
2731
  };
2681
2732
 
2682
2733
  // src/utils/DocumentParser.ts
@@ -2906,6 +2957,23 @@ function createUploadHandler(configOrPlugin) {
2906
2957
  }
2907
2958
  };
2908
2959
  }
2960
+ function createSuggestionsHandler(configOrPlugin) {
2961
+ const plugin = configOrPlugin instanceof VectorPlugin ? configOrPlugin : new VectorPlugin(configOrPlugin);
2962
+ return async function POST(req) {
2963
+ try {
2964
+ const body = await req.json();
2965
+ const { query, namespace } = body;
2966
+ if (typeof query !== "string") {
2967
+ return NextResponse.json({ error: "query is required" }, { status: 400 });
2968
+ }
2969
+ const suggestions = await plugin.getSuggestions(query, namespace);
2970
+ return NextResponse.json({ suggestions });
2971
+ } catch (err) {
2972
+ const message = err instanceof Error ? err.message : "Internal server error";
2973
+ return NextResponse.json({ error: message }, { status: 500 });
2974
+ }
2975
+ };
2976
+ }
2909
2977
 
2910
2978
  export {
2911
2979
  getRagConfig,
@@ -2935,5 +3003,6 @@ export {
2935
3003
  createStreamHandler,
2936
3004
  createIngestHandler,
2937
3005
  createHealthHandler,
2938
- createUploadHandler
3006
+ createUploadHandler,
3007
+ createSuggestionsHandler
2939
3008
  };
@@ -1,3 +1,3 @@
1
- import '../RagConfig-FyMB_UG6.mjs';
2
- export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from '../index-D-lfcqlL.mjs';
1
+ import '../RagConfig-BOLOz0_O.mjs';
2
+ export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, k as createSuggestionsHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from '../index-64BDupW3.mjs';
3
3
  import 'next/server';
@@ -1,3 +1,3 @@
1
- import '../RagConfig-FyMB_UG6.js';
2
- export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from '../index-CYgr00ot.js';
1
+ import '../RagConfig-BOLOz0_O.js';
2
+ export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, k as createSuggestionsHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from '../index-DbtE8wLM.js';
3
3
  import 'next/server';
@@ -1612,6 +1612,7 @@ __export(handlers_exports, {
1612
1612
  createHealthHandler: () => createHealthHandler,
1613
1613
  createIngestHandler: () => createIngestHandler,
1614
1614
  createStreamHandler: () => createStreamHandler,
1615
+ createSuggestionsHandler: () => createSuggestionsHandler,
1615
1616
  createUploadHandler: () => createUploadHandler,
1616
1617
  sseErrorFrame: () => sseErrorFrame,
1617
1618
  sseFrame: () => sseFrame,
@@ -4085,6 +4086,50 @@ Optimized Search Query:`;
4085
4086
  );
4086
4087
  return rewrite.trim() || question;
4087
4088
  }
4089
+ /**
4090
+ * Generate 3-5 short, relevant questions based on the vector database content.
4091
+ */
4092
+ async getSuggestions(query, namespace) {
4093
+ if (!query || query.trim().length < 3) {
4094
+ return [];
4095
+ }
4096
+ await this.initialize();
4097
+ const ns = namespace != null ? namespace : this.config.projectId;
4098
+ try {
4099
+ const { sources } = await this.retrieve(query, { namespace: ns, topK: 3 });
4100
+ if (sources.length === 0) {
4101
+ return [];
4102
+ }
4103
+ const context = sources.map((s) => s.content).join("\n\n---\n\n");
4104
+ const prompt = `
4105
+ Based on the following snippets from a document, what are 3 short, relevant questions a user might ask?
4106
+ Focus on questions that can be answered by the context.
4107
+ Keep each question under 10 words and make them very specific to the content.
4108
+ Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3"].
4109
+
4110
+ Context:
4111
+ ${context}
4112
+
4113
+ Suggestions:`;
4114
+ const response = await this.llmProvider.chat(
4115
+ [
4116
+ { role: "system", content: "You are a helpful assistant that generates search suggestions." },
4117
+ { role: "user", content: prompt }
4118
+ ],
4119
+ ""
4120
+ );
4121
+ const match = response.match(/\[[\s\S]*\]/);
4122
+ if (match) {
4123
+ const suggestions = JSON.parse(match[0]);
4124
+ if (Array.isArray(suggestions)) {
4125
+ return suggestions.map((s) => String(s)).slice(0, 3);
4126
+ }
4127
+ }
4128
+ } catch (error) {
4129
+ console.error("[Pipeline] Failed to generate suggestions:", error);
4130
+ }
4131
+ return [];
4132
+ }
4088
4133
  };
4089
4134
 
4090
4135
  // src/core/ProviderHealthCheck.ts
@@ -4239,6 +4284,13 @@ var VectorPlugin = class {
4239
4284
  await this.validationPromise;
4240
4285
  return this.pipeline.ingest(documents, namespace);
4241
4286
  }
4287
+ /**
4288
+ * Get auto-suggestions based on a query prefix.
4289
+ */
4290
+ async getSuggestions(query, namespace) {
4291
+ await this.validationPromise;
4292
+ return this.pipeline.getSuggestions(query, namespace);
4293
+ }
4242
4294
  };
4243
4295
 
4244
4296
  // src/utils/DocumentParser.ts
@@ -4468,12 +4520,30 @@ function createUploadHandler(configOrPlugin) {
4468
4520
  }
4469
4521
  };
4470
4522
  }
4523
+ function createSuggestionsHandler(configOrPlugin) {
4524
+ const plugin = configOrPlugin instanceof VectorPlugin ? configOrPlugin : new VectorPlugin(configOrPlugin);
4525
+ return async function POST(req) {
4526
+ try {
4527
+ const body = await req.json();
4528
+ const { query, namespace } = body;
4529
+ if (typeof query !== "string") {
4530
+ return import_server.NextResponse.json({ error: "query is required" }, { status: 400 });
4531
+ }
4532
+ const suggestions = await plugin.getSuggestions(query, namespace);
4533
+ return import_server.NextResponse.json({ suggestions });
4534
+ } catch (err) {
4535
+ const message = err instanceof Error ? err.message : "Internal server error";
4536
+ return import_server.NextResponse.json({ error: message }, { status: 500 });
4537
+ }
4538
+ };
4539
+ }
4471
4540
  // Annotate the CommonJS export names for ESM import in node:
4472
4541
  0 && (module.exports = {
4473
4542
  createChatHandler,
4474
4543
  createHealthHandler,
4475
4544
  createIngestHandler,
4476
4545
  createStreamHandler,
4546
+ createSuggestionsHandler,
4477
4547
  createUploadHandler,
4478
4548
  sseErrorFrame,
4479
4549
  sseFrame,
@@ -3,12 +3,13 @@ import {
3
3
  createHealthHandler,
4
4
  createIngestHandler,
5
5
  createStreamHandler,
6
+ createSuggestionsHandler,
6
7
  createUploadHandler,
7
8
  sseErrorFrame,
8
9
  sseFrame,
9
10
  sseMetaFrame,
10
11
  sseTextFrame
11
- } from "../chunk-OCDCJUNE.mjs";
12
+ } from "../chunk-GCPPRD2G.mjs";
12
13
  import "../chunk-YLTMFW4M.mjs";
13
14
  import "../chunk-X4TOT24V.mjs";
14
15
  export {
@@ -16,6 +17,7 @@ export {
16
17
  createHealthHandler,
17
18
  createIngestHandler,
18
19
  createStreamHandler,
20
+ createSuggestionsHandler,
19
21
  createUploadHandler,
20
22
  sseErrorFrame,
21
23
  sseFrame,
@@ -1,4 +1,4 @@
1
- import { g as RagConfig, C as ChatMessage, b as ChatResponse, e as IngestDocument } from './RagConfig-FyMB_UG6.js';
1
+ import { g as RagConfig, C as ChatMessage, b as ChatResponse, e as IngestDocument } from './RagConfig-BOLOz0_O.mjs';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
 
4
4
  interface ValidationError {
@@ -112,6 +112,10 @@ declare class VectorPlugin {
112
112
  docId: string | number;
113
113
  chunksIngested: number;
114
114
  }>>;
115
+ /**
116
+ * Get auto-suggestions based on a query prefix.
117
+ */
118
+ getSuggestions(query: string, namespace?: string): Promise<string[]>;
115
119
  }
116
120
 
117
121
  /**
@@ -202,5 +206,13 @@ declare function createUploadHandler(configOrPlugin?: Partial<RagConfig> | Vecto
202
206
  chunksIngested: number;
203
207
  }[];
204
208
  }>>;
209
+ /**
210
+ * createSuggestionsHandler — factory for the auto-suggestions endpoint.
211
+ */
212
+ declare function createSuggestionsHandler(configOrPlugin?: Partial<RagConfig> | VectorPlugin): (req: NextRequest) => Promise<NextResponse<{
213
+ error: string;
214
+ }> | NextResponse<{
215
+ suggestions: string[];
216
+ }>>;
205
217
 
206
- export { ConfigValidator as C, type HealthCheckResult as H, type IProviderValidator as I, type ValidationError as V, type IProviderHealthChecker as a, VectorPlugin as b, createChatHandler as c, createHealthHandler as d, createIngestHandler as e, createStreamHandler as f, createUploadHandler as g, sseFrame as h, sseMetaFrame as i, sseTextFrame as j, sseErrorFrame as s };
218
+ export { ConfigValidator as C, type HealthCheckResult as H, type IProviderValidator as I, type ValidationError as V, type IProviderHealthChecker as a, VectorPlugin as b, createChatHandler as c, createHealthHandler as d, createIngestHandler as e, createStreamHandler as f, createUploadHandler as g, sseFrame as h, sseMetaFrame as i, sseTextFrame as j, createSuggestionsHandler as k, sseErrorFrame as s };
@@ -1,4 +1,4 @@
1
- import { g as RagConfig, C as ChatMessage, b as ChatResponse, e as IngestDocument } from './RagConfig-FyMB_UG6.mjs';
1
+ import { g as RagConfig, C as ChatMessage, b as ChatResponse, e as IngestDocument } from './RagConfig-BOLOz0_O.js';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
 
4
4
  interface ValidationError {
@@ -112,6 +112,10 @@ declare class VectorPlugin {
112
112
  docId: string | number;
113
113
  chunksIngested: number;
114
114
  }>>;
115
+ /**
116
+ * Get auto-suggestions based on a query prefix.
117
+ */
118
+ getSuggestions(query: string, namespace?: string): Promise<string[]>;
115
119
  }
116
120
 
117
121
  /**
@@ -202,5 +206,13 @@ declare function createUploadHandler(configOrPlugin?: Partial<RagConfig> | Vecto
202
206
  chunksIngested: number;
203
207
  }[];
204
208
  }>>;
209
+ /**
210
+ * createSuggestionsHandler — factory for the auto-suggestions endpoint.
211
+ */
212
+ declare function createSuggestionsHandler(configOrPlugin?: Partial<RagConfig> | VectorPlugin): (req: NextRequest) => Promise<NextResponse<{
213
+ error: string;
214
+ }> | NextResponse<{
215
+ suggestions: string[];
216
+ }>>;
205
217
 
206
- export { ConfigValidator as C, type HealthCheckResult as H, type IProviderValidator as I, type ValidationError as V, type IProviderHealthChecker as a, VectorPlugin as b, createChatHandler as c, createHealthHandler as d, createIngestHandler as e, createStreamHandler as f, createUploadHandler as g, sseFrame as h, sseMetaFrame as i, sseTextFrame as j, sseErrorFrame as s };
218
+ export { ConfigValidator as C, type HealthCheckResult as H, type IProviderValidator as I, type ValidationError as V, type IProviderHealthChecker as a, VectorPlugin as b, createChatHandler as c, createHealthHandler as d, createIngestHandler as e, createStreamHandler as f, createUploadHandler as g, sseFrame as h, sseMetaFrame as i, sseTextFrame as j, createSuggestionsHandler as k, sseErrorFrame as s };
package/dist/index.d.mts CHANGED
@@ -1,13 +1,13 @@
1
1
  import React$1, { ReactNode } from 'react';
2
- import { C as ChatMessage, V as VectorMatch, U as UIConfig } from './RagConfig-FyMB_UG6.mjs';
3
- export { a as ChatOptions, b as ChatResponse, E as EmbedOptions, c as EmbeddingConfig, d as EmbeddingProvider, I as ILLMProvider, e as IngestDocument, L as LLMConfig, f as LLMProvider, R as RAGConfig, g as RagConfig, h as UpsertDocument, i as VectorDBConfig, j as VectorDBProvider } from './RagConfig-FyMB_UG6.mjs';
2
+ import { P as Product, C as ChatMessage, V as VectorMatch, U as UIConfig } from './RagConfig-BOLOz0_O.mjs';
3
+ export { a as ChatOptions, b as ChatResponse, E as EmbedOptions, c as EmbeddingConfig, d as EmbeddingProvider, I as ILLMProvider, e as IngestDocument, L as LLMConfig, f as LLMProvider, R as RAGConfig, g as RagConfig, h as UpsertDocument, i as VectorDBConfig, j as VectorDBProvider } from './RagConfig-BOLOz0_O.mjs';
4
4
  export { C as Chunk, a as ChunkOptions } from './DocumentChunker-Dh9TvmGG.mjs';
5
5
 
6
6
  interface ChatWidgetProps {
7
7
  /** Position of the floating button. Defaults to bottom-right. */
8
8
  position?: 'bottom-right' | 'bottom-left';
9
9
  /** Called when the user clicks 'Add to Cart' on a product */
10
- onAddToCart?: (product: any) => void;
10
+ onAddToCart?: (product: Product) => void;
11
11
  }
12
12
  declare function ChatWidget({ position, onAddToCart }: ChatWidgetProps): React$1.JSX.Element | null;
13
13
 
@@ -31,7 +31,7 @@ interface ChatWindowProps {
31
31
  /** Whether the window is currently maximized */
32
32
  isMaximized?: boolean;
33
33
  /** Called when the user clicks 'Add to Cart' on a product */
34
- onAddToCart?: (product: any) => void;
34
+ onAddToCart?: (product: Product) => void;
35
35
  }
36
36
  declare function ChatWindow({ className, style, onClose, showClose, onResizeStart, onResetResize, isResized, onMaximize, isMaximized, onAddToCart }: ChatWindowProps): React$1.JSX.Element;
37
37
 
@@ -45,16 +45,6 @@ interface DocumentUploadProps {
45
45
  }
46
46
  declare function DocumentUpload({ namespace, onUploadComplete, className }: DocumentUploadProps): React$1.JSX.Element;
47
47
 
48
- interface Product {
49
- id: string | number;
50
- name: string;
51
- brand?: string;
52
- price?: string | number;
53
- image?: string;
54
- link?: string;
55
- description?: string;
56
- }
57
-
58
48
  interface MessageBubbleProps {
59
49
  message: ChatMessage;
60
50
  sources?: VectorMatch[];
package/dist/index.d.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import React$1, { ReactNode } from 'react';
2
- import { C as ChatMessage, V as VectorMatch, U as UIConfig } from './RagConfig-FyMB_UG6.js';
3
- export { a as ChatOptions, b as ChatResponse, E as EmbedOptions, c as EmbeddingConfig, d as EmbeddingProvider, I as ILLMProvider, e as IngestDocument, L as LLMConfig, f as LLMProvider, R as RAGConfig, g as RagConfig, h as UpsertDocument, i as VectorDBConfig, j as VectorDBProvider } from './RagConfig-FyMB_UG6.js';
2
+ import { P as Product, C as ChatMessage, V as VectorMatch, U as UIConfig } from './RagConfig-BOLOz0_O.js';
3
+ export { a as ChatOptions, b as ChatResponse, E as EmbedOptions, c as EmbeddingConfig, d as EmbeddingProvider, I as ILLMProvider, e as IngestDocument, L as LLMConfig, f as LLMProvider, R as RAGConfig, g as RagConfig, h as UpsertDocument, i as VectorDBConfig, j as VectorDBProvider } from './RagConfig-BOLOz0_O.js';
4
4
  export { C as Chunk, a as ChunkOptions } from './DocumentChunker-Dh9TvmGG.js';
5
5
 
6
6
  interface ChatWidgetProps {
7
7
  /** Position of the floating button. Defaults to bottom-right. */
8
8
  position?: 'bottom-right' | 'bottom-left';
9
9
  /** Called when the user clicks 'Add to Cart' on a product */
10
- onAddToCart?: (product: any) => void;
10
+ onAddToCart?: (product: Product) => void;
11
11
  }
12
12
  declare function ChatWidget({ position, onAddToCart }: ChatWidgetProps): React$1.JSX.Element | null;
13
13
 
@@ -31,7 +31,7 @@ interface ChatWindowProps {
31
31
  /** Whether the window is currently maximized */
32
32
  isMaximized?: boolean;
33
33
  /** Called when the user clicks 'Add to Cart' on a product */
34
- onAddToCart?: (product: any) => void;
34
+ onAddToCart?: (product: Product) => void;
35
35
  }
36
36
  declare function ChatWindow({ className, style, onClose, showClose, onResizeStart, onResetResize, isResized, onMaximize, isMaximized, onAddToCart }: ChatWindowProps): React$1.JSX.Element;
37
37
 
@@ -45,16 +45,6 @@ interface DocumentUploadProps {
45
45
  }
46
46
  declare function DocumentUpload({ namespace, onUploadComplete, className }: DocumentUploadProps): React$1.JSX.Element;
47
47
 
48
- interface Product {
49
- id: string | number;
50
- name: string;
51
- brand?: string;
52
- price?: string | number;
53
- image?: string;
54
- link?: string;
55
- description?: string;
56
- }
57
-
58
48
  interface MessageBubbleProps {
59
49
  message: ChatMessage;
60
50
  sources?: VectorMatch[];
package/dist/index.js CHANGED
@@ -631,6 +631,32 @@ function ChatWindow({
631
631
  const isEmpty = messages.length === 0;
632
632
  const currentRadius = BORDER_RADIUS_MAP[ui.borderRadius || "xl"];
633
633
  const isGlass = ui.visualStyle !== "solid";
634
+ const [suggestions, setSuggestions] = (0, import_react7.useState)([]);
635
+ const [isSuggesting, setIsSuggesting] = (0, import_react7.useState)(false);
636
+ (0, import_react7.useEffect)(() => {
637
+ if (input.trim().length < 3) {
638
+ return;
639
+ }
640
+ const timer = setTimeout(async () => {
641
+ setIsSuggesting(true);
642
+ try {
643
+ const response = await fetch("/api/suggestions", {
644
+ method: "POST",
645
+ headers: { "Content-Type": "application/json" },
646
+ body: JSON.stringify({ query: input, namespace: projectId })
647
+ });
648
+ const data = await response.json();
649
+ if (data.suggestions) {
650
+ setSuggestions(data.suggestions);
651
+ }
652
+ } catch (err) {
653
+ console.error("Failed to fetch suggestions:", err);
654
+ } finally {
655
+ setIsSuggesting(false);
656
+ }
657
+ }, 800);
658
+ return () => clearTimeout(timer);
659
+ }, [input, projectId]);
634
660
  return /* @__PURE__ */ import_react7.default.createElement(
635
661
  "div",
636
662
  {
@@ -742,12 +768,32 @@ function ChatWindow({
742
768
  onAddToCart
743
769
  }
744
770
  ), error && /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex items-center gap-2 text-rose-500 dark:text-rose-400 text-sm bg-rose-50 dark:bg-rose-400/10 border border-rose-200 dark:border-rose-400/20 rounded-xl px-4 py-3" }, /* @__PURE__ */ import_react7.default.createElement(import_lucide_react5.TriangleAlert, { className: "w-4 h-4 flex-shrink-0" }), /* @__PURE__ */ import_react7.default.createElement("span", null, error)), /* @__PURE__ */ import_react7.default.createElement("div", { ref: bottomRef })),
745
- /* @__PURE__ */ import_react7.default.createElement("div", { className: `px-4 pb-4 pt-2 border-t border-slate-200 dark:border-white/10 ${isGlass ? "bg-transparent" : "bg-white dark:bg-[#0f0f1a]"}` }, /* @__PURE__ */ import_react7.default.createElement("div", { className: `flex items-end gap-2 bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 p-2 focus-within:border-slate-300 dark:focus-within:border-white/20 transition-all ${ui.borderRadius === "full" ? "rounded-3xl" : "rounded-2xl"}` }, /* @__PURE__ */ import_react7.default.createElement(
771
+ /* @__PURE__ */ import_react7.default.createElement("div", { className: `px-4 pb-4 pt-2 border-t border-slate-200 dark:border-white/10 ${isGlass ? "bg-transparent" : "bg-white dark:bg-[#0f0f1a]"}` }, (suggestions.length > 0 || isSuggesting) && !isLoading && /* @__PURE__ */ import_react7.default.createElement("div", { className: "mb-3 flex flex-wrap gap-2 animate-in fade-in slide-in-from-bottom-2 duration-300" }, suggestions.map((suggestion) => /* @__PURE__ */ import_react7.default.createElement(
772
+ "button",
773
+ {
774
+ key: suggestion,
775
+ onClick: () => {
776
+ var _a2;
777
+ setInput(suggestion);
778
+ setSuggestions([]);
779
+ (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
780
+ },
781
+ className: "flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-[11px] font-medium bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/60 hover:text-slate-900 dark:hover:text-white/90 hover:border-slate-300 dark:hover:border-white/20 transition-all shadow-sm cursor-pointer group"
782
+ },
783
+ /* @__PURE__ */ import_react7.default.createElement(import_lucide_react5.Sparkles, { className: "w-3 h-3 text-amber-400" }),
784
+ suggestion
785
+ )), isSuggesting && suggestions.length === 0 && /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-[11px] font-medium text-slate-400 dark:text-white/30 animate-pulse" }, /* @__PURE__ */ import_react7.default.createElement(import_lucide_react5.Sparkles, { className: "w-3 h-3" }), "Thinking...")), /* @__PURE__ */ import_react7.default.createElement("div", { className: `flex items-end gap-2 bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 p-2 focus-within:border-slate-300 dark:focus-within:border-white/20 transition-all ${ui.borderRadius === "full" ? "rounded-3xl" : "rounded-2xl"}` }, /* @__PURE__ */ import_react7.default.createElement(
746
786
  "textarea",
747
787
  {
748
788
  ref: inputRef,
749
789
  value: input,
750
- onChange: (e) => setInput(e.target.value),
790
+ onChange: (e) => {
791
+ const val = e.target.value;
792
+ setInput(val);
793
+ if (val.trim().length < 3) {
794
+ setSuggestions([]);
795
+ }
796
+ },
751
797
  onKeyDown: handleKeyDown,
752
798
  placeholder: ui.placeholder,
753
799
  rows: 1,
package/dist/index.mjs CHANGED
@@ -573,6 +573,32 @@ function ChatWindow({
573
573
  const isEmpty = messages.length === 0;
574
574
  const currentRadius = BORDER_RADIUS_MAP[ui.borderRadius || "xl"];
575
575
  const isGlass = ui.visualStyle !== "solid";
576
+ const [suggestions, setSuggestions] = useState4([]);
577
+ const [isSuggesting, setIsSuggesting] = useState4(false);
578
+ useEffect4(() => {
579
+ if (input.trim().length < 3) {
580
+ return;
581
+ }
582
+ const timer = setTimeout(async () => {
583
+ setIsSuggesting(true);
584
+ try {
585
+ const response = await fetch("/api/suggestions", {
586
+ method: "POST",
587
+ headers: { "Content-Type": "application/json" },
588
+ body: JSON.stringify({ query: input, namespace: projectId })
589
+ });
590
+ const data = await response.json();
591
+ if (data.suggestions) {
592
+ setSuggestions(data.suggestions);
593
+ }
594
+ } catch (err) {
595
+ console.error("Failed to fetch suggestions:", err);
596
+ } finally {
597
+ setIsSuggesting(false);
598
+ }
599
+ }, 800);
600
+ return () => clearTimeout(timer);
601
+ }, [input, projectId]);
576
602
  return /* @__PURE__ */ React7.createElement(
577
603
  "div",
578
604
  {
@@ -684,12 +710,32 @@ function ChatWindow({
684
710
  onAddToCart
685
711
  }
686
712
  ), error && /* @__PURE__ */ React7.createElement("div", { className: "flex items-center gap-2 text-rose-500 dark:text-rose-400 text-sm bg-rose-50 dark:bg-rose-400/10 border border-rose-200 dark:border-rose-400/20 rounded-xl px-4 py-3" }, /* @__PURE__ */ React7.createElement(TriangleAlert, { className: "w-4 h-4 flex-shrink-0" }), /* @__PURE__ */ React7.createElement("span", null, error)), /* @__PURE__ */ React7.createElement("div", { ref: bottomRef })),
687
- /* @__PURE__ */ React7.createElement("div", { className: `px-4 pb-4 pt-2 border-t border-slate-200 dark:border-white/10 ${isGlass ? "bg-transparent" : "bg-white dark:bg-[#0f0f1a]"}` }, /* @__PURE__ */ React7.createElement("div", { className: `flex items-end gap-2 bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 p-2 focus-within:border-slate-300 dark:focus-within:border-white/20 transition-all ${ui.borderRadius === "full" ? "rounded-3xl" : "rounded-2xl"}` }, /* @__PURE__ */ React7.createElement(
713
+ /* @__PURE__ */ React7.createElement("div", { className: `px-4 pb-4 pt-2 border-t border-slate-200 dark:border-white/10 ${isGlass ? "bg-transparent" : "bg-white dark:bg-[#0f0f1a]"}` }, (suggestions.length > 0 || isSuggesting) && !isLoading && /* @__PURE__ */ React7.createElement("div", { className: "mb-3 flex flex-wrap gap-2 animate-in fade-in slide-in-from-bottom-2 duration-300" }, suggestions.map((suggestion) => /* @__PURE__ */ React7.createElement(
714
+ "button",
715
+ {
716
+ key: suggestion,
717
+ onClick: () => {
718
+ var _a2;
719
+ setInput(suggestion);
720
+ setSuggestions([]);
721
+ (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
722
+ },
723
+ className: "flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-[11px] font-medium bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/60 hover:text-slate-900 dark:hover:text-white/90 hover:border-slate-300 dark:hover:border-white/20 transition-all shadow-sm cursor-pointer group"
724
+ },
725
+ /* @__PURE__ */ React7.createElement(Sparkles, { className: "w-3 h-3 text-amber-400" }),
726
+ suggestion
727
+ )), isSuggesting && suggestions.length === 0 && /* @__PURE__ */ React7.createElement("div", { className: "flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-[11px] font-medium text-slate-400 dark:text-white/30 animate-pulse" }, /* @__PURE__ */ React7.createElement(Sparkles, { className: "w-3 h-3" }), "Thinking...")), /* @__PURE__ */ React7.createElement("div", { className: `flex items-end gap-2 bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 p-2 focus-within:border-slate-300 dark:focus-within:border-white/20 transition-all ${ui.borderRadius === "full" ? "rounded-3xl" : "rounded-2xl"}` }, /* @__PURE__ */ React7.createElement(
688
728
  "textarea",
689
729
  {
690
730
  ref: inputRef,
691
731
  value: input,
692
- onChange: (e) => setInput(e.target.value),
732
+ onChange: (e) => {
733
+ const val = e.target.value;
734
+ setInput(val);
735
+ if (val.trim().length < 3) {
736
+ setSuggestions([]);
737
+ }
738
+ },
693
739
  onKeyDown: handleKeyDown,
694
740
  placeholder: ui.placeholder,
695
741
  rows: 1,
package/dist/server.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { i as VectorDBConfig, L as LLMConfig, c as EmbeddingConfig, g as RagConfig, e as IngestDocument, C as ChatMessage, b as ChatResponse, k as RetrievalResult, h as UpsertDocument, V as VectorMatch, G as GraphDBConfig, l as GraphNode, m as Edge, n as GraphSearchResult, I as ILLMProvider, j as VectorDBProvider, f as LLMProvider, d as EmbeddingProvider, R as RAGConfig, U as UIConfig, a as ChatOptions, E as EmbedOptions } from './RagConfig-FyMB_UG6.mjs';
1
+ import { i as VectorDBConfig, L as LLMConfig, c as EmbeddingConfig, g as RagConfig, e as IngestDocument, C as ChatMessage, b as ChatResponse, k as RetrievalResult, h as UpsertDocument, V as VectorMatch, G as GraphDBConfig, l as GraphNode, m as Edge, n as GraphSearchResult, I as ILLMProvider, j as VectorDBProvider, f as LLMProvider, d as EmbeddingProvider, R as RAGConfig, U as UIConfig, a as ChatOptions, E as EmbedOptions } from './RagConfig-BOLOz0_O.mjs';
2
2
  export { C as Chunk, a as ChunkOptions, D as DocumentChunker } from './DocumentChunker-Dh9TvmGG.mjs';
3
- import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-D-lfcqlL.mjs';
4
- export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-D-lfcqlL.mjs';
3
+ import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-64BDupW3.mjs';
4
+ export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-64BDupW3.mjs';
5
5
  import 'next/server';
6
6
 
7
7
  /**
@@ -166,6 +166,10 @@ declare class Pipeline {
166
166
  * Rewrite the user query for better retrieval performance.
167
167
  */
168
168
  private rewriteQuery;
169
+ /**
170
+ * Generate 3-5 short, relevant questions based on the vector database content.
171
+ */
172
+ getSuggestions(query: string, namespace?: string): Promise<string[]>;
169
173
  }
170
174
 
171
175
  /**
package/dist/server.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { i as VectorDBConfig, L as LLMConfig, c as EmbeddingConfig, g as RagConfig, e as IngestDocument, C as ChatMessage, b as ChatResponse, k as RetrievalResult, h as UpsertDocument, V as VectorMatch, G as GraphDBConfig, l as GraphNode, m as Edge, n as GraphSearchResult, I as ILLMProvider, j as VectorDBProvider, f as LLMProvider, d as EmbeddingProvider, R as RAGConfig, U as UIConfig, a as ChatOptions, E as EmbedOptions } from './RagConfig-FyMB_UG6.js';
1
+ import { i as VectorDBConfig, L as LLMConfig, c as EmbeddingConfig, g as RagConfig, e as IngestDocument, C as ChatMessage, b as ChatResponse, k as RetrievalResult, h as UpsertDocument, V as VectorMatch, G as GraphDBConfig, l as GraphNode, m as Edge, n as GraphSearchResult, I as ILLMProvider, j as VectorDBProvider, f as LLMProvider, d as EmbeddingProvider, R as RAGConfig, U as UIConfig, a as ChatOptions, E as EmbedOptions } from './RagConfig-BOLOz0_O.js';
2
2
  export { C as Chunk, a as ChunkOptions, D as DocumentChunker } from './DocumentChunker-Dh9TvmGG.js';
3
- import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-CYgr00ot.js';
4
- export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-CYgr00ot.js';
3
+ import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-DbtE8wLM.js';
4
+ export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-DbtE8wLM.js';
5
5
  import 'next/server';
6
6
 
7
7
  /**
@@ -166,6 +166,10 @@ declare class Pipeline {
166
166
  * Rewrite the user query for better retrieval performance.
167
167
  */
168
168
  private rewriteQuery;
169
+ /**
170
+ * Generate 3-5 short, relevant questions based on the vector database content.
171
+ */
172
+ getSuggestions(query: string, namespace?: string): Promise<string[]>;
169
173
  }
170
174
 
171
175
  /**
package/dist/server.js CHANGED
@@ -4177,6 +4177,50 @@ Optimized Search Query:`;
4177
4177
  );
4178
4178
  return rewrite.trim() || question;
4179
4179
  }
4180
+ /**
4181
+ * Generate 3-5 short, relevant questions based on the vector database content.
4182
+ */
4183
+ async getSuggestions(query, namespace) {
4184
+ if (!query || query.trim().length < 3) {
4185
+ return [];
4186
+ }
4187
+ await this.initialize();
4188
+ const ns = namespace != null ? namespace : this.config.projectId;
4189
+ try {
4190
+ const { sources } = await this.retrieve(query, { namespace: ns, topK: 3 });
4191
+ if (sources.length === 0) {
4192
+ return [];
4193
+ }
4194
+ const context = sources.map((s) => s.content).join("\n\n---\n\n");
4195
+ const prompt = `
4196
+ Based on the following snippets from a document, what are 3 short, relevant questions a user might ask?
4197
+ Focus on questions that can be answered by the context.
4198
+ Keep each question under 10 words and make them very specific to the content.
4199
+ Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3"].
4200
+
4201
+ Context:
4202
+ ${context}
4203
+
4204
+ Suggestions:`;
4205
+ const response = await this.llmProvider.chat(
4206
+ [
4207
+ { role: "system", content: "You are a helpful assistant that generates search suggestions." },
4208
+ { role: "user", content: prompt }
4209
+ ],
4210
+ ""
4211
+ );
4212
+ const match = response.match(/\[[\s\S]*\]/);
4213
+ if (match) {
4214
+ const suggestions = JSON.parse(match[0]);
4215
+ if (Array.isArray(suggestions)) {
4216
+ return suggestions.map((s) => String(s)).slice(0, 3);
4217
+ }
4218
+ }
4219
+ } catch (error) {
4220
+ console.error("[Pipeline] Failed to generate suggestions:", error);
4221
+ }
4222
+ return [];
4223
+ }
4180
4224
  };
4181
4225
 
4182
4226
  // src/core/ProviderHealthCheck.ts
@@ -4331,6 +4375,13 @@ var VectorPlugin = class {
4331
4375
  await this.validationPromise;
4332
4376
  return this.pipeline.ingest(documents, namespace);
4333
4377
  }
4378
+ /**
4379
+ * Get auto-suggestions based on a query prefix.
4380
+ */
4381
+ async getSuggestions(query, namespace) {
4382
+ await this.validationPromise;
4383
+ return this.pipeline.getSuggestions(query, namespace);
4384
+ }
4334
4385
  };
4335
4386
 
4336
4387
  // src/config/ConfigBuilder.ts
package/dist/server.mjs CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  sseFrame,
40
40
  sseMetaFrame,
41
41
  sseTextFrame
42
- } from "./chunk-OCDCJUNE.mjs";
42
+ } from "./chunk-GCPPRD2G.mjs";
43
43
  import "./chunk-YLTMFW4M.mjs";
44
44
  import {
45
45
  PineconeProvider
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
5
5
  "author": "Abhinav Alkuchi",
6
6
  "license": "MIT",
@@ -0,0 +1,4 @@
1
+ import { createSuggestionsHandler } from '@/handlers';
2
+ import { getRagConfig } from '@/config/serverConfig';
3
+
4
+ export const POST = createSuggestionsHandler(getRagConfig());
@@ -4,12 +4,13 @@ import React, { useState } from 'react';
4
4
  import { MessageSquare, X } from 'lucide-react';
5
5
  import { ChatWindow } from './ChatWindow';
6
6
  import { useConfig } from './ConfigProvider';
7
+ import { Product } from '../types';
7
8
 
8
9
  interface ChatWidgetProps {
9
10
  /** Position of the floating button. Defaults to bottom-right. */
10
11
  position?: 'bottom-right' | 'bottom-left';
11
12
  /** Called when the user clicks 'Add to Cart' on a product */
12
- onAddToCart?: (product: any) => void;
13
+ onAddToCart?: (product: Product) => void;
13
14
  }
14
15
 
15
16
  const DEFAULT_DIMENSIONS = { width: 380, height: 600 };
@@ -16,6 +16,7 @@ import { MessageBubble } from './MessageBubble';
16
16
  import { useConfig } from './ConfigProvider';
17
17
  import { useRagChat } from '../hooks/useRagChat';
18
18
  import { BORDER_RADIUS_MAP, CHAT_SUGGESTIONS } from '../config/uiConstants';
19
+ import { Product } from '../types';
19
20
 
20
21
  interface ChatWindowProps {
21
22
  /** Additional className for the wrapper div */
@@ -37,7 +38,7 @@ interface ChatWindowProps {
37
38
  /** Whether the window is currently maximized */
38
39
  isMaximized?: boolean;
39
40
  /** Called when the user clicks 'Add to Cart' on a product */
40
- onAddToCart?: (product: any) => void;
41
+ onAddToCart?: (product: Product) => void;
41
42
  }
42
43
 
43
44
  export function ChatWindow({
@@ -97,6 +98,37 @@ export function ChatWindow({
97
98
  const currentRadius = BORDER_RADIUS_MAP[ui.borderRadius || 'xl'];
98
99
  const isGlass = ui.visualStyle !== 'solid';
99
100
 
101
+ const [suggestions, setSuggestions] = useState<string[]>([]);
102
+ const [isSuggesting, setIsSuggesting] = useState(false);
103
+
104
+ // Auto-suggestions fetcher (debounced)
105
+ useEffect(() => {
106
+ if (input.trim().length < 3) {
107
+ return;
108
+ }
109
+
110
+ const timer = setTimeout(async () => {
111
+ setIsSuggesting(true);
112
+ try {
113
+ const response = await fetch('/api/suggestions', {
114
+ method: 'POST',
115
+ headers: { 'Content-Type': 'application/json' },
116
+ body: JSON.stringify({ query: input, namespace: projectId }),
117
+ });
118
+ const data = await response.json();
119
+ if (data.suggestions) {
120
+ setSuggestions(data.suggestions);
121
+ }
122
+ } catch (err) {
123
+ console.error('Failed to fetch suggestions:', err);
124
+ } finally {
125
+ setIsSuggesting(false);
126
+ }
127
+ }, 800); // 800ms debounce to save costs/latency
128
+
129
+ return () => clearTimeout(timer);
130
+ }, [input, projectId]);
131
+
100
132
  return (
101
133
  <div
102
134
  className={`relative flex flex-col border border-slate-200 dark:border-white/10 shadow-2xl transition-all duration-300 ${currentRadius} ${
@@ -265,11 +297,44 @@ export function ChatWindow({
265
297
 
266
298
  {/* ── Input ── */}
267
299
  <div className={`px-4 pb-4 pt-2 border-t border-slate-200 dark:border-white/10 ${isGlass ? 'bg-transparent' : 'bg-white dark:bg-[#0f0f1a]'}`}>
300
+
301
+ {/* Dynamic Suggestions */}
302
+ {(suggestions.length > 0 || isSuggesting) && !isLoading && (
303
+ <div className="mb-3 flex flex-wrap gap-2 animate-in fade-in slide-in-from-bottom-2 duration-300">
304
+ {suggestions.map((suggestion) => (
305
+ <button
306
+ key={suggestion}
307
+ onClick={() => {
308
+ setInput(suggestion);
309
+ setSuggestions([]);
310
+ inputRef.current?.focus();
311
+ }}
312
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-[11px] font-medium bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/60 hover:text-slate-900 dark:hover:text-white/90 hover:border-slate-300 dark:hover:border-white/20 transition-all shadow-sm cursor-pointer group"
313
+ >
314
+ <Sparkles className="w-3 h-3 text-amber-400" />
315
+ {suggestion}
316
+ </button>
317
+ ))}
318
+ {isSuggesting && suggestions.length === 0 && (
319
+ <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-[11px] font-medium text-slate-400 dark:text-white/30 animate-pulse">
320
+ <Sparkles className="w-3 h-3" />
321
+ Thinking...
322
+ </div>
323
+ )}
324
+ </div>
325
+ )}
326
+
268
327
  <div className={`flex items-end gap-2 bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 p-2 focus-within:border-slate-300 dark:focus-within:border-white/20 transition-all ${ui.borderRadius === 'full' ? 'rounded-3xl' : 'rounded-2xl'}`}>
269
328
  <textarea
270
329
  ref={inputRef}
271
330
  value={input}
272
- onChange={(e) => setInput(e.target.value)}
331
+ onChange={(e) => {
332
+ const val = e.target.value;
333
+ setInput(val);
334
+ if (val.trim().length < 3) {
335
+ setSuggestions([]);
336
+ }
337
+ }}
273
338
  onKeyDown={handleKeyDown}
274
339
  placeholder={ui.placeholder}
275
340
  rows={1}
@@ -8,7 +8,7 @@ import { ChatMessage } from '../llm/ILLMProvider';
8
8
  import { VectorMatch } from '../types';
9
9
  import { SourceCard } from './SourceCard';
10
10
  import { ProductCarousel } from './ProductCarousel';
11
- import { Product } from './ProductCard';
11
+ import { Product } from '../types';
12
12
 
13
13
  interface MessageBubbleProps {
14
14
  message: ChatMessage;
@@ -4,15 +4,7 @@ import React from 'react';
4
4
  import Image from 'next/image';
5
5
  import { ShoppingCart, ExternalLink } from 'lucide-react';
6
6
 
7
- export interface Product {
8
- id: string | number;
9
- name: string;
10
- brand?: string;
11
- price?: string | number;
12
- image?: string;
13
- link?: string;
14
- description?: string;
15
- }
7
+ import { Product } from '../types';
16
8
 
17
9
  interface ProductCardProps {
18
10
  product: Product;
@@ -2,7 +2,8 @@
2
2
 
3
3
  import React, { useRef, useState, useEffect } from 'react';
4
4
  import { ChevronLeft, ChevronRight } from 'lucide-react';
5
- import { Product, ProductCard } from './ProductCard';
5
+ import { ProductCard } from './ProductCard';
6
+ import { Product } from '../types';
6
7
 
7
8
  interface ProductCarouselProps {
8
9
  products: Product[];
@@ -423,4 +423,59 @@ Optimized Search Query:`;
423
423
 
424
424
  return rewrite.trim() || question;
425
425
  }
426
+
427
+ /**
428
+ * Generate 3-5 short, relevant questions based on the vector database content.
429
+ */
430
+ async getSuggestions(query: string, namespace?: string): Promise<string[]> {
431
+ if (!query || query.trim().length < 3) {
432
+ return [];
433
+ }
434
+
435
+ await this.initialize();
436
+ const ns = namespace ?? this.config.projectId;
437
+
438
+ try {
439
+ // 1. Retrieve relevant context (top 3 matches)
440
+ const { sources } = await this.retrieve(query, { namespace: ns, topK: 3 });
441
+
442
+ if (sources.length === 0) {
443
+ return [];
444
+ }
445
+
446
+ // 2. Generate suggestions using LLM
447
+ const context = sources.map((s) => s.content).join('\n\n---\n\n');
448
+ const prompt = `
449
+ Based on the following snippets from a document, what are 3 short, relevant questions a user might ask?
450
+ Focus on questions that can be answered by the context.
451
+ Keep each question under 10 words and make them very specific to the content.
452
+ Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3"].
453
+
454
+ Context:
455
+ ${context}
456
+
457
+ Suggestions:`;
458
+
459
+ const response = await this.llmProvider.chat(
460
+ [
461
+ { role: 'system', content: 'You are a helpful assistant that generates search suggestions.' },
462
+ { role: 'user', content: prompt }
463
+ ],
464
+ ''
465
+ );
466
+
467
+ // Simple parsing of JSON array from LLM response
468
+ const match = response.match(/\[[\s\S]*\]/);
469
+ if (match) {
470
+ const suggestions = JSON.parse(match[0]);
471
+ if (Array.isArray(suggestions)) {
472
+ return suggestions.map(s => String(s)).slice(0, 3);
473
+ }
474
+ }
475
+ } catch (error) {
476
+ console.error('[Pipeline] Failed to generate suggestions:', error);
477
+ }
478
+
479
+ return [];
480
+ }
426
481
  }
@@ -81,4 +81,11 @@ export class VectorPlugin {
81
81
  return this.pipeline.ingest(documents, namespace);
82
82
  }
83
83
 
84
+ /**
85
+ * Get auto-suggestions based on a query prefix.
86
+ */
87
+ async getSuggestions(query: string, namespace?: string): Promise<string[]> {
88
+ await this.validationPromise;
89
+ return this.pipeline.getSuggestions(query, namespace);
90
+ }
84
91
  }
@@ -261,5 +261,35 @@ export function createUploadHandler(configOrPlugin?: Partial<RagConfig> | Vector
261
261
  };
262
262
  }
263
263
 
264
+ /**
265
+ * createSuggestionsHandler — factory for the auto-suggestions endpoint.
266
+ */
267
+ export function createSuggestionsHandler(configOrPlugin?: Partial<RagConfig> | VectorPlugin) {
268
+ const plugin =
269
+ configOrPlugin instanceof VectorPlugin
270
+ ? configOrPlugin
271
+ : new VectorPlugin(configOrPlugin);
272
+
273
+ return async function POST(req: NextRequest) {
274
+ try {
275
+ const body = await req.json();
276
+ const { query, namespace } = body as {
277
+ query: string;
278
+ namespace?: string;
279
+ };
280
+
281
+ if (typeof query !== 'string') {
282
+ return NextResponse.json({ error: 'query is required' }, { status: 400 });
283
+ }
284
+
285
+ const suggestions = await plugin.getSuggestions(query, namespace);
286
+ return NextResponse.json({ suggestions });
287
+ } catch (err) {
288
+ const message = err instanceof Error ? err.message : 'Internal server error';
289
+ return NextResponse.json({ error: message }, { status: 500 });
290
+ }
291
+ };
292
+ }
293
+
264
294
  // Re-export SSE helper so host apps can use it in custom handlers
265
295
  export { sseFrame, sseTextFrame, sseMetaFrame, sseErrorFrame };
@@ -50,3 +50,17 @@ export interface RetrievalResult {
50
50
  export interface IRetriever {
51
51
  retrieve(query: string, options?: Record<string, unknown>): Promise<RetrievalResult>;
52
52
  }
53
+
54
+ export interface SuggestionsResponse {
55
+ suggestions: string[];
56
+ }
57
+
58
+ export interface Product {
59
+ id: string | number;
60
+ name: string;
61
+ brand?: string;
62
+ price?: string | number;
63
+ image?: string;
64
+ link?: string;
65
+ description?: string;
66
+ }