llms-py 2.0.20__py3-none-any.whl → 3.0.18__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 (207) 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 +588 -0
  6. llms/extensions/app/db.py +540 -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 +440 -0
  10. llms/extensions/computer/README.md +96 -0
  11. llms/extensions/computer/__init__.py +59 -0
  12. llms/extensions/computer/base.py +80 -0
  13. llms/extensions/computer/bash.py +185 -0
  14. llms/extensions/computer/computer.py +523 -0
  15. llms/extensions/computer/edit.py +299 -0
  16. llms/extensions/computer/filesystem.py +542 -0
  17. llms/extensions/computer/platform.py +461 -0
  18. llms/extensions/computer/run.py +37 -0
  19. llms/extensions/core_tools/CALCULATOR.md +32 -0
  20. llms/extensions/core_tools/__init__.py +599 -0
  21. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
  22. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
  23. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
  24. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
  25. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
  26. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
  27. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
  28. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
  29. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
  30. llms/extensions/core_tools/ui/codemirror/codemirror.css +344 -0
  31. llms/extensions/core_tools/ui/codemirror/codemirror.js +9884 -0
  32. llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
  33. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  34. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
  35. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
  36. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
  37. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
  38. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
  39. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
  40. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
  41. llms/extensions/core_tools/ui/index.mjs +650 -0
  42. llms/extensions/gallery/README.md +61 -0
  43. llms/extensions/gallery/__init__.py +63 -0
  44. llms/extensions/gallery/db.py +243 -0
  45. llms/extensions/gallery/ui/index.mjs +482 -0
  46. llms/extensions/katex/README.md +39 -0
  47. llms/extensions/katex/__init__.py +6 -0
  48. llms/extensions/katex/ui/README.md +125 -0
  49. llms/extensions/katex/ui/contrib/auto-render.js +338 -0
  50. llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
  51. llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
  52. llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
  53. llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
  54. llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
  55. llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
  56. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
  57. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
  58. llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
  59. llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
  60. llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
  61. llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
  62. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
  63. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
  64. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  65. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  66. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  67. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  68. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  69. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  70. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  71. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  72. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  73. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  74. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  75. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  76. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  77. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  78. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  79. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  80. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  81. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  82. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  83. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  84. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  85. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  86. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  87. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  88. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  89. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  90. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  91. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  92. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  93. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  94. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  95. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  96. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  97. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  98. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  99. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  100. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  101. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  102. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  103. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  104. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  115. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  116. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  117. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  118. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  119. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  120. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  121. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  122. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  123. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  124. llms/extensions/katex/ui/index.mjs +92 -0
  125. llms/extensions/katex/ui/katex-swap.css +1230 -0
  126. llms/extensions/katex/ui/katex-swap.min.css +1 -0
  127. llms/extensions/katex/ui/katex.css +1230 -0
  128. llms/extensions/katex/ui/katex.js +19080 -0
  129. llms/extensions/katex/ui/katex.min.css +1 -0
  130. llms/extensions/katex/ui/katex.min.js +1 -0
  131. llms/extensions/katex/ui/katex.min.mjs +1 -0
  132. llms/extensions/katex/ui/katex.mjs +18547 -0
  133. llms/extensions/providers/__init__.py +22 -0
  134. llms/extensions/providers/anthropic.py +260 -0
  135. llms/extensions/providers/cerebras.py +36 -0
  136. llms/extensions/providers/chutes.py +153 -0
  137. llms/extensions/providers/google.py +559 -0
  138. llms/extensions/providers/nvidia.py +103 -0
  139. llms/extensions/providers/openai.py +154 -0
  140. llms/extensions/providers/openrouter.py +74 -0
  141. llms/extensions/providers/zai.py +182 -0
  142. llms/extensions/skills/LICENSE +202 -0
  143. llms/extensions/skills/__init__.py +130 -0
  144. llms/extensions/skills/errors.py +25 -0
  145. llms/extensions/skills/models.py +39 -0
  146. llms/extensions/skills/parser.py +178 -0
  147. llms/extensions/skills/ui/index.mjs +376 -0
  148. llms/extensions/skills/ui/skills/create-plan/SKILL.md +74 -0
  149. llms/extensions/skills/validator.py +177 -0
  150. llms/extensions/system_prompts/README.md +22 -0
  151. llms/extensions/system_prompts/__init__.py +45 -0
  152. llms/extensions/system_prompts/ui/index.mjs +276 -0
  153. llms/extensions/system_prompts/ui/prompts.json +1067 -0
  154. llms/extensions/tools/__init__.py +67 -0
  155. llms/extensions/tools/ui/index.mjs +837 -0
  156. llms/index.html +36 -62
  157. llms/llms.json +180 -879
  158. llms/main.py +4009 -912
  159. llms/providers-extra.json +394 -0
  160. llms/providers.json +1 -0
  161. llms/ui/App.mjs +176 -8
  162. llms/ui/ai.mjs +156 -20
  163. llms/ui/app.css +3768 -321
  164. llms/ui/ctx.mjs +459 -0
  165. llms/ui/index.mjs +131 -0
  166. llms/ui/lib/chart.js +14 -0
  167. llms/ui/lib/charts.mjs +16 -0
  168. llms/ui/lib/color.js +14 -0
  169. llms/ui/lib/highlight.min.mjs +1243 -0
  170. llms/ui/lib/idb.min.mjs +8 -0
  171. llms/ui/lib/marked.min.mjs +8 -0
  172. llms/ui/lib/servicestack-client.mjs +1 -0
  173. llms/ui/lib/servicestack-vue.mjs +37 -0
  174. llms/ui/lib/vue-router.min.mjs +6 -0
  175. llms/ui/lib/vue.min.mjs +13 -0
  176. llms/ui/lib/vue.mjs +18530 -0
  177. llms/ui/markdown.mjs +25 -14
  178. llms/ui/modules/chat/ChatBody.mjs +1156 -0
  179. llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +74 -74
  180. llms/ui/modules/chat/index.mjs +995 -0
  181. llms/ui/modules/icons.mjs +46 -0
  182. llms/ui/modules/layout.mjs +271 -0
  183. llms/ui/modules/model-selector.mjs +811 -0
  184. llms/ui/tailwind.input.css +560 -78
  185. llms/ui/typography.css +54 -36
  186. llms/ui/utils.mjs +221 -92
  187. llms_py-3.0.18.dist-info/METADATA +49 -0
  188. llms_py-3.0.18.dist-info/RECORD +194 -0
  189. {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/WHEEL +1 -1
  190. {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/licenses/LICENSE +1 -2
  191. llms/ui/Avatar.mjs +0 -28
  192. llms/ui/Brand.mjs +0 -34
  193. llms/ui/ChatPrompt.mjs +0 -443
  194. llms/ui/Main.mjs +0 -740
  195. llms/ui/ModelSelector.mjs +0 -60
  196. llms/ui/ProviderIcon.mjs +0 -29
  197. llms/ui/ProviderStatus.mjs +0 -105
  198. llms/ui/SignIn.mjs +0 -64
  199. llms/ui/SystemPromptEditor.mjs +0 -31
  200. llms/ui/SystemPromptSelector.mjs +0 -36
  201. llms/ui/Welcome.mjs +0 -8
  202. llms/ui/threadStore.mjs +0 -524
  203. llms/ui.json +0 -1069
  204. llms_py-2.0.20.dist-info/METADATA +0 -931
  205. llms_py-2.0.20.dist-info/RECORD +0 -36
  206. {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/entry_points.txt +0 -0
  207. {llms_py-2.0.20.dist-info → llms_py-3.0.18.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,255 @@ 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)
91
+ }
92
+
93
+ export function pluralize(word, count) {
94
+ return count === 1 ? word : word + 's'
60
95
  }
61
96
 
62
- const numFmt = new Intl.NumberFormat(undefined,{style:'currency',currency:'USD', maximumFractionDigits:6})
63
- export function tokenCost(price) {
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
+ }
140
204
  }
141
205
 
142
- export function addCopyButtons() {
143
- addCopyButtonToCodeBlocks('.prose pre>code')
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
212
+ }
213
+
214
+ const htmlEntities = {
215
+ '&': '&amp;',
216
+ '<': '&lt;',
217
+ '>': '&gt;',
218
+ '"': '&quot;',
219
+ "'": '&#39;'
220
+ }
221
+
222
+ export function encodeHtml(str) {
223
+ if (!str) return ''
224
+ return str.replace(/[&<>"']/g, m => htmlEntities[m]);
225
+ }
226
+
227
+ /**
228
+ * @param {object|array} type
229
+ * @param {'div'|'table'|'thead'|'th'|'tr'|'td'} tag
230
+ * @param {number} depth
231
+ * @param {string} cls
232
+ * @param {number} index
233
+ */
234
+ function htmlFormatClasses(type, tag, depth, cls, index) {
235
+ cls = cls.replace('shadow ring-1 ring-black/5 md:rounded-lg', '')
236
+ if (tag == 'th') {
237
+ cls += ' lowercase'
238
+ }
239
+ if (tag == 'td') {
240
+ cls += ' whitespace-pre-wrap'
241
+ }
242
+ return cls
144
243
  }
145
244
 
146
245
  /**
147
246
  * Returns an ever-increasing unique integer id.
148
247
  */
149
248
  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
- })();
249
+ let last = 0 // cache of the last id that was handed out
250
+ return () => {
251
+ const now = Date.now() // current millisecond timestamp
252
+ last = (now > last) ? now : last + 1
253
+ return last
254
+ }
255
+ })();
256
+
257
+ export function fnv1a(str) {
258
+ let hash = 0x811c9dc5
259
+ for (let i = 0; i < str.length; i++) {
260
+ hash ^= str.charCodeAt(i)
261
+ hash = Math.imul(hash, 0x01000193)
262
+ }
263
+ return hash >>> 0
264
+ }
265
+ export const hashString = fnv1a
266
+
267
+ export function utilsFunctions() {
268
+ return {
269
+ nextId,
270
+ toJsonArray,
271
+ toJsonObject,
272
+ storageArray,
273
+ storageObject,
274
+ fileToBase64,
275
+ fileToDataUri,
276
+ serializedClone,
277
+ deepClone,
278
+ toModelInfo,
279
+ pluralize,
280
+ isHtml,
281
+ htmlFormatClasses,
282
+ encodeHtml,
283
+ hashString,
284
+ }
285
+ }
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: llms-py
3
+ Version: 3.0.18
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-v3.webp?raw=true)](https://llmspy.org)
48
+
49
+ GitHub: [llmspy.org](https://github.com/ServiceStack/llmspy.org)