llms-py 2.0.21__tar.gz → 2.0.23__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 (56) hide show
  1. {llms_py-2.0.21/llms_py.egg-info → llms_py-2.0.23}/PKG-INFO +1 -1
  2. {llms_py-2.0.21 → llms_py-2.0.23}/llms/main.py +1 -1
  3. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/Main.mjs +122 -52
  4. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/ai.mjs +1 -1
  5. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/threadStore.mjs +39 -0
  6. {llms_py-2.0.21 → llms_py-2.0.23/llms_py.egg-info}/PKG-INFO +1 -1
  7. {llms_py-2.0.21 → llms_py-2.0.23}/pyproject.toml +1 -1
  8. {llms_py-2.0.21 → llms_py-2.0.23}/setup.py +1 -1
  9. {llms_py-2.0.21 → llms_py-2.0.23}/LICENSE +0 -0
  10. {llms_py-2.0.21 → llms_py-2.0.23}/MANIFEST.in +0 -0
  11. {llms_py-2.0.21 → llms_py-2.0.23}/README.md +0 -0
  12. {llms_py-2.0.21 → llms_py-2.0.23}/llms/__init__.py +0 -0
  13. {llms_py-2.0.21 → llms_py-2.0.23}/llms/__main__.py +0 -0
  14. {llms_py-2.0.21 → llms_py-2.0.23}/llms/index.html +0 -0
  15. {llms_py-2.0.21 → llms_py-2.0.23}/llms/llms.json +0 -0
  16. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/Analytics.mjs +0 -0
  17. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/App.mjs +0 -0
  18. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/Avatar.mjs +0 -0
  19. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/Brand.mjs +0 -0
  20. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/ChatPrompt.mjs +0 -0
  21. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/ModelSelector.mjs +0 -0
  22. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/ProviderIcon.mjs +0 -0
  23. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/ProviderStatus.mjs +0 -0
  24. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/Recents.mjs +0 -0
  25. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/SettingsDialog.mjs +0 -0
  26. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/Sidebar.mjs +0 -0
  27. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/SignIn.mjs +0 -0
  28. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/SystemPromptEditor.mjs +0 -0
  29. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/SystemPromptSelector.mjs +0 -0
  30. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/Welcome.mjs +0 -0
  31. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/app.css +0 -0
  32. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/fav.svg +0 -0
  33. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/lib/chart.js +0 -0
  34. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/lib/charts.mjs +0 -0
  35. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/lib/color.js +0 -0
  36. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/lib/highlight.min.mjs +0 -0
  37. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/lib/idb.min.mjs +0 -0
  38. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/lib/marked.min.mjs +0 -0
  39. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/lib/servicestack-client.mjs +0 -0
  40. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/lib/servicestack-vue.mjs +0 -0
  41. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/lib/vue-router.min.mjs +0 -0
  42. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/lib/vue.min.mjs +0 -0
  43. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/lib/vue.mjs +0 -0
  44. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/markdown.mjs +0 -0
  45. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/tailwind.input.css +0 -0
  46. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/typography.css +0 -0
  47. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui/utils.mjs +0 -0
  48. {llms_py-2.0.21 → llms_py-2.0.23}/llms/ui.json +0 -0
  49. {llms_py-2.0.21 → llms_py-2.0.23}/llms_py.egg-info/SOURCES.txt +0 -0
  50. {llms_py-2.0.21 → llms_py-2.0.23}/llms_py.egg-info/dependency_links.txt +0 -0
  51. {llms_py-2.0.21 → llms_py-2.0.23}/llms_py.egg-info/entry_points.txt +0 -0
  52. {llms_py-2.0.21 → llms_py-2.0.23}/llms_py.egg-info/not-zip-safe +0 -0
  53. {llms_py-2.0.21 → llms_py-2.0.23}/llms_py.egg-info/requires.txt +0 -0
  54. {llms_py-2.0.21 → llms_py-2.0.23}/llms_py.egg-info/top_level.txt +0 -0
  55. {llms_py-2.0.21 → llms_py-2.0.23}/requirements.txt +0 -0
  56. {llms_py-2.0.21 → llms_py-2.0.23}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llms-py
