llms-py 3.0.0__py3-none-any.whl → 3.0.0b1__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 (206) hide show
  1. llms/index.html +77 -35
  2. llms/llms.json +23 -72
  3. llms/main.py +732 -1786
  4. llms/providers.json +1 -1
  5. llms/{extensions/analytics/ui/index.mjs → ui/Analytics.mjs} +238 -154
  6. llms/ui/App.mjs +60 -151
  7. llms/ui/Avatar.mjs +85 -0
  8. llms/ui/Brand.mjs +52 -0
  9. llms/ui/ChatPrompt.mjs +606 -0
  10. llms/ui/Main.mjs +873 -0
  11. llms/ui/ModelSelector.mjs +693 -0
  12. llms/ui/OAuthSignIn.mjs +92 -0
  13. llms/ui/ProviderIcon.mjs +36 -0
  14. llms/ui/ProviderStatus.mjs +105 -0
  15. llms/{extensions/app/ui → ui}/Recents.mjs +65 -91
  16. llms/ui/{modules/chat/SettingsDialog.mjs → SettingsDialog.mjs} +9 -9
  17. llms/{extensions/app/ui/index.mjs → ui/Sidebar.mjs} +58 -124
  18. llms/ui/SignIn.mjs +64 -0
  19. llms/ui/SystemPromptEditor.mjs +31 -0
  20. llms/ui/SystemPromptSelector.mjs +56 -0
  21. llms/ui/Welcome.mjs +8 -0
  22. llms/ui/ai.mjs +53 -125
  23. llms/ui/app.css +111 -1837
  24. llms/ui/lib/charts.mjs +13 -9
  25. llms/ui/lib/servicestack-vue.mjs +3 -3
  26. llms/ui/lib/vue.min.mjs +9 -10
  27. llms/ui/lib/vue.mjs +1602 -1763
  28. llms/ui/markdown.mjs +2 -10
  29. llms/ui/tailwind.input.css +80 -496
  30. llms/ui/threadStore.mjs +572 -0
  31. llms/ui/utils.mjs +117 -113
  32. llms/ui.json +1069 -0
  33. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/METADATA +1 -1
  34. llms_py-3.0.0b1.dist-info/RECORD +49 -0
  35. llms/__pycache__/__init__.cpython-312.pyc +0 -0
  36. llms/__pycache__/__init__.cpython-313.pyc +0 -0
  37. llms/__pycache__/__init__.cpython-314.pyc +0 -0
  38. llms/__pycache__/__main__.cpython-312.pyc +0 -0
  39. llms/__pycache__/__main__.cpython-314.pyc +0 -0
  40. llms/__pycache__/llms.cpython-312.pyc +0 -0
  41. llms/__pycache__/main.cpython-312.pyc +0 -0
  42. llms/__pycache__/main.cpython-313.pyc +0 -0
  43. llms/__pycache__/main.cpython-314.pyc +0 -0
  44. llms/__pycache__/plugins.cpython-314.pyc +0 -0
  45. llms/extensions/app/README.md +0 -20
  46. llms/extensions/app/__init__.py +0 -530
  47. llms/extensions/app/__pycache__/__init__.cpython-314.pyc +0 -0
  48. llms/extensions/app/__pycache__/db.cpython-314.pyc +0 -0
  49. llms/extensions/app/__pycache__/db_manager.cpython-314.pyc +0 -0
  50. llms/extensions/app/db.py +0 -644
  51. llms/extensions/app/db_manager.py +0 -195
  52. llms/extensions/app/requests.json +0 -9073
  53. llms/extensions/app/threads.json +0 -15290
  54. llms/extensions/app/ui/threadStore.mjs +0 -411
  55. llms/extensions/core_tools/CALCULATOR.md +0 -32
  56. llms/extensions/core_tools/__init__.py +0 -598
  57. llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
  58. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +0 -201
  59. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +0 -185
  60. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +0 -101
  61. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +0 -160
  62. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +0 -66
  63. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +0 -27
  64. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +0 -72
  65. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +0 -119
  66. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +0 -98
  67. llms/extensions/core_tools/ui/codemirror/doc/docs.css +0 -225
  68. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  69. llms/extensions/core_tools/ui/codemirror/lib/codemirror.css +0 -344
  70. llms/extensions/core_tools/ui/codemirror/lib/codemirror.js +0 -9884
  71. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +0 -942
  72. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +0 -118
  73. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +0 -962
  74. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +0 -62
  75. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +0 -402
  76. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +0 -40
  77. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +0 -135
  78. llms/extensions/core_tools/ui/index.mjs +0 -650
  79. llms/extensions/gallery/README.md +0 -61
  80. llms/extensions/gallery/__init__.py +0 -61
  81. llms/extensions/gallery/__pycache__/__init__.cpython-314.pyc +0 -0
  82. llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
  83. llms/extensions/gallery/db.py +0 -298
  84. llms/extensions/gallery/ui/index.mjs +0 -482
  85. llms/extensions/katex/README.md +0 -39
  86. llms/extensions/katex/__init__.py +0 -6
  87. llms/extensions/katex/__pycache__/__init__.cpython-314.pyc +0 -0
  88. llms/extensions/katex/ui/README.md +0 -125
  89. llms/extensions/katex/ui/contrib/auto-render.js +0 -338
  90. llms/extensions/katex/ui/contrib/auto-render.min.js +0 -1
  91. llms/extensions/katex/ui/contrib/auto-render.mjs +0 -244
  92. llms/extensions/katex/ui/contrib/copy-tex.js +0 -127
  93. llms/extensions/katex/ui/contrib/copy-tex.min.js +0 -1
  94. llms/extensions/katex/ui/contrib/copy-tex.mjs +0 -105
  95. llms/extensions/katex/ui/contrib/mathtex-script-type.js +0 -109
  96. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +0 -1
  97. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +0 -24
  98. llms/extensions/katex/ui/contrib/mhchem.js +0 -3213
  99. llms/extensions/katex/ui/contrib/mhchem.min.js +0 -1
  100. llms/extensions/katex/ui/contrib/mhchem.mjs +0 -3109
  101. llms/extensions/katex/ui/contrib/render-a11y-string.js +0 -887
  102. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +0 -1
  103. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +0 -800
  104. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  115. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  116. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  117. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  118. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  119. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  120. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  121. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  122. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  123. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  124. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  125. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  126. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  127. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  128. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  129. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  130. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  131. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  132. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  133. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  134. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  135. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  136. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  137. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  138. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  139. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  140. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  141. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  142. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  143. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  144. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  145. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  146. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  147. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  148. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  149. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  150. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  151. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  152. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  153. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  154. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  155. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  156. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  157. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  158. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  159. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  160. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  161. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  162. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  163. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  164. llms/extensions/katex/ui/index.mjs +0 -92
  165. llms/extensions/katex/ui/katex-swap.css +0 -1230
  166. llms/extensions/katex/ui/katex-swap.min.css +0 -1
  167. llms/extensions/katex/ui/katex.css +0 -1230
  168. llms/extensions/katex/ui/katex.js +0 -19080
  169. llms/extensions/katex/ui/katex.min.css +0 -1
  170. llms/extensions/katex/ui/katex.min.js +0 -1
  171. llms/extensions/katex/ui/katex.min.mjs +0 -1
  172. llms/extensions/katex/ui/katex.mjs +0 -18547
  173. llms/extensions/providers/__init__.py +0 -18
  174. llms/extensions/providers/__pycache__/__init__.cpython-314.pyc +0 -0
  175. llms/extensions/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
  176. llms/extensions/providers/__pycache__/chutes.cpython-314.pyc +0 -0
  177. llms/extensions/providers/__pycache__/google.cpython-314.pyc +0 -0
  178. llms/extensions/providers/__pycache__/nvidia.cpython-314.pyc +0 -0
  179. llms/extensions/providers/__pycache__/openai.cpython-314.pyc +0 -0
  180. llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
  181. llms/extensions/providers/anthropic.py +0 -229
  182. llms/extensions/providers/chutes.py +0 -155
  183. llms/extensions/providers/google.py +0 -378
  184. llms/extensions/providers/nvidia.py +0 -105
  185. llms/extensions/providers/openai.py +0 -156
  186. llms/extensions/providers/openrouter.py +0 -72
  187. llms/extensions/system_prompts/README.md +0 -22
  188. llms/extensions/system_prompts/__init__.py +0 -45
  189. llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc +0 -0
  190. llms/extensions/system_prompts/ui/index.mjs +0 -280
  191. llms/extensions/system_prompts/ui/prompts.json +0 -1067
  192. llms/extensions/tools/__init__.py +0 -5
  193. llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  194. llms/extensions/tools/ui/index.mjs +0 -204
  195. llms/providers-extra.json +0 -356
  196. llms/ui/ctx.mjs +0 -365
  197. llms/ui/index.mjs +0 -129
  198. llms/ui/modules/chat/ChatBody.mjs +0 -691
  199. llms/ui/modules/chat/index.mjs +0 -828
  200. llms/ui/modules/layout.mjs +0 -243
  201. llms/ui/modules/model-selector.mjs +0 -851
  202. llms_py-3.0.0.dist-info/RECORD +0 -202
  203. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/WHEEL +0 -0
  204. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/entry_points.txt +0 -0
  205. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
  206. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/top_level.txt +0 -0
