llms-py 2.0.34__py3-none-any.whl → 3.0.0__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/__pycache__/__init__.cpython-312.pyc +0 -0
  3. llms/__pycache__/__init__.cpython-313.pyc +0 -0
  4. llms/__pycache__/__init__.cpython-314.pyc +0 -0
  5. llms/__pycache__/__main__.cpython-312.pyc +0 -0
  6. llms/__pycache__/__main__.cpython-314.pyc +0 -0
  7. llms/__pycache__/llms.cpython-312.pyc +0 -0
  8. llms/__pycache__/main.cpython-312.pyc +0 -0
  9. llms/__pycache__/main.cpython-313.pyc +0 -0
  10. llms/__pycache__/main.cpython-314.pyc +0 -0
  11. llms/__pycache__/plugins.cpython-314.pyc +0 -0
  12. llms/{ui/Analytics.mjs → extensions/analytics/ui/index.mjs} +154 -238
  13. llms/extensions/app/README.md +20 -0
  14. llms/extensions/app/__init__.py +530 -0
  15. llms/extensions/app/__pycache__/__init__.cpython-314.pyc +0 -0
  16. llms/extensions/app/__pycache__/db.cpython-314.pyc +0 -0
  17. llms/extensions/app/__pycache__/db_manager.cpython-314.pyc +0 -0
  18. llms/extensions/app/db.py +644 -0
  19. llms/extensions/app/db_manager.py +195 -0
  20. llms/extensions/app/requests.json +9073 -0
  21. llms/extensions/app/threads.json +15290 -0
  22. llms/{ui → extensions/app/ui}/Recents.mjs +91 -65
  23. llms/{ui/Sidebar.mjs → extensions/app/ui/index.mjs} +124 -58
  24. llms/extensions/app/ui/threadStore.mjs +411 -0
  25. llms/extensions/core_tools/CALCULATOR.md +32 -0
  26. llms/extensions/core_tools/__init__.py +598 -0
  27. llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
  28. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
  29. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
  30. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
  31. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
  32. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
  33. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
  34. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
  35. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
  36. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
  37. llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
  38. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  39. llms/extensions/core_tools/ui/codemirror/lib/codemirror.css +344 -0
  40. llms/extensions/core_tools/ui/codemirror/lib/codemirror.js +9884 -0
  41. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
  42. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
  43. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
  44. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
  45. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
  46. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
  47. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
  48. llms/extensions/core_tools/ui/index.mjs +650 -0
  49. llms/extensions/gallery/README.md +61 -0
  50. llms/extensions/gallery/__init__.py +61 -0
  51. llms/extensions/gallery/__pycache__/__init__.cpython-314.pyc +0 -0
  52. llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
  53. llms/extensions/gallery/db.py +298 -0
  54. llms/extensions/gallery/ui/index.mjs +482 -0
  55. llms/extensions/katex/README.md +39 -0
  56. llms/extensions/katex/__init__.py +6 -0
  57. llms/extensions/katex/__pycache__/__init__.cpython-314.pyc +0 -0
  58. llms/extensions/katex/ui/README.md +125 -0
  59. llms/extensions/katex/ui/contrib/auto-render.js +338 -0
  60. llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
  61. llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
  62. llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
  63. llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
  64. llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
  65. llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
  66. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
  67. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
  68. llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
  69. llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
  70. llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
  71. llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
  72. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
  73. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
  74. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  75. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  76. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  77. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  78. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  79. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  80. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  81. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  82. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  83. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  84. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  85. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  86. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  87. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  88. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  89. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  90. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  91. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  92. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  93. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  94. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  95. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  96. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  97. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  98. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  99. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  100. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  101. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  102. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  103. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  104. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  115. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  116. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  117. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  118. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  119. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  120. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  121. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  122. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  123. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  124. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  125. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  126. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  127. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  128. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  129. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  130. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  131. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  132. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  133. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  134. llms/extensions/katex/ui/index.mjs +92 -0
  135. llms/extensions/katex/ui/katex-swap.css +1230 -0
  136. llms/extensions/katex/ui/katex-swap.min.css +1 -0
  137. llms/extensions/katex/ui/katex.css +1230 -0
  138. llms/extensions/katex/ui/katex.js +19080 -0
  139. llms/extensions/katex/ui/katex.min.css +1 -0
  140. llms/extensions/katex/ui/katex.min.js +1 -0
  141. llms/extensions/katex/ui/katex.min.mjs +1 -0
  142. llms/extensions/katex/ui/katex.mjs +18547 -0
  143. llms/extensions/providers/__init__.py +18 -0
  144. llms/extensions/providers/__pycache__/__init__.cpython-314.pyc +0 -0
  145. llms/extensions/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
  146. llms/extensions/providers/__pycache__/chutes.cpython-314.pyc +0 -0
  147. llms/extensions/providers/__pycache__/google.cpython-314.pyc +0 -0
  148. llms/extensions/providers/__pycache__/nvidia.cpython-314.pyc +0 -0
  149. llms/extensions/providers/__pycache__/openai.cpython-314.pyc +0 -0
  150. llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
  151. llms/extensions/providers/anthropic.py +229 -0
  152. llms/extensions/providers/chutes.py +155 -0
  153. llms/extensions/providers/google.py +378 -0
  154. llms/extensions/providers/nvidia.py +105 -0
  155. llms/extensions/providers/openai.py +156 -0
  156. llms/extensions/providers/openrouter.py +72 -0
  157. llms/extensions/system_prompts/README.md +22 -0
  158. llms/extensions/system_prompts/__init__.py +45 -0
  159. llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc +0 -0
  160. llms/extensions/system_prompts/ui/index.mjs +280 -0
  161. llms/extensions/system_prompts/ui/prompts.json +1067 -0
  162. llms/extensions/tools/__init__.py +5 -0
  163. llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  164. llms/extensions/tools/ui/index.mjs +204 -0
  165. llms/index.html +35 -77
  166. llms/llms.json +357 -1186
  167. llms/main.py +2847 -999
  168. llms/providers-extra.json +356 -0
  169. llms/providers.json +1 -0
  170. llms/ui/App.mjs +151 -60
  171. llms/ui/ai.mjs +132 -60
  172. llms/ui/app.css +2173 -161
  173. llms/ui/ctx.mjs +365 -0
  174. llms/ui/index.mjs +129 -0
  175. llms/ui/lib/charts.mjs +9 -13
  176. llms/ui/lib/servicestack-vue.mjs +3 -3
  177. llms/ui/lib/vue.min.mjs +10 -9
  178. llms/ui/lib/vue.mjs +1796 -1635
  179. llms/ui/markdown.mjs +18 -7
  180. llms/ui/modules/chat/ChatBody.mjs +691 -0
  181. llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +9 -9
  182. llms/ui/modules/chat/index.mjs +828 -0
  183. llms/ui/modules/layout.mjs +243 -0
  184. llms/ui/modules/model-selector.mjs +851 -0
  185. llms/ui/tailwind.input.css +496 -80
  186. llms/ui/utils.mjs +161 -93
  187. {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/METADATA +1 -1
  188. llms_py-3.0.0.dist-info/RECORD +202 -0
  189. llms/ui/Avatar.mjs +0 -85
  190. llms/ui/Brand.mjs +0 -52
  191. llms/ui/ChatPrompt.mjs +0 -590
  192. llms/ui/Main.mjs +0 -823
  193. llms/ui/ModelSelector.mjs +0 -78
  194. llms/ui/OAuthSignIn.mjs +0 -92
  195. llms/ui/ProviderIcon.mjs +0 -30
  196. llms/ui/ProviderStatus.mjs +0 -105
  197. llms/ui/SignIn.mjs +0 -64
  198. llms/ui/SystemPromptEditor.mjs +0 -31
  199. llms/ui/SystemPromptSelector.mjs +0 -56
  200. llms/ui/Welcome.mjs +0 -8
  201. llms/ui/threadStore.mjs +0 -563
  202. llms/ui.json +0 -1069
  203. llms_py-2.0.34.dist-info/RECORD +0 -48
  204. {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/WHEEL +0 -0
  205. {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/entry_points.txt +0 -0
  206. {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/licenses/LICENSE +0 -0
  207. {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/top_level.txt +0 -0
llms/ui/threadStore.mjs DELETED
@@ -1,563 +0,0 @@
1
- import { ref, computed, unref } from 'vue'
2
- import { openDB } from 'idb'
3
- import { nextId, toModelInfo } from './utils.mjs'
4
-
5
- // Thread store for managing chat threads with IndexedDB
6
- const threads = ref([])
7
- const currentThread = ref(null)
8
- const isLoading = ref(false)
9
-
10
- let db = null
11
-
12
- // Initialize IndexedDB
13
- async function initDB() {
14
- if (db) return db
15
-
16
- db = await openDB('LlmsThreads', 3, {
17
- upgrade(db, _oldVersion, _newVersion, transaction) {
18
- if (!db.objectStoreNames.contains('threads')) {
19
- // Create threads store
20
- const threadStore = db.createObjectStore('threads', {
21
- keyPath: 'id',
22
- autoIncrement: false
23
- })
24
-
25
- // Create indexes for efficient querying
26
- threadStore.createIndex('createdAt', 'createdAt')
27
- threadStore.createIndex('updatedAt', 'updatedAt')
28
- threadStore.createIndex('title', 'title')
29
- }
30
- if (!db.objectStoreNames.contains('requests')) {
31
- // Create requests store
32
- const requestStore = db.createObjectStore('requests', {
33
- keyPath: 'id',
34
- autoIncrement: false
35
- })
36
- requestStore.createIndex('threadId', 'threadId')
37
- requestStore.createIndex('model', 'model')
38
- requestStore.createIndex('provider', 'provider')
39
- requestStore.createIndex('inputTokens', 'inputTokens')
40
- requestStore.createIndex('outputTokens', 'outputTokens')
41
- requestStore.createIndex('cost', 'cost')
42
- requestStore.createIndex('duration', 'duration')
43
- requestStore.createIndex('created', 'created')
44
- }
45
- }
46
- })
47
-
48
- return db
49
- }
50
-
51
- // Generate unique thread ID
52
- function generateThreadId() {
53
- return Date.now().toString()
54
- }
55
-
56
- async function logRequest(threadId, model, request, response) {
57
- await initDB()
58
- const metadata = response.metadata || {}
59
- const usage = response.usage || {}
60
- const [inputPrice, outputPrice] = metadata.pricing ? metadata.pricing.split('/') : [0, 0]
61
- const lastUserContent = request.messages?.slice().reverse().find(m => m.role === 'user')?.content
62
- const content = Array.isArray(lastUserContent)
63
- ? lastUserContent.filter(c => c?.text).map(c => c.text).join(' ')
64
- : lastUserContent
65
- const title = content.slice(0, 100) + (content.length > 100 ? '...' : '')
66
- const inputTokens = usage?.prompt_tokens ?? 0
67
- const outputTokens = usage?.completion_tokens ?? 0
68
- const inputCachedTokens = usage?.prompt_token_details?.cached_tokens ?? 0
69
- const finishReason = response.choices[0]?.finish_reason || 'unknown'
70
-
71
- const subtractDays = (date, days) => {
72
- const result = new Date(date * 1000)
73
- result.setDate(result.getDate() - days)
74
- return parseInt(result.valueOf() / 1000)
75
- }
76
-
77
- const log = {
78
- id: nextId(),
79
- threadId: threadId,
80
- model: model.id,
81
- provider: model.provider,
82
- providerModel: response.model || model.provider_model,
83
- title,
84
- inputTokens,
85
- outputTokens,
86
- inputCachedTokens,
87
- totalTokens: usage.total_tokens ?? (inputTokens + outputTokens),
88
- inputPrice,
89
- outputPrice,
90
- cost: (parseFloat(inputPrice) * inputTokens) + (parseFloat(outputPrice) * outputTokens),
91
- duration: parseInt(metadata.duration) || 0,
92
- created: response.created ?? Math.floor(Date.now() / 1000),
93
- finishReason,
94
- providerRef: response.provider,
95
- ref: response.id || undefined,
96
- usage: usage,
97
- }
98
- console.debug('logRequest', log)
99
- const tx = db.transaction(['requests'], 'readwrite')
100
- await tx.objectStore('requests').add(log)
101
- await tx.complete
102
- return log
103
- }
104
-
105
- // Create a new thread
106
- async function createThread(title = 'New Chat', model = null, systemPrompt = '') {
107
- await initDB()
108
-
109
- const thread = {
110
- id: generateThreadId(),
111
- title: title,
112
- model: model?.id ?? '',
113
- info: toModelInfo(model),
114
- systemPrompt: systemPrompt,
115
- messages: [],
116
- createdAt: new Date().toISOString(),
117
- updatedAt: new Date().toISOString()
118
- }
119
-
120
- const tx = db.transaction(['threads'], 'readwrite')
121
- await tx.objectStore('threads').add(thread)
122
- await tx.complete
123
-
124
- threads.value.unshift(thread)
125
- // Note: currentThread will be set by router navigation
126
-
127
- return thread
128
- }
129
-
130
- // Update thread
131
- async function updateThread(threadId, updates) {
132
- await initDB()
133
-
134
- const tx = db.transaction(['threads'], 'readwrite')
135
- const store = tx.objectStore('threads')
136
-
137
- const thread = await store.get(threadId)
138
- if (!thread) throw new Error('Thread not found')
139
-
140
- const updatedThread = {
141
- ...thread,
142
- ...updates,
143
- updatedAt: new Date().toISOString()
144
- }
145
-
146
- await store.put(updatedThread)
147
- await tx.complete
148
-
149
- // Update in memory
150
- const index = threads.value.findIndex(t => t.id === threadId)
151
- if (index !== -1) {
152
- threads.value[index] = updatedThread
153
- }
154
-
155
- if (currentThread.value?.id === threadId) {
156
- currentThread.value = updatedThread
157
- }
158
-
159
- return updatedThread
160
- }
161
-
162
- async function calculateThreadStats(threadId) {
163
- await initDB()
164
-
165
- const tx = db.transaction(['requests'], 'readonly')
166
- const store = tx.objectStore('requests')
167
- const index = store.index('threadId')
168
-
169
- const requests = await index.getAll(threadId)
170
-
171
- let inputTokens = 0
172
- let outputTokens = 0
173
- let cost = 0.0
174
- let duration = 0
175
-
176
- requests.forEach(req => {
177
- inputTokens += req.inputTokens || 0
178
- outputTokens += req.outputTokens || 0
179
- cost += req.cost || 0.0
180
- duration += req.duration || 0
181
- })
182
-
183
- return {
184
- inputTokens,
185
- outputTokens,
186
- cost,
187
- duration,
188
- requests: requests.length
189
- }
190
- }
191
-
192
- // Add message to thread
193
- async function addMessageToThread(threadId, message, usage) {
194
- const thread = await getThread(threadId)
195
- if (!thread) throw new Error('Thread not found')
196
-
197
- const newMessage = {
198
- id: nextId(),
199
- timestamp: new Date().toISOString(),
200
- ...message
201
- }
202
-
203
- // Add input and output token usage to previous 'input' message
204
- if (usage?.prompt_tokens != null) {
205
- const lastMessage = thread.messages[thread.messages.length - 1]
206
- if (lastMessage && lastMessage.role === 'user') {
207
- lastMessage.usage = {
208
- tokens: parseInt(usage.prompt_tokens),
209
- price: usage.input || '0',
210
- }
211
- }
212
- }
213
- if (usage?.completion_tokens != null) {
214
- newMessage.usage = {
215
- tokens: parseInt(usage.completion_tokens),
216
- price: usage.output || '0',
217
- duration: usage.duration || undefined,
218
- }
219
- }
220
-
221
- const updatedMessages = [...thread.messages, newMessage]
222
-
223
- // Auto-generate title from first user message if still "New Chat"
224
- let title = thread.title
225
- if (title === 'New Chat' && message.role === 'user' && updatedMessages.length <= 2) {
226
- title = message.content.slice(0, 200) + (message.content.length > 200 ? '...' : '')
227
- }
228
-
229
- const stats = await calculateThreadStats(threadId)
230
-
231
- await updateThread(threadId, {
232
- messages: updatedMessages,
233
- title: title,
234
- stats,
235
- })
236
-
237
- return newMessage
238
- }
239
-
240
- async function deleteMessageFromThread(threadId, messageId) {
241
- const thread = await getThread(threadId)
242
- if (!thread) throw new Error('Thread not found')
243
- const updatedMessages = thread.messages.filter(m => m.id !== messageId)
244
- await updateThread(threadId, { messages: updatedMessages })
245
- }
246
-
247
- async function updateMessageInThread(threadId, messageId, updates) {
248
- const thread = await getThread(threadId)
249
- if (!thread) throw new Error('Thread not found')
250
-
251
- const messageIndex = thread.messages.findIndex(m => m.id === messageId)
252
- if (messageIndex === -1) throw new Error('Message not found')
253
-
254
- const updatedMessages = [...thread.messages]
255
- updatedMessages[messageIndex] = {
256
- ...updatedMessages[messageIndex],
257
- ...updates
258
- }
259
-
260
- await updateThread(threadId, { messages: updatedMessages })
261
- }
262
-
263
- async function redoMessageFromThread(threadId, messageId) {
264
- const thread = await getThread(threadId)
265
- if (!thread) throw new Error('Thread not found')
266
-
267
- // Find the index of the message to redo
268
- const messageIndex = thread.messages.findIndex(m => m.id === messageId)
269
- if (messageIndex === -1) throw new Error('Message not found')
270
-
271
- // Keep only messages up to and including the target message
272
- const updatedMessages = thread.messages.slice(0, messageIndex + 1)
273
-
274
- // Update the thread with the new messages
275
- await updateThread(threadId, { messages: updatedMessages })
276
- }
277
-
278
- // Get all threads
279
- async function loadThreads() {
280
- await initDB()
281
- isLoading.value = true
282
-
283
- try {
284
- const tx = db.transaction(['threads'], 'readonly')
285
- const store = tx.objectStore('threads')
286
- const index = store.index('updatedAt')
287
-
288
- const allThreads = await index.getAll()
289
- threads.value = allThreads.reverse() // Most recent first
290
-
291
- return threads.value
292
- } finally {
293
- isLoading.value = false
294
- }
295
- }
296
-
297
- // Get single thread
298
- async function getThread(threadId) {
299
- await initDB()
300
-
301
- const tx = db.transaction(['threads'], 'readonly')
302
- const thread = await tx.objectStore('threads').get(threadId)
303
-
304
- return thread
305
- }
306
-
307
- // Delete thread
308
- async function deleteThread(threadId) {
309
- await initDB()
310
-
311
- const tx = db.transaction(['threads'], 'readwrite')
312
- await tx.objectStore('threads').delete(threadId)
313
- await tx.complete
314
-
315
- threads.value = threads.value.filter(t => t.id !== threadId)
316
-
317
- if (currentThread.value?.id === threadId) {
318
- currentThread.value = null
319
- }
320
- }
321
-
322
- // Set current thread
323
- async function setCurrentThread(threadId) {
324
- const thread = await getThread(threadId)
325
- if (thread) {
326
- currentThread.value = thread
327
- }
328
- return thread
329
- }
330
-
331
- // Set current thread from router params (router-aware version)
332
- async function setCurrentThreadFromRoute(threadId, router) {
333
- if (!threadId) {
334
- currentThread.value = null
335
- return null
336
- }
337
-
338
- const thread = await getThread(threadId)
339
- if (thread) {
340
- currentThread.value = thread
341
- return thread
342
- } else {
343
- // Thread not found, redirect to home
344
- if (router) {
345
- router.push((globalThis.ai?.base || '') + '/')
346
- }
347
- currentThread.value = null
348
- return null
349
- }
350
- }
351
-
352
- // Clear current thread (go back to initial state)
353
- function clearCurrentThread() {
354
- currentThread.value = null
355
- }
356
-
357
- function getGroupedThreads(total) {
358
- const now = new Date()
359
- const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
360
- const lastWeek = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000)
361
- const lastMonth = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000)
362
-
363
- const groups = {
364
- today: [],
365
- lastWeek: [],
366
- lastMonth: [],
367
- older: {}
368
- }
369
-
370
- const takeThreads = threads.value.slice(0, total)
371
-
372
- takeThreads.forEach(thread => {
373
- const threadDate = new Date(thread.updatedAt)
374
-
375
- if (threadDate >= today) {
376
- groups.today.push(thread)
377
- } else if (threadDate >= lastWeek) {
378
- groups.lastWeek.push(thread)
379
- } else if (threadDate >= lastMonth) {
380
- groups.lastMonth.push(thread)
381
- } else {
382
- const year = threadDate.getFullYear()
383
- const month = threadDate.toLocaleString('default', { month: 'long' })
384
- const key = `${month} ${year}`
385
-
386
- if (!groups.older[key]) {
387
- groups.older[key] = []
388
- }
389
- groups.older[key].push(thread)
390
- }
391
- })
392
-
393
- return groups
394
- }
395
-
396
- // Group threads by time periods
397
- const groupedThreads = computed(() => getGroupedThreads(threads.value.length))
398
-
399
- async function getAllRequests() {
400
- await initDB()
401
-
402
- const tx = db.transaction(['requests'], 'readonly')
403
- const store = tx.objectStore('requests')
404
- const allRequests = await store.getAll()
405
- return allRequests
406
- }
407
-
408
- async function getRequest(requestId) {
409
- await initDB()
410
-
411
- const tx = db.transaction(['requests'], 'readonly')
412
- const store = tx.objectStore('requests')
413
- const request = await store.get(requestId)
414
- return request
415
- }
416
-
417
- async function getAllRequestIds() {
418
- await initDB()
419
-
420
- const tx = db.transaction(['requests'], 'readonly')
421
- const store = tx.objectStore('requests')
422
- const ids = await store.getAllKeys()
423
- return ids
424
- }
425
-
426
- async function getAllThreadIds() {
427
- await initDB()
428
- const tx = db.transaction(['threads'], 'readonly')
429
- const store = tx.objectStore('threads')
430
- const ids = await store.getAllKeys()
431
- return ids
432
- }
433
-
434
- // Query requests with pagination and filtering
435
- async function getRequests(filters = {}, limit = 20, offset = 0) {
436
- try {
437
- await initDB()
438
-
439
- const {
440
- model = null,
441
- provider = null,
442
- threadId = null,
443
- sortBy = 'created',
444
- sortOrder = 'desc',
445
- startDate = null,
446
- endDate = null
447
- } = filters
448
-
449
- const tx = db.transaction(['requests'], 'readonly')
450
- const store = tx.objectStore('requests')
451
-
452
- // Get all requests and filter in memory (IndexedDB limitations)
453
- const allRequests = await store.getAll()
454
-
455
- // Apply filters
456
- let results = allRequests.filter(req => {
457
- if (model && req.model !== model) return false
458
- if (provider && req.provider !== provider) return false
459
- if (threadId && req.threadId !== threadId) return false
460
- if (startDate && req.created < startDate) return false
461
- if (endDate && req.created > endDate) return false
462
- return true
463
- })
464
-
465
- // Sort
466
- results.sort((a, b) => {
467
- let aVal = a[sortBy]
468
- let bVal = b[sortBy]
469
-
470
- if (sortOrder === 'desc') {
471
- return bVal - aVal
472
- } else {
473
- return aVal - bVal
474
- }
475
- })
476
-
477
- // Paginate
478
- const total = results.length
479
- const paginatedResults = results.slice(offset, offset + limit)
480
-
481
- return {
482
- requests: paginatedResults,
483
- total,
484
- hasMore: offset + limit < total
485
- }
486
- } catch (error) {
487
- console.error('Error in getRequests:', error)
488
- return {
489
- requests: [],
490
- total: 0,
491
- hasMore: false
492
- }
493
- }
494
- }
495
-
496
- // Get unique values for filter options
497
- async function getFilterOptions() {
498
- try {
499
- await initDB()
500
-
501
- const tx = db.transaction(['requests'], 'readonly')
502
- const store = tx.objectStore('requests')
503
- const allRequests = await store.getAll()
504
-
505
- const models = [...new Set(allRequests.map(r => r.model).filter(m => m))].sort()
506
- const providers = [...new Set(allRequests.map(r => r.provider).filter(p => p))].sort()
507
-
508
- return {
509
- models,
510
- providers
511
- }
512
- } catch (error) {
513
- console.error('Error in getFilterOptions:', error)
514
- return {
515
- models: [],
516
- providers: []
517
- }
518
- }
519
- }
520
-
521
- // Delete a request by ID
522
- async function deleteRequest(requestId) {
523
- await initDB()
524
-
525
- const tx = db.transaction(['requests'], 'readwrite')
526
- await tx.objectStore('requests').delete(requestId)
527
- await tx.complete
528
- }
529
-
530
- // Export the store
531
- export function useThreadStore() {
532
- return {
533
- // State
534
- threads,
535
- currentThread,
536
- isLoading,
537
- groupedThreads,
538
-
539
- // Actions
540
- initDB,
541
- logRequest,
542
- createThread,
543
- updateThread,
544
- addMessageToThread,
545
- deleteMessageFromThread,
546
- updateMessageInThread,
547
- redoMessageFromThread,
548
- loadThreads,
549
- getThread,
550
- deleteThread,
551
- setCurrentThread,
552
- setCurrentThreadFromRoute,
553
- clearCurrentThread,
554
- getGroupedThreads,
555
- getRequest,
556
- getRequests,
557
- getAllRequests,
558
- getFilterOptions,
559
- deleteRequest,
560
- getAllRequestIds,
561
- getAllThreadIds,
562
- }
563
- }