llms-py 2.0.29__py3-none-any.whl → 2.0.30__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/main.py CHANGED
@@ -31,7 +31,7 @@ try:
31
31
  except ImportError:
32
32
  HAS_PIL = False
33
33
 
34
- VERSION = "2.0.29"
34
+ VERSION = "2.0.30"
35
35
  _ROOT = None
36
36
  g_config_path = None
37
37
  g_ui_path = None
@@ -1496,7 +1496,10 @@ def main():
1496
1496
  parser.add_argument('--verbose', action='store_true', help='Verbose output')
1497
1497
 
1498
1498
  cli_args, extra_args = parser.parse_known_args()
1499
- if cli_args.verbose:
1499
+
1500
+ # Check for verbose mode from CLI argument or environment variables
1501
+ verbose_env = os.environ.get('VERBOSE', '').lower()
1502
+ if cli_args.verbose or verbose_env in ('1', 'true'):
1500
1503
  g_verbose = True
1501
1504
  # printdump(cli_args)
1502
1505
  if cli_args.model:
llms/ui/Analytics.mjs CHANGED
@@ -24,16 +24,17 @@ export const colors = [
24
24
 
25
25
  const MonthSelector = {
26
26
  template:`
27
- <div class="flex gap-4 items-center">
27
+ <div class="flex flex-col sm:flex-row gap-2 sm:gap-4 items-stretch sm:items-center w-full sm:w-auto">
28
28
  <!-- Months Row -->
29
- <div class="flex gap-2 flex-wrap justify-center">
29
+ <div class="flex gap-1 sm:gap-2 flex-wrap justify-center overflow-x-auto">
30
30
  <template v-for="month in availableMonthsForYear" :key="month">
31
31
  <span v-if="selectedMonth === month"
32
- class="text-xs leading-5 font-semibold bg-indigo-600 text-white rounded-full py-1 px-3 flex items-center space-x-2">
33
- {{ new Date(selectedYear + '-' + month.toString().padStart(2,'0') + '-01').toLocaleString('default', { month: 'long' }) }}
32
+ class="text-xs leading-5 font-semibold bg-indigo-600 text-white rounded-full py-1 px-2 sm:px-3 flex items-center space-x-2 whitespace-nowrap">
33
+ <span class="hidden sm:inline">{{ new Date(selectedYear + '-' + month.toString().padStart(2,'0') + '-01').toLocaleString('default', { month: 'long' }) }}</span>
34
+ <span class="sm:hidden">{{ new Date(selectedYear + '-' + month.toString().padStart(2,'0') + '-01').toLocaleString('default', { month: 'short' }) }}</span>
34
35
  </span>
35
36
  <button v-else type="button"
36
- class="text-xs leading-5 font-semibold bg-slate-400/10 rounded-full py-1 px-3 flex items-center space-x-2 hover:bg-slate-400/20 dark:highlight-white/5"
37
+ class="text-xs leading-5 font-semibold bg-slate-400/10 rounded-full py-1 px-2 sm:px-3 flex items-center space-x-2 hover:bg-slate-400/20 dark:highlight-white/5 whitespace-nowrap"
37
38
  @click="updateSelection(selectedYear, month)">
38
39
  {{ new Date(selectedYear + '-' + month.toString().padStart(2,'0') + '-01').toLocaleString('default', { month: 'short' }) }}
39
40
  </button>
@@ -42,7 +43,7 @@ const MonthSelector = {
42
43
 
43
44
  <!-- Year Dropdown -->
44
45
  <select :value="selectedYear" @change="(e) => updateSelection(parseInt(e.target.value), selectedMonth)"
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
+ 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 flex-shrink-0">
46
47
  <option v-for="year in availableYears" :key="year" :value="year">
47
48
  {{ year }}
48
49
  </option>
@@ -115,9 +116,11 @@ export default {
115
116
  template: `
116
117
  <div class="flex flex-col h-full w-full">
117
118
  <!-- Header -->
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
- <div class="max-w-6xl mx-auto flex items-center justify-between gap-3">
120
- <h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
119
+ <div class="border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-2 sm:px-4 py-3">
120
+ <div
121
+ :class="!$ai.isSidebarOpen ? 'pl-3' : ''"
122
+ class="max-w-6xl mx-auto flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
123
+ <h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100 flex-shrink-0">
121
124
  <RouterLink to="/analytics">Analytics</RouterLink>
122
125
  </h2>
123
126
  <MonthSelector :dailyData="allDailyData" />
@@ -179,13 +182,13 @@ export default {
179
182
  </div>
180
183
 
181
184
  <!-- Cost Analysis Tab -->
182
- <div v-if="activeTab === 'cost'" class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
183
- <div class="flex items-center justify-between mb-6">
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">
185
+ <div v-if="activeTab === 'cost'" class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 sm:p-6">
186
+ <div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 mb-6">
187
+ <h3 class="text-base sm:text-lg font-semibold text-gray-900 dark:text-gray-100">Daily Costs</h3>
188
+ <h3 class="text-sm sm:text-lg font-semibold text-gray-900 dark:text-gray-100">
186
189
  {{ new Date(selectedDay).toLocaleDateString(undefined, { year: 'numeric', month: 'long' }) }}
187
190
  </h3>
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">
191
+ <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 flex-shrink-0">
189
192
  <option value="bar">Bar Chart</option>
190
193
  <option value="line">Line Chart</option>
191
194
  </select>
@@ -200,10 +203,10 @@ export default {
200
203
  </div>
201
204
 
202
205
  <!-- Token Usage Tab -->
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">
206
+ <div v-if="activeTab === 'tokens'" class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 sm:p-6">
207
+ <h3 class="text-base sm:text-lg font-semibold text-gray-900 dark:text-gray-100 mb-6 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-2">
205
208
  <span>Daily Token Usage</span>
206
- <span>
209
+ <span class="text-sm sm:text-base">
207
210
  {{ new Date(selectedDay).toLocaleDateString(undefined, { year: 'numeric', month: 'long' }) }}
208
211
  </span>
209
212
  </h3>
@@ -216,16 +219,16 @@ export default {
216
219
  </div>
217
220
  </div>
218
221
 
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">
222
+ <div v-if="allDailyData[selectedDay]?.requests && ['cost', 'tokens'].includes(activeTab)" class="mt-8 px-2 text-sm sm:text-base text-gray-700 dark:text-gray-300 font-medium flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2">
220
223
  <div>
221
224
  {{ new Date(selectedDay).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' }) }}
222
225
  </div>
223
- <div>
224
- {{ formatCost(allDailyData[selectedDay]?.cost || 0) }}
225
- &#183;
226
- {{ allDailyData[selectedDay]?.requests || 0 }} Requests
227
- &#183;
228
- {{ humanifyNumber(allDailyData[selectedDay]?.inputTokens || 0) }} -> {{ humanifyNumber(allDailyData[selectedDay]?.outputTokens || 0) }} Tokens
226
+ <div class="flex flex-wrap gap-x-2 gap-y-1">
227
+ <span>{{ formatCost(allDailyData[selectedDay]?.cost || 0) }}</span>
228
+ <span>&#183;</span>
229
+ <span>{{ allDailyData[selectedDay]?.requests || 0 }} Requests</span>
230
+ <span>&#183;</span>
231
+ <span>{{ humanifyNumber(allDailyData[selectedDay]?.inputTokens || 0) }} -> {{ humanifyNumber(allDailyData[selectedDay]?.outputTokens || 0) }} Tokens</span>
229
232
  </div>
230
233
  </div>
231
234
 
@@ -290,10 +293,10 @@ export default {
290
293
  <!-- Activity Tab - Full Page Layout -->
291
294
  <div v-if="activeTab === 'activity'" class="h-full flex flex-col bg-white dark:bg-gray-800">
292
295
  <!-- Filters Bar -->
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
- <div class="flex flex-wrap gap-4 items-end">
295
- <div class="flex flex-col">
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">
296
+ <div class="flex-shrink-0 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 sm:px-6 py-4">
297
+ <div class="flex flex-wrap gap-2 sm:gap-4 items-end">
298
+ <div class="flex flex-col flex-1 min-w-[120px] sm:flex-initial">
299
+ <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 w-full">
297
300
  <option value="">All Models</option>
298
301
  <option v-for="model in filterOptions.models" :key="model" :value="model">
299
302
  {{ model }}
@@ -301,8 +304,8 @@ export default {
301
304
  </select>
302
305
  </div>
303
306
 
304
- <div class="flex flex-col">
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">
307
+ <div class="flex flex-col flex-1 min-w-[120px] sm:flex-initial">
308
+ <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 w-full">
306
309
  <option value="">All Providers</option>
307
310
  <option v-for="provider in filterOptions.providers" :key="provider" :value="provider">
308
311
  {{ provider }}
@@ -310,8 +313,8 @@ export default {
310
313
  </select>
311
314
  </div>
312
315
 
313
- <div class="flex flex-col">
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">
316
+ <div class="flex flex-col flex-1 min-w-[140px] sm:flex-initial">
317
+ <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 w-full">
315
318
  <option value="created">Date (Newest)</option>
316
319
  <option value="cost">Cost (Highest)</option>
317
320
  <option value="duration">Duration (Longest)</option>
@@ -319,7 +322,7 @@ export default {
319
322
  </select>
320
323
  </div>
321
324
 
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">
325
+ <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 whitespace-nowrap">
323
326
  Clear Filters
324
327
  </button>
325
328
  </div>
@@ -339,30 +342,30 @@ export default {
339
342
  </div>
340
343
 
341
344
  <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
- <div class="flex items-start justify-between gap-4">
344
- <div class="flex-1 min-w-0">
345
- <div class="flex justify-between">
346
- <div class="flex items-center gap-2 mb-2 flex-wrap">
345
+ <div v-for="request in activityRequests" :key="request.id" class="px-3 sm:px-6 py-4 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
346
+ <div class="flex flex-col lg:flex-row items-start justify-between gap-4">
347
+ <div class="flex-1 min-w-0 w-full">
348
+ <div class="flex flex-col sm:flex-row justify-between gap-2 mb-2">
349
+ <div class="flex items-center gap-2 flex-wrap">
347
350
  <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
351
  <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
352
  <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
353
  <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
354
  </div>
352
- <div class="text-xs text-gray-500 dark:text-gray-400">
355
+ <div class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">
353
356
  {{ formatActivityDate(request.created) }}
354
357
  </div>
355
358
  </div>
356
- <div class="text-sm font-semibold text-gray-900 dark:text-gray-100 truncate">
359
+ <div class="text-sm font-semibold text-gray-900 dark:text-gray-100 truncate mb-3">
357
360
  {{ request.title }}
358
361
  </div>
359
362
 
360
- <div class="grid grid-cols-2 md:grid-cols-5 gap-4 mt-3">
363
+ <div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3 sm:gap-4">
361
364
  <div :title="request.cost">
362
365
  <div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Cost</div>
363
366
  <div class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ formatCost(request.cost) }}</div>
364
367
  </div>
365
- <div>
368
+ <div class="col-span-2 sm:col-span-1">
366
369
  <div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Tokens</div>
367
370
  <div class="text-sm font-semibold text-gray-900 dark:text-gray-100">
368
371
  {{ humanifyNumber(request.inputTokens) }} -> {{ humanifyNumber(request.outputTokens) }}
@@ -379,12 +382,12 @@ export default {
379
382
  </div>
380
383
  </div>
381
384
  </div>
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 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
- View<span class="hidden lg:inline"> Thread</span>
385
+ <div class="flex flex-row lg:flex-col gap-2 w-full lg:w-auto">
386
+ <button type="button" v-if="threadExists(request.threadId)" @click="openThread(request.threadId)" class="flex-1 lg:flex-initial px-3 sm: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">
387
+ View<span class="hidden sm:inline"> Thread</span>
385
388
  </button>
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
- Delete<span class="hidden lg:inline"> Request</span>
389
+ <button type="button" @click="deleteRequestLog(request.id)" class="flex-1 lg:flex-initial px-3 sm: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">
390
+ Delete<span class="hidden sm:inline"> Request</span>
388
391
  </button>
389
392
  </div>
390
393
  </div>
llms/ui/App.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { inject } from "vue"
1
+ import { inject, ref, onMounted, onUnmounted } from "vue"
2
2
  import Sidebar from "./Sidebar.mjs"
3
3
 
4
4
  export default {
@@ -7,17 +7,89 @@ export default {
7
7
  },
8
8
  setup() {
9
9
  const ai = inject('ai')
10
- return { ai }
10
+ const isMobile = ref(false)
11
+
12
+ const checkMobile = () => {
13
+ const wasMobile = isMobile.value
14
+ isMobile.value = window.innerWidth < 1024 // lg breakpoint
15
+
16
+ // Only auto-adjust sidebar state when transitioning between mobile/desktop
17
+ if (wasMobile !== isMobile.value) {
18
+ if (isMobile.value) {
19
+ ai.isSidebarOpen = false
20
+ } else {
21
+ ai.isSidebarOpen = true
22
+ }
23
+ }
24
+ }
25
+
26
+ const toggleSidebar = () => {
27
+ ai.isSidebarOpen = !ai.isSidebarOpen
28
+ }
29
+
30
+ const closeSidebar = () => {
31
+ if (isMobile.value) {
32
+ ai.isSidebarOpen = false
33
+ }
34
+ }
35
+
36
+ onMounted(() => {
37
+ checkMobile()
38
+ window.addEventListener('resize', checkMobile)
39
+ })
40
+
41
+ onUnmounted(() => {
42
+ window.removeEventListener('resize', checkMobile)
43
+ })
44
+
45
+ return { ai, isMobile, toggleSidebar, closeSidebar }
11
46
  },
12
47
  template: `
13
48
  <div class="flex h-screen bg-white dark:bg-gray-900">
49
+ <!-- Mobile Overlay -->
50
+ <div
51
+ v-if="isMobile && ai.isSidebarOpen && !(ai.requiresAuth && !ai.auth)"
52
+ @click="closeSidebar"
53
+ class="fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden"
54
+ ></div>
55
+
14
56
  <!-- Sidebar (hidden when auth required and not authenticated) -->
15
- <div v-if="!(ai.requiresAuth && !ai.auth)" class="w-72 xl:w-80 flex-shrink-0">
16
- <Sidebar />
57
+ <div
58
+ v-if="!(ai.requiresAuth && !ai.auth) && ai.isSidebarOpen"
59
+ :class="[
60
+ 'transition-transform duration-300 ease-in-out z-50',
61
+ 'w-72 xl:w-80 flex-shrink-0',
62
+ 'lg:relative',
63
+ 'fixed inset-y-0 left-0'
64
+ ]"
65
+ >
66
+ <Sidebar @thread-selected="closeSidebar" @toggle-sidebar="toggleSidebar" />
17
67
  </div>
18
68
 
19
69
  <!-- Main Area -->
20
70
  <div class="flex-1 flex flex-col">
71
+ <!-- Collapsed Sidebar Toggle Button -->
72
+ <div
73
+ v-if="!(ai.requiresAuth && !ai.auth) && !ai.isSidebarOpen"
74
+ class="fixed top-4 left-0"
75
+ >
76
+ <button type="button"
77
+ @click="toggleSidebar"
78
+ class="group p-1 text-gray-500 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
79
+ title="Open sidebar"
80
+ >
81
+ <div class="relative w-5 h-5">
82
+ <!-- Default sidebar icon -->
83
+ <svg class="absolute inset-0 group-hover:hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
84
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
85
+ <line x1="9" y1="3" x2="9" y2="21"></line>
86
+ </svg>
87
+ <!-- Hover state: |→ icon -->
88
+ <svg class="absolute inset-0 hidden group-hover:block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="m17.172 11l-4.657-4.657l1.414-1.414L21 12l-7.071 7.071l-1.414-1.414L17.172 13H8v-2zM4 19V5h2v14z"/></svg>
89
+ </div>
90
+ </button>
91
+ </div>
92
+
21
93
  <RouterView />
22
94
  </div>
23
95
  </div>
llms/ui/Brand.mjs CHANGED
@@ -1,14 +1,32 @@
1
1
  export default {
2
2
  template:`
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">
3
+ <div class="flex-shrink-0 pl-2 pr-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
- <button type="button"
6
- @click="$emit('home')"
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
- title="Go back to initial state"
9
- >
10
- History
11
- </button>
5
+ <div class="flex items-center space-x-2">
6
+ <button type="button"
7
+ @click="$emit('toggle-sidebar')"
8
+ class="group relative text-gray-500 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none transition-colors"
9
+ title="Collapse sidebar"
10
+ >
11
+ <div class="relative size-5">
12
+ <!-- Default sidebar icon -->
13
+ <svg class="absolute inset-0 group-hover:hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
14
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
15
+ <line x1="9" y1="3" x2="9" y2="21"></line>
16
+ </svg>
17
+ <!-- Hover state: |← icon -->
18
+ <svg class="absolute inset-0 hidden group-hover:block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="m10.071 4.929l1.414 1.414L6.828 11H16v2H6.828l4.657 4.657l-1.414 1.414L3 12zM18.001 19V5h2v14z"/></svg>
19
+ </div>
20
+ </button>
21
+
22
+ <button type="button"
23
+ @click="$emit('home')"
24
+ 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"
25
+ title="Go back to initial state"
26
+ >
27
+ History
28
+ </button>
29
+ </div>
12
30
 
13
31
  <div class="flex items-center space-x-2">
14
32
  <button type="button"
@@ -30,5 +48,5 @@ export default {
30
48
  </div>
31
49
  </div>
32
50
  `,
33
- emits:['home','new','analytics'],
51
+ emits:['home','new','analytics','toggle-sidebar'],
34
52
  }
llms/ui/ChatPrompt.mjs CHANGED
@@ -87,7 +87,7 @@ export default {
87
87
  <div class="flex-1">
88
88
  <div class="relative">
89
89
  <textarea
90
- ref="messageInput"
90
+ ref="refMessage"
91
91
  v-model="messageText"
92
92
  @keydown.enter.exact.prevent="sendMessage"
93
93
  @keydown.enter.shift.exact="addNewLine"
@@ -168,6 +168,7 @@ export default {
168
168
  } = threads
169
169
 
170
170
  const fileInput = ref(null)
171
+ const refMessage = ref(null)
171
172
  const showSettings = ref(false)
172
173
  const { applySettings } = chatSettings
173
174
 
@@ -550,6 +551,10 @@ export default {
550
551
  } finally {
551
552
  isGenerating.value = false
552
553
  chatPrompt.abortController.value = null
554
+ // Restore focus to the textarea
555
+ nextTick(() => {
556
+ refMessage.value?.focus()
557
+ })
553
558
  }
554
559
  }
555
560
 
@@ -567,6 +572,7 @@ export default {
567
572
  attachedFiles,
568
573
  messageText,
569
574
  fileInput,
575
+ refMessage,
570
576
  showSettings,
571
577
  isDragging,
572
578
  triggerFilePicker,
llms/ui/Main.mjs CHANGED
@@ -30,11 +30,13 @@ 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 dark:border-gray-700 bg-white dark:bg-gray-800 px-2 py-2 w-full min-h-16">
34
- <div class="flex items-center justify-between w-full">
33
+ <div v-if="!($ai.requiresAuth && !$ai.auth)"
34
+ :class="!$ai.isSidebarOpen ? 'pl-6' : ''"
35
+ 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">
36
+ <div class="flex flex-wrap items-center justify-between w-full">
35
37
  <ModelSelector :models="models" v-model="selectedModel" @updated="configUpdated" />
36
38
 
37
- <div class="flex items-center space-x-2">
39
+ <div class="flex items-center space-x-2 pl-4">
38
40
  <SystemPromptSelector :prompts="prompts" v-model="selectedPrompt"
39
41
  :show="showSystemPrompt" @toggle="showSystemPrompt = !showSystemPrompt" />
40
42
  <Avatar />
llms/ui/ModelSelector.mjs CHANGED
@@ -16,9 +16,10 @@ export default {
16
16
  :match="(x, value) => x.id.toLowerCase().includes(value.toLowerCase())"
17
17
  placeholder="Select Model...">
18
18
  <template #item="{ id, provider, provider_model, pricing }">
19
- <div :key="id + provider + provider_model" class="group truncate max-w-72 flex justify-between">
19
+ <div :key="id + provider + provider_model"
20
+ class="group truncate max-w-68 xl:max-w-72 flex justify-between">
20
21
  <span :title="id">{{id}}</span>
21
- <span class="flex items-center space-x-1">
22
+ <div class="hidden md:flex items-center space-x-1">
22
23
  <span v-if="pricing && (parseFloat(pricing.input) == 0 && parseFloat(pricing.input) == 0)">
23
24
  <span class="text-xs text-gray-500 dark:text-gray-400" title="Free to use">FREE</span>
24
25
  </span>
@@ -28,10 +29,10 @@ export default {
28
29
  &#183;
29
30
  {{tokenPrice(pricing.output)}} M
30
31
  </span>
31
- <span :title="provider_model + ' from ' + provider">
32
- <ProviderIcon :provider="provider" />
32
+ <span class="min-w-6" :title="provider_model + ' from ' + provider">
33
+ <ProviderIcon class="hidden xl:inline" :provider="provider" />
33
34
  </span>
34
- </span>
35
+ </div>
35
36
  </div>
36
37
  </template>
37
38
  </Autocomplete>
llms/ui/Sidebar.mjs CHANGED
@@ -164,7 +164,7 @@ const Sidebar = {
164
164
  },
165
165
  template: `
166
166
  <div class="flex flex-col h-full bg-gray-50 dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700">
167
- <Brand @home="goToInitialState" @new="createNewThread" @analytics="goToAnalytics" />
167
+ <Brand @home="goToInitialState" @new="createNewThread" @analytics="goToAnalytics" @toggle-sidebar="$emit('toggle-sidebar')" />
168
168
  <!-- Thread List -->
169
169
  <div class="flex-1 overflow-y-auto">
170
170
  <div v-if="isLoading" class="p-4 text-center text-gray-500 dark:text-gray-400">
@@ -187,7 +187,8 @@ const Sidebar = {
187
187
  </div>
188
188
  </div>
189
189
  `,
190
- setup() {
190
+ emits: ['thread-selected', 'toggle-sidebar'],
191
+ setup(props, { emit }) {
191
192
  const ai = inject('ai')
192
193
  const router = useRouter()
193
194
  const threadStore = useThreadStore()
@@ -208,6 +209,7 @@ const Sidebar = {
208
209
 
209
210
  const selectThread = async (threadId) => {
210
211
  router.push(`${ai.base}/c/${threadId}`)
212
+ emit('thread-selected')
211
213
  }
212
214
 
213
215
  const deleteThread = async (threadId) => {
@@ -223,15 +225,18 @@ const Sidebar = {
223
225
  const createNewThread = async () => {
224
226
  const newThread = await createThread()
225
227
  router.push(`${ai.base}/c/${newThread.id}`)
228
+ emit('thread-selected')
226
229
  }
227
230
 
228
231
  const goToInitialState = () => {
229
232
  clearCurrentThread()
230
233
  router.push(`${ai.base}/`)
234
+ emit('thread-selected')
231
235
  }
232
236
 
233
237
  const goToAnalytics = () => {
234
238
  router.push(`${ai.base}/analytics`)
239
+ emit('thread-selected')
235
240
  }
236
241
 
237
242
  return {
@@ -7,7 +7,7 @@ export default {
7
7
 
8
8
  <Autocomplete ref="refSelector" id="prompt" :options="prompts" label=""
9
9
  :modelValue="modelValue" @update:modelValue="$emit('update:modelValue', $event)"
10
- class="w-72 xl:w-84"
10
+ class="w-68 xl:w-84"
11
11
  :match="(x, value) => x.name.toLowerCase().includes(value.toLowerCase())"
12
12
  placeholder="Select a System Prompt...">
13
13
  <template #item="{ value }">
llms/ui/ai.mjs CHANGED
@@ -6,7 +6,7 @@ const headers = { 'Accept': 'application/json' }
6
6
  const prefsKey = 'llms.prefs'
7
7
 
8
8
  export const o = {
9
- version: '2.0.29',
9
+ version: '2.0.30',
10
10
  base,
11
11
  prefsKey,
12
12
  welcome: 'Welcome to llms.py',
@@ -14,6 +14,7 @@ export const o = {
14
14
  requiresAuth: false,
15
15
  authType: 'apikey', // 'oauth' or 'apikey' - controls which SignIn component to use
16
16
  headers,
17
+ isSidebarOpen: true, // Shared sidebar state (default open for lg+ screens)
17
18
 
18
19
  resolveUrl(url){
19
20
  return url.startsWith('http') || url.startsWith('/v1') ? url : base + url
llms/ui/app.css CHANGED
@@ -347,6 +347,15 @@
347
347
  .top-2 {
348
348
  top: calc(var(--spacing) * 2);
349
349
  }
350
+ .top-4 {
351
+ top: calc(var(--spacing) * 4);
352
+ }
353
+ .top-12 {
354
+ top: calc(var(--spacing) * 12);
355
+ }
356
+ .top-16 {
357
+ top: calc(var(--spacing) * 16);
358
+ }
350
359
  .right-0 {
351
360
  right: calc(var(--spacing) * 0);
352
361
  }
@@ -359,6 +368,9 @@
359
368
  .left-0 {
360
369
  left: calc(var(--spacing) * 0);
361
370
  }
371
+ .left-4 {
372
+ left: calc(var(--spacing) * 4);
373
+ }
362
374
  .left-full {
363
375
  left: 100%;
364
376
  }
@@ -371,9 +383,18 @@
371
383
  .z-20 {
372
384
  z-index: 20;
373
385
  }
386
+ .z-30 {
387
+ z-index: 30;
388
+ }
389
+ .z-40 {
390
+ z-index: 40;
391
+ }
374
392
  .z-50 {
375
393
  z-index: 50;
376
394
  }
395
+ .col-span-2 {
396
+ grid-column: span 2 / span 2;
397
+ }
377
398
  .col-span-3 {
378
399
  grid-column: span 3 / span 3;
379
400
  }
@@ -398,9 +419,15 @@
398
419
  max-width: 96rem;
399
420
  }
400
421
  }
422
+ .-m-2 {
423
+ margin: calc(var(--spacing) * -2);
424
+ }
401
425
  .-m-2\.5 {
402
426
  margin: calc(var(--spacing) * -2.5);
403
427
  }
428
+ .-mx-1 {
429
+ margin-inline: calc(var(--spacing) * -1);
430
+ }
404
431
  .-mx-1\.5 {
405
432
  margin-inline: calc(var(--spacing) * -1.5);
406
433
  }
@@ -413,6 +440,9 @@
413
440
  .mx-auto {
414
441
  margin-inline: auto;
415
442
  }
443
+ .-my-1 {
444
+ margin-block: calc(var(--spacing) * -1);
445
+ }
416
446
  .-my-1\.5 {
417
447
  margin-block: calc(var(--spacing) * -1.5);
418
448
  }
@@ -488,6 +518,9 @@
488
518
  .-ml-px {
489
519
  margin-left: -1px;
490
520
  }
521
+ .ml-0 {
522
+ margin-left: calc(var(--spacing) * 0);
523
+ }
491
524
  .ml-0\.5 {
492
525
  margin-left: calc(var(--spacing) * 0.5);
493
526
  }
@@ -673,6 +706,9 @@
673
706
  .w-48 {
674
707
  width: calc(var(--spacing) * 48);
675
708
  }
709
+ .w-68 {
710
+ width: calc(var(--spacing) * 68);
711
+ }
676
712
  .w-72 {
677
713
  width: calc(var(--spacing) * 72);
678
714
  }
@@ -697,6 +733,9 @@
697
733
  .max-w-48 {
698
734
  max-width: calc(var(--spacing) * 48);
699
735
  }
736
+ .max-w-68 {
737
+ max-width: calc(var(--spacing) * 68);
738
+ }
700
739
  .max-w-72 {
701
740
  max-width: calc(var(--spacing) * 72);
702
741
  }
@@ -715,6 +754,18 @@
715
754
  .min-w-0 {
716
755
  min-width: calc(var(--spacing) * 0);
717
756
  }
757
+ .min-w-2 {
758
+ min-width: calc(var(--spacing) * 2);
759
+ }
760
+ .min-w-6 {
761
+ min-width: calc(var(--spacing) * 6);
762
+ }
763
+ .min-w-\[120px\] {
764
+ min-width: 120px;
765
+ }
766
+ .min-w-\[140px\] {
767
+ min-width: 140px;
768
+ }
718
769
  .min-w-full {
719
770
  min-width: 100%;
720
771
  }
@@ -817,6 +868,9 @@
817
868
  .flex-col {
818
869
  flex-direction: column;
819
870
  }
871
+ .flex-row {
872
+ flex-direction: row;
873
+ }
820
874
  .flex-row-reverse {
821
875
  flex-direction: row-reverse;
822
876
  }
@@ -832,6 +886,9 @@
832
886
  .items-start {
833
887
  align-items: flex-start;
834
888
  }
889
+ .items-stretch {
890
+ align-items: stretch;
891
+ }
835
892
  .justify-between {
836
893
  justify-content: space-between;
837
894
  }
@@ -841,6 +898,9 @@
841
898
  .justify-end {
842
899
  justify-content: flex-end;
843
900
  }
901
+ .gap-1 {
902
+ gap: calc(var(--spacing) * 1);
903
+ }
844
904
  .gap-2 {
845
905
  gap: calc(var(--spacing) * 2);
846
906
  }
@@ -863,6 +923,13 @@
863
923
  margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)));
864
924
  }
865
925
  }
926
+ .space-y-2 {
927
+ :where(& > :not(:last-child)) {
928
+ --tw-space-y-reverse: 0;
929
+ margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));
930
+ margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)));
931
+ }
932
+ }
866
933
  .space-y-4 {
867
934
  :where(& > :not(:last-child)) {
868
935
  --tw-space-y-reverse: 0;
@@ -877,6 +944,9 @@
877
944
  margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
878
945
  }
879
946
  }
947
+ .gap-x-2 {
948
+ column-gap: calc(var(--spacing) * 2);
949
+ }
880
950
  .gap-x-4 {
881
951
  column-gap: calc(var(--spacing) * 4);
882
952
  }
@@ -916,6 +986,9 @@
916
986
  --tw-space-x-reverse: 1;
917
987
  }
918
988
  }
989
+ .gap-y-1 {
990
+ row-gap: calc(var(--spacing) * 1);
991
+ }
919
992
  .gap-y-5 {
920
993
  row-gap: calc(var(--spacing) * 5);
921
994
  }
@@ -1078,6 +1151,9 @@
1078
1151
  .border-yellow-400 {
1079
1152
  border-color: var(--color-yellow-400);
1080
1153
  }
1154
+ .bg-black {
1155
+ background-color: var(--color-black);
1156
+ }
1081
1157
  .bg-black\/40 {
1082
1158
  background-color: color-mix(in srgb, #000 40%, transparent);
1083
1159
  @supports (color: color-mix(in lab, red, red)) {
@@ -1118,6 +1194,9 @@
1118
1194
  .bg-gray-400 {
1119
1195
  background-color: var(--color-gray-400);
1120
1196
  }
1197
+ .bg-gray-500 {
1198
+ background-color: var(--color-gray-500);
1199
+ }
1121
1200
  .bg-gray-500\/75 {
1122
1201
  background-color: color-mix(in srgb, oklch(55.1% 0.027 264.364) 75%, transparent);
1123
1202
  @supports (color: color-mix(in lab, red, red)) {
@@ -1132,6 +1211,9 @@
1132
1211
  .bg-gray-700 {
1133
1212
  background-color: var(--color-gray-700);
1134
1213
  }
1214
+ .bg-gray-900 {
1215
+ background-color: var(--color-gray-900);
1216
+ }
1135
1217
  .bg-gray-900\/80 {
1136
1218
  background-color: color-mix(in srgb, oklch(21% 0.034 264.665) 80%, transparent);
1137
1219
  @supports (color: color-mix(in lab, red, red)) {
@@ -1182,6 +1264,9 @@
1182
1264
  .bg-sky-600 {
1183
1265
  background-color: var(--color-sky-600);
1184
1266
  }
1267
+ .bg-slate-400 {
1268
+ background-color: var(--color-slate-400);
1269
+ }
1185
1270
  .bg-slate-400\/10 {
1186
1271
  background-color: color-mix(in srgb, oklch(70.4% 0.04 256.788) 10%, transparent);
1187
1272
  @supports (color: color-mix(in lab, red, red)) {
@@ -1220,6 +1305,9 @@
1220
1305
  .p-2\.5 {
1221
1306
  padding: calc(var(--spacing) * 2.5);
1222
1307
  }
1308
+ .p-3 {
1309
+ padding: calc(var(--spacing) * 3);
1310
+ }
1223
1311
  .p-4 {
1224
1312
  padding: calc(var(--spacing) * 4);
1225
1313
  }
@@ -1244,6 +1332,9 @@
1244
1332
  .px-6 {
1245
1333
  padding-inline: calc(var(--spacing) * 6);
1246
1334
  }
1335
+ .py-0 {
1336
+ padding-block: calc(var(--spacing) * 0);
1337
+ }
1247
1338
  .py-0\.5 {
1248
1339
  padding-block: calc(var(--spacing) * 0.5);
1249
1340
  }
@@ -1274,6 +1365,9 @@
1274
1365
  .py-12 {
1275
1366
  padding-block: calc(var(--spacing) * 12);
1276
1367
  }
1368
+ .pt-0 {
1369
+ padding-top: calc(var(--spacing) * 0);
1370
+ }
1277
1371
  .pt-0\.5 {
1278
1372
  padding-top: calc(var(--spacing) * 0.5);
1279
1373
  }
@@ -1346,9 +1440,15 @@
1346
1440
  .pl-3 {
1347
1441
  padding-left: calc(var(--spacing) * 3);
1348
1442
  }
1443
+ .pl-4 {
1444
+ padding-left: calc(var(--spacing) * 4);
1445
+ }
1349
1446
  .pl-5 {
1350
1447
  padding-left: calc(var(--spacing) * 5);
1351
1448
  }
1449
+ .pl-6 {
1450
+ padding-left: calc(var(--spacing) * 6);
1451
+ }
1352
1452
  .pl-10 {
1353
1453
  padding-left: calc(var(--spacing) * 10);
1354
1454
  }
@@ -1589,6 +1689,9 @@
1589
1689
  .uppercase {
1590
1690
  text-transform: uppercase;
1591
1691
  }
1692
+ .underline {
1693
+ text-decoration-line: underline;
1694
+ }
1592
1695
  .placeholder-gray-500 {
1593
1696
  &::placeholder {
1594
1697
  color: var(--color-gray-500);
@@ -1650,6 +1753,9 @@
1650
1753
  --tw-inset-ring-shadow: inset 0 0 0 1px var(--tw-inset-ring-color, currentcolor);
1651
1754
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1652
1755
  }
1756
+ .ring-black {
1757
+ --tw-ring-color: var(--color-black);
1758
+ }
1653
1759
  .ring-black\/5 {
1654
1760
  --tw-ring-color: color-mix(in srgb, #000 5%, transparent);
1655
1761
  @supports (color: color-mix(in lab, red, red)) {
@@ -1664,6 +1770,9 @@
1664
1770
  .ring-indigo-500 {
1665
1771
  --tw-ring-color: var(--color-indigo-500);
1666
1772
  }
1773
+ .inset-ring-gray-900 {
1774
+ --tw-inset-ring-color: var(--color-gray-900);
1775
+ }
1667
1776
  .inset-ring-gray-900\/5 {
1668
1777
  --tw-inset-ring-color: color-mix(in srgb, oklch(21% 0.034 264.665) 5%, transparent);
1669
1778
  @supports (color: color-mix(in lab, red, red)) {
@@ -1766,6 +1875,20 @@
1766
1875
  -webkit-user-select: none;
1767
1876
  user-select: none;
1768
1877
  }
1878
+ .group-hover\:block {
1879
+ &:is(:where(.group):hover *) {
1880
+ @media (hover: hover) {
1881
+ display: block;
1882
+ }
1883
+ }
1884
+ }
1885
+ .group-hover\:hidden {
1886
+ &:is(:where(.group):hover *) {
1887
+ @media (hover: hover) {
1888
+ display: none;
1889
+ }
1890
+ }
1891
+ }
1769
1892
  .group-hover\:inline {
1770
1893
  &:is(:where(.group):hover *) {
1771
1894
  @media (hover: hover) {
@@ -1780,6 +1903,13 @@
1780
1903
  }
1781
1904
  }
1782
1905
  }
1906
+ .group-hover\:opacity-0 {
1907
+ &:is(:where(.group):hover *) {
1908
+ @media (hover: hover) {
1909
+ opacity: 0%;
1910
+ }
1911
+ }
1912
+ }
1783
1913
  .group-hover\:opacity-100 {
1784
1914
  &:is(:where(.group):hover *) {
1785
1915
  @media (hover: hover) {
@@ -2536,6 +2666,11 @@
2536
2666
  outline-width: 2px;
2537
2667
  }
2538
2668
  }
2669
+ .sm\:col-span-1 {
2670
+ @media (width >= 40rem) {
2671
+ grid-column: span 1 / span 1;
2672
+ }
2673
+ }
2539
2674
  .sm\:col-span-3 {
2540
2675
  @media (width >= 40rem) {
2541
2676
  grid-column: span 3 / span 3;
@@ -2581,11 +2716,21 @@
2581
2716
  display: none;
2582
2717
  }
2583
2718
  }
2719
+ .sm\:inline {
2720
+ @media (width >= 40rem) {
2721
+ display: inline;
2722
+ }
2723
+ }
2584
2724
  .sm\:table-cell {
2585
2725
  @media (width >= 40rem) {
2586
2726
  display: table-cell;
2587
2727
  }
2588
2728
  }
2729
+ .sm\:w-auto {
2730
+ @media (width >= 40rem) {
2731
+ width: auto;
2732
+ }
2733
+ }
2589
2734
  .sm\:w-full {
2590
2735
  @media (width >= 40rem) {
2591
2736
  width: 100%;
@@ -2601,6 +2746,11 @@
2601
2746
  max-width: 65ch;
2602
2747
  }
2603
2748
  }
2749
+ .sm\:flex-initial {
2750
+ @media (width >= 40rem) {
2751
+ flex: 0 auto;
2752
+ }
2753
+ }
2604
2754
  .sm\:translate-y-0 {
2605
2755
  @media (width >= 40rem) {
2606
2756
  --tw-translate-y: calc(var(--spacing) * 0);
@@ -2623,16 +2773,41 @@
2623
2773
  scale: var(--tw-scale-x) var(--tw-scale-y);
2624
2774
  }
2625
2775
  }
2776
+ .sm\:grid-cols-3 {
2777
+ @media (width >= 40rem) {
2778
+ grid-template-columns: repeat(3, minmax(0, 1fr));
2779
+ }
2780
+ }
2781
+ .sm\:flex-row {
2782
+ @media (width >= 40rem) {
2783
+ flex-direction: row;
2784
+ }
2785
+ }
2626
2786
  .sm\:flex-row-reverse {
2627
2787
  @media (width >= 40rem) {
2628
2788
  flex-direction: row-reverse;
2629
2789
  }
2630
2790
  }
2791
+ .sm\:flex-wrap {
2792
+ @media (width >= 40rem) {
2793
+ flex-wrap: wrap;
2794
+ }
2795
+ }
2631
2796
  .sm\:items-center {
2632
2797
  @media (width >= 40rem) {
2633
2798
  align-items: center;
2634
2799
  }
2635
2800
  }
2801
+ .sm\:gap-2 {
2802
+ @media (width >= 40rem) {
2803
+ gap: calc(var(--spacing) * 2);
2804
+ }
2805
+ }
2806
+ .sm\:gap-4 {
2807
+ @media (width >= 40rem) {
2808
+ gap: calc(var(--spacing) * 4);
2809
+ }
2810
+ }
2636
2811
  .sm\:rounded-lg {
2637
2812
  @media (width >= 40rem) {
2638
2813
  border-radius: var(--radius-lg);
@@ -2653,6 +2828,16 @@
2653
2828
  padding: calc(var(--spacing) * 6);
2654
2829
  }
2655
2830
  }
2831
+ .sm\:px-3 {
2832
+ @media (width >= 40rem) {
2833
+ padding-inline: calc(var(--spacing) * 3);
2834
+ }
2835
+ }
2836
+ .sm\:px-4 {
2837
+ @media (width >= 40rem) {
2838
+ padding-inline: calc(var(--spacing) * 4);
2839
+ }
2840
+ }
2656
2841
  .sm\:px-6 {
2657
2842
  @media (width >= 40rem) {
2658
2843
  padding-inline: calc(var(--spacing) * 6);
@@ -2683,6 +2868,18 @@
2683
2868
  text-align: left;
2684
2869
  }
2685
2870
  }
2871
+ .sm\:text-base {
2872
+ @media (width >= 40rem) {
2873
+ font-size: var(--text-base);
2874
+ line-height: var(--tw-leading, var(--text-base--line-height));
2875
+ }
2876
+ }
2877
+ .sm\:text-lg {
2878
+ @media (width >= 40rem) {
2879
+ font-size: var(--text-lg);
2880
+ line-height: var(--tw-leading, var(--text-lg--line-height));
2881
+ }
2882
+ }
2686
2883
  .sm\:text-sm {
2687
2884
  @media (width >= 40rem) {
2688
2885
  font-size: var(--text-sm);
@@ -2695,6 +2892,21 @@
2695
2892
  transition-duration: 700ms;
2696
2893
  }
2697
2894
  }
2895
+ .md\:relative {
2896
+ @media (width >= 48rem) {
2897
+ position: relative;
2898
+ }
2899
+ }
2900
+ .md\:flex {
2901
+ @media (width >= 48rem) {
2902
+ display: flex;
2903
+ }
2904
+ }
2905
+ .md\:hidden {
2906
+ @media (width >= 48rem) {
2907
+ display: none;
2908
+ }
2909
+ }
2698
2910
  .md\:inline {
2699
2911
  @media (width >= 48rem) {
2700
2912
  display: inline;
@@ -2710,6 +2922,12 @@
2710
2922
  max-width: var(--container-xl);
2711
2923
  }
2712
2924
  }
2925
+ .md\:translate-x-0 {
2926
+ @media (width >= 48rem) {
2927
+ --tw-translate-x: calc(var(--spacing) * 0);
2928
+ translate: var(--tw-translate-x) var(--tw-translate-y);
2929
+ }
2930
+ }
2713
2931
  .md\:grid-cols-2 {
2714
2932
  @media (width >= 48rem) {
2715
2933
  grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -2740,6 +2958,11 @@
2740
2958
  position: fixed;
2741
2959
  }
2742
2960
  }
2961
+ .lg\:relative {
2962
+ @media (width >= 64rem) {
2963
+ position: relative;
2964
+ }
2965
+ }
2743
2966
  .lg\:inset-y-0 {
2744
2967
  @media (width >= 64rem) {
2745
2968
  inset-block: calc(var(--spacing) * 0);
@@ -2775,21 +2998,52 @@
2775
2998
  width: calc(var(--spacing) * 72);
2776
2999
  }
2777
3000
  }
3001
+ .lg\:w-84 {
3002
+ @media (width >= 64rem) {
3003
+ width: calc(var(--spacing) * 84);
3004
+ }
3005
+ }
3006
+ .lg\:w-auto {
3007
+ @media (width >= 64rem) {
3008
+ width: auto;
3009
+ }
3010
+ }
2778
3011
  .lg\:max-w-screen-md {
2779
3012
  @media (width >= 64rem) {
2780
3013
  max-width: var(--breakpoint-md);
2781
3014
  }
2782
3015
  }
3016
+ .lg\:flex-initial {
3017
+ @media (width >= 64rem) {
3018
+ flex: 0 auto;
3019
+ }
3020
+ }
3021
+ .lg\:translate-x-0 {
3022
+ @media (width >= 64rem) {
3023
+ --tw-translate-x: calc(var(--spacing) * 0);
3024
+ translate: var(--tw-translate-x) var(--tw-translate-y);
3025
+ }
3026
+ }
2783
3027
  .lg\:grid-cols-2 {
2784
3028
  @media (width >= 64rem) {
2785
3029
  grid-template-columns: repeat(2, minmax(0, 1fr));
2786
3030
  }
2787
3031
  }
3032
+ .lg\:grid-cols-5 {
3033
+ @media (width >= 64rem) {
3034
+ grid-template-columns: repeat(5, minmax(0, 1fr));
3035
+ }
3036
+ }
2788
3037
  .lg\:flex-col {
2789
3038
  @media (width >= 64rem) {
2790
3039
  flex-direction: column;
2791
3040
  }
2792
3041
  }
3042
+ .lg\:flex-row {
3043
+ @media (width >= 64rem) {
3044
+ flex-direction: row;
3045
+ }
3046
+ }
2793
3047
  .lg\:px-8 {
2794
3048
  @media (width >= 64rem) {
2795
3049
  padding-inline: calc(var(--spacing) * 8);
@@ -2830,6 +3084,16 @@
2830
3084
  max-width: var(--container-3xl);
2831
3085
  }
2832
3086
  }
3087
+ .xl\:max-w-68 {
3088
+ @media (width >= 80rem) {
3089
+ max-width: calc(var(--spacing) * 68);
3090
+ }
3091
+ }
3092
+ .xl\:max-w-72 {
3093
+ @media (width >= 80rem) {
3094
+ max-width: calc(var(--spacing) * 72);
3095
+ }
3096
+ }
2833
3097
  .xl\:max-w-screen-lg {
2834
3098
  @media (width >= 80rem) {
2835
3099
  max-width: var(--breakpoint-lg);
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llms-py
3
- Version: 2.0.29
3
+ Version: 2.0.30
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
@@ -2,27 +2,27 @@ llms/__init__.py,sha256=Mk6eHi13yoUxLlzhwfZ6A1IjsfSQt9ShhOdbLXTvffU,53
2
2
  llms/__main__.py,sha256=hrBulHIt3lmPm1BCyAEVtB6DQ0Hvc3gnIddhHCmJasg,151
3
3
  llms/index.html,sha256=_pkjdzCX95HTf19LgE4gMh6tLittcnf7M_jL2hSEbbM,3250
4
4
  llms/llms.json,sha256=oTMlVM3nYeooQgsPbIGN2LQ-1aq0u1v38sjmxHPiGAc,41331
5
- llms/main.py,sha256=BLS3wUocCGDMUxraahVBXYbFwLnr0WmeFUqhwk-MtVE,90456
5
+ llms/main.py,sha256=TIJZL21UCZfyRMh_HPdsFi2ahpIFgs6o4KkRSkxTwqc,90617
6
6
  llms/ui.json,sha256=iBOmpNeD5-o8AgUa51ymS-KemovJ7bm9J1fnL0nf8jk,134025
7
- llms/ui/Analytics.mjs,sha256=r5il9Yvh2lje23e4zbGVUqDu2CG75mtyx84MeMVmHpU,72055
8
- llms/ui/App.mjs,sha256=95pBXexTt3tgaX-Toh5oS0PdFxrVt3ZU8wu8Ywoz6FI,648
7
+ llms/ui/Analytics.mjs,sha256=LfWbUlpb__0EEYtHu6e4r8AeyhsNQeAxrg44RuNSR0M,73261
8
+ llms/ui/App.mjs,sha256=S-yomlT-6UMB9R5dx-W6EEO4pcE3ldV5e9mr1Kk_Lyw,3822
9
9
  llms/ui/Avatar.mjs,sha256=TgouwV9bN-Ou1Tf2zCDtVaRiUB21TXZZPFCTlFL-xxQ,3387
10
- llms/ui/Brand.mjs,sha256=kJn7O4Oo3tbZi87Ifbbcq7AZvhfUqbn_ZcXcyv_WI1A,2075
11
- llms/ui/ChatPrompt.mjs,sha256=9VXtXz4vHKLujZOC_yRk0XRNP0L1OTtBNOXeEOTmI-M,26941
12
- llms/ui/Main.mjs,sha256=T_5CmGGDAMc-9tk2hmX6UTUWZ7sDt-A_G22utLndB0Y,42926
13
- llms/ui/ModelSelector.mjs,sha256=MignHFpyeF1_K6tcHqLLCmQgNGQf60gyiP6lzApQzJs,3112
10
+ llms/ui/Brand.mjs,sha256=JLN_lPirNXqS332g0B_WVOlFRVg3lNe1Q56TRnpj0zQ,3411
11
+ llms/ui/ChatPrompt.mjs,sha256=7Bx2-ossJPm8F2n9M82vNt8J-ayHEXft3qctd9TeSdw,27147
12
+ llms/ui/Main.mjs,sha256=SxItBoxJXf5vEVzfUYY6F7imJuwdm0By3EhrCfo4a1M,43016
13
+ llms/ui/ModelSelector.mjs,sha256=0YuNXlyEeiD4YKyXtxtHu0rT7zIv_lnfljvB3hm7YE8,3198
14
14
  llms/ui/OAuthSignIn.mjs,sha256=IdA9Tbswlh74a_-9e9YulOpqLfRpodRLGfCZ9sTZ5jU,4879
15
15
  llms/ui/ProviderIcon.mjs,sha256=HTjlgtXEpekn8iNN_S0uswbbvL0iGb20N15-_lXdojk,9054
16
16
  llms/ui/ProviderStatus.mjs,sha256=v5_Qx5kX-JbnJHD6Or5THiePzcX3Wf9ODcS4Q-kfQbM,6084
17
17
  llms/ui/Recents.mjs,sha256=sRoesSktUvBZ7UjfFJwp0btCQj5eQvnTDjSUDzN8ySU,8864
18
18
  llms/ui/SettingsDialog.mjs,sha256=vyqLOrACBICwT8qQ10oJAMOYeA1phrAyz93mZygn-9Y,19956
19
- llms/ui/Sidebar.mjs,sha256=lVhVxC74UeQmBdhv_MxUmbdBJCfjWY0P6rtx1yJHywg,10932
19
+ llms/ui/Sidebar.mjs,sha256=93wCzrbY4F9VBFGiWujNIj5ynxP_9bmv5pJQOGdybIc,11183
20
20
  llms/ui/SignIn.mjs,sha256=df3b-7L3ZIneDGbJWUk93K9RGo40gVeuR5StzT1ZH9g,2324
21
21
  llms/ui/SystemPromptEditor.mjs,sha256=PffkNPV6hGbm1QZBKPI7yvWPZSBL7qla0d-JEJ4mxYo,1466
22
- llms/ui/SystemPromptSelector.mjs,sha256=A0qZHJGNwsX53X-DVn8ddBGdgn32Gd7wf1rRc1LL0v8,3769
22
+ llms/ui/SystemPromptSelector.mjs,sha256=UgoeuscFes0B1oFkx74dFwC0JgRib37VM4Gy3-kCVDQ,3769
23
23
  llms/ui/Welcome.mjs,sha256=r9j7unF9CF3k7gEQBMRMVsa2oSjgHGNn46Oa5l5BwlY,950
24
- llms/ui/ai.mjs,sha256=XroCFHkmtsthJ5LtR5EY2ZoVekRzAEuWMYEEIzcTRFs,4768
25
- llms/ui/app.css,sha256=0QcB6usfv9o5UieT27lPEBami38akKjhanL3hudnMH8,108326
24
+ llms/ui/ai.mjs,sha256=Mrhsr-Y9VRWRZ9oSMu4z6u6nxll-xlRz0Cj1R9jv72A,4849
25
+ llms/ui/app.css,sha256=B0GMDo-hRJ5ufV4d1qYRpIX3LSnBfBZMHeqrDcC1z8A,113515
26
26
  llms/ui/fav.svg,sha256=_R6MFeXl6wBFT0lqcUxYQIDWgm246YH_3hSTW0oO8qw,734
27
27
  llms/ui/markdown.mjs,sha256=uWSyBZZ8a76Dkt53q6CJzxg7Gkx7uayX089td3Srv8w,6388
28
28
  llms/ui/tailwind.input.css,sha256=QInTVDpCR89OTzRo9AePdAa-MX3i66RkhNOfa4_7UAg,12086
@@ -40,9 +40,9 @@ llms/ui/lib/servicestack-vue.mjs,sha256=EU3cnlQuTzsmPvoK50JFN98t4AO80vVNA-CS2kaS
40
40
  llms/ui/lib/vue-router.min.mjs,sha256=fR30GHoXI1u81zyZ26YEU105pZgbbAKSXbpnzFKIxls,30418
41
41
  llms/ui/lib/vue.min.mjs,sha256=iXh97m5hotl0eFllb3aoasQTImvp7mQoRJ_0HoxmZkw,163811
42
42
  llms/ui/lib/vue.mjs,sha256=dS8LKOG01t9CvZ04i0tbFXHqFXOO_Ha4NmM3BytjQAs,537071
43
- llms_py-2.0.29.dist-info/licenses/LICENSE,sha256=bus9cuAOWeYqBk2OuhSABVV1P4z7hgrEFISpyda_H5w,1532
44
- llms_py-2.0.29.dist-info/METADATA,sha256=5fJrs4oI-YvVi3yA9Bx34j5K3e2cPXiqxiKnhsyBJ7E,37076
45
- llms_py-2.0.29.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
46
- llms_py-2.0.29.dist-info/entry_points.txt,sha256=WswyE7PfnkZMIxboC-MS6flBD6wm-CYU7JSUnMhqMfM,40
47
- llms_py-2.0.29.dist-info/top_level.txt,sha256=gC7hk9BKSeog8gyg-EM_g2gxm1mKHwFRfK-10BxOsa4,5
48
- llms_py-2.0.29.dist-info/RECORD,,
43
+ llms_py-2.0.30.dist-info/licenses/LICENSE,sha256=bus9cuAOWeYqBk2OuhSABVV1P4z7hgrEFISpyda_H5w,1532
44
+ llms_py-2.0.30.dist-info/METADATA,sha256=Z8sG5vefO_CfmlEX3myTw0NF8diiyoMreMTNni4w2OY,37076
45
+ llms_py-2.0.30.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
46
+ llms_py-2.0.30.dist-info/entry_points.txt,sha256=WswyE7PfnkZMIxboC-MS6flBD6wm-CYU7JSUnMhqMfM,40
47
+ llms_py-2.0.30.dist-info/top_level.txt,sha256=gC7hk9BKSeog8gyg-EM_g2gxm1mKHwFRfK-10BxOsa4,5
48
+ llms_py-2.0.30.dist-info/RECORD,,