@retrivora-ai/rag-engine 1.9.8 → 1.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +36 -28
  2. package/dist/{ILLMProvider-B8ROITYK.d.mts → ILLMProvider-DMxLyTdq.d.mts} +2 -2
  3. package/dist/{ILLMProvider-B8ROITYK.d.ts → ILLMProvider-DMxLyTdq.d.ts} +2 -2
  4. package/dist/handlers/index.d.mts +2 -2
  5. package/dist/handlers/index.d.ts +2 -2
  6. package/dist/handlers/index.js +140 -28
  7. package/dist/handlers/index.mjs +146 -28
  8. package/dist/{index-DNvoi-sV.d.ts → index-CfkqZd2Y.d.ts} +1 -1
  9. package/dist/{index-CrxCy36A.d.mts → index-DXd29KMq.d.mts} +1 -1
  10. package/dist/{index-BCPKUAVL.d.ts → index-D_bOdJML.d.ts} +1 -1
  11. package/dist/{index-B8PbEFSY.d.mts → index-xygonxpW.d.mts} +1 -1
  12. package/dist/index.css +485 -557
  13. package/dist/index.d.mts +16 -10
  14. package/dist/index.d.ts +16 -10
  15. package/dist/index.js +24 -765
  16. package/dist/index.mjs +20 -779
  17. package/dist/server.d.mts +8 -5
  18. package/dist/server.d.ts +8 -5
  19. package/dist/server.js +141 -28
  20. package/dist/server.mjs +147 -28
  21. package/package.json +3 -2
  22. package/src/app/page.tsx +2 -0
  23. package/src/components/constants.tsx +224 -0
  24. package/src/config/RagConfig.ts +2 -2
  25. package/src/core/ConfigResolver.ts +32 -6
  26. package/src/core/LicenseVerifier.ts +106 -0
  27. package/src/core/Pipeline.ts +8 -6
  28. package/src/core/Retrivora.ts +1 -0
  29. package/src/index.ts +3 -5
  30. package/src/components/AmbientBackground.tsx +0 -29
  31. package/src/components/ArchitectureCard.tsx +0 -53
  32. package/src/components/ArchitectureCardsSection.tsx +0 -48
  33. package/src/components/DocViewer.tsx +0 -97
  34. package/src/components/Documentation.tsx +0 -141
  35. package/src/components/Hero.tsx +0 -142
  36. package/src/components/Lifecycle.tsx +0 -77
  37. package/src/components/Navbar.tsx +0 -55
@@ -117,22 +117,48 @@ export class ConfigResolver {
117
117
  normalized.useReranking = retrieval.useReranking ?? normalized.useReranking;
118
118
 
119
119
  if (retrieval.strategy === 'hybrid') normalized.architecture = normalized.architecture ?? 'hybrid';
120
- if (retrieval.strategy === 'agentic') normalized.architecture = 'agentic';
121
- if (retrieval.strategy === 'contextual-compression') normalized.useReranking = true;
120
+ if (retrieval.strategy === 'agentic') normalized.architecture = 'multi-agent';
121
+ if (retrieval.strategy === 'contextual-compression') {
122
+ normalized.useReranking = true;
123
+ normalized.architecture = normalized.architecture ?? 'retrieve-and-rerank';
124
+ }
122
125
  }
123
126
 
124
127
  if (workflow?.type) {
125
128
  const type = workflow.type;
126
- if (type === 'agentic' || type === 'agentic-rag') normalized.architecture = 'agentic';
127
- else if (type === 'hybrid-rag') normalized.architecture = 'hybrid';
128
- else if (type === 'graph-rag') {
129
+ if (type === 'naive-rag') {
130
+ normalized.architecture = 'naive';
131
+ normalized.useReranking = false;
132
+ normalized.useGraphRetrieval = false;
133
+ } else if (type === 'retrieve-and-rerank') {
134
+ normalized.architecture = 'retrieve-and-rerank';
135
+ normalized.useReranking = true;
136
+ } else if (type === 'multimodal-rag') {
137
+ normalized.architecture = 'multimodal';
138
+ } else if (type === 'graph-rag') {
129
139
  normalized.architecture = 'graph';
130
140
  normalized.useGraphRetrieval = true;
141
+ } else if (type === 'hybrid-rag') {
142
+ normalized.architecture = 'hybrid';
143
+ } else if (type === 'agentic-router') {
144
+ normalized.architecture = 'agentic-router';
145
+ } else if (type === 'multi-agent-rag' || type === 'multi-agent' || type === 'agentic' || type === 'agentic-rag') {
146
+ normalized.architecture = 'multi-agent';
131
147
  } else if (type === 'simple-rag' || type === 'rag') {
132
- normalized.architecture = normalized.architecture ?? 'simple';
148
+ normalized.architecture = normalized.architecture ?? 'naive';
133
149
  }
134
150
  }
135
151
 
152
+ // Default fallbacks based on architecture configuration
153
+ if (normalized.architecture === 'retrieve-and-rerank') {
154
+ normalized.useReranking = true;
155
+ } else if (normalized.architecture === 'graph') {
156
+ normalized.useGraphRetrieval = true;
157
+ } else if (normalized.architecture === 'naive') {
158
+ normalized.useReranking = false;
159
+ normalized.useGraphRetrieval = false;
160
+ }
161
+
136
162
  return normalized;
137
163
  }