llms/ui/utils.mjs CHANGED
@@ -1,5 +1,37 @@
1
- import { toRaw } from "vue"
2
- import { rightPart } from "@servicestack/client"
1
+ import { $$, createElement, rightPart, pick } from "@servicestack/client"
2
+
3
+ const cacheUrlInfo = {}
4
+
5
+ export function getCacheInfo(url) {
6
+ return cacheUrlInfo[url]
7
+ }
8
+ export async function fetchCacheInfos(urls) {
9
+ const infos = {}
10
+ const fetchInfos = []
11
+ for (const url of urls) {
12
+ const info = getCacheInfo(url)
13
+ if (info) {
14
+ infos[url] = info
15
+ } else {
16
+ fetchInfos.push(fetch(url + "?info"))
17
+ }
18
+ }
19
+ const responses = await Promise.all(fetchInfos)
20
+ for (let i = 0; i < urls.length; i++) {
21
+ try {
22
+ const info = await responses[i].json()
23
+ setCacheInfo(urls[i], info)
24
+ infos[urls[i]] = info
25
+ } catch (e) {
26
+ console.error('Failed to fetch info for', urls[i], e)
27
+ }
28
+ }
29
+ return infos
30
+ }
31
+
32
+ export function setCacheInfo(url, info) {
33
+ cacheUrlInfo[url] = info
34
+ }
3
35
 
