@retrivora-ai/rag-engine 2.1.3 → 2.1.5

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.
@@ -12,12 +12,14 @@ interface FileState {
12
12
  error?: string;
13
13
  }
14
14
 
15
- export function DocumentUpload({ namespace, onUploadComplete, className = '' }: DocumentUploadProps) {
15
+ export function DocumentUpload({ namespace, uploadUrl, retrivoraApiBase, onUploadComplete, className = '' }: DocumentUploadProps) {
16
16
  const { ui, embedding, projectId } = useConfig();
17
17
  const [fileStates, setFileStates] = useState<FileState[]>([]);
18
18
  const [isDragging, setIsDragging] = useState(false);
19
19
  const fileInputRef = useRef<HTMLInputElement>(null);
20
20
 
21
+ const isFreeTier = !projectId || projectId.includes('free') || projectId.includes('demo');
22
+
21
23
  const MULTI_DIMENSION_MODELS: Record<string, number[]> = {
22
24
  'text-embedding-3-small': [512, 1536],
23
25
  'text-embedding-3-large': [256, 1024, 3072],
@@ -26,18 +28,20 @@ export function DocumentUpload({ namespace, onUploadComplete, className = '' }:
26
28
 
27
29
  const COMMON_DIMENSIONS = [64, 128, 256, 384, 512, 768, 1024, 1536, 3072];
28
30
  const currentModel = embedding?.model || 'unknown';
29
- const defaultDimension = embedding?.dimensions || 1536;
31
+ const defaultDimension = isFreeTier ? 768 : (embedding?.dimensions || 1536);
30
32
 
31
33
  // Provide specific dimensions for known models, otherwise provide common ones
32
- let availableDimensions = Object.entries(MULTI_DIMENSION_MODELS).find(([k]) => currentModel.includes(k))?.[1];
34
+ let availableDimensions = isFreeTier
35
+ ? [768]
36
+ : Object.entries(MULTI_DIMENSION_MODELS).find(([k]) => currentModel.includes(k))?.[1];
37
+
33
38
  if (!availableDimensions) {
34
39
  availableDimensions = COMMON_DIMENSIONS.includes(defaultDimension)
35
40
  ? COMMON_DIMENSIONS
36
41
  : [...COMMON_DIMENSIONS, defaultDimension].sort((a, b) => a - b);
37
42
  }
38
-
39
- const isChangeable = true; // Always allow the user to select the dimension
40
43
 
44
+ const isChangeable = !isFreeTier;
41
45
  const [dimension, setDimension] = useState(defaultDimension);
42
46
 
43
47
  const addFiles = (files: File[]) => {
@@ -85,20 +89,37 @@ export function DocumentUpload({ namespace, onUploadComplete, className = '' }:
85
89
  if (targetNs) formData.append('namespace', targetNs);
86
90
  formData.append('dimension', dimension.toString());
87
91
 
92
+ const apiBase = retrivoraApiBase || '/api/retrivora';
93
+ const primaryUrl = uploadUrl || `${apiBase.replace(/\/$/, '')}/upload`;
94
+
95
+ console.log(`[Retrivora SDK] 📤 Initiating upload request to "${primaryUrl}" (Files: ${idleFiles.length}, Namespace: "${targetNs}", Dimension: ${dimension})`);
96
+
88
97
  try {
89
- const response = await fetch('/api/upload', {
98
+ let response = await fetch(primaryUrl, {
90
99
  method: 'POST',
91
100
  body: formData,
92
101
  });
93
102
 
103
+ // Fallback resolution: if primaryUrl returns 404 and is not '/api/upload', try '/api/upload'
104
+ if (response.status === 404 && primaryUrl !== '/api/upload') {
105
+ console.warn(`[Retrivora SDK] ⚠️ Upload endpoint "${primaryUrl}" returned 404. Retrying fallback to "/api/upload"...`);
106
+ response = await fetch('/api/upload', {
107
+ method: 'POST',
108
+ body: formData,
109
+ });
110
+ }
111
+
94
112
  const result = await response.json();
95
113
 
96
- if (!response.ok) throw new Error(result.error || 'Upload failed');
114
+ if (!response.ok) throw new Error(result.error || `Upload failed with HTTP status ${response.status}`);
115
+
116
+ console.log(`[Retrivora SDK] ✅ Upload response success:`, result);
97
117
 
98
118
  setFileStates(prev => prev.map(s => s.status === 'uploading' ? { ...s, status: 'success' } : s));
99
119
  if (onUploadComplete) onUploadComplete(result);
100
- } catch (err) {
120
+ } catch (err: any) {
101
121
  const message = err instanceof Error ? err.message : 'Upload failed';
122
+ console.error(`[Retrivora SDK] ❌ Document upload failed:`, message);
102
123
  setFileStates(prev => prev.map(s => s.status === 'uploading' ? { ...s, status: 'error', error: message } : s));
103
124
  }
104
125
  };
@@ -152,22 +173,33 @@ export function DocumentUpload({ namespace, onUploadComplete, className = '' }:
152
173
 
153
174
  <div className="mt-6 flex flex-col items-center gap-2 text-sm">
154
175
  <label className="text-slate-600 dark:text-white/60 font-medium">Embedding Dimension</label>
155
- {isChangeable ? (
176
+ <div className="flex flex-col items-center gap-1.5">
156
177
  <select
157
178
  value={dimension}
158
179
  onChange={(e) => setDimension(Number(e.target.value))}
159
180
  disabled={isUploading}
160
- 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"
181
+ 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"
161
182
  >
162
- {availableDimensions.map(dim => (
163
- <option key={dim} value={dim} className="bg-white dark:bg-slate-900">{dim}</option>
164
- ))}
183
+ {COMMON_DIMENSIONS.map(dim => {
184
+ const isDisabled = isFreeTier && dim !== 768;
185
+ return (
186
+ <option
187
+ key={dim}
188
+ value={dim}
189
+ disabled={isDisabled}
190
+ className="bg-white dark:bg-slate-900 text-slate-800 dark:text-white disabled:text-slate-400 dark:disabled:text-white/30"
191
+ >
192
+ {dim} {dim === 768 ? '(Free Tier Fixed)' : isDisabled ? '(Pro/Enterprise Only)' : ''}
193
+ </option>
194
+ );
195
+ })}
165
196
  </select>
166
- ) : (
167
- <div className="px-3 py-1.5 rounded-lg bg-slate-100 dark:bg-white/10 border border-slate-200 dark:border-white/10 text-slate-500 dark:text-white/50 cursor-not-allowed text-xs">
168
- {dimension} (Fixed for {currentModel})
169
- </div>
170
- )}
197
+ {isFreeTier && (
198
+ <span className="text-[11px] text-amber-600 dark:text-amber-400/90 font-medium">
199
+ 768 Dimension active for Free Tier (Upgrade for 1536 / 3072)
200
+ </span>
201
+ )}
202
+ </div>
171
203
  </div>
172
204
  </div>
173
205
 
@@ -85,8 +85,8 @@ export function getEnvConfig(env: Record<string, string | undefined> = process.e
85
85
  const vectorDbOptions: Record<string, unknown> = {};
86
86
 
87
87
  if (vectorProvider === 'pinecone') {
88
- vectorDbOptions.apiKey = readString(env, 'PINECONE_API_KEY') ?? (base?.vectorDb?.options?.apiKey as string) ?? '';
89
- vectorDbOptions.indexName = readString(env, 'PINECONE_INDEX') ?? (base?.vectorDb?.indexName as string) ?? (base?.vectorDb?.options?.indexName as string);
88
+ vectorDbOptions.apiKey = readString(env, 'PINECONE_API_KEY') ?? (base?.vectorDb?.options?.apiKey as string) ?? process.env.PINECONE_API_KEY ?? '';
89
+ vectorDbOptions.indexName = readString(env, 'PINECONE_INDEX') ?? (base?.vectorDb?.indexName as string) ?? (base?.vectorDb?.options?.indexName as string) ?? process.env.PINECONE_INDEX ?? 'retrivora-free';
90
90
  } else if (vectorProvider === 'pgvector' || vectorProvider === 'postgresql') {
91
91
  vectorDbOptions.connectionString = readString(env, 'PGVECTOR_CONNECTION_STRING') ?? readString(env, 'POSTGRES_URL') ?? (base?.vectorDb?.options?.connectionString as string) ?? '';
92
92
  vectorDbOptions.tables = (readString(env, 'VECTOR_DB_TABLES') ?? readString(env, 'POSTGRES_TABLES'))?.split(',').map(t => t.trim()) ?? (base?.vectorDb?.options?.tables as string[]);
@@ -1,4 +1,5 @@
1
1
  import { BaseVectorProvider } from '../providers/vectordb/BaseVectorProvider';
2
+ import { SDK_VERSION } from '../version';
2
3
  import { BaseGraphProvider } from '../providers/graphdb/BaseGraphProvider';
3
4
  import { ILLMProvider } from '../llm/ILLMProvider';
4
5
  import { ChatMessage } from '../types';
@@ -933,6 +934,14 @@ You are a helpful assistant. Use the provided context to answer questions accura
933
934
  }
934
935
  }
935
936
 
937
+ const modelName = finalTrace?.model || this.config.llm?.model || 'llama-3.1-8b-instant';
938
+ const providerName = finalTrace?.provider || this.config.llm?.provider || 'groq';
939
+ const tokenCount = Number(finalTrace?.tokens?.totalTokens || 0);
940
+ const costEst = Number(finalTrace?.tokens?.estimatedCostUsd || 0);
941
+ const latencyDuration = Number(finalTrace?.latency?.totalMs || 0);
942
+
943
+ console.log(`[Retrivora Pipeline Telemetry] 📊 Dispatching RAG telemetry -> Project: "${ns}", Model: "${providerName}/${modelName}", Tokens: ${tokenCount}, Latency: ${latencyDuration}ms`);
944
+
936
945
  await fetch(absoluteUrl, {
937
946
  method: 'POST',
938
947
  headers: {
@@ -941,7 +950,20 @@ You are a helpful assistant. Use the provided context to answer questions accura
941
950
  body: JSON.stringify({
942
951
  trace: finalTrace,
943
952
  licenseKey: this.config.licenseKey,
944
- projectId: ns
953
+ projectId: ns,
954
+ organization: process.env.RETRIVORA_ORGANIZATION || 'default-org',
955
+ sdkVersion: SDK_VERSION,
956
+ model: modelName,
957
+ provider: providerName,
958
+ tokens: tokenCount,
959
+ cost: costEst,
960
+ costUsd: costEst,
961
+ latencyMs: latencyDuration,
962
+ status: 'success',
963
+ action: 'RAG_QUERY',
964
+ feature: 'RAG',
965
+ timestamp: new Date().toISOString(),
966
+ details: `Org: default-org | Model: ${providerName}/${modelName} | Tokens: ${tokenCount} | Latency: ${latencyDuration}ms | Cost: $${costEst} | SDK: v${SDK_VERSION} | Feature: RAG`
945
967
  })
946
968
  });
947
969
  } catch (err: any) {
@@ -147,6 +147,8 @@ function getOrCreatePlugin(configOrPlugin: Partial<RagConfig> | VectorPlugin | u
147
147
 
148
148
  // ─── Telemetry Reporter Helper ───────────────────────────────────────────────
149
149
 
150
+ import { SDK_VERSION } from '../version';
151
+
150
152
  /**
151
153
  * Asynchronously sends telemetry payload to telemetryUrl when telemetry is enabled
152
154
  * or when a licenseKey is set. Non-blocking.
@@ -157,7 +159,7 @@ function reportTelemetry(
157
159
  action: string,
158
160
  status: 'success' | 'error',
159
161
  details: string,
160
- trace?: unknown
162
+ trace?: any
161
163
  ) {
162
164
  try {
163
165
  const config = plugin.getConfig();
@@ -171,6 +173,7 @@ function reportTelemetry(
171
173
 
172
174
  const telemetryUrl = telemetryConfig?.url || process.env.TELEMETRY_URL || process.env.NEXT_PUBLIC_TELEMETRY_URL || defaultUrl;
173
175
  const host = req.headers.get('host') || 'localhost';
176
+ const userAgent = req.headers.get('user-agent') || `Retrivora-SDK/${SDK_VERSION}`;
174
177
 
175
178
  let absoluteUrl = telemetryUrl;
176
179
  if (!telemetryUrl.startsWith('http')) {
@@ -179,6 +182,33 @@ function reportTelemetry(
179
182
  }
180
183
 
181
184
  const projectId = config.projectId || 'default';
185
+ const model = trace?.model || config.llm?.model || config.embedding?.model || 'llama-3.1-8b-instant';
186
+ const provider = trace?.provider || config.llm?.provider || config.embedding?.provider || 'groq';
187
+ const tokens = Number(trace?.tokens?.totalTokens || trace?.totalTokens || 0);
188
+ const costUsd = Number(trace?.tokens?.estimatedCostUsd || trace?.costUsd || 0);
189
+ const latencyMs = Number(trace?.latency?.totalMs || trace?.latencyMs || 0);
190
+
191
+ const payload = {
192
+ trace,
193
+ licenseKey,
194
+ projectId,
195
+ organization: process.env.RETRIVORA_ORGANIZATION || 'default-org',
196
+ sdkVersion: SDK_VERSION,
197
+ model,
198
+ provider,
199
+ tokens,
200
+ cost: costUsd,
201
+ costUsd,
202
+ latencyMs,
203
+ status,
204
+ action,
205
+ feature: action,
206
+ userAgent,
207
+ timestamp: new Date().toISOString(),
208
+ details,
209
+ };
210
+
211
+ console.log(`[Retrivora SDK Telemetry] 📊 Dispatching telemetry -> Feature: "${action}", Status: "${status}", Project: "${projectId}", Model: "${provider}/${model}", Latency: ${latencyMs}ms`);
182
212
 
183
213
  fetch(absoluteUrl, {
184
214
  method: 'POST',
@@ -186,17 +216,11 @@ function reportTelemetry(
186
216
  'Content-Type': 'application/json',
187
217
  'x-forwarded-for': req.headers.get('x-forwarded-for') || '',
188
218
  'x-real-ip': req.headers.get('x-real-ip') || '',
219
+ 'user-agent': userAgent,
189
220
  },
190
- body: JSON.stringify({
191
- trace,
192
- licenseKey,
193
- projectId,
194
- action,
195
- status,
196
- details,
197
- }),
221
+ body: JSON.stringify(payload),
198
222
  }).catch(err => {
199
- console.warn('[Retrivora Telemetry] Async report warning:', err.message);
223
+ console.warn('[Retrivora SDK Telemetry] Async report warning:', err.message);
200
224
  });
201
225
  } catch {
202
226
  /* non-blocking */
@@ -354,6 +378,12 @@ export function createStreamHandler(
354
378
  userMessageId?: string;
355
379
  };
356
380
  try {
381
+ if (req.headers.get('content-type')?.includes('multipart/form-data')) {
382
+ return new Response(JSON.stringify({ error: { code: 'INVALID_ENDPOINT', message: 'Multipart file upload requests must be sent to the /upload endpoint.' } }), {
383
+ status: 400,
384
+ headers: { 'Content-Type': 'application/json' },
385
+ });
386
+ }
357
387
  body = await req.json();
358
388
  } catch {
359
389
  return new Response(JSON.stringify({ error: { code: 'INVALID_JSON', message: 'Invalid JSON body' } }), {
@@ -937,19 +967,39 @@ export function createRagHandler(
937
967
  }
938
968
  }
939
969
 
940
- async function getSegment(context: any): Promise<string> {
970
+ async function getSegment(req: NextRequest, context: any): Promise<string> {
971
+ // 1. If content-type is multipart/form-data, it MUST be an upload request!
972
+ const contentType = req.headers.get('content-type') || '';
973
+ if (contentType.includes('multipart/form-data')) {
974
+ return 'upload';
975
+ }
976
+
977
+ // 2. Check path directly from request URL
978
+ try {
979
+ const url = new URL(req.url);
980
+ const pathname = url.pathname;
981
+ if (pathname.endsWith('/upload')) return 'upload';
982
+ if (pathname.endsWith('/chat') || pathname.endsWith('/chat-sync')) return 'chat';
983
+ if (pathname.endsWith('/health')) return 'health';
984
+ if (pathname.endsWith('/suggestions')) return 'suggestions';
985
+ if (pathname.endsWith('/feedback')) return 'feedback';
986
+ if (pathname.endsWith('/history')) return 'history';
987
+ } catch { /* silent */ }
988
+
989
+ // 3. Fall back to resolved context params
941
990
  const resolvedParams = typeof context?.params?.then === 'function'
942
991
  ? await context.params
943
992
  : context?.params;
944
993
 
945
- const segments: string[] = resolvedParams?.retrivora || [];
946
- return segments.join('/') || 'chat';
994
+ const segments: string[] = resolvedParams?.retrivora || resolvedParams?.params?.retrivora || [];
995
+ const joined = segments.join('/');
996
+ return joined || 'chat';
947
997
  }
948
998
 
949
999
  return {
950
1000
  GET: async (req: NextRequest, context: any) => {
951
1001
  try {
952
- const segment = await getSegment(context);
1002
+ const segment = await getSegment(req, context);
953
1003
  return await routeGetRequest(req, segment);
954
1004
  } catch (err) {
955
1005
  const msg = err instanceof Error ? err.message : 'GET Routing failed';
@@ -958,7 +1008,7 @@ export function createRagHandler(
958
1008
  },
959
1009
  POST: async (req: NextRequest, context: any) => {
960
1010
  try {
961
- const segment = await getSegment(context);
1011
+ const segment = await getSegment(req, context);
962
1012
  return await routePostRequest(req, segment);
963
1013
  } catch (err) {
964
1014
  const msg = err instanceof Error ? err.message : 'POST Routing failed';
package/src/index.css CHANGED
@@ -2259,6 +2259,11 @@
2259
2259
  cursor: not-allowed;
2260
2260
  }
2261
2261
  }
2262
+ .disabled\:text-slate-400 {
2263
+ &:disabled {
2264
+ color: var(--color-slate-400);
2265
+ }
2266
+ }
2262
2267
  .disabled\:opacity-30 {
2263
2268
  &:disabled {
2264
2269
  opacity: 30%;
@@ -2912,6 +2917,14 @@
2912
2917
  color: var(--color-amber-400);
2913
2918
  }
2914
2919
  }
2920
+ .dark\:text-amber-400\/90 {
2921
+ @media (prefers-color-scheme: dark) {
2922
+ color: color-mix(in srgb, oklch(82.8% 0.189 84.429) 90%, transparent);
2923
+ @supports (color: color-mix(in lab, red, red)) {
2924
+ color: color-mix(in oklab, var(--color-amber-400) 90%, transparent);
2925
+ }
2926
+ }
2927
+ }
2915
2928
  .dark\:text-blue-100 {
2916
2929
  @media (prefers-color-scheme: dark) {
2917
2930
  color: var(--color-blue-100);
@@ -3371,6 +3384,16 @@
3371
3384
  }
3372
3385
  }
3373
3386
  }
3387
+ .dark\:disabled\:text-white\/30 {
3388
+ @media (prefers-color-scheme: dark) {
3389
+ &:disabled {
3390
+ color: color-mix(in srgb, #fff 30%, transparent);
3391
+ @supports (color: color-mix(in lab, red, red)) {
3392
+ color: color-mix(in oklab, var(--color-white) 30%, transparent);
3393
+ }
3394
+ }
3395
+ }
3396
+ }
3374
3397
  }
3375
3398
  @layer base {
3376
3399
  :root {
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ export { MessageBubble } from './components/MessageBubble';
15
15
  export { SourceCard } from './components/SourceCard';
16
16
  export { ObservabilityPanel } from './components/ObservabilityPanel';
17
17
  export { ConfigProvider, useConfig } from './components/ConfigProvider';
18
+ export { SDK_VERSION } from './version';
18
19
  export { CodeViewer } from './components/CodeViewer';
19
20
  export { ProductCard } from './components/ProductCard';
20
21
  export { ProductCarousel } from './components/ProductCarousel';
package/src/server.ts CHANGED
@@ -36,6 +36,7 @@ export { ConfigResolver } from './core/ConfigResolver';
36
36
  export { ProviderRegistry } from './core/ProviderRegistry';
37
37
  export { ConfigValidator } from './core/ConfigValidator';
38
38
  export { LicenseVerifier } from './core/LicenseVerifier';
39
+ export { SDK_VERSION } from './version';
39
40
  export { ProviderHealthCheck } from './core/ProviderHealthCheck';
40
41
  export { BatchProcessor } from './core/BatchProcessor';
41
42
 
@@ -40,6 +40,8 @@ export interface ObservabilityTrace {
40
40
  chunks: RetrievedChunk[];
41
41
  latency: LatencyBreakdown;
42
42
  tokens?: TokenUsage;
43
+ model?: string;
44
+ provider?: string;
43
45
  /** 0 = fully grounded, 1 = likely hallucinated. */
44
46
  hallucinationScore?: number;
45
47
  hallucinationReason?: string;
@@ -109,6 +109,10 @@ export interface ProductCarouselProps {
109
109
  export interface DocumentUploadProps {
110
110
  /** Optional namespace for the upload */
111
111
  namespace?: string;
112
+ /** Custom upload API endpoint URL (defaults to /api/retrivora/upload or /api/upload) */
113
+ uploadUrl?: string;
114
+ /** Base URL for Retrivora SDK endpoints (defaults to /api/retrivora) */
115
+ retrivoraApiBase?: string;
112
116
  /** Callback when upload completes */
113
117
  onUploadComplete?: (results: unknown) => void;
114
118
  /** Additional className */
package/src/version.ts ADDED
@@ -0,0 +1,6 @@
1
+ import pkg from '../package.json';
2
+
3
+ /**
4
+ * Dynamically resolved SDK Version from package.json
5
+ */
6
+ export const SDK_VERSION: string = pkg.version || '2.1.3';