llms-py 2.0.6__py3-none-any.whl → 2.0.8__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.
Files changed (31) hide show
  1. llms.py +19 -3
  2. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/llms.json +21 -0
  3. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/ChatPrompt.mjs +8 -6
  4. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/Main.mjs +34 -2
  5. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/Recents.mjs +31 -2
  6. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/app.css +36 -58
  7. {llms_py-2.0.6.dist-info → llms_py-2.0.8.dist-info}/METADATA +9 -1
  8. llms_py-2.0.8.dist-info/RECORD +30 -0
  9. llms_py-2.0.6.dist-info/RECORD +0 -30
  10. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/index.html +0 -0
  11. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/requirements.txt +0 -0
  12. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/App.mjs +0 -0
  13. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/Sidebar.mjs +0 -0
  14. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/fav.svg +0 -0
  15. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/lib/highlight.min.mjs +0 -0
  16. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/lib/idb.min.mjs +0 -0
  17. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/lib/marked.min.mjs +0 -0
  18. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/lib/servicestack-client.min.mjs +0 -0
  19. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/lib/servicestack-vue.min.mjs +0 -0
  20. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/lib/vue-router.min.mjs +0 -0
  21. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/lib/vue.min.mjs +0 -0
  22. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/markdown.mjs +0 -0
  23. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/tailwind.input.css +0 -0
  24. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/threadStore.mjs +0 -0
  25. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/typography.css +0 -0
  26. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui/utils.mjs +0 -0
  27. {llms_py-2.0.6.data → llms_py-2.0.8.data}/data/ui.json +0 -0
  28. {llms_py-2.0.6.dist-info → llms_py-2.0.8.dist-info}/WHEEL +0 -0
  29. {llms_py-2.0.6.dist-info → llms_py-2.0.8.dist-info}/entry_points.txt +0 -0
  30. {llms_py-2.0.6.dist-info → llms_py-2.0.8.dist-info}/licenses/LICENSE +0 -0
  31. {llms_py-2.0.6.dist-info → llms_py-2.0.8.dist-info}/top_level.txt +0 -0
llms.py CHANGED
@@ -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.6"
24
+ VERSION = "2.0.8"
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
 
@@ -209,7 +219,13 @@ class OpenAiProvider:
209
219
  self.api_key = api_key
210
220
  self.models = models
211
221
 
212
- self.chat_url = f"{base_url}/v1/chat/completions"
222
+ # check if base_url ends with /v{\d} to handle providers with different versions (e.g. z.ai uses /v4)
223
+ last_segment = base_url.rsplit('/',1)[1]
224
+ if last_segment.startswith('v') and last_segment[1:].isdigit():
225
+ self.chat_url = f"{base_url}/chat/completions"
226
+ else:
227
+ self.chat_url = f"{base_url}/v1/chat/completions"
228
+
213
229
  self.headers = kwargs['headers'] if 'headers' in kwargs else {
214
230
  "Content-Type": "application/json",
215
231
  }
@@ -417,7 +433,7 @@ class GoogleProvider(OpenAiProvider):
417
433
  gemini_chat_url = f"https://generativelanguage.googleapis.com/v1beta/models/{chat['model']}:generateContent?key={self.api_key}"
418
434
 
419
435
  _log(f"POST {gemini_chat_url}")
420
- _log(json.dumps(gemini_chat, indent=2))
436
+ _log(gemini_chat_summary(gemini_chat))
421
437
 
422
438
  if self.curl:
