llms-py 3.0.0b7__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/extensions/analytics/ui/index.mjs +51 -162
- 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 +78 -9
- llms/extensions/app/ui/threadStore.mjs +407 -0
- llms/extensions/core_tools/__init__.py +272 -32
- 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/__pycache__/db.cpython-314.pyc +0 -0
- llms/extensions/gallery/db.py +4 -4
- llms/extensions/gallery/ui/index.mjs +2 -1
- 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/__pycache__/anthropic.cpython-314.pyc +0 -0
- llms/extensions/providers/anthropic.py +44 -1
- llms/extensions/system_prompts/ui/index.mjs +2 -1
- llms/extensions/tools/__init__.py +5 -0
- llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/tools/ui/index.mjs +8 -8
- llms/index.html +26 -38
- llms/llms.json +4 -1
- llms/main.py +492 -103
- llms/ui/App.mjs +2 -3
- llms/ui/ai.mjs +29 -13
- llms/ui/app.css +250 -398
- llms/ui/ctx.mjs +84 -6
- llms/ui/index.mjs +4 -6
- 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 +90 -86
- llms/ui/modules/chat/HomeTools.mjs +0 -242
- llms/ui/modules/chat/index.mjs +103 -170
- llms/ui/modules/model-selector.mjs +2 -2
- llms/ui/tailwind.input.css +35 -1
- llms/ui/utils.mjs +12 -0
- {llms_py-3.0.0b7.dist-info → llms_py-3.0.0b8.dist-info}/METADATA +1 -1
- llms_py-3.0.0b8.dist-info/RECORD +198 -0
- llms/ui/modules/threads/threadStore.mjs +0 -640
- llms_py-3.0.0b7.dist-info/RECORD +0 -80
- {llms_py-3.0.0b7.dist-info → llms_py-3.0.0b8.dist-info}/WHEEL +0 -0
- {llms_py-3.0.0b7.dist-info → llms_py-3.0.0b8.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.0b7.dist-info → llms_py-3.0.0b8.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.0b7.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 -->
|
|
@@ -18,7 +40,7 @@ export default {
|
|
|
18
40
|
</div>
|
|
19
41
|
|
|
20
42
|
<!-- Messages -->
|
|
21
|
-
<div v-else class="space-y-2">
|
|
43
|
+
<div v-else-if="currentThread?.messages?.length" class="space-y-2">
|
|
22
44
|
<div v-if="currentThread?.messages.length && currentThread?.model" class="flex items-center justify-center select-none">
|
|
23
45
|
<span @click="$chat.setSelectedModel({ name: currentThread.model})"
|
|
24
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">
|
|
@@ -27,7 +49,7 @@ export default {
|
|
|
27
49
|
</span>
|
|
28
50
|
</div>
|
|
29
51
|
<div
|
|
30
|
-
v-for="message in currentThread.messages"
|
|
52
|
+
v-for="message in currentThread.messages.filter(x => x.role !== 'system')"
|
|
31
53
|
:key="message.id"
|
|
32
54
|
v-show="!(message.role === 'tool' && isToolLinked(message))"
|
|
33
55
|
class="flex items-start space-x-3 group"
|
|
@@ -50,7 +72,7 @@ export default {
|
|
|
50
72
|
</div>
|
|
51
73
|
|
|
52
74
|
<!-- Delete button (shown on hover) -->
|
|
53
|
-
<button type="button" @click.stop="threads.deleteMessageFromThread(currentThread.id, message.id)"
|
|
75
|
+
<button type="button" @click.stop="$threads.deleteMessageFromThread(currentThread.id, message.id)"
|
|
54
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"
|
|
55
77
|
title="Delete message">
|
|
56
78
|
<svg class="size-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -212,16 +234,7 @@ export default {
|
|
|
212
234
|
</div>
|
|
213
235
|
</div>
|
|
214
236
|
|
|
215
|
-
<
|
|
216
|
-
<span v-if="message.model" @click="$chat.setSelectedModel({ name: message.model })" title="Select model"><span class="cursor-pointer hover:underline">{{ message.model }}</span> • </span>
|
|
217
|
-
<span>{{ $fmt.time(message.timestamp) }}</span>
|
|
218
|
-
<span v-if="message.usage" :title="tokensTitle(message.usage)">
|
|
219
|
-
•
|
|
220
|
-
{{ $fmt.humanifyNumber(message.usage.tokens) }} tokens
|
|
221
|
-
<span v-if="message.usage.cost">· {{ message.usage.cost }}</span>
|
|
222
|
-
<span v-if="message.usage.duration"> in {{ $fmt.humanifyMs(message.usage.duration) }}</span>
|
|
223
|
-
</span>
|
|
224
|
-
</div>
|
|
237
|
+
<MessageUsage :message="message" :usage="getMessageUsage(message)" />
|
|
225
238
|
</div>
|
|
226
239
|
|
|
227
240
|
<!-- Edit and Redo buttons (shown on hover for user messages, outside bubble) -->
|
|
@@ -252,7 +265,7 @@ export default {
|
|
|
252
265
|
</div>
|
|
253
266
|
|
|
254
267
|
<!-- Loading indicator -->
|
|
255
|
-
<div v-if="
|
|
268
|
+
<div v-if="$threads.watchingThread" class="flex items-start space-x-3 group">
|
|
256
269
|
<!-- Avatar outside the bubble -->
|
|
257
270
|
<div class="flex-shrink-0">
|
|
258
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">
|
|
@@ -270,18 +283,36 @@ export default {
|
|
|
270
283
|
</div>
|
|
271
284
|
|
|
272
285
|
<!-- Cancel button -->
|
|
273
|
-
<button type="button" @click="
|
|
286
|
+
<button type="button" @click="$threads.cancelThread()"
|
|
274
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"
|
|
275
288
|
title="Cancel request">
|
|
276
289
|
cancel
|
|
277
290
|
</button>
|
|
278
291
|
</div>
|
|
279
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
|
+
|
|
280
311
|
<!-- Error message bubble -->
|
|
281
|
-
<div v-if="
|
|
312
|
+
<div v-if="$state.error" class="mt-8 flex items-start space-x-3">
|
|
282
313
|
<!-- Avatar outside the bubble -->
|
|
283
314
|
<div class="flex-shrink-0">
|
|
284
|
-
<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">
|
|
285
316
|
!
|
|
286
317
|
</div>
|
|
287
318
|
</div>
|
|
@@ -290,20 +321,20 @@ export default {
|
|
|
290
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">
|
|
291
322
|
<div class="flex items-start space-x-2">
|
|
292
323
|
<div class="flex-1 min-w-0">
|
|
293
|
-
<div class="
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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 }}
|
|
297
336
|
</div>
|
|
298
337
|
</div>
|
|
299
|
-
<button type="button"
|
|
300
|
-
@click="errorStatus = null"
|
|
301
|
-
class="text-red-400 dark:text-red-300 hover:text-red-600 dark:hover:text-red-100 flex-shrink-0"
|
|
302
|
-
>
|
|
303
|
-
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
304
|
-
<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>
|
|
305
|
-
</svg>
|
|
306
|
-
</button>
|
|
307
338
|
</div>
|
|
308
339
|
</div>
|
|
309
340
|
</div>
|
|
@@ -340,7 +371,6 @@ export default {
|
|
|
340
371
|
const threads = ctx.threads
|
|
341
372
|
const chatPrompt = ctx.chat
|
|
342
373
|
const { currentThread } = threads
|
|
343
|
-
const { errorStatus, isGenerating } = ctx.chat
|
|
344
374
|
|
|
345
375
|
const router = useRouter()
|
|
346
376
|
const route = useRoute()
|
|
@@ -383,12 +413,9 @@ export default {
|
|
|
383
413
|
|
|
384
414
|
// Watch for route changes and load the appropriate thread
|
|
385
415
|
watch(() => route.params.id, async (newId) => {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (thread?.model && Array.isArray(models) && models.includes(thread.model)) {
|
|
390
|
-
selectedModel.value = thread.model
|
|
391
|
-
}
|
|
416
|
+
// console.debug('watch route.params.id', newId)
|
|
417
|
+
ctx.clearError()
|
|
418
|
+
threads.setCurrentThreadFromRoute(newId, router)
|
|
392
419
|
|
|
393
420
|
if (!newId) {
|
|
394
421
|
chatPrompt.reset()
|
|
@@ -516,37 +543,18 @@ export default {
|
|
|
516
543
|
const redoMessage = async (message) => {
|
|
517
544
|
if (!currentThread.value || message.role !== 'user') return
|
|
518
545
|
|
|
519
|
-
|
|
520
|
-
const threadId = currentThread.value.id
|
|
521
|
-
|
|
522
|
-
// Clear all messages after this one
|
|
523
|
-
await threads.redoMessageFromThread(threadId, message.id)
|
|
524
|
-
|
|
525
|
-
const state = await extractMessageState(message)
|
|
546
|
+
const threadId = currentThread.value.id
|
|
526
547
|
|
|
527
|
-
|
|
528
|
-
|
|
548
|
+
// Clear all messages after this one
|
|
549
|
+
await threads.redoMessageFromThread(threadId, message.timestamp)
|
|
529
550
|
|
|
530
|
-
|
|
531
|
-
chatPrompt.attachedFiles.value = state.files
|
|
551
|
+
const state = await extractMessageState(message)
|
|
532
552
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
await nextTick()
|
|
553
|
+
// Set the message text in the chat prompt
|
|
554
|
+
chatPrompt.messageText.value = state.text
|
|
536
555
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
if (sendButton && !sendButton.disabled) {
|
|
540
|
-
sendButton.click()
|
|
541
|
-
}
|
|
542
|
-
} catch (error) {
|
|
543
|
-
console.error('Failed to redo message:', error)
|
|
544
|
-
errorStatus.value = {
|
|
545
|
-
errorCode: 'Error',
|
|
546
|
-
message: 'Failed to redo message: ' + error.message,
|
|
547
|
-
stackTrace: null
|
|
548
|
-
}
|
|
549
|
-
}
|
|
556
|
+
// Restore attached files
|
|
557
|
+
chatPrompt.attachedFiles.value = state.files
|
|
550
558
|
}
|
|
551
559
|
|
|
552
560
|
// Edit a user message
|
|
@@ -557,7 +565,7 @@ export default {
|
|
|
557
565
|
const state = await extractMessageState(message)
|
|
558
566
|
chatPrompt.messageText.value = state.text
|
|
559
567
|
chatPrompt.attachedFiles.value = state.files
|
|
560
|
-
chatPrompt.
|
|
568
|
+
chatPrompt.editingMessage.value = message.timestamp
|
|
561
569
|
|
|
562
570
|
// Focus the textarea
|
|
563
571
|
nextTick(() => {
|
|
@@ -570,23 +578,6 @@ export default {
|
|
|
570
578
|
})
|
|
571
579
|
}
|
|
572
580
|
|
|
573
|
-
// Cancel pending request
|
|
574
|
-
const cancelRequest = () => {
|
|
575
|
-
chatPrompt.cancel()
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function tokensTitle(usage) {
|
|
579
|
-
let title = []
|
|
580
|
-
if (usage.tokens && usage.price) {
|
|
581
|
-
const msg = parseFloat(usage.price) > 0
|
|
582
|
-
? `${usage.tokens} tokens @ ${usage.price} = ${ctx.fmt.tokenCostLong(usage.price, usage.tokens)}`
|
|
583
|
-
: `${usage.tokens} tokens`
|
|
584
|
-
const duration = usage.duration ? ` in ${usage.duration}ms` : ''
|
|
585
|
-
title.push(msg + duration)
|
|
586
|
-
}
|
|
587
|
-
return title.join('\n')
|
|
588
|
-
}
|
|
589
|
-
|
|
590
581
|
let sub
|
|
591
582
|
onMounted(() => {
|
|
592
583
|
sub = ctx.events.subscribe(`keydown:Escape`, closeLightbox)
|
|
@@ -598,6 +589,20 @@ export default {
|
|
|
598
589
|
return currentThread.value?.messages?.find(m => m.role === 'tool' && m.tool_call_id === toolCallId)
|
|
599
590
|
}
|
|
600
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
|
+
|
|
601
606
|
const isToolLinked = (message) => {
|
|
602
607
|
if (message.role !== 'tool') return false
|
|
603
608
|
return currentThread.value?.messages?.some(m => m.role === 'assistant' && m.tool_calls?.some(tc => tc.id === message.tool_call_id))
|
|
@@ -625,6 +630,9 @@ export default {
|
|
|
625
630
|
if (tag == 'th') {
|
|
626
631
|
cls += ' lowercase'
|
|
627
632
|
}
|
|
633
|
+
if (tag == 'td') {
|
|
634
|
+
cls += ' whitespace-pre-wrap'
|
|
635
|
+
}
|
|
628
636
|
return cls
|
|
629
637
|
}
|
|
630
638
|
|
|
@@ -638,13 +646,10 @@ export default {
|
|
|
638
646
|
setPrefs,
|
|
639
647
|
config,
|
|
640
648
|
models,
|
|
641
|
-
threads,
|
|
642
|
-
isGenerating,
|
|
643
649
|
currentThread,
|
|
644
650
|
selectedModel,
|
|
645
651
|
selectedModelObj,
|
|
646
652
|
messagesContainer,
|
|
647
|
-
errorStatus,
|
|
648
653
|
copying,
|
|
649
654
|
isReasoningExpanded,
|
|
650
655
|
toggleReasoning,
|
|
@@ -652,15 +657,14 @@ export default {
|
|
|
652
657
|
copyMessageContent,
|
|
653
658
|
redoMessage,
|
|
654
659
|
editMessage,
|
|
655
|
-
cancelRequest,
|
|
656
660
|
configUpdated,
|
|
657
|
-
tokensTitle,
|
|
658
661
|
getAttachments,
|
|
659
662
|
hasAttachments,
|
|
660
663
|
lightboxUrl,
|
|
661
664
|
openLightbox,
|
|
662
665
|
closeLightbox,
|
|
663
666
|
resolveUrl,
|
|
667
|
+
getMessageUsage,
|
|
664
668
|
getToolOutput,
|
|
665
669
|
isToolLinked,
|
|
666
670
|
tryParseJson,
|
|
@@ -2,253 +2,11 @@ import { ref, inject } from 'vue'
|
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
4
|
template: `
|
|
5
|
-
<!-- Export/Import buttons -->
|
|
6
5
|
<div class="mt-4 flex space-x-3 justify-center items-center">
|
|
7
|
-
<button type="button"
|
|
8
|
-
@click="(e) => e.altKey ? exportRequests() : exportThreads()"
|
|
9
|
-
:disabled="isExporting"
|
|
10
|
-
:title="'Export ' + threads?.threads?.value?.length + ' conversations'"
|
|
11
|
-
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"
|
|
12
|
-
>
|
|
13
|
-
<svg v-if="!isExporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
14
|
-
<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>
|
|
15
|
-
</svg>
|
|
16
|
-
<svg v-else class="size-5 mr-1 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
17
|
-
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
18
|
-
<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>
|
|
19
|
-
</svg>
|
|
20
|
-
{{ isExporting ? 'Exporting...' : 'Export' }}
|
|
21
|
-
</button>
|
|
22
|
-
|
|
23
|
-
<button type="button"
|
|
24
|
-
@click="triggerImport"
|
|
25
|
-
:disabled="isImporting"
|
|
26
|
-
title="Import conversations from JSON file"
|
|
27
|
-
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"
|
|
28
|
-
>
|
|
29
|
-
<svg v-if="!isImporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
30
|
-
<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"/>
|
|
31
|
-
</svg>
|
|
32
|
-
<svg v-else class="size-5 mr-1 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
33
|
-
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
34
|
-
<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>
|
|
35
|
-
</svg>
|
|
36
|
-
{{ isImporting ? 'Importing...' : 'Import' }}
|
|
37
|
-
</button>
|
|
38
|
-
|
|
39
|
-
<!-- Hidden file input for import -->
|
|
40
|
-
<input
|
|
41
|
-
ref="fileInput"
|
|
42
|
-
type="file"
|
|
43
|
-
accept=".json"
|
|
44
|
-
@change="handleFileImport"
|
|
45
|
-
class="hidden"
|
|
46
|
-
/>
|
|
47
|
-
|
|
48
6
|
<DarkModeToggle />
|
|
49
7
|
</div>
|
|
50
8
|
|
|
51
9
|
`,
|
|
52
10
|
setup() {
|
|
53
|
-
const ctx = inject('ctx')
|
|
54
|
-
const threads = ctx.threads
|
|
55
|
-
|
|
56
|
-
const isExporting = ref(false)
|
|
57
|
-
const isImporting = ref(false)
|
|
58
|
-
const fileInput = ref(null)
|
|
59
|
-
|
|
60
|
-
async function exportThreads() {
|
|
61
|
-
if (isExporting.value) return
|
|
62
|
-
|
|
63
|
-
isExporting.value = true
|
|
64
|
-
try {
|
|
65
|
-
// Load all threads from IndexedDB
|
|
66
|
-
await threads.loadThreads()
|
|
67
|
-
const allThreads = threads.threads.value
|
|
68
|
-
|
|
69
|
-
// Create export data with metadata
|
|
70
|
-
const exportData = {
|
|
71
|
-
exportedAt: new Date().toISOString(),
|
|
72
|
-
version: '1.0',
|
|
73
|
-
source: 'llmspy',
|
|
74
|
-
threadCount: allThreads.length,
|
|
75
|
-
threads: allThreads
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Create and download JSON file
|
|
79
|
-
const jsonString = JSON.stringify(exportData, null, 2)
|
|
80
|
-
const blob = new Blob([jsonString], { type: 'application/json' })
|
|
81
|
-
const url = URL.createObjectURL(blob)
|
|
82
|
-
|
|
83
|
-
const link = document.createElement('a')
|
|
84
|
-
link.href = url
|
|
85
|
-
link.download = `llmsthreads-export-${new Date().toISOString().split('T')[0]}.json`
|
|
86
|
-
document.body.appendChild(link)
|
|
87
|
-
link.click()
|
|
88
|
-
document.body.removeChild(link)
|
|
89
|
-
URL.revokeObjectURL(url)
|
|
90
|
-
|
|
91
|
-
} catch (error) {
|
|
92
|
-
console.error('Failed to export threads:', error)
|
|
93
|
-
alert('Failed to export threads: ' + error.message)
|
|
94
|
-
} finally {
|
|
95
|
-
isExporting.value = false
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async function exportRequests() {
|
|
100
|
-
if (isExporting.value) return
|
|
101
|
-
|
|
102
|
-
isExporting.value = true
|
|
103
|
-
try {
|
|
104
|
-
// Load all threads from IndexedDB
|
|
105
|
-
const allRequests = await threads.getAllRequests()
|
|
106
|
-
|
|
107
|
-
// Create export data with metadata
|
|
108
|
-
const exportData = {
|
|
109
|
-
exportedAt: new Date().toISOString(),
|
|
110
|
-
version: '1.0',
|
|
111
|
-
source: 'llmspy',
|
|
112
|
-
requestsCount: allRequests.length,
|
|
113
|
-
requests: allRequests
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Create and download JSON file
|
|
117
|
-
const jsonString = JSON.stringify(exportData, null, 2)
|
|
118
|
-
const blob = new Blob([jsonString], { type: 'application/json' })
|
|
119
|
-
const url = URL.createObjectURL(blob)
|
|
120
|
-
|
|
121
|
-
const link = document.createElement('a')
|
|
122
|
-
link.href = url
|
|
123
|
-
link.download = `llmsrequests-export-${new Date().toISOString().split('T')[0]}.json`
|
|
124
|
-
document.body.appendChild(link)
|
|
125
|
-
link.click()
|
|
126
|
-
document.body.removeChild(link)
|
|
127
|
-
URL.revokeObjectURL(url)
|
|
128
|
-
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error('Failed to export requests:', error)
|
|
131
|
-
alert('Failed to export requests: ' + error.message)
|
|
132
|
-
} finally {
|
|
133
|
-
isExporting.value = false
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function triggerImport() {
|
|
138
|
-
if (isImporting.value) return
|
|
139
|
-
fileInput.value?.click()
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async function handleFileImport(event) {
|
|
143
|
-
const file = event.target.files?.[0]
|
|
144
|
-
if (!file) return
|
|
145
|
-
|
|
146
|
-
isImporting.value = true
|
|
147
|
-
var importType = 'threads'
|
|
148
|
-
try {
|
|
149
|
-
const text = await file.text()
|
|
150
|
-
const importData = JSON.parse(text)
|
|
151
|
-
importType = importData.threads
|
|
152
|
-
? 'threads'
|
|
153
|
-
: importData.requests
|
|
154
|
-
? 'requests'
|
|
155
|
-
: 'unknown'
|
|
156
|
-
|
|
157
|
-
// Import threads one by one
|
|
158
|
-
let importedCount = 0
|
|
159
|
-
let existingCount = 0
|
|
160
|
-
|
|
161
|
-
const db = await threads.initDB()
|
|
162
|
-
|
|
163
|
-
if (importData.threads) {
|
|
164
|
-
if (!Array.isArray(importData.threads)) {
|
|
165
|
-
throw new Error('Invalid import file: missing or invalid threads array')
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const threadIds = new Set(await threads.getAllThreadIds())
|
|
169
|
-
|
|
170
|
-
for (const threadData of importData.threads) {
|
|
171
|
-
if (!threadData.id) {
|
|
172
|
-
console.warn('Skipping thread without ID:', threadData)
|
|
173
|
-
continue
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
// Check if thread already exists
|
|
178
|
-
const existingThread = threadIds.has(threadData.id)
|
|
179
|
-
if (existingThread) {
|
|
180
|
-
existingCount++
|
|
181
|
-
} else {
|
|
182
|
-
// Add new thread directly to IndexedDB
|
|
183
|
-
const tx = db.transaction(['threads'], 'readwrite')
|
|
184
|
-
await tx.objectStore('threads').add(threadData)
|
|
185
|
-
await tx.complete
|
|
186
|
-
importedCount++
|
|
187
|
-
}
|
|
188
|
-
} catch (error) {
|
|
189
|
-
console.error('Failed to import thread:', threadData.id, error)
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Reload threads to reflect changes
|
|
194
|
-
await threads.loadThreads()
|
|
195
|
-
|
|
196
|
-
alert(`Import completed!\nNew threads: ${importedCount}\nExisting threads: ${existingCount}`)
|
|
197
|
-
}
|
|
198
|
-
if (importData.requests) {
|
|
199
|
-
if (!Array.isArray(importData.requests)) {
|
|
200
|
-
throw new Error('Invalid import file: missing or invalid requests array')
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const requestIds = new Set(await threads.getAllRequestIds())
|
|
204
|
-
|
|
205
|
-
for (const requestData of importData.requests) {
|
|
206
|
-
if (!requestData.id) {
|
|
207
|
-
console.warn('Skipping request without ID:', requestData)
|
|
208
|
-
continue
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
try {
|
|
212
|
-
// Check if request already exists
|
|
213
|
-
const existingRequest = requestIds.has(requestData.id)
|
|
214
|
-
if (existingRequest) {
|
|
215
|
-
existingCount++
|
|
216
|
-
} else {
|
|
217
|
-
// Add new request directly to IndexedDB
|
|
218
|
-
const db = await threads.initDB()
|
|
219
|
-
const tx = db.transaction(['requests'], 'readwrite')
|
|
220
|
-
await tx.objectStore('requests').add(requestData)
|
|
221
|
-
await tx.complete
|
|
222
|
-
importedCount++
|
|
223
|
-
}
|
|
224
|
-
} catch (error) {
|
|
225
|
-
console.error('Failed to import request:', requestData.id, error)
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
alert(`Import completed!\nNew requests: ${importedCount}\nExisting requests: ${existingCount}`)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
} catch (error) {
|
|
233
|
-
console.error('Failed to import ' + importType + ':', error)
|
|
234
|
-
alert('Failed to import ' + importType + ': ' + error.message)
|
|
235
|
-
} finally {
|
|
236
|
-
isImporting.value = false
|
|
237
|
-
// Clear the file input
|
|
238
|
-
if (fileInput.value) {
|
|
239
|
-
fileInput.value.value = ''
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return {
|
|
245
|
-
exportThreads,
|
|
246
|
-
exportRequests,
|
|
247
|
-
isExporting,
|
|
248
|
-
triggerImport,
|
|
249
|
-
handleFileImport,
|
|
250
|
-
isImporting,
|
|
251
|
-
fileInput,
|
|
252
|
-
}
|
|
253
11
|
}
|
|
254
12
|
}
|