llms-py 2.0.26__tar.gz → 2.0.27__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {llms_py-2.0.26/llms_py.egg-info → llms_py-2.0.27}/PKG-INFO +1 -1
  2. {llms_py-2.0.26 → llms_py-2.0.27}/llms/index.html +17 -1
  3. {llms_py-2.0.26 → llms_py-2.0.27}/llms/llms.json +1 -1
  4. {llms_py-2.0.26 → llms_py-2.0.27}/llms/main.py +1 -1
  5. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/Analytics.mjs +73 -73
  6. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/App.mjs +1 -1
  7. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/Brand.mjs +4 -4
  8. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/ChatPrompt.mjs +110 -9
  9. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/Main.mjs +35 -34
  10. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/ModelSelector.mjs +3 -4
  11. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/OAuthSignIn.mjs +4 -4
  12. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/ProviderStatus.mjs +12 -12
  13. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/Recents.mjs +13 -13
  14. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/SettingsDialog.mjs +65 -65
  15. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/Sidebar.mjs +17 -17
  16. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/SystemPromptEditor.mjs +5 -5
  17. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/SystemPromptSelector.mjs +4 -4
  18. llms_py-2.0.27/llms/ui/Welcome.mjs +8 -0
  19. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/ai.mjs +1 -1
  20. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/app.css +386 -0
  21. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/markdown.mjs +8 -8
  22. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/tailwind.input.css +2 -0
  23. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/typography.css +50 -34
  24. {llms_py-2.0.26 → llms_py-2.0.27/llms_py.egg-info}/PKG-INFO +1 -1
  25. {llms_py-2.0.26 → llms_py-2.0.27}/pyproject.toml +1 -1
  26. {llms_py-2.0.26 → llms_py-2.0.27}/setup.py +1 -1
  27. llms_py-2.0.26/llms/ui/Welcome.mjs +0 -8
  28. {llms_py-2.0.26 → llms_py-2.0.27}/LICENSE +0 -0
  29. {llms_py-2.0.26 → llms_py-2.0.27}/MANIFEST.in +0 -0
  30. {llms_py-2.0.26 → llms_py-2.0.27}/README.md +0 -0
  31. {llms_py-2.0.26 → llms_py-2.0.27}/llms/__init__.py +0 -0
  32. {llms_py-2.0.26 → llms_py-2.0.27}/llms/__main__.py +0 -0
  33. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/Avatar.mjs +0 -0
  34. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/ProviderIcon.mjs +0 -0
  35. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/SignIn.mjs +0 -0
  36. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/fav.svg +0 -0
  37. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/lib/chart.js +0 -0
  38. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/lib/charts.mjs +0 -0
  39. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/lib/color.js +0 -0
  40. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/lib/highlight.min.mjs +0 -0
  41. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/lib/idb.min.mjs +0 -0
  42. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/lib/marked.min.mjs +0 -0
  43. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/lib/servicestack-client.mjs +0 -0
  44. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/lib/servicestack-vue.mjs +0 -0
  45. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/lib/vue-router.min.mjs +0 -0
  46. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/lib/vue.min.mjs +0 -0
  47. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/lib/vue.mjs +0 -0
  48. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/threadStore.mjs +0 -0
  49. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui/utils.mjs +0 -0
  50. {llms_py-2.0.26 → llms_py-2.0.27}/llms/ui.json +0 -0
  51. {llms_py-2.0.26 → llms_py-2.0.27}/llms_py.egg-info/SOURCES.txt +0 -0
  52. {llms_py-2.0.26 → llms_py-2.0.27}/llms_py.egg-info/dependency_links.txt +0 -0
  53. {llms_py-2.0.26 → llms_py-2.0.27}/llms_py.egg-info/entry_points.txt +0 -0
  54. {llms_py-2.0.26 → llms_py-2.0.27}/llms_py.egg-info/not-zip-safe +0 -0
  55. {llms_py-2.0.26 → llms_py-2.0.27}/llms_py.egg-info/requires.txt +0 -0
  56. {llms_py-2.0.26 → llms_py-2.0.27}/llms_py.egg-info/top_level.txt +0 -0
  57. {llms_py-2.0.26 → llms_py-2.0.27}/requirements.txt +0 -0
  58. {llms_py-2.0.26 → llms_py-2.0.27}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llms-py
