llms-py 2.0.9__py3-none-any.whl → 3.0.10__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/__init__.py +4 -0
- llms/__main__.py +9 -0
- llms/db.py +359 -0
- llms/extensions/analytics/ui/index.mjs +1444 -0
- llms/extensions/app/README.md +20 -0
- llms/extensions/app/__init__.py +589 -0
- llms/extensions/app/db.py +536 -0
- {llms_py-2.0.9.data/data → llms/extensions/app}/ui/Recents.mjs +100 -73
- llms_py-2.0.9.data/data/ui/Sidebar.mjs → llms/extensions/app/ui/index.mjs +150 -79
- llms/extensions/app/ui/threadStore.mjs +433 -0
- llms/extensions/core_tools/CALCULATOR.md +32 -0
- llms/extensions/core_tools/__init__.py +637 -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/codemirror.css +344 -0
- llms/extensions/core_tools/ui/codemirror/codemirror.js +9884 -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/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/README.md +61 -0
- llms/extensions/gallery/__init__.py +63 -0
- llms/extensions/gallery/db.py +243 -0
- llms/extensions/gallery/ui/index.mjs +482 -0
- llms/extensions/katex/README.md +39 -0
- llms/extensions/katex/__init__.py +6 -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 +22 -0
- llms/extensions/providers/anthropic.py +233 -0
- llms/extensions/providers/cerebras.py +37 -0
- llms/extensions/providers/chutes.py +153 -0
- llms/extensions/providers/google.py +481 -0
- llms/extensions/providers/nvidia.py +103 -0
- llms/extensions/providers/openai.py +154 -0
- llms/extensions/providers/openrouter.py +74 -0
- llms/extensions/providers/zai.py +182 -0
- llms/extensions/system_prompts/README.md +22 -0
- llms/extensions/system_prompts/__init__.py +45 -0
- llms/extensions/system_prompts/ui/index.mjs +280 -0
- llms/extensions/system_prompts/ui/prompts.json +1067 -0
- llms/extensions/tools/__init__.py +144 -0
- llms/extensions/tools/ui/index.mjs +706 -0
- llms/index.html +58 -0
- llms/llms.json +400 -0
- llms/main.py +4407 -0
- llms/providers-extra.json +394 -0
- llms/providers.json +1 -0
- llms/ui/App.mjs +188 -0
- llms/ui/ai.mjs +217 -0
- llms/ui/app.css +7081 -0
- llms/ui/ctx.mjs +412 -0
- llms/ui/index.mjs +131 -0
- llms/ui/lib/chart.js +14 -0
- llms/ui/lib/charts.mjs +16 -0
- llms/ui/lib/color.js +14 -0
- llms/ui/lib/servicestack-vue.mjs +37 -0
- llms/ui/lib/vue.min.mjs +13 -0
- llms/ui/lib/vue.mjs +18530 -0
- {llms_py-2.0.9.data/data → llms}/ui/markdown.mjs +33 -15
- llms/ui/modules/chat/ChatBody.mjs +976 -0
- llms/ui/modules/chat/SettingsDialog.mjs +374 -0
- llms/ui/modules/chat/index.mjs +991 -0
- llms/ui/modules/icons.mjs +46 -0
- llms/ui/modules/layout.mjs +271 -0
- llms/ui/modules/model-selector.mjs +811 -0
- llms/ui/tailwind.input.css +742 -0
- {llms_py-2.0.9.data/data → llms}/ui/typography.css +133 -7
- llms/ui/utils.mjs +261 -0
- llms_py-3.0.10.dist-info/METADATA +49 -0
- llms_py-3.0.10.dist-info/RECORD +177 -0
- llms_py-3.0.10.dist-info/entry_points.txt +2 -0
- {llms_py-2.0.9.dist-info → llms_py-3.0.10.dist-info}/licenses/LICENSE +1 -2
- llms.py +0 -1402
- llms_py-2.0.9.data/data/index.html +0 -64
- llms_py-2.0.9.data/data/llms.json +0 -447
- llms_py-2.0.9.data/data/requirements.txt +0 -1
- llms_py-2.0.9.data/data/ui/App.mjs +0 -20
- llms_py-2.0.9.data/data/ui/ChatPrompt.mjs +0 -389
- llms_py-2.0.9.data/data/ui/Main.mjs +0 -680
- llms_py-2.0.9.data/data/ui/app.css +0 -3951
- llms_py-2.0.9.data/data/ui/lib/servicestack-vue.min.mjs +0 -37
- llms_py-2.0.9.data/data/ui/lib/vue.min.mjs +0 -12
- llms_py-2.0.9.data/data/ui/tailwind.input.css +0 -261
- llms_py-2.0.9.data/data/ui/threadStore.mjs +0 -273
- llms_py-2.0.9.data/data/ui/utils.mjs +0 -114
- llms_py-2.0.9.data/data/ui.json +0 -1069
- llms_py-2.0.9.dist-info/METADATA +0 -941
- llms_py-2.0.9.dist-info/RECORD +0 -30
- llms_py-2.0.9.dist-info/entry_points.txt +0 -2
- {llms_py-2.0.9.data/data → llms}/ui/fav.svg +0 -0
- {llms_py-2.0.9.data/data → llms}/ui/lib/highlight.min.mjs +0 -0
- {llms_py-2.0.9.data/data → llms}/ui/lib/idb.min.mjs +0 -0
- {llms_py-2.0.9.data/data → llms}/ui/lib/marked.min.mjs +0 -0
- /llms_py-2.0.9.data/data/ui/lib/servicestack-client.min.mjs → /llms/ui/lib/servicestack-client.mjs +0 -0
- {llms_py-2.0.9.data/data → llms}/ui/lib/vue-router.min.mjs +0 -0
- {llms_py-2.0.9.dist-info → llms_py-3.0.10.dist-info}/WHEEL +0 -0
- {llms_py-2.0.9.dist-info → llms_py-3.0.10.dist-info}/top_level.txt +0 -0
|
@@ -1,44 +1,43 @@
|
|
|
1
1
|
import { ref, onMounted, watch, inject } from 'vue'
|
|
2
2
|
import { useRouter, useRoute } from 'vue-router'
|
|
3
|
-
import { useThreadStore } from './threadStore.mjs'
|
|
4
|
-
import { renderMarkdown } from './markdown.mjs'
|
|
5
3
|
|
|
6
4
|
const RecentResults = {
|
|
7
|
-
template
|
|
5
|
+
template: `
|
|
8
6
|
<div class="flex-1 overflow-y-auto" @scroll="onScroll">
|
|
9
7
|
<div class="mx-auto max-w-6xl px-4 py-4">
|
|
10
|
-
<div class="text-sm text-gray-600 mb-3"
|
|
11
|
-
<span v-if="q">{{
|
|
12
|
-
<span v-else>
|
|
8
|
+
<div class="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
|
9
|
+
<span v-if="q">{{ total }} result{{ total===1?'':'s' }}</span>
|
|
10
|
+
<span v-else>All conversations</span>
|
|
13
11
|
</div>
|
|
14
12
|
|
|
15
|
-
<div v-if="!threads.length" class="text-gray-500">No conversations
|
|
13
|
+
<div v-if="!loading && threads.length === 0" class="text-gray-500 dark:text-gray-400">No conversations found.</div>
|
|
16
14
|
|
|
17
15
|
<table class="w-full">
|
|
18
16
|
<tbody>
|
|
19
|
-
<tr v-for="t in
|
|
20
|
-
<td class="py-3 px-1 border-b border-gray-200 max-w-
|
|
17
|
+
<tr v-for="t in threads" :key="t.id" class="hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
18
|
+
<td class="py-3 px-1 border-b border-gray-200 dark:border-gray-700 max-w-2xl">
|
|
21
19
|
<button type="button" @click="open(t.id)" class="w-full text-left">
|
|
22
20
|
<div class="flex items-start justify-between gap-3">
|
|
23
21
|
<div class="min-w-0 flex-1">
|
|
24
|
-
<div class="font-medium text-gray-900 truncate" :title="t.title">{{ t.title || 'Untitled chat' }}</div>
|
|
25
|
-
<div class="mt-1 text-sm text-gray-600 line-clamp-2">
|
|
22
|
+
<div class="font-medium text-gray-900 dark:text-gray-100 truncate" :title="t.title">{{ t.title || 'Untitled chat' }}</div>
|
|
23
|
+
<div class="mt-1 text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
|
|
26
24
|
<div v-html="snippet(t)"></div>
|
|
27
25
|
</div>
|
|
28
26
|
</div>
|
|
29
27
|
</div>
|
|
30
28
|
</button>
|
|
31
29
|
</td>
|
|
32
|
-
<td class="py-3 px-1 border-b border-gray-200">
|
|
30
|
+
<td class="py-3 px-1 border-b border-gray-200 dark:border-gray-700">
|
|
33
31
|
<div class="text-right whitespace-nowrap">
|
|
34
|
-
<div class="text-xs text-gray-500">{{ formatDate(t.updatedAt || t.createdAt) }}</div>
|
|
35
|
-
<div class="text-[11px] text-gray-500/80">{{ (t.messages?.length || 0) }} messages</div>
|
|
36
|
-
<div v-if="t.model" class="text-[11px] text-blue-600">{{ t.model }}</div>
|
|
32
|
+
<div class="text-xs text-gray-500 dark:text-gray-400">{{ formatDate(t.updatedAt || t.createdAt) }}</div>
|
|
33
|
+
<div class="text-[11px] text-gray-500/80 dark:text-gray-400/80">{{ (t.messages?.length || 0) }} messages</div>
|
|
34
|
+
<div v-if="t.model" class="text-[11px] text-blue-600 dark:text-blue-400 max-w-[140px] truncate" :title="t.model">{{ t.model }}</div>
|
|
37
35
|
</div>
|
|
38
36
|
</td>
|
|
39
37
|
</tr>
|
|
40
38
|
</tbody>
|
|
41
39
|
</table>
|
|
40
|
+
<div v-if="loading" class="py-4 text-center text-gray-500 dark:text-gray-400">Loading...</div>
|
|
42
41
|
</div>
|
|
43
42
|
</div>
|
|
44
43
|
`,
|
|
@@ -46,99 +45,126 @@ const RecentResults = {
|
|
|
46
45
|
q: String
|
|
47
46
|
},
|
|
48
47
|
setup(props) {
|
|
48
|
+
const ctx = inject('ctx')
|
|
49
|
+
const ai = ctx.ai
|
|
49
50
|
const router = useRouter()
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
51
|
+
|
|
52
|
+
const threads = ref([])
|
|
53
|
+
const loading = ref(false)
|
|
54
|
+
const noMore = ref(false)
|
|
55
|
+
const total = ref(0)
|
|
56
|
+
let skip = 0
|
|
57
|
+
const take = 25
|
|
58
|
+
|
|
59
|
+
// Simple debounce function
|
|
60
|
+
function debounce(fn, delay) {
|
|
61
|
+
let timeoutID = null
|
|
62
|
+
return function () {
|
|
63
|
+
clearTimeout(timeoutID)
|
|
64
|
+
timeoutID = setTimeout(() => fn.apply(this, arguments), delay)
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
console.log('end', Date.now() - start)
|
|
67
|
-
})
|
|
66
|
+
}
|
|
68
67
|
|
|
69
68
|
const normalized = (s) => (s || '').toString().toLowerCase()
|
|
70
|
-
|
|
71
69
|
const replaceChars = new Set('<>`*|#'.split(''))
|
|
72
|
-
const clean = s => [...s].map(c => replaceChars.has(c) ? ' ' : c).join('')
|
|
70
|
+
const clean = s => [...(s || '')].map(c => replaceChars.has(c) ? ' ' : c).join('')
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
72
|
+
const loadMore = async (reset = false) => {
|
|
73
|
+
if (reset) {
|
|
74
|
+
skip = 0
|
|
75
|
+
threads.value = []
|
|
76
|
+
noMore.value = false
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (loading.value || noMore.value) return
|
|
80
|
+
|
|
81
|
+
loading.value = true
|
|
82
|
+
try {
|
|
83
|
+
const query = {
|
|
84
|
+
take,
|
|
85
|
+
skip,
|
|
86
|
+
...(props.q ? { q: props.q } : {})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const results = await ctx.threads.query(query)
|
|
90
|
+
|
|
91
|
+
if (results.length < take) {
|
|
92
|
+
noMore.value = true
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (reset) {
|
|
96
|
+
threads.value = results
|
|
97
|
+
} else {
|
|
98
|
+
threads.value.push(...results)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
skip += results.length
|
|
102
|
+
|
|
103
|
+
total.value = threads.value.length
|
|
104
|
+
} catch (e) {
|
|
105
|
+
console.error("Failed to load threads", e)
|
|
106
|
+
} finally {
|
|
107
|
+
loading.value = false
|
|
108
|
+
}
|
|
88
109
|
}
|
|
89
110
|
|
|
111
|
+
const update = debounce(() => loadMore(true), 250)
|
|
112
|
+
|
|
113
|
+
onMounted(() => {
|
|
114
|
+
loadMore(true)
|
|
115
|
+
})
|
|
116
|
+
|
|
90
117
|
const onScroll = (e) => {
|
|
91
118
|
const el = e.target
|
|
92
|
-
if (el.scrollTop + el.clientHeight >= el.scrollHeight -
|
|
93
|
-
|
|
94
|
-
visibleCount.value = Math.min(visibleCount.value + defaultVisibleCount, filtered.value.length)
|
|
95
|
-
updateVisible()
|
|
96
|
-
}
|
|
119
|
+
if (el.scrollTop + el.clientHeight >= el.scrollHeight - 50) { // 50px threshold
|
|
120
|
+
loadMore()
|
|
97
121
|
}
|
|
98
122
|
}
|
|
99
123
|
|
|
100
124
|
watch(() => props.q, () => {
|
|
101
|
-
visibleCount.value = defaultVisibleCount
|
|
102
125
|
update()
|
|
103
126
|
})
|
|
104
127
|
|
|
105
128
|
const snippet = (t) => {
|
|
106
129
|
const highlight = (s) => clean(s).replace(new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'), `<mark>$1</mark>`)
|
|
107
130
|
const query = normalized(props.q)
|
|
108
|
-
if (!query) return (t.messages && t.messages.length) ? highlight(t.messages[t.messages.length-1].content) : ''
|
|
131
|
+
if (!query) return (t.messages && t.messages.length) ? highlight(t.messages[t.messages.length - 1].content) : ''
|
|
132
|
+
|
|
133
|
+
// Check title
|
|
109
134
|
if (normalized(t.title).includes(query)) return highlight(t.title)
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
|
|
136
|
+
// Check messages
|
|
137
|
+
if (Array.isArray(t.messages)) {
|
|
138
|
+
for (const m of t.messages) {
|
|
112
139
|
const c = normalized(m?.content)
|
|
113
|
-
if (c.includes(query)){
|
|
140
|
+
if (c.includes(query)) {
|
|
114
141
|
// return small excerpt around first match
|
|
115
142
|
const idx = c.indexOf(query)
|
|
116
143
|
const orig = (m?.content || '')
|
|
117
144
|
const start = Math.max(0, idx - 40)
|
|
118
145
|
const end = Math.min(orig.length, idx + query.length + 60)
|
|
119
|
-
const prefix = start>0 ? '…' : ''
|
|
120
|
-
const suffix = end<orig.length ? '…' : ''
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
return highlight(snippet)
|
|
146
|
+
const prefix = start > 0 ? '…' : ''
|
|
147
|
+
const suffix = end < orig.length ? '…' : ''
|
|
148
|
+
const snippetText = prefix + orig.slice(start, end) + suffix
|
|
149
|
+
return highlight(snippetText)
|
|
124
150
|
}
|
|
125
151
|
}
|
|
126
152
|
}
|
|
127
|
-
|
|
153
|
+
|
|
154
|
+
// Fallback to last message if no specific match found (e.g. matched on hidden metadata or partial?)
|
|
155
|
+
return (t.messages && t.messages.length) ? highlight(t.messages[t.messages.length - 1].content) : ''
|
|
128
156
|
}
|
|
129
157
|
|
|
130
|
-
const open = (id) => router.push(
|
|
158
|
+
const open = (id) => router.push(`${ai.base}/c/${id}`)
|
|
131
159
|
const formatDate = (iso) => new Date(iso).toLocaleString()
|
|
132
160
|
|
|
133
161
|
return {
|
|
134
|
-
config,
|
|
135
162
|
threads,
|
|
136
|
-
|
|
137
|
-
|
|
163
|
+
loading,
|
|
164
|
+
total,
|
|
138
165
|
snippet,
|
|
139
166
|
open,
|
|
140
167
|
formatDate,
|
|
141
|
-
renderMarkdown,
|
|
142
168
|
onScroll,
|
|
143
169
|
}
|
|
144
170
|
}
|
|
@@ -151,16 +177,17 @@ export default {
|
|
|
151
177
|
template: `
|
|
152
178
|
<div class="flex flex-col h-full w-full">
|
|
153
179
|
<!-- Header -->
|
|
154
|
-
<div class="border-b border-gray-200 bg-white px-4 py-3 min-h-16">
|
|
180
|
+
<div class="border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-4 py-3 min-h-16">
|
|
155
181
|
<div class="max-w-6xl mx-auto flex items-center justify-between gap-3">
|
|
156
|
-
<
|
|
157
|
-
<div class="flex-1 flex items-center gap-2">
|
|
182
|
+
<label for="search-history" class="cursor-pointer text-lg font-semibold text-gray-900 dark:text-gray-100">Search History</label>
|
|
183
|
+
<div class="flex-1 flex items-center gap-2 max-w-sm">
|
|
158
184
|
<input
|
|
185
|
+
id="search-history"
|
|
159
186
|
v-model="q"
|
|
160
187
|
type="search"
|
|
161
188
|
placeholder="Search titles and messages..."
|
|
162
189
|
spellcheck="false"
|
|
163
|
-
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm placeholder-gray-500 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
190
|
+
class="w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 px-3 py-2 text-sm placeholder-gray-500 dark:placeholder-gray-400 focus:border-blue-500 dark:focus:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:focus:ring-blue-400"
|
|
164
191
|
/>
|
|
165
192
|
</div>
|
|
166
193
|
</div>
|
|
@@ -1,24 +1,32 @@
|
|
|
1
|
-
import { onMounted } from 'vue'
|
|
1
|
+
import { onMounted, inject } from 'vue'
|
|
2
2
|
import { useRouter } from 'vue-router'
|
|
3
|
-
import {
|
|
3
|
+
import { appendQueryString } from '@servicestack/client'
|
|
4
|
+
import ThreadStore from './threadStore.mjs'
|
|
5
|
+
import Recents from './Recents.mjs'
|
|
6
|
+
|
|
7
|
+
let ext
|
|
4
8
|
|
|
5
9
|
// Thread Item Component
|
|
6
10
|
const ThreadItem = {
|
|
7
11
|
template: `
|
|
8
12
|
<div
|
|
9
13
|
class="group relative mx-2 mb-1 rounded-md cursor-pointer transition-colors border border-transparent"
|
|
10
|
-
:class="isActive ? 'bg-blue-100 border-blue-200' : 'hover:bg-gray-100'"
|
|
14
|
+
:class="isActive ? 'bg-blue-100 dark:bg-blue-900 border-blue-200 dark:border-blue-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800'"
|
|
11
15
|
@click="$emit('select', thread.id)"
|
|
12
16
|
>
|
|
13
17
|
<div class="flex items-center px-3 py-2">
|
|
14
18
|
<div class="flex-1 min-w-0">
|
|
15
|
-
<div class="text-sm font-medium text-gray-900 truncate" :title="thread.title">
|
|
19
|
+
<div class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate" :title="thread.title">
|
|
16
20
|
{{ thread.title }}
|
|
17
21
|
</div>
|
|
18
|
-
<div class="text-xs text-gray-500 truncate">
|
|
19
|
-
{{
|
|
22
|
+
<div class="text-xs text-gray-500 dark:text-gray-400 truncate">
|
|
23
|
+
<span>{{ $fmt.relativeTime(thread.updatedAt) }} • {{ thread.messages.length }} msgs</span>
|
|
24
|
+
<span v-if="thread.stats?.inputTokens" :title="$fmt.statsTitle(thread.stats)">
|
|
25
|
+
• {{ $fmt.humanifyNumber(thread.stats.inputTokens + thread.stats.outputTokens) }} toks
|
|
26
|
+
{{ thread.stats.cost ? ' ' + $fmt.cost(thread.stats.cost) : '' }}
|
|
27
|
+
</span>
|
|
20
28
|
</div>
|
|
21
|
-
<div v-if="thread.model" class="text-xs text-blue-600 truncate">
|
|
29
|
+
<div v-if="thread.model" class="text-xs text-blue-600 dark:text-blue-400 truncate">
|
|
22
30
|
{{ thread.model }}
|
|
23
31
|
</div>
|
|
24
32
|
</div>
|
|
@@ -26,7 +34,7 @@ const ThreadItem = {
|
|
|
26
34
|
<!-- Delete button (shown on hover) -->
|
|
27
35
|
<button type="button"
|
|
28
36
|
@click.stop="$emit('delete', thread.id)"
|
|
29
|
-
class="opacity-0 group-hover:opacity-100 ml-2 p-1 rounded text-gray-400 hover:text-red-600 hover:bg-red-50 transition-all"
|
|
37
|
+
class="opacity-0 group-hover:opacity-100 ml-2 p-1 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"
|
|
30
38
|
title="Delete conversation"
|
|
31
39
|
>
|
|
32
40
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -51,38 +59,34 @@ const ThreadItem = {
|
|
|
51
59
|
emits: ['select', 'delete'],
|
|
52
60
|
|
|
53
61
|
setup() {
|
|
54
|
-
const formatRelativeTime = (timestamp) => {
|
|
55
|
-
const now = new Date()
|
|
56
|
-
const date = new Date(timestamp)
|
|
57
|
-
const diffInSeconds = Math.floor((now - date) / 1000)
|
|
58
|
-
|
|
59
|
-
if (diffInSeconds < 60) return 'Just now'
|
|
60
|
-
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`
|
|
61
|
-
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`
|
|
62
|
-
if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`
|
|
63
|
-
|
|
64
|
-
return date.toLocaleDateString()
|
|
65
|
-
}
|
|
66
|
-
|
|
67
62
|
return {
|
|
68
|
-
formatRelativeTime
|
|
69
63
|
}
|
|
70
64
|
}
|
|
71
65
|
}
|
|
72
66
|
|
|
73
67
|
const GroupedThreads = {
|
|
74
|
-
components: {
|
|
75
|
-
ThreadItem,
|
|
76
|
-
},
|
|
77
68
|
template: `
|
|
78
69
|
<!-- Today -->
|
|
79
70
|
<div v-if="groupedThreads.today.length > 0" class="mb-4">
|
|
80
|
-
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider select-none">Today</h3>
|
|
71
|
+
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider select-none">Today</h3>
|
|
81
72
|
<ThreadItem
|
|
82
73
|
v-for="thread in groupedThreads.today"
|
|
83
74
|
:key="thread.id"
|
|
84
75
|
:thread="thread"
|
|
85
|
-
:is-active="currentThread?.id
|
|
76
|
+
:is-active="currentThread?.id == thread.id"
|
|
77
|
+
@select="$emit('select', $event)"
|
|
78
|
+
@delete="$emit('delete', $event)"
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<!-- Yesterday -->
|
|
83
|
+
<div v-if="groupedThreads.yesterday.length > 0" class="mb-4">
|
|
84
|
+
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider select-none">Yesterday</h3>
|
|
85
|
+
<ThreadItem
|
|
86
|
+
v-for="thread in groupedThreads.yesterday"
|
|
87
|
+
:key="thread.id"
|
|
88
|
+
:thread="thread"
|
|
89
|
+
:is-active="currentThread?.id == thread.id"
|
|
86
90
|
@select="$emit('select', $event)"
|
|
87
91
|
@delete="$emit('delete', $event)"
|
|
88
92
|
/>
|
|
@@ -90,12 +94,12 @@ const GroupedThreads = {
|
|
|
90
94
|
|
|
91
95
|
<!-- Last 7 Days -->
|
|
92
96
|
<div v-if="groupedThreads.lastWeek.length > 0" class="mb-4">
|
|
93
|
-
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider select-none">Last 7 Days</h3>
|
|
97
|
+
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider select-none">Last 7 Days</h3>
|
|
94
98
|
<ThreadItem
|
|
95
99
|
v-for="thread in groupedThreads.lastWeek"
|
|
96
100
|
:key="thread.id"
|
|
97
101
|
:thread="thread"
|
|
98
|
-
:is-active="currentThread?.id
|
|
102
|
+
:is-active="currentThread?.id == thread.id"
|
|
99
103
|
@select="$emit('select', $event)"
|
|
100
104
|
@delete="$emit('delete', $event)"
|
|
101
105
|
/>
|
|
@@ -103,12 +107,12 @@ const GroupedThreads = {
|
|
|
103
107
|
|
|
104
108
|
<!-- Last 30 Days -->
|
|
105
109
|
<div v-if="groupedThreads.lastMonth.length > 0" class="mb-4">
|
|
106
|
-
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider select-none">Last 30 Days</h3>
|
|
110
|
+
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider select-none">Last 30 Days</h3>
|
|
107
111
|
<ThreadItem
|
|
108
112
|
v-for="thread in groupedThreads.lastMonth"
|
|
109
113
|
:key="thread.id"
|
|
110
114
|
:thread="thread"
|
|
111
|
-
:is-active="currentThread?.id
|
|
115
|
+
:is-active="currentThread?.id == thread.id"
|
|
112
116
|
@select="$emit('select', $event)"
|
|
113
117
|
@delete="$emit('delete', $event)"
|
|
114
118
|
/>
|
|
@@ -116,19 +120,19 @@ const GroupedThreads = {
|
|
|
116
120
|
|
|
117
121
|
<!-- Older (grouped by month/year) -->
|
|
118
122
|
<div v-for="(monthThreads, monthKey) in groupedThreads.older" :key="monthKey" class="mb-4">
|
|
119
|
-
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider select-none">{{ monthKey }}</h3>
|
|
123
|
+
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider select-none">{{ monthKey }}</h3>
|
|
120
124
|
<ThreadItem
|
|
121
125
|
v-for="thread in monthThreads"
|
|
122
126
|
:key="thread.id"
|
|
123
127
|
:thread="thread"
|
|
124
|
-
:is-active="currentThread?.id
|
|
128
|
+
:is-active="currentThread?.id == thread.id"
|
|
125
129
|
@select="$emit('select', $event)"
|
|
126
130
|
@delete="$emit('delete', $event)"
|
|
127
131
|
/>
|
|
128
132
|
</div>
|
|
129
133
|
<div class="mb-4 flex w-full justify-center">
|
|
130
|
-
<button @click="$router.push('/recents')" type="button"
|
|
131
|
-
class="flex text-sm space-x-1 font-semibold text-gray-900 hover:text-blue-600 focus:outline-none transition-colors">
|
|
134
|
+
<button @click="$router.push($ai.base + '/recents')" type="button"
|
|
135
|
+
class="flex text-sm space-x-1 font-semibold text-gray-900 dark:text-gray-100 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none transition-colors">
|
|
132
136
|
<svg class="size-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="currentColor" d="M8 2.19c3.13 0 5.68 2.25 5.68 5s-2.55 5-5.68 5a5.7 5.7 0 0 1-1.89-.29l-.75-.26l-.56.56a14 14 0 0 1-2 1.55a.13.13 0 0 1-.07 0v-.06a6.58 6.58 0 0 0 .15-4.29a5.25 5.25 0 0 1-.55-2.16c0-2.77 2.55-5 5.68-5M8 .94c-3.83 0-6.93 2.81-6.93 6.27a6.4 6.4 0 0 0 .64 2.64a5.53 5.53 0 0 1-.18 3.48a1.32 1.32 0 0 0 2 1.5a15 15 0 0 0 2.16-1.71a6.8 6.8 0 0 0 2.31.36c3.83 0 6.93-2.81 6.93-6.27S11.83.94 8 .94"></path><ellipse cx="5.2" cy="7.7" fill="currentColor" rx=".8" ry=".75"></ellipse><ellipse cx="8" cy="7.7" fill="currentColor" rx=".8" ry=".75"></ellipse><ellipse cx="10.8" cy="7.7" fill="currentColor" rx=".8" ry=".75"></ellipse></svg>
|
|
133
137
|
<span>All Chats</span>
|
|
134
138
|
</button>
|
|
@@ -144,58 +148,47 @@ const GroupedThreads = {
|
|
|
144
148
|
emits: ['select', 'delete'],
|
|
145
149
|
}
|
|
146
150
|
|
|
147
|
-
const
|
|
148
|
-
components: {
|
|
149
|
-
GroupedThreads,
|
|
150
|
-
ThreadItem,
|
|
151
|
-
},
|
|
151
|
+
const ThreadsSidebar = {
|
|
152
152
|
template: `
|
|
153
|
-
<div class="flex flex-col h-full
|
|
154
|
-
|
|
155
|
-
<div class="flex-shrink-0 px-4 py-4 border-b border-gray-200 bg-white min-h-16 select-none">
|
|
156
|
-
<div class="flex items-center justify-between">
|
|
157
|
-
<button type="button"
|
|
158
|
-
@click="goToInitialState"
|
|
159
|
-
class="text-lg font-semibold text-gray-900 hover:text-blue-600 focus:outline-none transition-colors"
|
|
160
|
-
title="Go back to initial state"
|
|
161
|
-
>
|
|
162
|
-
History
|
|
163
|
-
</button>
|
|
164
|
-
<button type="button"
|
|
165
|
-
@click="createNewThread"
|
|
166
|
-
class="text-gray-900 hover:text-blue-600 focus:outline-none transition-colors"
|
|
167
|
-
title="New Chat"
|
|
168
|
-
>
|
|
169
|
-
<svg class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></g></svg>
|
|
170
|
-
</button>
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
|
|
153
|
+
<div class="flex flex-col h-full">
|
|
154
|
+
<Brand />
|
|
174
155
|
<!-- Thread List -->
|
|
175
156
|
<div class="flex-1 overflow-y-auto">
|
|
176
|
-
<div v-if="isLoading" class="p-4 text-center text-gray-500">
|
|
177
|
-
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600 mx-auto"></div>
|
|
157
|
+
<div v-if="isLoading" class="p-4 text-center text-gray-500 dark:text-gray-400">
|
|
158
|
+
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600 dark:border-blue-400 mx-auto"></div>
|
|
178
159
|
<p class="mt-2 text-sm">Loading threads...</p>
|
|
179
160
|
</div>
|
|
180
161
|
|
|
181
|
-
<div v-else-if="threads.length === 0" class="p-4 text-center text-gray-500">
|
|
162
|
+
<div v-else-if="threads.length === 0" class="p-4 text-center text-gray-500 dark:text-gray-400">
|
|
182
163
|
<div class="mb-2 flex justify-center">
|
|
183
164
|
<svg class="size-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="currentColor" d="M8 2.19c3.13 0 5.68 2.25 5.68 5s-2.55 5-5.68 5a5.7 5.7 0 0 1-1.89-.29l-.75-.26l-.56.56a14 14 0 0 1-2 1.55a.13.13 0 0 1-.07 0v-.06a6.58 6.58 0 0 0 .15-4.29a5.25 5.25 0 0 1-.55-2.16c0-2.77 2.55-5 5.68-5M8 .94c-3.83 0-6.93 2.81-6.93 6.27a6.4 6.4 0 0 0 .64 2.64a5.53 5.53 0 0 1-.18 3.48a1.32 1.32 0 0 0 2 1.5a15 15 0 0 0 2.16-1.71a6.8 6.8 0 0 0 2.31.36c3.83 0 6.93-2.81 6.93-6.27S11.83.94 8 .94"/><ellipse cx="5.2" cy="7.7" fill="currentColor" rx=".8" ry=".75"/><ellipse cx="8" cy="7.7" fill="currentColor" rx=".8" ry=".75"/><ellipse cx="10.8" cy="7.7" fill="currentColor" rx=".8" ry=".75"/></svg>
|
|
184
165
|
</div>
|
|
185
166
|
<p class="text-sm">No conversations yet</p>
|
|
186
|
-
<p class="text-xs text-gray-400 mt-1">Start a new chat to begin</p>
|
|
167
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">Start a new chat to begin</p>
|
|
187
168
|
</div>
|
|
188
169
|
|
|
189
|
-
<div v-else class="py-2">
|
|
190
|
-
|
|
191
|
-
|
|
170
|
+
<div v-else class="relative py-2">
|
|
171
|
+
|
|
172
|
+
<div class="flex items-center space-x-2 absolute top-2 right-2">
|
|
173
|
+
<button type="button"
|
|
174
|
+
@click="createNewThread"
|
|
175
|
+
class="text-gray-900 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none transition-colors"
|
|
176
|
+
title="New Chat"
|
|
177
|
+
>
|
|
178
|
+
<svg class="size-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></g></svg>
|
|
179
|
+
</button>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<GroupedThreads :currentThread="currentThread" :groupedThreads="$threads.getGroupedThreads(50)"
|
|
183
|
+
@select="selectThread" @delete="deleteThread" />
|
|
192
184
|
</div>
|
|
193
185
|
</div>
|
|
194
186
|
</div>
|
|
195
187
|
`,
|
|
196
|
-
setup() {
|
|
188
|
+
setup(props) {
|
|
189
|
+
const ctx = inject('ctx')
|
|
190
|
+
const ai = ctx.ai
|
|
197
191
|
const router = useRouter()
|
|
198
|
-
const threadStore = useThreadStore()
|
|
199
192
|
const {
|
|
200
193
|
threads,
|
|
201
194
|
currentThread,
|
|
@@ -205,14 +198,14 @@ const Sidebar = {
|
|
|
205
198
|
createThread,
|
|
206
199
|
deleteThread: deleteThreadFromStore,
|
|
207
200
|
clearCurrentThread
|
|
208
|
-
} =
|
|
201
|
+
} = ctx.threads
|
|
209
202
|
|
|
210
203
|
onMounted(async () => {
|
|
211
204
|
await loadThreads()
|
|
212
205
|
})
|
|
213
206
|
|
|
214
207
|
const selectThread = async (threadId) => {
|
|
215
|
-
router.push(
|
|
208
|
+
router.push(`${ai.base}/c/${threadId}`)
|
|
216
209
|
}
|
|
217
210
|
|
|
218
211
|
const deleteThread = async (threadId) => {
|
|
@@ -220,23 +213,25 @@ const Sidebar = {
|
|
|
220
213
|
const wasCurrent = currentThread?.value?.id === threadId
|
|
221
214
|
await deleteThreadFromStore(threadId)
|
|
222
215
|
if (wasCurrent) {
|
|
223
|
-
router.push(
|
|
216
|
+
router.push(`${ai.base}/`)
|
|
224
217
|
}
|
|
225
218
|
}
|
|
226
219
|
}
|
|
227
220
|
|
|
228
221
|
const createNewThread = async () => {
|
|
229
|
-
|
|
230
|
-
router.push(`/c/${newThread.id}`)
|
|
222
|
+
ctx.threads.startNewThread({ title: 'New Chat', model: ctx.chat.getSelectedModel(), redirect: true })
|
|
231
223
|
}
|
|
232
224
|
|
|
233
225
|
const goToInitialState = () => {
|
|
234
226
|
clearCurrentThread()
|
|
235
|
-
|
|
227
|
+
ctx.to(`/`)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const goToAnalytics = () => {
|
|
231
|
+
ctx.to(`/analytics`)
|
|
236
232
|
}
|
|
237
233
|
|
|
238
234
|
return {
|
|
239
|
-
threadStore,
|
|
240
235
|
threads,
|
|
241
236
|
currentThread,
|
|
242
237
|
isLoading,
|
|
@@ -244,9 +239,85 @@ const Sidebar = {
|
|
|
244
239
|
selectThread,
|
|
245
240
|
deleteThread,
|
|
246
241
|
createNewThread,
|
|
247
|
-
goToInitialState
|
|
242
|
+
goToInitialState,
|
|
243
|
+
goToAnalytics,
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function useRequests(ext) {
|
|
249
|
+
async function query(query) {
|
|
250
|
+
return (await ext.getJson(appendQueryString(`/requests`, query))).response || []
|
|
251
|
+
}
|
|
252
|
+
async function deleteById(requestId) {
|
|
253
|
+
if (!requestId) {
|
|
254
|
+
throw new Error('Request ID is required')
|
|
248
255
|
}
|
|
256
|
+
return await ext.deleteJson(`/requests/${requestId}`)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function getThreadIds(query) {
|
|
260
|
+
return (await ext.getJson(appendQueryString(`/requests?fields=threadId¬_null=threadId&as=column&take=10000`, query))).response || []
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function getSummary() {
|
|
264
|
+
return (await ext.getJson(`/requests/summary`)).response
|
|
265
|
+
}
|
|
266
|
+
async function getDailySummary(day) {
|
|
267
|
+
return (await ext.getJson(`/requests/summary/${day}`)).response
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Get unique values for filter options
|
|
271
|
+
async function getFilterOptions() {
|
|
272
|
+
const results = await query({
|
|
273
|
+
select: 'distinct',
|
|
274
|
+
fields: 'model,provider',
|
|
275
|
+
not_null: 'model,provider',
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
if (results) {
|
|
279
|
+
const models = [...new Set(results.map(r => r.model).filter(m => m))].sort()
|
|
280
|
+
const providers = [...new Set(results.map(r => r.provider).filter(p => p))].sort()
|
|
281
|
+
console.log('getFilterOptions', models, providers)
|
|
282
|
+
return {
|
|
283
|
+
models,
|
|
284
|
+
providers
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
query,
|
|
291
|
+
deleteById,
|
|
292
|
+
getThreadIds,
|
|
293
|
+
getSummary,
|
|
294
|
+
getDailySummary,
|
|
295
|
+
getFilterOptions,
|
|
249
296
|
}
|
|
250
297
|
}
|
|
251
298
|
|
|
252
|
-
export default
|
|
299
|
+
export default {
|
|
300
|
+
order: -100,
|
|
301
|
+
|
|
302
|
+
install(ctx) {
|
|
303
|
+
ext = ctx.scope('app')
|
|
304
|
+
ctx.components({
|
|
305
|
+
ThreadsSidebar,
|
|
306
|
+
ThreadItem,
|
|
307
|
+
GroupedThreads,
|
|
308
|
+
Recents,
|
|
309
|
+
})
|
|
310
|
+
ctx.routes.push(...[
|
|
311
|
+
{ path: '/recents', component: Recents },
|
|
312
|
+
])
|
|
313
|
+
ThreadStore.install(ctx)
|
|
314
|
+
|
|
315
|
+
ctx.setGlobals({
|
|
316
|
+
requests: useRequests(ext)
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
ctx.setLayout({
|
|
320
|
+
left: 'ThreadsSidebar',
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
}
|