138
164
  }
@@ -12,6 +12,13 @@ export interface LicensePayload {
12
12
  * Enables zero-latency local license validation without external network calls.
13
13
  */
14
14
  export class LicenseVerifier {
15
+ // A simple in-memory cache to save CPU overhead of cryptographic signature checks.
16
+ private static readonly cache = new Map<
17
+ string,
18
+ { payload: LicensePayload; cachedAt: number }
19
+ >();
20
+ private static readonly CACHE_TTL_MS = 60 * 1000; // Cache verified license for 60 seconds
21
+
15
22
  // Retrivora's Public Key used to verify the license key signature.
16
23
  // In production, this matches the private key held by Retrivora SaaS.
17
24
  private static readonly PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
@@ -38,6 +45,22 @@ MwIDAQAB
38
45
  publicKeyOverride?: string
39
46
  ): LicensePayload {
40
47
  const isProduction = process.env.NODE_ENV === 'production';
48
+ const now = Date.now();
49
+
50
+ // Check verification cache to bypass crypto computations on hot paths
51
+ if (licenseKey) {
52
+ const cacheKey = `${licenseKey}:${currentProjectId}:${provider || ''}:${publicKeyOverride || ''}`;
53
+ const cached = this.cache.get(cacheKey);
54
+ if (cached && now - cached.cachedAt < this.CACHE_TTL_MS) {
55
+ // Enforce expiration check even for cached items
56
+ const currentTimestampSec = Math.floor(now / 1000);
57
+ if (cached.payload.expiresAt && currentTimestampSec > cached.payload.expiresAt) {
58
+ this.cache.delete(cacheKey);
59
+ } else {
60
+ return cached.payload;
61
+ }
62
+ }
63
+ }
41
64
 
42
65
  // 1. Development Fallback (Fail-open locally but log warning)
43
66
  if (!licenseKey) {
@@ -143,6 +166,11 @@ MwIDAQAB
143
166
  // Enterprise has access to all databases (including redis, weaviate, custom ones)
144
167
  }
145
168
 
169
+ if (licenseKey) {
170
+ const cacheKey = `${licenseKey}:${currentProjectId}:${provider || ''}:${publicKeyOverride || ''}`;
171
+ this.cache.set(cacheKey, { payload, cachedAt: now });
172
+ }
173
+
146
174
  return payload;
147
175
  } catch (err) {
148
176
  const message = err instanceof Error ? err.message : String(err);
@@ -151,4 +179,82 @@ MwIDAQAB
151
179
  );
152
180
  }
153
181
  }
