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