3
- Version: 2.0.21
3
+ Version: 2.0.23
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
@@ -22,7 +22,7 @@ from aiohttp import web
22
22
  from pathlib import Path
23
23
  from importlib import resources # Py≥3.9 (pip install importlib_resources for 3.7/3.8)
24
24
 
25
- VERSION = "2.0.21"
25
+ VERSION = "2.0.23"
26
26
  _ROOT = None
27
27
  g_config_path = None
28
28
  g_ui_path = None
@@ -61,7 +61,7 @@ export default {
61
61
  <!-- Export/Import buttons -->
62
62
  <div class="mt-2 flex space-x-3 justify-center">
63
63
  <button type="button"
64
- @click="exportThreads"
64
+ @click="(e) => e.altKey ? exportRequests() : exportThreads()"
65
65
  :disabled="isExporting"
66
66
  :title="'Export ' + threads?.threads?.value?.length + ' conversations'"
67
67
  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"
@@ -411,7 +411,7 @@ export default {
411
411
  const exportData = {
412
412
  exportedAt: new Date().toISOString(),
413
413
  version: '1.0',
414
- source: 'ServiceStack.AI.Chat',
414
+ source: 'llmspy',
415
415
  threadCount: allThreads.length,
416
416
  threads: allThreads
417
417
  }
@@ -423,7 +423,7 @@ export default {
423
423
 
424
424
  const link = document.createElement('a')
425
425
  link.href = url
426
- link.download = `aichat-threads-export-${new Date().toISOString().split('T')[0]}.json`
426
+ link.download = `llmsthreads-export-${new Date().toISOString().split('T')[0]}.json`
427
427
  document.body.appendChild(link)
428
428
  link.click()
429
429
  document.body.removeChild(link)
@@ -437,6 +437,44 @@ export default {
437
437
  }
438
438
  }
439
439
 
440
+ async function exportRequests() {
441
+ if (isExporting.value) return
442
+
443
+ isExporting.value = true
444
+ try {
445
+ // Load all threads from IndexedDB
446
+ const allRequests = await threads.getAllRequests()
447
+
448
+ // Create export data with metadata
449
+ const exportData = {
450
+ exportedAt: new Date().toISOString(),
451
+ version: '1.0',
452
+ source: 'llmspy',
453
+ requestsCount: allRequests.length,
454
+ requests: allRequests
455
+ }
456
+
457
+ // Create and download JSON file
458
+ const jsonString = JSON.stringify(exportData, null, 2)
459
+ const blob = new Blob([jsonString], { type: 'application/json' })
460
+ const url = URL.createObjectURL(blob)
461
+
462
+ const link = document.createElement('a')
463
+ link.href = url
464
+ link.download = `llmsrequests-export-${new Date().toISOString().split('T')[0]}.json`
465
+ document.body.appendChild(link)
466
+ link.click()
467
+ document.body.removeChild(link)
468
+ URL.revokeObjectURL(url)
469
+
470
+ } catch (error) {
471
+ console.error('Failed to export requests:', error)
472
+ alert('Failed to export requests: ' + error.message)
473
+ } finally {
474
+ isExporting.value = false
475
+ }
476
+ }
477
+
440
478
  function triggerImport() {
441
479
  if (isImporting.value) return
442
480
  fileInput.value?.click()
@@ -447,71 +485,102 @@ export default {
447
485
  if (!file) return
448
486
 
449
487
  isImporting.value = true
488
+ var importType = 'threads'
450
489
  try {
451
490
  const text = await file.text()
452
491
  const importData = JSON.parse(text)
453
-
454
- // Validate import data structure
455
- if (!importData.threads || !Array.isArray(importData.threads)) {
456
- throw new Error('Invalid import file: missing or invalid threads array')
457
- }
492
+ importType = importData.threads
493
+ ? 'threads'
494
+ : importData.requests
495
+ ? 'requests'
496
+ : 'unknown'
458
497
 
459
498
  // Import threads one by one
460
499
  let importedCount = 0
461
- let updatedCount = 0
500
+ let existingCount = 0
462
501
 
463
- for (const threadData of importData.threads) {
464
- if (!threadData.id) {
465
- console.warn('Skipping thread without ID:', threadData)
466
- continue
502
+ const db = await threads.initDB()
503
+
504
+ if (importData.threads) {
505
+ if (!Array.isArray(importData.threads)) {
506
+ throw new Error('Invalid import file: missing or invalid threads array')
467
507
  }
468
508
 
469
- try {
470
- // Check if thread already exists
471
- const existingThread = await threads.getThread(threadData.id)
472
-
473
- if (existingThread) {
474
- // Update existing thread
475
- await threads.updateThread(threadData.id, {
476
- title: threadData.title,
477
- model: threadData.model,
478
- systemPrompt: threadData.systemPrompt,
479
- messages: threadData.messages || [],
480
- createdAt: threadData.createdAt,
481
- // Keep the existing updatedAt or use imported one
482
- updatedAt: threadData.updatedAt || existingThread.updatedAt
483
- })
484
- updatedCount++
485
- } else {
486
- // Add new thread directly to IndexedDB
487
- //await threads.initDB()
488
- const db = await threads.initDB()
489
- const tx = db.transaction(['threads'], 'readwrite')
490
- await tx.objectStore('threads').add({
491
- id: threadData.id,
492
- title: threadData.title || 'Imported Chat',
493
- model: threadData.model || '',
494
- systemPrompt: threadData.systemPrompt || '',
495
- messages: threadData.messages || [],
496
- createdAt: threadData.createdAt || new Date().toISOString(),
497
- updatedAt: threadData.updatedAt || new Date().toISOString()
498
- })
499
- await tx.complete
500
- importedCount++
509
+ const threadIds = new Set(await threads.getAllThreadIds())
510
+
511
+ for (const threadData of importData.threads) {
512
+ if (!threadData.id) {
513
+ console.warn('Skipping thread without ID:', threadData)
514
+ continue
515
+ }
516
+
517
+ try {
518
+ // Check if thread already exists
519
+ const existingThread = threadIds.has(threadData.id)
520
+ if (existingThread) {
521
+ existingCount++
522
+ } else {
523
+ // Add new thread directly to IndexedDB
524
+ const tx = db.transaction(['threads'], 'readwrite')
525
+ await tx.objectStore('threads').add({
526
+ id: threadData.id,
527
+ title: threadData.title || 'Imported Chat',
528
+ model: threadData.model || '',
529
+ systemPrompt: threadData.systemPrompt || '',
530
+ messages: threadData.messages || [],
531
+ createdAt: threadData.createdAt || new Date().toISOString(),
532
+ updatedAt: threadData.updatedAt || new Date().toISOString()
533
+ })
534
+ await tx.complete
535
+ importedCount++
536
+ }
537
+ } catch (error) {
538
+ console.error('Failed to import thread:', threadData.id, error)
501
539
  }
502
- } catch (error) {
503
- console.error('Failed to import thread:', threadData.id, error)
504
540
  }
541
+
542
+ // Reload threads to reflect changes
543
+ await threads.loadThreads()
544
+
545
+ alert(`Import completed!\nNew threads: ${importedCount}\nExisting threads: ${existingCount}`)
505
546
  }
547
+ if (importData.requests) {
548
+ if (!Array.isArray(importData.requests)) {
549
+ throw new Error('Invalid import file: missing or invalid requests array')
550
+ }
506
551
 
507
- // Reload threads to reflect changes
508
- await threads.loadThreads()
552
+ const requestIds = new Set(await threads.getAllRequestIds())
509
553
 
510
- alert(`Import completed!\nNew threads: ${importedCount}\nUpdated threads: ${updatedCount}`)
554
+ for (const requestData of importData.requests) {
555
+ if (!requestData.id) {
556
+ console.warn('Skipping request without ID:', requestData)
557
+ continue
558
+ }
559
+
560
+ try {
561
+ // Check if request already exists
562
+ const existingRequest = requestIds.has(requestData.id)
563
+ if (existingRequest) {
564
+ existingCount++
565
+ } else {
566
+ // Add new request directly to IndexedDB
567
+ const db = await threads.initDB()
568
+ const tx = db.transaction(['requests'], 'readwrite')
569
+ await tx.objectStore('requests').add(requestData)
570
+ await tx.complete
571
+ importedCount++
572
+ }
573
+ } catch (error) {
574
+ console.error('Failed to import request:', requestData.id, error)
575
+ }
576
+ }
577
+
578
+ alert(`Import completed!\nNew requests: ${importedCount}\nExisting requests: ${existingCount}`)
579
+ }
511
580
 
512
581
  } catch (error) {
513
- console.error('Failed to import threads:', error)
514
- alert('Failed to import threads: ' + error.message)
582
+ console.error('Failed to import ' + importType + ':', error)
583
+ alert('Failed to import ' + importType + ': ' + error.message)
515
584
  } finally {
516
585
  isImporting.value = false
517
586
  // Clear the file input
@@ -725,6 +794,7 @@ export default {
725
794
  cancelEdit,
726
795
  configUpdated,
727
796
  exportThreads,
797
+ exportRequests,
728
798
  isExporting,
729
799
  triggerImport,
730
800
  handleFileImport,
@@ -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.21',
9
+ version: '2.0.23',
10
10
  base,
11
11
  prefsKey,
12
12
  welcome: 'Welcome to llms.py',
@@ -396,6 +396,41 @@ function getGroupedThreads(total) {
396
396
  // Group threads by time periods
397
397
  const groupedThreads = computed(() => getGroupedThreads(threads.value.length))
398
398
 
399
+ async function getAllRequests() {
400
+ await initDB()
401
+
402
+ const tx = db.transaction(['requests'], 'readonly')
403
+ const store = tx.objectStore('requests')
404
+ const allRequests = await store.getAll()
405
+ return allRequests
406
+ }
407
+
408
+ async function getRequest(requestId) {
409
+ await initDB()
410
+
411
+ const tx = db.transaction(['requests'], 'readonly')
412
+ const store = tx.objectStore('requests')
413
+ const request = await store.get(requestId)
414
+ return request
415
+ }
416
+
417
+ async function getAllRequestIds() {
418
+ await initDB()
419
+
420
+ const tx = db.transaction(['requests'], 'readonly')
421
+ const store = tx.objectStore('requests')
422
+ const ids = await store.getAllKeys()
423
+ return ids
424
+ }
425
+
426
+ async function getAllThreadIds() {
427
+ await initDB()
428
+ const tx = db.transaction(['threads'], 'readonly')
429
+ const store = tx.objectStore('threads')
430
+ const ids = await store.getAllKeys()
431
+ return ids
432
+ }
433
+
399
434
  // Query requests with pagination and filtering
400
435
  async function getRequests(filters = {}, limit = 20, offset = 0) {
401
436
  try {
@@ -517,8 +552,12 @@ export function useThreadStore() {
517
552
  setCurrentThreadFromRoute,
518
553
  clearCurrentThread,
519
554
  getGroupedThreads,
555
+ getRequest,
520
556
  getRequests,
557
+ getAllRequests,
521
558
  getFilterOptions,
522
559
  deleteRequest,
560
+ getAllRequestIds,
561
+ getAllThreadIds,
523
562
  }
524
563
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llms-py
3
- Version: 2.0.21
3
+ Version: 2.0.23
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "llms-py"
7
- version = "2.0.21"
7
+ version = "2.0.23"
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.21",
19
+ version="2.0.23",
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",
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
File without changes
File without changes
File without changes
File without changes
File without changes