@digilogiclabs/create-saas-app 1.17.1 → 1.18.0

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 (45) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/cli/commands/create.d.ts.map +1 -1
  4. package/dist/cli/commands/create.js +6 -2
  5. package/dist/cli/commands/create.js.map +1 -1
  6. package/dist/cli/index.js +1 -1
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/generators/template-generator.d.ts.map +1 -1
  9. package/dist/generators/template-generator.js +13 -7
  10. package/dist/generators/template-generator.js.map +1 -1
  11. package/dist/templates/mobile/ui-auth-payments-ai-rag/template/README.md +655 -0
  12. package/dist/templates/mobile/ui-auth-payments-ai-rag/template/app/(tabs)/ai.tsx +683 -0
  13. package/dist/templates/mobile/ui-auth-payments-ai-rag/template/docs/MOBILE-SETUP.md +787 -0
  14. package/dist/templates/mobile/ui-auth-payments-ai-rag/template/hooks/useRAGSystem.ts +346 -0
  15. package/dist/templates/mobile/ui-auth-payments-ai-rag/template/lib/rag/config.ts +180 -0
  16. package/dist/templates/mobile/ui-auth-payments-ai-rag/template/package.json +113 -0
  17. package/dist/templates/mobile/ui-auth-payments-ai-rag/template/scripts/setup-rag.js +599 -0
  18. package/dist/templates/web/ui-auth-payments-ai-rag/template/README.md +434 -0
  19. package/dist/templates/web/ui-auth-payments-ai-rag/template/components/rag/KnowledgeManager.tsx +642 -0
  20. package/dist/templates/web/ui-auth-payments-ai-rag/template/components/rag/RAGAnalytics.tsx +466 -0
  21. package/dist/templates/web/ui-auth-payments-ai-rag/template/components/rag/RAGChatInterface.tsx +393 -0
  22. package/dist/templates/web/ui-auth-payments-ai-rag/template/docs/GETTING-STARTED.md +457 -0
  23. package/dist/templates/web/ui-auth-payments-ai-rag/template/hooks/useRAGSystem.ts +478 -0
  24. package/dist/templates/web/ui-auth-payments-ai-rag/template/lib/rag/config.ts +250 -0
  25. package/dist/templates/web/ui-auth-payments-ai-rag/template/package.json +74 -0
  26. package/dist/templates/web/ui-auth-payments-ai-rag/template/scripts/setup-rag.js +622 -0
  27. package/dist/templates/web/ui-auth-payments-ai-rag/template/src/app/ai/page.tsx +396 -0
  28. package/package.json +1 -1
  29. package/src/templates/mobile/ui-auth-payments-ai-rag/template/README.md +655 -0
  30. package/src/templates/mobile/ui-auth-payments-ai-rag/template/app/(tabs)/ai.tsx +683 -0
  31. package/src/templates/mobile/ui-auth-payments-ai-rag/template/docs/MOBILE-SETUP.md +787 -0
  32. package/src/templates/mobile/ui-auth-payments-ai-rag/template/hooks/useRAGSystem.ts +346 -0
  33. package/src/templates/mobile/ui-auth-payments-ai-rag/template/lib/rag/config.ts +180 -0
  34. package/src/templates/mobile/ui-auth-payments-ai-rag/template/package.json +113 -0
  35. package/src/templates/mobile/ui-auth-payments-ai-rag/template/scripts/setup-rag.js +599 -0
  36. package/src/templates/web/ui-auth-payments-ai-rag/template/README.md +434 -0
  37. package/src/templates/web/ui-auth-payments-ai-rag/template/components/rag/KnowledgeManager.tsx +642 -0
  38. package/src/templates/web/ui-auth-payments-ai-rag/template/components/rag/RAGAnalytics.tsx +466 -0
  39. package/src/templates/web/ui-auth-payments-ai-rag/template/components/rag/RAGChatInterface.tsx +393 -0
  40. package/src/templates/web/ui-auth-payments-ai-rag/template/docs/GETTING-STARTED.md +457 -0
  41. package/src/templates/web/ui-auth-payments-ai-rag/template/hooks/useRAGSystem.ts +478 -0
  42. package/src/templates/web/ui-auth-payments-ai-rag/template/lib/rag/config.ts +250 -0
  43. package/src/templates/web/ui-auth-payments-ai-rag/template/package.json +74 -0
  44. package/src/templates/web/ui-auth-payments-ai-rag/template/scripts/setup-rag.js +622 -0
  45. package/src/templates/web/ui-auth-payments-ai-rag/template/src/app/ai/page.tsx +396 -0