182
+
183
+ static async verifyIP(licenseKey: string | undefined): Promise<void> {
184
+ if (!licenseKey) return;
185
+ try {
186
+ const parts = licenseKey.split('.');
187
+ if (parts.length !== 3) return;
188
+ const [, payloadB64] = parts;
189
+ const payload: LicensePayload & { ipv4?: string; ipv6?: string } = JSON.parse(
190
+ Buffer.from(payloadB64, 'base64url').toString('utf8')
191
+ );
192
+
193
+ const tier = (payload.tier || '').toLowerCase();
194
+ // Skip check if Enterprise or Custom
195
+ if (tier === 'enterprise' || tier === 'custom') {
196
+ return;
197
+ }
198
+
199
+ // If Hobby (free_trial) or Developer Pro, perform the check
200
+ if (payload.ipv4 || payload.ipv6) {
201
+ let currentIpv4 = '';
202
+ let currentIpv6 = '';
203
+
204
+ try {
205
+ const res = await fetch('https://api4.ipify.org?format=json', { signal: AbortSignal.timeout(3000) });
206
+ const data = await res.json();
207
+ currentIpv4 = data.ip;
208
+ } catch (e) {}
209
+
210
+ try {
211
+ const res = await fetch('https://api6.ipify.org?format=json', { signal: AbortSignal.timeout(3000) });
212
+ const data = await res.json();
213
+ currentIpv6 = data.ip;
214
+ } catch (e) {}
215
+
216
+ const localIps: string[] = [];
217
+ try {
218
+ const osModule = require('os');
219
+ const interfaces = osModule.networkInterfaces();
220
+ for (const name of Object.keys(interfaces)) {
221
+ for (const iface of interfaces[name] || []) {
222
+ if (!iface.internal) {
223
+ localIps.push(iface.address);
224
+ }
225
+ }
226
+ }
227
+ } catch (e) {}
228
+
229
+ const isIpv4Match = payload.ipv4 && (
230
+ currentIpv4 === payload.ipv4 ||
231
+ localIps.includes(payload.ipv4)
232
+ );
233
+ const isIpv6Match = payload.ipv6 && (
234
+ currentIpv6 === payload.ipv6 ||
235
+ localIps.includes(payload.ipv6)
236
+ );
237
+
238
+ if (payload.ipv4 && payload.ipv6) {
239
+ if (!isIpv4Match && !isIpv6Match) {
240
+ throw new Error(
241
+ `License key access restricted. This license key was created for a different system (authorized IPv4: ${payload.ipv4}, IPv6: ${payload.ipv6}) and cannot be used on this machine.`
242
+ );
243
+ }
244
+ } else if (payload.ipv4 && !isIpv4Match) {
245
+ throw new Error(
246
+ `License key access restricted. This license key was created for a different system (authorized IPv4: ${payload.ipv4}) and cannot be used on this machine.`
247
+ );
248
+ } else if (payload.ipv6 && !isIpv6Match) {
249
+ throw new Error(
250
+ `License key access restricted. This license key was created for a different system (authorized IPv6: ${payload.ipv6}) and cannot be used on this machine.`
251
+ );
252
+ }
253
+ }
254
+ } catch (err: any) {
255
+ if (err.message.includes('License key access restricted')) {
256
+ throw new ConfigurationException(`[Retrivora SDK] ${err.message}`);
257
+ }
258
+ }
259
+ }
154
260
  }
@@ -256,7 +256,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
256
256
 
257
257
  await this.vectorDB.initialize();
258
258
 