4
36
  export function toJsonArray(json) {
5
37
  try {
@@ -51,6 +83,19 @@ export function fileToDataUri(file) {
51
83
  })
52
84
  }
53
85
 
86
+ export async function uploadFile(file) {
87
+ const formData = new FormData()
88
+ formData.append('file', file)
89
+ const response = await fetch('/upload', {
90
+ method: 'POST',
91
+ body: formData
92
+ })
93
+ if (!response.ok) {
94
+ throw new Error(`Upload failed: ${response.statusText}`)
95
+ }
96
+ return response.json()
97
+ }
98
+
54
99
  export function serializedClone(obj) {
55
100
  try {
56
101
  return JSON.parse(JSON.stringify(obj))
@@ -73,8 +118,6 @@ export function deepClone(o) {
73
118
  return structuredClone(o)
74
119
  } catch (e) {
75
120
  console.warn('structuredClone failed, falling back to JSON:', e)
76
- console.log(o)
77
- // console.log(JSON.stringify(o, undefined, 2))
78
121
  }
79
122
  }
80
123
 
@@ -84,114 +127,92 @@ export function deepClone(o) {
84
127
 
85
128
  export function toModelInfo(model) {
86
129
  if (!model) return 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)
130
+ const { id, name, provider, cost, modalities } = model
131
+ return deepClone({ id, name, provider, cost, modalities })
91
132
  }
92
133
 
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 = 1) {
134
+ const numFmt = new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 6 })
135
+ export function tokenCost(price) {
101
136
  if (!price) return ''
102
- var ret = currFmt2.format(parseFloat(price) * tokens)
137
+ var ret = numFmt.format(parseFloat(price))
103
138
  return ret.endsWith('.00') ? ret.slice(0, -3) : ret
104
139
  }
105
- export function tokenCostLong(price, tokens = 1) {
106
- if (!price) return ''
107
- const ret = currFmt6.format(parseFloat(price) * tokens)
108
- return ret.endsWith('.000000') ? ret.slice(0, -7) : ret
109
- }
110
140
  export function formatCost(cost) {
111
141
  if (!cost) return ''
112
- return currFmt2.format(parseFloat(cost))
142
+ return numFmt.format(parseFloat(cost))
113
143
  }
