@retrivora-ai/rag-engine 2.2.2 → 2.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -125,7 +125,7 @@ var init_templateUtils = __esm({
125
125
  async function handleGroqRequest(req, apiKeyOverride) {
126
126
  const apiKey = apiKeyOverride || process.env.GROQ_API_KEY;
127
127
  if (!apiKey) {
128
- throw new Error("Groq API Key missing. Please set GROQ_API_KEY in environment variables.");
128
+ throw new Error("LLM service credentials not configured.");
129
129
  }
130
130
  const modelName = req.model.replace(/^groq\//i, "");
131
131
  const payload = __spreadProps(__spreadValues({}, req), {
@@ -622,7 +622,6 @@ async function dispatchChatCompletion(req, apiKeyOverride) {
622
622
  const effectiveKey = customGroqKey || cleanApiKeyOverride(apiKeyOverride);
623
623
  const activeModel = req.model || targetModel || "llama-3.1-8b-instant";
624
624
  const provider = resolveProvider(activeModel);
625
- console.log(`[LLM Gateway Router] Dispatching chat request: model=${activeModel}, provider=${provider}, hasCustomKey=${Boolean(customGroqKey)}, hasGlobalGroqKey=${Boolean(process.env.GROQ_API_KEY)}`);
626
625
  try {
627
626
  switch (provider) {
628
627
  case "groq":
@@ -4849,7 +4848,6 @@ ${context != null ? context : "None"}` },
4849
4848
  return String(result2);
4850
4849
  }
4851
4850
  } catch (httpErr) {
4852
- console.warn(`[UniversalLLMAdapter] Direct HTTP POST to ${this.baseUrl}${path2} failed (${httpErr.message}). Attempting in-process gateway dispatch fallback...`);
4853
4851
  const _g2 = globalThis;
4854
4852
  let dispatch = _g2.__retrivoraDispatchChat;
4855
4853
  if (typeof dispatch !== "function") {
@@ -5496,7 +5494,7 @@ var ConfigValidator = class {
5496
5494
  // package.json
5497
5495
  var package_default = {
5498
5496
  name: "@retrivora-ai/rag-engine",
5499
- version: "2.2.2",
5497
+ version: "2.2.4",
5500
5498
  description: "Retrivora AI is a plug-and-play AI engine for RAG chat experiences \u2014 generic vector DB + LLM provider, embeddable or standalone.",
5501
5499
  author: "Abhinav Alkuchi",
5502
5500
  license: "UNLICENSED",
@@ -9930,7 +9928,7 @@ ${context}`;
9930
9928
  const tokenCount = Number(((_c2 = finalTrace == null ? void 0 : finalTrace.tokens) == null ? void 0 : _c2.totalTokens) || 0);
9931
9929
  const costEst = Number(((_d2 = finalTrace == null ? void 0 : finalTrace.tokens) == null ? void 0 : _d2.estimatedCostUsd) || 0);
9932
9930
  const latencyDuration = Number(((_e2 = finalTrace == null ? void 0 : finalTrace.latency) == null ? void 0 : _e2.totalMs) || 0);
9933
- console.log(`[Retrivora Pipeline Telemetry] \u{1F4CA} Dispatching RAG telemetry -> Project: "${ns}", Model: "${providerName}/${modelName}", Tokens: ${tokenCount}, Latency: ${latencyDuration}ms`);
9931
+ console.log(`[Retrivora Pipeline Telemetry] \u{1F4CA} Dispatching RAG telemetry -> Project: "${ns}", Tokens: ${tokenCount}, Latency: ${latencyDuration}ms`);
9934
9932
  await fetch(absoluteUrl, {
9935
9933
  method: "POST",
9936
9934
  headers: {
@@ -11354,7 +11352,7 @@ function reportTelemetry(req, plugin, action, status, details, trace) {
11354
11352
  const licenseKey = config.licenseKey || process.env.RAG_LICENSE_KEY || process.env.RETRIVORA_LICENSE_KEY || process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY;
11355
11353
  const telemetryConfig = config.telemetry;
11356
11354
  const enabled = (_a2 = telemetryConfig == null ? void 0 : telemetryConfig.enabled) != null ? _a2 : Boolean(licenseKey);
11357
- const defaultUrl = process.env.NODE_ENV === "development" && !process.env.TELEMETRY_URL && !process.env.NEXT_PUBLIC_TELEMETRY_URL ? "http://localhost:3001/api/telemetry" : "https://retrivora.com/api/telemetry";
11355
+ const defaultUrl = process.env.NODE_ENV === "development" && !process.env.TELEMETRY_URL && !process.env.NEXT_PUBLIC_TELEMETRY_URL ? "http://localhost:3001/api/telemetry" : "https://www.retrivora.com/api/telemetry";
11358
11356
  const telemetryUrl = (telemetryConfig == null ? void 0 : telemetryConfig.url) || process.env.TELEMETRY_URL || process.env.NEXT_PUBLIC_TELEMETRY_URL || defaultUrl;
11359
11357
  const host = req.headers.get("host") || "localhost";
11360
11358
  const userAgent = req.headers.get("user-agent") || `Retrivora-SDK/${SDK_VERSION}`;
@@ -11388,7 +11386,7 @@ function reportTelemetry(req, plugin, action, status, details, trace) {
11388
11386
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11389
11387
  details
11390
11388
  };
11391
- console.log(`[Retrivora SDK Telemetry] \u{1F4CA} Dispatching telemetry -> Feature: "${action}", Status: "${status}", Project: "${projectId}", Model: "${provider}/${model}", Latency: ${latencyMs}ms`);
11389
+ console.log(`[Retrivora SDK Telemetry] Dispatching telemetry -> Feature: "${action}", Status: "${status}", Project: "${projectId}", Latency: ${latencyMs}ms`);
11392
11390
  fetch(absoluteUrl, {
11393
11391
  method: "POST",
11394
11392
  headers: {
@@ -11828,8 +11826,8 @@ function createSuggestionsHandler(configOrPlugin, options) {
11828
11826
  query = url.searchParams.get("query") || "";
11829
11827
  namespace = url.searchParams.get("namespace") || void 0;
11830
11828
  }
11831
- if (typeof query !== "string") {
11832
- return import_server.NextResponse.json({ error: "query is required" }, { status: 400 });
11829
+ if (!query || typeof query !== "string" || !query.trim()) {
11830
+ return import_server.NextResponse.json({ suggestions: [] });
11833
11831
  }
11834
11832
  const suggestions = await plugin.getSuggestions(query, namespace);
11835
11833
  reportTelemetry(req, plugin, "AUTO_SUGGESTIONS", "success", `Fetched ${suggestions.length} suggestions for: ${query.slice(0, 30)}`);
package/dist/server.mjs CHANGED
@@ -110,7 +110,7 @@ var init_templateUtils = __esm({
110
110
  async function handleGroqRequest(req, apiKeyOverride) {
111
111
  const apiKey = apiKeyOverride || process.env.GROQ_API_KEY;
112
112
  if (!apiKey) {
113
- throw new Error("Groq API Key missing. Please set GROQ_API_KEY in environment variables.");
113
+ throw new Error("LLM service credentials not configured.");
114
114
  }
115
115
  const modelName = req.model.replace(/^groq\//i, "");
116
116
  const payload = __spreadProps(__spreadValues({}, req), {
@@ -607,7 +607,6 @@ async function dispatchChatCompletion(req, apiKeyOverride) {
607
607
  const effectiveKey = customGroqKey || cleanApiKeyOverride(apiKeyOverride);
608
608
  const activeModel = req.model || targetModel || "llama-3.1-8b-instant";
609
609
  const provider = resolveProvider(activeModel);
610
- console.log(`[LLM Gateway Router] Dispatching chat request: model=${activeModel}, provider=${provider}, hasCustomKey=${Boolean(customGroqKey)}, hasGlobalGroqKey=${Boolean(process.env.GROQ_API_KEY)}`);
611
610
  try {
612
611
  switch (provider) {
613
612
  case "groq":
@@ -4752,7 +4751,6 @@ ${context != null ? context : "None"}` },
4752
4751
  return String(result2);
4753
4752
  }
4754
4753
  } catch (httpErr) {
4755
- console.warn(`[UniversalLLMAdapter] Direct HTTP POST to ${this.baseUrl}${path2} failed (${httpErr.message}). Attempting in-process gateway dispatch fallback...`);
4756
4754
  const _g2 = globalThis;
4757
4755
  let dispatch = _g2.__retrivoraDispatchChat;
4758
4756
  if (typeof dispatch !== "function") {
@@ -5399,7 +5397,7 @@ var ConfigValidator = class {
5399
5397
  // package.json
5400
5398
  var package_default = {
5401
5399
  name: "@retrivora-ai/rag-engine",
5402
- version: "2.2.2",
5400
+ version: "2.2.4",
5403
5401
  description: "Retrivora AI is a plug-and-play AI engine for RAG chat experiences \u2014 generic vector DB + LLM provider, embeddable or standalone.",
5404
5402
  author: "Abhinav Alkuchi",
5405
5403
  license: "UNLICENSED",
@@ -9833,7 +9831,7 @@ ${context}`;
9833
9831
  const tokenCount = Number(((_c2 = finalTrace == null ? void 0 : finalTrace.tokens) == null ? void 0 : _c2.totalTokens) || 0);
9834
9832
  const costEst = Number(((_d2 = finalTrace == null ? void 0 : finalTrace.tokens) == null ? void 0 : _d2.estimatedCostUsd) || 0);
9835
9833
  const latencyDuration = Number(((_e2 = finalTrace == null ? void 0 : finalTrace.latency) == null ? void 0 : _e2.totalMs) || 0);
9836
- console.log(`[Retrivora Pipeline Telemetry] \u{1F4CA} Dispatching RAG telemetry -> Project: "${ns}", Model: "${providerName}/${modelName}", Tokens: ${tokenCount}, Latency: ${latencyDuration}ms`);
9834
+ console.log(`[Retrivora Pipeline Telemetry] \u{1F4CA} Dispatching RAG telemetry -> Project: "${ns}", Tokens: ${tokenCount}, Latency: ${latencyDuration}ms`);
9837
9835
  await fetch(absoluteUrl, {
9838
9836
  method: "POST",
9839
9837
  headers: {
@@ -11257,7 +11255,7 @@ function reportTelemetry(req, plugin, action, status, details, trace) {
11257
11255
  const licenseKey = config.licenseKey || process.env.RAG_LICENSE_KEY || process.env.RETRIVORA_LICENSE_KEY || process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY;
11258
11256
  const telemetryConfig = config.telemetry;
11259
11257
  const enabled = (_a2 = telemetryConfig == null ? void 0 : telemetryConfig.enabled) != null ? _a2 : Boolean(licenseKey);
11260
- const defaultUrl = process.env.NODE_ENV === "development" && !process.env.TELEMETRY_URL && !process.env.NEXT_PUBLIC_TELEMETRY_URL ? "http://localhost:3001/api/telemetry" : "https://retrivora.com/api/telemetry";
11258
+ const defaultUrl = process.env.NODE_ENV === "development" && !process.env.TELEMETRY_URL && !process.env.NEXT_PUBLIC_TELEMETRY_URL ? "http://localhost:3001/api/telemetry" : "https://www.retrivora.com/api/telemetry";
11261
11259
  const telemetryUrl = (telemetryConfig == null ? void 0 : telemetryConfig.url) || process.env.TELEMETRY_URL || process.env.NEXT_PUBLIC_TELEMETRY_URL || defaultUrl;
11262
11260
  const host = req.headers.get("host") || "localhost";
11263
11261
  const userAgent = req.headers.get("user-agent") || `Retrivora-SDK/${SDK_VERSION}`;
@@ -11291,7 +11289,7 @@ function reportTelemetry(req, plugin, action, status, details, trace) {
11291
11289
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11292
11290
  details
11293
11291
  };
11294
- console.log(`[Retrivora SDK Telemetry] \u{1F4CA} Dispatching telemetry -> Feature: "${action}", Status: "${status}", Project: "${projectId}", Model: "${provider}/${model}", Latency: ${latencyMs}ms`);
11292
+ console.log(`[Retrivora SDK Telemetry] Dispatching telemetry -> Feature: "${action}", Status: "${status}", Project: "${projectId}", Latency: ${latencyMs}ms`);
11295
11293
  fetch(absoluteUrl, {
11296
11294
  method: "POST",
11297
11295
  headers: {
@@ -11731,8 +11729,8 @@ function createSuggestionsHandler(configOrPlugin, options) {
11731
11729
  query = url.searchParams.get("query") || "";
11732
11730
  namespace = url.searchParams.get("namespace") || void 0;
11733
11731
  }
11734
- if (typeof query !== "string") {
11735
- return NextResponse.json({ error: "query is required" }, { status: 400 });
11732
+ if (!query || typeof query !== "string" || !query.trim()) {
11733
+ return NextResponse.json({ suggestions: [] });
11736
11734
  }
11737
11735
  const suggestions = await plugin.getSuggestions(query, namespace);
11738
11736
  reportTelemetry(req, plugin, "AUTO_SUGGESTIONS", "success", `Fetched ${suggestions.length} suggestions for: ${query.slice(0, 30)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "2.2.2",
3
+ "version": "2.2.4",
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": "UNLICENSED",
@@ -1,9 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import React, { useState, useRef } from 'react';
4
- import { Upload, File, X, CheckCircle, AlertCircle, Loader2 } from 'lucide-react';
4
+ import { Upload, File, X, CheckCircle, AlertCircle, Loader2, CheckCircle2, Sparkles, Files } from 'lucide-react';
5
5
  import { useConfig } from './ConfigProvider';
6
-
7
6
  import { DocumentUploadProps } from '../types';
8
7
 
9
8
  interface FileState {
@@ -18,6 +17,14 @@ export function DocumentUpload({ namespace, uploadUrl, retrivoraApiBase, onUploa
18
17
  const [isDragging, setIsDragging] = useState(false);
19
18
  const fileInputRef = useRef<HTMLInputElement>(null);
20
19
 
20
+ // Success Modal State
21
+ const [showSuccessModal, setShowSuccessModal] = useState(false);
22
+ const [lastUploadSummary, setLastUploadSummary] = useState<{
23
+ totalFilesCount: number;
24
+ filesUploaded: string[];
25
+ namespaceUsed: string;
26
+ } | null>(null);
27
+
21
28
  const isFreeTier = !projectId || projectId.includes('free') || projectId.includes('demo');
22
29
 
23
30
  const MULTI_DIMENSION_MODELS: Record<string, number[]> = {
@@ -30,7 +37,6 @@ export function DocumentUpload({ namespace, uploadUrl, retrivoraApiBase, onUploa
30
37
  const currentModel = embedding?.model || 'unknown';
31
38
  const defaultDimension = isFreeTier ? 768 : (embedding?.dimensions || 1536);
32
39
 
33
- // Provide specific dimensions for known models, otherwise provide common ones
34
40
  let availableDimensions = isFreeTier
35
41
  ? [768]
36
42
  : Object.entries(MULTI_DIMENSION_MODELS).find(([k]) => currentModel.includes(k))?.[1];
@@ -41,7 +47,6 @@ export function DocumentUpload({ namespace, uploadUrl, retrivoraApiBase, onUploa
41
47
  : [...COMMON_DIMENSIONS, defaultDimension].sort((a, b) => a - b);
42
48
  }
43
49
 
44
- const isChangeable = !isFreeTier;
45
50
  const [dimension, setDimension] = useState(defaultDimension);
46
51
 
47
52
  const addFiles = (files: File[]) => {
@@ -80,12 +85,11 @@ export function DocumentUpload({ namespace, uploadUrl, retrivoraApiBase, onUploa
80
85
  const idleFiles = fileStates.filter(s => s.status === 'idle');
81
86
  if (idleFiles.length === 0) return;
82
87
 
83
- // Update status to uploading
84
88
  setFileStates(prev => prev.map(s => s.status === 'idle' ? { ...s, status: 'uploading' } : s));
85
89
 
86
90
  const formData = new FormData();
87
91
  idleFiles.forEach(s => formData.append('files', s.file));
88
- const targetNs = namespace || projectId;
92
+ const targetNs = namespace || projectId || 'default';
89
93
  if (targetNs) formData.append('namespace', targetNs);
90
94
  formData.append('dimension', dimension.toString());
91
95
 
@@ -100,7 +104,6 @@ export function DocumentUpload({ namespace, uploadUrl, retrivoraApiBase, onUploa
100
104
  body: formData,
101
105
  });
102
106
 
103
- // Fallback resolution: if primaryUrl returns 404 and is not '/api/upload', try '/api/upload'
104
107
  if (response.status === 404 && primaryUrl !== '/api/upload') {
105
108
  console.warn(`[Retrivora SDK] ⚠️ Upload endpoint "${primaryUrl}" returned 404. Retrying fallback to "/api/upload"...`);
106
109
  response = await fetch('/api/upload', {
@@ -116,6 +119,15 @@ export function DocumentUpload({ namespace, uploadUrl, retrivoraApiBase, onUploa
116
119
  console.log(`[Retrivora SDK] ✅ Upload response success:`, result);
117
120
 
118
121
  setFileStates(prev => prev.map(s => s.status === 'uploading' ? { ...s, status: 'success' } : s));
122
+
123
+ const updatedSuccessCount = fileStates.length;
124
+ setLastUploadSummary({
125
+ totalFilesCount: updatedSuccessCount,
126
+ filesUploaded: idleFiles.map(s => s.file.name),
127
+ namespaceUsed: targetNs,
128
+ });
129
+ setShowSuccessModal(true);
130
+
119
131
  if (onUploadComplete) onUploadComplete(result);
120
132
  } catch (err: any) {
121
133
  const message = err instanceof Error ? err.message : 'Upload failed';
@@ -128,127 +140,191 @@ export function DocumentUpload({ namespace, uploadUrl, retrivoraApiBase, onUploa
128
140
  const hasIdle = fileStates.some(s => s.status === 'idle');
129
141
 
130
142
  return (
131
- <div
132
- className={`p-6 border-2 border-dashed transition-all duration-300 rounded-2xl ${
133
- isDragging
134
- ? 'border-emerald-500 bg-emerald-50/50 dark:bg-emerald-500/5'
135
- : 'border-slate-200 dark:border-white/10 bg-white dark:bg-white/5'
136
- } ${className}`}
137
- onDragOver={onDragOver}
138
- onDragLeave={onDragLeave}
139
- onDrop={onDrop}
140
- >
141
- <div className="flex flex-col items-center justify-center text-center">
142
- <div
143
- className="w-12 h-12 rounded-full flex items-center justify-center mb-4 shadow-sm"
144
- style={{ background: `linear-gradient(135deg, ${ui.primaryColor}20, ${ui.accentColor}20)` }}
145
- >
146
- <Upload className="w-6 h-6" style={{ color: ui.primaryColor }} />
147
- </div>
148
- <h3 className="text-slate-900 dark:text-white font-semibold mb-1">Upload Documents</h3>
149
- <p className="text-slate-500 dark:text-white/40 text-sm mb-6 max-w-xs">
150
- Drag and drop PDF, DOCX, Excel (.xlsx, .xls), CSV, TXT, or JSON files to train your AI on your own data.
151
- </p>
152
-
153
- <button
154
- onClick={() => fileInputRef.current?.click()}
155
- disabled={isUploading}
156
- className="px-4 py-2 rounded-lg text-sm font-medium transition-all hover:scale-105 active:scale-95 disabled:opacity-50"
157
- style={{
158
- backgroundColor: `${ui.primaryColor}15`,
159
- color: ui.primaryColor,
160
- border: `1px solid ${ui.primaryColor}30`
161
- }}
162
- >
163
- Select Files
164
- </button>
165
- <input
166
- ref={fileInputRef}
167
- type="file"
168
- multiple
169
- onChange={onFileSelect}
170
- className="hidden"
171
- accept=".pdf,.docx,.doc,.xlsx,.xls,.csv,.txt,.md,.json"
172
- />
173
-
174
- <div className="mt-6 flex flex-col items-center gap-2 text-sm">
175
- <label className="text-slate-600 dark:text-white/60 font-medium">Embedding Dimension</label>
176
- <select
177
- value={dimension}
178
- onChange={(e) => setDimension(Number(e.target.value))}
143
+ <>
144
+ <div
145
+ className={`p-6 border-2 border-dashed transition-all duration-300 rounded-2xl ${
146
+ isDragging
147
+ ? 'border-emerald-500 bg-emerald-50/50 dark:bg-emerald-500/5'
148
+ : 'border-slate-200 dark:border-white/10 bg-white dark:bg-white/5'
149
+ } ${className}`}
150
+ onDragOver={onDragOver}
151
+ onDragLeave={onDragLeave}
152
+ onDrop={onDrop}
153
+ >
154
+ <div className="flex flex-col items-center justify-center text-center">
155
+ <div
156
+ className="w-12 h-12 rounded-full flex items-center justify-center mb-4 shadow-sm"
157
+ style={{ background: `linear-gradient(135deg, ${ui.primaryColor}20, ${ui.accentColor}20)` }}
158
+ >
159
+ <Upload className="w-6 h-6" style={{ color: ui.primaryColor }} />
160
+ </div>
161
+ <h3 className="text-slate-900 dark:text-white font-semibold mb-1">Upload Documents</h3>
162
+ <p className="text-slate-500 dark:text-white/40 text-sm mb-6 max-w-xs">
163
+ Drag and drop PDF, DOCX, Excel (.xlsx, .xls), CSV, TXT, or JSON files to train your AI on your own data.
164
+ </p>
165
+
166
+ <button
167
+ onClick={() => fileInputRef.current?.click()}
179
168
  disabled={isUploading}
180
- className="px-3 py-1.5 rounded-lg bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 outline-none focus:border-emerald-500 transition-colors text-sm font-medium cursor-pointer"
169
+ className="px-4 py-2 rounded-lg text-sm font-medium transition-all hover:scale-105 active:scale-95 disabled:opacity-50"
170
+ style={{
171
+ backgroundColor: `${ui.primaryColor}15`,
172
+ color: ui.primaryColor,
173
+ border: `1px solid ${ui.primaryColor}30`
174
+ }}
181
175
  >
182
- {COMMON_DIMENSIONS.map(dim => {
183
- const isDisabled = isFreeTier && dim !== 768;
184
- return (
185
- <option
186
- key={dim}
187
- value={dim}
188
- disabled={isDisabled}
189
- className="bg-white dark:bg-slate-900 text-slate-800 dark:text-white disabled:text-slate-400 dark:disabled:text-white/30"
190
- >
191
- {dim}
192
- </option>
193
- );
194
- })}
195
- </select>
176
+ Select Files
177
+ </button>
178
+ <input
179
+ ref={fileInputRef}
180
+ type="file"
181
+ multiple
182
+ onChange={onFileSelect}
183
+ className="hidden"
184
+ accept=".pdf,.docx,.doc,.xlsx,.xls,.csv,.txt,.md,.json"
185
+ />
186
+
187
+ <div className="mt-6 flex flex-col items-center gap-2 text-sm">
188
+ <label className="text-slate-600 dark:text-white/60 font-medium">Embedding Dimension</label>
189
+ <select
190
+ value={dimension}
191
+ onChange={(e) => setDimension(Number(e.target.value))}
192
+ disabled={isUploading}
193
+ className="px-3 py-1.5 rounded-lg bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 outline-none focus:border-emerald-500 transition-colors text-sm font-medium cursor-pointer"
194
+ >
195
+ {COMMON_DIMENSIONS.map(dim => {
196
+ const isDisabled = isFreeTier && dim !== 768;
197
+ return (
198
+ <option
199
+ key={dim}
200
+ value={dim}
201
+ disabled={isDisabled}
202
+ className="bg-white dark:bg-slate-900 text-slate-800 dark:text-white disabled:text-slate-400 dark:disabled:text-white/30"
203
+ >
204
+ {dim}
205
+ </option>
206
+ );
207
+ })}
208
+ </select>
209
+ </div>
196
210
  </div>
197
- </div>
198
211
 
199
- {fileStates.length > 0 && (
200
- <div className="mt-8 space-y-3">
201
- {fileStates.map((state, i) => (
202
- <div
203
- key={i}
204
- className="flex items-center justify-between p-3 rounded-xl bg-slate-50 dark:bg-white/5 border border-slate-100 dark:border-white/10"
205
- >
206
- <div className="flex items-center gap-3 overflow-hidden">
207
- <div className="w-8 h-8 rounded-lg bg-white dark:bg-white/10 flex items-center justify-center shadow-sm">
208
- <File className="w-4 h-4 text-slate-400" />
212
+ {fileStates.length > 0 && (
213
+ <div className="mt-8 space-y-3">
214
+ {fileStates.map((state, i) => (
215
+ <div
216
+ key={i}
217
+ className="flex items-center justify-between p-3 rounded-xl bg-slate-50 dark:bg-white/5 border border-slate-100 dark:border-white/10"
218
+ >
219
+ <div className="flex items-center gap-3 overflow-hidden">
220
+ <div className="w-8 h-8 rounded-lg bg-white dark:bg-white/10 flex items-center justify-center shadow-sm">
221
+ <File className="w-4 h-4 text-slate-400" />
222
+ </div>
223
+ <div className="truncate">
224
+ <p className="text-xs font-medium text-slate-700 dark:text-white/80 truncate">
225
+ {state.file.name}
226
+ </p>
227
+ <p className="text-[10px] text-slate-400">
228
+ {(state.file.size / 1024).toFixed(1)} KB
229
+ </p>
230
+ </div>
209
231
  </div>
210
- <div className="truncate">
211
- <p className="text-xs font-medium text-slate-700 dark:text-white/80 truncate">
212
- {state.file.name}
213
- </p>
214
- <p className="text-[10px] text-slate-400">
215
- {(state.file.size / 1024).toFixed(1)} KB
216
- </p>
232
+
233
+ <div className="flex items-center gap-2">
234
+ {state.status === 'uploading' && <Loader2 className="w-4 h-4 text-slate-400 animate-spin" />}
235
+ {state.status === 'success' && <CheckCircle className="w-4 h-4 text-emerald-500" />}
236
+ {state.status === 'error' && (
237
+ <div title={state.error}>
238
+ <AlertCircle className="w-4 h-4 text-rose-500" />
239
+ </div>
240
+ )}
241
+
242
+ {state.status !== 'uploading' && (
243
+ <button
244
+ onClick={() => removeFile(i)}
245
+ className="p-1 rounded-md hover:bg-slate-200 dark:hover:bg-white/10 transition-colors"
246
+ >
247
+ <X className="w-3.5 h-3.5 text-slate-400" />
248
+ </button>
249
+ )}
217
250
  </div>
218
251
  </div>
252
+ ))}
253
+
254
+ {hasIdle && !isUploading && (
255
+ <button
256
+ onClick={uploadFiles}
257
+ className="w-full mt-4 py-2.5 rounded-xl text-sm font-semibold text-white shadow-lg transition-all hover:scale-[1.02] active:scale-[0.98]"
258
+ style={{ background: `linear-gradient(135deg, ${ui.primaryColor}, ${ui.accentColor})` }}
259
+ >
260
+ Start Upload
261
+ </button>
262
+ )}
263
+ </div>
264
+ )}
265
+ </div>
219
266
 
220
- <div className="flex items-center gap-2">
221
- {state.status === 'uploading' && <Loader2 className="w-4 h-4 text-slate-400 animate-spin" />}
222
- {state.status === 'success' && <CheckCircle className="w-4 h-4 text-emerald-500" />}
223
- {state.status === 'error' && (
224
- <div title={state.error}>
225
- <AlertCircle className="w-4 h-4 text-rose-500" />
226
- </div>
227
- )}
228
-
229
- {state.status !== 'uploading' && (
230
- <button
231
- onClick={() => removeFile(i)}
232
- className="p-1 rounded-md hover:bg-slate-200 dark:hover:bg-white/10 transition-colors"
233
- >
234
- <X className="w-3.5 h-3.5 text-slate-400" />
235
- </button>
236
- )}
267
+ {/* ── Upload Successful Modal Popup ────────────────────────────────────────── */}
268
+ {showSuccessModal && lastUploadSummary && (
269
+ <div className="fixed inset-0 bg-slate-950/70 backdrop-blur-sm z-50 flex items-center justify-center p-4 animate-fade-in">
270
+ <div className="bg-white dark:bg-slate-900 rounded-3xl border border-emerald-100 dark:border-emerald-500/20 p-6 max-w-sm w-full shadow-2xl space-y-4 text-slate-900 dark:text-white relative overflow-hidden animate-scale-up">
271
+ <div className="flex flex-col items-center text-center space-y-2">
272
+ <div className="w-14 h-14 rounded-2xl bg-emerald-50 dark:bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 flex items-center justify-center border border-emerald-100 dark:border-emerald-500/20 shadow-sm">
273
+ <CheckCircle2 className="w-8 h-8" />
237
274
  </div>
275
+ <h3 className="text-xl font-extrabold tracking-tight">Upload Successful! 🎉</h3>
276
+ <p className="text-xs text-slate-500 dark:text-white/60 font-medium">
277
+ Your files have been chunked, vectorized, and ingested into Pinecone vector index.
278
+ </p>
238
279
  </div>
239
- ))}
240
280
 
241
- {hasIdle && !isUploading && (
242
- <button
243
- onClick={uploadFiles}
244
- className="w-full mt-4 py-2.5 rounded-xl text-sm font-semibold text-white shadow-lg transition-all hover:scale-[1.02] active:scale-[0.98]"
245
- style={{ background: `linear-gradient(135deg, ${ui.primaryColor}, ${ui.accentColor})` }}
246
- >
247
- Start Upload
248
- </button>
249
- )}
281
+ <div className="p-4 rounded-2xl bg-slate-50 dark:bg-white/5 border border-slate-100 dark:border-white/10 space-y-3">
282
+ <div className="flex items-center justify-between text-xs font-semibold text-slate-700 dark:text-white/90">
283
+ <span className="flex items-center gap-1.5 text-slate-500 dark:text-white/60">
284
+ <Files className="w-4 h-4 text-emerald-500" /> Total Files Ingested:
285
+ </span>
286
+ <span className="font-extrabold font-mono text-emerald-600 dark:text-emerald-400 text-sm">
287
+ {lastUploadSummary.totalFilesCount} Files
288
+ </span>
289
+ </div>
290
+ <div className="flex items-center justify-between text-xs font-semibold text-slate-700 dark:text-white/90">
291
+ <span className="text-slate-500 dark:text-white/60">Target Namespace:</span>
292
+ <span className="font-mono text-[11px] px-2 py-0.5 rounded bg-slate-200 dark:bg-white/10 text-slate-800 dark:text-white">
293
+ {lastUploadSummary.namespaceUsed}
294
+ </span>
295
+ </div>
296
+ </div>
297
+
298
+ <div className="max-h-32 overflow-y-auto space-y-1.5 pr-1 scrollbar-thin">
299
+ {lastUploadSummary.filesUploaded.map((fn, idx) => (
300
+ <div key={idx} className="flex items-center justify-between text-xs p-2 rounded-lg bg-emerald-50/50 dark:bg-emerald-500/5 text-emerald-900 dark:text-emerald-300 border border-emerald-100/50 dark:border-emerald-500/10">
301
+ <span className="truncate font-medium">{fn}</span>
302
+ <Sparkles className="w-3.5 h-3.5 text-emerald-500 shrink-0 ml-2" />
303
+ </div>
304
+ ))}
305
+ </div>
306
+
307
+ <div className="flex gap-2.5 pt-1">
308
+ <button
309
+ onClick={() => {
310
+ setShowSuccessModal(false);
311
+ setFileStates([]);
312
+ }}
313
+ className="flex-1 py-2.5 rounded-xl text-xs font-bold bg-slate-100 dark:bg-white/10 hover:bg-slate-200 dark:hover:bg-white/20 text-slate-700 dark:text-white transition-all cursor-pointer"
314
+ >
315
+ Clear & Reset
316
+ </button>
317
+ <button
318
+ onClick={() => setShowSuccessModal(false)}
319
+ className="flex-1 py-2.5 rounded-xl text-xs font-bold text-white shadow-md transition-all cursor-pointer"
320
+ style={{ backgroundColor: ui.primaryColor }}
321
+ >
322
+ Done
323
+ </button>
324
+ </div>
325
+ </div>
250
326
  </div>
251
327
  )}
252
- </div>
328
+ </>
253
329
  );
254
330
  }
@@ -620,7 +620,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
620
620
  const promptSources = fullSources.slice(0, 15);
621
621
  let context = promptSources.length
622
622
  ? `[SEARCH SUMMARY: Found ${totalCount} matching record(s) out of ${rawSources.length} total resources searched in database. Context includes top ${promptSources.length} relevant records]\n\n` +
623
- promptSources.map((m, i) => `[Source ${i + 1}]\n${m?.content ?? ''}`).join('\n\n---\n\n')
623
+ promptSources.map((m, i) => `[Source ${i + 1}]\n${m?.content ?? ''}`).join('\n\n---\n\n')
624
624
  : 'No relevant context found.';
625
625
 
626
626
  // Do NOT truncate UI sources — keep all qualified matches for UI citations & tables
@@ -938,7 +938,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
938
938
  const costEst = Number(finalTrace?.tokens?.estimatedCostUsd || 0);
939
939
  const latencyDuration = Number(finalTrace?.latency?.totalMs || 0);
940
940
 
941
- console.log(`[Retrivora Pipeline Telemetry] 📊 Dispatching RAG telemetry -> Project: "${ns}", Model: "${providerName}/${modelName}", Tokens: ${tokenCount}, Latency: ${latencyDuration}ms`);
941
+ console.log(`[Retrivora Pipeline Telemetry] 📊 Dispatching RAG telemetry -> Project: "${ns}", Tokens: ${tokenCount}, Latency: ${latencyDuration}ms`);
942
942
 
943
943
  await fetch(absoluteUrl, {
944
944
  method: 'POST',
@@ -169,7 +169,7 @@ function reportTelemetry(
169
169
  const enabled = telemetryConfig?.enabled ?? Boolean(licenseKey);
170
170
  const defaultUrl = process.env.NODE_ENV === 'development' && !process.env.TELEMETRY_URL && !process.env.NEXT_PUBLIC_TELEMETRY_URL
171
171
  ? 'http://localhost:3001/api/telemetry'
172
- : 'https://retrivora.com/api/telemetry';
172
+ : 'https://www.retrivora.com/api/telemetry';
173
173
 
174
174
  const telemetryUrl = telemetryConfig?.url || process.env.TELEMETRY_URL || process.env.NEXT_PUBLIC_TELEMETRY_URL || defaultUrl;
175
175
  const host = req.headers.get('host') || 'localhost';
@@ -208,7 +208,7 @@ function reportTelemetry(
208
208
  details,
209
209
  };
210
210
 
211
- console.log(`[Retrivora SDK Telemetry] 📊 Dispatching telemetry -> Feature: "${action}", Status: "${status}", Project: "${projectId}", Model: "${provider}/${model}", Latency: ${latencyMs}ms`);
211
+ console.log(`[Retrivora SDK Telemetry] Dispatching telemetry -> Feature: "${action}", Status: "${status}", Project: "${projectId}", Latency: ${latencyMs}ms`);
212
212
 
213
213
  fetch(absoluteUrl, {
214
214
  method: 'POST',
@@ -766,8 +766,8 @@ export function createSuggestionsHandler(
766
766
  namespace = url.searchParams.get('namespace') || undefined;
767
767
  }
768
768
 
769
- if (typeof query !== 'string') {
770
- return NextResponse.json({ error: 'query is required' }, { status: 400 });
769
+ if (!query || typeof query !== 'string' || !query.trim()) {
770
+ return NextResponse.json({ suggestions: [] });
771
771
  }
772
772
 
773
773
  const suggestions = await plugin.getSuggestions(query, namespace);