llms-py 2.0.5__tar.gz → 2.0.7__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 (39) hide show
  1. {llms_py-2.0.5/llms_py.egg-info → llms_py-2.0.7}/PKG-INFO +9 -1
  2. {llms_py-2.0.5 → llms_py-2.0.7}/README.md +8 -0
  3. {llms_py-2.0.5 → llms_py-2.0.7}/llms.py +12 -2
  4. {llms_py-2.0.5 → llms_py-2.0.7/llms_py.egg-info}/PKG-INFO +9 -1
  5. {llms_py-2.0.5 → llms_py-2.0.7}/pyproject.toml +1 -1
  6. {llms_py-2.0.5 → llms_py-2.0.7}/setup.py +1 -1
  7. {llms_py-2.0.5 → llms_py-2.0.7}/ui/ChatPrompt.mjs +8 -6
  8. {llms_py-2.0.5 → llms_py-2.0.7}/ui/Main.mjs +210 -1
  9. {llms_py-2.0.5 → llms_py-2.0.7}/ui/app.css +41 -55
  10. {llms_py-2.0.5 → llms_py-2.0.7}/LICENSE +0 -0
  11. {llms_py-2.0.5 → llms_py-2.0.7}/MANIFEST.in +0 -0
  12. {llms_py-2.0.5 → llms_py-2.0.7}/index.html +0 -0
  13. {llms_py-2.0.5 → llms_py-2.0.7}/llms.json +0 -0
  14. {llms_py-2.0.5 → llms_py-2.0.7}/llms_py.egg-info/SOURCES.txt +0 -0
  15. {llms_py-2.0.5 → llms_py-2.0.7}/llms_py.egg-info/dependency_links.txt +0 -0
  16. {llms_py-2.0.5 → llms_py-2.0.7}/llms_py.egg-info/entry_points.txt +0 -0
  17. {llms_py-2.0.5 → llms_py-2.0.7}/llms_py.egg-info/not-zip-safe +0 -0
  18. {llms_py-2.0.5 → llms_py-2.0.7}/llms_py.egg-info/requires.txt +0 -0
  19. {llms_py-2.0.5 → llms_py-2.0.7}/llms_py.egg-info/top_level.txt +0 -0
  20. {llms_py-2.0.5 → llms_py-2.0.7}/requirements.txt +0 -0
  21. {llms_py-2.0.5 → llms_py-2.0.7}/setup.cfg +0 -0
  22. {llms_py-2.0.5 → llms_py-2.0.7}/ui/App.mjs +0 -0
  23. {llms_py-2.0.5 → llms_py-2.0.7}/ui/Recents.mjs +0 -0
  24. {llms_py-2.0.5 → llms_py-2.0.7}/ui/Sidebar.mjs +0 -0
  25. {llms_py-2.0.5 → llms_py-2.0.7}/ui/fav.svg +0 -0
  26. {llms_py-2.0.5 → llms_py-2.0.7}/ui/lib/highlight.min.mjs +0 -0
  27. {llms_py-2.0.5 → llms_py-2.0.7}/ui/lib/idb.min.mjs +0 -0
  28. {llms_py-2.0.5 → llms_py-2.0.7}/ui/lib/marked.min.mjs +0 -0
  29. {llms_py-2.0.5 → llms_py-2.0.7}/ui/lib/servicestack-client.min.mjs +0 -0
  30. {llms_py-2.0.5 → llms_py-2.0.7}/ui/lib/servicestack-vue.min.mjs +0 -0
  31. {llms_py-2.0.5 → llms_py-2.0.7}/ui/lib/vue-router.min.mjs +0 -0
  32. {llms_py-2.0.5 → llms_py-2.0.7}/ui/lib/vue.min.mjs +0 -0
  33. {llms_py-2.0.5 → llms_py-2.0.7}/ui/lib/vue.mjs +0 -0
  34. {llms_py-2.0.5 → llms_py-2.0.7}/ui/markdown.mjs +0 -0
  35. {llms_py-2.0.5 → llms_py-2.0.7}/ui/tailwind.input.css +0 -0
  36. {llms_py-2.0.5 → llms_py-2.0.7}/ui/threadStore.mjs +0 -0
  37. {llms_py-2.0.5 → llms_py-2.0.7}/ui/typography.css +0 -0
  38. {llms_py-2.0.5 → llms_py-2.0.7}/ui/utils.mjs +0 -0
  39. {llms_py-2.0.5 → llms_py-2.0.7}/ui.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llms-py
