wu981526092 commited on
Commit
1425cf0
·
1 Parent(s): c1f7879

Refactored Playground.tsx into modular components + Assistant-specific documents

Browse files
frontend/src/components/playground/AssistantSelector.tsx ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Button } from '@/components/ui/button'
2
+ import { Label } from '@/components/ui/label'
3
+ import { Badge } from '@/components/ui/badge'
4
+ import {
5
+ Select,
6
+ SelectContent,
7
+ SelectGroup,
8
+ SelectItem,
9
+ SelectLabel,
10
+ SelectTrigger,
11
+ SelectValue,
12
+ } from '@/components/ui/select'
13
+ import { Bot, Plus, X, User, Bookmark, Save, Settings } from 'lucide-react'
14
+
15
+ interface SavedAssistant {
16
+ id: string
17
+ name: string
18
+ systemPrompt: string
19
+ temperature: number
20
+ maxTokens: number
21
+ model?: string
22
+ ragEnabled?: boolean
23
+ retrievalCount?: number
24
+ createdAt: string
25
+ }
26
+
27
+ interface SystemPromptPreset {
28
+ name: string
29
+ prompt: string
30
+ }
31
+
32
+ interface SelectedAssistant {
33
+ id: string
34
+ name: string
35
+ type: 'user'|'template'|'new'
36
+ originalTemplate?: string
37
+ }
38
+
39
+ interface AssistantSelectorProps {
40
+ savedAssistants: SavedAssistant[]
41
+ loadSavedAssistant: (id: string) => void
42
+ openSaveDialog: () => void
43
+ presets: SystemPromptPreset[]
44
+ onPresetSelect: (presetName: string) => void
45
+ isLoading: boolean
46
+ selectedAssistant: SelectedAssistant | null
47
+ createNewAssistant: () => void
48
+ clearCurrentAssistant: () => void
49
+ openRenameDialog: () => void
50
+ systemPrompt: string
51
+ }
52
+
53
+ export function AssistantSelector({
54
+ savedAssistants,
55
+ loadSavedAssistant,
56
+ openSaveDialog,
57
+ presets,
58
+ onPresetSelect,
59
+ isLoading,
60
+ selectedAssistant,
61
+ createNewAssistant,
62
+ clearCurrentAssistant,
63
+ openRenameDialog,
64
+ systemPrompt
65
+ }: AssistantSelectorProps) {
66
+ const handleAssistantSelect = (value: string) => {
67
+ if (value === 'create_new') {
68
+ createNewAssistant()
69
+ } else if (value === 'clear_current') {
70
+ clearCurrentAssistant()
71
+ } else if (value.startsWith('user_')) {
72
+ // Check if it's a saved assistant (starts with user_)
73
+ const assistantId = value.replace('user_', '')
74
+ loadSavedAssistant(assistantId)
75
+ } else {
76
+ // It's a template preset
77
+ onPresetSelect(value)
78
+ }
79
+ }
80
+
81
+ // Get current selection value for the Select component
82
+ const getCurrentValue = () => {
83
+ if (!selectedAssistant) return ''
84
+
85
+ if (selectedAssistant.type === 'user') {
86
+ return `user_${selectedAssistant.id}`
87
+ } else if (selectedAssistant.type === 'new') {
88
+ return 'new_assistant'
89
+ } else {
90
+ return selectedAssistant.id
91
+ }
92
+ }
93
+
94
+ // Get badge for assistant type
95
+ const getAssistantBadge = (type: 'user'|'template'|'new') => {
96
+ switch (type) {
97
+ case 'user':
98
+ return { variant: 'secondary' as const, text: 'Mine' }
99
+ case 'template':
100
+ return { variant: 'outline' as const, text: 'Template' }
101
+ case 'new':
102
+ return { variant: 'default' as const, text: 'New', className: 'bg-green-100 text-green-700' }
103
+ }
104
+ }
105
+
106
+ const totalAssistants = savedAssistants.length + presets.length
107
+
108
+ return (
109
+ <div className="space-y-4">
110
+ <div>
111
+ <Label className="text-sm font-medium mb-2 block">Load Assistant</Label>
112
+ <Select value={getCurrentValue()} onValueChange={handleAssistantSelect}>
113
+ <SelectTrigger className="w-full">
114
+ <SelectValue placeholder={`Choose assistant (${totalAssistants} available)`}>
115
+ {selectedAssistant ? (
116
+ <div className="flex items-center gap-2">
117
+ <Bot className="h-4 w-4" />
118
+ <span>{selectedAssistant.name}</span>
119
+ {selectedAssistant.originalTemplate && (
120
+ <span className="text-xs text-muted-foreground">
121
+ (from {selectedAssistant.originalTemplate})
122
+ </span>
123
+ )}
124
+ <Badge
125
+ variant={getAssistantBadge(selectedAssistant.type).variant}
126
+ className={`text-xs ml-auto ${getAssistantBadge(selectedAssistant.type).className || ''}`}
127
+ >
128
+ {getAssistantBadge(selectedAssistant.type).text}
129
+ </Badge>
130
+ </div>
131
+ ) : (
132
+ <div className="flex items-center gap-2">
133
+ <Bot className="h-4 w-4" />
134
+ <span>Choose assistant</span>
135
+ <Badge variant="outline" className="text-xs ml-auto">
136
+ {totalAssistants}
137
+ </Badge>
138
+ </div>
139
+ )}
140
+ </SelectValue>
141
+ </SelectTrigger>
142
+ <SelectContent>
143
+ {/* Actions Section */}
144
+ <SelectGroup>
145
+ <SelectLabel className="flex items-center gap-2 text-sm font-medium">
146
+ <Plus className="h-4 w-4" />
147
+ Actions
148
+ </SelectLabel>
149
+ <SelectItem value="create_new">
150
+ <div className="flex items-center gap-2 w-full">
151
+ <Plus className="h-4 w-4 text-green-600" />
152
+ <span className="truncate flex-1">Create New Assistant</span>
153
+ <Badge variant="default" className="text-xs bg-green-100 text-green-700">New</Badge>
154
+ </div>
155
+ </SelectItem>
156
+ {selectedAssistant && (
157
+ <SelectItem value="clear_current">
158
+ <div className="flex items-center gap-2 w-full">
159
+ <X className="h-4 w-4 text-gray-600" />
160
+ <span className="truncate flex-1">Clear Current</span>
161
+ <Badge variant="outline" className="text-xs">Clear</Badge>
162
+ </div>
163
+ </SelectItem>
164
+ )}
165
+ </SelectGroup>
166
+
167
+ {/* My Assistants Section */}
168
+ {savedAssistants.length > 0 && (
169
+ <SelectGroup>
170
+ <SelectLabel className="flex items-center gap-2 text-sm font-medium">
171
+ <User className="h-4 w-4" />
172
+ My Assistants ({savedAssistants.length})
173
+ </SelectLabel>
174
+ {savedAssistants.map((assistant) => (
175
+ <SelectItem key={`user_${assistant.id}`} value={`user_${assistant.id}`}>
176
+ <div className="flex items-center gap-2 w-full">
177
+ <Bot className="h-4 w-4 text-blue-600" />
178
+ <span className="truncate flex-1">{assistant.name}</span>
179
+ <Badge variant="secondary" className="text-xs">Mine</Badge>
180
+ </div>
181
+ </SelectItem>
182
+ ))}
183
+ </SelectGroup>
184
+ )}
185
+
186
+ {/* Templates Section */}
187
+ {presets.length > 0 && (
188
+ <SelectGroup>
189
+ <SelectLabel className="flex items-center gap-2 text-sm font-medium">
190
+ <Bookmark className="h-4 w-4" />
191
+ Templates ({presets.length})
192
+ </SelectLabel>
193
+ {presets.map((preset) => (
194
+ <SelectItem key={preset.name} value={preset.name}>
195
+ <div className="flex items-center gap-2 w-full">
196
+ <Bot className="h-4 w-4 text-green-600" />
197
+ <span className="truncate flex-1">{preset.name}</span>
198
+ <Badge variant="outline" className="text-xs">Template</Badge>
199
+ </div>
200
+ </SelectItem>
201
+ ))}
202
+ </SelectGroup>
203
+ )}
204
+
205
+ {/* Empty State */}
206
+ {totalAssistants === 0 && (
207
+ <div className="p-2 text-sm text-muted-foreground text-center">
208
+ No assistants available
209
+ </div>
210
+ )}
211
+ </SelectContent>
212
+ </Select>
213
+ </div>
214
+
215
+ {/* Rename Assistant */}
216
+ {selectedAssistant && selectedAssistant.type !== 'template' && (
217
+ <Button
218
+ onClick={openRenameDialog}
219
+ size="sm"
220
+ variant="ghost"
221
+ className="w-full justify-start"
222
+ disabled={isLoading}
223
+ >
224
+ <Settings className="h-4 w-4 mr-2" />
225
+ Rename
226
+ </Button>
227
+ )}
228
+
229
+ {/* Save Current Configuration */}
230
+ <Button
231
+ onClick={openSaveDialog}
232
+ size="sm"
233
+ variant="outline"
234
+ className="w-full"
235
+ disabled={isLoading || !systemPrompt.trim()}
236
+ >
237
+ <Save className="h-4 w-4 mr-2" />
238
+ Save Assistant
239
+ </Button>
240
+ </div>
241
+ )
242
+ }
frontend/src/components/playground/DocumentsTab.tsx ADDED
@@ -0,0 +1,382 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react'
2
+ import { Button } from '@/components/ui/button'
3
+ import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
4
+ import { Slider } from '@/components/ui/slider'
5
+ import { Label } from '@/components/ui/label'
6
+ import { User, Upload, FileText, File, X } from 'lucide-react'
7
+
8
+ interface UploadedFile {
9
+ id: string
10
+ name: string
11
+ size: number
12
+ type: string
13
+ uploadedAt: string
14
+ status: string
15
+ chunks?: number
16
+ assistantId?: string | null
17
+ }
18
+
19
+ interface SelectedAssistant {
20
+ id: string
21
+ name: string
22
+ type: 'user'|'template'|'new'
23
+ originalTemplate?: string
24
+ }
25
+
26
+ interface DocumentsTabProps {
27
+ isLoading: boolean
28
+ ragEnabled: boolean
29
+ setRagEnabled: (enabled: boolean) => void
30
+ retrievalCount: number
31
+ setRetrievalCount: (count: number) => void
32
+ currentAssistant: SelectedAssistant | null
33
+ }
34
+
35
+ export function DocumentsTab({
36
+ isLoading,
37
+ ragEnabled,
38
+ setRagEnabled,
39
+ retrievalCount,
40
+ setRetrievalCount,
41
+ currentAssistant
42
+ }: DocumentsTabProps) {
43
+ const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([])
44
+ const [isUploading, setIsUploading] = useState(false)
45
+
46
+ // Load assistant-specific documents when assistant changes
47
+ useEffect(() => {
48
+ const loadDocuments = async () => {
49
+ if (!currentAssistant) {
50
+ setUploadedFiles([])
51
+ return
52
+ }
53
+
54
+ try {
55
+ // For now, load all documents but filter by assistant in the future
56
+ const response = await fetch('/rag/documents')
57
+ if (response.ok) {
58
+ const data = await response.json()
59
+ if (data.documents) {
60
+ const documentList = Object.entries(data.documents).map(([docId, docInfo]: [string, any]) => ({
61
+ id: docId,
62
+ name: docInfo.filename,
63
+ size: 0,
64
+ type: docInfo.file_type,
65
+ uploadedAt: new Date().toISOString(),
66
+ status: docInfo.status,
67
+ chunks: docInfo.chunks,
68
+ assistantId: docInfo.assistant_id || null // Future: filter by assistant
69
+ })) as UploadedFile[]
70
+
71
+ // TODO: Filter documents by currentAssistant.id when backend supports it
72
+ setUploadedFiles(documentList)
73
+ }
74
+ }
75
+ } catch (error) {
76
+ console.error('Error loading documents:', error)
77
+ }
78
+ }
79
+
80
+ loadDocuments()
81
+ }, [currentAssistant])
82
+
83
+ const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
84
+ const files = event.target.files
85
+ if (!files || !currentAssistant) return
86
+
87
+ setIsUploading(true)
88
+
89
+ try {
90
+ const formData = new FormData()
91
+
92
+ // Add assistant ID to the upload
93
+ formData.append('assistant_id', currentAssistant.id)
94
+
95
+ for (const file of Array.from(files)) {
96
+ formData.append('files', file)
97
+ }
98
+
99
+ const response = await fetch('/rag/upload', {
100
+ method: 'POST',
101
+ body: formData,
102
+ })
103
+
104
+ if (response.ok) {
105
+ const result = await response.json()
106
+
107
+ // Add successfully processed files to the list
108
+ const newFiles = result.results
109
+ .filter((r: any) => r.success)
110
+ .map((r: any) => ({
111
+ id: r.doc_id,
112
+ name: r.filename,
113
+ size: 0, // Server doesn't return size currently
114
+ type: 'processed',
115
+ uploadedAt: new Date().toISOString(),
116
+ status: 'processed',
117
+ chunks: r.chunks,
118
+ assistantId: currentAssistant?.id
119
+ })) as UploadedFile[]
120
+
121
+ setUploadedFiles((prev: UploadedFile[]) => [...prev, ...newFiles])
122
+
123
+ // Show errors for failed uploads
124
+ const failedUploads = result.results.filter((r: any) => !r.success)
125
+ if (failedUploads.length > 0) {
126
+ console.error('Some files failed to upload:', failedUploads)
127
+ }
128
+ } else {
129
+ console.error('Upload failed:', response.statusText)
130
+ }
131
+
132
+ // Reset input
133
+ event.target.value = ''
134
+ } catch (error) {
135
+ console.error('File upload error:', error)
136
+ } finally {
137
+ setIsUploading(false)
138
+ }
139
+ }
140
+
141
+ const removeFile = async (fileId: string) => {
142
+ try {
143
+ const response = await fetch(`/rag/documents/${fileId}`, {
144
+ method: 'DELETE',
145
+ })
146
+
147
+ if (response.ok) {
148
+ setUploadedFiles((prev: UploadedFile[]) => prev.filter((f: UploadedFile) => f.id !== fileId))
149
+ } else {
150
+ console.error('Failed to delete document:', response.statusText)
151
+ }
152
+ } catch (error) {
153
+ console.error('Error deleting document:', error)
154
+ // Remove from UI anyway
155
+ setUploadedFiles((prev: UploadedFile[]) => prev.filter((f: UploadedFile) => f.id !== fileId))
156
+ }
157
+ }
158
+
159
+ const formatFileSize = (bytes: number) => {
160
+ if (bytes === 0) return '0 Bytes'
161
+ const k = 1024
162
+ const sizes = ['Bytes', 'KB', 'MB', 'GB']
163
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
164
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
165
+ }
166
+
167
+ return (
168
+ <div className="space-y-4 pb-6">
169
+ {/* Current Assistant Info */}
170
+ {currentAssistant && (
171
+ <Card>
172
+ <CardHeader>
173
+ <CardTitle className="text-sm">Current Assistant</CardTitle>
174
+ </CardHeader>
175
+ <CardContent>
176
+ <div className="flex items-center space-x-2">
177
+ <User className="h-4 w-4 text-blue-600" />
178
+ <span className="font-medium">{currentAssistant.name}</span>
179
+ <span className={`inline-block px-2 py-1 rounded text-xs font-medium ${
180
+ currentAssistant.type === 'user' ? 'bg-blue-100 text-blue-700' :
181
+ currentAssistant.type === 'template' ? 'bg-gray-100 text-gray-700' :
182
+ 'bg-green-100 text-green-700'
183
+ }`}>
184
+ {currentAssistant.type === 'user' ? 'My Assistant' :
185
+ currentAssistant.type === 'template' ? 'Template' : 'New Assistant'}
186
+ </span>
187
+ {currentAssistant.originalTemplate && (
188
+ <span className="text-xs text-muted-foreground">
189
+ (from {currentAssistant.originalTemplate})
190
+ </span>
191
+ )}
192
+ </div>
193
+ </CardContent>
194
+ </Card>
195
+ )}
196
+
197
+ {/* RAG Configuration */}
198
+ <Card>
199
+ <CardHeader>
200
+ <CardTitle className="text-base">
201
+ RAG Configuration
202
+ {currentAssistant && (
203
+ <span className="text-xs text-muted-foreground font-normal ml-2">
204
+ for "{currentAssistant.name}"
205
+ </span>
206
+ )}
207
+ </CardTitle>
208
+ </CardHeader>
209
+ <CardContent>
210
+ <div className="space-y-4">
211
+ <div className="flex items-center justify-between">
212
+ <div className="space-y-1">
213
+ <Label className="text-sm font-medium">Enable RAG</Label>
214
+ <p className="text-xs text-muted-foreground">
215
+ Use uploaded documents to enhance responses
216
+ </p>
217
+ </div>
218
+ <div className="flex items-center space-x-2">
219
+ <input
220
+ type="checkbox"
221
+ id="rag-enabled"
222
+ checked={ragEnabled}
223
+ onChange={(e) => setRagEnabled(e.target.checked)}
224
+ className="rounded"
225
+ disabled={isLoading || uploadedFiles.length === 0}
226
+ />
227
+ <Label htmlFor="rag-enabled" className="text-sm">
228
+ {ragEnabled ? 'On' : 'Off'}
229
+ </Label>
230
+ </div>
231
+ </div>
232
+
233
+ {ragEnabled && (
234
+ <div className="space-y-3 border-t pt-4">
235
+ <div>
236
+ <Label className="text-sm font-medium mb-2 block">
237
+ Retrieval Count: {retrievalCount}
238
+ </Label>
239
+ <Slider
240
+ value={[retrievalCount]}
241
+ onValueChange={(value) => setRetrievalCount(value[0])}
242
+ max={10}
243
+ min={1}
244
+ step={1}
245
+ className="w-full"
246
+ />
247
+ <div className="flex justify-between text-xs text-muted-foreground mt-1">
248
+ <span>1 (Focused)</span>
249
+ <span>5 (Balanced)</span>
250
+ <span>10 (Comprehensive)</span>
251
+ </div>
252
+ <p className="text-xs text-muted-foreground mt-2">
253
+ Number of relevant document chunks to retrieve for context
254
+ </p>
255
+ </div>
256
+ </div>
257
+ )}
258
+ </div>
259
+ </CardContent>
260
+ </Card>
261
+
262
+ {/* File Upload */}
263
+ <Card>
264
+ <CardHeader>
265
+ <CardTitle className="text-base flex items-center">
266
+ <Upload className="h-4 w-4 mr-2" />
267
+ Upload Documents
268
+ </CardTitle>
269
+ </CardHeader>
270
+ <CardContent>
271
+ <div className="space-y-4">
272
+ <div className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-gray-400 transition-colors">
273
+ <input
274
+ type="file"
275
+ id="file-upload"
276
+ multiple
277
+ accept=".pdf,.txt,.docx,.md"
278
+ onChange={handleFileUpload}
279
+ className="hidden"
280
+ disabled={isUploading || isLoading || !currentAssistant}
281
+ />
282
+ <Label
283
+ htmlFor="file-upload"
284
+ className="cursor-pointer flex flex-col items-center space-y-2"
285
+ >
286
+ <Upload className="h-8 w-8 text-gray-400" />
287
+ <span className="text-sm font-medium">
288
+ {isUploading ? 'Uploading...' :
289
+ !currentAssistant ? 'Select an assistant first' :
290
+ 'Click to upload documents'}
291
+ </span>
292
+ <span className="text-xs text-muted-foreground">
293
+ {currentAssistant ? 'PDF, TXT, DOCX, MD files supported' :
294
+ 'Documents will be specific to the selected assistant'}
295
+ </span>
296
+ </Label>
297
+ </div>
298
+
299
+ <div className="text-xs text-muted-foreground bg-blue-50 p-3 rounded">
300
+ <strong>💡 How it works:</strong>
301
+ <br />
302
+ • Upload documents specific to this assistant's domain
303
+ • Each assistant maintains its own knowledge base
304
+ • Enable RAG to use these documents as context
305
+ • The AI will search relevant information to answer questions
306
+ {!currentAssistant && (
307
+ <>
308
+ <br />
309
+ <strong>⚠️ Note:</strong> Select an assistant first to upload documents
310
+ </>
311
+ )}
312
+ </div>
313
+ </div>
314
+ </CardContent>
315
+ </Card>
316
+
317
+ {/* Uploaded Files */}
318
+ {uploadedFiles.length > 0 && (
319
+ <Card>
320
+ <CardHeader>
321
+ <CardTitle className="text-base flex items-center">
322
+ <FileText className="h-4 w-4 mr-2" />
323
+ {currentAssistant ?
324
+ `${currentAssistant.name}'s Documents (${uploadedFiles.length})` :
325
+ `Documents (${uploadedFiles.length})`
326
+ }
327
+ </CardTitle>
328
+ </CardHeader>
329
+ <CardContent>
330
+ <div className="space-y-2">
331
+ {uploadedFiles.map((file) => (
332
+ <div
333
+ key={file.id}
334
+ className="flex items-center justify-between p-3 border rounded-lg bg-gray-50"
335
+ >
336
+ <div className="flex items-center space-x-3">
337
+ <File className="h-4 w-4 text-blue-600" />
338
+ <div className="flex-1">
339
+ <p className="text-sm font-medium truncate max-w-[200px]">
340
+ {file.name}
341
+ </p>
342
+ <p className="text-xs text-muted-foreground">
343
+ {formatFileSize(file.size)} • {file.status}
344
+ </p>
345
+ </div>
346
+ </div>
347
+ <Button
348
+ size="sm"
349
+ variant="ghost"
350
+ onClick={() => removeFile(file.id)}
351
+ disabled={isLoading}
352
+ className="text-red-600 hover:text-red-700 hover:bg-red-50"
353
+ >
354
+ <X className="h-4 w-4" />
355
+ </Button>
356
+ </div>
357
+ ))}
358
+ </div>
359
+ </CardContent>
360
+ </Card>
361
+ )}
362
+
363
+ {/* RAG Status */}
364
+ {ragEnabled && uploadedFiles.length > 0 && (
365
+ <Card>
366
+ <CardContent className="pt-6">
367
+ <div className="flex items-center space-x-2 text-green-600">
368
+ <div className="w-2 h-2 bg-green-500 rounded-full"></div>
369
+ <span className="text-sm font-medium">
370
+ RAG Active - Using {uploadedFiles.length} document(s)
371
+ {currentAssistant && ` for "${currentAssistant.name}"`}
372
+ </span>
373
+ </div>
374
+ <p className="text-xs text-muted-foreground mt-1">
375
+ {currentAssistant?.name || 'This assistant'} will use context from uploaded documents to enhance responses
376
+ </p>
377
+ </CardContent>
378
+ </Card>
379
+ )}
380
+ </div>
381
+ )
382
+ }
frontend/src/components/playground/ModelParametersTab.tsx ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
2
+ import { Slider } from '@/components/ui/slider'
3
+ import { Label } from '@/components/ui/label'
4
+ import { Badge } from '@/components/ui/badge'
5
+ import {
6
+ Select,
7
+ SelectContent,
8
+ SelectGroup,
9
+ SelectItem,
10
+ SelectLabel,
11
+ SelectTrigger,
12
+ SelectValue,
13
+ } from '@/components/ui/select'
14
+ import { Cloud, Brain, Zap } from 'lucide-react'
15
+
16
+ interface ModelInfo {
17
+ model_name: string
18
+ name: string
19
+ supports_thinking: boolean
20
+ description: string
21
+ size_gb: string
22
+ is_loaded: boolean
23
+ type: 'local' | 'api'
24
+ }
25
+
26
+ interface ModelParametersTabProps {
27
+ models: ModelInfo[]
28
+ selectedModel: string | null
29
+ setSelectedModel: (model: string) => void
30
+ autoLoadingModel: string | null
31
+ temperature: number
32
+ setTemperature: (temp: number) => void
33
+ maxTokens: number
34
+ setMaxTokens: (tokens: number) => void
35
+ }
36
+
37
+ export function ModelParametersTab({
38
+ models,
39
+ selectedModel,
40
+ setSelectedModel,
41
+ autoLoadingModel,
42
+ temperature,
43
+ setTemperature,
44
+ maxTokens,
45
+ setMaxTokens
46
+ }: ModelParametersTabProps) {
47
+ return (
48
+ <>
49
+ {/* Model Selection */}
50
+ <Card>
51
+ <CardHeader>
52
+ <CardTitle className="text-base">Model</CardTitle>
53
+ </CardHeader>
54
+ <CardContent className="space-y-4">
55
+ <div>
56
+ <Select value={selectedModel || ""} onValueChange={setSelectedModel}>
57
+ <SelectTrigger className="w-full">
58
+ <SelectValue placeholder="Select a model...">
59
+ {selectedModel && (() => {
60
+ const model = models.find(m => m.model_name === selectedModel)
61
+ if (!model) return selectedModel
62
+ const isApiModel = model.type === 'api'
63
+ return (
64
+ <div className="flex items-center gap-2">
65
+ {isApiModel ? (
66
+ <Cloud className="h-4 w-4 text-blue-500" />
67
+ ) : model.supports_thinking ? (
68
+ <Brain className="h-4 w-4 text-purple-500" />
69
+ ) : (
70
+ <Zap className="h-4 w-4 text-green-500" />
71
+ )}
72
+ <span className="truncate">{model.name}</span>
73
+ {autoLoadingModel === selectedModel ? (
74
+ <Badge variant="outline" className="text-xs">
75
+ Loading...
76
+ </Badge>
77
+ ) : (
78
+ <Badge variant="outline" className="text-xs">
79
+ {isApiModel ? "API" : model.is_loaded ? "Loaded" : "Available"}
80
+ </Badge>
81
+ )}
82
+ </div>
83
+ )
84
+ })()}
85
+ </SelectValue>
86
+ </SelectTrigger>
87
+ <SelectContent>
88
+ <SelectGroup>
89
+ <SelectLabel>🌐 API Models</SelectLabel>
90
+ {models.filter(m => m.type === 'api').map((model) => (
91
+ <SelectItem key={model.model_name} value={model.model_name}>
92
+ <div className="flex items-center gap-2">
93
+ <Cloud className="h-4 w-4 text-blue-500" />
94
+ <span>{model.name}</span>
95
+ <Badge variant="outline" className="text-xs bg-blue-50">API</Badge>
96
+ </div>
97
+ </SelectItem>
98
+ ))}
99
+ </SelectGroup>
100
+ <SelectGroup>
101
+ <SelectLabel>💻 Local Models</SelectLabel>
102
+ {models.filter(m => m.type === 'local').map((model) => (
103
+ <SelectItem key={model.model_name} value={model.model_name}>
104
+ <div className="flex items-center gap-2">
105
+ {model.supports_thinking ? (
106
+ <Brain className="h-4 w-4 text-purple-500" />
107
+ ) : (
108
+ <Zap className="h-4 w-4 text-green-500" />
109
+ )}
110
+ <span>{model.name}</span>
111
+ {autoLoadingModel === model.model_name ? (
112
+ <Badge variant="outline" className="text-xs bg-yellow-50">Loading...</Badge>
113
+ ) : model.is_loaded ? (
114
+ <Badge variant="outline" className="text-xs bg-green-50">Loaded</Badge>
115
+ ) : (
116
+ <Badge variant="outline" className="text-xs bg-gray-50">Available</Badge>
117
+ )}
118
+ </div>
119
+ </SelectItem>
120
+ ))}
121
+ </SelectGroup>
122
+ </SelectContent>
123
+ </Select>
124
+ </div>
125
+ </CardContent>
126
+ </Card>
127
+
128
+ {/* Generation Parameters */}
129
+ <Card>
130
+ <CardHeader>
131
+ <CardTitle className="text-base">Parameters</CardTitle>
132
+ </CardHeader>
133
+ <CardContent className="space-y-6">
134
+ {/* Temperature Slider */}
135
+ <div>
136
+ <div className="flex items-center justify-between mb-3">
137
+ <Label className="text-sm font-medium">Temperature</Label>
138
+ <Badge variant="outline" className="text-xs">
139
+ {temperature}
140
+ </Badge>
141
+ </div>
142
+ <Slider
143
+ value={[temperature]}
144
+ onValueChange={(value) => setTemperature(value[0])}
145
+ max={2}
146
+ min={0}
147
+ step={0.1}
148
+ className="w-full mb-3"
149
+ />
150
+ <div className="flex justify-between text-xs text-gray-500 mb-2">
151
+ <span>0.0 (Focused)</span>
152
+ <span>1.0 (Balanced)</span>
153
+ <span>2.0 (Creative)</span>
154
+ </div>
155
+ <div className="text-xs text-muted-foreground bg-gray-50 p-2 rounded">
156
+ {temperature <= 0.3
157
+ ? "🎯 Very focused and deterministic responses"
158
+ : temperature <= 0.7
159
+ ? "⚖️ Balanced creativity and consistency"
160
+ : temperature <= 1.2
161
+ ? "🎨 More creative and varied responses"
162
+ : "🚀 Highly creative, unpredictable responses"
163
+ }
164
+ </div>
165
+ </div>
166
+
167
+ {/* Max Tokens Slider */}
168
+ <div>
169
+ <div className="flex items-center justify-between mb-3">
170
+ <Label className="text-sm font-medium">Max Tokens</Label>
171
+ <Badge variant="outline" className="text-xs">
172
+ {maxTokens}
173
+ </Badge>
174
+ </div>
175
+ <Slider
176
+ value={[maxTokens]}
177
+ onValueChange={(value) => setMaxTokens(value[0])}
178
+ max={4000}
179
+ min={50}
180
+ step={50}
181
+ className="w-full mb-3"
182
+ />
183
+ <div className="flex justify-between text-xs text-gray-500 mb-2">
184
+ <span>50 (Short)</span>
185
+ <span>1024 (Medium)</span>
186
+ <span>4000 (Long)</span>
187
+ </div>
188
+ <div className="text-xs text-muted-foreground bg-gray-50 p-2 rounded">
189
+ {maxTokens <= 200
190
+ ? "📝 Brief responses, good for quick answers"
191
+ : maxTokens <= 1000
192
+ ? "📄 Medium responses, balanced length"
193
+ : maxTokens <= 2000
194
+ ? "📋 Long responses, detailed explanations"
195
+ : "📚 Very long responses, comprehensive content"
196
+ }
197
+ </div>
198
+ </div>
199
+ </CardContent>
200
+ </Card>
201
+ </>
202
+ )
203
+ }
frontend/src/components/playground/SystemInstructionsTab.tsx ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
2
+ import { Label } from '@/components/ui/label'
3
+ import {
4
+ Select,
5
+ SelectContent,
6
+ SelectGroup,
7
+ SelectItem,
8
+ SelectLabel,
9
+ SelectTrigger,
10
+ SelectValue,
11
+ } from '@/components/ui/select'
12
+ import { Brain, MessageSquare } from 'lucide-react'
13
+
14
+ interface SystemInstructionsTabProps {
15
+ systemPrompt: string
16
+ setSystemPrompt: (prompt: string) => void
17
+ isLoading: boolean
18
+ }
19
+
20
+ export function SystemInstructionsTab({
21
+ systemPrompt,
22
+ setSystemPrompt,
23
+ isLoading
24
+ }: SystemInstructionsTabProps) {
25
+ const promptExamples = [
26
+ {
27
+ title: "Helpful Assistant",
28
+ prompt: "You are a helpful, harmless, and honest AI assistant. Provide clear, accurate, and well-structured responses. If you're unsure about something, say so."
29
+ },
30
+ {
31
+ title: "Code Expert",
32
+ prompt: "You are an expert software developer. Provide clean, efficient code with clear explanations. Always follow best practices and include helpful comments."
33
+ },
34
+ {
35
+ title: "Creative Writer",
36
+ prompt: "You are a creative writer. Use vivid language, engaging storytelling, and imaginative descriptions. Be expressive and artistic in your responses."
37
+ },
38
+ {
39
+ title: "Teacher/Tutor",
40
+ prompt: "You are a patient and knowledgeable teacher. Break down complex concepts into simple, understandable parts. Use examples and analogies to help explain ideas."
41
+ }
42
+ ]
43
+
44
+ const bestPractices = [
45
+ "Be specific about the assistant's role and expertise",
46
+ "Include desired communication style and tone",
47
+ "Specify output format if needed (lists, paragraphs, etc.)",
48
+ "Add ethical guidelines and behavior expectations"
49
+ ]
50
+
51
+ return (
52
+ <div className="space-y-4 pb-6">
53
+ <Card>
54
+ <CardHeader>
55
+ <CardTitle className="text-base">System Instructions</CardTitle>
56
+ </CardHeader>
57
+ <CardContent>
58
+ <div className="space-y-4">
59
+ <Label className="text-base font-medium text-blue-700">
60
+ System Prompt
61
+ </Label>
62
+ <textarea
63
+ value={systemPrompt}
64
+ onChange={(e) => setSystemPrompt(e.target.value)}
65
+ placeholder="Define your AI assistant's role, personality, and behavior..."
66
+ className="w-full h-[250px] text-base p-4 border-2 border-blue-200 rounded-lg bg-white focus:border-blue-400 focus:ring-2 focus:ring-blue-100 resize-none overflow-y-auto"
67
+ disabled={isLoading}
68
+ />
69
+
70
+ {/* Character count */}
71
+ <div className="text-xs text-muted-foreground text-right">
72
+ {systemPrompt.length} characters
73
+ </div>
74
+ </div>
75
+ </CardContent>
76
+ </Card>
77
+
78
+ {/* Quick Examples */}
79
+ <Card>
80
+ <CardHeader>
81
+ <CardTitle className="text-sm flex items-center">
82
+ <Brain className="h-4 w-4 mr-2" />
83
+ Quick Examples
84
+ </CardTitle>
85
+ </CardHeader>
86
+ <CardContent>
87
+ <div className="space-y-3">
88
+ <Label className="text-xs font-medium text-muted-foreground">
89
+ Choose a template to get started:
90
+ </Label>
91
+ <Select
92
+ value=""
93
+ onValueChange={(value) => {
94
+ const example = promptExamples[parseInt(value)]
95
+ if (example) {
96
+ setSystemPrompt(example.prompt)
97
+ }
98
+ }}
99
+ disabled={isLoading}
100
+ >
101
+ <SelectTrigger className="w-full h-10">
102
+ <SelectValue placeholder="Select example prompt..." />
103
+ </SelectTrigger>
104
+ <SelectContent>
105
+ <SelectGroup>
106
+ <SelectLabel>Example Prompts</SelectLabel>
107
+ {promptExamples.map((example, index) => (
108
+ <SelectItem key={index} value={index.toString()}>
109
+ <div className="flex flex-col items-start">
110
+ <span className="font-medium text-sm">{example.title}</span>
111
+ <span className="text-xs text-muted-foreground line-clamp-1 mt-1">
112
+ {example.prompt.substring(0, 60)}...
113
+ </span>
114
+ </div>
115
+ </SelectItem>
116
+ ))}
117
+ </SelectGroup>
118
+ </SelectContent>
119
+ </Select>
120
+ </div>
121
+ </CardContent>
122
+ </Card>
123
+
124
+ {/* Best Practices */}
125
+ <Card>
126
+ <CardHeader>
127
+ <CardTitle className="text-sm flex items-center">
128
+ <MessageSquare className="h-4 w-4 mr-2" />
129
+ Best Practices
130
+ </CardTitle>
131
+ </CardHeader>
132
+ <CardContent>
133
+ <ul className="text-xs space-y-2 text-muted-foreground">
134
+ {bestPractices.map((practice, index) => (
135
+ <li key={index} className="flex items-start">
136
+ <span className="inline-block w-1.5 h-1.5 bg-blue-400 rounded-full mt-1.5 mr-2 flex-shrink-0" />
137
+ {practice}
138
+ </li>
139
+ ))}
140
+ </ul>
141
+ </CardContent>
142
+ </Card>
143
+ </div>
144
+ )
145
+ }
frontend/src/components/playground/index.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ export { ModelParametersTab } from './ModelParametersTab'
2
+ export { AssistantSelector } from './AssistantSelector'
3
+ export { SystemInstructionsTab } from './SystemInstructionsTab'
4
+ export { DocumentsTab } from './DocumentsTab'
frontend/src/pages/Playground.tsx CHANGED
@@ -1,21 +1,9 @@
1
  import { useState, useEffect } from 'react'
