@chimerai/cli 0.2.73
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/LICENSE +21 -0
- package/README.md +293 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +317 -0
- package/dist/commands/add.d.ts +11 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +2126 -0
- package/dist/commands/create.d.ts +12 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +1703 -0
- package/dist/commands/deploy.d.ts +11 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +219 -0
- package/dist/commands/dev.d.ts +17 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +206 -0
- package/dist/commands/doctor.d.ts +11 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +728 -0
- package/dist/commands/generate.d.ts +19 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +429 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +269 -0
- package/dist/commands/list.d.ts +12 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +328 -0
- package/dist/commands/migrate.d.ts +14 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +197 -0
- package/dist/commands/plugin.d.ts +10 -0
- package/dist/commands/plugin.d.ts.map +1 -0
- package/dist/commands/plugin.js +239 -0
- package/dist/commands/remove.d.ts +11 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.js +472 -0
- package/dist/commands/secret.d.ts +12 -0
- package/dist/commands/secret.d.ts.map +1 -0
- package/dist/commands/secret.js +102 -0
- package/dist/commands/setup.d.ts +9 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +788 -0
- package/dist/commands/update.d.ts +14 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +211 -0
- package/dist/commands/use.d.ts +9 -0
- package/dist/commands/use.d.ts.map +1 -0
- package/dist/commands/use.js +51 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/license.d.ts +55 -0
- package/dist/license.d.ts.map +1 -0
- package/dist/license.js +258 -0
- package/dist/scanner.d.ts +31 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +113 -0
- package/dist/schema-manager.d.ts +26 -0
- package/dist/schema-manager.d.ts.map +1 -0
- package/dist/schema-manager.js +132 -0
- package/dist/templates/admin.d.ts +49 -0
- package/dist/templates/admin.d.ts.map +1 -0
- package/dist/templates/admin.js +1358 -0
- package/dist/templates/ai-routes.d.ts +17 -0
- package/dist/templates/ai-routes.d.ts.map +1 -0
- package/dist/templates/ai-routes.js +1130 -0
- package/dist/templates/ai-service-tools.d.ts +22 -0
- package/dist/templates/ai-service-tools.d.ts.map +1 -0
- package/dist/templates/ai-service-tools.js +1424 -0
- package/dist/templates/ai-service.d.ts +66 -0
- package/dist/templates/ai-service.d.ts.map +1 -0
- package/dist/templates/ai-service.js +2202 -0
- package/dist/templates/api-routes.d.ts +108 -0
- package/dist/templates/api-routes.d.ts.map +1 -0
- package/dist/templates/api-routes.js +1219 -0
- package/dist/templates/auth.d.ts +48 -0
- package/dist/templates/auth.d.ts.map +1 -0
- package/dist/templates/auth.js +381 -0
- package/dist/templates/billing.d.ts +44 -0
- package/dist/templates/billing.d.ts.map +1 -0
- package/dist/templates/billing.js +551 -0
- package/dist/templates/chat.d.ts +63 -0
- package/dist/templates/chat.d.ts.map +1 -0
- package/dist/templates/chat.js +1979 -0
- package/dist/templates/components.d.ts +22 -0
- package/dist/templates/components.d.ts.map +1 -0
- package/dist/templates/components.js +672 -0
- package/dist/templates/config.d.ts +6 -0
- package/dist/templates/config.d.ts.map +1 -0
- package/dist/templates/config.js +86 -0
- package/dist/templates/docker.d.ts +25 -0
- package/dist/templates/docker.d.ts.map +1 -0
- package/dist/templates/docker.js +165 -0
- package/dist/templates/gdpr.d.ts +16 -0
- package/dist/templates/gdpr.d.ts.map +1 -0
- package/dist/templates/gdpr.js +259 -0
- package/dist/templates/index.d.ts +77 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +339 -0
- package/dist/templates/layout.d.ts +67 -0
- package/dist/templates/layout.d.ts.map +1 -0
- package/dist/templates/layout.js +670 -0
- package/dist/templates/mfa.d.ts +23 -0
- package/dist/templates/mfa.d.ts.map +1 -0
- package/dist/templates/mfa.js +353 -0
- package/dist/templates/middleware.d.ts +12 -0
- package/dist/templates/middleware.d.ts.map +1 -0
- package/dist/templates/middleware.js +116 -0
- package/dist/templates/prisma.d.ts +35 -0
- package/dist/templates/prisma.d.ts.map +1 -0
- package/dist/templates/prisma.js +724 -0
- package/dist/templates/provider-routes.d.ts +21 -0
- package/dist/templates/provider-routes.d.ts.map +1 -0
- package/dist/templates/provider-routes.js +1203 -0
- package/dist/templates/rag.d.ts +48 -0
- package/dist/templates/rag.d.ts.map +1 -0
- package/dist/templates/rag.js +532 -0
- package/dist/templates/widget.d.ts +64 -0
- package/dist/templates/widget.d.ts.map +1 -0
- package/dist/templates/widget.js +1360 -0
- package/dist/utils/provider-db.d.ts +63 -0
- package/dist/utils/provider-db.d.ts.map +1 -0
- package/dist/utils/provider-db.js +300 -0
- package/dist/utils.d.ts +78 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +330 -0
- package/package.json +60 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RAG (Retrieval-Augmented Generation) template generators
|
|
3
|
+
* Generates RAG page, upload/query routes, and RAG utility lib
|
|
4
|
+
* SPEC: FRONTEND_UI_SPEC.md Section B2
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Generates the RAG utility library
|
|
8
|
+
* Thin wrapper around the AI Service API for document operations
|
|
9
|
+
* @returns TypeScript content for lib/rag.ts
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateRagLib(): string;
|
|
12
|
+
/**
|
|
13
|
+
* Generates the RAG page with tabs for Documents and Chat
|
|
14
|
+
* SPEC: B2 — Upload, Documents list, Chat with Docs
|
|
15
|
+
* @returns TypeScript/JSX content for app/rag/page.tsx
|
|
16
|
+
*/
|
|
17
|
+
export declare function generateRagPage(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Generates the RAG document upload API route
|
|
20
|
+
* POST: Proxies document upload to the Python AI Service
|
|
21
|
+
* @returns TypeScript content for app/api/rag/route.ts
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateRagUploadRoute(): string;
|
|
24
|
+
/**
|
|
25
|
+
* Generates the RAG query API route
|
|
26
|
+
* POST: Proxies RAG chat queries to the Python AI Service
|
|
27
|
+
* @returns TypeScript content for app/api/rag/query/route.ts
|
|
28
|
+
*/
|
|
29
|
+
export declare function generateRagQueryRoute(): string;
|
|
30
|
+
/**
|
|
31
|
+
* Generates the RAG stats API route
|
|
32
|
+
* GET: Proxies vector store stats from the Python AI Service
|
|
33
|
+
* @returns TypeScript content for app/api/rag/stats/route.ts
|
|
34
|
+
*/
|
|
35
|
+
export declare function generateRagStatsRoute(): string;
|
|
36
|
+
/**
|
|
37
|
+
* Generates the RAG clear API route
|
|
38
|
+
* POST: Proxies vector store clear to the Python AI Service
|
|
39
|
+
* @returns TypeScript content for app/api/rag/clear/route.ts
|
|
40
|
+
*/
|
|
41
|
+
export declare function generateRagClearRoute(): string;
|
|
42
|
+
/**
|
|
43
|
+
* Generates the RAG delete API route
|
|
44
|
+
* DELETE: Proxies per-document deletion to the Python AI Service
|
|
45
|
+
* @returns TypeScript content for app/api/rag/delete/route.ts
|
|
46
|
+
*/
|
|
47
|
+
export declare function generateRagDeleteRoute(): string;
|
|
48
|
+
//# sourceMappingURL=rag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rag.d.ts","sourceRoot":"","sources":["../../src/templates/rag.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAuDvC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,MAAM,CA0PxC;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAkD/C;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAkC9C;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CA4B9C;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CA4B9C;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CA+B/C"}
|
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* RAG (Retrieval-Augmented Generation) template generators
|
|
4
|
+
* Generates RAG page, upload/query routes, and RAG utility lib
|
|
5
|
+
* SPEC: FRONTEND_UI_SPEC.md Section B2
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.generateRagLib = generateRagLib;
|
|
9
|
+
exports.generateRagPage = generateRagPage;
|
|
10
|
+
exports.generateRagUploadRoute = generateRagUploadRoute;
|
|
11
|
+
exports.generateRagQueryRoute = generateRagQueryRoute;
|
|
12
|
+
exports.generateRagStatsRoute = generateRagStatsRoute;
|
|
13
|
+
exports.generateRagClearRoute = generateRagClearRoute;
|
|
14
|
+
exports.generateRagDeleteRoute = generateRagDeleteRoute;
|
|
15
|
+
/**
|
|
16
|
+
* Generates the RAG utility library
|
|
17
|
+
* Thin wrapper around the AI Service API for document operations
|
|
18
|
+
* @returns TypeScript content for lib/rag.ts
|
|
19
|
+
*/
|
|
20
|
+
function generateRagLib() {
|
|
21
|
+
return `// @chimerai component=RagLib version=1.0
|
|
22
|
+
const AI_SERVICE_URL = process.env.AI_SERVICE_URL || 'http://localhost:8002';
|
|
23
|
+
|
|
24
|
+
async function aiServiceFetch(endpoint: string, options?: RequestInit) {
|
|
25
|
+
const res = await fetch(\`\${AI_SERVICE_URL}\${endpoint}\`, {
|
|
26
|
+
...options,
|
|
27
|
+
headers: {
|
|
28
|
+
'Content-Type': 'application/json',
|
|
29
|
+
...options?.headers,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
const err = await res.json().catch(() => ({ detail: 'AI Service error' }));
|
|
34
|
+
throw new Error(err.detail || \`AI Service error: \${res.status}\`);
|
|
35
|
+
}
|
|
36
|
+
return res.json();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function addDocuments(documents: string[], metadatas?: Record<string, any>[]) {
|
|
40
|
+
return aiServiceFetch('/api/rag/documents', {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
body: JSON.stringify({ documents, metadatas }),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function searchDocuments(query: string, k: number = 4) {
|
|
47
|
+
return aiServiceFetch('/api/rag/search', {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
body: JSON.stringify({ query, k }),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function ragChat(query: string, model: string = 'gpt-3.5-turbo', k: number = 3) {
|
|
54
|
+
return aiServiceFetch('/api/rag/chat', {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
body: JSON.stringify({ query, model, k }),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function getVectorStats() {
|
|
61
|
+
return aiServiceFetch('/api/rag/stats', { method: 'GET' });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function clearVectorStore() {
|
|
65
|
+
return aiServiceFetch('/api/rag/clear', { method: 'DELETE' });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function deleteDocuments(documentIds: number[]) {
|
|
69
|
+
return aiServiceFetch('/api/rag/delete', {
|
|
70
|
+
method: 'DELETE',
|
|
71
|
+
body: JSON.stringify({ document_ids: documentIds }),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Generates the RAG page with tabs for Documents and Chat
|
|
78
|
+
* SPEC: B2 — Upload, Documents list, Chat with Docs
|
|
79
|
+
* @returns TypeScript/JSX content for app/rag/page.tsx
|
|
80
|
+
*/
|
|
81
|
+
function generateRagPage() {
|
|
82
|
+
return `// @chimerai component=RagPage version=1.0
|
|
83
|
+
'use client';
|
|
84
|
+
|
|
85
|
+
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
86
|
+
|
|
87
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
88
|
+
interface VectorStats {
|
|
89
|
+
total_vectors: number;
|
|
90
|
+
dimension: number;
|
|
91
|
+
index_type: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface UploadedDoc {
|
|
95
|
+
name: string;
|
|
96
|
+
type: string;
|
|
97
|
+
size: number;
|
|
98
|
+
chunks: number;
|
|
99
|
+
status: 'uploading' | 'processing' | 'indexed' | 'error';
|
|
100
|
+
progress: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface ChatMessage {
|
|
104
|
+
role: 'user' | 'assistant';
|
|
105
|
+
content: string;
|
|
106
|
+
sources?: Array<{ text: string; score: number; metadata: Record<string, any> }>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ─── Component ──────────────────────────────────────────────────────────────
|
|
110
|
+
export default function RagPage() {
|
|
111
|
+
const [tab, setTab] = useState<'documents' | 'chat'>('documents');
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div className="min-h-screen bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
|
|
115
|
+
<div className="container mx-auto py-8 max-w-5xl">
|
|
116
|
+
<h1 className="text-3xl font-bold mb-2">RAG Pipeline</h1>
|
|
117
|
+
<p className="text-muted-foreground mb-6">Upload documents and chat with your knowledge base</p>
|
|
118
|
+
|
|
119
|
+
{/* Tabs */}
|
|
120
|
+
<div className="flex border-b mb-6">
|
|
121
|
+
<button onClick={() => setTab('documents')}
|
|
122
|
+
className={\`px-4 py-2 font-medium border-b-2 transition-colors \${tab === 'documents' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground'}\`}>
|
|
123
|
+
📄 Documents
|
|
124
|
+
</button>
|
|
125
|
+
<button onClick={() => setTab('chat')}
|
|
126
|
+
className={\`px-4 py-2 font-medium border-b-2 transition-colors \${tab === 'chat' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground'}\`}>
|
|
127
|
+
💬 Chat with Docs
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
{tab === 'documents' ? <DocumentsTab /> : <ChatTab />}
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── Documents Tab ──────────────────────────────────────────────────────────
|
|
138
|
+
function DocumentsTab() {
|
|
139
|
+
const [stats, setStats] = useState<VectorStats | null>(null);
|
|
140
|
+
const [docs, setDocs] = useState<UploadedDoc[]>([]);
|
|
141
|
+
const [dragActive, setDragActive] = useState(false);
|
|
142
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
143
|
+
|
|
144
|
+
const fetchStats = useCallback(async () => {
|
|
145
|
+
try {
|
|
146
|
+
const res = await fetch('/api/rag/stats');
|
|
147
|
+
if (res.ok) setStats(await res.json());
|
|
148
|
+
} catch {}
|
|
149
|
+
}, []);
|
|
150
|
+
|
|
151
|
+
useEffect(() => { fetchStats(); }, [fetchStats]);
|
|
152
|
+
|
|
153
|
+
const handleFiles = async (files: FileList) => {
|
|
154
|
+
for (const file of Array.from(files)) {
|
|
155
|
+
const doc: UploadedDoc = {
|
|
156
|
+
name: file.name,
|
|
157
|
+
type: file.type || file.name.split('.').pop() || 'unknown',
|
|
158
|
+
size: file.size,
|
|
159
|
+
chunks: 0,
|
|
160
|
+
status: 'uploading',
|
|
161
|
+
progress: 30,
|
|
162
|
+
};
|
|
163
|
+
setDocs(prev => [...prev, doc]);
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
setDocs(prev => prev.map(d => d.name === file.name ? { ...d, status: 'processing', progress: 60 } : d));
|
|
167
|
+
|
|
168
|
+
const formData = new FormData();
|
|
169
|
+
formData.append('files', file);
|
|
170
|
+
|
|
171
|
+
const res = await fetch('/api/rag', {
|
|
172
|
+
method: 'POST',
|
|
173
|
+
body: formData,
|
|
174
|
+
});
|
|
175
|
+
const result = await res.json();
|
|
176
|
+
|
|
177
|
+
setDocs(prev => prev.map(d =>
|
|
178
|
+
d.name === file.name ? { ...d, status: 'indexed', progress: 100, chunks: result.added || 1 } : d
|
|
179
|
+
));
|
|
180
|
+
fetchStats();
|
|
181
|
+
} catch {
|
|
182
|
+
setDocs(prev => prev.map(d => d.name === file.name ? { ...d, status: 'error', progress: 0 } : d));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const handleClearAll = async () => {
|
|
188
|
+
if (!confirm('Clear all documents from the vector store?')) return;
|
|
189
|
+
try {
|
|
190
|
+
await fetch('/api/rag/clear', { method: 'DELETE' });
|
|
191
|
+
setDocs([]);
|
|
192
|
+
fetchStats();
|
|
193
|
+
} catch {}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div>
|
|
198
|
+
{/* Stats */}
|
|
199
|
+
{stats && (
|
|
200
|
+
<div className="grid grid-cols-3 gap-4 mb-6">
|
|
201
|
+
<div className="p-4 border rounded-lg bg-card"><p className="text-sm text-muted-foreground">Total Vectors</p><p className="text-2xl font-bold">{stats.total_vectors}</p></div>
|
|
202
|
+
<div className="p-4 border rounded-lg bg-card"><p className="text-sm text-muted-foreground">Dimension</p><p className="text-2xl font-bold">{stats.dimension}</p></div>
|
|
203
|
+
<div className="p-4 border rounded-lg bg-card"><p className="text-sm text-muted-foreground">Index Type</p><p className="text-2xl font-bold">{stats.index_type}</p></div>
|
|
204
|
+
</div>
|
|
205
|
+
)}
|
|
206
|
+
|
|
207
|
+
{/* Upload Zone */}
|
|
208
|
+
<div
|
|
209
|
+
onDragOver={e => { e.preventDefault(); setDragActive(true); }}
|
|
210
|
+
onDragLeave={() => setDragActive(false)}
|
|
211
|
+
onDrop={e => { e.preventDefault(); setDragActive(false); if (e.dataTransfer.files) handleFiles(e.dataTransfer.files); }}
|
|
212
|
+
onClick={() => fileInputRef.current?.click()}
|
|
213
|
+
className={\`border-2 border-dashed rounded-xl p-8 text-center cursor-pointer transition-colors mb-6 \${dragActive ? 'border-primary bg-primary/10' : 'border-muted-foreground/30 hover:border-muted-foreground/50'}\`}>
|
|
214
|
+
<p className="text-lg mb-1">📁 Drop files here or click to browse</p>
|
|
215
|
+
<p className="text-sm text-muted-foreground">Supports .pdf, .txt, .md, .docx — Max 10 MB</p>
|
|
216
|
+
<input ref={fileInputRef} type="file" multiple accept=".pdf,.txt,.md,.docx" className="hidden"
|
|
217
|
+
onChange={e => e.target.files && handleFiles(e.target.files)} />
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
{/* Document List */}
|
|
221
|
+
{docs.length > 0 && (
|
|
222
|
+
<div className="border rounded-lg overflow-hidden">
|
|
223
|
+
<div className="flex justify-between items-center p-3 bg-muted border-b">
|
|
224
|
+
<span className="font-medium">{docs.length} document(s)</span>
|
|
225
|
+
<button onClick={handleClearAll} className="text-sm text-red-600 hover:text-red-700">Clear All</button>
|
|
226
|
+
</div>
|
|
227
|
+
{docs.map((doc, i) => (
|
|
228
|
+
<div key={i} className="flex items-center justify-between p-3 border-b last:border-b-0">
|
|
229
|
+
<div className="flex-1">
|
|
230
|
+
<p className="font-medium text-sm">{doc.name}</p>
|
|
231
|
+
<p className="text-xs text-muted-foreground">{(doc.size / 1024).toFixed(1)} KB • {doc.chunks} chunks</p>
|
|
232
|
+
</div>
|
|
233
|
+
<span className={\`text-xs px-2 py-1 rounded-full \${
|
|
234
|
+
doc.status === 'indexed' ? 'bg-green-100 text-green-700' :
|
|
235
|
+
doc.status === 'error' ? 'bg-red-100 text-red-700' :
|
|
236
|
+
'bg-yellow-100 text-yellow-700'
|
|
237
|
+
}\`}>{doc.status}</span>
|
|
238
|
+
</div>
|
|
239
|
+
))}
|
|
240
|
+
</div>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ─── Chat Tab ───────────────────────────────────────────────────────────────
|
|
247
|
+
function ChatTab() {
|
|
248
|
+
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
|
249
|
+
const [input, setInput] = useState('');
|
|
250
|
+
const [loading, setLoading] = useState(false);
|
|
251
|
+
const chatEndRef = useRef<HTMLDivElement>(null);
|
|
252
|
+
|
|
253
|
+
useEffect(() => { chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
|
|
254
|
+
|
|
255
|
+
const handleSend = async () => {
|
|
256
|
+
if (!input.trim() || loading) return;
|
|
257
|
+
const query = input.trim();
|
|
258
|
+
setInput('');
|
|
259
|
+
setMessages(prev => [...prev, { role: 'user', content: query }]);
|
|
260
|
+
setLoading(true);
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
const res = await fetch('/api/rag/query', {
|
|
264
|
+
method: 'POST',
|
|
265
|
+
headers: { 'Content-Type': 'application/json' },
|
|
266
|
+
body: JSON.stringify({ query, k: 3 }),
|
|
267
|
+
});
|
|
268
|
+
const data = await res.json();
|
|
269
|
+
|
|
270
|
+
setMessages(prev => [...prev, {
|
|
271
|
+
role: 'assistant',
|
|
272
|
+
content: data.choices?.[0]?.message?.content || data.answer || 'No answer found.',
|
|
273
|
+
sources: data.rag_metadata?.documents || [],
|
|
274
|
+
}]);
|
|
275
|
+
} catch {
|
|
276
|
+
setMessages(prev => [...prev, { role: 'assistant', content: 'Error: Failed to get response.' }]);
|
|
277
|
+
} finally {
|
|
278
|
+
setLoading(false);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<div className="flex flex-col h-[600px]">
|
|
284
|
+
{/* Messages */}
|
|
285
|
+
<div className="flex-1 overflow-y-auto space-y-4 mb-4 p-4 border rounded-lg bg-muted/50">
|
|
286
|
+
{messages.length === 0 && (
|
|
287
|
+
<div className="text-center text-muted-foreground py-12">
|
|
288
|
+
<p className="text-lg mb-1">💬 Chat with your documents</p>
|
|
289
|
+
<p className="text-sm">Upload documents in the Documents tab first, then ask questions here.</p>
|
|
290
|
+
</div>
|
|
291
|
+
)}
|
|
292
|
+
{messages.map((msg, i) => (
|
|
293
|
+
<div key={i} className={\`flex \${msg.role === 'user' ? 'justify-end' : 'justify-start'}\`}>
|
|
294
|
+
<div className={\`max-w-[80%] rounded-lg px-4 py-2 \${msg.role === 'user' ? 'bg-primary text-primary-foreground' : 'bg-card border'}\`}>
|
|
295
|
+
<p className="whitespace-pre-wrap">{msg.content}</p>
|
|
296
|
+
{msg.sources && msg.sources.length > 0 && (
|
|
297
|
+
<div className="mt-2 pt-2 border-t border-border">
|
|
298
|
+
<p className="text-xs text-muted-foreground mb-1">Sources:</p>
|
|
299
|
+
{msg.sources.map((s, j) => (
|
|
300
|
+
<div key={j} className="text-xs bg-muted rounded p-2 mb-1">
|
|
301
|
+
<span className="font-medium">{Math.round(s.score * 100)}% match</span>
|
|
302
|
+
<p className="text-muted-foreground mt-0.5 line-clamp-2">{s.text}</p>
|
|
303
|
+
</div>
|
|
304
|
+
))}
|
|
305
|
+
</div>
|
|
306
|
+
)}
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
))}
|
|
310
|
+
{loading && (
|
|
311
|
+
<div className="flex justify-start"><div className="bg-card border rounded-lg px-4 py-2 text-muted-foreground">Thinking...</div></div>
|
|
312
|
+
)}
|
|
313
|
+
<div ref={chatEndRef} />
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
{/* Input */}
|
|
317
|
+
<div className="flex gap-2">
|
|
318
|
+
<input value={input} onChange={e => setInput(e.target.value)}
|
|
319
|
+
onKeyDown={e => e.key === 'Enter' && !e.shiftKey && handleSend()}
|
|
320
|
+
placeholder="Ask a question about your documents..."
|
|
321
|
+
className="flex-1 px-4 py-2 border rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-ring" />
|
|
322
|
+
<button onClick={handleSend} disabled={loading || !input.trim()}
|
|
323
|
+
className="px-6 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50">
|
|
324
|
+
Send
|
|
325
|
+
</button>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
`;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Generates the RAG document upload API route
|
|
334
|
+
* POST: Proxies document upload to the Python AI Service
|
|
335
|
+
* @returns TypeScript content for app/api/rag/route.ts
|
|
336
|
+
*/
|
|
337
|
+
function generateRagUploadRoute() {
|
|
338
|
+
return `// @chimerai component=RagUploadRoute version=1.0
|
|
339
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
340
|
+
import { getServerSession } from 'next-auth';
|
|
341
|
+
import { authOptions } from '@/lib/auth';
|
|
342
|
+
|
|
343
|
+
const AI_SERVICE_URL = process.env.AI_SERVICE_URL || 'http://localhost:8002';
|
|
344
|
+
|
|
345
|
+
export async function POST(request: NextRequest) {
|
|
346
|
+
try {
|
|
347
|
+
const session = await getServerSession(authOptions);
|
|
348
|
+
if (!session?.user?.id) {
|
|
349
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const contentType = request.headers.get('content-type') || '';
|
|
353
|
+
|
|
354
|
+
let res: Response;
|
|
355
|
+
|
|
356
|
+
if (contentType.includes('multipart/form-data')) {
|
|
357
|
+
// Forward multipart file upload to /api/rag/upload
|
|
358
|
+
const formData = await request.formData();
|
|
359
|
+
const body = new FormData();
|
|
360
|
+
for (const [key, value] of formData.entries()) {
|
|
361
|
+
if (value instanceof Blob) {
|
|
362
|
+
body.append('files', value, (value as File).name);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
res = await fetch(\`\${AI_SERVICE_URL}/api/rag/upload\`, {
|
|
366
|
+
method: 'POST',
|
|
367
|
+
body,
|
|
368
|
+
});
|
|
369
|
+
} else {
|
|
370
|
+
// JSON fallback for programmatic use
|
|
371
|
+
const body = await request.json();
|
|
372
|
+
res = await fetch(\`\${AI_SERVICE_URL}/api/rag/documents\`, {
|
|
373
|
+
method: 'POST',
|
|
374
|
+
headers: { 'Content-Type': 'application/json' },
|
|
375
|
+
body: JSON.stringify(body),
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const data = await res.json();
|
|
380
|
+
return NextResponse.json(data, { status: res.status });
|
|
381
|
+
} catch (error: any) {
|
|
382
|
+
console.error('RAG upload error:', error);
|
|
383
|
+
return NextResponse.json({ error: 'Failed to process documents' }, { status: 500 });
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
`;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Generates the RAG query API route
|
|
390
|
+
* POST: Proxies RAG chat queries to the Python AI Service
|
|
391
|
+
* @returns TypeScript content for app/api/rag/query/route.ts
|
|
392
|
+
*/
|
|
393
|
+
function generateRagQueryRoute() {
|
|
394
|
+
return `// @chimerai component=RagQueryRoute version=1.0
|
|
395
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
396
|
+
import { getServerSession } from 'next-auth';
|
|
397
|
+
import { authOptions } from '@/lib/auth';
|
|
398
|
+
|
|
399
|
+
const AI_SERVICE_URL = process.env.AI_SERVICE_URL || 'http://localhost:8002';
|
|
400
|
+
|
|
401
|
+
export async function POST(request: NextRequest) {
|
|
402
|
+
try {
|
|
403
|
+
const session = await getServerSession(authOptions);
|
|
404
|
+
if (!session?.user?.id) {
|
|
405
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const body = await request.json();
|
|
409
|
+
|
|
410
|
+
const res = await fetch(\`\${AI_SERVICE_URL}/api/rag/chat\`, {
|
|
411
|
+
method: 'POST',
|
|
412
|
+
headers: { 'Content-Type': 'application/json' },
|
|
413
|
+
body: JSON.stringify({
|
|
414
|
+
...body,
|
|
415
|
+
user_id: session.user.id,
|
|
416
|
+
}),
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const data = await res.json();
|
|
420
|
+
return NextResponse.json(data, { status: res.status });
|
|
421
|
+
} catch (error: any) {
|
|
422
|
+
console.error('RAG query error:', error);
|
|
423
|
+
return NextResponse.json({ error: 'Failed to query documents' }, { status: 500 });
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
`;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Generates the RAG stats API route
|
|
430
|
+
* GET: Proxies vector store stats from the Python AI Service
|
|
431
|
+
* @returns TypeScript content for app/api/rag/stats/route.ts
|
|
432
|
+
*/
|
|
433
|
+
function generateRagStatsRoute() {
|
|
434
|
+
return `// @chimerai component=RagStatsRoute version=1.0
|
|
435
|
+
import { NextResponse } from 'next/server';
|
|
436
|
+
import { getServerSession } from 'next-auth';
|
|
437
|
+
import { authOptions } from '@/lib/auth';
|
|
438
|
+
|
|
439
|
+
const AI_SERVICE_URL = process.env.AI_SERVICE_URL || 'http://localhost:8002';
|
|
440
|
+
|
|
441
|
+
export async function GET() {
|
|
442
|
+
try {
|
|
443
|
+
const session = await getServerSession(authOptions);
|
|
444
|
+
if (!session?.user?.id) {
|
|
445
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const res = await fetch(\`\${AI_SERVICE_URL}/api/rag/stats\`, {
|
|
449
|
+
method: 'GET',
|
|
450
|
+
headers: { 'Content-Type': 'application/json' },
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const data = await res.json();
|
|
454
|
+
return NextResponse.json(data, { status: res.status });
|
|
455
|
+
} catch (error: any) {
|
|
456
|
+
console.error('RAG stats error:', error);
|
|
457
|
+
return NextResponse.json({ error: 'Failed to fetch stats' }, { status: 500 });
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
`;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Generates the RAG clear API route
|
|
464
|
+
* POST: Proxies vector store clear to the Python AI Service
|
|
465
|
+
* @returns TypeScript content for app/api/rag/clear/route.ts
|
|
466
|
+
*/
|
|
467
|
+
function generateRagClearRoute() {
|
|
468
|
+
return `// @chimerai component=RagClearRoute version=1.0
|
|
469
|
+
import { NextResponse } from 'next/server';
|
|
470
|
+
import { getServerSession } from 'next-auth';
|
|
471
|
+
import { authOptions } from '@/lib/auth';
|
|
472
|
+
|
|
473
|
+
const AI_SERVICE_URL = process.env.AI_SERVICE_URL || 'http://localhost:8002';
|
|
474
|
+
|
|
475
|
+
export async function DELETE() {
|
|
476
|
+
try {
|
|
477
|
+
const session = await getServerSession(authOptions);
|
|
478
|
+
if (!session?.user?.id) {
|
|
479
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const res = await fetch(\`\${AI_SERVICE_URL}/api/rag/clear\`, {
|
|
483
|
+
method: 'DELETE',
|
|
484
|
+
headers: { 'Content-Type': 'application/json' },
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
const data = await res.json();
|
|
488
|
+
return NextResponse.json(data, { status: res.status });
|
|
489
|
+
} catch (error: any) {
|
|
490
|
+
console.error('RAG clear error:', error);
|
|
491
|
+
return NextResponse.json({ error: 'Failed to clear documents' }, { status: 500 });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
`;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Generates the RAG delete API route
|
|
498
|
+
* DELETE: Proxies per-document deletion to the Python AI Service
|
|
499
|
+
* @returns TypeScript content for app/api/rag/delete/route.ts
|
|
500
|
+
*/
|
|
501
|
+
function generateRagDeleteRoute() {
|
|
502
|
+
return `// @chimerai component=RagDeleteRoute version=1.0
|
|
503
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
504
|
+
import { getServerSession } from 'next-auth';
|
|
505
|
+
import { authOptions } from '@/lib/auth';
|
|
506
|
+
|
|
507
|
+
const AI_SERVICE_URL = process.env.AI_SERVICE_URL || 'http://localhost:8002';
|
|
508
|
+
|
|
509
|
+
export async function DELETE(request: NextRequest) {
|
|
510
|
+
try {
|
|
511
|
+
const session = await getServerSession(authOptions);
|
|
512
|
+
if (!session?.user?.id) {
|
|
513
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const body = await request.json();
|
|
517
|
+
|
|
518
|
+
const res = await fetch(\`\${AI_SERVICE_URL}/api/rag/delete\`, {
|
|
519
|
+
method: 'DELETE',
|
|
520
|
+
headers: { 'Content-Type': 'application/json' },
|
|
521
|
+
body: JSON.stringify(body),
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
const data = await res.json();
|
|
525
|
+
return NextResponse.json(data, { status: res.status });
|
|
526
|
+
} catch (error: any) {
|
|
527
|
+
console.error('RAG delete error:', error);
|
|
528
|
+
return NextResponse.json({ error: 'Failed to delete documents' }, { status: 500 });
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
`;
|
|
532
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Widget Template Generators
|
|
3
|
+
*
|
|
4
|
+
* Generates the embeddable chat widget (Web Component + Shadow DOM)
|
|
5
|
+
* and the API-Key Management UI for external integrations.
|
|
6
|
+
*
|
|
7
|
+
* SPEC: COMPONENT_PORTABILITY_SPEC.md — Phase 1.5 + 1.6
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Generates the self-contained chat widget bundle.
|
|
11
|
+
* Output: public/widget/chat.js
|
|
12
|
+
*
|
|
13
|
+
* Features:
|
|
14
|
+
* - Web Component with Shadow DOM (no CSS leak)
|
|
15
|
+
* - SSE streaming client
|
|
16
|
+
* - Inline CSS (dark/light/auto theme)
|
|
17
|
+
* - No React, no external deps
|
|
18
|
+
* - ChimerAI.mount() API + data-attribute auto-mount
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateWidgetBundle(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Generates the minimal widget loader script.
|
|
23
|
+
* Output: public/widget/loader.js
|
|
24
|
+
*
|
|
25
|
+
* Allows async loading of the widget bundle.
|
|
26
|
+
*/
|
|
27
|
+
export declare function generateWidgetLoader(): string;
|
|
28
|
+
/**
|
|
29
|
+
* Generates the API-Key Management page.
|
|
30
|
+
* Output: app/(app)/settings/api-keys/page.tsx
|
|
31
|
+
*
|
|
32
|
+
* Features:
|
|
33
|
+
* - List API keys (prefix, name, scopes, last used, created)
|
|
34
|
+
* - Create new key (name, scopes selection, expiration)
|
|
35
|
+
* - Show key once after creation (then never again)
|
|
36
|
+
* - Revoke key (soft-delete)
|
|
37
|
+
* - Embed-code generator: select key → copy-paste HTML snippet
|
|
38
|
+
*/
|
|
39
|
+
export declare function generateApiKeyManagementPage(): string;
|
|
40
|
+
/**
|
|
41
|
+
* Generates the API-Key CRUD route.
|
|
42
|
+
* Output: app/api/v1/api-keys/route.ts
|
|
43
|
+
*
|
|
44
|
+
* GET — list current user's API keys
|
|
45
|
+
* POST — create a new key
|
|
46
|
+
*/
|
|
47
|
+
export declare function generateApiKeysRoute(): string;
|
|
48
|
+
/**
|
|
49
|
+
* Generates the API-Key [id] route for revocation.
|
|
50
|
+
* Output: app/api/v1/api-keys/[id]/route.ts
|
|
51
|
+
*/
|
|
52
|
+
export declare function generateApiKeyIdRoute(): string;
|
|
53
|
+
/**
|
|
54
|
+
* Generates the rate limiter with API-key-specific tier.
|
|
55
|
+
* Output: lib/rate-limit.ts
|
|
56
|
+
*
|
|
57
|
+
* Features:
|
|
58
|
+
* - In-memory fallback (single-instance)
|
|
59
|
+
* - Upstash Redis if configured (multi-instance / serverless)
|
|
60
|
+
* - Separate limits: session-based (per userId) vs API-key (per apiKeyId)
|
|
61
|
+
* - API-key default: 60 req/min
|
|
62
|
+
*/
|
|
63
|
+
export declare function generateRateLimiter(): string;
|
|
64
|
+
//# sourceMappingURL=widget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget.d.ts","sourceRoot":"","sources":["../../src/templates/widget.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAmpB7C;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAiC7C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,CAiTrD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAiF7C;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAiC9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAkL5C"}
|