@retrivora-ai/rag-engine 1.9.7 → 1.9.8
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/README.md +127 -135
- package/dist/{ILLMProvider-Bhk6zJOK.d.mts → ILLMProvider-B8ROITYK.d.mts} +57 -2
- package/dist/{ILLMProvider-Bhk6zJOK.d.ts → ILLMProvider-B8ROITYK.d.ts} +57 -2
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +2198 -457
- package/dist/handlers/index.mjs +2195 -457
- package/dist/{index-BJ4cd-t5.d.mts → index-B8PbEFSY.d.mts} +12 -2
- package/dist/{index-Bu7T6xgr.d.ts → index-BCPKUAVL.d.ts} +27 -52
- package/dist/{index-C3SVtPYg.d.mts → index-CrxCy36A.d.mts} +27 -52
- package/dist/{index-B9J_XEh0.d.ts → index-DNvoi-sV.d.ts} +12 -2
- package/dist/index.css +578 -273
- package/dist/index.d.mts +29 -7
- package/dist/index.d.ts +29 -7
- package/dist/index.js +1160 -282
- package/dist/index.mjs +1185 -292
- package/dist/server.d.mts +62 -6
- package/dist/server.d.ts +62 -6
- package/dist/server.js +2061 -306
- package/dist/server.mjs +2055 -306
- package/package.json +11 -7
- package/src/app/constants.tsx +37 -7
- package/src/components/AmbientBackground.tsx +10 -10
- package/src/components/ArchitectureCard.tsx +43 -7
- package/src/components/ArchitectureCardsSection.tsx +37 -4
- package/src/components/ChatWidget.tsx +2 -1
- package/src/components/ChatWindow.tsx +4 -1
- package/src/components/CodeViewer.tsx +19 -14
- package/src/components/DocViewer.tsx +43 -49
- package/src/components/Documentation.tsx +71 -51
- package/src/components/Hero.tsx +103 -20
- package/src/components/Lifecycle.tsx +65 -25
- package/src/components/MarkdownComponents.tsx +44 -1
- package/src/components/MessageBubble.tsx +162 -50
- package/src/components/constants.tsx +4 -0
- package/src/config/RagConfig.ts +46 -0
- package/src/config/constants.ts +4 -0
- package/src/config/serverConfig.ts +15 -0
- package/src/core/ConfigResolver.ts +6 -0
- package/src/core/DatabaseStorage.ts +469 -0
- package/src/core/LicenseVerifier.ts +154 -0
- package/src/core/MultiAgentCoordinator.ts +239 -0
- package/src/core/Pipeline.ts +146 -15
- package/src/core/Retrivora.ts +6 -0
- package/src/core/VectorPlugin.ts +12 -3
- package/src/core/mcp.ts +261 -0
- package/src/handlers/index.ts +449 -63
- package/src/hooks/useRagChat.ts +96 -42
- package/src/hooks/useStoredMessages.ts +15 -4
- package/src/index.ts +5 -0
- package/src/llm/LLMFactory.ts +9 -1
- package/src/llm/providers/GroqProvider.ts +176 -0
- package/src/llm/providers/QwenProvider.ts +191 -0
- package/src/server.ts +7 -0
- package/src/types/chat.ts +14 -0
- package/src/types/props.ts +12 -0
- package/.env.example +0 -80
- package/LICENSE.txt +0 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.8",
|
|
4
4
|
"description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
|
|
5
5
|
"author": "Abhinav Alkuchi",
|
|
6
6
|
"license": "MIT",
|
|
@@ -72,16 +72,21 @@
|
|
|
72
72
|
"README.md"
|
|
73
73
|
],
|
|
74
74
|
"scripts": {
|
|
75
|
-
"dev": "next
|
|
76
|
-
"build": "
|
|
75
|
+
"dev": "npx @tailwindcss/cli -i src/tailwind.css -o src/index.css && tsup src/index.ts src/handlers/index.ts src/server.ts --format cjs,esm --watch --tsconfig tsconfig.build.json --external react,react-dom,next,mongodb,pg,openai,@anthropic-ai/sdk,@pinecone-database/pinecone,axios,lucide-react,mammoth,pdf-parse,react-markdown,remark-gfm,next-themes,langchain,@langchain/core,@langchain/openai,llamaindex --inject-style",
|
|
76
|
+
"build": "npm run build:pkg",
|
|
77
77
|
"build:pkg": "npx @tailwindcss/cli -i src/tailwind.css -o src/index.css && tsup src/index.ts src/handlers/index.ts src/server.ts --format cjs,esm --dts --clean --no-splitting --tsconfig tsconfig.build.json --external react,react-dom,next,mongodb,pg,openai,@anthropic-ai/sdk,@pinecone-database/pinecone,axios,lucide-react,mammoth,pdf-parse,react-markdown,remark-gfm,next-themes,langchain,@langchain/core,@langchain/openai,llamaindex --inject-style && npx @tailwindcss/cli -i src/tailwind.css -o dist/index.css && rm src/index.css",
|
|
78
|
-
"start": "next start",
|
|
79
78
|
"lint": "eslint",
|
|
80
|
-
"
|
|
79
|
+
"clean": "rm -rf dist"
|
|
81
80
|
},
|
|
82
81
|
"peerDependencies": {
|
|
83
82
|
"react": ">=18.0.0",
|
|
84
|
-
"react-dom": ">=18.0.0"
|
|
83
|
+
"react-dom": ">=18.0.0",
|
|
84
|
+
"next": ">=15.0.0"
|
|
85
|
+
},
|
|
86
|
+
"peerDependenciesMeta": {
|
|
87
|
+
"next": {
|
|
88
|
+
"optional": true
|
|
89
|
+
}
|
|
85
90
|
},
|
|
86
91
|
"dependencies": {
|
|
87
92
|
"@anthropic-ai/sdk": "^0.95.1",
|
|
@@ -91,7 +96,6 @@
|
|
|
91
96
|
"@types/papaparse": "^5.5.2",
|
|
92
97
|
"axios": "^1.15.0",
|
|
93
98
|
"lucide-react": "^1.8.0",
|
|
94
|
-
"next": "^16.2.6",
|
|
95
99
|
"next-themes": "^0.4.6",
|
|
96
100
|
"openai": "^6.34.0",
|
|
97
101
|
"papaparse": "^5.5.3",
|
package/src/app/constants.tsx
CHANGED
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
FileText,
|
|
18
18
|
Puzzle,
|
|
19
19
|
MessageSquare,
|
|
20
|
+
Cpu,
|
|
21
|
+
Cloud,
|
|
20
22
|
} from 'lucide-react';
|
|
21
23
|
import { ArchitectureCardProps, Snippet, PipelineStep, ProviderPill } from './types';
|
|
22
24
|
|
|
@@ -51,6 +53,8 @@ export const AI_MODELS: ProviderPill[] = [
|
|
|
51
53
|
{ Icon: Sparkles, label: 'OpenAI' },
|
|
52
54
|
{ Icon: Bot, label: 'Anthropic' },
|
|
53
55
|
{ Icon: Brain, label: 'Gemini' },
|
|
56
|
+
{ Icon: Cpu, label: 'Groq' },
|
|
57
|
+
{ Icon: Cloud, label: 'Qwen' },
|
|
54
58
|
{ Icon: Code, label: 'Copilot' },
|
|
55
59
|
{ Icon: Terminal, label: 'LiteLLM' },
|
|
56
60
|
];
|
|
@@ -130,23 +134,49 @@ const { reply, sources } = await pipeline.ask('How does Retrivora work?');`,
|
|
|
130
134
|
id: 'handlers',
|
|
131
135
|
title: 'Route Handlers',
|
|
132
136
|
language: 'typescript',
|
|
133
|
-
description: '
|
|
134
|
-
code: `//
|
|
135
|
-
|
|
137
|
+
description: 'Mount pre-built API endpoints. Configuration is automatically read from environment variables, or can be passed explicitly.',
|
|
138
|
+
code: `// ─── OPTION A: ZERO CONFIG (Reads from environment variables) ───
|
|
139
|
+
// src/app/api/chat/route.ts
|
|
140
|
+
import { createStreamHandler } from '@retrivora-ai/rag-engine/handlers';
|
|
141
|
+
export const POST = createStreamHandler();
|
|
142
|
+
|
|
143
|
+
// ─── OPTION B: EXPLICIT CONFIGURATION ───
|
|
144
|
+
// src/app/api/chat/route.ts
|
|
145
|
+
import { createStreamHandler } from '@retrivora-ai/rag-engine/handlers';
|
|
136
146
|
|
|
137
|
-
|
|
138
|
-
|
|
147
|
+
export const POST = createStreamHandler({
|
|
148
|
+
vectorDb: {
|
|
149
|
+
provider: 'pinecone',
|
|
150
|
+
apiKey: process.env.CUSTOM_PINECONE_KEY,
|
|
151
|
+
indexName: 'docs-index',
|
|
152
|
+
},
|
|
153
|
+
llm: {
|
|
154
|
+
provider: 'openai',
|
|
155
|
+
apiKey: process.env.CUSTOM_OPENAI_KEY,
|
|
156
|
+
model: 'gpt-4o',
|
|
157
|
+
}
|
|
158
|
+
});`,
|
|
139
159
|
},
|
|
140
160
|
{
|
|
141
161
|
id: 'ui',
|
|
142
162
|
title: 'React Components',
|
|
143
163
|
language: 'typescript',
|
|
144
|
-
description: '
|
|
164
|
+
description: 'Configure and embed themeable UI widgets using the ConfigProvider context.',
|
|
145
165
|
code: `import { ConfigProvider, ChatWidget } from '@retrivora-ai/rag-engine';
|
|
146
166
|
|
|
147
167
|
export default function Layout({ children }) {
|
|
148
168
|
return (
|
|
149
|
-
<ConfigProvider config={{
|
|
169
|
+
<ConfigProvider config={{
|
|
170
|
+
projectId: 'my-project-id',
|
|
171
|
+
ui: {
|
|
172
|
+
title: 'Retrivora Assistant',
|
|
173
|
+
welcomeMessage: 'Hi! Ask me anything about our software.',
|
|
174
|
+
primaryColor: '#4f46e5', // Custom primary brand color
|
|
175
|
+
accentColor: '#8b5cf6', // Custom accent gradient color
|
|
176
|
+
borderRadius: 'lg', // 'none' | 'sm' | 'md' | 'lg' | 'xl'
|
|
177
|
+
allowUpload: true, // Toggle custom file uploading
|
|
178
|
+
}
|
|
179
|
+
}}>
|
|
150
180
|
{children}
|
|
151
181
|
<ChatWidget position="bottom-right" />
|
|
152
182
|
</ConfigProvider>
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import { useConfig } from '@/components/ConfigProvider';
|
|
5
4
|
|
|
6
5
|
export function AmbientBackground() {
|
|
7
|
-
const { ui } = useConfig();
|
|
8
|
-
|
|
9
6
|
return (
|
|
10
7
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
|
8
|
+
{/* Soft indigo orb — top left */}
|
|
11
9
|
<div
|
|
12
|
-
className="absolute -top-
|
|
13
|
-
style={{ background:
|
|
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%)' }}
|
|
14
12
|
/>
|
|
13
|
+
{/* Soft violet orb — top right */}
|
|
15
14
|
<div
|
|
16
|
-
className="absolute -
|
|
17
|
-
style={{ background:
|
|
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%)' }}
|
|
18
17
|
/>
|
|
18
|
+
{/* Subtle grid */}
|
|
19
19
|
<div
|
|
20
|
-
className="absolute inset-0 opacity-[0.
|
|
20
|
+
className="absolute inset-0 opacity-[0.025]"
|
|
21
21
|
style={{
|
|
22
22
|
backgroundImage:
|
|
23
|
-
'linear-gradient(
|
|
24
|
-
backgroundSize: '
|
|
23
|
+
'linear-gradient(#6366f1 1px, transparent 1px), linear-gradient(90deg, #6366f1 1px, transparent 1px)',
|
|
24
|
+
backgroundSize: '56px 56px',
|
|
25
25
|
}}
|
|
26
26
|
/>
|
|
27
27
|
</div>
|
|
@@ -1,17 +1,53 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ArchitectureCardProps } from '@/app/types';
|
|
3
|
+
import { ArrowRight } from 'lucide-react';
|
|
3
4
|
|
|
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) {
|
|
5
11
|
return (
|
|
6
|
-
<div className="group rounded-2xl border border-slate-200
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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}`}>
|
|
10
31
|
{badge}
|
|
11
32
|
</span>
|
|
12
33
|
</div>
|
|
13
|
-
|
|
14
|
-
<
|
|
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
|
+
/>
|
|
15
51
|
</div>
|
|
16
52
|
);
|
|
17
53
|
}
|
|
@@ -1,15 +1,48 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import React from 'react';
|
|
4
|
+
import Link from 'next/link';
|
|
4
5
|
import { ArchitectureCard } from '@/components/ArchitectureCard';
|
|
5
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
|
+
];
|
|
6
14
|
|
|
7
15
|
export function ArchitectureCardsSection() {
|
|
8
16
|
return (
|
|
9
|
-
<div className="relative
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
|
|
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>
|
|
13
46
|
</div>
|
|
14
47
|
);
|
|
15
48
|
}
|
|
@@ -46,7 +46,7 @@ const GEMINI_STYLES = `
|
|
|
46
46
|
}
|
|
47
47
|
`;
|
|
48
48
|
|
|
49
|
-
export function ChatWidget({ position = 'bottom-right', onAddToCart, apiUrl, headers }: ChatWidgetProps) {
|
|
49
|
+
export function ChatWidget({ position = 'bottom-right', onAddToCart, apiUrl, retrivoraApiBase, headers }: ChatWidgetProps) {
|
|
50
50
|
const { ui } = useConfig();
|
|
51
51
|
const [isOpen, setIsOpen] = useState(false);
|
|
52
52
|
const [hasUnread, setHasUnread] = useState(false);
|
|
@@ -147,6 +147,7 @@ export function ChatWidget({ position = 'bottom-right', onAddToCart, apiUrl, hea
|
|
|
147
147
|
isMaximized={isMaximized}
|
|
148
148
|
onAddToCart={onAddToCart}
|
|
149
149
|
apiUrl={apiUrl}
|
|
150
|
+
retrivoraApiBase={retrivoraApiBase}
|
|
150
151
|
headers={headers}
|
|
151
152
|
/>
|
|
152
153
|
{/* Pointer (Tail) */}
|
|
@@ -64,6 +64,7 @@ export function ChatWindow({
|
|
|
64
64
|
isMaximized = false,
|
|
65
65
|
onAddToCart,
|
|
66
66
|
apiUrl,
|
|
67
|
+
retrivoraApiBase,
|
|
67
68
|
headers
|
|
68
69
|
}: ChatWindowProps) {
|
|
69
70
|
const { ui, projectId } = useConfig();
|
|
@@ -73,9 +74,10 @@ export function ChatWindow({
|
|
|
73
74
|
const windowRef = useRef<HTMLDivElement>(null);
|
|
74
75
|
const bottomRef = useRef<HTMLDivElement>(null);
|
|
75
76
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
76
|
-
const { messages, send, clear, isLoading, error, stop } = useRagChat(projectId, {
|
|
77
|
+
const { messages, send, clear, isLoading, error, stop, submitFeedback } = useRagChat(projectId, {
|
|
77
78
|
namespace: projectId,
|
|
78
79
|
apiUrl,
|
|
80
|
+
retrivoraApiBase,
|
|
79
81
|
headers,
|
|
80
82
|
});
|
|
81
83
|
|
|
@@ -376,6 +378,7 @@ export function ChatWindow({
|
|
|
376
378
|
accentColor={ui.accentColor}
|
|
377
379
|
onAddToCart={onAddToCart}
|
|
378
380
|
viewportSize={viewportSize}
|
|
381
|
+
onFeedback={submitFeedback}
|
|
379
382
|
/>
|
|
380
383
|
))
|
|
381
384
|
)}
|
|
@@ -14,35 +14,40 @@ export function CodeViewer({ snippet }: { snippet: Snippet }) {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
|
-
<div className="flex flex-col
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
<div className="flex flex-col rounded-2xl border border-white/8 overflow-hidden min-h-[300px] shadow-xl">
|
|
18
|
+
{/* VS Code-style chrome header */}
|
|
19
|
+
<div className="flex items-center justify-between px-5 py-3 border-b border-white/5 bg-[#0d0d1a]">
|
|
20
|
+
<div className="flex items-center gap-4">
|
|
20
21
|
<div className="flex gap-1.5">
|
|
21
|
-
<div className="w-2.5 h-2.5 rounded-full bg-
|
|
22
|
-
<div className="w-2.5 h-2.5 rounded-full bg-
|
|
23
|
-
<div className="w-2.5 h-2.5 rounded-full bg-
|
|
22
|
+
<div className="w-2.5 h-2.5 rounded-full bg-rose-500/80" />
|
|
23
|
+
<div className="w-2.5 h-2.5 rounded-full bg-amber-500/80" />
|
|
24
|
+
<div className="w-2.5 h-2.5 rounded-full bg-emerald-500/80" />
|
|
24
25
|
</div>
|
|
25
|
-
<span className="text-[10px] font-mono text-
|
|
26
|
+
<span className="text-[10px] font-mono text-white/25 uppercase tracking-widest">
|
|
27
|
+
{snippet.language}
|
|
28
|
+
</span>
|
|
26
29
|
</div>
|
|
27
30
|
<button
|
|
28
31
|
onClick={handleCopy}
|
|
29
|
-
className="flex items-center gap-2 text-[10px] font-bold text-
|
|
32
|
+
className="flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-[10px] font-bold text-white/35 hover:text-white/80 hover:bg-white/5 transition-all active:scale-95"
|
|
30
33
|
>
|
|
31
34
|
{copied ? (
|
|
32
35
|
<>
|
|
33
|
-
<Check size={
|
|
34
|
-
<span className="text-emerald-
|
|
36
|
+
<Check size={12} className="text-emerald-400" />
|
|
37
|
+
<span className="text-emerald-400">Copied!</span>
|
|
35
38
|
</>
|
|
36
39
|
) : (
|
|
37
40
|
<>
|
|
38
|
-
<Copy size={
|
|
39
|
-
|
|
41
|
+
<Copy size={12} />
|
|
42
|
+
Copy Code
|
|
40
43
|
</>
|
|
41
44
|
)}
|
|
42
45
|
</button>
|
|
43
46
|
</div>
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
|
|
48
|
+
{/* Code body */}
|
|
49
|
+
<div className="p-6 font-mono text-[12px] leading-relaxed overflow-auto flex-grow bg-[#080818]">
|
|
50
|
+
<pre className="whitespace-pre text-indigo-200/85">
|
|
46
51
|
<code>{snippet.code}</code>
|
|
47
52
|
</pre>
|
|
48
53
|
</div>
|
|
@@ -6,98 +6,92 @@ import { SNIPPETS } from '@/app/constants';
|
|
|
6
6
|
import { CodeViewer } from './CodeViewer';
|
|
7
7
|
import { ArrowLeft, ArrowRight } from 'lucide-react';
|
|
8
8
|
|
|
9
|
-
export function DocViewer({ activeSnippet, setActiveSnippet }: { activeSnippet: Snippet
|
|
10
|
-
const currentIndex = SNIPPETS.findIndex(
|
|
11
|
-
const totalSteps
|
|
12
|
-
const isFirst
|
|
13
|
-
const isLast
|
|
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
14
|
|
|
15
|
-
const handlePrev = () => {
|
|
16
|
-
|
|
17
|
-
setActiveSnippet(SNIPPETS[currentIndex - 1]);
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const handleNext = () => {
|
|
22
|
-
if (!isLast) {
|
|
23
|
-
setActiveSnippet(SNIPPETS[currentIndex + 1]);
|
|
24
|
-
}
|
|
25
|
-
};
|
|
15
|
+
const handlePrev = () => { if (!isFirst) setActiveSnippet(SNIPPETS[currentIndex - 1]); };
|
|
16
|
+
const handleNext = () => { if (!isLast) setActiveSnippet(SNIPPETS[currentIndex + 1]); };
|
|
26
17
|
|
|
27
18
|
const progressPercent = Math.round(((currentIndex + 1) / totalSteps) * 100);
|
|
28
19
|
|
|
29
20
|
return (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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 => (
|
|
33
25
|
<button
|
|
34
26
|
key={snippet.id}
|
|
35
27
|
onClick={() => setActiveSnippet(snippet)}
|
|
36
|
-
className={`px-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
+
}`}
|
|
40
33
|
>
|
|
41
34
|
{snippet.title}
|
|
42
35
|
</button>
|
|
43
36
|
))}
|
|
44
37
|
</div>
|
|
45
38
|
|
|
46
|
-
{/*
|
|
47
|
-
<div className="
|
|
48
|
-
<div
|
|
49
|
-
className="h-full
|
|
50
|
-
style={{
|
|
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
|
+
}}
|
|
51
48
|
/>
|
|
52
49
|
</div>
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
|
|
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">
|
|
56
54
|
<div>
|
|
57
|
-
<h3 className="text-
|
|
55
|
+
<h3 className="text-lg font-bold text-slate-900 mb-1.5 flex items-center gap-3">
|
|
58
56
|
{activeSnippet.title}
|
|
59
|
-
<span className="text-[
|
|
57
|
+
<span className="text-[9px] font-extrabold uppercase px-2 py-0.5 rounded bg-indigo-50 border border-indigo-100 text-indigo-600">
|
|
60
58
|
Step {currentIndex + 1} of {totalSteps}
|
|
61
59
|
</span>
|
|
62
60
|
</h3>
|
|
63
|
-
<p className="text-
|
|
64
|
-
{activeSnippet.description}
|
|
65
|
-
</p>
|
|
61
|
+
<p className="text-xs text-slate-500 leading-relaxed italic">{activeSnippet.description}</p>
|
|
66
62
|
</div>
|
|
67
|
-
<span className="text-xs font-mono font-bold text-indigo-600
|
|
68
|
-
{progressPercent}% Complete
|
|
69
|
-
</span>
|
|
63
|
+
<span className="text-xs font-mono font-bold text-indigo-600 self-center shrink-0">{progressPercent}% Complete</span>
|
|
70
64
|
</div>
|
|
71
|
-
|
|
65
|
+
|
|
72
66
|
<CodeViewer snippet={activeSnippet} />
|
|
73
67
|
|
|
74
|
-
{/*
|
|
75
|
-
<div className="flex items-center justify-between border-t border-slate-
|
|
68
|
+
{/* Navigation */}
|
|
69
|
+
<div className="flex items-center justify-between border-t border-slate-100 pt-5 mt-auto">
|
|
76
70
|
<button
|
|
77
71
|
onClick={handlePrev}
|
|
78
72
|
disabled={isFirst}
|
|
79
73
|
className={`flex items-center gap-2 px-5 py-2.5 rounded-xl text-xs font-bold transition-all border ${
|
|
80
74
|
isFirst
|
|
81
|
-
? 'opacity-
|
|
82
|
-
: 'bg-white
|
|
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'
|
|
83
77
|
}`}
|
|
84
78
|
>
|
|
85
|
-
<ArrowLeft size={
|
|
79
|
+
<ArrowLeft size={13} /> Previous Step
|
|
86
80
|
</button>
|
|
87
|
-
|
|
88
81
|
<button
|
|
89
82
|
onClick={handleNext}
|
|
90
83
|
disabled={isLast}
|
|
91
84
|
className={`flex items-center gap-2 px-6 py-2.5 rounded-xl text-xs font-bold transition-all border ${
|
|
92
85
|
isLast
|
|
93
|
-
? 'opacity-
|
|
94
|
-
: '
|
|
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'
|
|
95
88
|
}`}
|
|
89
|
+
style={!isLast ? { background: 'linear-gradient(135deg, #4f46e5, #7c3aed)', boxShadow: '0 4px 14px -4px rgba(79,70,229,0.35)' } : {}}
|
|
96
90
|
>
|
|
97
|
-
{isLast ? 'Complete!' : 'Next Step'} <ArrowRight size={
|
|
91
|
+
{isLast ? 'Complete!' : 'Next Step'} <ArrowRight size={13} />
|
|
98
92
|
</button>
|
|
99
93
|
</div>
|
|
100
94
|
</div>
|
|
101
|
-
|
|
95
|
+
</div>
|
|
102
96
|
);
|
|
103
97
|
}
|