@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.
- package/CHANGELOG.md +51 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +6 -2
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/generators/template-generator.d.ts.map +1 -1
- package/dist/generators/template-generator.js +13 -7
- package/dist/generators/template-generator.js.map +1 -1
- package/dist/templates/mobile/ui-auth-payments-ai-rag/template/README.md +655 -0
- package/dist/templates/mobile/ui-auth-payments-ai-rag/template/app/(tabs)/ai.tsx +683 -0
- package/dist/templates/mobile/ui-auth-payments-ai-rag/template/docs/MOBILE-SETUP.md +787 -0
- package/dist/templates/mobile/ui-auth-payments-ai-rag/template/hooks/useRAGSystem.ts +346 -0
- package/dist/templates/mobile/ui-auth-payments-ai-rag/template/lib/rag/config.ts +180 -0
- package/dist/templates/mobile/ui-auth-payments-ai-rag/template/package.json +113 -0
- package/dist/templates/mobile/ui-auth-payments-ai-rag/template/scripts/setup-rag.js +599 -0
- package/dist/templates/web/ui-auth-payments-ai-rag/template/README.md +434 -0
- package/dist/templates/web/ui-auth-payments-ai-rag/template/components/rag/KnowledgeManager.tsx +642 -0
- package/dist/templates/web/ui-auth-payments-ai-rag/template/components/rag/RAGAnalytics.tsx +466 -0
- package/dist/templates/web/ui-auth-payments-ai-rag/template/components/rag/RAGChatInterface.tsx +393 -0
- package/dist/templates/web/ui-auth-payments-ai-rag/template/docs/GETTING-STARTED.md +457 -0
- package/dist/templates/web/ui-auth-payments-ai-rag/template/hooks/useRAGSystem.ts +478 -0
- package/dist/templates/web/ui-auth-payments-ai-rag/template/lib/rag/config.ts +250 -0
- package/dist/templates/web/ui-auth-payments-ai-rag/template/package.json +74 -0
- package/dist/templates/web/ui-auth-payments-ai-rag/template/scripts/setup-rag.js +622 -0
- package/dist/templates/web/ui-auth-payments-ai-rag/template/src/app/ai/page.tsx +396 -0
- package/package.json +1 -1
- package/src/templates/mobile/ui-auth-payments-ai-rag/template/README.md +655 -0
- package/src/templates/mobile/ui-auth-payments-ai-rag/template/app/(tabs)/ai.tsx +683 -0
- package/src/templates/mobile/ui-auth-payments-ai-rag/template/docs/MOBILE-SETUP.md +787 -0
- package/src/templates/mobile/ui-auth-payments-ai-rag/template/hooks/useRAGSystem.ts +346 -0
- package/src/templates/mobile/ui-auth-payments-ai-rag/template/lib/rag/config.ts +180 -0
- package/src/templates/mobile/ui-auth-payments-ai-rag/template/package.json +113 -0
- package/src/templates/mobile/ui-auth-payments-ai-rag/template/scripts/setup-rag.js +599 -0
- package/src/templates/web/ui-auth-payments-ai-rag/template/README.md +434 -0
- package/src/templates/web/ui-auth-payments-ai-rag/template/components/rag/KnowledgeManager.tsx +642 -0
- package/src/templates/web/ui-auth-payments-ai-rag/template/components/rag/RAGAnalytics.tsx +466 -0
- package/src/templates/web/ui-auth-payments-ai-rag/template/components/rag/RAGChatInterface.tsx +393 -0
- package/src/templates/web/ui-auth-payments-ai-rag/template/docs/GETTING-STARTED.md +457 -0
- package/src/templates/web/ui-auth-payments-ai-rag/template/hooks/useRAGSystem.ts +478 -0
- package/src/templates/web/ui-auth-payments-ai-rag/template/lib/rag/config.ts +250 -0
- package/src/templates/web/ui-auth-payments-ai-rag/template/package.json +74 -0
- package/src/templates/web/ui-auth-payments-ai-rag/template/scripts/setup-rag.js +622 -0
- package/src/templates/web/ui-auth-payments-ai-rag/template/src/app/ai/page.tsx +396 -0
package/src/templates/web/ui-auth-payments-ai-rag/template/components/rag/RAGChatInterface.tsx
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useEffect } from 'react'
|
|
4
|
+
import { Button } from '@digilogiclabs/saas-factory-ui'
|
|
5
|
+
import {
|
|
6
|
+
Send,
|
|
7
|
+
Loader2,
|
|
8
|
+
Copy,
|
|
9
|
+
ThumbsUp,
|
|
10
|
+
ThumbsDown,
|
|
11
|
+
RefreshCw,
|
|
12
|
+
StopCircle,
|
|
13
|
+
Mic,
|
|
14
|
+
MicOff,
|
|
15
|
+
FileText,
|
|
16
|
+
ExternalLink,
|
|
17
|
+
Sparkles
|
|
18
|
+
} from 'lucide-react'
|
|
19
|
+
import { toast } from 'sonner'
|
|
20
|
+
import { motion, AnimatePresence } from 'framer-motion'
|
|
21
|
+
import type { UseRAGSystemReturn } from '../../hooks/useRAGSystem'
|
|
22
|
+
import type { RAGResponse, RAGSearchResult } from '@digilogiclabs/saas-factory-ai-types'
|
|
23
|
+
|
|
24
|
+
interface RAGChatInterfaceProps {
|
|
25
|
+
ragSystem: UseRAGSystemReturn<any>
|
|
26
|
+
enableStreaming?: boolean
|
|
27
|
+
showSources?: boolean
|
|
28
|
+
showConfidence?: boolean
|
|
29
|
+
onError?: (error: Error) => void
|
|
30
|
+
className?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface Message {
|
|
34
|
+
id: string
|
|
35
|
+
type: 'user' | 'assistant'
|
|
36
|
+
content: string
|
|
37
|
+
sources?: RAGSearchResult[]
|
|
38
|
+
confidence?: number
|
|
39
|
+
timestamp: Date
|
|
40
|
+
isStreaming?: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function RAGChatInterface({
|
|
44
|
+
ragSystem,
|
|
45
|
+
enableStreaming = true,
|
|
46
|
+
showSources = true,
|
|
47
|
+
showConfidence = false,
|
|
48
|
+
onError,
|
|
49
|
+
className = ''
|
|
50
|
+
}: RAGChatInterfaceProps) {
|
|
51
|
+
const [messages, setMessages] = useState<Message[]>([])
|
|
52
|
+
const [input, setInput] = useState('')
|
|
53
|
+
const [isRecording, setIsRecording] = useState(false)
|
|
54
|
+
const messagesEndRef = useRef<HTMLDivElement>(null)
|
|
55
|
+
const inputRef = useRef<HTMLTextAreaElement>(null)
|
|
56
|
+
|
|
57
|
+
const scrollToBottom = () => {
|
|
58
|
+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
scrollToBottom()
|
|
63
|
+
}, [messages])
|
|
64
|
+
|
|
65
|
+
// Handle streaming responses
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (ragSystem.streamingState.isStreaming) {
|
|
68
|
+
setMessages(prev => {
|
|
69
|
+
const newMessages = [...prev]
|
|
70
|
+
const lastMessage = newMessages[newMessages.length - 1]
|
|
71
|
+
|
|
72
|
+
if (lastMessage && lastMessage.type === 'assistant' && lastMessage.isStreaming) {
|
|
73
|
+
// Update existing streaming message
|
|
74
|
+
lastMessage.content = ragSystem.streamingState.currentResponse
|
|
75
|
+
lastMessage.sources = ragSystem.streamingState.sources
|
|
76
|
+
lastMessage.confidence = ragSystem.streamingState.confidence
|
|
77
|
+
} else {
|
|
78
|
+
// Create new streaming message
|
|
79
|
+
newMessages.push({
|
|
80
|
+
id: `streaming-${Date.now()}`,
|
|
81
|
+
type: 'assistant',
|
|
82
|
+
content: ragSystem.streamingState.currentResponse,
|
|
83
|
+
sources: ragSystem.streamingState.sources,
|
|
84
|
+
confidence: ragSystem.streamingState.confidence,
|
|
85
|
+
timestamp: new Date(),
|
|
86
|
+
isStreaming: true
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return newMessages
|
|
91
|
+
})
|
|
92
|
+
} else {
|
|
93
|
+
// Finalize streaming message
|
|
94
|
+
setMessages(prev => {
|
|
95
|
+
const newMessages = [...prev]
|
|
96
|
+
const lastMessage = newMessages[newMessages.length - 1]
|
|
97
|
+
|
|
98
|
+
if (lastMessage && lastMessage.isStreaming) {
|
|
99
|
+
lastMessage.isStreaming = false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return newMessages
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
}, [ragSystem.streamingState])
|
|
106
|
+
|
|
107
|
+
const handleSubmit = async (e?: React.FormEvent) => {
|
|
108
|
+
e?.preventDefault()
|
|
109
|
+
|
|
110
|
+
if (!input.trim() || !ragSystem.canQuery) return
|
|
111
|
+
|
|
112
|
+
const userMessage: Message = {
|
|
113
|
+
id: `user-${Date.now()}`,
|
|
114
|
+
type: 'user',
|
|
115
|
+
content: input.trim(),
|
|
116
|
+
timestamp: new Date()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
setMessages(prev => [...prev, userMessage])
|
|
120
|
+
setInput('')
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const response = await ragSystem.query(input.trim(), {
|
|
124
|
+
stream: enableStreaming,
|
|
125
|
+
includeMetadata: true
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
if (!enableStreaming) {
|
|
129
|
+
// Add assistant response for non-streaming
|
|
130
|
+
const assistantMessage: Message = {
|
|
131
|
+
id: `assistant-${Date.now()}`,
|
|
132
|
+
type: 'assistant',
|
|
133
|
+
content: response.response,
|
|
134
|
+
sources: response.sources,
|
|
135
|
+
confidence: response.confidence,
|
|
136
|
+
timestamp: new Date()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
setMessages(prev => [...prev, assistantMessage])
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error('Chat error:', error)
|
|
144
|
+
onError?.(error as Error)
|
|
145
|
+
|
|
146
|
+
const errorMessage: Message = {
|
|
147
|
+
id: `error-${Date.now()}`,
|
|
148
|
+
type: 'assistant',
|
|
149
|
+
content: 'I apologize, but I encountered an error processing your request. Please try again.',
|
|
150
|
+
timestamp: new Date()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
setMessages(prev => [...prev, errorMessage])
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const handleKeyPress = (e: React.KeyboardEvent) => {
|
|
158
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
159
|
+
e.preventDefault()
|
|
160
|
+
handleSubmit()
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const handleCopy = async (content: string) => {
|
|
165
|
+
try {
|
|
166
|
+
await navigator.clipboard.writeText(content)
|
|
167
|
+
toast.success('Copied to clipboard')
|
|
168
|
+
} catch (error) {
|
|
169
|
+
toast.error('Failed to copy')
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const handleFeedback = (messageId: string, feedback: 'positive' | 'negative') => {
|
|
174
|
+
// Implementation for feedback tracking
|
|
175
|
+
toast.success(`Feedback recorded: ${feedback}`)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const handleStop = () => {
|
|
179
|
+
ragSystem.cancelOperation()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const renderMessage = (message: Message) => {
|
|
183
|
+
const isUser = message.type === 'user'
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<motion.div
|
|
187
|
+
key={message.id}
|
|
188
|
+
initial={{ opacity: 0, y: 20 }}
|
|
189
|
+
animate={{ opacity: 1, y: 0 }}
|
|
190
|
+
className={`flex ${isUser ? 'justify-end' : 'justify-start'} mb-4`}
|
|
191
|
+
>
|
|
192
|
+
<div className={`max-w-[80%] ${isUser ? 'order-2' : 'order-1'}`}>
|
|
193
|
+
{/* Message bubble */}
|
|
194
|
+
<div
|
|
195
|
+
className={`rounded-2xl px-4 py-3 ${
|
|
196
|
+
isUser
|
|
197
|
+
? 'bg-blue-500 text-white'
|
|
198
|
+
: 'bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700'
|
|
199
|
+
}`}
|
|
200
|
+
>
|
|
201
|
+
<div className="whitespace-pre-wrap">
|
|
202
|
+
{message.content}
|
|
203
|
+
{message.isStreaming && (
|
|
204
|
+
<span className="inline-block w-2 h-5 bg-current animate-pulse ml-1" />
|
|
205
|
+
)}
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
{/* Confidence score */}
|
|
209
|
+
{!isUser && showConfidence && message.confidence !== undefined && (
|
|
210
|
+
<div className="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
|
211
|
+
Confidence: {Math.round(message.confidence * 100)}%
|
|
212
|
+
</div>
|
|
213
|
+
)}
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
{/* Sources */}
|
|
217
|
+
{!isUser && showSources && message.sources && message.sources.length > 0 && (
|
|
218
|
+
<div className="mt-3 space-y-2">
|
|
219
|
+
<p className="text-xs font-medium text-gray-600 dark:text-gray-400">
|
|
220
|
+
Sources:
|
|
221
|
+
</p>
|
|
222
|
+
<div className="space-y-1">
|
|
223
|
+
{message.sources.slice(0, 3).map((source, index) => (
|
|
224
|
+
<div
|
|
225
|
+
key={index}
|
|
226
|
+
className="flex items-center gap-2 text-xs p-2 bg-gray-50 dark:bg-gray-800/50 rounded-lg"
|
|
227
|
+
>
|
|
228
|
+
<FileText className="w-3 h-3 text-gray-400" />
|
|
229
|
+
<span className="flex-1 truncate">{source.title || source.content}</span>
|
|
230
|
+
{source.score && (
|
|
231
|
+
<span className="text-gray-500">
|
|
232
|
+
{Math.round(source.score * 100)}%
|
|
233
|
+
</span>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
))}
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
)}
|
|
240
|
+
|
|
241
|
+
{/* Message actions */}
|
|
242
|
+
{!isUser && !message.isStreaming && (
|
|
243
|
+
<div className="flex items-center gap-1 mt-2">
|
|
244
|
+
<Button
|
|
245
|
+
variant="ghost"
|
|
246
|
+
size="sm"
|
|
247
|
+
onClick={() => handleCopy(message.content)}
|
|
248
|
+
className="h-6 px-2"
|
|
249
|
+
>
|
|
250
|
+
<Copy className="w-3 h-3" />
|
|
251
|
+
</Button>
|
|
252
|
+
<Button
|
|
253
|
+
variant="ghost"
|
|
254
|
+
size="sm"
|
|
255
|
+
onClick={() => handleFeedback(message.id, 'positive')}
|
|
256
|
+
className="h-6 px-2"
|
|
257
|
+
>
|
|
258
|
+
<ThumbsUp className="w-3 h-3" />
|
|
259
|
+
</Button>
|
|
260
|
+
<Button
|
|
261
|
+
variant="ghost"
|
|
262
|
+
size="sm"
|
|
263
|
+
onClick={() => handleFeedback(message.id, 'negative')}
|
|
264
|
+
className="h-6 px-2"
|
|
265
|
+
>
|
|
266
|
+
<ThumbsDown className="w-3 h-3" />
|
|
267
|
+
</Button>
|
|
268
|
+
</div>
|
|
269
|
+
)}
|
|
270
|
+
|
|
271
|
+
{/* Timestamp */}
|
|
272
|
+
<div className={`text-xs text-gray-500 mt-1 ${isUser ? 'text-right' : 'text-left'}`}>
|
|
273
|
+
{message.timestamp.toLocaleTimeString()}
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</motion.div>
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<div className={`flex flex-col h-full ${className}`}>
|
|
282
|
+
{/* Header */}
|
|
283
|
+
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
|
|
284
|
+
<div className="flex items-center gap-2">
|
|
285
|
+
<Sparkles className="w-5 h-5 text-blue-500" />
|
|
286
|
+
<h3 className="font-semibold">AI Assistant</h3>
|
|
287
|
+
<div className={`w-2 h-2 rounded-full ${
|
|
288
|
+
ragSystem.isReady ? 'bg-green-500' : 'bg-yellow-500'
|
|
289
|
+
}`} />
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
<div className="flex items-center gap-2">
|
|
293
|
+
{ragSystem.streamingState.isStreaming && (
|
|
294
|
+
<Button
|
|
295
|
+
variant="outline"
|
|
296
|
+
size="sm"
|
|
297
|
+
onClick={handleStop}
|
|
298
|
+
className="text-red-500 border-red-200 hover:bg-red-50"
|
|
299
|
+
>
|
|
300
|
+
<StopCircle className="w-4 h-4 mr-1" />
|
|
301
|
+
Stop
|
|
302
|
+
</Button>
|
|
303
|
+
)}
|
|
304
|
+
|
|
305
|
+
<Button
|
|
306
|
+
variant="outline"
|
|
307
|
+
size="sm"
|
|
308
|
+
onClick={() => setMessages([])}
|
|
309
|
+
disabled={ragSystem.isLoading}
|
|
310
|
+
>
|
|
311
|
+
<RefreshCw className="w-4 h-4" />
|
|
312
|
+
</Button>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
{/* Messages */}
|
|
317
|
+
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
|
318
|
+
<AnimatePresence>
|
|
319
|
+
{messages.length === 0 ? (
|
|
320
|
+
<div className="text-center text-gray-500 dark:text-gray-400 mt-8">
|
|
321
|
+
<Sparkles className="w-12 h-12 mx-auto mb-4 text-gray-300" />
|
|
322
|
+
<p className="text-lg font-medium mb-2">Welcome to your AI Knowledge Assistant</p>
|
|
323
|
+
<p className="text-sm">
|
|
324
|
+
Ask me anything about your knowledge base. I can help you find information,
|
|
325
|
+
analyze content, and provide insights.
|
|
326
|
+
</p>
|
|
327
|
+
</div>
|
|
328
|
+
) : (
|
|
329
|
+
messages.map(renderMessage)
|
|
330
|
+
)}
|
|
331
|
+
</AnimatePresence>
|
|
332
|
+
|
|
333
|
+
<div ref={messagesEndRef} />
|
|
334
|
+
</div>
|
|
335
|
+
|
|
336
|
+
{/* Input */}
|
|
337
|
+
<div className="p-4 border-t border-gray-200 dark:border-gray-700">
|
|
338
|
+
<form onSubmit={handleSubmit} className="flex gap-2">
|
|
339
|
+
<div className="flex-1 relative">
|
|
340
|
+
<textarea
|
|
341
|
+
ref={inputRef}
|
|
342
|
+
value={input}
|
|
343
|
+
onChange={(e) => setInput(e.target.value)}
|
|
344
|
+
onKeyPress={handleKeyPress}
|
|
345
|
+
placeholder={ragSystem.isReady ? "Ask me anything..." : "Initializing..."}
|
|
346
|
+
disabled={!ragSystem.canQuery}
|
|
347
|
+
className="w-full resize-none rounded-xl border border-gray-200 dark:border-gray-700 px-4 py-3 pr-12 focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 max-h-32"
|
|
348
|
+
rows={1}
|
|
349
|
+
/>
|
|
350
|
+
|
|
351
|
+
{/* Voice input button */}
|
|
352
|
+
<Button
|
|
353
|
+
type="button"
|
|
354
|
+
variant="ghost"
|
|
355
|
+
size="sm"
|
|
356
|
+
className="absolute right-2 top-1/2 transform -translate-y-1/2"
|
|
357
|
+
onClick={() => setIsRecording(!isRecording)}
|
|
358
|
+
disabled={!ragSystem.canQuery}
|
|
359
|
+
>
|
|
360
|
+
{isRecording ? (
|
|
361
|
+
<MicOff className="w-4 h-4 text-red-500" />
|
|
362
|
+
) : (
|
|
363
|
+
<Mic className="w-4 h-4" />
|
|
364
|
+
)}
|
|
365
|
+
</Button>
|
|
366
|
+
</div>
|
|
367
|
+
|
|
368
|
+
<Button
|
|
369
|
+
type="submit"
|
|
370
|
+
disabled={!input.trim() || !ragSystem.canQuery || ragSystem.isLoading}
|
|
371
|
+
className="px-4 py-3"
|
|
372
|
+
>
|
|
373
|
+
{ragSystem.isLoading ? (
|
|
374
|
+
<Loader2 className="w-4 h-4 animate-spin" />
|
|
375
|
+
) : (
|
|
376
|
+
<Send className="w-4 h-4" />
|
|
377
|
+
)}
|
|
378
|
+
</Button>
|
|
379
|
+
</form>
|
|
380
|
+
|
|
381
|
+
{/* Status indicator */}
|
|
382
|
+
<div className="flex items-center justify-between mt-2 text-xs text-gray-500">
|
|
383
|
+
<span>
|
|
384
|
+
{ragSystem.totalDocuments} documents • {ragSystem.queryHistory.length} recent queries
|
|
385
|
+
</span>
|
|
386
|
+
<span>
|
|
387
|
+
Press Enter to send, Shift+Enter for new line
|
|
388
|
+
</span>
|
|
389
|
+
</div>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
)
|
|
393
|
+
}
|