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
@@ -0,0 +1,440 @@
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
+ ctxRequest = ctx.createChatContext(ctxRequest)
358
+ ctx.chatRequestFilters.forEach(f => f(ctxRequest))
359
+ const { thread, request } = ctxRequest
360
+ ctx.completeChatContext(ctxRequest)
361
+
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
+ function getCurrentThreadSystemPrompt() {
385
+ return currentThread.value?.systemPrompt
386
+ ?? currentThread.value?.messages?.find(m => m.role == 'system')?.content
387
+ ?? ''
388
+ }
389
+
390
+ // Export the store
391
+ export function useThreadStore() {
392
+ return {
393
+ // State
394
+ threads,
395
+ currentThread,
396
+ isLoading,
397
+ groupedThreads,
398
+
399
+ // Actions
400
+ getCurrentThreadSystemPrompt,
401
+ query,
402
+ createThread,
403
+ updateThread,
404
+ deleteMessageFromThread,
405
+ updateMessageInThread,
406
+ redoMessageFromThread,
407
+ loadThreads,
408
+ getThread,
409
+ deleteThread,
410
+ setCurrentThread,
411
+ setCurrentThreadFromRoute,
412
+ clearCurrentThread,
413
+ getGroupedThreads,
414
+ getLatestCachedThread,
415
+ startNewThread,
416
+ replaceThread,
417
+ queueChat,
418
+ threadDetails,
419
+ loadThreadDetails,
420
+ isWatchingThread,
421
+ startWatchingThread,
422
+ stopWatchingThread,
423
+ cancelThread,
424
+ get watchingThread() {
425
+ return isWatchingThread.value
426
+ },
427
+ }
428
+ }
429
+
430
+ export default {
431
+ install(context) {
432
+ ctx = context
433
+ ext = ctx.scope('app')
434
+ ctx.setGlobals({ threads: useThreadStore() })
435
+ },
436
+
437
+ async load() {
438
+ await ctx.threads.loadThreads()
439
+ }
440
+ }
@@ -0,0 +1,96 @@
1
+ # Computer Use Tools
2
+
3
+ This extension provides a set of tools that allow an Agent to interact with a computer environment in a way similar to a human user. It includes capabilities for screen interaction (mouse/keyboard), shell execution, and file editing. Based on [Anthropic's computer use tools](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo).
4
+
5
+ ## Available Tools
6
+
7
+ ### 1. Computer Tool (`computer`)
8
+ Allows interaction with the screen, keyboard, and mouse.
9
+
10
+ **Capabilities:**
11
+ - **Mouse Interaction**: Move cursor, click (left, right, middle, double, triple), click & drag.
12
+ - **Keyboard Interaction**: Type text, press specific keys or key combinations.
13
+ - **Screen**: Take screenshots, get cursor position.
14
+ - **Zooming**: Zoom into specific regions of the screen (Action: `zoom`).
15
+
16
+ **Key Parameters:**
17
+ - `action`: The action to perform (e.g., `mouse_move`, `left_click`, `type`, `screenshot`, `zoom`).
18
+ - `coordinate`: `(x, y)` coordinates for mouse actions.
19
+ - `text`: Text to type.
20
+ - `key`: Key sequence to press (e.g., `Return`, `Control+c`).
21
+ - `region`: `(x0, y0, x1, y1)` region for zooming.
22
+
23
+ ### 2. Bash Tool (`bash`)
24
+ Provides a persistent shell session to execute command-line instructions.
25
+
26
+ **Capabilities:**
27
+ - **Execute Commands**: Run any bash command.
28
+ - **Persistent Session**: State (like environment variables, working directory) is preserved between calls within the same session.
29
+ - **Process Management**: Can restart the session if needed.
30
+ - **Open Files/URLs**: Helper function `open` allows opening files or URLs using the system's default handler (`xdg-open`, `open`, or `start`).
31
+
32
+ **Key Parameters:**
33
+ - `command`: The bash command to execute.
34
+ - `restart`: Boolean to restart the session.
35
+
36
+ ### 3. Edit Tool (`str_replace_editor`)
37
+ A filesystem editor for viewing and modifying files.
38
+
39
+ **Capabilities:**
40
+ - **View**: Read file contents or list directories.
41
+ - **Create**: Create new files with content.
42
+ - **String Replace**: Replace unique strings in a file (robust for LLM editing).
43
+ - **Insert**: Insert text at specific line numbers.
44
+ - **Undo**: Undo the last edit to a file.
45
+
46
+ **Key Parameters:**
47
+ - `command`: The edit command (`view`, `create`, `str_replace`, `insert`, `undo_edit`).
48
+ - `path`: Absolute path to the file or directory.
49
+ - `file_text`: Content for file creation.
50
+ - `old_str` / `new_str`: Strings for replacement.
51
+
52
+ ---
53
+
54
+ ## Capabilities & Workflows
55
+
56
+ These tools are designed to work together to enable complex end-to-end tasks. An Agent can act as a developer, tester, or general user.
57
+
58
+ ### Example: "Build a Tetris web app in a tetris folder, open it then take a screenshot"
59
+
60
+ To achieve this high-level task, the Agent would sequence the tools as follows:
61
+
62
+ 1. **Create the Project Structure**
63
+ * **Tool**: `bash`
64
+ * **Command**: `mkdir -p tetris`
65
+ * *Result*: Creates the folder.
66
+
67
+ 2. **Create the Application Files**
68
+ * **Tool**: `edit` (command: `create`)
69
+ * **Path**: `/path/to/tetris/index.html`
70
+ * **Content**: (HTML code for Tetris game)
71
+ * *Result*: Writes the HTML file.
72
+
73
+ 3. **Open the Application**
74
+ * **Tool**: `bash` (via helper `open`) or `bash` directly.
75
+ * **Command**: `xdg-open /path/to/tetris/index.html` (Linux) or just `python -m http.server` and open localhost.
76
+ * *Result*: Opens the file in the default web browser.
77
+
78
+ 4. **Wait & Verify**
79
+ * **Tool**: `computer`
80
+ * **Action**: `wait` or `screenshot` to see if it loaded.
81
+
82
+ 5. **Take a Screenshot**
83
+ * **Tool**: `computer`
84
+ * **Action**: `screenshot`
85
+ * *Result*: Captures the visual state of the running Tetris app for the user to see.
86
+
87
+ ### How it handles the "Build a Tetris..." request:
88
+ When a user gives the command:
89
+ > "Build a Tetris web app in a tetris folder, open it then take a screenshot"
90
+
91
+ The Agent decomposes this into:
92
+ 1. **"Build... in a tetris folder"** -> Uses `bash` to make the directory and `edit` to write the `index.html` / `style.css` / `script.js` files.
93
+ 2. **"Open it"** -> Uses `bash` to run a server or open the file in a browser.
94
+ 3. **"Take a screenshot"** -> Uses `computer` to verify the visual output.
95
+
96
+ This combination allows the Agent to not just generate code, but **verify** it visually and interactively, closing the loop on development tasks.
@@ -0,0 +1,59 @@
1
+ """
2
+ Anthropic's Computer Use Tools
3
+ https://github.com/anthropics/claude-quickstarts/tree/main/computer-use-demo
4
+ """
5
+
6
+ import os
7
+
8
+ from .bash import open, run_bash
9
+ from .computer import computer
10
+ from .edit import edit
11
+ from .filesystem import (
12
+ create_directory,
13
+ directory_tree,
14
+ edit_file,
15
+ filesystem_init,
16
+ get_file_info,
17
+ list_allowed_directories,
18
+ list_directory,
19
+ list_directory_with_sizes,
20
+ move_file,
21
+ read_media_file,
22
+ read_multiple_files,
23
+ read_text_file,
24
+ search_files,
25
+ write_file,
26
+ )
27
+ from .platform import get_display_num, get_screen_resolution
28
+
29
+ width, height = get_screen_resolution()
30
+ # set enviroment variables
31
+ os.environ["WIDTH"] = str(width)
32
+ os.environ["HEIGHT"] = str(height)
33
+ os.environ["DISPLAY_NUM"] = str(get_display_num())
34
+
35
+
36
+ def install(ctx):
37
+ filesystem_init(ctx)
38
+
39
+ ctx.register_tool(run_bash, group="computer")
40
+ ctx.register_tool(open, group="computer")
41
+ ctx.register_tool(edit, group="computer")
42
+ ctx.register_tool(computer, group="computer")
43
+
44
+ ctx.register_tool(read_text_file, group="filesystem")
45
+ ctx.register_tool(read_media_file, group="filesystem")
46
+ ctx.register_tool(read_multiple_files, group="filesystem")
47
+ ctx.register_tool(write_file, group="filesystem")
48
+ ctx.register_tool(edit_file, group="filesystem")
49
+ ctx.register_tool(create_directory, group="filesystem")
50
+ ctx.register_tool(list_directory, group="filesystem")
51
+ ctx.register_tool(list_directory_with_sizes, group="filesystem")
52
+ ctx.register_tool(directory_tree, group="filesystem")
53
+ ctx.register_tool(move_file, group="filesystem")
54
+ ctx.register_tool(search_files, group="filesystem")
55
+ ctx.register_tool(get_file_info, group="filesystem")
56
+ ctx.register_tool(list_allowed_directories, group="filesystem")
57
+
58
+
59
+ __install__ = install
@@ -0,0 +1,80 @@
1
+ from abc import ABCMeta, abstractmethod
2
+ from dataclasses import dataclass, fields, replace
3
+ from typing import Any
4
+
5
+
6
+ class BaseTool(metaclass=ABCMeta):
7
+ """Abstract base class"""
8
+
9
+ @abstractmethod
10
+ def __call__(self, **kwargs) -> Any:
11
+ """Executes the tool with the given arguments."""
12
+ ...
13
+
14
+ @abstractmethod
15
+ def to_params(
16
+ self,
17
+ ) -> Any:
18
+ raise NotImplementedError
19
+
20
+
21
+ @dataclass(kw_only=True, frozen=True)
22
+ class ToolResult:
23
+ """Represents the result of a tool execution."""
24
+
25
+ output: str | None = None
26
+ error: str | None = None
27
+ base64_image: str | None = None
28
+ system: str | None = None
29
+
30
+ def __bool__(self):
31
+ return any(getattr(self, field.name) for field in fields(self))
32
+
33
+ def __add__(self, other: "ToolResult"):
34
+ def combine_fields(field: str | None, other_field: str | None, concatenate: bool = True):
35
+ if field and other_field:
36
+ if concatenate:
37
+ return field + other_field
38
+ raise ValueError("Cannot combine tool results")
39
+ return field or other_field
40
+
41
+ return ToolResult(
42
+ output=combine_fields(self.output, other.output),
43
+ error=combine_fields(self.error, other.error),
44
+ base64_image=combine_fields(self.base64_image, other.base64_image, False),
45
+ system=combine_fields(self.system, other.system),
46
+ )
47
+
48
+ def replace(self, **kwargs):
49
+ """Returns a new ToolResult with the given fields replaced."""
50
+ return replace(self, **kwargs)
51
+
52
+ def to_tool_results(self) -> list[dict[str, Any]]:
53
+ text = ""
54
+ if self.output:
55
+ text += f"{self.output}\n"
56
+ if self.error:
57
+ text += f"stderr: {self.error}\n"
58
+ if self.system:
59
+ text += f"system: {self.system}\n"
60
+ ret = []
61
+ if text:
62
+ ret.append({"type": "text", "text": text})
63
+ if self.base64_image:
64
+ ret.append({"type": "image", "format": "png", "data": self.base64_image})
65
+ return ret
66
+
67
+
68
+ class CLIResult(ToolResult):
69
+ """A ToolResult that can be rendered as a CLI output."""
70
+
71
+
72
+ class ToolFailure(ToolResult):
73
+ """A ToolResult that represents a failure."""
74
+
75
+
76
+ class ToolError(Exception):
77
+ """Raised when a tool encounters an error."""
78
+
79
+ def __init__(self, message):
80
+ self.message = message