@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/handlers/index.js +7 -9
- package/dist/handlers/index.mjs +7 -9
- package/dist/index.css +44 -0
- package/dist/index.js +199 -128
- package/dist/index.mjs +224 -153
- package/dist/server.js +7 -9
- package/dist/server.mjs +7 -9
- package/package.json +1 -1
- package/src/components/DocumentUpload.tsx +192 -116
- package/src/core/Pipeline.ts +2 -2
- package/src/handlers/index.ts +4 -4
- package/src/hooks/useRagChat.ts +30 -14
- package/src/index.css +44 -0
- package/src/llm/providers/UniversalLLMAdapter.ts +1 -1
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("
|
|
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.
|
|
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}",
|
|
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]
|
|
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({
|
|
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("
|
|
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.
|
|
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}",
|
|
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]
|
|
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({
|
|
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.
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<div
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
{
|
|
216
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
328
|
+
</>
|
|
253
329
|
);
|
|
254
330
|
}
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -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
|
-
|
|
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}",
|
|
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',
|
package/src/handlers/index.ts
CHANGED
|
@@ -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]
|
|
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({
|
|
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);
|