3
- Version: 2.0.26
3
+ Version: 2.0.27
4
4
  Summary: A lightweight CLI tool and OpenAI-compatible server for querying multiple Large Language Model (LLM) providers
5
5
  Home-page: https://github.com/ServiceStack/llms
6
6
  Author: ServiceStack
@@ -1,8 +1,8 @@
1
1
  <html>
2
2
  <head>
3
3
  <title>llms.py</title>
4
- <link rel="stylesheet" href="/ui/typography.css">
5
4
  <link rel="stylesheet" href="/ui/app.css">
5
+ <link rel="stylesheet" href="/ui/typography.css">
6
6
  <link rel="icon" type="image/svg" href="/ui/fav.svg">
7
7
  <style>
8
8
  [type='button'],button[type='submit']{cursor:pointer}
@@ -38,6 +38,22 @@
38
38
  <body>
39
39
  <div id="app"></div>
40
40
  </body>
41
+ <script>
42
+ let colorScheme = location.search === "?dark"
43
+ ? "dark"
44
+ : location.search === "?light"
45
+ ? "light"
46
+ : localStorage.getItem('color-scheme')
47
+ let darkMode = colorScheme != null
48
+ ? colorScheme === 'dark'
49
+ : window.matchMedia('(prefers-color-scheme: dark)').matches
50
+ let html = document.documentElement
51
+ html.classList.toggle('dark', darkMode)
52
+ html.style.setProperty('color-scheme', darkMode ? 'dark' : null)
53
+ if (localStorage.getItem('color-scheme') === null) {
54
+ localStorage.setItem('color-scheme', darkMode ? 'dark' : 'light')
55
+ }
56
+ </script>
41
57
  <script type="module">
42
58
  import { createApp, defineAsyncComponent } from 'vue'