114
- export function tokensTitle(usage) {
144
+ export function statsTitle(stats) {
115
145
  let title = []
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)
146
+ // Each stat on its own line
147
+ if (stats.cost) {
148
+ title.push(`Total Cost: ${formatCost(stats.cost)}`)
122
149
  }
123
- return title.join('\n')
124
- }
125
-
126
- // Accessible in views via $fmt
127
- export function utilsFormatters() {
128
- function relativeTime(timestamp) {
129
- const now = new Date()
130
- const date = new Date(timestamp)
131
- const diffInSeconds = Math.floor((now - date) / 1000)
132
-
133
- if (diffInSeconds < 60) return 'Just now'
134
- if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`
135
- if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`
136
- if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`
137
-
138
- return date.toLocaleDateString()
150
+ if (stats.inputTokens) {
151
+ title.push(`Input Tokens: ${stats.inputTokens}`)
139
152
  }
140
- function costLong(cost) {
141
- if (!cost) return ''
142
- const ret = currFmt6.format(parseFloat(cost))
143
- return ret.endsWith('.000000') ? ret.slice(0, -7) : ret
153
+ if (stats.outputTokens) {
154
+ title.push(`Output Tokens: ${stats.outputTokens}`)
144
155
  }
145
- function statsTitle(stats) {
146
- let title = []
147
- // Each stat on its own line
148
- if (stats.cost) {
149
- title.push(`Total Cost: ${costLong(stats.cost)}`)
150
- }
151
- if (stats.inputTokens) {
152
- title.push(`Input Tokens: ${stats.inputTokens}`)
153
- }
154
- if (stats.outputTokens) {
155
- title.push(`Output Tokens: ${stats.outputTokens}`)
156
- }
157
- if (stats.requests) {
158
- title.push(`Requests: ${stats.requests}`)
159
- }
160
- if (stats.duration) {
161
- title.push(`Duration: ${stats.duration}ms`)
162
- }
163
- return title.join('\n')
156
+ if (stats.requests) {
157
+ title.push(`Requests: ${stats.requests}`)
164
158
  }
165
-
166
- function time(timestamp) {
167
- return new Date(timestamp).toLocaleTimeString([], {
168
- hour: '2-digit',
169
- minute: '2-digit'
170
- })
159
+ if (stats.duration) {
160
+ title.push(`Duration: ${stats.duration}ms`)
171
161
  }
162
+ return title.join('\n')
163
+ }
172
164
 
173
- function shortDate(ts) {
174
- if (!ts) return ''
175
- const date = typeof ts === 'number' ? new Date(ts * 1000) : new Date(ts)
176
- return date.toLocaleDateString(undefined, {
177
- month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
165
+ const svg = {
166
+ 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>`,
167
+ 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>`,
168
+ }
169
+
170
+ function copyBlock(btn) {
171
+ // console.log('copyBlock',btn)
172
+ const label = btn.previousElementSibling
173
+ const code = btn.parentElement.nextElementSibling
174
+ label.classList.remove('hidden')
175
+ label.innerHTML = 'copied'
176
+ btn.classList.add('border-gray-600', 'bg-gray-700')
177
+ btn.classList.remove('border-gray-700')
178
+ btn.innerHTML = svg.check
179
+ navigator.clipboard.writeText(code.innerText)
180
+ setTimeout(() => {
181
+ label.classList.add('hidden')
182
+ label.innerHTML = ''
183
+ btn.innerHTML = svg.clipboard
184
+ btn.classList.remove('border-gray-600', 'bg-gray-700')
185
+ btn.classList.add('border-gray-700')
186
+ }, 2000)
187
+ }
188
+
189
+ export function addCopyButtonToCodeBlocks(sel) {
190
+ globalThis.copyBlock ??= copyBlock
191
+ //console.log('addCopyButtonToCodeBlocks', sel, [...$$(sel)].length)
192
+
193
+ $$(sel).forEach(code => {
194
+ let pre = code.parentElement;
195
+ if (pre.classList.contains('group')) return
196
+ pre.classList.add('relative', 'group')
197
+
198
+ const div = createElement('div', { attrs: { className: 'opacity-0 group-hover:opacity-100 transition-opacity duration-100 flex absolute right-2 -mt-1 select-none' } })
199
+ 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' } })
200
+ const btn = createElement('button', {
201
+ attrs: {
202
+ type: 'button',
203
+ className: 'p-1 rounded-md border block text-gray-500 hover:text-gray-400 border-gray-700 hover:border-gray-600',
204
+ onclick: 'copyBlock(this)'
205
+ }
178
206
  })
179
- }
180
-
207
+ btn.innerHTML = svg.clipboard
208
+ div.appendChild(label)
209
+ div.appendChild(btn)
210
+ pre.insertBefore(div, code)
211
+ })
212
+ }
181
213
 
182
- return {
183
- currFmt: currFmt2,
184
- tokenCost,
185
- tokenCostLong,
186
- tokensTitle,
187
- cost: formatCost,
188
- costLong,
189
- statsTitle,
190
- relativeTime,
191
- time,
192
- pluralize,
193
- shortDate,
194
- }
214
+ export function addCopyButtons() {
215
+ addCopyButtonToCodeBlocks('.prose pre>code')
195
216
  }
196
217
 
197
218
  /**
@@ -204,21 +225,4 @@ export const nextId = (() => {
204
225
  last = (now > last) ? now : last + 1
205
226
  return last
206
227
  }
207
- })();
208
-
209
- export function utilsFunctions() {
210
- return {
211
- nextId,
212
- toJsonArray,
213
- toJsonObject,
214
- storageArray,
215
- storageObject,
216
- fileToBase64,
217
- fileToDataUri,
218
- serializedClone,
219
- deepClone,
220
- toModelInfo,
221
- pluralize,
222
- }
223
- }
224
-
228
+ })();