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
@@ -0,0 +1,572 @@
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 / 1_000_000) + (parseFloat(outputPrice) * outputTokens / 1_000_000),
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
+ try {
121
+ const tx = db.transaction(['threads'], 'readwrite')
122
+ await tx.objectStore('threads').add(thread)
123
+ await tx.complete
124
+
125
+ threads.value.unshift(thread)
126
+ // Note: currentThread will be set by router navigation
127
+
128
+ } catch (e) {
129
+ console.error('Error creating thread', e, thread)
130
+ }
131
+
132
+ return thread
133
+ }
134
+
135
+ // Update thread
136
+ async function updateThread(threadId, updates) {
137
+ await initDB()
138
+
139
+ const tx = db.transaction(['threads'], 'readwrite')
140
+ const store = tx.objectStore('threads')
141
+
142
+ const thread = await store.get(threadId)
143
+ if (!thread) throw new Error('Thread not found')
144
+
145
+ const updatedThread = {
146
+ ...thread,
147
+ ...updates,
148
+ updatedAt: new Date().toISOString()
149
+ }
150
+
151
+ await store.put(updatedThread)
152
+ await tx.complete
153
+
154
+ // Update in memory
155
+ const index = threads.value.findIndex(t => t.id === threadId)
156
+ if (index !== -1) {
157
+ threads.value[index] = updatedThread
158
+ }
159
+
160
+ if (currentThread.value?.id === threadId) {
161
+ currentThread.value = updatedThread
162
+ }
163
+
164
+ return updatedThread
165
+ }
166
+
167
+ async function calculateThreadStats(threadId) {
168
+ await initDB()
169
+
170
+ const tx = db.transaction(['requests'], 'readonly')
171
+ const store = tx.objectStore('requests')
172
+ const index = store.index('threadId')
173
+
174
+ const requests = await index.getAll(threadId)
175
+
176
+ let inputTokens = 0
177
+ let outputTokens = 0
178
+ let cost = 0.0
179
+ let duration = 0
180
+
181
+ requests.forEach(req => {
182
+ inputTokens += req.inputTokens || 0
183
+ outputTokens += req.outputTokens || 0
184
+ cost += req.cost || 0.0
185
+ duration += req.duration || 0
186
+ })
187
+
188
+ return {
189
+ inputTokens,
190
+ outputTokens,
191
+ cost,
192
+ duration,
193
+ requests: requests.length
194
+ }
195
+ }
196
+
197
+ // Add message to thread
198
+ async function addMessageToThread(threadId, message, usage) {
199
+ const thread = await getThread(threadId)
200
+ if (!thread) throw new Error('Thread not found')
201
+
202
+ const newMessage = {
203
+ id: nextId(),
204
+ timestamp: new Date().toISOString(),
205
+ ...message
206
+ }
207
+
208
+ // Add input and output token usage to previous 'input' message
209
+ if (usage?.prompt_tokens != null) {
210
+ const lastMessage = thread.messages[thread.messages.length - 1]
211
+ if (lastMessage && lastMessage.role === 'user') {
212
+ lastMessage.usage = {
213
+ tokens: parseInt(usage.prompt_tokens),
214
+ price: usage.input || '0',
215
+ }
216
+ }
217
+ }
218
+ if (usage?.completion_tokens != null) {
219
+ newMessage.usage = {
220
+ tokens: parseInt(usage.completion_tokens),
221
+ price: usage.output || '0',
222
+ duration: usage.duration || undefined,
223
+ }
224
+ }
225
+
226
+ const updatedMessages = [...thread.messages, newMessage]
227
+
228
+ // Auto-generate title from first user message if still "New Chat"
229
+ let title = thread.title
230
+ if (title === 'New Chat' && message.role === 'user' && updatedMessages.length <= 2) {
231
+ let contentText = message.content
232
+ if (Array.isArray(contentText)) {
233
+ contentText = contentText.filter(c => c.type === 'text').map(c => c.text).join(' ')
234
+ }
235
+ title = contentText.slice(0, 200) + (contentText.length > 200 ? '...' : '')
236
+ }
237
+
238
+ const stats = await calculateThreadStats(threadId)
239
+
240
+ await updateThread(threadId, {
241
+ messages: updatedMessages,
242
+ title: title,
243
+ stats,
244
+ })
245
+
246
+ return newMessage
247
+ }
248
+
249
+ async function deleteMessageFromThread(threadId, messageId) {
250
+ const thread = await getThread(threadId)
251
+ if (!thread) throw new Error('Thread not found')
252
+ const updatedMessages = thread.messages.filter(m => m.id !== messageId)
253
+ await updateThread(threadId, { messages: updatedMessages })
254
+ }
255
+
256
+ async function updateMessageInThread(threadId, messageId, updates) {
257
+ const thread = await getThread(threadId)
258
+ if (!thread) throw new Error('Thread not found')
259
+
260
+ const messageIndex = thread.messages.findIndex(m => m.id === messageId)
261
+ if (messageIndex === -1) throw new Error('Message not found')
262
+
263
+ const updatedMessages = [...thread.messages]
264
+ updatedMessages[messageIndex] = {
265
+ ...updatedMessages[messageIndex],
266
+ ...updates
267
+ }
268
+
269
+ await updateThread(threadId, { messages: updatedMessages })
270
+ }
271
+
272
+ async function redoMessageFromThread(threadId, messageId) {
273
+ const thread = await getThread(threadId)
274
+ if (!thread) throw new Error('Thread not found')
275
+
276
+ // Find the index of the message to redo
277
+ const messageIndex = thread.messages.findIndex(m => m.id === messageId)
278
+ if (messageIndex === -1) throw new Error('Message not found')
279
+
280
+ // Keep only messages up to and including the target message
281
+ const updatedMessages = thread.messages.slice(0, messageIndex + 1)
282
+
283
+ // Update the thread with the new messages
284
+ await updateThread(threadId, { messages: updatedMessages })
285
+ }
286
+
287
+ // Get all threads
288
+ async function loadThreads() {
289
+ await initDB()
290
+ isLoading.value = true
291
+
292
+ try {
293
+ const tx = db.transaction(['threads'], 'readonly')
294
+ const store = tx.objectStore('threads')
295
+ const index = store.index('updatedAt')
296
+
297
+ const allThreads = await index.getAll()
298
+ threads.value = allThreads.reverse() // Most recent first
299
+
300
+ return threads.value
301
+ } finally {
302
+ isLoading.value = false
303
+ }
304
+ }
305
+
306
+ // Get single thread
307
+ async function getThread(threadId) {
308
+ await initDB()
309
+
310
+ const tx = db.transaction(['threads'], 'readonly')
311
+ const thread = await tx.objectStore('threads').get(threadId)
312
+
313
+ return thread
314
+ }
315
+
316
+ // Delete thread
317
+ async function deleteThread(threadId) {
318
+ await initDB()
319
+
320
+ const tx = db.transaction(['threads'], 'readwrite')
321
+ await tx.objectStore('threads').delete(threadId)
322
+ await tx.complete
323
+
324
+ threads.value = threads.value.filter(t => t.id !== threadId)
325
+
326
+ if (currentThread.value?.id === threadId) {
327
+ currentThread.value = null
328
+ }
329
+ }
330
+
331
+ // Set current thread
332
+ async function setCurrentThread(threadId) {
333
+ const thread = await getThread(threadId)
334
+ if (thread) {
335
+ currentThread.value = thread
336
+ }
337
+ return thread
338
+ }
339
+
340
+ // Set current thread from router params (router-aware version)
341
+ async function setCurrentThreadFromRoute(threadId, router) {
342
+ if (!threadId) {
343
+ currentThread.value = null
344
+ return null
345
+ }
346
+
347
+ const thread = await getThread(threadId)
348
+ if (thread) {
349
+ currentThread.value = thread
350
+ return thread
351
+ } else {
352
+ // Thread not found, redirect to home
353
+ if (router) {
354
+ router.push((globalThis.ai?.base || '') + '/')
355
+ }
356
+ currentThread.value = null
357
+ return null
358
+ }
359
+ }
360
+
361
+ // Clear current thread (go back to initial state)
362
+ function clearCurrentThread() {
363
+ currentThread.value = null
364
+ }
365
+
366
+ function getGroupedThreads(total) {
367
+ const now = new Date()
368
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
369
+ const lastWeek = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000)
370
+ const lastMonth = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000)
371
+
372
+ const groups = {
373
+ today: [],
374
+ lastWeek: [],
375
+ lastMonth: [],
376
+ older: {}
377
+ }
378
+
379
+ const takeThreads = threads.value.slice(0, total)
380
+
381
+ takeThreads.forEach(thread => {
382
+ const threadDate = new Date(thread.updatedAt)
383
+
384
+ if (threadDate >= today) {
385
+ groups.today.push(thread)
386
+ } else if (threadDate >= lastWeek) {
387
+ groups.lastWeek.push(thread)
388
+ } else if (threadDate >= lastMonth) {
389
+ groups.lastMonth.push(thread)
390
+ } else {
391
+ const year = threadDate.getFullYear()
392
+ const month = threadDate.toLocaleString('default', { month: 'long' })
393
+ const key = `${month} ${year}`
394
+
395
+ if (!groups.older[key]) {
396
+ groups.older[key] = []
397
+ }
398
+ groups.older[key].push(thread)
399
+ }
400
+ })
401
+
402
+ return groups
403
+ }
404
+
405
+ // Group threads by time periods
406
+ const groupedThreads = computed(() => getGroupedThreads(threads.value.length))
407
+
408
+ async function getAllRequests() {
409
+ await initDB()
410
+
411
+ const tx = db.transaction(['requests'], 'readonly')
412
+ const store = tx.objectStore('requests')
413
+ const allRequests = await store.getAll()
414
+ return allRequests
415
+ }
416
+
417
+ async function getRequest(requestId) {
418
+ await initDB()
419
+
420
+ const tx = db.transaction(['requests'], 'readonly')
421
+ const store = tx.objectStore('requests')
422
+ const request = await store.get(requestId)
423
+ return request
424
+ }
425
+
426
+ async function getAllRequestIds() {
427
+ await initDB()
428
+
429
+ const tx = db.transaction(['requests'], 'readonly')
430
+ const store = tx.objectStore('requests')
431
+ const ids = await store.getAllKeys()
432
+ return ids
433
+ }
434
+
435
+ async function getAllThreadIds() {
436
+ await initDB()
437
+ const tx = db.transaction(['threads'], 'readonly')
438
+ const store = tx.objectStore('threads')
439
+ const ids = await store.getAllKeys()
440
+ return ids
441
+ }
442
+
443
+ // Query requests with pagination and filtering
444
+ async function getRequests(filters = {}, limit = 20, offset = 0) {
445
+ try {
446
+ await initDB()
447
+
448
+ const {
449
+ model = null,
450
+ provider = null,
451
+ threadId = null,
452
+ sortBy = 'created',
453
+ sortOrder = 'desc',
454
+ startDate = null,
455
+ endDate = null
456
+ } = filters
457
+
458
+ const tx = db.transaction(['requests'], 'readonly')
459
+ const store = tx.objectStore('requests')
460
+
461
+ // Get all requests and filter in memory (IndexedDB limitations)
462
+ const allRequests = await store.getAll()
463
+
464
+ // Apply filters
465
+ let results = allRequests.filter(req => {
466
+ if (model && req.model !== model) return false
467
+ if (provider && req.provider !== provider) return false
468
+ if (threadId && req.threadId !== threadId) return false
469
+ if (startDate && req.created < startDate) return false
470
+ if (endDate && req.created > endDate) return false
471
+ return true
472
+ })
473
+
474
+ // Sort
475
+ results.sort((a, b) => {
476
+ let aVal = a[sortBy]
477
+ let bVal = b[sortBy]
478
+
479
+ if (sortOrder === 'desc') {
480
+ return bVal - aVal
481
+ } else {
482
+ return aVal - bVal
483
+ }
484
+ })
485
+
486
+ // Paginate
487
+ const total = results.length
488
+ const paginatedResults = results.slice(offset, offset + limit)
489
+
490
+ return {
491
+ requests: paginatedResults,
492
+ total,
493
+ hasMore: offset + limit < total
494
+ }
495
+ } catch (error) {
496
+ console.error('Error in getRequests:', error)
497
+ return {
498
+ requests: [],
499
+ total: 0,
500
+ hasMore: false
501
+ }
502
+ }
503
+ }
504
+
505
+ // Get unique values for filter options
506
+ async function getFilterOptions() {
507
+ try {
508
+ await initDB()
509
+
510
+ const tx = db.transaction(['requests'], 'readonly')
511
+ const store = tx.objectStore('requests')
512
+ const allRequests = await store.getAll()
513
+
514
+ const models = [...new Set(allRequests.map(r => r.model).filter(m => m))].sort()
515
+ const providers = [...new Set(allRequests.map(r => r.provider).filter(p => p))].sort()
516
+
517
+ return {
518
+ models,
519
+ providers
520
+ }
521
+ } catch (error) {
522
+ console.error('Error in getFilterOptions:', error)
523
+ return {
524
+ models: [],
525
+ providers: []
526
+ }
527
+ }
528
+ }
529
+
530
+ // Delete a request by ID
531
+ async function deleteRequest(requestId) {
532
+ await initDB()
533
+
534
+ const tx = db.transaction(['requests'], 'readwrite')
535
+ await tx.objectStore('requests').delete(requestId)
536
+ await tx.complete
537
+ }
538
+
539
+ // Export the store
540
+ export function useThreadStore() {
541
+ return {
542
+ // State
543
+ threads,
544
+ currentThread,
545
+ isLoading,
546
+ groupedThreads,
547
+
548
+ // Actions
549
+ initDB,
550
+ logRequest,
551
+ createThread,
552
+ updateThread,
553
+ addMessageToThread,
554
+ deleteMessageFromThread,
555
+ updateMessageInThread,
556
+ redoMessageFromThread,
557
+ loadThreads,
558
+ getThread,
559
+ deleteThread,
560
+ setCurrentThread,
561
+ setCurrentThreadFromRoute,
562
+ clearCurrentThread,
563
+ getGroupedThreads,
564
+ getRequest,
565
+ getRequests,
566
+ getAllRequests,
567
+ getFilterOptions,
568
+ deleteRequest,
569
+ getAllRequestIds,
570
+ getAllThreadIds,
571
+ }
572
+ }