259
- if (this.config.rag?.architecture === 'agentic' || (this.config.mcpServers && this.config.mcpServers.length > 0)) {
259
+ if (this.config.rag?.architecture === 'agentic' || this.config.rag?.architecture === 'multi-agent' || (this.config.mcpServers && this.config.mcpServers.length > 0)) {
260
260
  this.mcpRegistry = new MCPRegistry(this.config.mcpServers || []);
261
261
  this.multiAgentCoordinator = new MultiAgentCoordinator({
262
262
  llmProvider: this.llmProvider,
@@ -448,7 +448,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
448
448
  async ask(question: string, history: ChatMessage[] = [], namespace?: string): Promise<ChatResponse> {
449
449
  await this.initialize();
450
450
 
451
- if ((this.config.rag?.architecture === 'agentic' || (this.config.mcpServers && this.config.mcpServers.length > 0)) && this.multiAgentCoordinator) {
451
+ if ((this.config.rag?.architecture === 'agentic' || this.config.rag?.architecture === 'multi-agent' || (this.config.mcpServers && this.config.mcpServers.length > 0)) && this.multiAgentCoordinator) {
452
452
  return await this.multiAgentCoordinator.run(question, history);
453
453
  }
454
454
 
@@ -475,7 +475,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
475
475
 
476
476
  async *askStream(question: string, history: ChatMessage[] = [], namespace?: string): AsyncIterable<string | ChatResponse> {
477
477
  await this.initialize();
478
- if ((this.config.rag?.architecture === 'agentic' || (this.config.mcpServers && this.config.mcpServers.length > 0)) && this.multiAgentCoordinator) {
478
+ if ((this.config.rag?.architecture === 'agentic' || this.config.rag?.architecture === 'multi-agent' || (this.config.mcpServers && this.config.mcpServers.length > 0)) && this.multiAgentCoordinator) {
479
479
  const stream = this.multiAgentCoordinator.runStream(question, history);
480
480
  for await (const chunk of stream) {
481
481
  yield chunk;
@@ -535,7 +535,8 @@ You are a helpful assistant. Use the provided context to answer questions accura
535
535
  const cacheKey = `${ns}::${searchQuery}`;
536
536
  const cachedVector = this.embeddingCache.get(cacheKey);
537
537
 
538
- const useGraph = this.graphDB && this.config.rag?.useGraphRetrieval;
538
+ const arch = this.config.rag?.architecture;
539
+ const useGraph = arch !== 'naive' && arch !== 'retrieve-and-rerank' && this.graphDB && this.config.rag?.useGraphRetrieval;
539
540
  const [strategyResult, embeddedVector] = await Promise.all([
540
541
  QueryProcessor.determineRetrievalStrategy(
541
542
  searchQuery,
@@ -579,7 +580,8 @@ You are a helpful assistant. Use the provided context to answer questions accura
579
580
  ? structuredSources
580
581
  : structuredSources.filter((m) => m.score >= scoreThreshold);
581
582
  const rerankLimit = wantsExhaustiveList ? retrievalLimit : topK;
582
- if (!hasNumericPredicates && this.config.rag?.useReranking) {
583
+ const useReranking = arch !== 'naive' && (arch === 'retrieve-and-rerank' || this.config.rag?.useReranking);
584
+ if (!hasNumericPredicates && useReranking) {
583
585
  fullSources = await this.reranker.rerank(fullSources, question, rerankLimit);
584
586
  } else if (!wantsExhaustiveList) {
585
587
  fullSources = fullSources.slice(0, topK);
@@ -830,7 +832,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
830
832
  const latency: LatencyBreakdown = {
831
833
  embedMs: Math.round(embedMs),
832
834
  retrieveMs: Math.round(retrieveMs),
833
- rerankMs: this.config.rag?.useReranking ? Math.round(rerankMs) : undefined,
835
+ rerankMs: (arch !== 'naive' && (arch === 'retrieve-and-rerank' || this.config.rag?.useReranking)) ? Math.round(rerankMs) : undefined,
834
836
  generateMs: Math.round(generateMs),
835
837
  totalMs: Math.round(totalMs),
836
838
  };
@@ -29,6 +29,7 @@ export class Retrivora {
29
29
  this.config.projectId,
30
30
  this.config.vectorDb?.provider
31
31
  );
32
+ await LicenseVerifier.verifyIP(this.config.licenseKey);
32
33
  } catch (err) {
33
34
  throw wrapError(err, 'CONFIGURATION_ERROR');
34
35
  }
package/src/index.ts CHANGED
@@ -15,11 +15,9 @@ 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 { Hero } from './components/Hero';
19
- export { Lifecycle } from './components/Lifecycle';
20
- export { ArchitectureCardsSection } from './components/ArchitectureCardsSection';
21
- export { Documentation } from './components/Documentation';
22
- export { AmbientBackground } from './components/AmbientBackground';
18
+ export { CodeViewer } from './components/CodeViewer';
19
+ export { ProductCard } from './components/ProductCard';
20
+ export { ProductCarousel } from './components/ProductCarousel';
23
21
 
24
22
  // ── Headless Hooks ────────────────────────────────────────────
25
23
  export { useRagChat } from './hooks/useRagChat';
@@ -1,29 +0,0 @@
1
- 'use client';
2
-
3
- import React from 'react';
4
-
5
- export function AmbientBackground() {
6
- return (
7
- <div className="absolute inset-0 overflow-hidden pointer-events-none">
8
- {/* Soft indigo orb — top left */}
9
- <div
10
- className="absolute -top-24 -left-24 w-[600px] h-[600px] rounded-full opacity-25 blur-3xl"
11
- style={{ background: 'radial-gradient(circle, #c7d2fe 0%, #a5b4fc 40%, transparent 70%)' }}
12
- />
13
- {/* Soft violet orb — top right */}
14
- <div
15
- className="absolute -top-20 right-0 w-[500px] h-[500px] rounded-full opacity-20 blur-3xl"
16
- style={{ background: 'radial-gradient(circle, #ddd6fe 0%, #c4b5fd 50%, transparent 70%)' }}
17
- />
18
- {/* Subtle grid */}
19
- <div
20
- className="absolute inset-0 opacity-[0.025]"
21
- style={{
22
- backgroundImage:
23
- 'linear-gradient(#6366f1 1px, transparent 1px), linear-gradient(90deg, #6366f1 1px, transparent 1px)',
24
- backgroundSize: '56px 56px',
25
- }}
26
- />
27
- </div>
28
- );
29
- }
@@ -1,53 +0,0 @@
1
- import React from 'react';
2
- import { ArchitectureCardProps } from '@/app/types';
3
- import { ArrowRight } from 'lucide-react';
4
-
5
- interface ExtendedArchitectureCardProps extends ArchitectureCardProps {
6
- gradientFrom?: string;
7
- gradientTo?: string;
8
- }
9
-
10
- export function ArchitectureCard({ icon, title, description, badge, badgeColor, gradientFrom = '#6366f1', gradientTo = '#8b5cf6' }: ExtendedArchitectureCardProps) {
11
- return (
12
- <div className="group relative bg-white rounded-2xl border border-slate-200 p-6 transition-all duration-300 hover:-translate-y-1 hover:shadow-xl hover:shadow-slate-200/80 overflow-hidden cursor-default">
13
- {/* Gradient top accent */}
14
- <div
15
- className="absolute top-0 left-0 right-0 h-0.5 rounded-t-2xl opacity-80 group-hover:opacity-100 transition-opacity"
16
- style={{ background: `linear-gradient(90deg, ${gradientFrom}, ${gradientTo})` }}
17
- />
18
-
19
- {/* Card top row */}
20
- <div className="mb-5 flex items-center justify-between">
21
- <div
22
- className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl transition-transform duration-300 group-hover:scale-110"
23
- style={{
24
- background: `linear-gradient(135deg, ${gradientFrom}18, ${gradientTo}10)`,
25
- border: `1px solid ${gradientFrom}20`,
26
- }}
27
- >
28
- {icon}
29
- </div>
30
- <span className={`rounded-full border px-3 py-1 text-[10px] font-bold uppercase tracking-wider ${badgeColor}`}>
31
- {badge}
32
- </span>
33
- </div>
34
-
35
- <h3 className="mb-2 font-bold text-slate-900 text-base">{title}</h3>
36
- <p className="text-sm text-slate-500 leading-relaxed">{description}</p>
37
-
38
- {/* "Learn more" on hover */}
39
- <div
40
- className="mt-5 flex items-center gap-1 text-[11px] font-semibold opacity-0 group-hover:opacity-100 transition-all duration-300 -translate-y-1 group-hover:translate-y-0"
41
- style={{ color: gradientFrom }}
42
- >
43
- Learn more <ArrowRight className="h-3 w-3" />
44
- </div>
45
-
46
- {/* Subtle hover bg tint */}
47
- <div
48
- className="absolute inset-0 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none"
49
- style={{ background: `radial-gradient(ellipse at 50% 0%, ${gradientFrom}05 0%, transparent 70%)` }}
50
- />
51
- </div>
52
- );
53
- }
@@ -1,48 +0,0 @@
1
- 'use client';
2
-
3
- import React from 'react';
4
- import Link from 'next/link';
5
- import { ArchitectureCard } from '@/components/ArchitectureCard';
6
- import { ARCHITECTURE_CARDS } from '@/app/constants';
7
- import { ArrowRight } from 'lucide-react';
8
-
9
- const CARD_GRADIENTS = [
10
- { from: '#6366f1', to: '#8b5cf6' },
11
- { from: '#f59e0b', to: '#f97316' },
12
- { from: '#10b981', to: '#06b6d4' },
13
- ];
14
-
15
- export function ArchitectureCardsSection() {
16
- return (
17
- <div className="relative">
18
- <div className="text-center mb-12">
19
- <h2 className="text-3xl md:text-4xl font-black text-slate-900 mb-4">
20
- Built for every layer of the stack
21
- </h2>
22
- <p className="text-slate-500 max-w-xl mx-auto text-base leading-relaxed">
23
- Swap providers without rewriting a single line of business logic.
24
- </p>
25
- </div>
26
-
27
- <div className="grid gap-5 sm:grid-cols-3 max-w-5xl mx-auto">
28
- {ARCHITECTURE_CARDS.map((card, i) => (
29
- <ArchitectureCard
30
- key={i}
31
- {...card}
32
- gradientFrom={CARD_GRADIENTS[i]?.from}
33
- gradientTo={CARD_GRADIENTS[i]?.to}
34
- />
35
- ))}
36
- </div>
37
-
38
- <div className="mt-10 text-center">
39
- <Link
40
- href="/features"
41
- className="inline-flex items-center gap-2 px-6 py-3 rounded-xl text-sm font-semibold text-slate-600 hover:text-indigo-600 border border-slate-200 hover:border-indigo-200 bg-white hover:bg-indigo-50 transition-all shadow-sm"
42
- >
43
- Explore all features <ArrowRight className="h-4 w-4" />
44
- </Link>
45
- </div>
46
- </div>
47
- );
48
- }
@@ -1,97 +0,0 @@
1
- 'use client';
2
-
3
- import React from 'react';
4
- import { Snippet } from '@/app/types';
5
- import { SNIPPETS } from '@/app/constants';
6
- import { CodeViewer } from './CodeViewer';
7
- import { ArrowLeft, ArrowRight } from 'lucide-react';
8
-
9
- export function DocViewer({ activeSnippet, setActiveSnippet }: { activeSnippet: Snippet; setActiveSnippet: (s: Snippet) => void }) {
10
- const currentIndex = SNIPPETS.findIndex(s => s.id === activeSnippet.id);
11
- const totalSteps = SNIPPETS.length;
12
- const isFirst = currentIndex === 0;
13
- const isLast = currentIndex === totalSteps - 1;
14
-
15
- const handlePrev = () => { if (!isFirst) setActiveSnippet(SNIPPETS[currentIndex - 1]); };
16
- const handleNext = () => { if (!isLast) setActiveSnippet(SNIPPETS[currentIndex + 1]); };
17
-
18
- const progressPercent = Math.round(((currentIndex + 1) / totalSteps) * 100);
19
-
20
- return (
21
- <div className="flex flex-col h-full p-6 gap-5">
22
- {/* Tab selector */}
23
- <div className="flex flex-wrap gap-1.5 border-b border-slate-100 pb-5">
24
- {SNIPPETS.map(snippet => (
25
- <button
26
- key={snippet.id}
27
- onClick={() => setActiveSnippet(snippet)}
28
- className={`px-4 py-1.5 rounded-lg text-[10px] font-bold uppercase tracking-wider transition-all border ${
29
- activeSnippet.id === snippet.id
30
- ? 'bg-indigo-600 text-white border-indigo-600 shadow-sm scale-[1.03]'
31
- : 'bg-slate-50 border-slate-200 text-slate-500 hover:text-indigo-600 hover:border-indigo-200 hover:bg-indigo-50'
32
- }`}
33
- >
34
- {snippet.title}
35
- </button>
36
- ))}
37
- </div>
38
-
39
- {/* Progress bar */}
40
- <div className="bg-slate-100 rounded-full h-1.5 overflow-hidden">
41
- <div
42
- className="h-full rounded-full transition-all duration-500"
43
- style={{
44
- width: `${progressPercent}%`,
45
- background: 'linear-gradient(90deg, #4f46e5, #7c3aed)',
46
- boxShadow: '0 0 6px rgba(79, 102, 241, 0.4)',
47
- }}
48
- />
49
- </div>
50
-
51
- {/* Content */}
52
- <div className="flex-grow flex flex-col gap-5">
53
- <div className="flex items-start justify-between gap-4 flex-wrap sm:flex-nowrap">
54
- <div>
55
- <h3 className="text-lg font-bold text-slate-900 mb-1.5 flex items-center gap-3">
56
- {activeSnippet.title}
57
- <span className="text-[9px] font-extrabold uppercase px-2 py-0.5 rounded bg-indigo-50 border border-indigo-100 text-indigo-600">
58
- Step {currentIndex + 1} of {totalSteps}
59
- </span>
60
- </h3>
61
- <p className="text-xs text-slate-500 leading-relaxed italic">{activeSnippet.description}</p>
62
- </div>
63
- <span className="text-xs font-mono font-bold text-indigo-600 self-center shrink-0">{progressPercent}% Complete</span>
64
- </div>
65
-
66
- <CodeViewer snippet={activeSnippet} />
67
-
68
- {/* Navigation */}
69
- <div className="flex items-center justify-between border-t border-slate-100 pt-5 mt-auto">
70
- <button
71
- onClick={handlePrev}
72
- disabled={isFirst}
73
- className={`flex items-center gap-2 px-5 py-2.5 rounded-xl text-xs font-bold transition-all border ${
74
- isFirst
75
- ? 'opacity-35 cursor-not-allowed border-slate-100 text-slate-300 bg-slate-50'
76
- : 'bg-white border-slate-200 text-slate-600 hover:border-slate-300 hover:bg-slate-50 active:scale-95'
77
- }`}
78
- >
79
- <ArrowLeft size={13} /> Previous Step
80
- </button>
81
- <button
82
- onClick={handleNext}
83
- disabled={isLast}
84
- className={`flex items-center gap-2 px-6 py-2.5 rounded-xl text-xs font-bold transition-all border ${
85
- isLast
86
- ? 'opacity-35 cursor-not-allowed border-slate-100 text-slate-300 bg-slate-50'
87
- : 'text-white border-transparent shadow-md active:scale-95 hover:-translate-y-0.5'
88
- }`}
89
- style={!isLast ? { background: 'linear-gradient(135deg, #4f46e5, #7c3aed)', boxShadow: '0 4px 14px -4px rgba(79,70,229,0.35)' } : {}}
90
- >
91
- {isLast ? 'Complete!' : 'Next Step'} <ArrowRight size={13} />
92
- </button>
93
- </div>
94
- </div>
95
- </div>
96
- );
97
- }
@@ -1,141 +0,0 @@
1
- 'use client';
2
-
3
- import React from 'react';
4
- import { FileText, Zap, Package, ExternalLink, Code } from 'lucide-react';
5
- import { DocViewer } from '@/components/DocViewer';
6
- import { LANDING_PAGE_CONTENT, QUICK_START_STEPS, ADVANCED_STEPS, API_ENDPOINTS, SNIPPETS } from '@/app/constants';
7
- import { Snippet } from '@/app/types';
8
-
9
- export function Documentation() {
10
- const [activeSnippet, setActiveSnippet] = React.useState<Snippet>(SNIPPETS[0]);
11
-
12
- return (
13
- <div className="max-w-6xl mx-auto">
14
- {/* Header */}
15
- <div className="text-center mb-14">
16
- <span className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-indigo-50 border border-indigo-100 text-[10px] font-bold uppercase tracking-widest text-indigo-600 mb-5">
17
- <span className="w-1.5 h-1.5 rounded-full bg-indigo-500 animate-pulse" />
18
- Developer Guide
19
- </span>
20
- <h2 className="text-3xl md:text-4xl font-black text-slate-900 mb-4">
21
- {LANDING_PAGE_CONTENT.guide.title}
22
- </h2>
23
- <p className="text-slate-500 max-w-xl mx-auto text-base leading-relaxed">
24
- {LANDING_PAGE_CONTENT.guide.subtitle}
25
- </p>
26
- </div>
27
-
28
- {/* Grid */}
29
- <div className="w-full grid gap-8 lg:grid-cols-[270px_1fr] items-start">
30
- {/* Sidebar */}
31
- <aside className="flex flex-col gap-5">
32
- {/* Quick Start */}
33
- <section>
34
- <h2 className="mb-3 text-[10px] font-black text-slate-400 uppercase tracking-[0.3em] flex items-center gap-2">
35
- <FileText size={12} className="text-indigo-500" /> Quick Start
36
- </h2>
37
- <ol className="space-y-1">
38
- {QUICK_START_STEPS.map((step, i) => {
39
- const isActive = activeSnippet.id === step.id;
40
- return (
41
- <li key={step.id}>
42
- <button
43
- onClick={() => { const s = SNIPPETS.find(s => s.id === step.id); if (s) setActiveSnippet(s); }}
44
- className={`w-full flex items-start gap-3 p-3 rounded-xl border text-left transition-all active:scale-[0.98] ${
45
- isActive
46
- ? 'bg-indigo-50 border-indigo-200 shadow-sm'
47
- : 'bg-white border-slate-200 hover:border-slate-300 hover:bg-slate-50'
48
- }`}
49
- >
50
- <span className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-lg font-mono text-[10px] font-bold border transition-all ${
51
- isActive ? 'bg-indigo-600 text-white border-indigo-600 shadow-sm' : 'bg-slate-100 text-slate-500 border-slate-200'
52
- }`}>{i + 1}</span>
53
- <div className="pt-0.5">
54
- <span className={`text-xs font-semibold block ${isActive ? 'text-indigo-700 font-bold' : 'text-slate-700'}`}>{step.text}</span>
55
- <span className="text-[9px] text-slate-400 block mt-0.5">Step {i + 1}</span>
56
- </div>
57
- </button>
58
- </li>
59
- );
60
- })}
61
- </ol>
62
- </section>
63
-
64
- {/* Advanced */}
65
- <section>
66
- <h2 className="mb-3 text-[10px] font-black text-slate-400 uppercase tracking-[0.3em] flex items-center gap-2">
67
- <Code size={12} className="text-violet-500" /> Advanced Options
68
- </h2>
69
- <ol className="space-y-1">
70
- {ADVANCED_STEPS.map((step, i) => {
71
- const isActive = activeSnippet.id === step.id;
72
- return (
73
- <li key={step.id}>
74
- <button
75
- onClick={() => { const s = SNIPPETS.find(s => s.id === step.id); if (s) setActiveSnippet(s); }}
76
- className={`w-full flex items-start gap-3 p-3 rounded-xl border text-left transition-all active:scale-[0.98] ${
77
- isActive
78
- ? 'bg-violet-50 border-violet-200 shadow-sm'
79
- : 'bg-white border-slate-200 hover:border-slate-300 hover:bg-slate-50'
80
- }`}
81
- >
82
- <span className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-lg font-mono text-[10px] font-bold border transition-all ${
83
- isActive ? 'bg-violet-600 text-white border-violet-600 shadow-sm' : 'bg-slate-100 text-slate-500 border-slate-200'
84
- }`}>{i + 5}</span>
85
- <div className="pt-0.5">
86
- <span className={`text-xs font-semibold block ${isActive ? 'text-violet-700 font-bold' : 'text-slate-700'}`}>{step.text}</span>
87
- <span className="text-[9px] text-slate-400 block mt-0.5">Extension</span>
88
- </div>
89
- </button>
90
- </li>
91
- );
92
- })}
93
- </ol>
94
- </section>
95
-
96
- {/* API Endpoints */}
97
- <section className="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm">
98
- <h2 className="mb-4 text-[10px] font-black text-slate-400 uppercase tracking-[0.3em] flex items-center gap-2">
99
- <Zap size={12} className="text-indigo-500" /> API Endpoints
100
- </h2>
101
- <div className="grid gap-2">
102
- {API_ENDPOINTS.map((slug) => (
103
- <div
104
- key={slug}
105
- className="flex items-center justify-between p-2.5 rounded-lg bg-slate-50 border border-slate-100 hover:border-indigo-200 hover:bg-indigo-50 transition-all group"
106
- >
107
- <span className="text-[10px] font-mono text-slate-500 group-hover:text-indigo-600 transition-colors">/api/{slug}</span>
108
- <span className="text-[8px] font-bold text-indigo-600 bg-indigo-50 px-1.5 py-0.5 rounded border border-indigo-100">POST</span>
109
- </div>
110
- ))}
111
- </div>
112
- </section>
113
- </aside>
114
-
115
- {/* Code Viewer */}
116
- <section className="flex flex-col h-full min-h-[560px]">
117
- <div className="rounded-2xl border border-slate-200 bg-white overflow-hidden h-full shadow-sm">
118
- <DocViewer activeSnippet={activeSnippet} setActiveSnippet={setActiveSnippet} />
119
- </div>
120
- </section>
121
- </div>
122
-
123
- {/* CTA */}
124
- <div className="mt-14 text-center">
125
- <a
126
- href="https://www.npmjs.com/package/@retrivora-ai/rag-engine"
127
- target="_blank" rel="noreferrer"
128
- className="inline-flex items-center gap-3 px-8 py-4 rounded-2xl text-white font-bold text-sm shadow-lg transition-all hover:-translate-y-0.5"
129
- style={{
130
- background: 'linear-gradient(135deg, #4f46e5, #7c3aed)',
131
- boxShadow: '0 8px 24px -6px rgba(79, 70, 229, 0.35)',
132
- }}
133
- >
134
- <Package className="w-5 h-5" />
135
- Get Started on NPM
136
- <ExternalLink className="w-4 h-4" />
137
- </a>
138
- </div>
139
- </div>
140
- );
141
- }