@@ -0,0 +1,642 @@
1
+ 'use client'
2
+
3
+ import { useState, useRef } from 'react'
4
+ import { Button, Card } from '@digilogiclabs/saas-factory-ui'
5
+ import {
6
+ Plus,
7
+ Search,
8
+ Filter,
9
+ Upload,
10
+ Download,
11
+ Trash2,
12
+ Edit,
13
+ FileText,
14
+ Calendar,
15
+ Tag,
16
+ Eye,
17
+ MoreHorizontal,
18
+ Loader2,
19
+ CheckCircle,
20
+ AlertCircle,
21
+ Info
22
+ } from 'lucide-react'
23
+ import { toast } from 'sonner'
24
+ import { motion, AnimatePresence } from 'framer-motion'
25
+ import type { UseRAGSystemReturn } from '../../hooks/useRAGSystem'
26
+ import type { RAGDocument, RAGSearchResult } from '@digilogiclabs/saas-factory-ai-types'
27
+
28
+ interface KnowledgeManagerProps {
29
+ ragSystem: UseRAGSystemReturn<any>
30
+ enableBulkOperations?: boolean
31
+ enableExport?: boolean
32
+ onSuccess?: (message: string) => void
33
+ onError?: (error: string) => void
34
+ className?: string
35
+ }
36
+
37
+ interface KnowledgeDocument extends RAGDocument<any> {
38
+ id: string
39
+ selected?: boolean
40
+ }
41
+
42
+ export function KnowledgeManager({
43
+ ragSystem,
44
+ enableBulkOperations = true,
45
+ enableExport = true,
46
+ onSuccess,
47
+ onError,
48
+ className = ''
49
+ }: KnowledgeManagerProps) {
50
+ const [documents, setDocuments] = useState<KnowledgeDocument[]>([])
51
+ const [searchQuery, setSearchQuery] = useState('')
52
+ const [selectedCategory, setSelectedCategory] = useState<string>('all')
53
+ const [isAddingDocument, setIsAddingDocument] = useState(false)
54
+ const [editingDocument, setEditingDocument] = useState<KnowledgeDocument | null>(null)
55
+ const [viewingDocument, setViewingDocument] = useState<KnowledgeDocument | null>(null)
56
+ const [selectedDocuments, setSelectedDocuments] = useState<Set<string>>(new Set())
57
+ const [bulkAction, setBulkAction] = useState<'none' | 'delete' | 'export'>('none')
58
+
59
+ const fileInputRef = useRef<HTMLInputElement>(null)
60
+
61
+ // Load documents
62
+ const loadDocuments = async () => {
63
+ try {
64
+ const results = await ragSystem.searchKnowledge(searchQuery || '', {
65
+ category: selectedCategory === 'all' ? undefined : selectedCategory,
66
+ limit: 50,
67
+ includeMetadata: true
68
+ })
69
+
70
+ setDocuments(results.map(result => ({
71
+ ...result,
72
+ id: result.id || `doc-${Date.now()}-${Math.random()}`,
73
+ selected: selectedDocuments.has(result.id || '')
74
+ })))
75
+ } catch (error) {
76
+ console.error('Failed to load documents:', error)
77
+ onError?.('Failed to load documents')
78
+ }
79
+ }
80
+
81
+ // Search documents
82
+ const handleSearch = async () => {
83
+ await loadDocuments()
84
+ }
85
+
86
+ // Add new document
87
+ const handleAddDocument = async (documentData: Partial<RAGDocument<any>>) => {
88
+ try {
89
+ const id = await ragSystem.addKnowledge({
90
+ content: documentData.content || '',
91
+ category: documentData.category || 'general',
92
+ fields: documentData.fields || {},
93
+ metadata: documentData.metadata || {},
94
+ source: documentData.source || { type: 'manual', createdBy: 'user' }
95
+ })
96
+
97
+ if (id) {
98
+ onSuccess?.('Document added successfully')
99
+ setIsAddingDocument(false)
100
+ await loadDocuments()
101
+ }
102
+ } catch (error) {
103
+ console.error('Failed to add document:', error)
104
+ onError?.('Failed to add document')
105
+ }
106
+ }
107
+
108
+ // Update document
109
+ const handleUpdateDocument = async (id: string, updates: Partial<RAGDocument<any>>) => {
110
+ try {
111
+ const success = await ragSystem.updateKnowledge(id, updates)
112
+
113
+ if (success) {
114
+ onSuccess?.('Document updated successfully')
115
+ setEditingDocument(null)
116
+ await loadDocuments()
117
+ }
118
+ } catch (error) {
119
+ console.error('Failed to update document:', error)
120
+ onError?.('Failed to update document')
121
+ }
122
+ }
123
+
124
+ // Delete document
125
+ const handleDeleteDocument = async (id: string) => {
126
+ try {
127
+ const success = await ragSystem.deleteKnowledge(id)
128
+
129
+ if (success) {
130
+ onSuccess?.('Document deleted successfully')
131
+ await loadDocuments()
132
+ }
133
+ } catch (error) {
134
+ console.error('Failed to delete document:', error)
135
+ onError?.('Failed to delete document')
136
+ }
137
+ }
138
+
139
+ // Bulk operations
140
+ const handleBulkAction = async () => {
141
+ if (selectedDocuments.size === 0) return
142
+
143
+ try {
144
+ switch (bulkAction) {
145
+ case 'delete':
146
+ for (const id of selectedDocuments) {
147
+ await ragSystem.deleteKnowledge(id)
148
+ }
149
+ onSuccess?.(`Deleted ${selectedDocuments.size} documents`)
150
+ break
151
+
152
+ case 'export':
153
+ const exportData = documents
154
+ .filter(doc => selectedDocuments.has(doc.id))
155
+ .map(doc => ({
156
+ content: doc.content,
157
+ category: doc.category,
158
+ fields: doc.fields,
159
+ metadata: doc.metadata
160
+ }))
161
+
162
+ const blob = new Blob([JSON.stringify(exportData, null, 2)], {
163
+ type: 'application/json'
164
+ })
165
+ const url = URL.createObjectURL(blob)
166
+ const a = document.createElement('a')
167
+ a.href = url
168
+ a.download = `knowledge-export-${new Date().toISOString().split('T')[0]}.json`
169
+ a.click()
170
+ URL.revokeObjectURL(url)
171
+
172
+ onSuccess?.(`Exported ${selectedDocuments.size} documents`)
173
+ break
174
+ }
175
+
176
+ setSelectedDocuments(new Set())
177
+ setBulkAction('none')
178
+ await loadDocuments()
179
+
180
+ } catch (error) {
181
+ console.error('Bulk operation failed:', error)
182
+ onError?.('Bulk operation failed')
183
+ }
184
+ }
185
+
186
+ // File upload
187
+ const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
188
+ const files = event.target.files
189
+ if (!files) return
190
+
191
+ try {
192
+ for (const file of Array.from(files)) {
193
+ const content = await file.text()
194
+
195
+ await handleAddDocument({
196
+ content,
197
+ category: 'uploaded',
198
+ metadata: {
199
+ filename: file.name,
200
+ fileType: file.type,
201
+ fileSize: file.size,
202
+ uploadedAt: new Date().toISOString()
203
+ },
204
+ source: { type: 'file', filename: file.name }
205
+ })
206
+ }
207
+ } catch (error) {
208
+ console.error('File upload failed:', error)
209
+ onError?.('File upload failed')
210
+ }
211
+ }
212
+
213
+ // Document selection
214
+ const toggleDocumentSelection = (id: string) => {
215
+ const newSelection = new Set(selectedDocuments)
216
+ if (newSelection.has(id)) {
217
+ newSelection.delete(id)
218
+ } else {
219
+ newSelection.add(id)
220
+ }
221
+ setSelectedDocuments(newSelection)
222
+ }
223
+
224
+ const selectAllDocuments = () => {
225
+ if (selectedDocuments.size === documents.length) {
226
+ setSelectedDocuments(new Set())
227
+ } else {
228
+ setSelectedDocuments(new Set(documents.map(doc => doc.id)))
229
+ }
230
+ }
231
+
232
+ const categories = ['all', 'general', 'uploaded', 'manual', 'imported']
233
+
234
+ return (
235
+ <div className={`space-y-6 ${className}`}>
236
+ {/* Header */}
237
+ <div className="flex items-center justify-between">
238
+ <div>
239
+ <h2 className="text-2xl font-bold">Knowledge Base Manager</h2>
240
+ <p className="text-gray-600 dark:text-gray-300">
241
+ {ragSystem.totalDocuments} documents in your knowledge base
242
+ </p>
243
+ </div>
244
+
245
+ <div className="flex items-center gap-2">
246
+ <Button
247
+ variant="outline"
248
+ onClick={() => fileInputRef.current?.click()}
249
+ disabled={ragSystem.isLoading}
250
+ >
251
+ <Upload className="w-4 h-4 mr-2" />
252
+ Upload
253
+ </Button>
254
+
255
+ <Button
256
+ onClick={() => setIsAddingDocument(true)}
257
+ disabled={ragSystem.isLoading}
258
+ >
259
+ <Plus className="w-4 h-4 mr-2" />
260
+ Add Document
261
+ </Button>
262
+ </div>
263
+ </div>
264
+
265
+ {/* Search and Filters */}
266
+ <Card className="p-4">
267
+ <div className="flex flex-col sm:flex-row gap-4">
268
+ <div className="flex-1 relative">
269
+ <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
270
+ <input
271
+ type="text"
272
+ placeholder="Search documents..."
273
+ value={searchQuery}
274
+ onChange={(e) => setSearchQuery(e.target.value)}
275
+ onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
276
+ className="w-full pl-10 pr-4 py-2 border border-gray-200 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
277
+ />
278
+ </div>
279
+
280
+ <select
281
+ value={selectedCategory}
282
+ onChange={(e) => setSelectedCategory(e.target.value)}
283
+ className="px-4 py-2 border border-gray-200 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
284
+ >
285
+ {categories.map(category => (
286
+ <option key={category} value={category}>
287
+ {category === 'all' ? 'All Categories' : category.charAt(0).toUpperCase() + category.slice(1)}
288
+ </option>
289
+ ))}
290
+ </select>
291
+
292
+ <Button onClick={handleSearch} disabled={ragSystem.isLoading}>
293
+ {ragSystem.isLoading ? (
294
+ <Loader2 className="w-4 h-4 animate-spin" />
295
+ ) : (
296
+ <Search className="w-4 h-4" />
297
+ )}
298
+ </Button>
299
+ </div>
300
+ </Card>
301
+
302
+ {/* Bulk Actions */}
303
+ {enableBulkOperations && selectedDocuments.size > 0 && (
304
+ <Card className="p-4 bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800">
305
+ <div className="flex items-center justify-between">
306
+ <span className="font-medium">
307
+ {selectedDocuments.size} document{selectedDocuments.size !== 1 ? 's' : ''} selected
308
+ </span>
309
+
310
+ <div className="flex items-center gap-2">
311
+ <select
312
+ value={bulkAction}
313
+ onChange={(e) => setBulkAction(e.target.value as any)}
314
+ className="px-3 py-1 border border-gray-200 dark:border-gray-700 rounded"
315
+ >
316
+ <option value="none">Choose action...</option>
317
+ <option value="delete">Delete</option>
318
+ {enableExport && <option value="export">Export</option>}
319
+ </select>
320
+
321
+ <Button
322
+ onClick={handleBulkAction}
323
+ disabled={bulkAction === 'none' || ragSystem.isLoading}
324
+ variant="outline"
325
+ size="sm"
326
+ >
327
+ Apply
328
+ </Button>
329
+
330
+ <Button
331
+ onClick={() => setSelectedDocuments(new Set())}
332
+ variant="ghost"
333
+ size="sm"
334
+ >
335
+ Clear Selection
336
+ </Button>
337
+ </div>
338
+ </div>
339
+ </Card>
340
+ )}
341
+
342
+ {/* Documents List */}
343
+ <Card className="p-0">
344
+ {documents.length === 0 ? (
345
+ <div className="text-center py-12">
346
+ <FileText className="w-12 h-12 mx-auto mb-4 text-gray-300" />
347
+ <h3 className="text-lg font-medium mb-2">No documents found</h3>
348
+ <p className="text-gray-600 dark:text-gray-300 mb-4">
349
+ {searchQuery ? 'Try adjusting your search terms' : 'Start by adding your first document'}
350
+ </p>
351
+ <Button onClick={() => setIsAddingDocument(true)}>
352
+ <Plus className="w-4 h-4 mr-2" />
353
+ Add Document
354
+ </Button>
355
+ </div>
356
+ ) : (
357
+ <div className="divide-y divide-gray-200 dark:divide-gray-700">
358
+ {/* Header */}
359
+ {enableBulkOperations && (
360
+ <div className="px-6 py-3 bg-gray-50 dark:bg-gray-800/50">
361
+ <label className="flex items-center gap-3">
362
+ <input
363
+ type="checkbox"
364
+ checked={selectedDocuments.size === documents.length && documents.length > 0}
365
+ onChange={selectAllDocuments}
366
+ className="rounded border-gray-300"
367
+ />
368
+ <span className="text-sm font-medium">Select All</span>
369
+ </label>
370
+ </div>
371
+ )}
372
+
373
+ {/* Document Items */}
374
+ <AnimatePresence>
375
+ {documents.map((document) => (
376
+ <motion.div
377
+ key={document.id}
378
+ initial={{ opacity: 0, y: 20 }}
379
+ animate={{ opacity: 1, y: 0 }}
380
+ exit={{ opacity: 0, y: -20 }}
381
+ className="px-6 py-4 hover:bg-gray-50 dark:hover:bg-gray-800/50"
382
+ >
383
+ <div className="flex items-start gap-4">
384
+ {enableBulkOperations && (
385
+ <input
386
+ type="checkbox"
387
+ checked={selectedDocuments.has(document.id)}
388
+ onChange={() => toggleDocumentSelection(document.id)}
389
+ className="mt-1 rounded border-gray-300"
390
+ />
391
+ )}
392
+
393
+ <div className="flex-1 min-w-0">
394
+ <div className="flex items-start justify-between">
395
+ <div className="flex-1">
396
+ <h4 className="font-medium line-clamp-1">
397
+ {document.fields?.title || `Document ${document.id.slice(-8)}`}
398
+ </h4>
399
+ <p className="text-sm text-gray-600 dark:text-gray-300 line-clamp-2 mt-1">
400
+ {document.content}
401
+ </p>
402
+
403
+ <div className="flex items-center gap-4 mt-2 text-xs text-gray-500">
404
+ <span className="flex items-center gap-1">
405
+ <Tag className="w-3 h-3" />
406
+ {document.category}
407
+ </span>
408
+ <span className="flex items-center gap-1">
409
+ <Calendar className="w-3 h-3" />
410
+ {new Date(document.createdAt || Date.now()).toLocaleDateString()}
411
+ </span>
412
+ {document.metadata?.filename && (
413
+ <span className="flex items-center gap-1">
414
+ <FileText className="w-3 h-3" />
415
+ {document.metadata.filename}
416
+ </span>
417
+ )}
418
+ </div>
419
+ </div>
420
+
421
+ <div className="flex items-center gap-1 ml-4">
422
+ <Button
423
+ variant="ghost"
424
+ size="sm"
425
+ onClick={() => setViewingDocument(document)}
426
+ >
427
+ <Eye className="w-4 h-4" />
428
+ </Button>
429
+
430
+ <Button
431
+ variant="ghost"
432
+ size="sm"
433
+ onClick={() => setEditingDocument(document)}
434
+ >
435
+ <Edit className="w-4 h-4" />
436
+ </Button>
437
+
438
+ <Button
439
+ variant="ghost"
440
+ size="sm"
441
+ onClick={() => handleDeleteDocument(document.id)}
442
+ className="text-red-500 hover:text-red-700"
443
+ >
444
+ <Trash2 className="w-4 h-4" />
445
+ </Button>
446
+ </div>
447
+ </div>
448
+ </div>
449
+ </div>
450
+ </motion.div>
451
+ ))}
452
+ </AnimatePresence>
453
+ </div>
454
+ )}
455
+ </Card>
456
+
457
+ {/* Hidden file input */}
458
+ <input
459
+ ref={fileInputRef}
460
+ type="file"
461
+ multiple
462
+ accept=".txt,.md,.json,.csv"
463
+ onChange={handleFileUpload}
464
+ className="hidden"
465
+ />
466
+
467
+ {/* Add Document Modal */}
468
+ {isAddingDocument && (
469
+ <DocumentModal
470
+ onSave={handleAddDocument}
471
+ onCancel={() => setIsAddingDocument(false)}
472
+ />
473
+ )}
474
+
475
+ {/* Edit Document Modal */}
476
+ {editingDocument && (
477
+ <DocumentModal
478
+ document={editingDocument}
479
+ onSave={(data) => handleUpdateDocument(editingDocument.id, data)}
480
+ onCancel={() => setEditingDocument(null)}
481
+ />
482
+ )}
483
+
484
+ {/* View Document Modal */}
485
+ {viewingDocument && (
486
+ <DocumentViewModal
487
+ document={viewingDocument}
488
+ onClose={() => setViewingDocument(null)}
489
+ />
490
+ )}
491
+ </div>
492
+ )
493
+ }
494
+
495
+ // Document Modal Component
496
+ function DocumentModal({
497
+ document,
498
+ onSave,
499
+ onCancel
500
+ }: {
501
+ document?: KnowledgeDocument
502
+ onSave: (data: Partial<RAGDocument<any>>) => void
503
+ onCancel: () => void
504
+ }) {
505
+ const [formData, setFormData] = useState({
506
+ content: document?.content || '',
507
+ category: document?.category || 'general',
508
+ title: document?.fields?.title || '',
509
+ description: document?.fields?.description || ''
510
+ })
511
+
512
+ const handleSubmit = (e: React.FormEvent) => {
513
+ e.preventDefault()
514
+ onSave({
515
+ content: formData.content,
516
+ category: formData.category,
517
+ fields: {
518
+ title: formData.title,
519
+ description: formData.description
520
+ }
521
+ })
522
+ }
523
+
524
+ return (
525
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
526
+ <div className="bg-white dark:bg-gray-800 rounded-xl p-6 w-full max-w-2xl max-h-[90vh] overflow-y-auto">
527
+ <h3 className="text-lg font-semibold mb-4">
528
+ {document ? 'Edit Document' : 'Add New Document'}
529
+ </h3>
530
+
531
+ <form onSubmit={handleSubmit} className="space-y-4">
532
+ <div>
533
+ <label className="block text-sm font-medium mb-2">Title</label>
534
+ <input
535
+ type="text"
536
+ value={formData.title}
537
+ onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
538
+ className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
539
+ placeholder="Document title..."
540
+ />
541
+ </div>
542
+
543
+ <div>
544
+ <label className="block text-sm font-medium mb-2">Category</label>
545
+ <select
546
+ value={formData.category}
547
+ onChange={(e) => setFormData(prev => ({ ...prev, category: e.target.value }))}
548
+ className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
549
+ >
550
+ <option value="general">General</option>
551
+ <option value="manual">Manual</option>
552
+ <option value="faq">FAQ</option>
553
+ <option value="guide">Guide</option>
554
+ <option value="reference">Reference</option>
555
+ </select>
556
+ </div>
557
+
558
+ <div>
559
+ <label className="block text-sm font-medium mb-2">Description</label>
560
+ <input
561
+ type="text"
562
+ value={formData.description}
563
+ onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
564
+ className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
565
+ placeholder="Brief description..."
566
+ />
567
+ </div>
568
+
569
+ <div>
570
+ <label className="block text-sm font-medium mb-2">Content</label>
571
+ <textarea
572
+ value={formData.content}
573
+ onChange={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))}
574
+ className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent h-32"
575
+ placeholder="Document content..."
576
+ required
577
+ />
578
+ </div>
579
+
580
+ <div className="flex justify-end gap-3">
581
+ <Button type="button" variant="outline" onClick={onCancel}>
582
+ Cancel
583
+ </Button>
584
+ <Button type="submit">
585
+ {document ? 'Update' : 'Add'} Document
586
+ </Button>
587
+ </div>
588
+ </form>
589
+ </div>
590
+ </div>
591
+ )
592
+ }
593
+
594
+ // Document View Modal Component
595
+ function DocumentViewModal({
596
+ document,
597
+ onClose
598
+ }: {
599
+ document: KnowledgeDocument
600
+ onClose: () => void
601
+ }) {
602
+ return (
603
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
604
+ <div className="bg-white dark:bg-gray-800 rounded-xl p-6 w-full max-w-4xl max-h-[90vh] overflow-y-auto">
605
+ <div className="flex items-start justify-between mb-4">
606
+ <div>
607
+ <h3 className="text-lg font-semibold">
608
+ {document.fields?.title || `Document ${document.id.slice(-8)}`}
609
+ </h3>
610
+ <p className="text-sm text-gray-600 dark:text-gray-300">
611
+ {document.category} • {new Date(document.createdAt || Date.now()).toLocaleDateString()}
612
+ </p>
613
+ </div>
614
+ <Button variant="ghost" onClick={onClose}>
615
+
616
+ </Button>
617
+ </div>
618
+
619
+ {document.fields?.description && (
620
+ <p className="text-gray-700 dark:text-gray-300 mb-4">
621
+ {document.fields.description}
622
+ </p>
623
+ )}
624
+
625
+ <div className="prose dark:prose-invert max-w-none">
626
+ <div className="whitespace-pre-wrap">
627
+ {document.content}
628
+ </div>
629
+ </div>
630
+
631
+ {document.metadata && Object.keys(document.metadata).length > 0 && (
632
+ <div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
633
+ <h4 className="font-medium mb-2">Metadata</h4>
634
+ <pre className="text-xs bg-gray-50 dark:bg-gray-900 p-3 rounded overflow-x-auto">
635
+ {JSON.stringify(document.metadata, null, 2)}
636
+ </pre>
637
+ </div>
638
+ )}
639
+ </div>
640
+ </div>
641
+ )
642
+ }