423
439
  curl_args = [
@@ -193,6 +193,9 @@
193
193
  "nova-micro": "amazon/nova-micro-v1",
194
194
  "nova-lite": "amazon/nova-lite-v1",
195
195
  "nova-pro": "amazon/nova-pro-v1",
196
+ "claude-opus-4-1": "anthropic/claude-opus-4.1",
197
+ "claude-sonnet-4-5": "anthropic/claude-sonnet-4.5",
198
+ "claude-sonnet-4-0": "anthropic/claude-sonnet-4",
196
199
  "gpt-5": "openai/gpt-5",
197
200
  "gpt-5-chat": "openai/gpt-5-chat",
198
201
  "gpt-5-mini": "openai/gpt-5-mini",
@@ -210,11 +213,13 @@
210
213
  "grok-4": "x-ai/grok-4",
211
214
  "grok-4-fast": "x-ai/grok-4-fast",
212
215
  "grok-code-fast-1": "x-ai/grok-code-fast-1",
216
+ "glm-4.6": "z-ai/glm-4.6",
213
217
  "glm-4.5v": "z-ai/glm-4.5v",
214
218
  "glm-4.5": "z-ai/glm-4.5",
215
219
  "glm-4.5-air": "z-ai/glm-4.5-air",
216
220
  "kimi-k2": "moonshotai/kimi-k2",
217
221
  "deepseek-v3.1:671b": "deepseek/deepseek-chat",
222
+ "deepseek-v3.2-exp": "deepseek/deepseek-v3.2-exp",
218
223
  "deepseek-chat-v3.1:671b": "deepseek/deepseek-chat-v3.1:free",
219
224
  "deepseek-r1:671b": "deepseek/deepseek-r1",
220
225
  "deepseek-v3.1-terminus": "deepseek/deepseek-v3.1-terminus",
@@ -263,6 +268,7 @@
263
268
  "models": {
264
269
  "claude-opus-4-1": "claude-opus-4-1",
265
270
  "claude-opus-4": "claude-opus-4",
271
+ "claude-sonnet-4-5": "claude-sonnet-4-5",
266
272
  "claude-sonnet-4-0": "claude-sonnet-4-0",
267
273
  "claude-3-7-sonnet": "claude-3-7-sonnet-latest",
268
274
  "claude-3-5-haiku": "claude-3-5-haiku-latest",
@@ -385,6 +391,21 @@
385
391
  "qwen2.5-omni:7b": "qwen2.5-omni-7b"
386
392
  }
387
393
  },
394
+ "z.ai": {
395
+ "enabled": false,
396
+ "type": "OpenAiProvider",
397
+ "base_url": "https://api.z.ai/api/paas/v4",
398
+ "api_key": "$ZAI_API_KEY",
399
+ "models": {
400
+ "glm-4.6": "glm-4.6",
401
+ "glm-4.5": "glm-4.5",
402
+ "glm-4.5-air": "glm-4.5-air",
403
+ "glm-4.5-x": "glm-4.5-x",
404
+ "glm-4.5-airx": "glm-4.5-airx",
405
+ "glm-4.5-flash": "glm-4.5-flash",
406
+ "glm-4:32b": "glm-4-32b-0414-128k"
407
+ }
408
+ },
388
409
  "mistral": {
389
410
  "enabled": false,
390
411
  "type": "OpenAiProvider",
@@ -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) => {
@@ -20,7 +20,7 @@ const ProviderStatus = {
20
20
  <span class="text-red-700">{{(config.status.disabled||[]).length}}</span>
21
21
  </div>
22
22
  </button>
23
- <div v-if="showPopover" ref="popoverRef" class="absolute right-0 mt-2 w-72 max-h-112 overflow-y-auto bg-white border border-gray-200 rounded-md shadow-lg z-10">
23
+ <div v-if="showPopover" ref="popoverRef" class="absolute right-0 mt-2 w-72 max-h-116 overflow-y-auto bg-white border border-gray-200 rounded-md shadow-lg z-10">
24
24
  <div class="divide-y divide-gray-100">
25
25
  <div v-for="p in allProviders" :key="p" class="flex items-center justify-between px-3 py-2">
26
26
  <label :for="'chk_' + p" class="cursor-pointer text-sm text-gray-900 truncate mr-2" :title="p">{{ p }}</label>
@@ -274,11 +274,25 @@ export default {
274
274
 
275
275
  <!-- Message bubble -->
276
276
  <div
277
- class="message rounded-lg px-4 py-3"
277
+ class="message rounded-lg px-4 py-3 relative group"
278
278
  :class="message.role === 'user'
279
279
  ? 'bg-blue-600 text-white'
280
280
  : 'bg-gray-100 text-gray-900 border border-gray-200'"
281
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
+
282
296
  <div
283
297
  v-if="message.role === 'assistant'"
284
298
  v-html="renderMarkdown(message.content)"
@@ -612,6 +626,23 @@ export default {
612
626
  }
613
627
  const formatReasoning = (r) => typeof r === 'string' ? r : JSON.stringify(r, null, 2)
614
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
+
615
646
  onMounted(() => {
616
647
  setTimeout(addCopyButtons, 1)
617
648
  })
@@ -636,6 +667,7 @@ export default {
636
667
  isReasoningExpanded,
637
668
  toggleReasoning,
638
669
  formatReasoning,
670
+ copyMessageContent,
639
671
  configUpdated,
640
672
  exportThreads,
641
673
  isExporting,
@@ -1,5 +1,5 @@
1
- import { ref, computed, onMounted, watch, inject } from 'vue'
2
- import { useRouter } from 'vue-router'
1
+ import { ref, onMounted, watch, inject } from 'vue'
2
+ import { useRouter, useRoute } from 'vue-router'
3
3
  import { useThreadStore } from './threadStore.mjs'
4
4
  import { renderMarkdown } from './markdown.mjs'
5
5
 
@@ -169,7 +169,36 @@ export default {
169
169
  </div>
170
170
  `,
171
171
  setup() {
172
+ const router = useRouter()
173
+ const route = useRoute()
172
174
  const q = ref('')
175
+
176
+ // Initialize search query from URL parameter
177
+ onMounted(() => {
178
+ const urlQuery = route.query.q || ''
179
+ q.value = urlQuery
180
+ })
181
+
182
+ // Watch for changes in the search input and update URL
183
+ watch(q, (newQuery) => {
184
+ const currentQuery = route.query.q || ''
185
+ if (newQuery !== currentQuery) {
186
+ // Update URL without triggering navigation
187
+ router.replace({
188
+ path: route.path,
189
+ query: newQuery ? { q: newQuery } : {}
190
+ })
191
+ }
192
+ })
193
+
194
+ // Watch for URL changes (browser back/forward) and update search input
195
+ watch(() => route.query.q, (newQuery) => {
196
+ const urlQuery = newQuery || ''
197
+ if (q.value !== urlQuery) {
198
+ q.value = urlQuery
199
+ }
200
+ })
201
+
173
202
  return {
174
203
  q,
175
204
  }
@@ -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
  }
@@ -457,15 +450,9 @@
457
450
  .mb-4 {
458
451
  margin-bottom: calc(var(--spacing) * 4);
459
452
  }
460
- .mb-6 {
461
- margin-bottom: calc(var(--spacing) * 6);
462
- }
463
453
  .-ml-px {
464
454
  margin-left: -1px;
465
455
  }
466
- .ml-0 {
467
- margin-left: calc(var(--spacing) * 0);
468
- }
469
456
  .ml-0\.5 {
470
457
  margin-left: calc(var(--spacing) * 0.5);
471
458
  }
@@ -636,9 +623,6 @@
636
623
  .w-80 {
637
624
  width: calc(var(--spacing) * 80);
638
625
  }
639
- .w-84 {
640
- width: calc(var(--spacing) * 84);
641
- }
642
626
  .w-full {
643
627
  width: 100%;
644
628
  }
@@ -1034,9 +1018,6 @@
1034
1018
  .bg-gray-400 {
1035
1019
  background-color: var(--color-gray-400);
1036
1020
  }
1037
- .bg-gray-500 {
1038
- background-color: var(--color-gray-500);
1039
- }
1040
1021
  .bg-gray-500\/75 {
1041
1022
  background-color: color-mix(in srgb, oklch(55.1% 0.027 264.364) 75%, transparent);
1042
1023
  @supports (color: color-mix(in lab, red, red)) {
@@ -1049,9 +1030,6 @@
1049
1030
  .bg-gray-700 {
1050
1031
  background-color: var(--color-gray-700);
1051
1032
  }
1052
- .bg-gray-900 {
1053
- background-color: var(--color-gray-900);
1054
- }
1055
1033
  .bg-gray-900\/80 {
1056
1034
  background-color: color-mix(in srgb, oklch(21% 0.034 264.665) 80%, transparent);
1057
1035
  @supports (color: color-mix(in lab, red, red)) {
@@ -1145,9 +1123,6 @@
1145
1123
  .px-6 {
1146
1124
  padding-inline: calc(var(--spacing) * 6);
1147
1125
  }
1148
- .py-0 {
1149
- padding-block: calc(var(--spacing) * 0);
1150
- }
1151
1126
  .py-0\.5 {
1152
1127
  padding-block: calc(var(--spacing) * 0.5);
1153
1128
  }
@@ -1178,9 +1153,6 @@
1178
1153
  .py-12 {
1179
1154
  padding-block: calc(var(--spacing) * 12);
1180
1155
  }
1181
- .pt-0 {
1182
- padding-top: calc(var(--spacing) * 0);
1183
- }
1184
1156
  .pt-0\.5 {
1185
1157
  padding-top: calc(var(--spacing) * 0.5);
1186
1158
  }
@@ -1447,15 +1419,18 @@
1447
1419
  .text-sky-600 {
1448
1420
  color: var(--color-sky-600);
1449
1421
  }
1450
- .text-slate-300 {
1451
- color: var(--color-slate-300);
1452
- }
1453
1422
  .text-slate-500 {
1454
1423
  color: var(--color-slate-500);
1455
1424
  }
1456
1425
  .text-white {
1457
1426
  color: var(--color-white);
1458
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
+ }
1459
1434
  .text-yellow-400 {
1460
1435
  color: var(--color-yellow-400);
1461
1436
  }
@@ -1474,9 +1449,6 @@
1474
1449
  .uppercase {
1475
1450
  text-transform: uppercase;
1476
1451
  }
1477
- .underline {
1478
- text-decoration-line: underline;
1479
- }
1480
1452
  .placeholder-gray-500 {
1481
1453
  &::placeholder {
1482
1454
  color: var(--color-gray-500);
@@ -1534,9 +1506,6 @@
1534
1506
  --tw-inset-ring-shadow: inset 0 0 0 1px var(--tw-inset-ring-color, currentcolor);
1535
1507
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1536
1508
  }
1537
- .ring-black {
1538
- --tw-ring-color: var(--color-black);
1539
- }
1540
1509
  .ring-black\/5 {
1541
1510
  --tw-ring-color: color-mix(in srgb, #000 5%, transparent);
1542
1511
  @supports (color: color-mix(in lab, red, red)) {
@@ -1546,9 +1515,6 @@
1546
1515
  .ring-indigo-500 {
1547
1516
  --tw-ring-color: var(--color-indigo-500);
1548
1517
  }
1549
- .inset-ring-gray-900 {
1550
- --tw-inset-ring-color: var(--color-gray-900);
1551
- }
1552
1518
  .inset-ring-gray-900\/5 {
1553
1519
  --tw-inset-ring-color: color-mix(in srgb, oklch(21% 0.034 264.665) 5%, transparent);
1554
1520
  @supports (color: color-mix(in lab, red, red)) {
@@ -1791,6 +1757,16 @@
1791
1757
  }
1792
1758
  }
1793
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
+ }
1794
1770
  .hover\:bg-blue-100 {
1795
1771
  &:hover {
1796
1772
  @media (hover: hover) {
@@ -1889,6 +1865,16 @@
1889
1865
  }
1890
1866
  }
1891
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
+ }
1892
1878
  .hover\:bg-yellow-50 {
1893
1879
  &:hover {
1894
1880
  @media (hover: hover) {
@@ -2022,6 +2008,13 @@
2022
2008
  }
2023
2009
  }
2024
2010
  }
2011
+ .hover\:text-white {
2012
+ &:hover {
2013
+ @media (hover: hover) {
2014
+ color: var(--color-white);
2015
+ }
2016
+ }
2017
+ }
2025
2018
  .hover\:opacity-70 {
2026
2019
  &:hover {
2027
2020
  @media (hover: hover) {
@@ -2515,11 +2508,6 @@
2515
2508
  display: table-cell;
2516
2509
  }
2517
2510
  }
2518
- .md\:w-84 {
2519
- @media (width >= 48rem) {
2520
- width: calc(var(--spacing) * 84);
2521
- }
2522
- }
2523
2511
  .md\:max-w-xl {
2524
2512
  @media (width >= 48rem) {
2525
2513
  max-width: var(--container-xl);
@@ -2570,16 +2558,6 @@
2570
2558
  width: calc(var(--spacing) * 72);
2571
2559
  }
2572
2560
  }
2573
- .lg\:w-80 {
2574
- @media (width >= 64rem) {
2575
- width: calc(var(--spacing) * 80);
2576
- }
2577
- }
2578
- .lg\:w-84 {
2579
- @media (width >= 64rem) {
2580
- width: calc(var(--spacing) * 84);
2581
- }
2582
- }
2583
2561
  .lg\:max-w-screen-md {
2584
2562
  @media (width >= 64rem) {
2585
2563
  max-width: var(--breakpoint-md);
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llms-py
3
- Version: 2.0.6
3
+ Version: 2.0.8
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
@@ -0,0 +1,30 @@
1
+ llms.py,sha256=1E1TCAR67oFLjGRnpt-sw84rxPJo5_rBEqEc5KyJinc,52762
2
+ llms_py-2.0.8.data/data/index.html,sha256=nOgYc7t9QjlbRoy35-W6HpQbEEuD0-O1rkA8j-IYhZ4,2276
3
+ llms_py-2.0.8.data/data/llms.json,sha256=aFYhZDnlO9eCrbnEwm-zx7WQJiIlllQ2OikknnPPfRw,19274
4
+ llms_py-2.0.8.data/data/requirements.txt,sha256=iv-JIaacmTl-hSho3QmphcKnbRRYx1st47yjz_178Ro,8
5
+ llms_py-2.0.8.data/data/ui.json,sha256=iBOmpNeD5-o8AgUa51ymS-KemovJ7bm9J1fnL0nf8jk,134025
6
+ llms_py-2.0.8.data/data/ui/App.mjs,sha256=hXtUjaL3GrcIHieEK3BzIG72OVzrorBBS4RkE1DOGc4,439
7
+ llms_py-2.0.8.data/data/ui/ChatPrompt.mjs,sha256=j5oHTSmV93D34_tu6t2I0OcequQRI5KOmXfELqt2cV8,17250
8
+ llms_py-2.0.8.data/data/ui/Main.mjs,sha256=gQkSW40awblNYPT7E_8lDutiJmyACrcMm3nE4rAdwvU,37772
9
+ llms_py-2.0.8.data/data/ui/Recents.mjs,sha256=WdoIAvetlXbVYLsZOwLabf_DB2TI82FHzFxvijG_HAg,8406
10
+ llms_py-2.0.8.data/data/ui/Sidebar.mjs,sha256=TUyicSr8MRpLoUZoQ6neBG0PwhFcz2BhIQVwdaKSuo8,10870
11
+ llms_py-2.0.8.data/data/ui/app.css,sha256=4O2j3GG8V1s0gJcpOLt1erKpCJJ_PwVSMh_IEFbWULo,92442
12
+ llms_py-2.0.8.data/data/ui/fav.svg,sha256=_R6MFeXl6wBFT0lqcUxYQIDWgm246YH_3hSTW0oO8qw,734
13
+ llms_py-2.0.8.data/data/ui/markdown.mjs,sha256=YeOnS7bPM0oyXHMVsgwobLYKgqzRNjrmBJ8NIrhU9Z8,6096
14
+ llms_py-2.0.8.data/data/ui/tailwind.input.css,sha256=31O3JLwvf8qPoEmKAGMPXEOgVRNcP7i7Y0DH_xw6qsg,10612
15
+ llms_py-2.0.8.data/data/ui/threadStore.mjs,sha256=bL-yPx1ShU7lBJrzgMHI7K-ZtzcuWDSjtSf--nn0DVk,7199
16
+ llms_py-2.0.8.data/data/ui/typography.css,sha256=XtIOwBvWujwTOSOuF-zs5x3xEJLnmg27q2lmv4JyLm4,16127
17
+ llms_py-2.0.8.data/data/ui/utils.mjs,sha256=exyN-YohPvcGSR-IrjYUj0t3_RDIrnaJgSvkHA2bIRk,4016
18
+ llms_py-2.0.8.data/data/ui/lib/highlight.min.mjs,sha256=sG7wq8bF-IKkfie7S4QSyh5DdHBRf0NqQxMOEH8-MT0,127458
19
+ llms_py-2.0.8.data/data/ui/lib/idb.min.mjs,sha256=CeTXyV4I_pB5vnibvJuyXdMs0iVF2ZL0Z7cdm3w_QaI,3853
20
+ llms_py-2.0.8.data/data/ui/lib/marked.min.mjs,sha256=QRHb_VZugcBJRD2EP6gYlVFEsJw5C2fQ8ImMf_pA2_s,39488
21
+ llms_py-2.0.8.data/data/ui/lib/servicestack-client.min.mjs,sha256=UVafVbzhJ_0N2lzv7rlzIbzwnWpoqXxGk3N3FSKgOOc,54534
22
+ llms_py-2.0.8.data/data/ui/lib/servicestack-vue.min.mjs,sha256=oqeaDTgQxCi4gLt9X0J8UwIJgW9FWCV0d-Iiak6k-qI,218926
23
+ llms_py-2.0.8.data/data/ui/lib/vue-router.min.mjs,sha256=fR30GHoXI1u81zyZ26YEU105pZgbbAKSXbpnzFKIxls,30418
24
+ llms_py-2.0.8.data/data/ui/lib/vue.min.mjs,sha256=iXh97m5hotl0eFllb3aoasQTImvp7mQoRJ_0HoxmZkw,163811
25
+ llms_py-2.0.8.dist-info/licenses/LICENSE,sha256=rRryrddGfVftpde-rmAZpW0R8IJihqJ8t8wpfDXoKiQ,1549
26
+ llms_py-2.0.8.dist-info/METADATA,sha256=x40R6AAeFCsHLQbtpeFIj9j3mMwTdKc5N2VWdX6LhiE,23931
27
+ llms_py-2.0.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
+ llms_py-2.0.8.dist-info/entry_points.txt,sha256=svUoTt28bIJeo6Mb1ffDBLyzhD90DWtFbfp0ShyRCgU,35
29
+ llms_py-2.0.8.dist-info/top_level.txt,sha256=gC7hk9BKSeog8gyg-EM_g2gxm1mKHwFRfK-10BxOsa4,5
30
+ llms_py-2.0.8.dist-info/RECORD,,
@@ -1,30 +0,0 @@
1
- llms.py,sha256=0Mymf6WTi8tI0_HssCqyP0TzaDXeNg_KLjqlITQ1J5E,51990
2
- llms_py-2.0.6.data/data/index.html,sha256=nOgYc7t9QjlbRoy35-W6HpQbEEuD0-O1rkA8j-IYhZ4,2276
3
- llms_py-2.0.6.data/data/llms.json,sha256=nw9g63Ht15V5h2A9ynx8_1KLLboNrzUN5PTPhPV1lAY,18365
4
- llms_py-2.0.6.data/data/requirements.txt,sha256=iv-JIaacmTl-hSho3QmphcKnbRRYx1st47yjz_178Ro,8
5
- llms_py-2.0.6.data/data/ui.json,sha256=iBOmpNeD5-o8AgUa51ymS-KemovJ7bm9J1fnL0nf8jk,134025
6
- llms_py-2.0.6.data/data/ui/App.mjs,sha256=hXtUjaL3GrcIHieEK3BzIG72OVzrorBBS4RkE1DOGc4,439
7
- llms_py-2.0.6.data/data/ui/ChatPrompt.mjs,sha256=S_p2h6CJUDMvuetMgauJzS0MkACDw-bWw0FR-YXre_w,17163
8
- llms_py-2.0.6.data/data/ui/Main.mjs,sha256=n3LIrGk_97ZA0eTeE8wmp1dyuj12Z7GH-XOTU7ydPo4,35836
9
- llms_py-2.0.6.data/data/ui/Recents.mjs,sha256=cA9njbuOXeTWWagiArmswwgXuAV36ExhXGPE6sZrffY,7459
10
- llms_py-2.0.6.data/data/ui/Sidebar.mjs,sha256=TUyicSr8MRpLoUZoQ6neBG0PwhFcz2BhIQVwdaKSuo8,10870
11
- llms_py-2.0.6.data/data/ui/app.css,sha256=ircS9mQC4MRj7_BC2lGDZ6wCALkte_H_-z0Xw01jdZ4,92617
12
- llms_py-2.0.6.data/data/ui/fav.svg,sha256=_R6MFeXl6wBFT0lqcUxYQIDWgm246YH_3hSTW0oO8qw,734
13
- llms_py-2.0.6.data/data/ui/markdown.mjs,sha256=YeOnS7bPM0oyXHMVsgwobLYKgqzRNjrmBJ8NIrhU9Z8,6096
14
- llms_py-2.0.6.data/data/ui/tailwind.input.css,sha256=31O3JLwvf8qPoEmKAGMPXEOgVRNcP7i7Y0DH_xw6qsg,10612
15
- llms_py-2.0.6.data/data/ui/threadStore.mjs,sha256=bL-yPx1ShU7lBJrzgMHI7K-ZtzcuWDSjtSf--nn0DVk,7199
16
- llms_py-2.0.6.data/data/ui/typography.css,sha256=XtIOwBvWujwTOSOuF-zs5x3xEJLnmg27q2lmv4JyLm4,16127
17
- llms_py-2.0.6.data/data/ui/utils.mjs,sha256=exyN-YohPvcGSR-IrjYUj0t3_RDIrnaJgSvkHA2bIRk,4016
18
- llms_py-2.0.6.data/data/ui/lib/highlight.min.mjs,sha256=sG7wq8bF-IKkfie7S4QSyh5DdHBRf0NqQxMOEH8-MT0,127458
19
- llms_py-2.0.6.data/data/ui/lib/idb.min.mjs,sha256=CeTXyV4I_pB5vnibvJuyXdMs0iVF2ZL0Z7cdm3w_QaI,3853
20
- llms_py-2.0.6.data/data/ui/lib/marked.min.mjs,sha256=QRHb_VZugcBJRD2EP6gYlVFEsJw5C2fQ8ImMf_pA2_s,39488
21
- llms_py-2.0.6.data/data/ui/lib/servicestack-client.min.mjs,sha256=UVafVbzhJ_0N2lzv7rlzIbzwnWpoqXxGk3N3FSKgOOc,54534
22
- llms_py-2.0.6.data/data/ui/lib/servicestack-vue.min.mjs,sha256=oqeaDTgQxCi4gLt9X0J8UwIJgW9FWCV0d-Iiak6k-qI,218926
23
- llms_py-2.0.6.data/data/ui/lib/vue-router.min.mjs,sha256=fR30GHoXI1u81zyZ26YEU105pZgbbAKSXbpnzFKIxls,30418
24
- llms_py-2.0.6.data/data/ui/lib/vue.min.mjs,sha256=iXh97m5hotl0eFllb3aoasQTImvp7mQoRJ_0HoxmZkw,163811
25
- llms_py-2.0.6.dist-info/licenses/LICENSE,sha256=rRryrddGfVftpde-rmAZpW0R8IJihqJ8t8wpfDXoKiQ,1549
26
- llms_py-2.0.6.dist-info/METADATA,sha256=MQBuIPhMcfrf0ne9Vx2Z5Q0c9AIcSapPWxY_aL0rZpw,23652
27
- llms_py-2.0.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
- llms_py-2.0.6.dist-info/entry_points.txt,sha256=svUoTt28bIJeo6Mb1ffDBLyzhD90DWtFbfp0ShyRCgU,35
29
- llms_py-2.0.6.dist-info/top_level.txt,sha256=gC7hk9BKSeog8gyg-EM_g2gxm1mKHwFRfK-10BxOsa4,5
30
- llms_py-2.0.6.dist-info/RECORD,,
File without changes