llms-py 2.0.20__py3-none-any.whl → 3.0.10__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 (190) hide show
  1. llms/__init__.py +3 -1
  2. llms/db.py +359 -0
  3. llms/{ui/Analytics.mjs → extensions/analytics/ui/index.mjs} +254 -327
  4. llms/extensions/app/README.md +20 -0
  5. llms/extensions/app/__init__.py +589 -0
  6. llms/extensions/app/db.py +536 -0
  7. llms/{ui → extensions/app/ui}/Recents.mjs +99 -73
  8. llms/{ui/Sidebar.mjs → extensions/app/ui/index.mjs} +139 -68
  9. llms/extensions/app/ui/threadStore.mjs +433 -0
  10. llms/extensions/core_tools/CALCULATOR.md +32 -0
  11. llms/extensions/core_tools/__init__.py +637 -0
  12. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
  13. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
  14. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
  15. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
  16. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
  17. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
  18. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
  19. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
  20. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
  21. llms/extensions/core_tools/ui/codemirror/codemirror.css +344 -0
  22. llms/extensions/core_tools/ui/codemirror/codemirror.js +9884 -0
  23. llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
  24. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  25. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
  26. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
  27. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
  28. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
  29. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
  30. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
  31. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
  32. llms/extensions/core_tools/ui/index.mjs +650 -0
  33. llms/extensions/gallery/README.md +61 -0
  34. llms/extensions/gallery/__init__.py +63 -0
  35. llms/extensions/gallery/db.py +243 -0
  36. llms/extensions/gallery/ui/index.mjs +482 -0
  37. llms/extensions/katex/README.md +39 -0
  38. llms/extensions/katex/__init__.py +6 -0
  39. llms/extensions/katex/ui/README.md +125 -0
  40. llms/extensions/katex/ui/contrib/auto-render.js +338 -0
  41. llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
  42. llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
  43. llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
  44. llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
  45. llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
  46. llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
  47. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
  48. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
  49. llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
  50. llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
  51. llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
  52. llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
  53. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
  54. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
  55. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  56. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  57. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  58. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  59. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  60. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  61. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  62. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  63. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  64. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  65. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  66. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  67. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  68. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  69. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  70. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  71. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  72. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  73. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  74. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  75. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  76. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  77. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  78. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  79. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  80. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  81. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  82. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  83. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  84. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  85. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  86. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  87. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  88. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  89. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  90. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  91. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  92. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  93. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  94. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  95. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  96. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  97. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  98. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  99. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  100. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  101. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  102. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  103. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  104. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  115. llms/extensions/katex/ui/index.mjs +92 -0
  116. llms/extensions/katex/ui/katex-swap.css +1230 -0
  117. llms/extensions/katex/ui/katex-swap.min.css +1 -0
  118. llms/extensions/katex/ui/katex.css +1230 -0
  119. llms/extensions/katex/ui/katex.js +19080 -0
  120. llms/extensions/katex/ui/katex.min.css +1 -0
  121. llms/extensions/katex/ui/katex.min.js +1 -0
  122. llms/extensions/katex/ui/katex.min.mjs +1 -0
  123. llms/extensions/katex/ui/katex.mjs +18547 -0
  124. llms/extensions/providers/__init__.py +22 -0
  125. llms/extensions/providers/anthropic.py +233 -0
  126. llms/extensions/providers/cerebras.py +37 -0
  127. llms/extensions/providers/chutes.py +153 -0
  128. llms/extensions/providers/google.py +481 -0
  129. llms/extensions/providers/nvidia.py +103 -0
  130. llms/extensions/providers/openai.py +154 -0
  131. llms/extensions/providers/openrouter.py +74 -0
  132. llms/extensions/providers/zai.py +182 -0
  133. llms/extensions/system_prompts/README.md +22 -0
  134. llms/extensions/system_prompts/__init__.py +45 -0
  135. llms/extensions/system_prompts/ui/index.mjs +280 -0
  136. llms/extensions/system_prompts/ui/prompts.json +1067 -0
  137. llms/extensions/tools/__init__.py +144 -0
  138. llms/extensions/tools/ui/index.mjs +706 -0
  139. llms/index.html +36 -62
  140. llms/llms.json +180 -879
  141. llms/main.py +3640 -899
  142. llms/providers-extra.json +394 -0
  143. llms/providers.json +1 -0
  144. llms/ui/App.mjs +176 -8
  145. llms/ui/ai.mjs +156 -20
  146. llms/ui/app.css +3161 -244
  147. llms/ui/ctx.mjs +412 -0
  148. llms/ui/index.mjs +131 -0
  149. llms/ui/lib/chart.js +14 -0
  150. llms/ui/lib/charts.mjs +16 -0
  151. llms/ui/lib/color.js +14 -0
  152. llms/ui/lib/highlight.min.mjs +1243 -0
  153. llms/ui/lib/idb.min.mjs +8 -0
  154. llms/ui/lib/marked.min.mjs +8 -0
  155. llms/ui/lib/servicestack-client.mjs +1 -0
  156. llms/ui/lib/servicestack-vue.mjs +37 -0
  157. llms/ui/lib/vue-router.min.mjs +6 -0
  158. llms/ui/lib/vue.min.mjs +13 -0
  159. llms/ui/lib/vue.mjs +18530 -0
  160. llms/ui/markdown.mjs +25 -14
  161. llms/ui/modules/chat/ChatBody.mjs +976 -0
  162. llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +74 -74
  163. llms/ui/modules/chat/index.mjs +991 -0
  164. llms/ui/modules/icons.mjs +46 -0
  165. llms/ui/modules/layout.mjs +271 -0
  166. llms/ui/modules/model-selector.mjs +811 -0
  167. llms/ui/tailwind.input.css +550 -78
  168. llms/ui/typography.css +54 -36
  169. llms/ui/utils.mjs +197 -92
  170. llms_py-3.0.10.dist-info/METADATA +49 -0
  171. llms_py-3.0.10.dist-info/RECORD +177 -0
  172. {llms_py-2.0.20.dist-info → llms_py-3.0.10.dist-info}/licenses/LICENSE +1 -2
  173. llms/ui/Avatar.mjs +0 -28
  174. llms/ui/Brand.mjs +0 -34
  175. llms/ui/ChatPrompt.mjs +0 -443
  176. llms/ui/Main.mjs +0 -740
  177. llms/ui/ModelSelector.mjs +0 -60
  178. llms/ui/ProviderIcon.mjs +0 -29
  179. llms/ui/ProviderStatus.mjs +0 -105
  180. llms/ui/SignIn.mjs +0 -64
  181. llms/ui/SystemPromptEditor.mjs +0 -31
  182. llms/ui/SystemPromptSelector.mjs +0 -36
  183. llms/ui/Welcome.mjs +0 -8
  184. llms/ui/threadStore.mjs +0 -524
  185. llms/ui.json +0 -1069
  186. llms_py-2.0.20.dist-info/METADATA +0 -931
  187. llms_py-2.0.20.dist-info/RECORD +0 -36
  188. {llms_py-2.0.20.dist-info → llms_py-3.0.10.dist-info}/WHEEL +0 -0
  189. {llms_py-2.0.20.dist-info → llms_py-3.0.10.dist-info}/entry_points.txt +0 -0
  190. {llms_py-2.0.20.dist-info → llms_py-3.0.10.dist-info}/top_level.txt +0 -0
