llms-py 2.0.9__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 (194) hide show
  1. llms/__init__.py +4 -0
  2. llms/__main__.py +9 -0
  3. llms/db.py +359 -0
  4. llms/extensions/analytics/ui/index.mjs +1444 -0
  5. llms/extensions/app/README.md +20 -0
  6. llms/extensions/app/__init__.py +589 -0
  7. llms/extensions/app/db.py +536 -0
  8. {llms_py-2.0.9.data/data → llms/extensions/app}/ui/Recents.mjs +100 -73
  9. llms_py-2.0.9.data/data/ui/Sidebar.mjs → llms/extensions/app/ui/index.mjs +150 -79
  10. llms/extensions/app/ui/threadStore.mjs +433 -0
  11. llms/extensions/core_tools/CALCULATOR.md +32 -0
  12. llms/extensions/core_tools/__init__.py +637 -0
  13. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
  14. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
  15. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
  16. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
  17. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
  18. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
  19. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
  20. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
  21. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
  22. llms/extensions/core_tools/ui/codemirror/codemirror.css +344 -0
  23. llms/extensions/core_tools/ui/codemirror/codemirror.js +9884 -0
  24. llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
  25. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  26. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
  27. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
  28. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
  29. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
  30. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
  31. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
  32. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
  33. llms/extensions/core_tools/ui/index.mjs +650 -0
  34. llms/extensions/gallery/README.md +61 -0
  35. llms/extensions/gallery/__init__.py +63 -0
  36. llms/extensions/gallery/db.py +243 -0
  37. llms/extensions/gallery/ui/index.mjs +482 -0
  38. llms/extensions/katex/README.md +39 -0
  39. llms/extensions/katex/__init__.py +6 -0
  40. llms/extensions/katex/ui/README.md +125 -0
  41. llms/extensions/katex/ui/contrib/auto-render.js +338 -0
  42. llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
  43. llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
  44. llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
  45. llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
  46. llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
  47. llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
  48. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
  49. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
  50. llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
  51. llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
  52. llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
  53. llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
  54. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
  55. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
  56. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  57. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  58. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  59. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  60. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  61. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  62. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  63. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  64. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  65. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  66. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  67. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  68. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  69. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  70. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  71. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  72. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  73. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  74. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  75. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  76. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  77. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  78. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  79. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  80. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  81. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  82. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  83. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  84. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  85. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  86. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  87. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  88. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  89. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  90. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  91. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  92. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  93. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  94. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  95. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  96. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  97. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  98. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  99. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  100. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  101. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  102. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  103. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  104. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  115. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  116. llms/extensions/katex/ui/index.mjs +92 -0
  117. llms/extensions/katex/ui/katex-swap.css +1230 -0
  118. llms/extensions/katex/ui/katex-swap.min.css +1 -0
  119. llms/extensions/katex/ui/katex.css +1230 -0
  120. llms/extensions/katex/ui/katex.js +19080 -0
  121. llms/extensions/katex/ui/katex.min.css +1 -0
  122. llms/extensions/katex/ui/katex.min.js +1 -0
  123. llms/extensions/katex/ui/katex.min.mjs +1 -0
  124. llms/extensions/katex/ui/katex.mjs +18547 -0
  125. llms/extensions/providers/__init__.py +22 -0
  126. llms/extensions/providers/anthropic.py +233 -0
  127. llms/extensions/providers/cerebras.py +37 -0
  128. llms/extensions/providers/chutes.py +153 -0
  129. llms/extensions/providers/google.py +481 -0
  130. llms/extensions/providers/nvidia.py +103 -0
  131. llms/extensions/providers/openai.py +154 -0
  132. llms/extensions/providers/openrouter.py +74 -0
  133. llms/extensions/providers/zai.py +182 -0
  134. llms/extensions/system_prompts/README.md +22 -0
  135. llms/extensions/system_prompts/__init__.py +45 -0
  136. llms/extensions/system_prompts/ui/index.mjs +280 -0
  137. llms/extensions/system_prompts/ui/prompts.json +1067 -0
  138. llms/extensions/tools/__init__.py +144 -0
  139. llms/extensions/tools/ui/index.mjs +706 -0
  140. llms/index.html +58 -0
  141. llms/llms.json +400 -0
  142. llms/main.py +4407 -0
  143. llms/providers-extra.json +394 -0
  144. llms/providers.json +1 -0
  145. llms/ui/App.mjs +188 -0
  146. llms/ui/ai.mjs +217 -0
  147. llms/ui/app.css +7081 -0
  148. llms/ui/ctx.mjs +412 -0
  149. llms/ui/index.mjs +131 -0
  150. llms/ui/lib/chart.js +14 -0
  151. llms/ui/lib/charts.mjs +16 -0
  152. llms/ui/lib/color.js +14 -0
  153. llms/ui/lib/servicestack-vue.mjs +37 -0
  154. llms/ui/lib/vue.min.mjs +13 -0
  155. llms/ui/lib/vue.mjs +18530 -0
  156. {llms_py-2.0.9.data/data → llms}/ui/markdown.mjs +33 -15
  157. llms/ui/modules/chat/ChatBody.mjs +976 -0
  158. llms/ui/modules/chat/SettingsDialog.mjs +374 -0
  159. llms/ui/modules/chat/index.mjs +991 -0
  160. llms/ui/modules/icons.mjs +46 -0
  161. llms/ui/modules/layout.mjs +271 -0
  162. llms/ui/modules/model-selector.mjs +811 -0
  163. llms/ui/tailwind.input.css +742 -0
  164. {llms_py-2.0.9.data/data → llms}/ui/typography.css +133 -7
  165. llms/ui/utils.mjs +261 -0
  166. llms_py-3.0.10.dist-info/METADATA +49 -0
  167. llms_py-3.0.10.dist-info/RECORD +177 -0
  168. llms_py-3.0.10.dist-info/entry_points.txt +2 -0
  169. {llms_py-2.0.9.dist-info → llms_py-3.0.10.dist-info}/licenses/LICENSE +1 -2
  170. llms.py +0 -1402
  171. llms_py-2.0.9.data/data/index.html +0 -64
  172. llms_py-2.0.9.data/data/llms.json +0 -447
  173. llms_py-2.0.9.data/data/requirements.txt +0 -1
  174. llms_py-2.0.9.data/data/ui/App.mjs +0 -20
  175. llms_py-2.0.9.data/data/ui/ChatPrompt.mjs +0 -389
  176. llms_py-2.0.9.data/data/ui/Main.mjs +0 -680
  177. llms_py-2.0.9.data/data/ui/app.css +0 -3951
  178. llms_py-2.0.9.data/data/ui/lib/servicestack-vue.min.mjs +0 -37
  179. llms_py-2.0.9.data/data/ui/lib/vue.min.mjs +0 -12
  180. llms_py-2.0.9.data/data/ui/tailwind.input.css +0 -261
  181. llms_py-2.0.9.data/data/ui/threadStore.mjs +0 -273
  182. llms_py-2.0.9.data/data/ui/utils.mjs +0 -114
  183. llms_py-2.0.9.data/data/ui.json +0 -1069
  184. llms_py-2.0.9.dist-info/METADATA +0 -941
  185. llms_py-2.0.9.dist-info/RECORD +0 -30
  186. llms_py-2.0.9.dist-info/entry_points.txt +0 -2
  187. {llms_py-2.0.9.data/data → llms}/ui/fav.svg +0 -0
  188. {llms_py-2.0.9.data/data → llms}/ui/lib/highlight.min.mjs +0 -0
  189. {llms_py-2.0.9.data/data → llms}/ui/lib/idb.min.mjs +0 -0
  190. {llms_py-2.0.9.data/data → llms}/ui/lib/marked.min.mjs +0 -0
  191. /llms_py-2.0.9.data/data/ui/lib/servicestack-client.min.mjs → /llms/ui/lib/servicestack-client.mjs +0 -0
  192. {llms_py-2.0.9.data/data → llms}/ui/lib/vue-router.min.mjs +0 -0
  193. {llms_py-2.0.9.dist-info → llms_py-3.0.10.dist-info}/WHEEL +0 -0
  194. {llms_py-2.0.9.dist-info → llms_py-3.0.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,433 @@
1
+ import { ref, computed } from 'vue'
2
+ import { appendQueryString } from '@servicestack/client'
3
+
4
+ /**
5
+ * Returns an ever-increasing unique integer id.
6
+ */
7
+ export const nextId = (() => {
8
+ let last = 0 // cache of the last id that was handed out
9
+ return () => {
10
+ const now = Date.now() // current millisecond timestamp
11
+ last = (now > last) ? now : last + 1
12
+ return last
13
+ }
14
+ })();
15
+
16
+
17
+ const threads = ref([])
18
+ const threadDetails = ref({})
19
+ const currentThread = ref(null)
20
+ const isLoading = ref(false)
21
+
22
+ let ctx = null
23
+ let ext = null
24
+
25
+ function setError(error, msg = null) {
26
+ ctx?.setError(error, msg)
27
+ }
28
+
29
+ async function query(query) {
30
+ return (await ext.getJson(appendQueryString(`/threads`, query))).response || []
31
+ }
32
+
33
+ let watchThreadTimeout = ref(null)
34
+ async function watchThreadUpdates() {
35
+ const thread = currentThread.value
36
+ // console.debug('watchThreadUpdates', thread?.id, thread?.messages?.length, thread?.completedAt)
37
+ if (thread && thread?.messages?.length && !thread.completedAt) {
38
+ const api = await ext.getJson(appendQueryString(`/threads/${thread.id}/updates`, { after: thread.updatedAt }))
39
+ // console.log('watchThreadUpdates', api)
40
+ if (api.response) {
41
+ replaceThread(api.response)
42
+ return
43
+ } else {
44
+ setError(api.error, `watching thread ${thread.id}`)
45
+ }
46
+ }
47
+ stopWatchingThread()
48
+ }
49
+
50
+ function startWatchingThread() {
51
+ stopWatchingThread()
52
+ const thread = currentThread.value
53
+ if (thread && thread?.messages?.length && !thread.completedAt) {
54
+ watchThreadTimeout.value = setTimeout(watchThreadUpdates, 100)
55
+ }
56
+ }
57
+
58
+ function stopWatchingThread() {
59
+ console.debug('stopWatchingThread')
60
+ if (watchThreadTimeout.value) {
61
+ clearTimeout(watchThreadTimeout.value)
62
+ }
63
+ watchThreadTimeout.value = null
64
+ }
65
+
66
+ const isWatchingThread = computed(() => watchThreadTimeout.value != null)
67
+
68
+ async function cancelThread() {
69
+ console.log('cancelThread')
70
+ stopWatchingThread()
71
+ const thread = currentThread.value
72
+ if (!thread) return
73
+ const api = await ext.postJson(`/threads/${thread.id}/cancel`)
74
+ if (api.response) {
75
+ replaceThread(api.response)
76
+ } else {
77
+ setError(api.error, `Canceling thread ${thread.id}`)
78
+ }
79
+ }
80
+
81
+ // Create a new thread
82
+ async function createThread(args = {}) {
83
+ const thread = {
84
+ messages: [],
85
+ ...args
86
+ }
87
+ if (!thread.title) {
88
+ thread.title = 'New Chat'
89
+ }
90
+ if (thread.title.length > 200) {
91
+ thread.title = thread.title.slice(0, 200) + '...'
92
+ }
93
+
94
+ ctx.createThreadFilters.forEach(f => f(thread))
95
+
96
+ const api = await ext.postJson("/threads", thread)
97
+ if (api.response) {
98
+ threads.value.unshift(api.response)
99
+ return api.response
100
+ } else {
101
+ setError(api.error, `Creating thread ${thread.title}`)
102
+ }
103
+
104
+ return thread
105
+ }
106
+
107
+ function replaceThread(thread) {
108
+ if (!thread) {
109
+ console.error('replaceThread(null)')
110
+ return
111
+ }
112
+ const index = threads.value.findIndex(t => t.id === thread.id)
113
+ if (index !== -1) {
114
+ threads.value[index] = thread
115
+ }
116
+ if (currentThread.value?.id === thread.id) {
117
+ currentThread.value = thread
118
+ }
119
+ if (thread.completedAt || thread.error) {
120
+ threadDetails.value[thread.id] = thread
121
+ }
122
+ startWatchingThread()
123
+ return thread
124
+ }
125
+
126
+ // Update thread
127
+ async function updateThread(threadId, updates) {
128
+
129
+ if (!threadId)
130
+ throw new Error('threadId is required')
131
+
132
+ ctx.updateThreadFilters.forEach(f => f(updates))
133
+
134
+ const api = await ext.patchJson(`/threads/${threadId}`, updates)
135
+ if (api.response) {
136
+ return replaceThread(api.response)
137
+ } else {
138
+ setError(api.error, `Updating thread ${threadId}`)
139
+ }
140
+ }
141
+
142
+ async function deleteMessageFromThread(threadId, timestamp) {
143
+ const thread = await getThread(threadId)
144
+ if (!thread) throw new Error('Thread not found')
145
+ const updatedMessages = thread.messages.filter(m => m.timestamp !== timestamp)
146
+ console.log('deleteMessageFromThread', threadId, timestamp, updatedMessages)
147
+ await updateThread(threadId, { messages: updatedMessages })
148
+ }
149
+
150
+ async function updateMessageInThread(threadId, messageId, updates) {
151
+ const thread = await getThread(threadId)
152
+ if (!thread) throw new Error('Thread not found')
153
+
154
+ const messageIndex = thread.messages.findIndex(m => m.timestamp === messageId)
155
+ if (messageIndex === -1) throw new Error('Message not found')
156
+
157
+ const updatedMessages = [...thread.messages]
158
+ updatedMessages[messageIndex] = {
159
+ ...updatedMessages[messageIndex],
160
+ ...updates
161
+ }
162
+
163
+ await updateThread(threadId, { messages: updatedMessages })
164
+ }
165
+
166
+ async function redoMessageFromThread(threadId, timestamp) {
167
+ const thread = await getThread(threadId)
168
+ if (!thread) throw new Error('Thread not found')
169
+
170
+ // Find the index of the message to redo
171
+ const messageIndex = thread.messages.findIndex(m => m.timestamp === timestamp)
172
+ if (messageIndex === -1) {
173
+ setError({ message: `Message not found for timestamp ${timestamp}` })
174
+ return
175
+ }
176
+
177
+ // setError({
178
+ // errorCode: 'TestError',
179
+ // message: `Error redoing message ${timestamp} in thread ${threadId}`,
180
+ // stackTrace: `Error in page.mjs
181
+ // at Line 1
182
+ // at Line 2
183
+ // at Line 3`,
184
+ // })
185
+ // return
186
+
187
+ // Keep only messages up to and including the target message
188
+ const updatedMessages = thread.messages.slice(0, messageIndex + 1)
189
+
190
+ // Update the thread with the new messages
191
+ const request = { messages: updatedMessages }
192
+ const api = await queueChat({ request, thread })
193
+ if (api.response) {
194
+ replaceThread(api.response)
195
+ } else {
196
+ setError(api.error, `Redoing message ${timestamp} in thread ${threadId}`)
197
+ }
198
+ }
199
+
200
+ async function loadThreads() {
201
+ isLoading.value = true
202
+
203
+ try {
204
+ const api = await ext.getJson('/threads?take=30')
205
+ threads.value = api.response || []
206
+ return threads.value
207
+ } finally {
208
+ isLoading.value = false
209
+ }
210
+ }
211
+
212
+ async function getThread(threadId) {
213
+ const cachedThread = threads.value.find(t => t.id == threadId)
214
+ if (cachedThread) return cachedThread
215
+ const api = await ext.getJson(`/threads?id=${threadId}`)
216
+ return api.response && api.response[0] || null
217
+ }
218
+
219
+ // Delete thread
220
+ async function deleteThread(threadId) {
221
+ await ext.delete(`/threads/${threadId}`)
222
+
223
+ threads.value = threads.value.filter(t => t.id !== threadId)
224
+
225
+ if (currentThread.value?.id === threadId) {
226
+ currentThread.value = null
227
+ }
228
+ }
229
+
230
+ // Set current thread
231
+ async function setCurrentThread(threadId) {
232
+ const thread = await getThread(threadId)
233
+ if (thread) {
234
+ currentThread.value = thread
235
+ startWatchingThread()
236
+ }
237
+ return thread
238
+ }
239
+
240
+ // Set current thread from router params (router-aware version)
241
+ async function setCurrentThreadFromRoute(threadId, router) {
242
+ if (!threadId) {
243
+ currentThread.value = null
244
+ return null
245
+ }
246
+
247
+ loadThreadDetails(threadId)
248
+ const thread = setCurrentThread(threadId)
249
+ if (thread) {
250
+ return thread
251
+ } else {
252
+ // Thread not found, redirect to home
253
+ if (router) {
254
+ router.push((globalThis.ai?.base || '') + '/')
255
+ }
256
+ currentThread.value = null
257
+ return null
258
+ }
259
+ }
260
+
261
+ // Clear current thread (go back to initial state)
262
+ function clearCurrentThread() {
263
+ currentThread.value = null
264
+ }
265
+
266
+ function getGroupedThreads(total) {
267
+ const now = new Date()
268
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
269
+ const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000)
270
+ const lastWeek = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000)
271
+ const lastMonth = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000)
272
+
273
+ const groups = {
274
+ today: [],
275
+ yesterday: [],
276
+ lastWeek: [],
277
+ lastMonth: [],
278
+ older: {}
279
+ }
280
+
281
+ const takeThreads = threads.value.slice(0, total)
282
+
283
+ takeThreads.forEach(thread => {
284
+ const threadDate = new Date(thread.updatedAt)
285
+
286
+ if (threadDate >= today) {
287
+ groups.today.push(thread)
288
+ } else if (threadDate >= yesterday) {
289
+ groups.yesterday.push(thread)
290
+ } else if (threadDate >= lastWeek) {
291
+ groups.lastWeek.push(thread)
292
+ } else if (threadDate >= lastMonth) {
293
+ groups.lastMonth.push(thread)
294
+ } else {
295
+ const year = threadDate.getFullYear()
296
+ const month = threadDate.toLocaleString('default', { month: 'long' })
297
+ const key = `${month} ${year}`
298
+
299
+ if (!groups.older[key]) {
300
+ groups.older[key] = []
301
+ }
302
+ groups.older[key].push(thread)
303
+ }
304
+ })
305
+
306
+ return groups
307
+ }
308
+
309
+ // Group threads by time periods
310
+ const groupedThreads = computed(() => getGroupedThreads(threads.value.length))
311
+
312
+ function getLatestCachedThread() {
313
+ return threads.value[0]
314
+ }
315
+
316
+ async function startNewThread({ title, model, tools, redirect }) {
317
+ if (!model) {
318
+ console.error('No model selected')
319
+ return
320
+ }
321
+ if (!title) {
322
+ title = 'New Chat'
323
+ }
324
+ const latestThread = getLatestCachedThread()
325
+
326
+ console.log('startNewThread', title, model.name, ctx.router.currentRoute.value?.path, latestThread?.messages?.length)
327
+ ctx.setLayout({ left: 'ThreadsSidebar' })
328
+
329
+ if (latestThread && latestThread.title == title && !latestThread.messages?.length) {
330
+ if (ctx.router.currentRoute.value?.path != `/c/${latestThread.id}`) {
331
+ ctx.to(`/c/${latestThread.id}`)
332
+ }
333
+ return latestThread
334
+ }
335
+ const newThread = await createThread({
336
+ title,
337
+ model: model.name,
338
+ info: ctx.utils.toModelInfo(model),
339
+ tools,
340
+ })
341
+
342
+ console.log('newThread', newThread, model)
343
+ if (redirect) {
344
+ // Navigate to the new thread URL
345
+ ctx.to(`/c/${newThread.id}`)
346
+ }
347
+
348
+ // Get the thread to check for duplicates
349
+ let thread = await getThread(newThread.id)
350
+ console.log('thread', thread)
351
+ return thread
352
+ }
353
+
354
+ async function queueChat(ctxRequest, options = {}) {
355
+ if (!ctxRequest.request) return ctx.createErrorResult({ message: 'No request provided' })
356
+ if (!ctxRequest.thread) return ctx.createErrorResult({ message: 'No thread provided' })
357
+ if (!ctxRequest.request.metadata) {
358
+ ctxRequest.request.metadata = {}
359
+ }
360
+ ctx.chatRequestFilters.forEach(f => f(ctxRequest))
361
+ const { thread, request } = ctxRequest
362
+ const api = await ctx.postJson(`/ext/app/threads/${thread.id}/chat`, {
363
+ ...options,
364
+ body: typeof request == 'string'
365
+ ? request
366
+ : JSON.stringify(request),
367
+ })
368
+ return api
369
+ }
370
+
371
+ async function loadThreadDetails(id, opt = null) {
372
+ if (!threadDetails.value[id] || opt?.force) {
373
+ const api = await ctx.getJson(`/ext/app/threads/${id}`)
374
+ if (api.response) {
375
+ threadDetails.value[id] = api.response
376
+ }
377
+ if (api.error) {
378
+ console.error(api.error)
379
+ }
380
+ }
381
+ return threadDetails.value[id]
382
+ }
383
+
384
+ // Export the store
385
+ export function useThreadStore() {
386
+ return {
387
+ // State
388
+ threads,
389
+ currentThread,
390
+ isLoading,
391
+ groupedThreads,
392
+
393
+ // Actions
394
+ query,
395
+ createThread,
396
+ updateThread,
397
+ deleteMessageFromThread,
398
+ updateMessageInThread,
399
+ redoMessageFromThread,
400
+ loadThreads,
401
+ getThread,
402
+ deleteThread,
403
+ setCurrentThread,
404
+ setCurrentThreadFromRoute,
405
+ clearCurrentThread,
406
+ getGroupedThreads,
407
+ getLatestCachedThread,
408
+ startNewThread,
409
+ replaceThread,
410
+ queueChat,
411
+ threadDetails,
412
+ loadThreadDetails,
413
+ isWatchingThread,
414
+ startWatchingThread,
415
+ stopWatchingThread,
416
+ cancelThread,
417
+ get watchingThread() {
418
+ return isWatchingThread.value
419
+ },
420
+ }
421
+ }
422
+
423
+ export default {
424
+ install(context) {
425
+ ctx = context
426
+ ext = ctx.scope('app')
427
+ ctx.setGlobals({ threads: useThreadStore() })
428
+ },
429
+
430
+ async load() {
431
+ await ctx.threads.loadThreads()
432
+ }
433
+ }
@@ -0,0 +1,32 @@
1
+ # Calculator
2
+
3
+ A powerful and safe mathematical expression evaluator with a rich web interface.
4
+
5
+ ## Features
6
+
7
+ ### 🖥️ UX Friendly Interface
8
+ Experience a clean, modern interface designed for efficiency. The UI is fully responsive and supports dark mode, seamlessly integrating with the rest of the application.
9
+
10
+ ### 💾 Persistent History
11
+ Never lose track of your calculations. The Calculator automatically saves your history to `localStorage`, ensuring your previous expressions and results are preserved between sessions.
12
+
13
+ ### ⚡ 1-Click Interaction
14
+ Streamline your workflow with interactive history items:
15
+ - **Load & Copy**: Click on any past expression or answer to instantly load it into the input field and copy it to your clipboard.
16
+ - **Visual Feedback**: Temporary checkmarks confirm successful copy actions.
17
+
18
+ ### ⌨️ Keyboard-Free Access
19
+ While full keyboard support is available, you can perform complex calculations entirely via the UI:
20
+ - **Numbers & Constants**: Quick access to digits and mathematical constants like `pi`, `e`, `inf`.
21
+ - **Operators**: Comprehensive set of buttons for arithmetic (`+`, `-`, `*`, `/`, `%`, `^`) and boolean logic (`and`, `or`, `not`).
22
+ - **Functions**: One-click insertion or wrapping of selection for all supported math functions.
23
+
24
+ ### 🐍 Python Math Support
25
+ Unlock the power of Python's math library directly in the browser.
26
+ - **Math Functions**: Support for `sin`, `cos`, `tan`, `sqrt`, `log`, `factorial`, and many more.
27
+ - **Statistics**: Built-in functions for `mean`, `median`, `stdev`, and `variance`.
28
+
29
+ ### 🛡️ Safe Evaluation
30
+ Security is a priority. Instead of using Python's unsafe `eval()`, the Calculator uses a robust **AST (Abstract Syntax Tree) evaluator**.
31
+ - **Restricted Environment**: Only allowed mathematical operations and functions are executed.
32
+ - **No Side Effects**: Prevents arbitrary code execution, making it safe to evaluate expressions from untrusted sources.