@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,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
+ }