3
- Version: 2.0.5
3
+ Version: 2.0.7
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
@@ -63,6 +63,14 @@ Configure additional providers and models in [llms.json](llms.json)
63
63
  - **Unified Models**: Define custom model names that map to different provider-specific names
64
64
  - **Multi-Model Support**: Support for over 160+ different LLMs
65
65
 
66
+ ## llms.py UI
67
+
68
+ Simple ChatGPT-like UI to access ALL Your LLMs, Locally or Remotely!
69
+
70
+ [![llms.py UI](https://servicestack.net/img/posts/llms-py-ui/bg.webp)](https://servicestack.net/posts/llms-py-ui)
71
+
72
+ Read the [Introductory Blog Post](https://servicestack.net/posts/llms-py-ui).
73
+
66
74
  ## Installation
67
75
 
68
76
  ### Option 1: Install from PyPI
@@ -23,6 +23,14 @@ Configure additional providers and models in [llms.json](llms.json)
23
23
  - **Unified Models**: Define custom model names that map to different provider-specific names
24
24
  - **Multi-Model Support**: Support for over 160+ different LLMs
25
25
 
26
+ ## llms.py UI
27
+
28
+ Simple ChatGPT-like UI to access ALL Your LLMs, Locally or Remotely!
29
+
30
+ [![llms.py UI](https://servicestack.net/img/posts/llms-py-ui/bg.webp)](https://servicestack.net/posts/llms-py-ui)
31
+
32
+ Read the [Introductory Blog Post](https://servicestack.net/posts/llms-py-ui).
33
+
26
34
  ## Installation
27
35
 
28
36
  ### Option 1: Install from PyPI
@@ -21,7 +21,7 @@ from aiohttp import web
21
21
  from pathlib import Path
22
22
  from importlib import resources # Py≥3.9 (pip install importlib_resources for 3.7/3.8)
23
23
 
24
- VERSION = "2.0.5"
24
+ VERSION = "2.0.7"
25
25
  _ROOT = None
26
26
  g_config_path = None
27
27
  g_ui_path = None
@@ -66,6 +66,16 @@ def chat_summary(chat):
66
66
  item['file']['file_data'] = f"({len(data)})"
67
67
  return json.dumps(clone, indent=2)
68
68
 
69
+ def gemini_chat_summary(gemini_chat):
70
+ """Summarize Gemini chat completion request for logging. Replace inline_data with size of content only"""
71
+ clone = json.loads(json.dumps(gemini_chat))
72
+ for content in clone['contents']:
73
+ for part in content['parts']:
74
+ if 'inline_data' in part:
75
+ data = part['inline_data']['data']
76
+ part['inline_data']['data'] = f"({len(data)})"
77
+ return json.dumps(clone, indent=2)
78
+
69
79
  image_exts = 'png,webp,jpg,jpeg,gif,bmp,svg,tiff,ico'.split(',')
70
80
  audio_exts = 'mp3,wav,ogg,flac,m4a,opus,webm'.split(',')
71
81
 
@@ -417,7 +427,7 @@ class GoogleProvider(OpenAiProvider):
417
427
  gemini_chat_url = f"https://generativelanguage.googleapis.com/v1beta/models/{chat['model']}:generateContent?key={self.api_key}"
418
428
 
419
429
  _log(f"POST {gemini_chat_url}")
420
- _log(json.dumps(gemini_chat, indent=2))
430
+ _log(gemini_chat_summary(gemini_chat))
421
431
 
422
432
  if self.curl:
423
433
  curl_args = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llms-py
3
- Version: 2.0.5
3
+ Version: 2.0.7
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
@@ -63,6 +63,14 @@ Configure additional providers and models in [llms.json](llms.json)
63
63
  - **Unified Models**: Define custom model names that map to different provider-specific names
64
64
  - **Multi-Model Support**: Support for over 160+ different LLMs
65
65
 
66
+ ## llms.py UI
67
+
68
+ Simple ChatGPT-like UI to access ALL Your LLMs, Locally or Remotely!
69
+
70
+ [![llms.py UI](https://servicestack.net/img/posts/llms-py-ui/bg.webp)](https://servicestack.net/posts/llms-py-ui)
71
+
72
+ Read the [Introductory Blog Post](https://servicestack.net/posts/llms-py-ui).
73
+
66
74
  ## Installation
67
75
 
68
76
  ### Option 1: Install from PyPI
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "llms-py"
7
- version = "2.0.5"
7
+ version = "2.0.7"
8
8
  description = "A lightweight CLI tool and OpenAI-compatible server for querying multiple Large Language Model (LLM) providers"
9
9
  readme = "README.md"
10
10
  license = "BSD-3-Clause"
@@ -16,7 +16,7 @@ with open(os.path.join(this_directory, "requirements.txt"), encoding="utf-8") as
16
16
 
17
17
  setup(
18
18
  name="llms-py",
19
- version="2.0.5",
19
+ version="2.0.7",
20
20
  author="ServiceStack",
21
21
  author_email="team@servicestack.net",
22
22
  description="A lightweight CLI tool and OpenAI-compatible server for querying multiple Large Language Model (LLM) providers",
@@ -146,12 +146,14 @@ export default {
146
146
  // allow re-selecting the same file
147
147
  if (fileInput.value) fileInput.value.value = ''
148
148
 
149
- if (hasImage()) {
150
- messageText.value = getTextContent(config.defaults.image)
151
- } else if (hasAudio()) {
152
- messageText.value = getTextContent(config.defaults.audio)
153
- } else {
154
- messageText.value = getTextContent(config.defaults.file)
149
+ if (!messageText.value.trim()) {
150
+ if (hasImage()) {
151
+ messageText.value = getTextContent(config.defaults.image)
152
+ } else if (hasAudio()) {
153
+ messageText.value = getTextContent(config.defaults.audio)
154
+ } else {
155
+ messageText.value = getTextContent(config.defaults.file)
156
+ }
155
157
  }
156
158
  }
157
159
  const removeAttachment = (i) => {
@@ -197,6 +197,51 @@ export default {
197
197
  <div class="max-w-2xl mx-auto">
198
198
  <ChatPrompt :model="selectedModel" :systemPrompt="currentSystemPrompt" />
199
199
  </div>
200
+
201
+ <!-- Export/Import buttons -->
202
+ <div class="mt-2 flex space-x-3 justify-center">
203
+ <button type="button"
204
+ @click="exportThreads"
205
+ :disabled="isExporting"
206
+ :title="'Export ' + threads?.threads?.value?.length + ' conversations'"
207
+ 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"
208
+ >
209
+ <svg v-if="!isExporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
210
+ <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>
211
+ </svg>
212
+ <svg v-else class="size-5 mr-1 animate-spin" fill="none" viewBox="0 0 24 24">
213
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
214
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
215
+ </svg>
216
+ {{ isExporting ? 'Exporting...' : 'Export' }}
217
+ </button>
218
+
219
+ <button type="button"
220
+ @click="triggerImport"
221
+ :disabled="isImporting"
222
+ title="Import conversations from JSON file"
223
+ 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"
224
+ >
225
+ <svg v-if="!isImporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
226
+ <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"/>
227
+ </svg>
228
+ <svg v-else class="size-5 mr-1 animate-spin" fill="none" viewBox="0 0 24 24">
229
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
230
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
231
+ </svg>
232
+ {{ isImporting ? 'Importing...' : 'Import' }}
233
+ </button>
234
+
235
+ <!-- Hidden file input for import -->
236
+ <input
237
+ ref="fileInput"
238
+ type="file"
239
+ accept=".json"
240
+ @change="handleFileImport"
241
+ class="hidden"
242
+ />
243
+ </div>
244
+
200
245
  </div>
201
246
 
202
247
  <!-- Messages -->
@@ -229,11 +274,25 @@ export default {
229
274
 
230
275
  <!-- Message bubble -->
231
276
  <div
232
- class="message rounded-lg px-4 py-3"
277
+ class="message rounded-lg px-4 py-3 relative group"
233
278
  :class="message.role === 'user'
234
279
  ? 'bg-blue-600 text-white'
235
280
  : 'bg-gray-100 text-gray-900 border border-gray-200'"
236
281
  >
282
+ <!-- Copy button in top right corner -->
283
+ <button
284
+ type="button"
285
+ @click="copyMessageContent(message)"
286
+ 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-2 focus:ring-blue-500"
287
+ :class="message.role === 'user' ? 'text-white/70 hover:text-white hover:bg-white/20' : 'text-gray-500 hover:text-gray-700'"
288
+ title="Copy message content"
289
+ >
290
+ <svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
291
+ <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
292
+ <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
293
+ </svg>
294
+ </button>
295
+
237
296
  <div
238
297
  v-if="message.role === 'assistant'"
239
298
  v-html="renderMarkdown(message.content)"
@@ -351,6 +410,9 @@ export default {
351
410
  const currentSystemPrompt = ref('')
352
411
  const showSystemPrompt = ref(false)
353
412
  const messagesContainer = ref(null)
413
+ const isExporting = ref(false)
414
+ const isImporting = ref(false)
415
+ const fileInput = ref(null)
354
416
 
355
417
  // Auto-scroll to bottom when new messages arrive
356
418
  const scrollToBottom = async () => {
@@ -412,6 +474,129 @@ export default {
412
474
  }))
413
475
  })
414
476
 
477
+ async function exportThreads() {
478
+ if (isExporting.value) return
479
+
480
+ isExporting.value = true
481
+ try {
482
+ // Load all threads from IndexedDB
483
+ await threads.loadThreads()
484
+ const allThreads = threads.threads.value
485
+
486
+ // Create export data with metadata
487
+ const exportData = {
488
+ exportedAt: new Date().toISOString(),
489
+ version: '1.0',
490
+ source: 'llms.py',
491
+ threadCount: allThreads.length,
492
+ threads: allThreads
493
+ }
494
+
495
+ // Create and download JSON file
496
+ const jsonString = JSON.stringify(exportData, null, 2)
497
+ const blob = new Blob([jsonString], { type: 'application/json' })
498
+ const url = URL.createObjectURL(blob)
499
+
500
+ const link = document.createElement('a')
501
+ link.href = url
502
+ link.download = `llms-threads-export-${new Date().toISOString().split('T')[0]}.json`
503
+ document.body.appendChild(link)
504
+ link.click()
505
+ document.body.removeChild(link)
506
+ URL.revokeObjectURL(url)
507
+
508
+ } catch (error) {
509
+ console.error('Failed to export threads:', error)
510
+ alert('Failed to export threads: ' + error.message)
511
+ } finally {
512
+ isExporting.value = false
513
+ }
514
+ }
515
+
516
+ function triggerImport() {
517
+ if (isImporting.value) return
518
+ fileInput.value?.click()
519
+ }
520
+
521
+ async function handleFileImport(event) {
522
+ const file = event.target.files?.[0]
523
+ if (!file) return
524
+
525
+ isImporting.value = true
526
+ try {
527
+ const text = await file.text()
528
+ const importData = JSON.parse(text)
529
+
530
+ // Validate import data structure
531
+ if (!importData.threads || !Array.isArray(importData.threads)) {
532
+ throw new Error('Invalid import file: missing or invalid threads array')
533
+ }
534
+
535
+ // Import threads one by one
536
+ let importedCount = 0
537
+ let updatedCount = 0
538
+
539
+ for (const threadData of importData.threads) {
540
+ if (!threadData.id) {
541
+ console.warn('Skipping thread without ID:', threadData)
542
+ continue
543
+ }
544
+
545
+ try {
546
+ // Check if thread already exists
547
+ const existingThread = await threads.getThread(threadData.id)
548
+
549
+ if (existingThread) {
550
+ // Update existing thread
551
+ await threads.updateThread(threadData.id, {
552
+ title: threadData.title,
553
+ model: threadData.model,
554
+ systemPrompt: threadData.systemPrompt,
555
+ messages: threadData.messages || [],
556
+ createdAt: threadData.createdAt,
557
+ // Keep the existing updatedAt or use imported one
558
+ updatedAt: threadData.updatedAt || existingThread.updatedAt
559
+ })
560
+ updatedCount++
561
+ } else {
562
+ // Add new thread directly to IndexedDB
563
+ await threads.initDB()
564
+ const db = await threads.initDB()
565
+ const tx = db.transaction(['threads'], 'readwrite')
566
+ await tx.objectStore('threads').add({
567
+ id: threadData.id,
568
+ title: threadData.title || 'Imported Chat',
569
+ model: threadData.model || '',
570
+ systemPrompt: threadData.systemPrompt || '',
571
+ messages: threadData.messages || [],
572
+ createdAt: threadData.createdAt || new Date().toISOString(),
573
+ updatedAt: threadData.updatedAt || new Date().toISOString()
574
+ })
575
+ await tx.complete
576
+ importedCount++
577
+ }
578
+ } catch (error) {
579
+ console.error('Failed to import thread:', threadData.id, error)
580
+ }
581
+ }
582
+
583
+ // Reload threads to reflect changes
584
+ await threads.loadThreads()
585
+
586
+ alert(`Import completed!\nNew threads: ${importedCount}\nUpdated threads: ${updatedCount}`)
587
+
588
+ } catch (error) {
589
+ console.error('Failed to import threads:', error)
590
+ alert('Failed to import threads: ' + error.message)
591
+ } finally {
592
+ isImporting.value = false
593
+ // Clear the file input
594
+ if (fileInput.value) {
595
+ fileInput.value.value = ''
596
+ }
597
+ }
598
+ }
599
+
415
600
  function configUpdated() {
416
601
  console.log('configUpdated', selectedModel.value, models.length, models.includes(selectedModel.value))
417
602
  if (selectedModel.value && !models.includes(selectedModel.value)) {
@@ -441,6 +626,23 @@ export default {
441
626
  }
442
627
  const formatReasoning = (r) => typeof r === 'string' ? r : JSON.stringify(r, null, 2)
443
628
 
629
+ // Copy message content to clipboard
630
+ const copyMessageContent = async (message) => {
631
+ try {
632
+ await navigator.clipboard.writeText(message.content)
633
+ // Could add a toast notification here if desired
634
+ } catch (err) {
635
+ console.error('Failed to copy message content:', err)
636
+ // Fallback for older browsers
637
+ const textArea = document.createElement('textarea')
638
+ textArea.value = message.content
639
+ document.body.appendChild(textArea)
640
+ textArea.select()
641
+ document.execCommand('copy')
642
+ document.body.removeChild(textArea)
643
+ }
644
+ }
645
+
444
646
  onMounted(() => {
445
647
  setTimeout(addCopyButtons, 1)
446
648
  })
@@ -465,7 +667,14 @@ export default {
465
667
  isReasoningExpanded,
466
668
  toggleReasoning,
467
669
  formatReasoning,
670
+ copyMessageContent,
468
671
  configUpdated,
672
+ exportThreads,
673
+ isExporting,
674
+ triggerImport,
675
+ handleFileImport,
676
+ isImporting,
677
+ fileInput,
469
678
  }
470
679
  }
471
680
  }
@@ -135,7 +135,6 @@
135
135
  --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
136
136
  --default-font-family: var(--font-sans);
137
137
  --default-mono-font-family: var(--font-mono);
138
- --default-ring-color: hsl(var(--ring));
139
138
  }
140
139
  }
141
140
  @layer base {
@@ -331,6 +330,9 @@
331
330
  .top-0 {
332
331
  top: calc(var(--spacing) * 0);
333
332
  }
333
+ .top-2 {
334
+ top: calc(var(--spacing) * 2);
335
+ }
334
336
  .right-0 {
335
337
  right: calc(var(--spacing) * 0);
336
338
  }
@@ -376,15 +378,9 @@
376
378
  max-width: 96rem;
377
379
  }
378
380
  }
379
- .-m-2 {
380
- margin: calc(var(--spacing) * -2);
381
- }
382
381
  .-m-2\.5 {
383
382
  margin: calc(var(--spacing) * -2.5);
384
383
  }
385
- .-mx-1 {
386
- margin-inline: calc(var(--spacing) * -1);
387
- }
388
384
  .-mx-1\.5 {
389
385
  margin-inline: calc(var(--spacing) * -1.5);
390
386
  }
@@ -394,9 +390,6 @@
394
390
  .mx-auto {
395
391
  margin-inline: auto;
396
392
  }
397
- .-my-1 {
398
- margin-block: calc(var(--spacing) * -1);
399
- }
400
393
  .-my-1\.5 {
401
394
  margin-block: calc(var(--spacing) * -1.5);
402
395
  }
@@ -460,9 +453,6 @@
460
453
  .-ml-px {
461
454
  margin-left: -1px;
462
455
  }
463
- .ml-0 {
464
- margin-left: calc(var(--spacing) * 0);
465
- }
466
456
  .ml-0\.5 {
467
457
  margin-left: calc(var(--spacing) * 0.5);
468
458
  }
@@ -633,9 +623,6 @@
633
623
  .w-80 {
634
624
  width: calc(var(--spacing) * 80);
635
625
  }
636
- .w-84 {
637
- width: calc(var(--spacing) * 84);
638
- }
639
626
  .w-full {
640
627
  width: 100%;
641
628
  }
@@ -1031,9 +1018,6 @@
1031
1018
  .bg-gray-400 {
1032
1019
  background-color: var(--color-gray-400);
1033
1020
  }
1034
- .bg-gray-500 {
1035
- background-color: var(--color-gray-500);
1036
- }
1037
1021
  .bg-gray-500\/75 {
1038
1022
  background-color: color-mix(in srgb, oklch(55.1% 0.027 264.364) 75%, transparent);
1039
1023
  @supports (color: color-mix(in lab, red, red)) {
@@ -1046,9 +1030,6 @@
1046
1030
  .bg-gray-700 {
1047
1031
  background-color: var(--color-gray-700);
1048
1032
  }
1049
- .bg-gray-900 {
1050
- background-color: var(--color-gray-900);
1051
- }
1052
1033
  .bg-gray-900\/80 {
1053
1034
  background-color: color-mix(in srgb, oklch(21% 0.034 264.665) 80%, transparent);
1054
1035
  @supports (color: color-mix(in lab, red, red)) {
@@ -1142,9 +1123,6 @@
1142
1123
  .px-6 {
1143
1124
  padding-inline: calc(var(--spacing) * 6);
1144
1125
  }
1145
- .py-0 {
1146
- padding-block: calc(var(--spacing) * 0);
1147
- }
1148
1126
  .py-0\.5 {
1149
1127
  padding-block: calc(var(--spacing) * 0.5);
1150
1128
  }
@@ -1175,9 +1153,6 @@
1175
1153
  .py-12 {
1176
1154
  padding-block: calc(var(--spacing) * 12);
1177
1155
  }
1178
- .pt-0 {
1179
- padding-top: calc(var(--spacing) * 0);
1180
- }
1181
1156
  .pt-0\.5 {
1182
1157
  padding-top: calc(var(--spacing) * 0.5);
1183
1158
  }
@@ -1444,15 +1419,18 @@
1444
1419
  .text-sky-600 {
1445
1420
  color: var(--color-sky-600);
1446
1421
  }
1447
- .text-slate-300 {
1448
- color: var(--color-slate-300);
1449
- }
1450
1422
  .text-slate-500 {
1451
1423
  color: var(--color-slate-500);
1452
1424
  }
1453
1425
  .text-white {
1454
1426
  color: var(--color-white);
1455
1427
  }
1428
+ .text-white\/70 {
1429
+ color: color-mix(in srgb, #fff 70%, transparent);
1430
+ @supports (color: color-mix(in lab, red, red)) {
1431
+ color: color-mix(in oklab, var(--color-white) 70%, transparent);
1432
+ }
1433
+ }
1456
1434
  .text-yellow-400 {
1457
1435
  color: var(--color-yellow-400);
1458
1436
  }
@@ -1471,9 +1449,6 @@
1471
1449
  .uppercase {
1472
1450
  text-transform: uppercase;
1473
1451
  }
1474
- .underline {
1475
- text-decoration-line: underline;
1476
- }
1477
1452
  .placeholder-gray-500 {
1478
1453
  &::placeholder {
1479
1454
  color: var(--color-gray-500);
@@ -1531,9 +1506,6 @@
1531
1506
  --tw-inset-ring-shadow: inset 0 0 0 1px var(--tw-inset-ring-color, currentcolor);
1532
1507
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1533
1508
  }
1534
- .ring-black {
1535
- --tw-ring-color: var(--color-black);
1536
- }
1537
1509
  .ring-black\/5 {
1538
1510
  --tw-ring-color: color-mix(in srgb, #000 5%, transparent);
1539
1511
  @supports (color: color-mix(in lab, red, red)) {
@@ -1543,9 +1515,6 @@
1543
1515
  .ring-indigo-500 {
1544
1516
  --tw-ring-color: var(--color-indigo-500);
1545
1517
  }
1546
- .inset-ring-gray-900 {
1547
- --tw-inset-ring-color: var(--color-gray-900);
1548
- }
1549
1518
  .inset-ring-gray-900\/5 {
1550
1519
  --tw-inset-ring-color: color-mix(in srgb, oklch(21% 0.034 264.665) 5%, transparent);
1551
1520
  @supports (color: color-mix(in lab, red, red)) {
@@ -1788,6 +1757,16 @@
1788
1757
  }
1789
1758
  }
1790
1759
  }
1760
+ .hover\:bg-black\/10 {
1761
+ &:hover {
1762
+ @media (hover: hover) {
1763
+ background-color: color-mix(in srgb, #000 10%, transparent);
1764
+ @supports (color: color-mix(in lab, red, red)) {
1765
+ background-color: color-mix(in oklab, var(--color-black) 10%, transparent);
1766
+ }
1767
+ }
1768
+ }
1769
+ }
1791
1770
  .hover\:bg-blue-100 {
1792
1771
  &:hover {
1793
1772
  @media (hover: hover) {
@@ -1886,6 +1865,16 @@
1886
1865
  }
1887
1866
  }
1888
1867
  }
1868
+ .hover\:bg-white\/20 {
1869
+ &:hover {
1870
+ @media (hover: hover) {
1871
+ background-color: color-mix(in srgb, #fff 20%, transparent);
1872
+ @supports (color: color-mix(in lab, red, red)) {
1873
+ background-color: color-mix(in oklab, var(--color-white) 20%, transparent);
1874
+ }
1875
+ }
1876
+ }
1877
+ }
1889
1878
  .hover\:bg-yellow-50 {
1890
1879
  &:hover {
1891
1880
  @media (hover: hover) {
@@ -2019,6 +2008,13 @@
2019
2008
  }
2020
2009
  }
2021
2010
  }
2011
+ .hover\:text-white {
2012
+ &:hover {
2013
+ @media (hover: hover) {
2014
+ color: var(--color-white);
2015
+ }
2016
+ }
2017
+ }
2022
2018
  .hover\:opacity-70 {
2023
2019
  &:hover {
2024
2020
  @media (hover: hover) {
@@ -2245,6 +2241,11 @@
2245
2241
  color: var(--color-slate-500);
2246
2242
  }
2247
2243
  }
2244
+ .disabled\:opacity-50 {
2245
+ &:disabled {
2246
+ opacity: 50%;
2247
+ }
2248
+ }
2248
2249
  .disabled\:shadow-none {
2249
2250
  &:disabled {
2250
2251
  --tw-shadow: 0 0 #0000;
@@ -2507,11 +2508,6 @@
2507
2508
  display: table-cell;
2508
2509
  }
2509
2510
  }
2510
- .md\:w-84 {
2511
- @media (width >= 48rem) {
2512
- width: calc(var(--spacing) * 84);
2513
- }
2514
- }
2515
2511
  .md\:max-w-xl {
2516
2512
  @media (width >= 48rem) {
2517
2513
  max-width: var(--container-xl);
@@ -2562,16 +2558,6 @@
2562
2558
  width: calc(var(--spacing) * 72);
2563
2559
  }
2564
2560
  }
2565
- .lg\:w-80 {
2566
- @media (width >= 64rem) {
2567
- width: calc(var(--spacing) * 80);
2568
- }
2569
- }
2570
- .lg\:w-84 {
2571
- @media (width >= 64rem) {
2572
- width: calc(var(--spacing) * 84);
2573
- }
2574
- }
2575
2561
  .lg\:max-w-screen-md {
2576
2562
  @media (width >= 64rem) {
2577
2563
  max-width: var(--breakpoint-md);
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes