llms-py 3.0.0b5__py3-none-any.whl → 3.0.0b7__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 (55) hide show
  1. llms/__pycache__/main.cpython-314.pyc +0 -0
  2. llms/{ui/modules/analytics.mjs → extensions/analytics/ui/index.mjs} +4 -2
  3. llms/extensions/core_tools/__init__.py +358 -0
  4. llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
  5. llms/extensions/gallery/__init__.py +61 -0
  6. llms/extensions/gallery/__pycache__/__init__.cpython-314.pyc +0 -0
  7. llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
  8. llms/extensions/gallery/db.py +298 -0
  9. llms/extensions/gallery/ui/index.mjs +480 -0
  10. llms/extensions/providers/__init__.py +18 -0
  11. llms/extensions/providers/__pycache__/__init__.cpython-314.pyc +0 -0
  12. llms/{providers → extensions/providers}/__pycache__/anthropic.cpython-314.pyc +0 -0
  13. llms/extensions/providers/__pycache__/chutes.cpython-314.pyc +0 -0
  14. llms/extensions/providers/__pycache__/google.cpython-314.pyc +0 -0
  15. llms/{providers → extensions/providers}/__pycache__/nvidia.cpython-314.pyc +0 -0
  16. llms/{providers → extensions/providers}/__pycache__/openai.cpython-314.pyc +0 -0
  17. llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
  18. llms/{providers → extensions/providers}/anthropic.py +1 -4
  19. llms/{providers → extensions/providers}/chutes.py +21 -18
  20. llms/{providers → extensions/providers}/google.py +99 -27
  21. llms/{providers → extensions/providers}/nvidia.py +6 -8
  22. llms/{providers → extensions/providers}/openai.py +3 -6
  23. llms/{providers → extensions/providers}/openrouter.py +12 -10
  24. llms/extensions/system_prompts/__init__.py +45 -0
  25. llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc +0 -0
  26. llms/extensions/system_prompts/ui/index.mjs +284 -0
  27. llms/extensions/system_prompts/ui/prompts.json +1067 -0
  28. llms/{ui/modules/tools.mjs → extensions/tools/ui/index.mjs} +4 -2
  29. llms/llms.json +17 -1
  30. llms/main.py +407 -175
  31. llms/providers-extra.json +0 -32
  32. llms/ui/App.mjs +17 -18
  33. llms/ui/ai.mjs +10 -3
  34. llms/ui/app.css +1553 -24
  35. llms/ui/ctx.mjs +70 -12
  36. llms/ui/index.mjs +13 -8
  37. llms/ui/modules/chat/ChatBody.mjs +11 -248
  38. llms/ui/modules/chat/HomeTools.mjs +254 -0
  39. llms/ui/modules/chat/SettingsDialog.mjs +1 -1
  40. llms/ui/modules/chat/index.mjs +278 -174
  41. llms/ui/modules/layout.mjs +2 -26
  42. llms/ui/modules/model-selector.mjs +1 -1
  43. llms/ui/modules/threads/index.mjs +5 -11
  44. llms/ui/modules/threads/threadStore.mjs +56 -2
  45. llms/ui/utils.mjs +21 -3
  46. {llms_py-3.0.0b5.dist-info → llms_py-3.0.0b7.dist-info}/METADATA +1 -1
  47. llms_py-3.0.0b7.dist-info/RECORD +80 -0
  48. llms/providers/__pycache__/chutes.cpython-314.pyc +0 -0
  49. llms/providers/__pycache__/google.cpython-314.pyc +0 -0
  50. llms/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
  51. llms_py-3.0.0b5.dist-info/RECORD +0 -66
  52. {llms_py-3.0.0b5.dist-info → llms_py-3.0.0b7.dist-info}/WHEEL +0 -0
  53. {llms_py-3.0.0b5.dist-info → llms_py-3.0.0b7.dist-info}/entry_points.txt +0 -0
  54. {llms_py-3.0.0b5.dist-info → llms_py-3.0.0b7.dist-info}/licenses/LICENSE +0 -0
  55. {llms_py-3.0.0b5.dist-info → llms_py-3.0.0b7.dist-info}/top_level.txt +0 -0
llms/ui/ctx.mjs CHANGED
@@ -19,6 +19,9 @@ export class ExtensionScope {
19
19
  setPrefs(o) {
20
20
  storageObject(this.storageKey, Object.assign(this.prefs, o))
21
21
  }
22
+ savePrefs() {
23
+ storageObject(this.storageKey, this.prefs)
24
+ }
22
25
  get(url, options) {
23
26
  return this.ctx.ai.get(combinePaths(this.baseUrl, url), options)
24
27
  }
@@ -28,6 +31,9 @@ export class ExtensionScope {
28
31
  post(url, options) {
29
32
  return this.ctx.ai.post(combinePaths(this.baseUrl, url), options)
30
33
  }
34
+ async postForm(url, options) {
35
+ return await this.ctx.ai.postForm(combinePaths(this.baseUrl, url), options)
36
+ }
31
37
  async postJson(url, options) {
32
38
  return this.ctx.ai.postJson(combinePaths(this.baseUrl, url), options)
33
39
  }
@@ -55,6 +61,8 @@ export class AppContext {
55
61
  this.left = {}
56
62
  this.layout = reactive(storageObject(`llms.layout`))
57
63
  this.prefs = reactive(storageObject(ai.prefsKey))
64
+ this._onRouterBeforeEach = []
65
+ this._onClass = []
58
66
 
59
67
  if (!Array.isArray(this.layout.hide)) {
60
68
  this.layout.hide = []
@@ -182,24 +190,74 @@ export class AppContext {
182
190
  layoutVisible(key) {
183
191
  return !this.layout.hide.includes(key)
184
192
  }
185
- toggleTop(name) {
186
- console.log('toggleTop', name)
187
- this.layout.top = this.layout.top == name ? undefined : name
193
+ toggleTop(name, toggle) {
194
+ if (toggle === false) {
195
+ this.layout.top = undefined
196
+ } else if (toggle === true) {
197
+ this.layout.top = name
198
+ } else {
199
+ this.layout.top = this.layout.top == name ? undefined : name
200
+ }
188
201
  storageObject(`llms.layout`, this.layout)
202
+ console.log('toggleTop', name, toggle, this.layout.top, this.layout.top === name)
203
+ return this.layout.top === name
189
204
  }
190
- togglePath(path) {
205
+ togglePath(path, toggle) {
191
206
  const currentPath = this.router.currentRoute.value?.path
192
- console.log('togglePath', path, currentPath)
193
- if (currentPath == path) {
194
- this.toggleLayout('left')
195
- } else {
207
+ console.log('togglePath', path, currentPath, toggle)
208
+ if (currentPath != path) {
209
+ if (toggle === undefined) {
210
+ toggle = true
211
+ }
196
212
  this.router.push({ path })
197
213
  }
214
+ this.toggleLayout('left', toggle)
215
+ return toggle
198
216
  }
199
- getJson(url, options) {
200
- return this.ai.getJson(url, options)
217
+ async getJson(url, options) {
218
+ return await this.ai.getJson(url, options)
219
+ }
220
+ async post(url, options) {
221
+ return await this.ai.post(url, options)
222
+ }
223
+ async postForm(url, options) {
224
+ return await this.ai.postForm(url, options)
225
+ }
226
+ async postJson(url, options) {
227
+ return await this.ai.postJson(url, options)
201
228
  }
202
- postJson(url, options) {
203
- return this.ai.postJson(url, options)
229
+ to(route) {
230
+ if (typeof route == 'string') {
231
+ route = route.startsWith(this.ai.base)
232
+ ? route
233
+ : combinePaths(this.ai.base, route)
234
+ const path = { path: route }
235
+ console.log('to', path)
236
+ this.router.push(path)
237
+ } else {
238
+ route.path = route.path.startsWith(this.ai.base)
239
+ ? route.path
240
+ : combinePaths(this.ai.base, route.path)
241
+ console.log('to', route)
242
+ this.router.push(route)
243
+ }
244
+ }
245
+
246
+ // Events
247
+ onRouterBeforeEach(callback) {
248
+ this._onRouterBeforeEach.push(callback)
249
+ }
250
+
251
+ onClass(callback) {
252
+ this._onClass.push(callback)
253
+ }
254
+
255
+ cls(id, cls) {
256
+ if (this._onClass.length) {
257
+ this._onClass.forEach(callback => {
258
+ cls = callback(id, cls) ?? cls
259
+ })
260
+ }
261
+ return cls
204
262
  }
205
263
  }
llms/ui/index.mjs CHANGED
@@ -8,8 +8,6 @@ import LayoutModule from './modules/layout.mjs'
8
8
  import ChatModule from './modules/chat/index.mjs'
9
9
  import ThreadsModule from './modules/threads/index.mjs'
10
10
  import ModelSelectorModule from './modules/model-selector.mjs'
11
- import AnalyticsModule from './modules/analytics.mjs'
12
- import ToolsModule from './modules/tools.mjs'
13
11
  import { utilsFunctions, utilsFormatters } from './utils.mjs'
14
12
  import { markdownFormatters } from './markdown.mjs'
15
13
  import { AppContext } from './ctx.mjs'
@@ -22,8 +20,6 @@ const BuiltInModules = {
22
20
  ChatModule,
23
21
  ThreadsModule,
24
22
  ModelSelectorModule,
25
- AnalyticsModule,
26
- ToolsModule,
27
23
  }
28
24
 
29
25
 
@@ -48,13 +44,17 @@ export async function createContext() {
48
44
  ctx.modules = await Promise.all(validExtensions.map(async extension => {
49
45
  try {
50
46
  const module = await import(extension.path)
51
- return { extension, module }
47
+ const order = module.default.order || 0
48
+ return { extension, module, order }
52
49
  } catch (e) {
53
50
  console.error(`Failed to load extension module ${extension.name}:`, e)
54
51
  return null
55
52
  }
56
53
  }))
57
54
 
55
+ // sort modules by order
56
+ ctx.modules.sort((a, b) => a.order - b.order)
57
+
58
58
  const installedModules = []
59
59
 
60
60
  // Install built-in modules sequentially
@@ -103,10 +103,15 @@ export async function createContext() {
103
103
  document.title = title
104
104
  return true
105
105
  })
106
+ ctx._onRouterBeforeEach.forEach(ctx.router.beforeEach)
106
107
 
107
- if (ctx.layout.path && location.pathname === '/' && !location.search) {
108
- console.log('redirecting to saved path: ', ctx.layout.path)
109
- ctx.router.push({ path: ctx.layout.path })
108
+ if (ai.hasAccess) {
109
+ if (ctx.layout.path && location.pathname === '/' && !location.search) {
110
+ console.log('redirecting to saved path: ', ctx.layout.path)
111
+ ctx.router.push({ path: ctx.layout.path })
112
+ }
113
+ } else {
114
+ ctx.router.push({ path: '/' })
110
115
  }
111
116
 
112
117
  const loadModules = installedModules.filter(x => x.module.default && x.module.default.load)
@@ -14,58 +14,7 @@ export default {
14
14
  <!-- Welcome message when no thread is selected -->
15
15
  <div v-else-if="!currentThread" class="text-center py-12">
16
16
  <Welcome />
17
-
18
- <!-- Chat input for new conversation -->
19
- <!-- Moved to bottom input area -->
20
- <div class="h-2"></div>
21
-
22
- <!-- Export/Import buttons -->
23
- <div class="mt-2 flex space-x-3 justify-center items-center">
24
- <button type="button"
25
- @click="(e) => e.altKey ? exportRequests() : exportThreads()"
26
- :disabled="isExporting"
27
- :title="'Export ' + threads?.threads?.value?.length + ' conversations'"
28
- class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
29
- >
30
- <svg v-if="!isExporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
31
- <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>
32
- </svg>
33
- <svg v-else class="size-5 mr-1 animate-spin" fill="none" viewBox="0 0 24 24">
34
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
35
- <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>
36
- </svg>
37
- {{ isExporting ? 'Exporting...' : 'Export' }}
38
- </button>
39
-
40
- <button type="button"
41
- @click="triggerImport"
42
- :disabled="isImporting"
43
- title="Import conversations from JSON file"
44
- class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
45
- >
46
- <svg v-if="!isImporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
47
- <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"/>
48
- </svg>
49
- <svg v-else class="size-5 mr-1 animate-spin" fill="none" viewBox="0 0 24 24">
50
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
51
- <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>
52
- </svg>
53
- {{ isImporting ? 'Importing...' : 'Import' }}
54
- </button>
55
-
56
- <!-- Hidden file input for import -->
57
- <input
58
- ref="fileInput"
59
- type="file"
60
- accept=".json"
61
- @change="handleFileImport"
62
- class="hidden"
63
- />
64
-
65
- <DarkModeToggle />
66
-
67
- </div>
68
-
17
+ <HomeTools />
69
18
  </div>
70
19
 
71
20
  <!-- Messages -->
@@ -228,6 +177,15 @@ export default {
228
177
  </template>
229
178
  </div>
230
179
 
180
+ <!-- Assistant Audios -->
181
+ <div v-if="message.audios && message.audios.length > 0" class="mt-2 flex flex-wrap gap-2">
182
+ <template v-for="(audio, i) in message.audios" :key="i">
183
+ <div v-if="audio.type === 'audio_url'" class="flex items-center gap-2 p-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
184
+ <audio controls :src="resolveUrl(audio.audio_url.url)" class="h-8 w-64"></audio>
185
+ </div>
186
+ </template>
187
+ </div>
188
+
231
189
  <!-- User Message with separate attachments -->
232
190
  <div v-else-if="message.role !== 'assistant' && message.role !== 'tool'">
233
191
  <div v-html="$fmt.markdown(message.content)" class="prose prose-sm max-w-none dark:prose-invert break-words"></div>
@@ -355,7 +313,7 @@ export default {
355
313
  </div>
356
314
 
357
315
  <!-- Input Area -->
358
- <div v-if="$ai.hasAccess" class="flex-shrink-0 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-6 py-4">
316
+ <div v-if="$ai.hasAccess" :class="$ctx.cls('chat-input', 'flex-shrink-0 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-6 py-4')">
359
317
  <ChatPrompt :model="$chat.getSelectedModel()" />
360
318
  </div>
361
319
 
@@ -395,9 +353,6 @@ export default {
395
353
  return models.find(m => m.name === selectedModel.value) || models.find(m => m.id === selectedModel.value)
396
354
  })
397
355
  const messagesContainer = ref(null)
398
- const isExporting = ref(false)
399
- const isImporting = ref(false)
400
- const fileInput = ref(null)
401
356
  const copying = ref(null)
402
357
  const lightboxUrl = ref(null)
403
358
 
@@ -446,191 +401,6 @@ export default {
446
401
  model: selectedModel.value,
447
402
  })
448
403
  })
449
-
450
- async function exportThreads() {
451
- if (isExporting.value) return
452
-
453
- isExporting.value = true
454
- try {
455
- // Load all threads from IndexedDB
456
- await threads.loadThreads()
457
- const allThreads = threads.threads.value
458
-
459
- // Create export data with metadata
460
- const exportData = {
461
- exportedAt: new Date().toISOString(),
462
- version: '1.0',
463
- source: 'llmspy',
464
- threadCount: allThreads.length,
465
- threads: allThreads
466
- }
467
-
468
- // Create and download JSON file
469
- const jsonString = JSON.stringify(exportData, null, 2)
470
- const blob = new Blob([jsonString], { type: 'application/json' })
471
- const url = URL.createObjectURL(blob)
472
-
473
- const link = document.createElement('a')
474
- link.href = url
475
- link.download = `llmsthreads-export-${new Date().toISOString().split('T')[0]}.json`
476
- document.body.appendChild(link)
477
- link.click()
478
- document.body.removeChild(link)
479
- URL.revokeObjectURL(url)
480
-
481
- } catch (error) {
482
- console.error('Failed to export threads:', error)
483
- alert('Failed to export threads: ' + error.message)
484
- } finally {
485
- isExporting.value = false
486
- }
487
- }
488
-
489
- async function exportRequests() {
490
- if (isExporting.value) return
491
-
492
- isExporting.value = true
493
- try {
494
- // Load all threads from IndexedDB
495
- const allRequests = await threads.getAllRequests()
496
-
497
- // Create export data with metadata
498
- const exportData = {
499
- exportedAt: new Date().toISOString(),
500
- version: '1.0',
501
- source: 'llmspy',
502
- requestsCount: allRequests.length,
503
- requests: allRequests
504
- }
505
-
506
- // Create and download JSON file
507
- const jsonString = JSON.stringify(exportData, null, 2)
508
- const blob = new Blob([jsonString], { type: 'application/json' })
509
- const url = URL.createObjectURL(blob)
510
-
511
- const link = document.createElement('a')
512
- link.href = url
513
- link.download = `llmsrequests-export-${new Date().toISOString().split('T')[0]}.json`
514
- document.body.appendChild(link)
515
- link.click()
516
- document.body.removeChild(link)
517
- URL.revokeObjectURL(url)
518
-
519
- } catch (error) {
520
- console.error('Failed to export requests:', error)
521
- alert('Failed to export requests: ' + error.message)
522
- } finally {
523
- isExporting.value = false
524
- }
525
- }
526
-
527
- function triggerImport() {
528
- if (isImporting.value) return
529
- fileInput.value?.click()
530
- }
531
-
532
- async function handleFileImport(event) {
533
- const file = event.target.files?.[0]
534
- if (!file) return
535
-
536
- isImporting.value = true
537
- var importType = 'threads'
538
- try {
539
- const text = await file.text()
540
- const importData = JSON.parse(text)
541
- importType = importData.threads
542
- ? 'threads'
543
- : importData.requests
544
- ? 'requests'
545
- : 'unknown'
546
-
547
- // Import threads one by one
548
- let importedCount = 0
549
- let existingCount = 0
550
-
551
- const db = await threads.initDB()
552
-
553
- if (importData.threads) {
554
- if (!Array.isArray(importData.threads)) {
555
- throw new Error('Invalid import file: missing or invalid threads array')
556
- }
557
-
558
- const threadIds = new Set(await threads.getAllThreadIds())
559
-
560
- for (const threadData of importData.threads) {
561
- if (!threadData.id) {
562
- console.warn('Skipping thread without ID:', threadData)
563
- continue
564
- }
565
-
566
- try {
567
- // Check if thread already exists
568
- const existingThread = threadIds.has(threadData.id)
569
- if (existingThread) {
570
- existingCount++
571
- } else {
572
- // Add new thread directly to IndexedDB
573
- const tx = db.transaction(['threads'], 'readwrite')
574
- await tx.objectStore('threads').add(threadData)
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}\nExisting threads: ${existingCount}`)
587
- }
588
- if (importData.requests) {
589
- if (!Array.isArray(importData.requests)) {
590
- throw new Error('Invalid import file: missing or invalid requests array')
591
- }
592
-
593
- const requestIds = new Set(await threads.getAllRequestIds())
594
-
595
- for (const requestData of importData.requests) {
596
- if (!requestData.id) {
597
- console.warn('Skipping request without ID:', requestData)
598
- continue
599
- }
600
-
601
- try {
602
- // Check if request already exists
603
- const existingRequest = requestIds.has(requestData.id)
604
- if (existingRequest) {
605
- existingCount++
606
- } else {
607
- // Add new request directly to IndexedDB
608
- const db = await threads.initDB()
609
- const tx = db.transaction(['requests'], 'readwrite')
610
- await tx.objectStore('requests').add(requestData)
611
- await tx.complete
612
- importedCount++
613
- }
614
- } catch (error) {
615
- console.error('Failed to import request:', requestData.id, error)
616
- }
617
- }
618
-
619
- alert(`Import completed!\nNew requests: ${importedCount}\nExisting requests: ${existingCount}`)
620
- }
621
-
622
- } catch (error) {
623
- console.error('Failed to import ' + importType + ':', error)
624
- alert('Failed to import ' + importType + ': ' + error.message)
625
- } finally {
626
- isImporting.value = false
627
- // Clear the file input
628
- if (fileInput.value) {
629
- fileInput.value.value = ''
630
- }
631
- }
632
- }
633
-
634
404
  function configUpdated() {
635
405
  console.log('configUpdated', selectedModel.value, models.length, models.includes(selectedModel.value))
636
406
  if (selectedModel.value && !models.includes(selectedModel.value)) {
@@ -884,13 +654,6 @@ export default {
884
654
  editMessage,
885
655
  cancelRequest,
886
656
  configUpdated,
887
- exportThreads,
888
- exportRequests,
889
- isExporting,
890
- triggerImport,
891
- handleFileImport,
892
- isImporting,
893
- fileInput,
894
657
  tokensTitle,
895
658
  getAttachments,
896
659
  hasAttachments,