llms/ui/typography.css CHANGED
@@ -94,16 +94,18 @@
94
94
  border-left-style: solid;
95
95
  }
96
96
 
97
- .dark .prose :not(:where([class~="not-prose"] *)), .dark .prose :where(td):not(:where([class~="not-prose"] *)) {
97
+ .dark .prose :where(td):not(:where([class~="not-prose"] *)) {
98
98
  color: rgb(209 213 219); /*text-gray-300*/
99
99
  }
100
100
  .dark .prose :where(h1,h2,h3,h4,h5,h6,th):not(:where([class~="not-prose"] *)) {
101
101
  color: rgb(243 244 246); /*text-gray-100*/
102
102
  }
103
- .dark .prose :where(code):not(:where([class~="not-prose"] *)) {
103
+ .dark .prose :where(code):not(:where([class~="not-prose"] *)),
104
+ .dark .message em {
104
105
  background-color: rgb(30 58 138); /*text-blue-900*/
105
106
  color: rgb(243 244 246); /*text-gray-100*/
106
107
  }
108
+
107
109
  .dark .prose :where(pre code):not(:where([class~="not-prose"] *)) {
108
110
  background-color: unset;
109
111
  }
@@ -130,10 +132,6 @@
130
132
  .not-prose { max-width: unset; }
131
133
  .prose-table { max-width: 56rem; width: 56rem; padding-left: 1px; overflow-x: auto; }
132
134
  .hide-h2+h2 { display:none }
133
- .hljs, .prose :where(pre):not(:where([class~="not-prose"] *)) .hljs {
134
- color: var(--tw-prose-pre-code) !important;
135
- background-color: var(--tw-prose-pre-bg) !important;
136
- }
137
135
  @media (min-width: 1024px) {
138
136
  .lg\:prose-xl {
139
137
  font-size: 1.25rem;
@@ -285,6 +283,12 @@
285
283
  .prose pre::-webkit-scrollbar-thumb, .prose code::-webkit-scrollbar-thumb {
286
284
  background-color: rgb(100 116 139);
287
285
  }
286
+ .dark .prose pre::-webkit-scrollbar, .dark .prose code::-webkit-scrollbar {
287
+ background: #111827;
288
+ }
289
+ .dark .prose pre::-webkit-scrollbar-thumb, .dark .prose code::-webkit-scrollbar-thumb {
290
+ background-color: rgb(71 85 105);
291
+ }
288
292
 
289
293
  .html-format {
290
294
  max-width: unset;
@@ -390,6 +394,33 @@ h1:hover .header-anchor, h1 .header-anchor:focus, h2:hover .header-anchor, h2 .h
390
394
  font-weight: 600;
391
395
  }
392
396
 
397
+ pre {
398
+ overflow-x: auto;
399
+ font-weight: 400;
400
+ font-size: .875em;
401
+ line-height: 1.7142857;
402
+ margin-top: 1.7142857em;
403
+ margin-bottom: 1.7142857em;
404
+ border-radius: .375rem;
405
+ padding: .8571429em 1.1428571em;
406
+ max-width: calc(100vw - 1rem);
407
+ min-width: fit-content;
408
+ background-color: #282c34;
409
+ }
410
+ pre code.hljs {
411
+ display: block;
412
+ overflow-x: auto;
413
+ padding: 1em;
414
+ }
415
+ .message pre {
416
+ max-width: 100%;
417
+ min-width: auto;
418
+ }
419
+ .message pre code.hljs {
420
+ overflow-x: unset;
421
+ width: 100%;
422
+ }
423
+
393
424
  /* highlight.js - vs.css */
394
425
  .hljs {background:white;color:black}
395
426
  .hljs-comment,.hljs-quote,.hljs-variable{color:#008000}
@@ -413,38 +444,25 @@ pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5p
413
444
  .hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
414
445
  .hljs-link{text-decoration:underline}
415
446
 
416
- /*highlightjs*/
417
- .hljs, .prose :where(pre):not(:where([class~="not-prose"] *)) .hljs {
418
- color: #e5e7eb !important;
419
- background-color: #282c34 !important;
447
+ /* Dark mode overrides for Chat-specific styles */
448
+ .dark .prose blockquote {
449
+ border-left-color: #374151;
450
+ color: #9ca3af;
420
451
  }
421
- .hljs-comment, .hljs-quote {
422
- color: rgb(148 163 184); /*text-slate-400*/
452
+ .dark .prose th,
453
+ .dark .prose td {
454
+ border-color: #374151;
423
455
  }
424
-
425
- pre {
426
- overflow-x: auto;
427
- font-weight: 400;
428
- font-size: .875em;
429
- line-height: 1.7142857;
430
- margin-top: 1.7142857em;
431
- margin-bottom: 1.7142857em;
432
- border-radius: .375rem;
433
- padding: .8571429em 1.1428571em;
434
- max-width: calc(100vw - 1rem);
435
- min-width: fit-content;
436
- background-color: #282c34 !important;
456
+ .dark .prose th {
457
+ background-color: #1f2937;
437
458
  }
438
- pre code.hljs {
439
- display: block;
440
- overflow-x: auto;
441
- padding: 1em;
459
+ .dark .prose td {
460
+ background-color: #111827;
442
461
  }
443
- .message pre {
444
- max-width: 100%;
445
- min-width: auto;
462
+ .hljs-comment, .hljs-quote {
463
+ color: rgb(148 163 184); /*text-slate-400*/
464
+ }
465
+ .dark .prose > pre, .dark .prose pre code.hljs {
466
+ background-color: #111827 !important;
467
+ color: #f3f4f6;
446
468
  }
447
- .message pre code.hljs {
448
- overflow-x: unset;
449
- width: 100%;
450
- }
llms/ui/utils.mjs CHANGED
@@ -1,4 +1,5 @@
1
- import { $$, createElement, rightPart } from "@servicestack/client"
1
+ import { toRaw } from "vue"
2
+ import { rightPart, toDate } from "@servicestack/client"
2
3
 
3
4
  export function toJsonArray(json) {
4
5
  try {
@@ -30,127 +31,231 @@ export function storageObject(key, save) {
30
31
  return toJsonObject(localStorage.getItem(key)) ?? {}
31
32
  }
32
33
 
33
- export function deepClone(obj) {
34
- return JSON.parse(JSON.stringify(obj))
34
+ export function fileToBase64(file) {
35
+ return new Promise((resolve, reject) => {
36
+ const reader = new FileReader()
37
+ reader.readAsDataURL(file) //= "data:…;base64,…"
38
+ reader.onload = () => {
39
+ resolve(rightPart(reader.result, ',')) // strip prefix
40
+ }
41
+ reader.onerror = err => reject(err)
42
+ })
35
43
  }
36
44
 
37
- export function fileToBase64(file) {
38
- return new Promise((resolve, reject) => {
39
- const reader = new FileReader()
40
- reader.readAsDataURL(file) //= "data:…;base64,…"
41
- reader.onload = () => {
42
- resolve(rightPart(reader.result, ',')) // strip prefix
45
+ export function fileToDataUri(file) {
46
+ return new Promise((resolve, reject) => {
47
+ const reader = new FileReader()
48
+ reader.readAsDataURL(file) //= "data:…;base64,…"
49
+ reader.onload = () => resolve(reader.result)
50
+ reader.onerror = err => reject(err)
51
+ })
52
+ }
53
+
54
+ export function serializedClone(obj) {
55
+ try {
56
+ return JSON.parse(JSON.stringify(obj))
57
+ } catch (e) {
58
+ console.warn('Deep cloning failed, returning original value:', e)
59
+ return obj
43
60
  }
44
- reader.onerror = err => reject(err)
45
- })
46
61
  }
47
62
 
48
- export function fileToDataUri(file) {
49
- return new Promise((resolve, reject) => {
50
- const reader = new FileReader()
51
- reader.readAsDataURL(file) //= "data:…;base64,…"
52
- reader.onload = () => resolve(reader.result)
53
- reader.onerror = err => reject(err)
54
- })
63
+ export function deepClone(o) {
64
+ if (o === null || typeof o !== 'object') return o
65
+
66
+ // Handle Array objects
67
+ if (Array.isArray(o)) {
68
+ return o.map(x => deepClone(x))
69
+ }
70
+
71
+ if (typeof structuredClone === 'function') { // available (modern browsers, Node.js 17+)
72
+ try {
73
+ return structuredClone(o)
74
+ } catch (e) {
75
+ console.warn('structuredClone failed, falling back to JSON:', e)
76
+ console.log(o)
77
+ // console.log(JSON.stringify(o, undefined, 2))
78
+ }
79
+ }
80
+
81
+ // Fallback to JSON stringify/parse for older environments
82
+ return serializedClone(o)
55
83
  }
56
84
 
57
85
  export function toModelInfo(model) {
58
86
  if (!model) return undefined
59
- return Object.assign({}, model, { pricing: Object.assign({}, model.pricing) || undefined })
87
+ const props = ['id', 'name', 'provider', 'cost', 'modalities']
88
+ const to = {}
89
+ props.forEach(k => to[k] = toRaw(model[k]))
90
+ return deepClone(to)
60
91
  }
61
92
 
62
- const numFmt = new Intl.NumberFormat(undefined,{style:'currency',currency:'USD', maximumFractionDigits:6})
63
- export function tokenCost(price) {
93
+ export function pluralize(word, count) {
94
+ return count === 1 ? word : word + 's'
95
+ }
96
+
97
+ const currFmt2 = new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 2 })
98
+ const currFmt6 = new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 6 })
99
+
100
+ export function tokenCost(price, tokens = 1000000) {
64
101
  if (!price) return ''
65
- var ret = numFmt.format(parseFloat(price))
102
+ var ret = currFmt2.format(parseFloat(price) * (tokens / 1000000))
66
103
  return ret.endsWith('.00') ? ret.slice(0, -3) : ret
67
104
  }
105
+ export function tokenCostLong(price, tokens = 1000000) {
106
+ if (!price) return ''
107
+ const ret = currFmt6.format(parseFloat(price) * (tokens / 1000000))
108
+ return ret.endsWith('.000000') ? ret.slice(0, -7) : ret
109
+ }
68
110
  export function formatCost(cost) {
69
111
  if (!cost) return ''
70
- return numFmt.format(parseFloat(cost))
112
+ return currFmt2.format(parseFloat(cost))
71
113
  }
72
- export function statsTitle(stats) {
114
+ export function tokensTitle(usage) {
73
115
  let title = []
74
- // Each stat on its own line
75
- if (stats.cost) {
76
- title.push(`Total Cost: ${formatCost(stats.cost)}`)
116
+ if (usage.tokens && usage.price) {
117
+ const msg = parseFloat(usage.price) > 0
118
+ ? `${usage.tokens} tokens @ ${usage.price} = ${tokenCostLong(usage.price, usage.tokens)}`
119
+ : `${usage.tokens} tokens`
120
+ const duration = usage.duration ? ` in ${usage.duration}ms` : ''
121
+ title.push(msg + duration)
122
+ }
123
+ return title.join('\n')
124
+ }
125
+
126
+
127
+ // Accessible in views via $fmt
128
+ export function utilsFormatters() {
129
+ function relativeTime(timestamp) {
130
+ const now = new Date()
131
+ const date = new Date(timestamp)
132
+ const diffInSeconds = Math.floor((now - date) / 1000)
133
+
134
+ if (diffInSeconds < 60) return 'Just now'
135
+ if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`
136
+ if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`
137
+ if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`
138
+
139
+ return date.toLocaleDateString()
77
140
  }
78
- if (stats.inputTokens) {
79
- title.push(`Input Tokens: ${stats.inputTokens}`)
141
+ function costLong(cost) {
142
+ if (!cost) return ''
143
+ const ret = currFmt6.format(parseFloat(cost))
144
+ return ret.endsWith('.000000') ? ret.slice(0, -7) : ret
80
145
  }
81
- if (stats.outputTokens) {
82
- title.push(`Output Tokens: ${stats.outputTokens}`)
146
+ function statsTitle(stats) {
147
+ let title = []
148
+ // Each stat on its own line
149
+ if (stats.cost) {
150
+ title.push(`Total Cost: ${costLong(stats.cost)}`)
151
+ }
152
+ if (stats.inputTokens) {
153
+ title.push(`Input Tokens: ${stats.inputTokens}`)
154
+ }
155
+ if (stats.outputTokens) {
156
+ title.push(`Output Tokens: ${stats.outputTokens}`)
157
+ }
158
+ if (stats.requests) {
159
+ title.push(`Requests: ${stats.requests}`)
160
+ }
161
+ if (stats.duration) {
162
+ title.push(`Duration: ${stats.duration}ms`)
163
+ }
164
+ return title.join('\n')
83
165
  }
84
- if (stats.requests) {
85
- title.push(`Requests: ${stats.requests}`)
166
+
167
+ function time(timestamp) {
168
+ return new Date(timestamp).toLocaleTimeString([], {
169
+ hour: '2-digit',
170
+ minute: '2-digit'
171
+ })
86
172
  }
87
- if (stats.duration) {
88
- title.push(`Duration: ${stats.duration}ms`)
173
+
174
+ function shortDate(ts) {
175
+ if (!ts) return ''
176
+ const date = typeof ts === 'number' ? new Date(ts * 1000) : new Date(ts)
177
+ return date.toLocaleDateString(undefined, {
178
+ month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
179
+ })
89
180
  }
90
- return title.join('\n')
91
- }
92
181
 
93
- const svg = {
94
- clipboard: `<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none"><path d="M8 5H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1M8 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M8 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m0 0h2a2 2 0 0 1 2 2v3m2 4H10m0 0l3-3m-3 3l3 3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></g></svg>`,
95
- check: `<svg class="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>`,
96
- }
97
-
98
- function copyBlock(btn) {
99
- // console.log('copyBlock',btn)
100
- const label = btn.previousElementSibling
101
- const code = btn.parentElement.nextElementSibling
102
- label.classList.remove('hidden')
103
- label.innerHTML = 'copied'
104
- btn.classList.add('border-gray-600', 'bg-gray-700')
105
- btn.classList.remove('border-gray-700')
106
- btn.innerHTML = svg.check
107
- navigator.clipboard.writeText(code.innerText)
108
- setTimeout(() => {
109
- label.classList.add('hidden')
110
- label.innerHTML = ''
111
- btn.innerHTML = svg.clipboard
112
- btn.classList.remove('border-gray-600', 'bg-gray-700')
113
- btn.classList.add('border-gray-700')
114
- }, 2000)
115
- }
116
-
117
- export function addCopyButtonToCodeBlocks(sel) {
118
- globalThis.copyBlock ??= copyBlock
119
- //console.log('addCopyButtonToCodeBlocks', sel, [...$$(sel)].length)
120
-
121
- $$(sel).forEach(code => {
122
- let pre = code.parentElement;
123
- if (pre.classList.contains('group')) return
124
- pre.classList.add('relative', 'group')
125
-
126
- const div = createElement('div', {attrs: {className: 'opacity-0 group-hover:opacity-100 transition-opacity duration-100 flex absolute right-2 -mt-1 select-none'}})
127
- const label = createElement('div', {attrs: {className: 'hidden font-sans p-1 px-2 mr-1 rounded-md border border-gray-600 bg-gray-700 text-gray-400'}})
128
- const btn = createElement('button', {
129
- attrs: {
130
- type: 'button',
131
- className: 'p-1 rounded-md border block text-gray-500 hover:text-gray-400 border-gray-700 hover:border-gray-600',
132
- onclick: 'copyBlock(this)'
133
- }
182
+ function date(d) {
183
+ date = toDate(d)
184
+ return date.toLocaleDateString(undefined, {
185
+ month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
134
186
  })
135
- btn.innerHTML = svg.clipboard
136
- div.appendChild(label)
137
- div.appendChild(btn)
138
- pre.insertBefore(div, code)
139
- })
187
+ }
188
+
189
+
190
+ return {
191
+ currFmt: currFmt2,
192
+ tokenCost,
193
+ tokenCostLong,
194
+ tokensTitle,
195
+ cost: formatCost,
196
+ costLong,
197
+ statsTitle,
198
+ relativeTime,
199
+ time,
200
+ pluralize,
201
+ shortDate,
202
+ date,
203
+ }
204
+ }
205
+
206
+ const htmlStartTags = ['<!doctype', '<html', '<head', '<body', '<script', '<style', '<link']
207
+ export function isHtml(s) {
208
+ if (!s || typeof s != 'string') return false
209
+ const lower = s.toLowerCase().trim()
210
+ const isHtml = htmlStartTags.some(tag => lower.startsWith(tag))
211
+ return isHtml
140
212
  }
141
213
 
142
- export function addCopyButtons() {
143
- addCopyButtonToCodeBlocks('.prose pre>code')
214
+ /**
215
+ * @param {object|array} type
216
+ * @param {'div'|'table'|'thead'|'th'|'tr'|'td'} tag
217
+ * @param {number} depth
218
+ * @param {string} cls
219
+ * @param {number} index
220
+ */
221
+ function htmlFormatClasses(type, tag, depth, cls, index) {
222
+ cls = cls.replace('shadow ring-1 ring-black/5 md:rounded-lg', '')
223
+ if (tag == 'th') {
224
+ cls += ' lowercase'
225
+ }
226
+ if (tag == 'td') {
227
+ cls += ' whitespace-pre-wrap'
228
+ }
229
+ return cls
144
230
  }
145
231
 
146
232
  /**
147
233
  * Returns an ever-increasing unique integer id.
148
234
  */
149
235
  export const nextId = (() => {
150
- let last = 0 // cache of the last id that was handed out
151
- return () => {
152
- const now = Date.now() // current millisecond timestamp
153
- last = (now > last) ? now : last + 1
154
- return last
155
- }
156
- })();
236
+ let last = 0 // cache of the last id that was handed out
237
+ return () => {
238
+ const now = Date.now() // current millisecond timestamp
239
+ last = (now > last) ? now : last + 1
240
+ return last
241
+ }
242
+ })();
243
+
244
+ export function utilsFunctions() {
245
+ return {
246
+ nextId,
247
+ toJsonArray,
248
+ toJsonObject,
249
+ storageArray,
250
+ storageObject,
251
+ fileToBase64,
252
+ fileToDataUri,
253
+ serializedClone,
254
+ deepClone,
255
+ toModelInfo,
256
+ pluralize,
257
+ isHtml,
258
+ htmlFormatClasses,
259
+ }
260
+ }
261
+
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: llms-py
3
+ Version: 3.0.10
4
+ Summary: A lightweight CLI tool and OpenAI-compatible server for querying multiple Large Language Model (LLM) providers
5
+ Home-page: https://github.com/ServiceStack/llms
6
+ Author: ServiceStack
7
+ Author-email: ServiceStack <team@servicestack.net>
8
+ Maintainer-email: ServiceStack <team@servicestack.net>
9
+ License-Expression: BSD-3-Clause
10
+ Project-URL: Homepage, https://github.com/ServiceStack/llms
11
+ Project-URL: Documentation, https://github.com/ServiceStack/llms#readme
12
+ Project-URL: Repository, https://github.com/ServiceStack/llms
13
+ Project-URL: Bug Reports, https://github.com/ServiceStack/llms/issues
14
+ Keywords: llm,ai,openai,anthropic,google,gemini,groq,mistral,ollama,cli,server,chat,completion
15
+ Classifier: Development Status :: 5 - Production/Stable
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: System Administrators
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.7
21
+ Classifier: Programming Language :: Python :: 3.8
22
+ Classifier: Programming Language :: Python :: 3.9
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
28
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
29
+ Classifier: Topic :: System :: Systems Administration
30
+ Classifier: Topic :: Utilities
31
+ Classifier: Environment :: Console
32
+ Requires-Python: >=3.7
33
+ Description-Content-Type: text/markdown
34
+ License-File: LICENSE
35
+ Requires-Dist: aiohttp
36
+ Dynamic: author
37
+ Dynamic: home-page
38
+ Dynamic: license-file
39
+ Dynamic: requires-python
40
+
41
+ # llms.py
42
+
43
+ Lightweight CLI, API and ChatGPT-like alternative to Open WebUI for accessing multiple LLMs, entirely offline, with all data kept private in browser storage.
44
+
45
+ [llmspy.org](https://llmspy.org)
46
+
47
+ [![](https://github.com/ServiceStack/llmspy.org/blob/main/public/img/llmspy-home.webp?raw=true)](https://llmspy.org)
48
+
49
+ GitHub: [llmspy.org](https://github.com/ServiceStack/llmspy.org)