43
59
  import { createWebHistory, createRouter } from "vue-router"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "auth": {
3
- "enabled": true,
3
+ "enabled": false,
4
4
  "github": {
5
5
  "client_id": "$GITHUB_CLIENT_ID",
6
6
  "client_secret": "$GITHUB_CLIENT_SECRET",
@@ -23,7 +23,7 @@ from aiohttp import web
23
23
  from pathlib import Path
24
24
  from importlib import resources # Py≥3.9 (pip install importlib_resources for 3.7/3.8)
25
25
 
26
- VERSION = "2.0.26"
26
+ VERSION = "2.0.27"
27
27
  _ROOT = None
28
28
  g_config_path = None
29
29
  g_ui_path = None
@@ -42,7 +42,7 @@ const MonthSelector = {
42
42
 
43
43
  <!-- Year Dropdown -->
44
44
  <select :value="selectedYear" @change="(e) => updateSelection(parseInt(e.target.value), selectedMonth)"
45
- class="border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500">
45
+ class="border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 text-gray-700 dark:text-gray-300 rounded-md text-sm font-medium hover:bg-gray-50 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-indigo-500">
46
46
  <option v-for="year in availableYears" :key="year" :value="year">
47
47
  {{ year }}
48
48
  </option>
@@ -115,9 +115,9 @@ export default {
115
115
  template: `
116
116
  <div class="flex flex-col h-full w-full">
117
117
  <!-- Header -->
118
- <div class="border-b border-gray-200 bg-white px-4 py-3 min-h-16">
118
+ <div class="border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-4 py-3 min-h-16">
119
119
  <div class="max-w-6xl mx-auto flex items-center justify-between gap-3">
120
- <h2 class="text-lg font-semibold text-gray-900">
120
+ <h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
121
121
  <RouterLink to="/analytics">Analytics</RouterLink>
122
122
  </h2>
123
123
  <MonthSelector :dailyData="allDailyData" />
@@ -125,67 +125,67 @@ export default {
125
125
  </div>
126
126
 
127
127
  <!-- Tabs -->
128
- <div class="border-b border-gray-200 bg-white px-4">
128
+ <div class="border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-4">
129
129
  <div class="max-w-6xl mx-auto flex gap-8">
130
130
  <button type="button"
131
131
  @click="activeTab = 'cost'"
132
132
  :class="['py-3 px-1 border-b-2 font-medium text-sm transition-colors',
133
133
  activeTab === 'cost'
134
- ? 'border-blue-500 text-blue-600'
135
- : 'border-transparent text-gray-600 hover:text-gray-900']">
134
+ ? 'border-blue-500 text-blue-600 dark:text-blue-400'
135
+ : 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200']">
136
136
  Cost Analysis
137
137
  </button>
138
138
  <button type="button"
139
139
  @click="activeTab = 'tokens'"
140
140
  :class="['py-3 px-1 border-b-2 font-medium text-sm transition-colors',
141
141
  activeTab === 'tokens'
142
- ? 'border-blue-500 text-blue-600'
143
- : 'border-transparent text-gray-600 hover:text-gray-900']">
142
+ ? 'border-blue-500 text-blue-600 dark:text-blue-400'
143
+ : 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200']">
144
144
  Token Usage
145
145
  </button>
146
146
  <button type="button"
147
147
  @click="activeTab = 'activity'"
148
148
  :class="['py-3 px-1 border-b-2 font-medium text-sm transition-colors',
149
149
  activeTab === 'activity'
150
- ? 'border-blue-500 text-blue-600'
151
- : 'border-transparent text-gray-600 hover:text-gray-900']">
150
+ ? 'border-blue-500 text-blue-600 dark:text-blue-400'
151
+ : 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200']">
152
152
  Activity
153
153
  </button>
154
154
  </div>
155
155
  </div>
156
156
 
157
157
  <!-- Content -->
158
- <div class="flex-1 overflow-auto bg-gray-50" :class="activeTab === 'activity' ? 'p-0' : 'p-4'">
158
+ <div class="flex-1 overflow-auto bg-gray-50 dark:bg-gray-900" :class="activeTab === 'activity' ? 'p-0' : 'p-4'">
159
159
 
160
160
  <div :class="activeTab === 'activity' ? 'h-full' : 'max-w-6xl mx-auto'">
161
161
  <!-- Stats Summary (hidden for Activity tab) -->
162
162
  <div v-if="activeTab !== 'activity'" class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
163
- <div class="bg-white rounded-lg shadow p-4">
164
- <div class="text-sm font-medium text-gray-600">Total Cost</div>
165
- <div class="text-2xl font-bold text-gray-900 mt-1">{{ formatCost(totalCost) }}</div>
163
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
164
+ <div class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Cost</div>
165
+ <div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{ formatCost(totalCost) }}</div>
166
166
  </div>
167
- <div class="bg-white rounded-lg shadow p-4">
168
- <div class="text-sm font-medium text-gray-600">Total Requests</div>
169
- <div class="text-2xl font-bold text-gray-900 mt-1">{{ totalRequests }}</div>
167
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
168
+ <div class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Requests</div>
169
+ <div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{ totalRequests }}</div>
170
170
  </div>
171
- <div class="bg-white rounded-lg shadow p-4">
172
- <div class="text-sm font-medium text-gray-600">Total Input Tokens</div>
173
- <div class="text-2xl font-bold text-gray-900 mt-1">{{ humanifyNumber(totalInputTokens) }}</div>
171
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
172
+ <div class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Input Tokens</div>
173
+ <div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{ humanifyNumber(totalInputTokens) }}</div>
174
174
  </div>
175
- <div class="bg-white rounded-lg shadow p-4">
176
- <div class="text-sm font-medium text-gray-600">Total Output Tokens</div>
177
- <div class="text-2xl font-bold text-gray-900 mt-1">{{ humanifyNumber(totalOutputTokens) }}</div>
175
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
176
+ <div class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Output Tokens</div>
177
+ <div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{ humanifyNumber(totalOutputTokens) }}</div>
178
178
  </div>
179
179
  </div>
180
180
 
181
181
  <!-- Cost Analysis Tab -->
182
- <div v-if="activeTab === 'cost'" class="bg-white rounded-lg shadow p-6">
182
+ <div v-if="activeTab === 'cost'" class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
183
183
  <div class="flex items-center justify-between mb-6">
184
- <h3 class="text-lg font-semibold text-gray-900">Daily Costs</h3>
185
- <h3 class="text-lg font-semibold text-gray-900">
184
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Daily Costs</h3>
185
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
186
186
  {{ new Date(selectedDay).toLocaleDateString(undefined, { year: 'numeric', month: 'long' }) }}
187
187
  </h3>
188
- <select v-model="costChartType" class="px-3 pr-6 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50">
188
+ <select v-model="costChartType" class="px-3 pr-6 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 text-gray-700 dark:text-gray-300 rounded-md text-sm font-medium hover:bg-gray-50 dark:hover:bg-gray-800">
189
189
  <option value="bar">Bar Chart</option>
190
190
  <option value="line">Line Chart</option>
191
191
  </select>
@@ -194,14 +194,14 @@ export default {
194
194
  <div v-if="chartData.labels.length > 0" class="relative h-96">
195
195
  <canvas ref="costChartCanvas"></canvas>
196
196
  </div>
197
- <div v-else class="flex items-center justify-center h-96 text-gray-500">
197
+ <div v-else class="flex items-center justify-center h-96 text-gray-500 dark:text-gray-400">
198
198
  <p>No request data available</p>
199
199
  </div>
200
200
  </div>
201
201
 
202
202
  <!-- Token Usage Tab -->
203
- <div v-if="activeTab === 'tokens'" class="bg-white rounded-lg shadow p-6">
204
- <h3 class="text-lg font-semibold text-gray-900 mb-6 flex justify-between items-center">
203
+ <div v-if="activeTab === 'tokens'" class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
204
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-6 flex justify-between items-center">
205
205
  <span>Daily Token Usage</span>
206
206
  <span>
207
207
  {{ new Date(selectedDay).toLocaleDateString(undefined, { year: 'numeric', month: 'long' }) }}
@@ -211,14 +211,14 @@ export default {
211
211
  <div v-if="tokenChartData.labels.length > 0" class="relative h-96">
212
212
  <canvas ref="tokenChartCanvas"></canvas>
213
213
  </div>
214
- <div v-else class="flex items-center justify-center h-96 text-gray-500">
214
+ <div v-else class="flex items-center justify-center h-96 text-gray-500 dark:text-gray-400">
215
215
  <p>No request data available</p>
216
216
  </div>
217
217
  </div>
218
218
 
219
- <div v-if="allDailyData[selectedDay]?.requests && ['cost', 'tokens'].includes(activeTab)" class="mt-8 px-2 text-gray-700 font-medium flex items-center justify-between">
219
+ <div v-if="allDailyData[selectedDay]?.requests && ['cost', 'tokens'].includes(activeTab)" class="mt-8 px-2 text-gray-700 dark:text-gray-300 font-medium flex items-center justify-between">
220
220
  <div>
221
- {{ new Date(selectedDay).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' }) }}
221
+ {{ new Date(selectedDay).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' }) }}
222
222
  </div>
223
223
  <div>
224
224
  {{ formatCost(allDailyData[selectedDay]?.cost || 0) }}
@@ -232,27 +232,27 @@ export default {
232
232
  <!-- Pie Charts for Selected Day -->
233
233
  <div v-if="allDailyData[selectedDay]?.requests && activeTab === 'cost' && selectedDay" class="mt-6 grid grid-cols-1 lg:grid-cols-2 gap-6">
234
234
  <!-- Model Pie Chart -->
235
- <div class="bg-white rounded-lg shadow p-6">
236
- <h3 class="text-lg font-semibold text-gray-900 mb-4">
235
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
236
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
237
237
  Cost by Model
238
238
  </h3>
239
239
  <div v-if="modelPieData.labels.length > 0" class="relative h-80">
240
240
  <canvas ref="modelPieCanvas"></canvas>
241
241
  </div>
242
- <div v-else class="flex items-center justify-center h-80 text-gray-500">
242
+ <div v-else class="flex items-center justify-center h-80 text-gray-500 dark:text-gray-400">
243
243
  <p>No data for selected day</p>
244
244
  </div>
245
245
  </div>
246
246
 
247
247
  <!-- Provider Pie Chart -->
248
- <div class="bg-white rounded-lg shadow p-6">
249
- <h3 class="text-lg font-semibold text-gray-900 mb-4">
248
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
249
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
250
250
  Cost by Provider
251
251
  </h3>
252
252
  <div v-if="providerPieData.labels.length > 0" class="relative h-80">
253
253
  <canvas ref="providerPieCanvas"></canvas>
254
254
  </div>
255
- <div v-else class="flex items-center justify-center h-80 text-gray-500">
255
+ <div v-else class="flex items-center justify-center h-80 text-gray-500 dark:text-gray-400">
256
256
  <p>No data for selected day</p>
257
257
  </div>
258
258
  </div>
@@ -261,39 +261,39 @@ export default {
261
261
  <!-- Token Pie Charts for Selected Day -->
262
262
  <div v-if="allDailyData[selectedDay]?.requests && activeTab === 'tokens' && selectedDay" class="mt-6 grid grid-cols-1 lg:grid-cols-2 gap-6">
263
263
  <!-- Token Model Pie Chart -->
264
- <div class="bg-white rounded-lg shadow p-6">
265
- <h3 class="text-lg font-semibold text-gray-900 mb-4">
264
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
265
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
266
266
  Tokens by Model
267
267
  </h3>
268
268
  <div v-if="tokenModelPieData.labels.length > 0" class="relative h-80">
269
269
  <canvas ref="tokenModelPieCanvas"></canvas>
270
270
  </div>
271
- <div v-else class="flex items-center justify-center h-80 text-gray-500">
271
+ <div v-else class="flex items-center justify-center h-80 text-gray-500 dark:text-gray-400">
272
272
  <p>No data for selected day</p>
273
273
  </div>
274
274
  </div>
275
275
 
276
276
  <!-- Token Provider Pie Chart -->
277
- <div class="bg-white rounded-lg shadow p-6">
278
- <h3 class="text-lg font-semibold text-gray-900 mb-4">
277
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
278
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
279
279
  Tokens by Provider
280
280
  </h3>
281
281
  <div v-if="tokenProviderPieData.labels.length > 0" class="relative h-80">
282
282
  <canvas ref="tokenProviderPieCanvas"></canvas>
283
283
  </div>
284
- <div v-else class="flex items-center justify-center h-80 text-gray-500">
284
+ <div v-else class="flex items-center justify-center h-80 text-gray-500 dark:text-gray-400">
285
285
  <p>No data for selected day</p>
286
286
  </div>
287
287
  </div>
288
288
  </div>
289
289
 
290
290
  <!-- Activity Tab - Full Page Layout -->
291
- <div v-if="activeTab === 'activity'" class="h-full flex flex-col bg-white">
291
+ <div v-if="activeTab === 'activity'" class="h-full flex flex-col bg-white dark:bg-gray-800">
292
292
  <!-- Filters Bar -->
293
- <div class="flex-shrink-0 border-b border-gray-200 bg-white px-6 py-4">
293
+ <div class="flex-shrink-0 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-6 py-4">
294
294
  <div class="flex flex-wrap gap-4 items-end">
295
295
  <div class="flex flex-col">
296
- <select v-model="selectedModel" class="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
296
+ <select v-model="selectedModel" class="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 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
297
297
  <option value="">All Models</option>
298
298
  <option v-for="model in filterOptions.models" :key="model" :value="model">
299
299
  {{ model }}
@@ -302,7 +302,7 @@ export default {
302
302
  </div>
303
303
 
304
304
  <div class="flex flex-col">
305
- <select v-model="selectedProvider" class="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
305
+ <select v-model="selectedProvider" class="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 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
306
306
  <option value="">All Providers</option>
307
307
  <option v-for="provider in filterOptions.providers" :key="provider" :value="provider">
308
308
  {{ provider }}
@@ -311,7 +311,7 @@ export default {
311
311
  </div>
312
312
 
313
313
  <div class="flex flex-col">
314
- <select v-model="sortBy" class="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
314
+ <select v-model="sortBy" class="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 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
315
315
  <option value="created">Date (Newest)</option>
316
316
  <option value="cost">Cost (Highest)</option>
317
317
  <option value="duration">Duration (Longest)</option>
@@ -319,7 +319,7 @@ export default {
319
319
  </select>
320
320
  </div>
321
321
 
322
- <button v-if="hasActiveFilters" @click="clearActivityFilters" class="px-4 py-2 text-sm text-gray-600 hover:text-gray-900 border border-gray-300 rounded-md hover:bg-gray-100 transition-colors">
322
+ <button v-if="hasActiveFilters" @click="clearActivityFilters" class="px-4 py-2 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
323
323
  Clear Filters
324
324
  </button>
325
325
  </div>
@@ -330,60 +330,60 @@ export default {
330
330
  <div v-if="isActivityLoading && activityRequests.length === 0" class="flex items-center justify-center h-full">
331
331
  <div class="text-center">
332
332
  <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
333
- <p class="mt-4 text-gray-600">Loading requests...</p>
333
+ <p class="mt-4 text-gray-600 dark:text-gray-400">Loading requests...</p>
334
334
  </div>
335
335
  </div>
336
336
 
337
337
  <div v-else-if="activityRequests.length === 0" class="flex items-center justify-center h-full">
338
- <p class="text-gray-500">No requests found</p>
338
+ <p class="text-gray-500 dark:text-gray-400">No requests found</p>
339
339
  </div>
340
340
 
341
- <div v-else class="divide-y divide-gray-200">
342
- <div v-for="request in activityRequests" :key="request.id" class="px-6 py-4 hover:bg-gray-50 transition-colors">
341
+ <div v-else class="divide-y divide-gray-200 dark:divide-gray-700">
342
+ <div v-for="request in activityRequests" :key="request.id" class="px-6 py-4 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
343
343
  <div class="flex items-start justify-between gap-4">
344
344
  <div class="flex-1 min-w-0">
345
345
  <div class="flex justify-between">
346
346
  <div class="flex items-center gap-2 mb-2 flex-wrap">
347
- <span class="text-xs px-2 py-1 bg-blue-100 text-blue-800 rounded font-medium">{{ request.model }}</span>
348
- <span class="text-xs px-2 py-1 bg-purple-100 text-purple-800 rounded font-medium">{{ request.provider }}</span>
349
- <span v-if="request.providerRef" class="text-xs px-2 py-1 bg-green-100 text-green-800 rounded font-medium">{{ request.providerRef }}</span>
350
- <span v-if="request.finishReason" class="text-xs px-2 py-1 bg-gray-100 text-gray-800 rounded font-medium">{{ request.finishReason }}</span>
347
+ <span class="text-xs px-2 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300 rounded font-medium">{{ request.model }}</span>
348
+ <span class="text-xs px-2 py-1 bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300 rounded font-medium">{{ request.provider }}</span>
349
+ <span v-if="request.providerRef" class="text-xs px-2 py-1 bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 rounded font-medium">{{ request.providerRef }}</span>
350
+ <span v-if="request.finishReason" class="text-xs px-2 py-1 bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300 rounded font-medium">{{ request.finishReason }}</span>
351
351
  </div>
352
- <div class="text-xs text-gray-500">
352
+ <div class="text-xs text-gray-500 dark:text-gray-400">
353
353
  {{ formatActivityDate(request.created) }}
354
354
  </div>
355
355
  </div>
356
- <div class="text-sm font-semibold text-gray-900 truncate">
356
+ <div class="text-sm font-semibold text-gray-900 dark:text-gray-100 truncate">
357
357
  {{ request.title }}
358
358
  </div>
359
359
 
360
360
  <div class="grid grid-cols-2 md:grid-cols-5 gap-4 mt-3">
361
361
  <div :title="request.cost">
362
- <div class="text-xs text-gray-500 font-medium">Cost</div>
363
- <div class="text-sm font-semibold text-gray-900">{{ formatCost(request.cost) }}</div>
362
+ <div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Cost</div>
363
+ <div class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ formatCost(request.cost) }}</div>
364
364
  </div>
365
365
  <div>
366
- <div class="text-xs text-gray-500 font-medium">Tokens</div>
367
- <div class="text-sm font-semibold text-gray-900">
366
+ <div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Tokens</div>
367
+ <div class="text-sm font-semibold text-gray-900 dark:text-gray-100">
368
368
  {{ humanifyNumber(request.inputTokens) }} -> {{ humanifyNumber(request.outputTokens) }}
369
- <span v-if="request.inputCachedTokens" class="ml-1 text-xs text-gray-500">({{ humanifyNumber(request.inputCachedTokens) }} cached)</span>
369
+ <span v-if="request.inputCachedTokens" class="ml-1 text-xs text-gray-500 dark:text-gray-400">({{ humanifyNumber(request.inputCachedTokens) }} cached)</span>
370
370
  </div>
371
371
  </div>
372
372
  <div>
373
- <div class="text-xs text-gray-500 font-medium">Duration</div>
374
- <div class="text-sm font-semibold text-gray-900">{{ request.duration ? humanifyMs(request.duration) : '—' }}</div>
373
+ <div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Duration</div>
374
+ <div class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ request.duration ? humanifyMs(request.duration) : '—' }}</div>
375
375
  </div>
376
376
  <div>
377
- <div class="text-xs text-gray-500 font-medium">Speed</div>
378
- <div class="text-sm font-semibold text-gray-900">{{ request.duration && request.outputTokens ? (request.outputTokens / (request.duration / 1000)).toFixed(1) + ' tok/s' : '—' }}</div>
377
+ <div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Speed</div>
378
+ <div class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ request.duration && request.outputTokens ? (request.outputTokens / (request.duration / 1000)).toFixed(1) + ' tok/s' : '—' }}</div>
379
379
  </div>
380
380
  </div>
381
381
  </div>
382
382
  <div class="flex flex-col gap-2">
383
- <button type="button" v-if="threadExists(request.threadId)" @click="openThread(request.threadId)" class="flex-shrink-0 px-4 py-2 text-sm font-medium text-blue-600 hover:text-blue-800 border border-blue-300 rounded hover:bg-blue-50 transition-colors whitespace-nowrap">
383
+ <button type="button" v-if="threadExists(request.threadId)" @click="openThread(request.threadId)" class="flex-shrink-0 px-4 py-2 text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 border border-blue-300 dark:border-blue-600 rounded hover:bg-blue-50 dark:hover:bg-blue-900/30 transition-colors whitespace-nowrap">
384
384
  View<span class="hidden lg:inline"> Thread</span>
385
385
  </button>
386
- <button type="button" @click="deleteRequestLog(request.id)" class="flex-shrink-0 px-4 py-2 text-sm font-medium text-red-600 hover:text-red-800 border border-red-300 rounded hover:bg-red-50 transition-colors whitespace-nowrap">
386
+ <button type="button" @click="deleteRequestLog(request.id)" class="flex-shrink-0 px-4 py-2 text-sm font-medium text-red-600 dark:text-red-500 hover:text-red-800 dark:hover:text-red-400 border border-red-300 dark:border-red-600 rounded hover:bg-red-50 dark:hover:bg-red-900/30 transition-colors whitespace-nowrap">
387
387
  Delete<span class="hidden lg:inline"> Request</span>
388
388
  </button>
389
389
  </div>
@@ -394,7 +394,7 @@ export default {
394
394
  <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
395
395
  </div>
396
396
 
397
- <div v-if="!activityHasMore && activityRequests.length > 0" class="px-6 py-8 text-center text-gray-500 text-sm">
397
+ <div v-if="!activityHasMore && activityRequests.length > 0" class="px-6 py-8 text-center text-gray-500 dark:text-gray-400 text-sm">
398
398
  No more requests to load
399
399
  </div>
400
400
  </div>
@@ -10,7 +10,7 @@ export default {
10
10
  return { ai }
11
11
  },
12
12
  template: `
13
- <div class="flex h-screen bg-white">
13
+ <div class="flex h-screen bg-white dark:bg-gray-900">
14
14
  <!-- Sidebar (hidden when auth required and not authenticated) -->
15
15
  <div v-if="!(ai.requiresAuth && !ai.auth)" class="w-72 xl:w-80 flex-shrink-0">
16
16
  <Sidebar />
@@ -1,10 +1,10 @@
1
1
  export default {
2
2
  template:`
3
- <div class="flex-shrink-0 px-4 py-4 border-b border-gray-200 bg-white min-h-16 select-none">
3
+ <div class="flex-shrink-0 px-4 py-4 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 min-h-16 select-none">
4
4
  <div class="flex items-center justify-between">
5
5
  <button type="button"
6
6
  @click="$emit('home')"
7
- class="text-lg font-semibold text-gray-900 hover:text-blue-600 focus:outline-none transition-colors"
7
+ class="text-lg font-semibold text-gray-900 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none transition-colors"
8
8
  title="Go back to initial state"
9
9
  >
10
10
  History
@@ -13,7 +13,7 @@ export default {
13
13
  <div class="flex items-center space-x-2">
14
14
  <button type="button"
15
15
  @click="$emit('analytics')"
16
- class="text-gray-900 hover:text-blue-600 focus:outline-none transition-colors"
16
+ class="text-gray-900 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none transition-colors"
17
17
  title="Analytics"
18
18
  >
19
19
  <svg class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M5 22a1 1 0 0 1-1-1v-8a1 1 0 0 1 2 0v8a1 1 0 0 1-1 1m5 0a1 1 0 0 1-1-1V3a1 1 0 0 1 2 0v18a1 1 0 0 1-1 1m5 0a1 1 0 0 1-1-1V9a1 1 0 0 1 2 0v12a1 1 0 0 1-1 1m5 0a1 1 0 0 1-1-1v-4a1 1 0 0 1 2 0v4a1 1 0 0 1-1 1"/></svg>
@@ -21,7 +21,7 @@ export default {
21
21
 
22
22
  <button type="button"
23
23
  @click="$emit('new')"
24
- class="text-gray-900 hover:text-blue-600 focus:outline-none transition-colors"
24
+ class="text-gray-900 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none transition-colors"
25
25
  title="New Chat"
26
26
  >
27
27
  <svg class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></g></svg>