llms-py 3.0.0b6__py3-none-any.whl → 3.0.0b8__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/{ui/modules/analytics.mjs → extensions/analytics/ui/index.mjs} +55 -164
- llms/extensions/app/__init__.py +519 -0
- 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 +641 -0
- llms/extensions/app/db_manager.py +195 -0
- llms/extensions/app/requests.json +9073 -0
- llms/extensions/app/threads.json +15290 -0
- llms/{ui/modules/threads → extensions/app/ui}/Recents.mjs +82 -55
- llms/{ui/modules/threads → extensions/app/ui}/index.mjs +83 -20
- llms/extensions/app/ui/threadStore.mjs +407 -0
- llms/extensions/core_tools/__init__.py +598 -0
- llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
- llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
- llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
- llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
- llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
- llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
- llms/extensions/core_tools/ui/codemirror/lib/codemirror.css +344 -0
- llms/extensions/core_tools/ui/codemirror/lib/codemirror.js +9884 -0
- llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
- llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
- llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
- llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
- llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
- llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
- llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
- llms/extensions/core_tools/ui/index.mjs +650 -0
- llms/extensions/gallery/__init__.py +61 -0
- 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 +298 -0
- llms/extensions/gallery/ui/index.mjs +481 -0
- llms/extensions/katex/__init__.py +6 -0
- llms/extensions/katex/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/katex/ui/README.md +125 -0
- llms/extensions/katex/ui/contrib/auto-render.js +338 -0
- llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
- llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
- llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
- llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
- llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
- llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
- llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
- llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
- llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
- llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
- llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
- llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
- llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
- llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
- 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 +92 -0
- llms/extensions/katex/ui/katex-swap.css +1230 -0
- llms/extensions/katex/ui/katex-swap.min.css +1 -0
- llms/extensions/katex/ui/katex.css +1230 -0
- llms/extensions/katex/ui/katex.js +19080 -0
- llms/extensions/katex/ui/katex.min.css +1 -0
- llms/extensions/katex/ui/katex.min.js +1 -0
- llms/extensions/katex/ui/katex.min.mjs +1 -0
- llms/extensions/katex/ui/katex.mjs +18547 -0
- llms/extensions/providers/__init__.py +18 -0
- 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/{providers → extensions/providers}/__pycache__/nvidia.cpython-314.pyc +0 -0
- llms/{providers → extensions/providers}/__pycache__/openai.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
- llms/{providers → extensions/providers}/anthropic.py +45 -5
- llms/{providers → extensions/providers}/chutes.py +21 -18
- llms/{providers → extensions/providers}/google.py +99 -27
- llms/{providers → extensions/providers}/nvidia.py +6 -8
- llms/{providers → extensions/providers}/openai.py +3 -6
- llms/{providers → extensions/providers}/openrouter.py +12 -10
- llms/extensions/system_prompts/__init__.py +45 -0
- llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/system_prompts/ui/index.mjs +285 -0
- llms/extensions/system_prompts/ui/prompts.json +1067 -0
- llms/extensions/tools/__init__.py +5 -0
- llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/{ui/modules/tools.mjs → extensions/tools/ui/index.mjs} +12 -10
- llms/index.html +26 -38
- llms/llms.json +20 -1
- llms/main.py +845 -245
- llms/providers-extra.json +0 -32
- llms/ui/App.mjs +18 -20
- llms/ui/ai.mjs +38 -15
- llms/ui/app.css +1440 -59
- llms/ui/ctx.mjs +154 -18
- llms/ui/index.mjs +17 -14
- llms/ui/lib/vue.min.mjs +10 -9
- llms/ui/lib/vue.mjs +1796 -1635
- llms/ui/markdown.mjs +4 -2
- llms/ui/modules/chat/ChatBody.mjs +101 -334
- llms/ui/modules/chat/HomeTools.mjs +12 -0
- llms/ui/modules/chat/SettingsDialog.mjs +1 -1
- llms/ui/modules/chat/index.mjs +351 -314
- llms/ui/modules/layout.mjs +2 -26
- llms/ui/modules/model-selector.mjs +3 -3
- llms/ui/tailwind.input.css +35 -1
- llms/ui/utils.mjs +33 -3
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/METADATA +1 -1
- llms_py-3.0.0b8.dist-info/RECORD +198 -0
- llms/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
- llms/providers/__pycache__/chutes.cpython-314.pyc +0 -0
- llms/providers/__pycache__/google.cpython-314.pyc +0 -0
- llms/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
- llms/ui/modules/threads/threadStore.mjs +0 -586
- llms_py-3.0.0b6.dist-info/RECORD +0 -66
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/WHEEL +0 -0
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/top_level.txt +0 -0
llms/ui/markdown.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Marked } from "marked"
|
|
2
2
|
import hljs from "highlight.js"
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export function createMarked() {
|
|
5
5
|
const aliases = {
|
|
6
6
|
vue: 'html',
|
|
7
7
|
}
|
|
@@ -21,7 +21,9 @@ export const marked = (() => {
|
|
|
21
21
|
ret.use({ extensions: [thinkTag()] })
|
|
22
22
|
//ret.use({ extensions: [divExtension()] })
|
|
23
23
|
return ret
|
|
24
|
-
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const marked = createMarked();
|
|
25
27
|
|
|
26
28
|
export function renderMarkdown(content) {
|
|
27
29
|
if (Array.isArray(content)) {
|
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
import { ref, computed, nextTick, watch, onMounted, onUnmounted, inject } from 'vue'
|
|
2
2
|
import { useRouter, useRoute } from 'vue-router'
|
|
3
3
|
|
|
4
|
+
const MessageUsage = {
|
|
5
|
+
template: `
|
|
6
|
+
<div class="mt-2 text-xs opacity-70">
|
|
7
|
+
<span v-if="message.model" @click="$chat.setSelectedModel({ name: message.model })" title="Select model"><span class="cursor-pointer hover:underline">{{ message.model }}</span> • </span>
|
|
8
|
+
<span>{{ $fmt.time(message.timestamp) }}</span>
|
|
9
|
+
<span v-if="usage" :title="$fmt.tokensTitle(usage)">
|
|
10
|
+
•
|
|
11
|
+
{{ $fmt.humanifyNumber(usage.tokens) }} tokens
|
|
12
|
+
<span v-if="usage.cost">· {{ $fmt.tokenCostLong(usage.cost) }}</span>
|
|
13
|
+
<span v-if="usage.duration"> in {{ $fmt.humanifyMs(usage.duration) }}</span>
|
|
14
|
+
</span>
|
|
15
|
+
</div>
|
|
16
|
+
`,
|
|
17
|
+
props: {
|
|
18
|
+
usage: Object,
|
|
19
|
+
message: Object,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
4
23
|
export default {
|
|
24
|
+
components: {
|
|
25
|
+
MessageUsage,
|
|
26
|
+
},
|
|
5
27
|
template: `
|
|
6
28
|
<div class="flex flex-col h-full">
|
|
7
29
|
<!-- Messages Area -->
|
|
@@ -14,62 +36,11 @@ export default {
|
|
|
14
36
|
<!-- Welcome message when no thread is selected -->
|
|
15
37
|
<div v-else-if="!currentThread" class="text-center py-12">
|
|
16
38
|
<Welcome />
|
|
17
|
-
|
|
18
|
-
<!-- Chat input for new conversation -->
|
|
19
|
-
<!-- Moved to bottom input area -->
|
|
20
|
-
<div class="h-2"></div>
|
|
21
|
-
|
|
22
|
-
<!-- Export/Import buttons -->
|
|
23
|
-
<div class="mt-2 flex space-x-3 justify-center items-center">
|
|
24
|
-
<button type="button"
|
|
25
|
-
@click="(e) => e.altKey ? exportRequests() : exportThreads()"
|
|
26
|
-
:disabled="isExporting"
|
|
27
|
-
:title="'Export ' + threads?.threads?.value?.length + ' conversations'"
|
|
28
|
-
class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
29
|
-
>
|
|
30
|
-
<svg v-if="!isExporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
31
|
-
<path fill="currentColor" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z"></path>
|
|
32
|
-
</svg>
|
|
33
|
-
<svg v-else class="size-5 mr-1 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
34
|
-
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
35
|
-
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
36
|
-
</svg>
|
|
37
|
-
{{ isExporting ? 'Exporting...' : 'Export' }}
|
|
38
|
-
</button>
|
|
39
|
-
|
|
40
|
-
<button type="button"
|
|
41
|
-
@click="triggerImport"
|
|
42
|
-
:disabled="isImporting"
|
|
43
|
-
title="Import conversations from JSON file"
|
|
44
|
-
class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
45
|
-
>
|
|
46
|
-
<svg v-if="!isImporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
47
|
-
<path fill="currentColor" d="m14 12l-4-4v3H2v2h8v3m10 2V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v3h2V6h12v12H6v-3H4v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2"/>
|
|
48
|
-
</svg>
|
|
49
|
-
<svg v-else class="size-5 mr-1 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
50
|
-
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
51
|
-
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
52
|
-
</svg>
|
|
53
|
-
{{ isImporting ? 'Importing...' : 'Import' }}
|
|
54
|
-
</button>
|
|
55
|
-
|
|
56
|
-
<!-- Hidden file input for import -->
|
|
57
|
-
<input
|
|
58
|
-
ref="fileInput"
|
|
59
|
-
type="file"
|
|
60
|
-
accept=".json"
|
|
61
|
-
@change="handleFileImport"
|
|
62
|
-
class="hidden"
|
|
63
|
-
/>
|
|
64
|
-
|
|
65
|
-
<DarkModeToggle />
|
|
66
|
-
|
|
67
|
-
</div>
|
|
68
|
-
|
|
39
|
+
<HomeTools />
|
|
69
40
|
</div>
|
|
70
41
|
|
|
71
42
|
<!-- Messages -->
|
|
72
|
-
<div v-else class="space-y-2">
|
|
43
|
+
<div v-else-if="currentThread?.messages?.length" class="space-y-2">
|
|
73
44
|
<div v-if="currentThread?.messages.length && currentThread?.model" class="flex items-center justify-center select-none">
|
|
74
45
|
<span @click="$chat.setSelectedModel({ name: currentThread.model})"
|
|
75
46
|
class="flex items-center cursor-pointer px-1.5 py-0.5 text-xs rounded text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-gray-100 transition-colors border hover:border-gray-300 dark:hover:border-gray-700">
|
|
@@ -78,7 +49,7 @@ export default {
|
|
|
78
49
|
</span>
|
|
79
50
|
</div>
|
|
80
51
|
<div
|
|
81
|
-
v-for="message in currentThread.messages"
|
|
52
|
+
v-for="message in currentThread.messages.filter(x => x.role !== 'system')"
|
|
82
53
|
:key="message.id"
|
|
83
54
|
v-show="!(message.role === 'tool' && isToolLinked(message))"
|
|
84
55
|
class="flex items-start space-x-3 group"
|
|
@@ -101,7 +72,7 @@ export default {
|
|
|
101
72
|
</div>
|
|
102
73
|
|
|
103
74
|
<!-- Delete button (shown on hover) -->
|
|
104
|
-
<button type="button" @click.stop="threads.deleteMessageFromThread(currentThread.id, message.id)"
|
|
75
|
+
<button type="button" @click.stop="$threads.deleteMessageFromThread(currentThread.id, message.id)"
|
|
105
76
|
class="mx-auto opacity-0 group-hover:opacity-100 mt-2 rounded text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 transition-all"
|
|
106
77
|
title="Delete message">
|
|
107
78
|
<svg class="size-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -228,6 +199,15 @@ export default {
|
|
|
228
199
|
</template>
|
|
229
200
|
</div>
|
|
230
201
|
|
|
202
|
+
<!-- Assistant Audios -->
|
|
203
|
+
<div v-if="message.audios && message.audios.length > 0" class="mt-2 flex flex-wrap gap-2">
|
|
204
|
+
<template v-for="(audio, i) in message.audios" :key="i">
|
|
205
|
+
<div v-if="audio.type === 'audio_url'" class="flex items-center gap-2 p-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
|
206
|
+
<audio controls :src="resolveUrl(audio.audio_url.url)" class="h-8 w-64"></audio>
|
|
207
|
+
</div>
|
|
208
|
+
</template>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
231
211
|
<!-- User Message with separate attachments -->
|
|
232
212
|
<div v-else-if="message.role !== 'assistant' && message.role !== 'tool'">
|
|
233
213
|
<div v-html="$fmt.markdown(message.content)" class="prose prose-sm max-w-none dark:prose-invert break-words"></div>
|
|
@@ -254,16 +234,7 @@ export default {
|
|
|
254
234
|
</div>
|
|
255
235
|
</div>
|
|
256
236
|
|
|
257
|
-
<
|
|
258
|
-
<span v-if="message.model" @click="$chat.setSelectedModel({ name: message.model })" title="Select model"><span class="cursor-pointer hover:underline">{{ message.model }}</span> • </span>
|
|
259
|
-
<span>{{ $fmt.time(message.timestamp) }}</span>
|
|
260
|
-
<span v-if="message.usage" :title="tokensTitle(message.usage)">
|
|
261
|
-
•
|
|
262
|
-
{{ $fmt.humanifyNumber(message.usage.tokens) }} tokens
|
|
263
|
-
<span v-if="message.usage.cost">· {{ message.usage.cost }}</span>
|
|
264
|
-
<span v-if="message.usage.duration"> in {{ $fmt.humanifyMs(message.usage.duration) }}</span>
|
|
265
|
-
</span>
|
|
266
|
-
</div>
|
|
237
|
+
<MessageUsage :message="message" :usage="getMessageUsage(message)" />
|
|
267
238
|
</div>
|
|
268
239
|
|
|
269
240
|
<!-- Edit and Redo buttons (shown on hover for user messages, outside bubble) -->
|
|
@@ -294,7 +265,7 @@ export default {
|
|
|
294
265
|
</div>
|
|
295
266
|
|
|
296
267
|
<!-- Loading indicator -->
|
|
297
|
-
<div v-if="
|
|
268
|
+
<div v-if="$threads.watchingThread" class="flex items-start space-x-3 group">
|
|
298
269
|
<!-- Avatar outside the bubble -->
|
|
299
270
|
<div class="flex-shrink-0">
|
|
300
271
|
<div class="w-8 h-8 rounded-full bg-gray-600 dark:bg-gray-500 text-white flex items-center justify-center text-sm font-medium">
|
|
@@ -312,18 +283,36 @@ export default {
|
|
|
312
283
|
</div>
|
|
313
284
|
|
|
314
285
|
<!-- Cancel button -->
|
|
315
|
-
<button type="button" @click="
|
|
286
|
+
<button type="button" @click="$threads.cancelThread()"
|
|
316
287
|
class="px-3 py-1 rounded text-sm text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 border border-transparent hover:border-red-300 dark:hover:border-red-600 transition-all"
|
|
317
288
|
title="Cancel request">
|
|
318
289
|
cancel
|
|
319
290
|
</button>
|
|
320
291
|
</div>
|
|
321
292
|
|
|
293
|
+
<!-- Thread error message bubble -->
|
|
294
|
+
<div v-if="currentThread?.error" class="mt-8 flex items-center space-x-3">
|
|
295
|
+
<!-- Avatar outside the bubble -->
|
|
296
|
+
<div class="flex-shrink-0">
|
|
297
|
+
<div class="size-8 rounded-full bg-red-600 dark:bg-red-500 text-white flex items-center justify-center text-lg font-bold">
|
|
298
|
+
!
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
<!-- Error bubble -->
|
|
302
|
+
<div class="max-w-[85%] rounded-lg px-3 py-1 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 text-red-800 dark:text-red-200 shadow-sm">
|
|
303
|
+
<div class="flex items-start space-x-2">
|
|
304
|
+
<div class="flex-1 min-w-0">
|
|
305
|
+
<div v-if="currentThread.error" class="text-base mb-1">{{ currentThread.error }}</div>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
322
311
|
<!-- Error message bubble -->
|
|
323
|
-
<div v-if="
|
|
312
|
+
<div v-if="$state.error" class="mt-8 flex items-start space-x-3">
|
|
324
313
|
<!-- Avatar outside the bubble -->
|
|
325
314
|
<div class="flex-shrink-0">
|
|
326
|
-
<div class="
|
|
315
|
+
<div class="size-8 rounded-full bg-red-600 dark:bg-red-500 text-white flex items-center justify-center text-lg font-bold">
|
|
327
316
|
!
|
|
328
317
|
</div>
|
|
329
318
|
</div>
|
|
@@ -332,20 +321,20 @@ export default {
|
|
|
332
321
|
<div class="max-w-[85%] rounded-lg px-4 py-3 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 text-red-800 dark:text-red-200 shadow-sm">
|
|
333
322
|
<div class="flex items-start space-x-2">
|
|
334
323
|
<div class="flex-1 min-w-0">
|
|
335
|
-
<div class="
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
324
|
+
<div class="flex justify-between items-start">
|
|
325
|
+
<div class="text-base font-medium mb-1">{{ $state.error?.errorCode || 'Error' }}</div>
|
|
326
|
+
<button type="button" @click="$ctx.clearError()" title="Clear Error"
|
|
327
|
+
class="text-red-400 dark:text-red-300 hover:text-red-600 dark:hover:text-red-100 flex-shrink-0">
|
|
328
|
+
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
329
|
+
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
330
|
+
</svg>
|
|
331
|
+
</button>
|
|
332
|
+
</div>
|
|
333
|
+
<div v-if="$state.error?.message" class="text-base mb-1">{{ $state.error.message }}</div>
|
|
334
|
+
<div v-if="$state.error?.stackTrace" class="mt-2 text-sm whitespace-pre-wrap break-words max-h-80 overflow-y-auto font-mono p-2 border border-red-200/70 dark:border-red-800/70">
|
|
335
|
+
{{ $state.error.stackTrace }}
|
|
339
336
|
</div>
|
|
340
337
|
</div>
|
|
341
|
-
<button type="button"
|
|
342
|
-
@click="errorStatus = null"
|
|
343
|
-
class="text-red-400 dark:text-red-300 hover:text-red-600 dark:hover:text-red-100 flex-shrink-0"
|
|
344
|
-
>
|
|
345
|
-
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
346
|
-
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
347
|
-
</svg>
|
|
348
|
-
</button>
|
|
349
338
|
</div>
|
|
350
339
|
</div>
|
|
351
340
|
</div>
|
|
@@ -355,7 +344,7 @@ export default {
|
|
|
355
344
|
</div>
|
|
356
345
|
|
|
357
346
|
<!-- Input Area -->
|
|
358
|
-
<div v-if="$ai.hasAccess" class="flex-shrink-0 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-6 py-4">
|
|
347
|
+
<div v-if="$ai.hasAccess" :class="$ctx.cls('chat-input', 'flex-shrink-0 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-6 py-4')">
|
|
359
348
|
<ChatPrompt :model="$chat.getSelectedModel()" />
|
|
360
349
|
</div>
|
|
361
350
|
|
|
@@ -382,7 +371,6 @@ export default {
|
|
|
382
371
|
const threads = ctx.threads
|
|
383
372
|
const chatPrompt = ctx.chat
|
|
384
373
|
const { currentThread } = threads
|
|
385
|
-
const { errorStatus, isGenerating } = ctx.chat
|
|
386
374
|
|
|
387
375
|
const router = useRouter()
|
|
388
376
|
const route = useRoute()
|
|
@@ -395,9 +383,6 @@ export default {
|
|
|
395
383
|
return models.find(m => m.name === selectedModel.value) || models.find(m => m.id === selectedModel.value)
|
|
396
384
|
})
|
|
397
385
|
const messagesContainer = ref(null)
|
|
398
|
-
const isExporting = ref(false)
|
|
399
|
-
const isImporting = ref(false)
|
|
400
|
-
const fileInput = ref(null)
|
|
401
386
|
const copying = ref(null)
|
|
402
387
|
const lightboxUrl = ref(null)
|
|
403
388
|
|
|
@@ -428,12 +413,9 @@ export default {
|
|
|
428
413
|
|
|
429
414
|
// Watch for route changes and load the appropriate thread
|
|
430
415
|
watch(() => route.params.id, async (newId) => {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (thread?.model && Array.isArray(models) && models.includes(thread.model)) {
|
|
435
|
-
selectedModel.value = thread.model
|
|
436
|
-
}
|
|
416
|
+
// console.debug('watch route.params.id', newId)
|
|
417
|
+
ctx.clearError()
|
|
418
|
+
threads.setCurrentThreadFromRoute(newId, router)
|
|
437
419
|
|
|
438
420
|
if (!newId) {
|
|
439
421
|
chatPrompt.reset()
|
|
@@ -446,191 +428,6 @@ export default {
|
|
|
446
428
|
model: selectedModel.value,
|
|
447
429
|
})
|
|
448
430
|
})
|
|
449
|
-
|
|
450
|
-
async function exportThreads() {
|
|
451
|
-
if (isExporting.value) return
|
|
452
|
-
|
|
453
|
-
isExporting.value = true
|
|
454
|
-
try {
|
|
455
|
-
// Load all threads from IndexedDB
|
|
456
|
-
await threads.loadThreads()
|
|
457
|
-
const allThreads = threads.threads.value
|
|
458
|
-
|
|
459
|
-
// Create export data with metadata
|
|
460
|
-
const exportData = {
|
|
461
|
-
exportedAt: new Date().toISOString(),
|
|
462
|
-
version: '1.0',
|
|
463
|
-
source: 'llmspy',
|
|
464
|
-
threadCount: allThreads.length,
|
|
465
|
-
threads: allThreads
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Create and download JSON file
|
|
469
|
-
const jsonString = JSON.stringify(exportData, null, 2)
|
|
470
|
-
const blob = new Blob([jsonString], { type: 'application/json' })
|
|
471
|
-
const url = URL.createObjectURL(blob)
|
|
472
|
-
|
|
473
|
-
const link = document.createElement('a')
|
|
474
|
-
link.href = url
|
|
475
|
-
link.download = `llmsthreads-export-${new Date().toISOString().split('T')[0]}.json`
|
|
476
|
-
document.body.appendChild(link)
|
|
477
|
-
link.click()
|
|
478
|
-
document.body.removeChild(link)
|
|
479
|
-
URL.revokeObjectURL(url)
|
|
480
|
-
|
|
481
|
-
} catch (error) {
|
|
482
|
-
console.error('Failed to export threads:', error)
|
|
483
|
-
alert('Failed to export threads: ' + error.message)
|
|
484
|
-
} finally {
|
|
485
|
-
isExporting.value = false
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
async function exportRequests() {
|
|
490
|
-
if (isExporting.value) return
|
|
491
|
-
|
|
492
|
-
isExporting.value = true
|
|
493
|
-
try {
|
|
494
|
-
// Load all threads from IndexedDB
|
|
495
|
-
const allRequests = await threads.getAllRequests()
|
|
496
|
-
|
|
497
|
-
// Create export data with metadata
|
|
498
|
-
const exportData = {
|
|
499
|
-
exportedAt: new Date().toISOString(),
|
|
500
|
-
version: '1.0',
|
|
501
|
-
source: 'llmspy',
|
|
502
|
-
requestsCount: allRequests.length,
|
|
503
|
-
requests: allRequests
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Create and download JSON file
|
|
507
|
-
const jsonString = JSON.stringify(exportData, null, 2)
|
|
508
|
-
const blob = new Blob([jsonString], { type: 'application/json' })
|
|
509
|
-
const url = URL.createObjectURL(blob)
|
|
510
|
-
|
|
511
|
-
const link = document.createElement('a')
|
|
512
|
-
link.href = url
|
|
513
|
-
link.download = `llmsrequests-export-${new Date().toISOString().split('T')[0]}.json`
|
|
514
|
-
document.body.appendChild(link)
|
|
515
|
-
link.click()
|
|
516
|
-
document.body.removeChild(link)
|
|
517
|
-
URL.revokeObjectURL(url)
|
|
518
|
-
|
|
519
|
-
} catch (error) {
|
|
520
|
-
console.error('Failed to export requests:', error)
|
|
521
|
-
alert('Failed to export requests: ' + error.message)
|
|
522
|
-
} finally {
|
|
523
|
-
isExporting.value = false
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function triggerImport() {
|
|
528
|
-
if (isImporting.value) return
|
|
529
|
-
fileInput.value?.click()
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
async function handleFileImport(event) {
|
|
533
|
-
const file = event.target.files?.[0]
|
|
534
|
-
if (!file) return
|
|
535
|
-
|
|
536
|
-
isImporting.value = true
|
|
537
|
-
var importType = 'threads'
|
|
538
|
-
try {
|
|
539
|
-
const text = await file.text()
|
|
540
|
-
const importData = JSON.parse(text)
|
|
541
|
-
importType = importData.threads
|
|
542
|
-
? 'threads'
|
|
543
|
-
: importData.requests
|
|
544
|
-
? 'requests'
|
|
545
|
-
: 'unknown'
|
|
546
|
-
|
|
547
|
-
// Import threads one by one
|
|
548
|
-
let importedCount = 0
|
|
549
|
-
let existingCount = 0
|
|
550
|
-
|
|
551
|
-
const db = await threads.initDB()
|
|
552
|
-
|
|
553
|
-
if (importData.threads) {
|
|
554
|
-
if (!Array.isArray(importData.threads)) {
|
|
555
|
-
throw new Error('Invalid import file: missing or invalid threads array')
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const threadIds = new Set(await threads.getAllThreadIds())
|
|
559
|
-
|
|
560
|
-
for (const threadData of importData.threads) {
|
|
561
|
-
if (!threadData.id) {
|
|
562
|
-
console.warn('Skipping thread without ID:', threadData)
|
|
563
|
-
continue
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
try {
|
|
567
|
-
// Check if thread already exists
|
|
568
|
-
const existingThread = threadIds.has(threadData.id)
|
|
569
|
-
if (existingThread) {
|
|
570
|
-
existingCount++
|
|
571
|
-
} else {
|
|
572
|
-
// Add new thread directly to IndexedDB
|
|
573
|
-
const tx = db.transaction(['threads'], 'readwrite')
|
|
574
|
-
await tx.objectStore('threads').add(threadData)
|
|
575
|
-
await tx.complete
|
|
576
|
-
importedCount++
|
|
577
|
-
}
|
|
578
|
-
} catch (error) {
|
|
579
|
-
console.error('Failed to import thread:', threadData.id, error)
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// Reload threads to reflect changes
|
|
584
|
-
await threads.loadThreads()
|
|
585
|
-
|
|
586
|
-
alert(`Import completed!\nNew threads: ${importedCount}\nExisting threads: ${existingCount}`)
|
|
587
|
-
}
|
|
588
|
-
if (importData.requests) {
|
|
589
|
-
if (!Array.isArray(importData.requests)) {
|
|
590
|
-
throw new Error('Invalid import file: missing or invalid requests array')
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
const requestIds = new Set(await threads.getAllRequestIds())
|
|
594
|
-
|
|
595
|
-
for (const requestData of importData.requests) {
|
|
596
|
-
if (!requestData.id) {
|
|
597
|
-
console.warn('Skipping request without ID:', requestData)
|
|
598
|
-
continue
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
try {
|
|
602
|
-
// Check if request already exists
|
|
603
|
-
const existingRequest = requestIds.has(requestData.id)
|
|
604
|
-
if (existingRequest) {
|
|
605
|
-
existingCount++
|
|
606
|
-
} else {
|
|
607
|
-
// Add new request directly to IndexedDB
|
|
608
|
-
const db = await threads.initDB()
|
|
609
|
-
const tx = db.transaction(['requests'], 'readwrite')
|
|
610
|
-
await tx.objectStore('requests').add(requestData)
|
|
611
|
-
await tx.complete
|
|
612
|
-
importedCount++
|
|
613
|
-
}
|
|
614
|
-
} catch (error) {
|
|
615
|
-
console.error('Failed to import request:', requestData.id, error)
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
alert(`Import completed!\nNew requests: ${importedCount}\nExisting requests: ${existingCount}`)
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
} catch (error) {
|
|
623
|
-
console.error('Failed to import ' + importType + ':', error)
|
|
624
|
-
alert('Failed to import ' + importType + ': ' + error.message)
|
|
625
|
-
} finally {
|
|
626
|
-
isImporting.value = false
|
|
627
|
-
// Clear the file input
|
|
628
|
-
if (fileInput.value) {
|
|
629
|
-
fileInput.value.value = ''
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
431
|
function configUpdated() {
|
|
635
432
|
console.log('configUpdated', selectedModel.value, models.length, models.includes(selectedModel.value))
|
|
636
433
|
if (selectedModel.value && !models.includes(selectedModel.value)) {
|
|
@@ -746,37 +543,18 @@ export default {
|
|
|
746
543
|
const redoMessage = async (message) => {
|
|
747
544
|
if (!currentThread.value || message.role !== 'user') return
|
|
748
545
|
|
|
749
|
-
|
|
750
|
-
const threadId = currentThread.value.id
|
|
751
|
-
|
|
752
|
-
// Clear all messages after this one
|
|
753
|
-
await threads.redoMessageFromThread(threadId, message.id)
|
|
546
|
+
const threadId = currentThread.value.id
|
|
754
547
|
|
|
755
|
-
|
|
548
|
+
// Clear all messages after this one
|
|
549
|
+
await threads.redoMessageFromThread(threadId, message.timestamp)
|
|
756
550
|
|
|
757
|
-
|
|
758
|
-
chatPrompt.messageText.value = state.text
|
|
759
|
-
|
|
760
|
-
// Restore attached files
|
|
761
|
-
chatPrompt.attachedFiles.value = state.files
|
|
551
|
+
const state = await extractMessageState(message)
|
|
762
552
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
await nextTick()
|
|
553
|
+
// Set the message text in the chat prompt
|
|
554
|
+
chatPrompt.messageText.value = state.text
|
|
766
555
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
if (sendButton && !sendButton.disabled) {
|
|
770
|
-
sendButton.click()
|
|
771
|
-
}
|
|
772
|
-
} catch (error) {
|
|
773
|
-
console.error('Failed to redo message:', error)
|
|
774
|
-
errorStatus.value = {
|
|
775
|
-
errorCode: 'Error',
|
|
776
|
-
message: 'Failed to redo message: ' + error.message,
|
|
777
|
-
stackTrace: null
|
|
778
|
-
}
|
|
779
|
-
}
|
|
556
|
+
// Restore attached files
|
|
557
|
+
chatPrompt.attachedFiles.value = state.files
|
|
780
558
|
}
|
|
781
559
|
|
|
782
560
|
// Edit a user message
|
|
@@ -787,7 +565,7 @@ export default {
|
|
|
787
565
|
const state = await extractMessageState(message)
|
|
788
566
|
chatPrompt.messageText.value = state.text
|
|
789
567
|
chatPrompt.attachedFiles.value = state.files
|
|
790
|
-
chatPrompt.
|
|
568
|
+
chatPrompt.editingMessage.value = message.timestamp
|
|
791
569
|
|
|
792
570
|
// Focus the textarea
|
|
793
571
|
nextTick(() => {
|
|
@@ -800,23 +578,6 @@ export default {
|
|
|
800
578
|
})
|
|
801
579
|
}
|
|
802
580
|
|
|
803
|
-
// Cancel pending request
|
|
804
|
-
const cancelRequest = () => {
|
|
805
|
-
chatPrompt.cancel()
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
function tokensTitle(usage) {
|
|
809
|
-
let title = []
|
|
810
|
-
if (usage.tokens && usage.price) {
|
|
811
|
-
const msg = parseFloat(usage.price) > 0
|
|
812
|
-
? `${usage.tokens} tokens @ ${usage.price} = ${ctx.fmt.tokenCostLong(usage.price, usage.tokens)}`
|
|
813
|
-
: `${usage.tokens} tokens`
|
|
814
|
-
const duration = usage.duration ? ` in ${usage.duration}ms` : ''
|
|
815
|
-
title.push(msg + duration)
|
|
816
|
-
}
|
|
817
|
-
return title.join('\n')
|
|
818
|
-
}
|
|
819
|
-
|
|
820
581
|
let sub
|
|
821
582
|
onMounted(() => {
|
|
822
583
|
sub = ctx.events.subscribe(`keydown:Escape`, closeLightbox)
|
|
@@ -828,6 +589,20 @@ export default {
|
|
|
828
589
|
return currentThread.value?.messages?.find(m => m.role === 'tool' && m.tool_call_id === toolCallId)
|
|
829
590
|
}
|
|
830
591
|
|
|
592
|
+
const getMessageUsage = (message) => {
|
|
593
|
+
if (message.usage) return message.usage
|
|
594
|
+
if (message.tool_calls?.length) {
|
|
595
|
+
const toolUsages = message.tool_calls.map(tc => getToolOutput(tc.id)?.usage)
|
|
596
|
+
const agg = {
|
|
597
|
+
tokens: toolUsages.reduce((a, b) => a + (b?.tokens || 0), 0),
|
|
598
|
+
cost: toolUsages.reduce((a, b) => a + (b?.cost || 0), 0),
|
|
599
|
+
duration: toolUsages.reduce((a, b) => a + (b?.duration || 0), 0)
|
|
600
|
+
}
|
|
601
|
+
return agg
|
|
602
|
+
}
|
|
603
|
+
return null
|
|
604
|
+
}
|
|
605
|
+
|
|
831
606
|
const isToolLinked = (message) => {
|
|
832
607
|
if (message.role !== 'tool') return false
|
|
833
608
|
return currentThread.value?.messages?.some(m => m.role === 'assistant' && m.tool_calls?.some(tc => tc.id === message.tool_call_id))
|
|
@@ -855,6 +630,9 @@ export default {
|
|
|
855
630
|
if (tag == 'th') {
|
|
856
631
|
cls += ' lowercase'
|
|
857
632
|
}
|
|
633
|
+
if (tag == 'td') {
|
|
634
|
+
cls += ' whitespace-pre-wrap'
|
|
635
|
+
}
|
|
858
636
|
return cls
|
|
859
637
|
}
|
|
860
638
|
|
|
@@ -868,13 +646,10 @@ export default {
|
|
|
868
646
|
setPrefs,
|
|
869
647
|
config,
|
|
870
648
|
models,
|
|
871
|
-
threads,
|
|
872
|
-
isGenerating,
|
|
873
649
|
currentThread,
|
|
874
650
|
selectedModel,
|
|
875
651
|
selectedModelObj,
|
|
876
652
|
messagesContainer,
|
|
877
|
-
errorStatus,
|
|
878
653
|
copying,
|
|
879
654
|
isReasoningExpanded,
|
|
880
655
|
toggleReasoning,
|
|
@@ -882,22 +657,14 @@ export default {
|
|
|
882
657
|
copyMessageContent,
|
|
883
658
|
redoMessage,
|
|
884
659
|
editMessage,
|
|
885
|
-
cancelRequest,
|
|
886
660
|
configUpdated,
|
|
887
|
-
exportThreads,
|
|
888
|
-
exportRequests,
|
|
889
|
-
isExporting,
|
|
890
|
-
triggerImport,
|
|
891
|
-
handleFileImport,
|
|
892
|
-
isImporting,
|
|
893
|
-
fileInput,
|
|
894
|
-
tokensTitle,
|
|
895
661
|
getAttachments,
|
|
896
662
|
hasAttachments,
|
|
897
663
|
lightboxUrl,
|
|
898
664
|
openLightbox,
|
|
899
665
|
closeLightbox,
|
|
900
666
|
resolveUrl,
|
|
667
|
+
getMessageUsage,
|
|
901
668
|
getToolOutput,
|
|
902
669
|
isToolLinked,
|
|
903
670
|
tryParseJson,
|
|
@@ -107,7 +107,7 @@ export function useSettings() {
|
|
|
107
107
|
|
|
108
108
|
export default {
|
|
109
109
|
template: `
|
|
110
|
-
<div v-if="isOpen" class="fixed inset-0 z-
|
|
110
|
+
<div v-if="isOpen" class="fixed inset-0 z-100 overflow-y-auto" @click.self="close">
|
|
111
111
|
<div class="flex min-h-screen items-center justify-center p-4">
|
|
112
112
|
<!-- Backdrop -->
|
|
113
113
|
<div class="fixed inset-0 bg-black/40 transition-opacity" @click="close"></div>
|