llms-py 2.0.26__py3-none-any.whl → 2.0.28__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/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
 
@@ -62,12 +62,12 @@ export default {
62
62
  </div>
63
63
 
64
64
  <!-- Export/Import buttons -->
65
- <div class="mt-2 flex space-x-3 justify-center">
65
+ <div class="mt-2 flex space-x-3 justify-center items-center">
66
66
  <button type="button"
67
67
  @click="(e) => e.altKey ? exportRequests() : exportThreads()"
68
68
  :disabled="isExporting"
69
69
  :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"
70
+ 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
71
  >
72
72
  <svg v-if="!isExporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
73
73
  <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 +83,7 @@ export default {
83
83
  @click="triggerImport"
84
84
  :disabled="isImporting"
85
85
  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"
86
+ 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
87
  >
88
88
  <svg v-if="!isImporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
89
89
  <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"/>
@@ -103,6 +103,9 @@ export default {
103
103
  @change="handleFileImport"
104
104
  class="hidden"
105
105
  />
106
+
107
+ <DarkModeToggle />
108
+
106
109
  </div>
107
110
 
108
111
  </div>
@@ -119,15 +122,15 @@ export default {
119
122
  <div class="flex-shrink-0 flex flex-col justify-center">
120
123
  <div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium"
121
124
  :class="message.role === 'user'
122
- ? 'bg-blue-600 text-white'
123
- : 'bg-gray-600 text-white'"
125
+ ? 'bg-blue-100 dark:bg-blue-900 text-gray-900 dark:text-gray-100 border border-blue-200 dark:border-blue-700'
126
+ : 'bg-gray-600 dark:bg-gray-500 text-white'"
124
127
  >
125
128
  {{ message.role === 'user' ? 'U' : 'AI' }}
126
129
  </div>
127
130
 
128
131
  <!-- Delete button (shown on hover) -->
129
132
  <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"
133
+ 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
134
  title="Delete message">
132
135
  <svg class="size-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
133
136
  <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 +142,18 @@ export default {
139
142
  <div
140
143
  class="message rounded-lg px-4 py-3 relative group"
141
144
  :class="message.role === 'user'
142
- ? 'bg-blue-600 text-white'
143
- : 'bg-gray-100 text-gray-900 border border-gray-200'"
145
+ ? 'bg-blue-100 dark:bg-blue-900 text-gray-900 dark:text-gray-100 border border-blue-200 dark:border-blue-700'
146
+ : 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100 border border-gray-200 dark:border-gray-700'"
144
147
  >
145
148
  <!-- Copy button in top right corner -->
146
149
  <button
147
150
  type="button"
148
151
  @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-white/70 hover:text-white hover:bg-white/20' : 'text-gray-500 hover:text-gray-700'"
152
+ 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"
153
+ :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
154
  title="Copy message content"
152
155
  >
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>
156
+ <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
157
  <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
158
  <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
156
159
  <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 +163,18 @@ export default {
160
163
  <div
161
164
  v-if="message.role === 'assistant'"
162
165
  v-html="renderMarkdown(message.content)"
163
- class="prose prose-sm max-w-none"
166
+ class="prose prose-sm max-w-none dark:prose-invert"
164
167
  ></div>
165
168
 
166
169
  <!-- Collapsible reasoning section -->
167
170
  <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">
171
+ <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
172
  <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
173
  <span>{{ isReasoningExpanded(message.id) ? 'Hide reasoning' : 'Show reasoning' }}</span>
171
174
  </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>
175
+ <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">
176
+ <div v-if="typeof message.reasoning === 'string'" v-html="renderMarkdown(message.reasoning)" class="prose prose-xs max-w-none dark:prose-invert"></div>
177
+ <pre v-else class="text-xs whitespace-pre-wrap overflow-x-auto text-gray-900 dark:text-gray-100">{{ formatReasoning(message.reasoning) }}</pre>
175
178
  </div>
176
179
  </div>
177
180
 
@@ -190,7 +193,7 @@ export default {
190
193
  <!-- Edit and Redo buttons (shown on hover for user messages, outside bubble) -->
191
194
  <div v-if="message.role === 'user'" class="flex flex-col gap-2 opacity-0 group-hover:opacity-100 transition-opacity mt-1">
192
195
  <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"
196
+ 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
197
  title="Edit message">
195
198
  <svg class="size-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
196
199
  <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 +201,7 @@ export default {
198
201
  Edit
199
202
  </button>
200
203
  <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"
204
+ 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
205
  title="Redo message (clears all responses after this message and re-runs it)">
203
206
  <svg class="size-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
204
207
  <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 +211,7 @@ export default {
208
211
  </div>
209
212
  </div>
210
213
 
211
- <div v-if="currentThread.stats && currentThread.stats.outputTokens" class="text-center text-gray-500 text-sm">
214
+ <div v-if="currentThread.stats && currentThread.stats.outputTokens" class="text-center text-gray-500 dark:text-gray-400 text-sm">
212
215
  <span :title="statsTitle(currentThread.stats)">
213
216
  {{ 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
217
  </span>
@@ -218,17 +221,17 @@ export default {
218
221
  <div v-if="isGenerating" class="flex items-start space-x-3">
219
222
  <!-- Avatar outside the bubble -->
220
223
  <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">
224
+ <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
225
  AI
223
226
  </div>
224
227
  </div>
225
228
 
226
229
  <!-- Loading bubble -->
227
- <div class="rounded-lg px-4 py-3 bg-gray-100 border border-gray-200">
230
+ <div class="rounded-lg px-4 py-3 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
228
231
  <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>
232
+ <div class="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce"></div>
233
+ <div class="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce" style="animation-delay: 0.1s"></div>
234
+ <div class="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
232
235
  </div>
233
236
  </div>
234
237
  </div>
@@ -237,24 +240,24 @@ export default {
237
240
  <div v-if="errorStatus" class="flex items-start space-x-3">
238
241
  <!-- Avatar outside the bubble -->
239
242
  <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">
243
+ <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
244
  !
242
245
  </div>
243
246
  </div>
244
247
 
245
248
  <!-- 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">
249
+ <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
250
  <div class="flex items-start space-x-2">
248
251
  <div class="flex-1 min-w-0">
249
252
  <div class="text-base font-medium mb-1">{{ errorStatus?.errorCode || 'Error' }}</div>
250
253
  <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">
254
+ <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
255
  {{ errorStatus.stackTrace }}
253
256
  </div>
254
257
  </div>
255
258
  <button type="button"
256
259
  @click="errorStatus = null"
257
- class="text-red-400 hover:text-red-600 flex-shrink-0"
260
+ class="text-red-400 dark:text-red-300 hover:text-red-600 dark:hover:text-red-100 flex-shrink-0"
258
261
  >
259
262
  <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
260
263
  <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 +271,21 @@ export default {
268
271
 
269
272
  <!-- Edit message modal -->
270
273
  <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">
274
+ <div class="relative bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 max-w-2xl w-full mx-4">
272
275
  <CloseButton @click="cancelEdit" class="" />
273
- <h3 class="text-lg font-semibold text-gray-900 mb-4">Edit Message</h3>
276
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Edit Message</h3>
274
277
  <textarea
275
278
  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"
279
+ 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
280
  placeholder="Edit your message..."
278
281
  ></textarea>
279
282
  <div class="mt-4 flex gap-2 justify-end">
280
283
  <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">
284
+ 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
285
  Cancel
283
286
  </button>
284
287
  <button type="button" @click="saveEditedMessage"
285
- class="px-4 py-2 rounded-md bg-blue-600 text-white hover:bg-blue-700 transition-all">
288
+ 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
289
  Save
287
290
  </button>
288
291
  </div>
@@ -291,7 +294,7 @@ export default {
291
294
  </div>
292
295
 
293
296
  <!-- 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">
297
+ <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
298
  <ChatPrompt :model="selectedModel" :systemPrompt="currentSystemPrompt" />
296
299
  </div>
297
300
  </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
  &#183;
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" />
@@ -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>