2
  import { AssistantInfo } from '@/types/chat'
3
  import { Button } from '@/components/ui/button'
4
- import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
5
  import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
6
- import { Slider } from '@/components/ui/slider'
7
  import { Label } from '@/components/ui/label'
8
- import { Badge } from '@/components/ui/badge'
9
-
10
- import {
11
- Select,
12
- SelectContent,
13
- SelectGroup,
14
- SelectItem,
15
- SelectLabel,
16
- SelectTrigger,
17
- SelectValue,
18
- } from '@/components/ui/select'
19
 
20
  import {
21
  AlertDialog,
@@ -30,27 +18,25 @@ import {
30
  import { Chat } from '@/components/ui/chat'
31
  import { useChat } from '@/hooks/useChat'
32
  import {
33
- Brain,
34
- Zap,
35
- MessageSquare,
36
- Settings,
37
- Cloud,
38
  Plus,
39
- X,
40
  Trash2,
41
- Bot,
42
  Save,
 
43
  Sliders,
44
- Bookmark,
45
- User,
46
- ChevronLeft,
47
- ChevronRight,
48
  BookOpen,
49
- Upload,
50
- FileText,
51
- File
52
  } from 'lucide-react'
53
 
 
 
 
 
 
 
 
 
54
  interface ModelInfo {
55
  model_name: string
56
  name: string
@@ -115,8 +101,6 @@ export function Playground() {
115
  // Rename assistant dialog state
116
  const [showRenameDialog, setShowRenameDialog] = useState(false)
117
  const [renameAssistantName, setRenameAssistantName] = useState('')
118
-
119
-
120
 
121
  // Load saved assistants
122
  const loadSavedAssistants = () => {
@@ -161,6 +145,7 @@ export function Playground() {
161
  model: selectedModel,
162
  ragEnabled,
163
  retrievalCount,
 
164
  createdAt: new Date().toISOString()
165
  }
166
 
@@ -300,9 +285,7 @@ export function Playground() {
300
  setShowRenameDialog(false)
301
  setRenameAssistantName('')
302
  }
303
-
304
 
305
-
306
  // Get current assistant information
307
  const getCurrentAssistantInfo = (): AssistantInfo => {
308
  return {
@@ -314,8 +297,6 @@ export function Playground() {
314
  originalTemplate: selectedAssistant?.originalTemplate
315
  }
316
  }
317
-
318
-
319
 
320
  // Convert template to new assistant when settings change
321
  const convertTemplateToNew = () => {
@@ -361,8 +342,6 @@ export function Playground() {
361
  loadSavedAssistants()
362
  }, [])
363
 
364
-
365
-
366
  // Debug logs for Session issue
367
  useEffect(() => {
368
  console.log('Sidebar states:', { sessionsCollapsed, configCollapsed, sessionsCount: sessions.length, currentSessionId })
@@ -481,8 +460,6 @@ export function Playground() {
481
  }
482
  }
483
 
484
-
485
-
486
  // Cleanup: unload all local models when component unmounts or user leaves
487
  useEffect(() => {
488
  const handlePageUnload = async () => {
@@ -623,12 +600,8 @@ export function Playground() {
623
  </div>
624
  </div>
625
 
626
-
627
-
628
  {/* Main Content */}
629
  <div className="flex-1 flex flex-col h-full">
630
-
631
-
632
  {/* Content Area - Responsive layout */}
633
  <div className="flex-1 flex overflow-hidden">
634
  {/* Chat Area */}
@@ -884,854 +857,3 @@ export function Playground() {
884
  </div>
885
  )
886
  }
887
-
888
- // Model Parameters Tab Component
889
- function ModelParametersTab({
890
- models,
891
- selectedModel,
892
- setSelectedModel,
893
- autoLoadingModel,
894
- temperature,
895
- setTemperature,
896
- maxTokens,
897
- setMaxTokens
898
- }: {
899
- models: ModelInfo[]
900
- selectedModel: string | null
901
- setSelectedModel: (model: string) => void
902
- autoLoadingModel: string | null
903
- temperature: number
904
- setTemperature: (temp: number) => void
905
- maxTokens: number
906
- setMaxTokens: (tokens: number) => void
907
- }) {
908
- return (
909
- <>
910
- {/* Model Selection */}
911
- <Card>
912
- <CardHeader>
913
- <CardTitle className="text-base">Model</CardTitle>
914
- </CardHeader>
915
- <CardContent className="space-y-4">
916
- <div>
917
- <Select value={selectedModel || ""} onValueChange={setSelectedModel}>
918
- <SelectTrigger className="w-full">
919
- <SelectValue placeholder="Select a model...">
920
- {selectedModel && (() => {
921
- const model = models.find(m => m.model_name === selectedModel)
922
- if (!model) return selectedModel
923
- const isApiModel = model.type === 'api'
924
- return (
925
- <div className="flex items-center gap-2">
926
- {isApiModel ? (
927
- <Cloud className="h-4 w-4 text-blue-500" />
928
- ) : model.supports_thinking ? (
929
- <Brain className="h-4 w-4 text-purple-500" />
930
- ) : (
931
- <Zap className="h-4 w-4 text-green-500" />
932
- )}
933
- <span className="truncate">{model.name}</span>
934
- {autoLoadingModel === selectedModel ? (
935
- <Badge variant="outline" className="text-xs">
936
- Loading...
937
- </Badge>
938
- ) : (
939
- <Badge variant="outline" className="text-xs">
940
- {isApiModel ? "API" : model.is_loaded ? "Loaded" : "Available"}
941
- </Badge>
942
- )}
943
- </div>
944
- )
945
- })()}
946
- </SelectValue>
947
- </SelectTrigger>
948
- <SelectContent>
949
- <SelectGroup>
950
- <SelectLabel>🌐 API Models</SelectLabel>
951
- {models.filter(m => m.type === 'api').map((model) => (
952
- <SelectItem key={model.model_name} value={model.model_name}>
953
- <div className="flex items-center gap-2">
954
- <Cloud className="h-4 w-4 text-blue-500" />
955
- <span>{model.name}</span>
956
- <Badge variant="outline" className="text-xs bg-blue-50">API</Badge>
957
- </div>
958
- </SelectItem>
959
- ))}
960
- </SelectGroup>
961
- <SelectGroup>
962
- <SelectLabel>💻 Local Models</SelectLabel>
963
- {models.filter(m => m.type === 'local').map((model) => (
964
- <SelectItem key={model.model_name} value={model.model_name}>
965
- <div className="flex items-center gap-2">
966
- {model.supports_thinking ? (
967
- <Brain className="h-4 w-4 text-purple-500" />
968
- ) : (
969
- <Zap className="h-4 w-4 text-green-500" />
970
- )}
971
- <span>{model.name}</span>
972
- {autoLoadingModel === model.model_name ? (
973
- <Badge variant="outline" className="text-xs bg-yellow-50">Loading...</Badge>
974
- ) : model.is_loaded ? (
975
- <Badge variant="outline" className="text-xs bg-green-50">Loaded</Badge>
976
- ) : (
977
- <Badge variant="outline" className="text-xs bg-gray-50">Available</Badge>
978
- )}
979
- </div>
980
- </SelectItem>
981
- ))}
982
- </SelectGroup>
983
- </SelectContent>
984
- </Select>
985
- </div>
986
-
987
-
988
- </CardContent>
989
- </Card>
990
-
991
- {/* Generation Parameters */}
992
- <Card>
993
- <CardHeader>
994
- <CardTitle className="text-base">Parameters</CardTitle>
995
- </CardHeader>
996
- <CardContent className="space-y-6">
997
- {/* Temperature Slider */}
998
- <div>
999
- <div className="flex items-center justify-between mb-3">
1000
- <Label className="text-sm font-medium">Temperature</Label>
1001
- <Badge variant="outline" className="text-xs">
1002
- {temperature}
1003
- </Badge>
1004
- </div>
1005
- <Slider
1006
- value={[temperature]}
1007
- onValueChange={(value) => setTemperature(value[0])}
1008
- max={2}
1009
- min={0}
1010
- step={0.1}
1011
- className="w-full mb-3"
1012
- />
1013
- <div className="flex justify-between text-xs text-gray-500 mb-2">
1014
- <span>0.0 (Focused)</span>
1015
- <span>1.0 (Balanced)</span>
1016
- <span>2.0 (Creative)</span>
1017
- </div>
1018
- <div className="text-xs text-muted-foreground bg-gray-50 p-2 rounded">
1019
- {temperature <= 0.3
1020
- ? "🎯 Very focused and deterministic responses"
1021
- : temperature <= 0.7
1022
- ? "⚖️ Balanced creativity and consistency"
1023
- : temperature <= 1.2
1024
- ? "🎨 More creative and varied responses"
1025
- : "🚀 Highly creative, unpredictable responses"
1026
- }
1027
- </div>
1028
- </div>
1029
-
1030
- {/* Max Tokens Slider */}
1031
- <div>
1032
- <div className="flex items-center justify-between mb-3">
1033
- <Label className="text-sm font-medium">Max Tokens</Label>
1034
- <Badge variant="outline" className="text-xs">
1035
- {maxTokens}
1036
- </Badge>
1037
- </div>
1038
- <Slider
1039
- value={[maxTokens]}
1040
- onValueChange={(value) => setMaxTokens(value[0])}
1041
- max={4000}
1042
- min={50}
1043
- step={50}
1044
- className="w-full mb-3"
1045
- />
1046
- <div className="flex justify-between text-xs text-gray-500 mb-2">
1047
- <span>50 (Short)</span>
1048
- <span>1024 (Medium)</span>
1049
- <span>4000 (Long)</span>
1050
- </div>
1051
- <div className="text-xs text-muted-foreground bg-gray-50 p-2 rounded">
1052
- {maxTokens <= 200
1053
- ? "📝 Brief responses, good for quick answers"
1054
- : maxTokens <= 1000
1055
- ? "📄 Medium responses, balanced length"
1056
- : maxTokens <= 2000
1057
- ? "📋 Long responses, detailed explanations"
1058
- : "📚 Very long responses, comprehensive content"
1059
- }
1060
- </div>
1061
- </div>
1062
- </CardContent>
1063
- </Card>
1064
- </>
1065
- )
1066
- }
1067
-
1068
-
1069
-
1070
- // Assistant Selector Component (above tabs)
1071
- function AssistantSelector({
1072
- savedAssistants,
1073
- loadSavedAssistant,
1074
- openSaveDialog,
1075
- presets,
1076
- onPresetSelect,
1077
- isLoading,
1078
- selectedAssistant,
1079
- createNewAssistant,
1080
- clearCurrentAssistant,
1081
- openRenameDialog,
1082
- systemPrompt
1083
- }: {
1084
- savedAssistants: any[]
1085
- loadSavedAssistant: (id: string) => void
1086
- openSaveDialog: () => void
1087
- presets: Array<{name: string; prompt: string}>
1088
- onPresetSelect: (presetName: string) => void
1089
- isLoading: boolean
1090
- selectedAssistant: {id: string, name: string, type: 'user'|'template'|'new', originalTemplate?: string} | null
1091
- createNewAssistant: () => void
1092
- clearCurrentAssistant: () => void
1093
- openRenameDialog: () => void
1094
- systemPrompt: string
1095
- }) {
1096
- const handleAssistantSelect = (value: string) => {
1097
- if (value === 'create_new') {
1098
- createNewAssistant()
1099
- } else if (value === 'clear_current') {
1100
- clearCurrentAssistant()
1101
- } else if (value.startsWith('user_')) {
1102
- // Check if it's a saved assistant (starts with user_)
1103
- const assistantId = value.replace('user_', '')
1104
- loadSavedAssistant(assistantId)
1105
- } else {
1106
- // It's a template preset
1107
- onPresetSelect(value)
1108
- }
1109
- }
1110
-
1111
- // Get current selection value for the Select component
1112
- const getCurrentValue = () => {
1113
- if (!selectedAssistant) return ''
1114
-
1115
- if (selectedAssistant.type === 'user') {
1116
- return `user_${selectedAssistant.id}`
1117
- } else if (selectedAssistant.type === 'new') {
1118
- return 'new_assistant'
1119
- } else {
1120
- return selectedAssistant.id
1121
- }
1122
- }
1123
-
1124
- // Get badge for assistant type
1125
- const getAssistantBadge = (type: 'user'|'template'|'new') => {
1126
- switch (type) {
1127
- case 'user':
1128
- return { variant: 'secondary' as const, text: 'Mine' }
1129
- case 'template':
1130
- return { variant: 'outline' as const, text: 'Template' }
1131
- case 'new':
1132
- return { variant: 'default' as const, text: 'New', className: 'bg-green-100 text-green-700' }
1133
- }
1134
- }
1135
-
1136
- const totalAssistants = savedAssistants.length + presets.length
1137
-
1138
- return (
1139
- <div className="space-y-4">
1140
- <div>
1141
- <Label className="text-sm font-medium mb-2 block">Load Assistant</Label>
1142
- <Select value={getCurrentValue()} onValueChange={handleAssistantSelect}>
1143
- <SelectTrigger className="w-full">
1144
- <SelectValue placeholder={`Choose assistant (${totalAssistants} available)`}>
1145
- {selectedAssistant ? (
1146
- <div className="flex items-center gap-2">
1147
- <Bot className="h-4 w-4" />
1148
- <span>{selectedAssistant.name}</span>
1149
- {selectedAssistant.originalTemplate && (
1150
- <span className="text-xs text-muted-foreground">
1151
- (from {selectedAssistant.originalTemplate})
1152
- </span>
1153
- )}
1154
- <Badge
1155
- variant={getAssistantBadge(selectedAssistant.type).variant}
1156
- className={`text-xs ml-auto ${getAssistantBadge(selectedAssistant.type).className || ''}`}
1157
- >
1158
- {getAssistantBadge(selectedAssistant.type).text}
1159
- </Badge>
1160
- </div>
1161
- ) : (
1162
- <div className="flex items-center gap-2">
1163
- <Bot className="h-4 w-4" />
1164
- <span>Choose assistant</span>
1165
- <Badge variant="outline" className="text-xs ml-auto">
1166
- {totalAssistants}
1167
- </Badge>
1168
- </div>
1169
- )}
1170
- </SelectValue>
1171
- </SelectTrigger>
1172
- <SelectContent>
1173
- {/* Actions Section */}
1174
- <SelectGroup>
1175
- <SelectLabel className="flex items-center gap-2 text-sm font-medium">
1176
- <Plus className="h-4 w-4" />
1177
- Actions
1178
- </SelectLabel>
1179
- <SelectItem value="create_new">
1180
- <div className="flex items-center gap-2 w-full">
1181
- <Plus className="h-4 w-4 text-green-600" />
1182
- <span className="truncate flex-1">Create New Assistant</span>
1183
- <Badge variant="default" className="text-xs bg-green-100 text-green-700">New</Badge>
1184
- </div>
1185
- </SelectItem>
1186
- {selectedAssistant && (
1187
- <SelectItem value="clear_current">
1188
- <div className="flex items-center gap-2 w-full">
1189
- <X className="h-4 w-4 text-gray-600" />
1190
- <span className="truncate flex-1">Clear Current</span>
1191
- <Badge variant="outline" className="text-xs">Clear</Badge>
1192
- </div>
1193
- </SelectItem>
1194
- )}
1195
- </SelectGroup>
1196
-
1197
- {/* My Assistants Section */}
1198
- {savedAssistants.length > 0 && (
1199
- <SelectGroup>
1200
- <SelectLabel className="flex items-center gap-2 text-sm font-medium">
1201
- <User className="h-4 w-4" />
1202
- My Assistants ({savedAssistants.length})
1203
- </SelectLabel>
1204
- {savedAssistants.map((assistant) => (
1205
- <SelectItem key={`user_${assistant.id}`} value={`user_${assistant.id}`}>
1206
- <div className="flex items-center gap-2 w-full">
1207
- <Bot className="h-4 w-4 text-blue-600" />
1208
- <span className="truncate flex-1">{assistant.name}</span>
1209
- <Badge variant="secondary" className="text-xs">Mine</Badge>
1210
- </div>
1211
- </SelectItem>
1212
- ))}
1213
- </SelectGroup>
1214
- )}
1215
-
1216
- {/* Templates Section */}
1217
- {presets.length > 0 && (
1218
- <SelectGroup>
1219
- <SelectLabel className="flex items-center gap-2 text-sm font-medium">
1220
- <Bookmark className="h-4 w-4" />
1221
- Templates ({presets.length})
1222
- </SelectLabel>
1223
- {presets.map((preset) => (
1224
- <SelectItem key={preset.name} value={preset.name}>
1225
- <div className="flex items-center gap-2 w-full">
1226
- <Bot className="h-4 w-4 text-green-600" />
1227
- <span className="truncate flex-1">{preset.name}</span>
1228
- <Badge variant="outline" className="text-xs">Template</Badge>
1229
- </div>
1230
- </SelectItem>
1231
- ))}
1232
- </SelectGroup>
1233
- )}
1234
-
1235
- {/* Empty State */}
1236
- {totalAssistants === 0 && (
1237
- <div className="p-2 text-sm text-muted-foreground text-center">
1238
- No assistants available
1239
- </div>
1240
- )}
1241
- </SelectContent>
1242
- </Select>
1243
- </div>
1244
-
1245
- {/* Rename Assistant */}
1246
- {selectedAssistant && selectedAssistant.type !== 'template' && (
1247
- <Button
1248
- onClick={openRenameDialog}
1249
- size="sm"
1250
- variant="ghost"
1251
- className="w-full justify-start"
1252
- disabled={isLoading}
1253
- >
1254
- <Settings className="h-4 w-4 mr-2" />
1255
- Rename
1256
- </Button>
1257
- )}
1258
-
1259
-
1260
-
1261
- {/* Save Current Configuration */}
1262
- <Button
1263
- onClick={openSaveDialog}
1264
- size="sm"
1265
- variant="outline"
1266
- className="w-full"
1267
- disabled={isLoading || !systemPrompt.trim()}
1268
- >
1269
- <Save className="h-4 w-4 mr-2" />
1270
- Save Assistant
1271
- </Button>
1272
- </div>
1273
- )
1274
- }
1275
-
1276
- // System Instructions Tab Component
1277
- function SystemInstructionsTab({
1278
- systemPrompt,
1279
- setSystemPrompt,
1280
- isLoading
1281
- }: {
1282
- systemPrompt: string
1283
- setSystemPrompt: (prompt: string) => void
1284
- isLoading: boolean
1285
- }) {
1286
- const promptExamples = [
1287
- {
1288
- title: "Helpful Assistant",
1289
- prompt: "You are a helpful, harmless, and honest AI assistant. Provide clear, accurate, and well-structured responses. If you're unsure about something, say so."
1290
- },
1291
- {
1292
- title: "Code Expert",
1293
- prompt: "You are an expert software developer. Provide clean, efficient code with clear explanations. Always follow best practices and include helpful comments."
1294
- },
1295
- {
1296
- title: "Creative Writer",
1297
- prompt: "You are a creative writer. Use vivid language, engaging storytelling, and imaginative descriptions. Be expressive and artistic in your responses."
1298
- },
1299
- {
1300
- title: "Teacher/Tutor",
1301
- prompt: "You are a patient and knowledgeable teacher. Break down complex concepts into simple, understandable parts. Use examples and analogies to help explain ideas."
1302
- }
1303
- ]
1304
-
1305
- const bestPractices = [
1306
- "Be specific about the assistant's role and expertise",
1307
- "Include desired communication style and tone",
1308
- "Specify output format if needed (lists, paragraphs, etc.)",
1309
- "Add ethical guidelines and behavior expectations"
1310
- ]
1311
-
1312
- return (
1313
- <div className="space-y-4 pb-6">
1314
- <Card>
1315
- <CardHeader>
1316
- <CardTitle className="text-base">System Instructions</CardTitle>
1317
- </CardHeader>
1318
- <CardContent>
1319
- <div className="space-y-4">
1320
- <Label className="text-base font-medium text-blue-700">
1321
- System Prompt
1322
- </Label>
1323
- <textarea
1324
- value={systemPrompt}
1325
- onChange={(e) => setSystemPrompt(e.target.value)}
1326
- placeholder="Define your AI assistant's role, personality, and behavior..."
1327
- className="w-full h-[250px] text-base p-4 border-2 border-blue-200 rounded-lg bg-white focus:border-blue-400 focus:ring-2 focus:ring-blue-100 resize-none overflow-y-auto"
1328
- disabled={isLoading}
1329
- />
1330
-
1331
- {/* Character count */}
1332
- <div className="text-xs text-muted-foreground text-right">
1333
- {systemPrompt.length} characters
1334
- </div>
1335
- </div>
1336
- </CardContent>
1337
- </Card>
1338
-
1339
- {/* Quick Examples */}
1340
- <Card>
1341
- <CardHeader>
1342
- <CardTitle className="text-sm flex items-center">
1343
- <Brain className="h-4 w-4 mr-2" />
1344
- Quick Examples
1345
- </CardTitle>
1346
- </CardHeader>
1347
- <CardContent>
1348
- <div className="space-y-3">
1349
- <Label className="text-xs font-medium text-muted-foreground">
1350
- Choose a template to get started:
1351
- </Label>
1352
- <Select
1353
- value=""
1354
- onValueChange={(value) => {
1355
- const example = promptExamples[parseInt(value)]
1356
- if (example) {
1357
- setSystemPrompt(example.prompt)
1358
- }
1359
- }}
1360
- disabled={isLoading}
1361
- >
1362
- <SelectTrigger className="w-full h-10">
1363
- <SelectValue placeholder="Select example prompt..." />
1364
- </SelectTrigger>
1365
- <SelectContent>
1366
- <SelectGroup>
1367
- <SelectLabel>Example Prompts</SelectLabel>
1368
- {promptExamples.map((example, index) => (
1369
- <SelectItem key={index} value={index.toString()}>
1370
- <div className="flex flex-col items-start">
1371
- <span className="font-medium text-sm">{example.title}</span>
1372
- <span className="text-xs text-muted-foreground line-clamp-1 mt-1">
1373
- {example.prompt.substring(0, 60)}...
1374
- </span>
1375
- </div>
1376
- </SelectItem>
1377
- ))}
1378
- </SelectGroup>
1379
- </SelectContent>
1380
- </Select>
1381
- </div>
1382
- </CardContent>
1383
- </Card>
1384
-
1385
- {/* Best Practices */}
1386
- <Card>
1387
- <CardHeader>
1388
- <CardTitle className="text-sm flex items-center">
1389
- <MessageSquare className="h-4 w-4 mr-2" />
1390
- Best Practices
1391
- </CardTitle>
1392
- </CardHeader>
1393
- <CardContent>
1394
- <ul className="text-xs space-y-2 text-muted-foreground">
1395
- {bestPractices.map((practice, index) => (
1396
- <li key={index} className="flex items-start">
1397
- <span className="inline-block w-1.5 h-1.5 bg-blue-400 rounded-full mt-1.5 mr-2 flex-shrink-0" />
1398
- {practice}
1399
- </li>
1400
- ))}
1401
- </ul>
1402
- </CardContent>
1403
- </Card>
1404
- </div>
1405
- )
1406
- }
1407
-
1408
- // Documents Tab Component for RAG functionality
1409
- function DocumentsTab({
1410
- isLoading,
1411
- ragEnabled,
1412
- setRagEnabled,
1413
- retrievalCount,
1414
- setRetrievalCount,
1415
- currentAssistant
1416
- }: {
1417
- isLoading: boolean
1418
- ragEnabled: boolean
1419
- setRagEnabled: (enabled: boolean) => void
1420
- retrievalCount: number
1421
- setRetrievalCount: (count: number) => void
1422
- currentAssistant: {id: string, name: string, type: 'user'|'template'|'new', originalTemplate?: string} | null
1423
- }) {
1424
- const [uploadedFiles, setUploadedFiles] = useState<any[]>([])
1425
- const [isUploading, setIsUploading] = useState(false)
1426
-
1427
- // Load existing documents on component mount
1428
- useEffect(() => {
1429
- const loadDocuments = async () => {
1430
- try {
1431
- const response = await fetch('/rag/documents')
1432
- if (response.ok) {
1433
- const data = await response.json()
1434
- if (data.documents) {
1435
- const documentList = Object.entries(data.documents).map(([docId, docInfo]: [string, any]) => ({
1436
- id: docId,
1437
- name: docInfo.filename,
1438
- size: 0,
1439
- type: docInfo.file_type,
1440
- uploadedAt: new Date().toISOString(),
1441
- status: docInfo.status,
1442
- chunks: docInfo.chunks
1443
- }))
1444
- setUploadedFiles(documentList)
1445
- }
1446
- }
1447
- } catch (error) {
1448
- console.error('Error loading documents:', error)
1449
- }
1450
- }
1451
-
1452
- loadDocuments()
1453
- }, [])
1454
-
1455
- const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
1456
- const files = event.target.files
1457
- if (!files) return
1458
-
1459
- setIsUploading(true)
1460
-
1461
- try {
1462
- const formData = new FormData()
1463
-
1464
- for (const file of Array.from(files)) {
1465
- formData.append('files', file)
1466
- }
1467
-
1468
- const response = await fetch('/rag/upload', {
1469
- method: 'POST',
1470
- body: formData,
1471
- })
1472
-
1473
- if (response.ok) {
1474
- const result = await response.json()
1475
-
1476
- // Add successfully processed files to the list
1477
- const newFiles = result.results
1478
- .filter((r: any) => r.success)
1479
- .map((r: any) => ({
1480
- id: r.doc_id,
1481
- name: r.filename,
1482
- size: 0, // Server doesn't return size currently
1483
- type: 'processed',
1484
- uploadedAt: new Date().toISOString(),
1485
- status: 'processed',
1486
- chunks: r.chunks
1487
- }))
1488
-
1489
- setUploadedFiles(prev => [...prev, ...newFiles])
1490
-
1491
- // Show errors for failed uploads
1492
- const failedUploads = result.results.filter((r: any) => !r.success)
1493
- if (failedUploads.length > 0) {
1494
- console.error('Some files failed to upload:', failedUploads)
1495
- }
1496
- } else {
1497
- console.error('Upload failed:', response.statusText)
1498
- }
1499
-
1500
- // Reset input
1501
- event.target.value = ''
1502
- } catch (error) {
1503
- console.error('File upload error:', error)
1504
- } finally {
1505
- setIsUploading(false)
1506
- }
1507
- }
1508
-
1509
- const removeFile = async (fileId: string) => {
1510
- try {
1511
- const response = await fetch(`/rag/documents/${fileId}`, {
1512
- method: 'DELETE',
1513
- })
1514
-
1515
- if (response.ok) {
1516
- setUploadedFiles(prev => prev.filter(f => f.id !== fileId))
1517
- } else {
1518
- console.error('Failed to delete document:', response.statusText)
1519
- }
1520
- } catch (error) {
1521
- console.error('Error deleting document:', error)
1522
- // Remove from UI anyway
1523
- setUploadedFiles(prev => prev.filter(f => f.id !== fileId))
1524
- }
1525
- }
1526
-
1527
- const formatFileSize = (bytes: number) => {
1528
- if (bytes === 0) return '0 Bytes'
1529
- const k = 1024
1530
- const sizes = ['Bytes', 'KB', 'MB', 'GB']
1531
- const i = Math.floor(Math.log(bytes) / Math.log(k))
1532
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
1533
- }
1534
-
1535
- return (
1536
- <div className="space-y-4 pb-6">
1537
- {/* Current Assistant Info */}
1538
- {currentAssistant && (
1539
- <Card>
1540
- <CardHeader>
1541
- <CardTitle className="text-sm">Current Assistant</CardTitle>
1542
- </CardHeader>
1543
- <CardContent>
1544
- <div className="flex items-center space-x-2">
1545
- <User className="h-4 w-4 text-blue-600" />
1546
- <span className="font-medium">{currentAssistant.name}</span>
1547
- <span className={`inline-block px-2 py-1 rounded text-xs font-medium ${
1548
- currentAssistant.type === 'user' ? 'bg-blue-100 text-blue-700' :
1549
- currentAssistant.type === 'template' ? 'bg-gray-100 text-gray-700' :
1550
- 'bg-green-100 text-green-700'
1551
- }`}>
1552
- {currentAssistant.type === 'user' ? 'My Assistant' :
1553
- currentAssistant.type === 'template' ? 'Template' : 'New Assistant'}
1554
- </span>
1555
- {currentAssistant.originalTemplate && (
1556
- <span className="text-xs text-muted-foreground">
1557
- (from {currentAssistant.originalTemplate})
1558
- </span>
1559
- )}
1560
- </div>
1561
- </CardContent>
1562
- </Card>
1563
- )}
1564
-
1565
- {/* RAG Configuration */}
1566
- <Card>
1567
- <CardHeader>
1568
- <CardTitle className="text-base">
1569
- RAG Configuration
1570
- {currentAssistant && (
1571
- <span className="text-xs text-muted-foreground font-normal ml-2">
1572
- for "{currentAssistant.name}"
1573
- </span>
1574
- )}
1575
- </CardTitle>
1576
- </CardHeader>
1577
- <CardContent>
1578
- <div className="space-y-4">
1579
- <div className="flex items-center justify-between">
1580
- <div className="space-y-1">
1581
- <Label className="text-sm font-medium">Enable RAG</Label>
1582
- <p className="text-xs text-muted-foreground">
1583
- Use uploaded documents to enhance responses
1584
- </p>
1585
- </div>
1586
- <div className="flex items-center space-x-2">
1587
- <input
1588
- type="checkbox"
1589
- id="rag-enabled"
1590
- checked={ragEnabled}
1591
- onChange={(e) => setRagEnabled(e.target.checked)}
1592
- className="rounded"
1593
- disabled={isLoading || uploadedFiles.length === 0}
1594
- />
1595
- <Label htmlFor="rag-enabled" className="text-sm">
1596
- {ragEnabled ? 'On' : 'Off'}
1597
- </Label>
1598
- </div>
1599
- </div>
1600
-
1601
- {ragEnabled && (
1602
- <div className="space-y-3 border-t pt-4">
1603
- <div>
1604
- <Label className="text-sm font-medium mb-2 block">
1605
- Retrieval Count: {retrievalCount}
1606
- </Label>
1607
- <Slider
1608
- value={[retrievalCount]}
1609
- onValueChange={(value) => setRetrievalCount(value[0])}
1610
- max={10}
1611
- min={1}
1612
- step={1}
1613
- className="w-full"
1614
- />
1615
- <div className="flex justify-between text-xs text-muted-foreground mt-1">
1616
- <span>1 (Focused)</span>
1617
- <span>5 (Balanced)</span>
1618
- <span>10 (Comprehensive)</span>
1619
- </div>
1620
- <p className="text-xs text-muted-foreground mt-2">
1621
- Number of relevant document chunks to retrieve for context
1622
- </p>
1623
- </div>
1624
- </div>
1625
- )}
1626
- </div>
1627
- </CardContent>
1628
- </Card>
1629
-
1630
- {/* File Upload */}
1631
- <Card>
1632
- <CardHeader>
1633
- <CardTitle className="text-base flex items-center">
1634
- <Upload className="h-4 w-4 mr-2" />
1635
- Upload Documents
1636
- </CardTitle>
1637
- </CardHeader>
1638
- <CardContent>
1639
- <div className="space-y-4">
1640
- <div className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-gray-400 transition-colors">
1641
- <input
1642
- type="file"
1643
- id="file-upload"
1644
- multiple
1645
- accept=".pdf,.txt,.docx,.md"
1646
- onChange={handleFileUpload}
1647
- className="hidden"
1648
- disabled={isUploading || isLoading}
1649
- />
1650
- <Label
1651
- htmlFor="file-upload"
1652
- className="cursor-pointer flex flex-col items-center space-y-2"
1653
- >
1654
- <Upload className="h-8 w-8 text-gray-400" />
1655
- <span className="text-sm font-medium">
1656
- {isUploading ? 'Uploading...' : 'Click to upload documents'}
1657
- </span>
1658
- <span className="text-xs text-muted-foreground">
1659
- PDF, TXT, DOCX, MD files supported
1660
- </span>
1661
- </Label>
1662
- </div>
1663
-
1664
- <div className="text-xs text-muted-foreground bg-blue-50 p-3 rounded">
1665
- <strong>💡 How it works:</strong>
1666
- <br />
1667
- • Upload your documents to create a knowledge base
1668
- • Enable RAG to use these documents as context
1669
- • The AI will search relevant information to answer questions
1670
- </div>
1671
- </div>
1672
- </CardContent>
1673
- </Card>
1674
-
1675
- {/* Uploaded Files */}
1676
- {uploadedFiles.length > 0 && (
1677
- <Card>
1678
- <CardHeader>
1679
- <CardTitle className="text-base flex items-center">
1680
- <FileText className="h-4 w-4 mr-2" />
1681
- Uploaded Documents ({uploadedFiles.length})
1682
- </CardTitle>
1683
- </CardHeader>
1684
- <CardContent>
1685
- <div className="space-y-2">
1686
- {uploadedFiles.map((file) => (
1687
- <div
1688
- key={file.id}
1689
- className="flex items-center justify-between p-3 border rounded-lg bg-gray-50"
1690
- >
1691
- <div className="flex items-center space-x-3">
1692
- <File className="h-4 w-4 text-blue-600" />
1693
- <div className="flex-1">
1694
- <p className="text-sm font-medium truncate max-w-[200px]">
1695
- {file.name}
1696
- </p>
1697
- <p className="text-xs text-muted-foreground">
1698
- {formatFileSize(file.size)} • {file.status}
1699
- </p>
1700
- </div>
1701
- </div>
1702
- <Button
1703
- size="sm"
1704
- variant="ghost"
1705
- onClick={() => removeFile(file.id)}
1706
- disabled={isLoading}
1707
- className="text-red-600 hover:text-red-700 hover:bg-red-50"
1708
- >
1709
- <X className="h-4 w-4" />
1710
- </Button>
1711
- </div>
1712
- ))}
1713
- </div>
1714
- </CardContent>
1715
- </Card>
1716
- )}
1717
-
1718
- {/* RAG Status */}
1719
- {ragEnabled && uploadedFiles.length > 0 && (
1720
- <Card>
1721
- <CardContent className="pt-6">
1722
- <div className="flex items-center space-x-2 text-green-600">
1723
- <div className="w-2 h-2 bg-green-500 rounded-full"></div>
1724
- <span className="text-sm font-medium">
1725
- RAG Active - Using {uploadedFiles.length} document(s)
1726
- {currentAssistant && ` for "${currentAssistant.name}"`}
1727
- </span>
1728
- </div>
1729
- <p className="text-xs text-muted-foreground mt-1">
1730
- {currentAssistant?.name || 'This assistant'} will use context from uploaded documents to enhance responses
1731
- </p>
1732
- </CardContent>
1733
- </Card>
1734
- )}
1735
- </div>
1736
- )
1737
- }
 
1
  import { useState, useEffect } from 'react'
2
  import { AssistantInfo } from '@/types/chat'
3
  import { Button } from '@/components/ui/button'
4
+ import { Card } from '@/components/ui/card'
5
  import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
 
6
  import { Label } from '@/components/ui/label'
 
 
 
 
 
 
 
 
 
 
 
7
 
8
  import {
9
  AlertDialog,
 
18
  import { Chat } from '@/components/ui/chat'
19
  import { useChat } from '@/hooks/useChat'
20
  import {
 
 
 
 
 
21
  Plus,
 
22
  Trash2,
 
23
  Save,
24
+ Settings,
25
  Sliders,
 
 
 
 
26
  BookOpen,
27
+ MessageSquare,
28
+ ChevronLeft,
29
+ ChevronRight
30
  } from 'lucide-react'
31
 
32
+ // Import refactored components
33
+ import {
34
+ ModelParametersTab,
35
+ AssistantSelector,
36
+ SystemInstructionsTab,
37
+ DocumentsTab
38
+ } from '@/components/playground'
39
+
40
  interface ModelInfo {
41
  model_name: string
42
  name: string
 
101
  // Rename assistant dialog state
102
  const [showRenameDialog, setShowRenameDialog] = useState(false)
103
  const [renameAssistantName, setRenameAssistantName] = useState('')
 
 
104
 
105
  // Load saved assistants
106
  const loadSavedAssistants = () => {
 
145
  model: selectedModel,
146
  ragEnabled,
147
  retrievalCount,
148
+ documents: [], // Assistant-specific documents (future: populated from DocumentsTab)
149
  createdAt: new Date().toISOString()
150
  }
151
 
 
285
  setShowRenameDialog(false)
286
  setRenameAssistantName('')
287
  }
 
288
 
 
289
  // Get current assistant information
290
  const getCurrentAssistantInfo = (): AssistantInfo => {
291
  return {
 
297
  originalTemplate: selectedAssistant?.originalTemplate
298
  }
299
  }
 
 
300
 
301
  // Convert template to new assistant when settings change
302
  const convertTemplateToNew = () => {
 
342
  loadSavedAssistants()
343
  }, [])
344
 
 
 
345
  // Debug logs for Session issue
346
  useEffect(() => {
347
  console.log('Sidebar states:', { sessionsCollapsed, configCollapsed, sessionsCount: sessions.length, currentSessionId })
 
460
  }
461
  }
462
 
 
 
463
  // Cleanup: unload all local models when component unmounts or user leaves
464
  useEffect(() => {
465
  const handlePageUnload = async () => {
 
600
  </div>
601
  </div>
602
 
 
 
603
  {/* Main Content */}
604
  <div className="flex-1 flex flex-col h-full">
 
 
605
  {/* Content Area - Responsive layout */}
606
  <div className="flex-1 flex overflow-hidden">
607
  {/* Chat Area */}
 
857
  </div>
858
  )
859
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/assets/index-76cf04a9.js ADDED
The diff for this file is too large to render. See raw diff
 
static/assets/index-76cf04a9.js.map ADDED
The diff for this file is too large to render. See raw diff
 
static/index.html CHANGED
@@ -5,7 +5,7 @@
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
  <title>Edge LLM</title>
8
- <script type="module" crossorigin src="/assets/index-37eb6913.js"></script>
9
  <link rel="stylesheet" href="/assets/index-7ec1bc04.css">
10
  </head>
11
  <body>
 
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
  <title>Edge LLM</title>
8
+ <script type="module" crossorigin src="/assets/index-76cf04a9.js"></script>
9
  <link rel="stylesheet" href="/assets/index-7ec1bc04.css">
10
  </head>
11
  <body>