llms-py 2.0.25__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 +9 -0
- llms/main.py +254 -4
- llms/ui/Analytics.mjs +73 -73
- llms/ui/App.mjs +8 -3
- llms/ui/Avatar.mjs +61 -4
- llms/ui/Brand.mjs +4 -4
- llms/ui/ChatPrompt.mjs +110 -9
- llms/ui/Main.mjs +42 -38
- llms/ui/ModelSelector.mjs +3 -4
- llms/ui/OAuthSignIn.mjs +92 -0
- 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 +68 -5
- llms/ui/app.css +422 -0
- llms/ui/markdown.mjs +8 -8
- llms/ui/tailwind.input.css +2 -0
- llms/ui/typography.css +50 -34
- {llms_py-2.0.25.dist-info → llms_py-2.0.27.dist-info}/METADATA +59 -51
- llms_py-2.0.27.dist-info/RECORD +48 -0
- llms_py-2.0.25.dist-info/RECORD +0 -47
- {llms_py-2.0.25.dist-info → llms_py-2.0.27.dist-info}/WHEEL +0 -0
- {llms_py-2.0.25.dist-info → llms_py-2.0.27.dist-info}/entry_points.txt +0 -0
- {llms_py-2.0.25.dist-info → llms_py-2.0.27.dist-info}/licenses/LICENSE +0 -0
- {llms_py-2.0.25.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
|
@@ -6,6 +6,7 @@ import { storageObject, addCopyButtons, formatCost, statsTitle } from './utils.m
|
|
|
6
6
|
import { renderMarkdown } from './markdown.mjs'
|
|
7
7
|
import ChatPrompt, { useChatPrompt } from './ChatPrompt.mjs'
|
|
8
8
|
import SignIn from './SignIn.mjs'
|
|
9
|
+
import OAuthSignIn from './OAuthSignIn.mjs'
|
|
9
10
|
import Avatar from './Avatar.mjs'
|
|
10
11
|
import ModelSelector from './ModelSelector.mjs'
|
|
11
12
|
import SystemPromptSelector from './SystemPromptSelector.mjs'
|
|
@@ -22,32 +23,35 @@ export default {
|
|
|
22
23
|
SystemPromptEditor,
|
|
23
24
|
ChatPrompt,
|
|
24
25
|
SignIn,
|
|
26
|
+
OAuthSignIn,
|
|
25
27
|
Avatar,
|
|
26
28
|
Welcome,
|
|
27
29
|
},
|
|
28
30
|
template: `
|
|
29
31
|
<div class="flex flex-col h-full w-full">
|
|
30
|
-
<!-- Header with model and prompt selectors -->
|
|
31
|
-
<div class="border-b border-gray-200 bg-white px-2 py-2 w-full min-h-16">
|
|
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 dark:border-gray-700 bg-white dark:bg-gray-800 px-2 py-2 w-full min-h-16">
|
|
32
34
|
<div class="flex items-center justify-between w-full">
|
|
33
35
|
<ModelSelector :models="models" v-model="selectedModel" @updated="configUpdated" />
|
|
34
36
|
|
|
35
37
|
<div class="flex items-center space-x-2">
|
|
36
|
-
<SystemPromptSelector :prompts="prompts" v-model="selectedPrompt"
|
|
38
|
+
<SystemPromptSelector :prompts="prompts" v-model="selectedPrompt"
|
|
37
39
|
:show="showSystemPrompt" @toggle="showSystemPrompt = !showSystemPrompt" />
|
|
38
40
|
<Avatar />
|
|
41
|
+
<DarkModeToggle />
|
|
39
42
|
</div>
|
|
40
43
|
</div>
|
|
41
44
|
</div>
|
|
42
45
|
|
|
43
|
-
<SystemPromptEditor v-if="showSystemPrompt"
|
|
46
|
+
<SystemPromptEditor v-if="showSystemPrompt && !($ai.requiresAuth && !$ai.auth)"
|
|
44
47
|
v-model="currentSystemPrompt" :prompts="prompts" :selected="selectedPrompt" />
|
|
45
48
|
|
|
46
49
|
<!-- Messages Area -->
|
|
47
50
|
<div class="flex-1 overflow-y-auto" ref="messagesContainer">
|
|
48
51
|
<div class="mx-auto max-w-6xl px-4 py-6">
|
|
49
52
|
<div v-if="$ai.requiresAuth && !$ai.auth">
|
|
50
|
-
<
|
|
53
|
+
<OAuthSignIn v-if="$ai.authType === 'oauth'" @done="$ai.signIn($event)" />
|
|
54
|
+
<SignIn v-else @done="$ai.signIn($event)" />
|
|
51
55
|
</div>
|
|
52
56
|
<!-- Welcome message when no thread is selected -->
|
|
53
57
|
<div v-else-if="!currentThread" class="text-center py-12">
|
|
@@ -64,7 +68,7 @@ export default {
|
|
|
64
68
|
@click="(e) => e.altKey ? exportRequests() : exportThreads()"
|
|
65
69
|
:disabled="isExporting"
|
|
66
70
|
:title="'Export ' + threads?.threads?.value?.length + ' conversations'"
|
|
67
|
-
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"
|
|
68
72
|
>
|
|
69
73
|
<svg v-if="!isExporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
70
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>
|
|
@@ -80,7 +84,7 @@ export default {
|
|
|
80
84
|
@click="triggerImport"
|
|
81
85
|
:disabled="isImporting"
|
|
82
86
|
title="Import conversations from JSON file"
|
|
83
|
-
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"
|
|
84
88
|
>
|
|
85
89
|
<svg v-if="!isImporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
86
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"/>
|
|
@@ -116,15 +120,15 @@ export default {
|
|
|
116
120
|
<div class="flex-shrink-0 flex flex-col justify-center">
|
|
117
121
|
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium"
|
|
118
122
|
:class="message.role === 'user'
|
|
119
|
-
? 'bg-blue-
|
|
120
|
-
: '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'"
|
|
121
125
|
>
|
|
122
126
|
{{ message.role === 'user' ? 'U' : 'AI' }}
|
|
123
127
|
</div>
|
|
124
128
|
|
|
125
129
|
<!-- Delete button (shown on hover) -->
|
|
126
130
|
<button type="button" @click.stop="threads.deleteMessageFromThread(currentThread.id, message.id)"
|
|
127
|
-
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"
|
|
128
132
|
title="Delete message">
|
|
129
133
|
<svg class="size-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
130
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>
|
|
@@ -136,18 +140,18 @@ export default {
|
|
|
136
140
|
<div
|
|
137
141
|
class="message rounded-lg px-4 py-3 relative group"
|
|
138
142
|
:class="message.role === 'user'
|
|
139
|
-
? 'bg-blue-
|
|
140
|
-
: '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'"
|
|
141
145
|
>
|
|
142
146
|
<!-- Copy button in top right corner -->
|
|
143
147
|
<button
|
|
144
148
|
type="button"
|
|
145
149
|
@click="copyMessageContent(message)"
|
|
146
|
-
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"
|
|
147
|
-
: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'"
|
|
148
152
|
title="Copy message content"
|
|
149
153
|
>
|
|
150
|
-
<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>
|
|
151
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">
|
|
152
156
|
<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
|
|
153
157
|
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
|
|
@@ -157,18 +161,18 @@ export default {
|
|
|
157
161
|
<div
|
|
158
162
|
v-if="message.role === 'assistant'"
|
|
159
163
|
v-html="renderMarkdown(message.content)"
|
|
160
|
-
class="prose prose-sm max-w-none"
|
|
164
|
+
class="prose prose-sm max-w-none dark:prose-invert"
|
|
161
165
|
></div>
|
|
162
166
|
|
|
163
167
|
<!-- Collapsible reasoning section -->
|
|
164
168
|
<div v-if="message.role === 'assistant' && message.reasoning" class="mt-2">
|
|
165
|
-
<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">
|
|
166
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>
|
|
167
171
|
<span>{{ isReasoningExpanded(message.id) ? 'Hide reasoning' : 'Show reasoning' }}</span>
|
|
168
172
|
</button>
|
|
169
|
-
<div v-if="isReasoningExpanded(message.id)" class="mt-2 rounded border border-gray-200 bg-gray-50 p-2">
|
|
170
|
-
<div v-if="typeof message.reasoning === 'string'" v-html="renderMarkdown(message.reasoning)" class="prose prose-xs max-w-none"></div>
|
|
171
|
-
<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>
|
|
172
176
|
</div>
|
|
173
177
|
</div>
|
|
174
178
|
|
|
@@ -187,7 +191,7 @@ export default {
|
|
|
187
191
|
<!-- Edit and Redo buttons (shown on hover for user messages, outside bubble) -->
|
|
188
192
|
<div v-if="message.role === 'user'" class="flex flex-col gap-2 opacity-0 group-hover:opacity-100 transition-opacity mt-1">
|
|
189
193
|
<button type="button" @click.stop="editMessage(message)"
|
|
190
|
-
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"
|
|
191
195
|
title="Edit message">
|
|
192
196
|
<svg class="size-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
193
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>
|
|
@@ -195,7 +199,7 @@ export default {
|
|
|
195
199
|
Edit
|
|
196
200
|
</button>
|
|
197
201
|
<button type="button" @click.stop="redoMessage(message)"
|
|
198
|
-
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"
|
|
199
203
|
title="Redo message (clears all responses after this message and re-runs it)">
|
|
200
204
|
<svg class="size-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
201
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>
|
|
@@ -205,7 +209,7 @@ export default {
|
|
|
205
209
|
</div>
|
|
206
210
|
</div>
|
|
207
211
|
|
|
208
|
-
<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">
|
|
209
213
|
<span :title="statsTitle(currentThread.stats)">
|
|
210
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) }}
|
|
211
215
|
</span>
|
|
@@ -215,17 +219,17 @@ export default {
|
|
|
215
219
|
<div v-if="isGenerating" class="flex items-start space-x-3">
|
|
216
220
|
<!-- Avatar outside the bubble -->
|
|
217
221
|
<div class="flex-shrink-0">
|
|
218
|
-
<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">
|
|
219
223
|
AI
|
|
220
224
|
</div>
|
|
221
225
|
</div>
|
|
222
226
|
|
|
223
227
|
<!-- Loading bubble -->
|
|
224
|
-
<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">
|
|
225
229
|
<div class="flex space-x-1">
|
|
226
|
-
<div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
|
|
227
|
-
<div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.1s"></div>
|
|
228
|
-
<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>
|
|
229
233
|
</div>
|
|
230
234
|
</div>
|
|
231
235
|
</div>
|
|
@@ -234,24 +238,24 @@ export default {
|
|
|
234
238
|
<div v-if="errorStatus" class="flex items-start space-x-3">
|
|
235
239
|
<!-- Avatar outside the bubble -->
|
|
236
240
|
<div class="flex-shrink-0">
|
|
237
|
-
<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">
|
|
238
242
|
!
|
|
239
243
|
</div>
|
|
240
244
|
</div>
|
|
241
245
|
|
|
242
246
|
<!-- Error bubble -->
|
|
243
|
-
<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">
|
|
244
248
|
<div class="flex items-start space-x-2">
|
|
245
249
|
<div class="flex-1 min-w-0">
|
|
246
250
|
<div class="text-base font-medium mb-1">{{ errorStatus?.errorCode || 'Error' }}</div>
|
|
247
251
|
<div v-if="errorStatus?.message" class="text-base mb-1">{{ errorStatus.message }}</div>
|
|
248
|
-
<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">
|
|
249
253
|
{{ errorStatus.stackTrace }}
|
|
250
254
|
</div>
|
|
251
255
|
</div>
|
|
252
256
|
<button type="button"
|
|
253
257
|
@click="errorStatus = null"
|
|
254
|
-
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"
|
|
255
259
|
>
|
|
256
260
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
257
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>
|
|
@@ -265,21 +269,21 @@ export default {
|
|
|
265
269
|
|
|
266
270
|
<!-- Edit message modal -->
|
|
267
271
|
<div v-if="editingMessageId" class="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
268
|
-
<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">
|
|
269
273
|
<CloseButton @click="cancelEdit" class="" />
|
|
270
|
-
<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>
|
|
271
275
|
<textarea
|
|
272
276
|
v-model="editingMessageContent"
|
|
273
|
-
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"
|
|
274
278
|
placeholder="Edit your message..."
|
|
275
279
|
></textarea>
|
|
276
280
|
<div class="mt-4 flex gap-2 justify-end">
|
|
277
281
|
<button type="button" @click="cancelEdit"
|
|
278
|
-
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">
|
|
279
283
|
Cancel
|
|
280
284
|
</button>
|
|
281
285
|
<button type="button" @click="saveEditedMessage"
|
|
282
|
-
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">
|
|
283
287
|
Save
|
|
284
288
|
</button>
|
|
285
289
|
</div>
|
|
@@ -288,7 +292,7 @@ export default {
|
|
|
288
292
|
</div>
|
|
289
293
|
|
|
290
294
|
<!-- Input Area - only show when thread is selected -->
|
|
291
|
-
<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">
|
|
292
296
|
<ChatPrompt :model="selectedModel" :systemPrompt="currentSystemPrompt" />
|
|
293
297
|
</div>
|
|
294
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
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { inject, ref, onMounted } from "vue"
|
|
2
|
+
import Welcome from './Welcome.mjs'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
components: {
|
|
6
|
+
Welcome,
|
|
7
|
+
},
|
|
8
|
+
template: `
|
|
9
|
+
<div class="min-h-full -mt-36 flex flex-col justify-center sm:px-6 lg:px-8">
|
|
10
|
+
<div class="sm:mx-auto sm:w-full sm:max-w-md text-center">
|
|
11
|
+
<Welcome />
|
|
12
|
+
</div>
|
|
13
|
+
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
|
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
|
+
<div class="flex items-start space-x-2">
|
|
16
|
+
<div class="flex-1">
|
|
17
|
+
<div class="text-base font-medium">{{ errorMessage }}</div>
|
|
18
|
+
</div>
|
|
19
|
+
<button type="button"
|
|
20
|
+
@click="errorMessage = null"
|
|
21
|
+
class="text-red-400 dark:text-red-300 hover:text-red-600 dark:hover:text-red-100 flex-shrink-0"
|
|
22
|
+
>
|
|
23
|
+
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
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>
|
|
25
|
+
</svg>
|
|
26
|
+
</button>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="py-8 px-4 sm:px-10">
|
|
30
|
+
<div class="space-y-4">
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
@click="signInWithGitHub"
|
|
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
|
+
>
|
|
36
|
+
<svg class="w-6 h-6 mr-3" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
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" />
|
|
38
|
+
</svg>
|
|
39
|
+
Sign in with GitHub
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
`,
|
|
46
|
+
emits: ['done'],
|
|
47
|
+
setup(props, { emit }) {
|
|
48
|
+
const ai = inject('ai')
|
|
49
|
+
const errorMessage = ref(null)
|
|
50
|
+
|
|
51
|
+
function signInWithGitHub() {
|
|
52
|
+
// Redirect to GitHub OAuth endpoint
|
|
53
|
+
window.location.href = '/auth/github'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check for session token in URL (after OAuth callback redirect)
|
|
57
|
+
onMounted(async () => {
|
|
58
|
+
const urlParams = new URLSearchParams(window.location.search)
|
|
59
|
+
const sessionToken = urlParams.get('session')
|
|
60
|
+
|
|
61
|
+
if (sessionToken) {
|
|
62
|
+
try {
|
|
63
|
+
// Validate session with server
|
|
64
|
+
const response = await ai.get(`/auth/session?session=${sessionToken}`)
|
|
65
|
+
|
|
66
|
+
if (response.ok) {
|
|
67
|
+
const sessionData = await response.json()
|
|
68
|
+
|
|
69
|
+
// Clean up URL
|
|
70
|
+
const url = new URL(window.location.href)
|
|
71
|
+
url.searchParams.delete('session')
|
|
72
|
+
window.history.replaceState({}, '', url.toString())
|
|
73
|
+
|
|
74
|
+
// Emit done event with session data
|
|
75
|
+
emit('done', sessionData)
|
|
76
|
+
} else {
|
|
77
|
+
errorMessage.value = 'Failed to validate session'
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Session validation error:', error)
|
|
81
|
+
errorMessage.value = 'Failed to validate session'
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
signInWithGitHub,
|
|
88
|
+
errorMessage,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
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>
|