llms-py 2.0.26__py3-none-any.whl → 2.0.27__py3-none-any.whl
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.
- llms/index.html +17 -1
- llms/llms.json +1 -1
- llms/main.py +1 -1
- llms/ui/Analytics.mjs +73 -73
- llms/ui/App.mjs +1 -1
- llms/ui/Brand.mjs +4 -4
- llms/ui/ChatPrompt.mjs +110 -9
- llms/ui/Main.mjs +35 -34
- llms/ui/ModelSelector.mjs +3 -4
- llms/ui/OAuthSignIn.mjs +4 -4
- llms/ui/ProviderStatus.mjs +12 -12
- llms/ui/Recents.mjs +13 -13
- llms/ui/SettingsDialog.mjs +65 -65
- llms/ui/Sidebar.mjs +17 -17
- llms/ui/SystemPromptEditor.mjs +5 -5
- llms/ui/SystemPromptSelector.mjs +4 -4
- llms/ui/Welcome.mjs +2 -2
- llms/ui/ai.mjs +1 -1
- llms/ui/app.css +386 -0
- llms/ui/markdown.mjs +8 -8
- llms/ui/tailwind.input.css +2 -0
- llms/ui/typography.css +50 -34
- {llms_py-2.0.26.dist-info → llms_py-2.0.27.dist-info}/METADATA +1 -1
- llms_py-2.0.27.dist-info/RECORD +48 -0
- llms_py-2.0.26.dist-info/RECORD +0 -48
- {llms_py-2.0.26.dist-info → llms_py-2.0.27.dist-info}/WHEEL +0 -0
- {llms_py-2.0.26.dist-info → llms_py-2.0.27.dist-info}/entry_points.txt +0 -0
- {llms_py-2.0.26.dist-info → llms_py-2.0.27.dist-info}/licenses/LICENSE +0 -0
- {llms_py-2.0.26.dist-info → llms_py-2.0.27.dist-info}/top_level.txt +0 -0
llms/ui/ChatPrompt.mjs
CHANGED
|
@@ -50,7 +50,7 @@ export default {
|
|
|
50
50
|
<button type="button"
|
|
51
51
|
@click="triggerFilePicker"
|
|
52
52
|
:disabled="isGenerating || !model"
|
|
53
|
-
class="size-8 flex items-center justify-center rounded-md border border-gray-300 text-gray-600 hover:bg-gray-50 disabled:text-gray-400 disabled:cursor-not-allowed"
|
|
53
|
+
class="size-8 flex items-center justify-center rounded-md border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed"
|
|
54
54
|
title="Attach image or audio">
|
|
55
55
|
<svg class="size-5" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 256 256">
|
|
56
56
|
<path d="M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z"></path>
|
|
@@ -64,8 +64,8 @@ export default {
|
|
|
64
64
|
<div>
|
|
65
65
|
<button type="button" title="Settings" @click="showSettings = true"
|
|
66
66
|
:disabled="isGenerating || !model"
|
|
67
|
-
class="size-8 flex items-center justify-center rounded-md border border-gray-300 text-gray-600 hover:bg-gray-50 disabled:text-gray-400 disabled:cursor-not-allowed">
|
|
68
|
-
<svg class="size-4 text-gray-600 disabled:text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 256 256"><path d="M40,88H73a32,32,0,0,0,62,0h81a8,8,0,0,0,0-16H135a32,32,0,0,0-62,0H40a8,8,0,0,0,0,16Zm64-24A16,16,0,1,1,88,80,16,16,0,0,1,104,64ZM216,168H199a32,32,0,0,0-62,0H40a8,8,0,0,0,0,16h97a32,32,0,0,0,62,0h17a8,8,0,0,0,0-16Zm-48,24a16,16,0,1,1,16-16A16,16,0,0,1,168,192Z"></path></svg>
|
|
67
|
+
class="size-8 flex items-center justify-center rounded-md border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed">
|
|
68
|
+
<svg class="size-4 text-gray-600 dark:text-gray-400 disabled:text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 256 256"><path d="M40,88H73a32,32,0,0,0,62,0h81a8,8,0,0,0,0-16H135a32,32,0,0,0-62,0H40a8,8,0,0,0,0,16Zm64-24A16,16,0,1,1,88,80,16,16,0,0,1,104,64ZM216,168H199a32,32,0,0,0-62,0H40a8,8,0,0,0,0,16h97a32,32,0,0,0,62,0h17a8,8,0,0,0,0-16Zm-48,24a16,16,0,1,1,16-16A16,16,0,0,1,168,192Z"></path></svg>
|
|
69
69
|
</button>
|
|
70
70
|
</div>
|
|
71
71
|
</div>
|
|
@@ -77,15 +77,24 @@ export default {
|
|
|
77
77
|
v-model="messageText"
|
|
78
78
|
@keydown.enter.exact.prevent="sendMessage"
|
|
79
79
|
@keydown.enter.shift.exact="addNewLine"
|
|
80
|
-
|
|
80
|
+
@paste="onPaste"
|
|
81
|
+
@dragover="onDragOver"
|
|
82
|
+
@dragleave="onDragLeave"
|
|
83
|
+
@drop="onDrop"
|
|
84
|
+
placeholder="Type message... (Enter to send, Shift+Enter for new line, drag & drop or paste files)"
|
|
81
85
|
rows="3"
|
|
82
|
-
class="
|
|
86
|
+
:class="[
|
|
87
|
+
'block w-full rounded-md border px-3 py-2 pr-12 text-sm text-gray-900 dark:text-gray-100 bg-white dark:bg-gray-900 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-1',
|
|
88
|
+
isDragging
|
|
89
|
+
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/30 ring-1 ring-blue-500'
|
|
90
|
+
: 'border-gray-300 dark:border-gray-600 focus:border-blue-500 focus:ring-blue-500'
|
|
91
|
+
]"
|
|
83
92
|
:disabled="isGenerating || !model"
|
|
84
93
|
></textarea>
|
|
85
94
|
<button title="Send (Enter)" type="button"
|
|
86
95
|
@click="sendMessage"
|
|
87
96
|
:disabled="!messageText.trim() || isGenerating || !model"
|
|
88
|
-
class="absolute bottom-2 right-2 size-8 flex items-center justify-center rounded-md border border-gray-300 text-gray-600 hover:bg-gray-50 disabled:text-gray-400 disabled:cursor-not-allowed disabled:border-gray-200 transition-colors">
|
|
97
|
+
class="absolute bottom-2 right-2 size-8 flex items-center justify-center rounded-md border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed disabled:border-gray-200 dark:disabled:border-gray-700 transition-colors">
|
|
89
98
|
<svg v-if="isGenerating" class="size-5 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
90
99
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
91
100
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
@@ -96,15 +105,15 @@ export default {
|
|
|
96
105
|
|
|
97
106
|
<!-- Attached files preview -->
|
|
98
107
|
<div v-if="attachedFiles.length" class="mt-2 flex flex-wrap gap-2">
|
|
99
|
-
<div v-for="(f,i) in attachedFiles" :key="i" class="flex items-center gap-2 px-2 py-1 rounded-md border border-gray-300 text-xs text-gray-700 bg-gray-50">
|
|
108
|
+
<div v-for="(f,i) in attachedFiles" :key="i" class="flex items-center gap-2 px-2 py-1 rounded-md border border-gray-300 dark:border-gray-600 text-xs text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-800">
|
|
100
109
|
<span class="truncate max-w-48" :title="f.name">{{ f.name }}</span>
|
|
101
|
-
<button type="button" class="text-gray-500 hover:text-gray-700" @click="removeAttachment(i)" title="Remove Attachment">
|
|
110
|
+
<button type="button" class="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200" @click="removeAttachment(i)" title="Remove Attachment">
|
|
102
111
|
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
|
103
112
|
</button>
|
|
104
113
|
</div>
|
|
105
114
|
</div>
|
|
106
115
|
|
|
107
|
-
<div v-if="!model" class="mt-2 text-sm text-red-600">
|
|
116
|
+
<div v-if="!model" class="mt-2 text-sm text-red-600 dark:text-red-400">
|
|
108
117
|
Please select a model
|
|
109
118
|
</div>
|
|
110
119
|
</div>
|
|
@@ -169,6 +178,93 @@ export default {
|
|
|
169
178
|
attachedFiles.value.splice(i, 1)
|
|
170
179
|
}
|
|
171
180
|
|
|
181
|
+
// Helper function to add files and set default message
|
|
182
|
+
const addFilesAndSetMessage = (files) => {
|
|
183
|
+
if (files.length === 0) return
|
|
184
|
+
|
|
185
|
+
attachedFiles.value.push(...files)
|
|
186
|
+
|
|
187
|
+
// Set default message text if empty
|
|
188
|
+
if (!messageText.value.trim()) {
|
|
189
|
+
if (hasImage()) {
|
|
190
|
+
messageText.value = getTextContent(config.defaults.image)
|
|
191
|
+
} else if (hasAudio()) {
|
|
192
|
+
messageText.value = getTextContent(config.defaults.audio)
|
|
193
|
+
} else {
|
|
194
|
+
messageText.value = getTextContent(config.defaults.file)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Handle paste events for clipboard images, audio, and files
|
|
200
|
+
const onPaste = async (e) => {
|
|
201
|
+
// Use the paste event's clipboardData directly (works best for paste events)
|
|
202
|
+
const items = e.clipboardData?.items
|
|
203
|
+
if (!items) return
|
|
204
|
+
|
|
205
|
+
const files = []
|
|
206
|
+
|
|
207
|
+
// Check all clipboard items
|
|
208
|
+
for (let i = 0; i < items.length; i++) {
|
|
209
|
+
const item = items[i]
|
|
210
|
+
|
|
211
|
+
// Handle files (images, audio, etc.)
|
|
212
|
+
if (item.kind === 'file') {
|
|
213
|
+
const file = item.getAsFile()
|
|
214
|
+
if (file) {
|
|
215
|
+
// Generate a better filename based on type
|
|
216
|
+
let filename = file.name
|
|
217
|
+
if (!filename || filename === 'image.png' || filename === 'blob') {
|
|
218
|
+
const ext = file.type.split('/')[1] || 'png'
|
|
219
|
+
const timestamp = new Date().getTime()
|
|
220
|
+
if (file.type.startsWith('image/')) {
|
|
221
|
+
filename = `pasted-image-${timestamp}.${ext}`
|
|
222
|
+
} else if (file.type.startsWith('audio/')) {
|
|
223
|
+
filename = `pasted-audio-${timestamp}.${ext}`
|
|
224
|
+
} else {
|
|
225
|
+
filename = `pasted-file-${timestamp}.${ext}`
|
|
226
|
+
}
|
|
227
|
+
// Create a new File object with the better name
|
|
228
|
+
files.push(new File([file], filename, { type: file.type }))
|
|
229
|
+
} else {
|
|
230
|
+
files.push(file)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (files.length > 0) {
|
|
237
|
+
e.preventDefault()
|
|
238
|
+
addFilesAndSetMessage(files)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Handle drag and drop events
|
|
243
|
+
const isDragging = ref(false)
|
|
244
|
+
|
|
245
|
+
const onDragOver = (e) => {
|
|
246
|
+
e.preventDefault()
|
|
247
|
+
e.stopPropagation()
|
|
248
|
+
isDragging.value = true
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const onDragLeave = (e) => {
|
|
252
|
+
e.preventDefault()
|
|
253
|
+
e.stopPropagation()
|
|
254
|
+
isDragging.value = false
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const onDrop = (e) => {
|
|
258
|
+
e.preventDefault()
|
|
259
|
+
e.stopPropagation()
|
|
260
|
+
isDragging.value = false
|
|
261
|
+
|
|
262
|
+
const files = Array.from(e.dataTransfer?.files || [])
|
|
263
|
+
if (files.length > 0) {
|
|
264
|
+
addFilesAndSetMessage(files)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
172
268
|
function createChatRequest() {
|
|
173
269
|
if (hasImage()) {
|
|
174
270
|
return deepClone(config.defaults.image)
|
|
@@ -433,8 +529,13 @@ export default {
|
|
|
433
529
|
messageText,
|
|
434
530
|
fileInput,
|
|
435
531
|
showSettings,
|
|
532
|
+
isDragging,
|
|
436
533
|
triggerFilePicker,
|
|
437
534
|
onFilesSelected,
|
|
535
|
+
onPaste,
|
|
536
|
+
onDragOver,
|
|
537
|
+
onDragLeave,
|
|
538
|
+
onDrop,
|
|
438
539
|
removeAttachment,
|
|
439
540
|
sendMessage,
|
|
440
541
|
addNewLine,
|
llms/ui/Main.mjs
CHANGED
|
@@ -30,7 +30,7 @@ export default {
|
|
|
30
30
|
template: `
|
|
31
31
|
<div class="flex flex-col h-full w-full">
|
|
32
32
|
<!-- Header with model and prompt selectors (hidden when auth required and not authenticated) -->
|
|
33
|
-
<div v-if="!($ai.requiresAuth && !$ai.auth)" class="border-b border-gray-200 bg-white px-2 py-2 w-full min-h-16">
|
|
33
|
+
<div v-if="!($ai.requiresAuth && !$ai.auth)" class="border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-2 py-2 w-full min-h-16">
|
|
34
34
|
<div class="flex items-center justify-between w-full">
|
|
35
35
|
<ModelSelector :models="models" v-model="selectedModel" @updated="configUpdated" />
|
|
36
36
|
|
|
@@ -38,6 +38,7 @@ export default {
|
|
|
38
38
|
<SystemPromptSelector :prompts="prompts" v-model="selectedPrompt"
|
|
39
39
|
:show="showSystemPrompt" @toggle="showSystemPrompt = !showSystemPrompt" />
|
|
40
40
|
<Avatar />
|
|
41
|
+
<DarkModeToggle />
|
|
41
42
|
</div>
|
|
42
43
|
</div>
|
|
43
44
|
</div>
|
|
@@ -67,7 +68,7 @@ export default {
|
|
|
67
68
|
@click="(e) => e.altKey ? exportRequests() : exportThreads()"
|
|
68
69
|
:disabled="isExporting"
|
|
69
70
|
:title="'Export ' + threads?.threads?.value?.length + ' conversations'"
|
|
70
|
-
class="inline-flex items-center px-3 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
71
|
+
class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
71
72
|
>
|
|
72
73
|
<svg v-if="!isExporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
73
74
|
<path fill="currentColor" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z"></path>
|
|
@@ -83,7 +84,7 @@ export default {
|
|
|
83
84
|
@click="triggerImport"
|
|
84
85
|
:disabled="isImporting"
|
|
85
86
|
title="Import conversations from JSON file"
|
|
86
|
-
class="inline-flex items-center px-3 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
87
|
+
class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
87
88
|
>
|
|
88
89
|
<svg v-if="!isImporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
89
90
|
<path fill="currentColor" d="m14 12l-4-4v3H2v2h8v3m10 2V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v3h2V6h12v12H6v-3H4v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2"/>
|
|
@@ -119,15 +120,15 @@ export default {
|
|
|
119
120
|
<div class="flex-shrink-0 flex flex-col justify-center">
|
|
120
121
|
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium"
|
|
121
122
|
:class="message.role === 'user'
|
|
122
|
-
? 'bg-blue-
|
|
123
|
-
: 'bg-gray-600 text-white'"
|
|
123
|
+
? 'bg-blue-100 dark:bg-blue-900 text-gray-900 dark:text-gray-100 border border-blue-200 dark:border-blue-700'
|
|
124
|
+
: 'bg-gray-600 dark:bg-gray-500 text-white'"
|
|
124
125
|
>
|
|
125
126
|
{{ message.role === 'user' ? 'U' : 'AI' }}
|
|
126
127
|
</div>
|
|
127
128
|
|
|
128
129
|
<!-- Delete button (shown on hover) -->
|
|
129
130
|
<button type="button" @click.stop="threads.deleteMessageFromThread(currentThread.id, message.id)"
|
|
130
|
-
class="mx-auto opacity-0 group-hover:opacity-100 mt-2 rounded text-gray-400 hover:text-red-600 hover:bg-red-50 transition-all"
|
|
131
|
+
class="mx-auto opacity-0 group-hover:opacity-100 mt-2 rounded text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 transition-all"
|
|
131
132
|
title="Delete message">
|
|
132
133
|
<svg class="size-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
133
134
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
|
@@ -139,18 +140,18 @@ export default {
|
|
|
139
140
|
<div
|
|
140
141
|
class="message rounded-lg px-4 py-3 relative group"
|
|
141
142
|
:class="message.role === 'user'
|
|
142
|
-
? 'bg-blue-
|
|
143
|
-
: 'bg-gray-100 text-gray-900 border border-gray-200'"
|
|
143
|
+
? 'bg-blue-100 dark:bg-blue-900 text-gray-900 dark:text-gray-100 border border-blue-200 dark:border-blue-700'
|
|
144
|
+
: 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100 border border-gray-200 dark:border-gray-700'"
|
|
144
145
|
>
|
|
145
146
|
<!-- Copy button in top right corner -->
|
|
146
147
|
<button
|
|
147
148
|
type="button"
|
|
148
149
|
@click="copyMessageContent(message)"
|
|
149
|
-
class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 p-1 rounded hover:bg-black/10 focus:outline-none focus:ring-0"
|
|
150
|
-
:class="message.role === 'user' ? 'text-
|
|
150
|
+
class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 p-1 rounded hover:bg-black/10 dark:hover:bg-white/10 focus:outline-none focus:ring-0"
|
|
151
|
+
:class="message.role === 'user' ? 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200' : 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200'"
|
|
151
152
|
title="Copy message content"
|
|
152
153
|
>
|
|
153
|
-
<svg v-if="copying === message" class="size-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>
|
|
154
|
+
<svg v-if="copying === message" class="size-4 text-green-500 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>
|
|
154
155
|
<svg v-else class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
155
156
|
<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
|
|
156
157
|
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
|
|
@@ -160,18 +161,18 @@ export default {
|
|
|
160
161
|
<div
|
|
161
162
|
v-if="message.role === 'assistant'"
|
|
162
163
|
v-html="renderMarkdown(message.content)"
|
|
163
|
-
class="prose prose-sm max-w-none"
|
|
164
|
+
class="prose prose-sm max-w-none dark:prose-invert"
|
|
164
165
|
></div>
|
|
165
166
|
|
|
166
167
|
<!-- Collapsible reasoning section -->
|
|
167
168
|
<div v-if="message.role === 'assistant' && message.reasoning" class="mt-2">
|
|
168
|
-
<button type="button" @click="toggleReasoning(message.id)" class="text-xs text-gray-600 hover:text-gray-800 flex items-center space-x-1">
|
|
169
|
+
<button type="button" @click="toggleReasoning(message.id)" class="text-xs text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 flex items-center space-x-1">
|
|
169
170
|
<svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" :class="isReasoningExpanded(message.id) ? 'transform rotate-90' : ''"><path fill="currentColor" d="M7 5l6 5l-6 5z"/></svg>
|
|
170
171
|
<span>{{ isReasoningExpanded(message.id) ? 'Hide reasoning' : 'Show reasoning' }}</span>
|
|
171
172
|
</button>
|
|
172
|
-
<div v-if="isReasoningExpanded(message.id)" class="mt-2 rounded border border-gray-200 bg-gray-50 p-2">
|
|
173
|
-
<div v-if="typeof message.reasoning === 'string'" v-html="renderMarkdown(message.reasoning)" class="prose prose-xs max-w-none"></div>
|
|
174
|
-
<pre v-else class="text-xs whitespace-pre-wrap overflow-x-auto">{{ formatReasoning(message.reasoning) }}</pre>
|
|
173
|
+
<div v-if="isReasoningExpanded(message.id)" class="mt-2 rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 p-2">
|
|
174
|
+
<div v-if="typeof message.reasoning === 'string'" v-html="renderMarkdown(message.reasoning)" class="prose prose-xs max-w-none dark:prose-invert"></div>
|
|
175
|
+
<pre v-else class="text-xs whitespace-pre-wrap overflow-x-auto text-gray-900 dark:text-gray-100">{{ formatReasoning(message.reasoning) }}</pre>
|
|
175
176
|
</div>
|
|
176
177
|
</div>
|
|
177
178
|
|
|
@@ -190,7 +191,7 @@ export default {
|
|
|
190
191
|
<!-- Edit and Redo buttons (shown on hover for user messages, outside bubble) -->
|
|
191
192
|
<div v-if="message.role === 'user'" class="flex flex-col gap-2 opacity-0 group-hover:opacity-100 transition-opacity mt-1">
|
|
192
193
|
<button type="button" @click.stop="editMessage(message)"
|
|
193
|
-
class="whitespace-nowrap text-xs px-2 py-1 rounded text-gray-400 hover:text-green-600 hover:bg-green-50 transition-all"
|
|
194
|
+
class="whitespace-nowrap text-xs px-2 py-1 rounded text-gray-400 dark:text-gray-500 hover:text-green-600 dark:hover:text-green-400 hover:bg-green-50 dark:hover:bg-green-900/30 transition-all"
|
|
194
195
|
title="Edit message">
|
|
195
196
|
<svg class="size-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
196
197
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
|
@@ -198,7 +199,7 @@ export default {
|
|
|
198
199
|
Edit
|
|
199
200
|
</button>
|
|
200
201
|
<button type="button" @click.stop="redoMessage(message)"
|
|
201
|
-
class="whitespace-nowrap text-xs px-2 py-1 rounded text-gray-400 hover:text-blue-600 hover:bg-blue-50 transition-all"
|
|
202
|
+
class="whitespace-nowrap text-xs px-2 py-1 rounded text-gray-400 dark:text-gray-500 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/30 transition-all"
|
|
202
203
|
title="Redo message (clears all responses after this message and re-runs it)">
|
|
203
204
|
<svg class="size-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
204
205
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
|
@@ -208,7 +209,7 @@ export default {
|
|
|
208
209
|
</div>
|
|
209
210
|
</div>
|
|
210
211
|
|
|
211
|
-
<div v-if="currentThread.stats && currentThread.stats.outputTokens" class="text-center text-gray-500 text-sm">
|
|
212
|
+
<div v-if="currentThread.stats && currentThread.stats.outputTokens" class="text-center text-gray-500 dark:text-gray-400 text-sm">
|
|
212
213
|
<span :title="statsTitle(currentThread.stats)">
|
|
213
214
|
{{ currentThread.stats.cost ? formatCost(currentThread.stats.cost) + ' for ' : '' }} {{ humanifyNumber(currentThread.stats.inputTokens) }} → {{ humanifyNumber(currentThread.stats.outputTokens) }} tokens over {{ currentThread.stats.requests }} request{{currentThread.stats.requests===1?'':'s'}} in {{ humanifyMs(currentThread.stats.duration) }}
|
|
214
215
|
</span>
|
|
@@ -218,17 +219,17 @@ export default {
|
|
|
218
219
|
<div v-if="isGenerating" class="flex items-start space-x-3">
|
|
219
220
|
<!-- Avatar outside the bubble -->
|
|
220
221
|
<div class="flex-shrink-0">
|
|
221
|
-
<div class="w-8 h-8 rounded-full bg-gray-600 text-white flex items-center justify-center text-sm font-medium">
|
|
222
|
+
<div class="w-8 h-8 rounded-full bg-gray-600 dark:bg-gray-500 text-white flex items-center justify-center text-sm font-medium">
|
|
222
223
|
AI
|
|
223
224
|
</div>
|
|
224
225
|
</div>
|
|
225
226
|
|
|
226
227
|
<!-- Loading bubble -->
|
|
227
|
-
<div class="rounded-lg px-4 py-3 bg-gray-100 border border-gray-200">
|
|
228
|
+
<div class="rounded-lg px-4 py-3 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
|
228
229
|
<div class="flex space-x-1">
|
|
229
|
-
<div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
|
|
230
|
-
<div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.1s"></div>
|
|
231
|
-
<div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
|
|
230
|
+
<div class="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce"></div>
|
|
231
|
+
<div class="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce" style="animation-delay: 0.1s"></div>
|
|
232
|
+
<div class="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
|
|
232
233
|
</div>
|
|
233
234
|
</div>
|
|
234
235
|
</div>
|
|
@@ -237,24 +238,24 @@ export default {
|
|
|
237
238
|
<div v-if="errorStatus" class="flex items-start space-x-3">
|
|
238
239
|
<!-- Avatar outside the bubble -->
|
|
239
240
|
<div class="flex-shrink-0">
|
|
240
|
-
<div class="w-8 h-8 rounded-full bg-red-600 text-white flex items-center justify-center text-sm font-medium">
|
|
241
|
+
<div class="w-8 h-8 rounded-full bg-red-600 dark:bg-red-500 text-white flex items-center justify-center text-sm font-medium">
|
|
241
242
|
!
|
|
242
243
|
</div>
|
|
243
244
|
</div>
|
|
244
245
|
|
|
245
246
|
<!-- Error bubble -->
|
|
246
|
-
<div class="max-w-[85%] rounded-lg px-4 py-3 bg-red-50 border border-red-200 text-red-800 shadow-sm">
|
|
247
|
+
<div class="max-w-[85%] rounded-lg px-4 py-3 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 text-red-800 dark:text-red-200 shadow-sm">
|
|
247
248
|
<div class="flex items-start space-x-2">
|
|
248
249
|
<div class="flex-1 min-w-0">
|
|
249
250
|
<div class="text-base font-medium mb-1">{{ errorStatus?.errorCode || 'Error' }}</div>
|
|
250
251
|
<div v-if="errorStatus?.message" class="text-base mb-1">{{ errorStatus.message }}</div>
|
|
251
|
-
<div v-if="errorStatus?.stackTrace" class="text-sm whitespace-pre-wrap break-words max-h-80 overflow-y-auto font-mono p-2 rounded">
|
|
252
|
+
<div v-if="errorStatus?.stackTrace" class="text-sm whitespace-pre-wrap break-words max-h-80 overflow-y-auto font-mono p-2 rounded bg-red-100 dark:bg-red-950/50">
|
|
252
253
|
{{ errorStatus.stackTrace }}
|
|
253
254
|
</div>
|
|
254
255
|
</div>
|
|
255
256
|
<button type="button"
|
|
256
257
|
@click="errorStatus = null"
|
|
257
|
-
class="text-red-400 hover:text-red-600 flex-shrink-0"
|
|
258
|
+
class="text-red-400 dark:text-red-300 hover:text-red-600 dark:hover:text-red-100 flex-shrink-0"
|
|
258
259
|
>
|
|
259
260
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
260
261
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
@@ -268,21 +269,21 @@ export default {
|
|
|
268
269
|
|
|
269
270
|
<!-- Edit message modal -->
|
|
270
271
|
<div v-if="editingMessageId" class="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
271
|
-
<div class="relative bg-white rounded-lg shadow-lg p-6 max-w-2xl w-full mx-4">
|
|
272
|
+
<div class="relative bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 max-w-2xl w-full mx-4">
|
|
272
273
|
<CloseButton @click="cancelEdit" class="" />
|
|
273
|
-
<h3 class="text-lg font-semibold text-gray-900 mb-4">Edit Message</h3>
|
|
274
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Edit Message</h3>
|
|
274
275
|
<textarea
|
|
275
276
|
v-model="editingMessageContent"
|
|
276
|
-
class="w-full h-40 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
|
277
|
+
class="w-full h-40 px-3 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
|
277
278
|
placeholder="Edit your message..."
|
|
278
279
|
></textarea>
|
|
279
280
|
<div class="mt-4 flex gap-2 justify-end">
|
|
280
281
|
<button type="button" @click="cancelEdit"
|
|
281
|
-
class="px-4 py-2 rounded-md border border-gray-300 text-gray-700 hover:bg-gray-50 transition-all">
|
|
282
|
+
class="px-4 py-2 rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-all">
|
|
282
283
|
Cancel
|
|
283
284
|
</button>
|
|
284
285
|
<button type="button" @click="saveEditedMessage"
|
|
285
|
-
class="px-4 py-2 rounded-md bg-blue-600 text-white hover:bg-blue-700 transition-all">
|
|
286
|
+
class="px-4 py-2 rounded-md bg-blue-600 dark:bg-blue-500 text-white hover:bg-blue-700 dark:hover:bg-blue-600 transition-all">
|
|
286
287
|
Save
|
|
287
288
|
</button>
|
|
288
289
|
</div>
|
|
@@ -291,7 +292,7 @@ export default {
|
|
|
291
292
|
</div>
|
|
292
293
|
|
|
293
294
|
<!-- Input Area - only show when thread is selected -->
|
|
294
|
-
<div v-if="currentThread" class="flex-shrink-0 border-t border-gray-200 bg-white px-6 py-4">
|
|
295
|
+
<div v-if="currentThread" class="flex-shrink-0 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-6 py-4">
|
|
295
296
|
<ChatPrompt :model="selectedModel" :systemPrompt="currentSystemPrompt" />
|
|
296
297
|
</div>
|
|
297
298
|
</div>
|
llms/ui/ModelSelector.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import ProviderStatus from "./ProviderStatus.mjs"
|
|
2
2
|
import ProviderIcon from "./ProviderIcon.mjs"
|
|
3
|
-
import { useFormatters } from "@servicestack/vue"
|
|
4
3
|
|
|
5
4
|
export default {
|
|
6
5
|
components: {
|
|
@@ -20,15 +19,15 @@ export default {
|
|
|
20
19
|
<span :title="id">{{id}}</span>
|
|
21
20
|
<span class="flex items-center space-x-1">
|
|
22
21
|
<span v-if="pricing && (parseFloat(pricing.input) == 0 && parseFloat(pricing.input) == 0)">
|
|
23
|
-
<span class="text-xs text-gray-500" title="Free to use">FREE</span>
|
|
22
|
+
<span class="text-xs text-gray-500 dark:text-gray-400" title="Free to use">FREE</span>
|
|
24
23
|
</span>
|
|
25
|
-
<span v-else-if="pricing" class="text-xs text-gray-500"
|
|
24
|
+
<span v-else-if="pricing" class="text-xs text-gray-500 dark:text-gray-400"
|
|
26
25
|
:title="'Estimated Cost per token: ' + pricing.input + ' input | ' + pricing.output + ' output'">
|
|
27
26
|
{{tokenPrice(pricing.input)}}
|
|
28
27
|
·
|
|
29
28
|
{{tokenPrice(pricing.output)}} M
|
|
30
29
|
</span>
|
|
31
|
-
<span :title="provider_model + ' from ' + provider">
|
|
30
|
+
<span :title="provider_model + ' from ' + provider">
|
|
32
31
|
<ProviderIcon :provider="provider" />
|
|
33
32
|
</span>
|
|
34
33
|
</span>
|
llms/ui/OAuthSignIn.mjs
CHANGED
|
@@ -11,14 +11,14 @@ export default {
|
|
|
11
11
|
<Welcome />
|
|
12
12
|
</div>
|
|
13
13
|
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
|
14
|
-
<div v-if="errorMessage" class="mb-3 bg-red-50 border border-red-200 text-red-800 rounded-lg px-4 py-3">
|
|
14
|
+
<div v-if="errorMessage" class="mb-3 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 text-red-800 dark:text-red-200 rounded-lg px-4 py-3">
|
|
15
15
|
<div class="flex items-start space-x-2">
|
|
16
16
|
<div class="flex-1">
|
|
17
17
|
<div class="text-base font-medium">{{ errorMessage }}</div>
|
|
18
18
|
</div>
|
|
19
19
|
<button type="button"
|
|
20
20
|
@click="errorMessage = null"
|
|
21
|
-
class="text-red-400 hover:text-red-600 flex-shrink-0"
|
|
21
|
+
class="text-red-400 dark:text-red-300 hover:text-red-600 dark:hover:text-red-100 flex-shrink-0"
|
|
22
22
|
>
|
|
23
23
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
24
24
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
@@ -28,10 +28,10 @@ export default {
|
|
|
28
28
|
</div>
|
|
29
29
|
<div class="py-8 px-4 sm:px-10">
|
|
30
30
|
<div class="space-y-4">
|
|
31
|
-
<button
|
|
31
|
+
<button
|
|
32
32
|
type="button"
|
|
33
33
|
@click="signInWithGitHub"
|
|
34
|
-
class="w-full inline-flex items-center justify-center px-4 py-3 border border-gray-300 rounded-md shadow-sm text-base font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 transition-colors"
|
|
34
|
+
class="w-full inline-flex items-center justify-center px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-base font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 transition-colors"
|
|
35
35
|
>
|
|
36
36
|
<svg class="w-6 h-6 mr-3" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
37
37
|
<path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd" />
|
llms/ui/ProviderStatus.mjs
CHANGED
|
@@ -3,25 +3,25 @@ import { ref, computed, inject, onMounted, onUnmounted } from "vue"
|
|
|
3
3
|
export default {
|
|
4
4
|
template:`
|
|
5
5
|
<div v-if="$ai.isAdmin" ref="triggerRef" class="relative" :key="renderKey">
|
|
6
|
-
<button type="button" @click="togglePopover"
|
|
7
|
-
class="mt-1 flex space-x-2 items-center text-sm font-semibold select-none rounded-sm py-2 px-3 border border-transparent hover:bg-gray-50 hover:shadow hover:border-gray-200">
|
|
8
|
-
<span class="text-gray-600" :title="models.length + ' models from ' + (config.status.enabled||[]).length + ' enabled providers'">{{models.length}}</span>
|
|
6
|
+
<button type="button" @click="togglePopover"
|
|
7
|
+
class="mt-1 flex space-x-2 items-center text-sm font-semibold select-none rounded-sm py-2 px-3 border border-transparent hover:bg-gray-50 dark:hover:bg-gray-700 hover:shadow hover:border-gray-200 dark:hover:border-gray-600">
|
|
8
|
+
<span class="text-gray-600 dark:text-gray-400" :title="models.length + ' models from ' + (config.status.enabled||[]).length + ' enabled providers'">{{models.length}}</span>
|
|
9
9
|
<div class="cursor-pointer flex items-center" :title="'Enabled:\\n' + (config.status.enabled||[]).map(x => ' ' + x).join('\\n')">
|
|
10
|
-
<svg class="size-4 text-green-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="currentColor"/></svg>
|
|
11
|
-
<span class="text-green-700">{{(config.status.enabled||[]).length}}</span>
|
|
10
|
+
<svg class="size-4 text-green-400 dark:text-green-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="currentColor"/></svg>
|
|
11
|
+
<span class="text-green-700 dark:text-green-400">{{(config.status.enabled||[]).length}}</span>
|
|
12
12
|
</div>
|
|
13
13
|
<div class="cursor-pointer flex items-center" :title="'Disabled:\\n' + (config.status.disabled||[]).map(x => ' ' + x).join('\\n')">
|
|
14
|
-
<svg class="size-4 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="currentColor"/></svg>
|
|
15
|
-
<span class="text-red-700">{{(config.status.disabled||[]).length}}</span>
|
|
14
|
+
<svg class="size-4 text-red-400 dark:text-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="currentColor"/></svg>
|
|
15
|
+
<span class="text-red-700 dark:text-red-400">{{(config.status.disabled||[]).length}}</span>
|
|
16
16
|
</div>
|
|
17
17
|
</button>
|
|
18
|
-
<div v-if="showPopover" ref="popoverRef" class="absolute right-0 mt-2 w-72 max-h-120 overflow-y-auto bg-white border border-gray-200 rounded-md shadow-lg z-10">
|
|
19
|
-
<div class="divide-y divide-gray-100">
|
|
18
|
+
<div v-if="showPopover" ref="popoverRef" class="absolute right-0 mt-2 w-72 max-h-120 overflow-y-auto bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md shadow-lg z-10">
|
|
19
|
+
<div class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
20
20
|
<div v-for="p in allProviders" :key="p" class="flex items-center justify-between px-3 py-2">
|
|
21
|
-
<label :for="'chk_' + p" class="cursor-pointer text-sm text-gray-900 truncate mr-2" :title="p">{{ p }}</label>
|
|
21
|
+
<label :for="'chk_' + p" class="cursor-pointer text-sm text-gray-900 dark:text-gray-100 truncate mr-2" :title="p">{{ p }}</label>
|
|
22
22
|
<div @click="onToggle(p, !isEnabled(p))" class="cursor-pointer group relative inline-flex h-5 w-10 shrink-0 items-center justify-center rounded-full outline-offset-2 outline-green-600 has-focus-visible:outline-2">
|
|
23
|
-
<span class="absolute mx-auto h-4 w-9 rounded-full bg-gray-200 inset-ring inset-ring-gray-900/5 transition-colors duration-200 ease-in-out group-has-checked:bg-green-600" />
|
|
24
|
-
<span class="absolute left-0 size-5 rounded-full border border-gray-300 bg-white shadow-xs transition-transform duration-200 ease-in-out group-has-checked:translate-x-5" />
|
|
23
|
+
<span class="absolute mx-auto h-4 w-9 rounded-full bg-gray-200 dark:bg-gray-700 inset-ring inset-ring-gray-900/5 dark:inset-ring-gray-100/5 transition-colors duration-200 ease-in-out group-has-checked:bg-green-600 dark:group-has-checked:bg-green-500" />
|
|
24
|
+
<span class="absolute left-0 size-5 rounded-full border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-200 shadow-xs transition-transform duration-200 ease-in-out group-has-checked:translate-x-5" />
|
|
25
25
|
<input :id="'chk_' + p" type="checkbox" :checked="isEnabled(p)" class="switch cursor-pointer absolute inset-0 appearance-none focus:outline-hidden" aria-label="Use setting" name="setting" />
|
|
26
26
|
</div>
|
|
27
27
|
</div>
|
llms/ui/Recents.mjs
CHANGED
|
@@ -7,33 +7,33 @@ const RecentResults = {
|
|
|
7
7
|
template:`
|
|
8
8
|
<div class="flex-1 overflow-y-auto" @scroll="onScroll">
|
|
9
9
|
<div class="mx-auto max-w-6xl px-4 py-4">
|
|
10
|
-
<div class="text-sm text-gray-600 mb-3" v-if="threads.length">
|
|
10
|
+
<div class="text-sm text-gray-600 dark:text-gray-400 mb-3" v-if="threads.length">
|
|
11
11
|
<span v-if="q">{{ filtered.length }} result{{ filtered.length===1?'':'s' }}</span>
|
|
12
12
|
<span v-else>Searching {{ threads.length }} conversation{{ threads.length===1?'':'s' }}</span>
|
|
13
13
|
</div>
|
|
14
14
|
|
|
15
|
-
<div v-if="!threads.length" class="text-gray-500">No conversations yet.</div>
|
|
15
|
+
<div v-if="!threads.length" class="text-gray-500 dark:text-gray-400">No conversations yet.</div>
|
|
16
16
|
|
|
17
17
|
<table class="w-full">
|
|
18
18
|
<tbody>
|
|
19
|
-
<tr v-for="t in displayed" :key="t.id" class="hover:bg-gray-50">
|
|
20
|
-
<td class="py-3 px-1 border-b border-gray-200 max-w-3xl">
|
|
19
|
+
<tr v-for="t in displayed" :key="t.id" class="hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
20
|
+
<td class="py-3 px-1 border-b border-gray-200 dark:border-gray-700 max-w-3xl">
|
|
21
21
|
<button type="button" @click="open(t.id)" class="w-full text-left">
|
|
22
22
|
<div class="flex items-start justify-between gap-3">
|
|
23
23
|
<div class="min-w-0 flex-1">
|
|
24
|
-
<div class="font-medium text-gray-900 truncate" :title="t.title">{{ t.title || 'Untitled chat' }}</div>
|
|
25
|
-
<div class="mt-1 text-sm text-gray-600 line-clamp-2">
|
|
24
|
+
<div class="font-medium text-gray-900 dark:text-gray-100 truncate" :title="t.title">{{ t.title || 'Untitled chat' }}</div>
|
|
25
|
+
<div class="mt-1 text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
|
|
26
26
|
<div v-html="snippet(t)"></div>
|
|
27
27
|
</div>
|
|
28
28
|
</div>
|
|
29
29
|
</div>
|
|
30
30
|
</button>
|
|
31
31
|
</td>
|
|
32
|
-
<td class="py-3 px-1 border-b border-gray-200">
|
|
32
|
+
<td class="py-3 px-1 border-b border-gray-200 dark:border-gray-700">
|
|
33
33
|
<div class="text-right whitespace-nowrap">
|
|
34
|
-
<div class="text-xs text-gray-500">{{ formatDate(t.updatedAt || t.createdAt) }}</div>
|
|
35
|
-
<div class="text-[11px] text-gray-500/80">{{ (t.messages?.length || 0) }} messages</div>
|
|
36
|
-
<div v-if="t.model" class="text-[11px] text-blue-600">{{ t.model }}</div>
|
|
34
|
+
<div class="text-xs text-gray-500 dark:text-gray-400">{{ formatDate(t.updatedAt || t.createdAt) }}</div>
|
|
35
|
+
<div class="text-[11px] text-gray-500/80 dark:text-gray-400/80">{{ (t.messages?.length || 0) }} messages</div>
|
|
36
|
+
<div v-if="t.model" class="text-[11px] text-blue-600 dark:text-blue-400">{{ t.model }}</div>
|
|
37
37
|
</div>
|
|
38
38
|
</td>
|
|
39
39
|
</tr>
|
|
@@ -152,16 +152,16 @@ export default {
|
|
|
152
152
|
template: `
|
|
153
153
|
<div class="flex flex-col h-full w-full">
|
|
154
154
|
<!-- Header -->
|
|
155
|
-
<div class="border-b border-gray-200 bg-white px-4 py-3 min-h-16">
|
|
155
|
+
<div class="border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-4 py-3 min-h-16">
|
|
156
156
|
<div class="max-w-6xl mx-auto flex items-center justify-between gap-3">
|
|
157
|
-
<h2 class="text-lg font-semibold text-gray-900">Search Chats</h2>
|
|
157
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Search Chats</h2>
|
|
158
158
|
<div class="flex-1 flex items-center gap-2">
|
|
159
159
|
<input
|
|
160
160
|
v-model="q"
|
|
161
161
|
type="search"
|
|
162
162
|
placeholder="Search titles and messages..."
|
|
163
163
|
spellcheck="false"
|
|
164
|
-
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm placeholder-gray-500 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
164
|
+
class="w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 px-3 py-2 text-sm placeholder-gray-500 dark:placeholder-gray-400 focus:border-blue-500 dark:focus:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:focus:ring-blue-400"
|
|
165
165
|
/>
|
|
166
166
|
</div>
|
|
167
167
|
</div>
|