llms-py 2.0.34__py3-none-any.whl → 3.0.0__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 +3 -1
- llms/__pycache__/__init__.cpython-312.pyc +0 -0
- llms/__pycache__/__init__.cpython-313.pyc +0 -0
- llms/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/__pycache__/__main__.cpython-312.pyc +0 -0
- llms/__pycache__/__main__.cpython-314.pyc +0 -0
- llms/__pycache__/llms.cpython-312.pyc +0 -0
- llms/__pycache__/main.cpython-312.pyc +0 -0
- llms/__pycache__/main.cpython-313.pyc +0 -0
- llms/__pycache__/main.cpython-314.pyc +0 -0
- llms/__pycache__/plugins.cpython-314.pyc +0 -0
- llms/{ui/Analytics.mjs → extensions/analytics/ui/index.mjs} +154 -238
- llms/extensions/app/README.md +20 -0
- llms/extensions/app/__init__.py +530 -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 +644 -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 → extensions/app/ui}/Recents.mjs +91 -65
- llms/{ui/Sidebar.mjs → extensions/app/ui/index.mjs} +124 -58
- llms/extensions/app/ui/threadStore.mjs +411 -0
- llms/extensions/core_tools/CALCULATOR.md +32 -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/README.md +61 -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 +482 -0
- llms/extensions/katex/README.md +39 -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/extensions/providers/__pycache__/nvidia.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/openai.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
- llms/extensions/providers/anthropic.py +229 -0
- llms/extensions/providers/chutes.py +155 -0
- llms/extensions/providers/google.py +378 -0
- llms/extensions/providers/nvidia.py +105 -0
- llms/extensions/providers/openai.py +156 -0
- llms/extensions/providers/openrouter.py +72 -0
- llms/extensions/system_prompts/README.md +22 -0
- 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 +280 -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/extensions/tools/ui/index.mjs +204 -0
- llms/index.html +35 -77
- llms/llms.json +357 -1186
- llms/main.py +2847 -999
- llms/providers-extra.json +356 -0
- llms/providers.json +1 -0
- llms/ui/App.mjs +151 -60
- llms/ui/ai.mjs +132 -60
- llms/ui/app.css +2173 -161
- llms/ui/ctx.mjs +365 -0
- llms/ui/index.mjs +129 -0
- llms/ui/lib/charts.mjs +9 -13
- llms/ui/lib/servicestack-vue.mjs +3 -3
- llms/ui/lib/vue.min.mjs +10 -9
- llms/ui/lib/vue.mjs +1796 -1635
- llms/ui/markdown.mjs +18 -7
- llms/ui/modules/chat/ChatBody.mjs +691 -0
- llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +9 -9
- llms/ui/modules/chat/index.mjs +828 -0
- llms/ui/modules/layout.mjs +243 -0
- llms/ui/modules/model-selector.mjs +851 -0
- llms/ui/tailwind.input.css +496 -80
- llms/ui/utils.mjs +161 -93
- {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/METADATA +1 -1
- llms_py-3.0.0.dist-info/RECORD +202 -0
- llms/ui/Avatar.mjs +0 -85
- llms/ui/Brand.mjs +0 -52
- llms/ui/ChatPrompt.mjs +0 -590
- llms/ui/Main.mjs +0 -823
- llms/ui/ModelSelector.mjs +0 -78
- llms/ui/OAuthSignIn.mjs +0 -92
- llms/ui/ProviderIcon.mjs +0 -30
- llms/ui/ProviderStatus.mjs +0 -105
- llms/ui/SignIn.mjs +0 -64
- llms/ui/SystemPromptEditor.mjs +0 -31
- llms/ui/SystemPromptSelector.mjs +0 -56
- llms/ui/Welcome.mjs +0 -8
- llms/ui/threadStore.mjs +0 -563
- llms/ui.json +0 -1069
- llms_py-2.0.34.dist-info/RECORD +0 -48
- {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/WHEEL +0 -0
- {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/entry_points.txt +0 -0
- {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,29 +1,24 @@
|
|
|
1
|
-
import { ref,
|
|
1
|
+
import { ref, watch, nextTick, computed, inject, onMounted, onUnmounted } from 'vue'
|
|
2
2
|
import { useRouter, useRoute } from 'vue-router'
|
|
3
|
-
import { useFormatters } from "@servicestack/vue"
|
|
4
3
|
import { leftPart } from '@servicestack/client'
|
|
5
4
|
import { Chart, registerables } from "chart.js"
|
|
6
|
-
import { useThreadStore } from './threadStore.mjs'
|
|
7
|
-
import { formatCost } from './utils.mjs'
|
|
8
5
|
Chart.register(...registerables)
|
|
9
6
|
|
|
10
|
-
const { humanifyNumber, humanifyMs } = useFormatters()
|
|
11
|
-
|
|
12
7
|
export const colors = [
|
|
13
|
-
{ background: 'rgba(54, 162, 235, 0.2)',
|
|
14
|
-
{ background: 'rgba(255, 99, 132, 0.2)',
|
|
8
|
+
{ background: 'rgba(54, 162, 235, 0.2)', border: 'rgb(54, 162, 235)' }, //blue
|
|
9
|
+
{ background: 'rgba(255, 99, 132, 0.2)', border: 'rgb(255, 99, 132)' },
|
|
15
10
|
{ background: 'rgba(153, 102, 255, 0.2)', border: 'rgb(153, 102, 255)' },
|
|
16
|
-
{ background: 'rgba(54, 162, 235, 0.2)',
|
|
17
|
-
{ background: 'rgba(255, 159, 64, 0.2)',
|
|
18
|
-
{ background: 'rgba(67, 56, 202, 0.2)',
|
|
19
|
-
{ background: 'rgba(255, 99, 132, 0.2)',
|
|
20
|
-
{ background: 'rgba(14, 116, 144, 0.2)',
|
|
21
|
-
{ background: 'rgba(162, 28, 175, 0.2)',
|
|
11
|
+
{ background: 'rgba(54, 162, 235, 0.2)', border: 'rgb(54, 162, 235)' },
|
|
12
|
+
{ background: 'rgba(255, 159, 64, 0.2)', border: 'rgb(255, 159, 64)' },
|
|
13
|
+
{ background: 'rgba(67, 56, 202, 0.2)', border: 'rgb(67, 56, 202)' },
|
|
14
|
+
{ background: 'rgba(255, 99, 132, 0.2)', border: 'rgb(255, 99, 132)' },
|
|
15
|
+
{ background: 'rgba(14, 116, 144, 0.2)', border: 'rgb(14, 116, 144)' },
|
|
16
|
+
{ background: 'rgba(162, 28, 175, 0.2)', border: 'rgb(162, 28, 175)' },
|
|
22
17
|
{ background: 'rgba(201, 203, 207, 0.2)', border: 'rgb(201, 203, 207)' },
|
|
23
18
|
]
|
|
24
19
|
|
|
25
20
|
const MonthSelector = {
|
|
26
|
-
template
|
|
21
|
+
template: `
|
|
27
22
|
<div class="flex flex-col sm:flex-row gap-2 sm:gap-4 items-stretch sm:items-center w-full sm:w-auto">
|
|
28
23
|
<!-- Months Row -->
|
|
29
24
|
<div class="flex gap-1 sm:gap-2 flex-wrap justify-center overflow-x-auto">
|
|
@@ -51,7 +46,7 @@ const MonthSelector = {
|
|
|
51
46
|
</div>
|
|
52
47
|
`,
|
|
53
48
|
props: {
|
|
54
|
-
dailyData:
|
|
49
|
+
dailyData: Object,
|
|
55
50
|
},
|
|
56
51
|
setup(props) {
|
|
57
52
|
const router = useRouter()
|
|
@@ -109,12 +104,9 @@ const MonthSelector = {
|
|
|
109
104
|
}
|
|
110
105
|
}
|
|
111
106
|
|
|
112
|
-
export
|
|
113
|
-
components: {
|
|
114
|
-
MonthSelector,
|
|
115
|
-
},
|
|
107
|
+
export const Analytics = {
|
|
116
108
|
template: `
|
|
117
|
-
<div class="flex flex-col
|
|
109
|
+
<div class="flex flex-col w-full">
|
|
118
110
|
<!-- Header -->
|
|
119
111
|
<div class="border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-2 sm:px-4 py-3">
|
|
120
112
|
<div
|
|
@@ -158,14 +150,14 @@ export default {
|
|
|
158
150
|
</div>
|
|
159
151
|
|
|
160
152
|
<!-- Content -->
|
|
161
|
-
<div class="flex-1
|
|
153
|
+
<div class="flex-1 bg-gray-50 dark:bg-gray-900" :class="activeTab === 'activity' ? 'p-0' : 'p-4'">
|
|
162
154
|
|
|
163
|
-
<div :class="activeTab === 'activity' ? '
|
|
155
|
+
<div :class="activeTab === 'activity' ? '' : 'max-w-6xl mx-auto'">
|
|
164
156
|
<!-- Stats Summary (hidden for Activity tab) -->
|
|
165
157
|
<div v-if="activeTab !== 'activity'" class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
|
166
158
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
|
167
159
|
<div class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Cost</div>
|
|
168
|
-
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{
|
|
160
|
+
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{ $fmt.cost(totalCost) }}</div>
|
|
169
161
|
</div>
|
|
170
162
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
|
171
163
|
<div class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Requests</div>
|
|
@@ -173,11 +165,11 @@ export default {
|
|
|
173
165
|
</div>
|
|
174
166
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
|
175
167
|
<div class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Input Tokens</div>
|
|
176
|
-
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{ humanifyNumber(totalInputTokens) }}</div>
|
|
168
|
+
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{ $fmt.humanifyNumber(totalInputTokens) }}</div>
|
|
177
169
|
</div>
|
|
178
170
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
|
179
171
|
<div class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Output Tokens</div>
|
|
180
|
-
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{ humanifyNumber(totalOutputTokens) }}</div>
|
|
172
|
+
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{ $fmt.humanifyNumber(totalOutputTokens) }}</div>
|
|
181
173
|
</div>
|
|
182
174
|
</div>
|
|
183
175
|
|
|
@@ -224,11 +216,11 @@ export default {
|
|
|
224
216
|
{{ new Date(selectedDay).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' }) }}
|
|
225
217
|
</div>
|
|
226
218
|
<div class="flex flex-wrap gap-x-2 gap-y-1">
|
|
227
|
-
<span>{{
|
|
219
|
+
<span>{{ $fmt.cost(allDailyData[selectedDay]?.cost || 0) }}</span>
|
|
228
220
|
<span>·</span>
|
|
229
221
|
<span>{{ allDailyData[selectedDay]?.requests || 0 }} Requests</span>
|
|
230
222
|
<span>·</span>
|
|
231
|
-
<span>{{ humanifyNumber(allDailyData[selectedDay]?.inputTokens || 0) }} -> {{ humanifyNumber(allDailyData[selectedDay]?.outputTokens || 0) }} Tokens</span>
|
|
223
|
+
<span>{{ $fmt.humanifyNumber(allDailyData[selectedDay]?.inputTokens || 0) }} -> {{ $fmt.humanifyNumber(allDailyData[selectedDay]?.outputTokens || 0) }} Tokens</span>
|
|
232
224
|
</div>
|
|
233
225
|
</div>
|
|
234
226
|
|
|
@@ -291,7 +283,7 @@ export default {
|
|
|
291
283
|
</div>
|
|
292
284
|
|
|
293
285
|
<!-- Activity Tab - Full Page Layout -->
|
|
294
|
-
<div v-if="activeTab === 'activity'" class="
|
|
286
|
+
<div v-if="activeTab === 'activity'" class="flex flex-col bg-white dark:bg-gray-800">
|
|
295
287
|
<!-- Filters Bar -->
|
|
296
288
|
<div class="flex-shrink-0 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 sm:px-6 py-4">
|
|
297
289
|
<div class="flex flex-wrap gap-2 sm:gap-4 items-end">
|
|
@@ -315,7 +307,7 @@ export default {
|
|
|
315
307
|
|
|
316
308
|
<div class="flex flex-col flex-1 min-w-[140px] sm:flex-initial">
|
|
317
309
|
<select v-model="sortBy" class="px-3 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 w-full">
|
|
318
|
-
<option value="
|
|
310
|
+
<option value="createdAt">Date (Newest)</option>
|
|
319
311
|
<option value="cost">Cost (Highest)</option>
|
|
320
312
|
<option value="duration">Duration (Longest)</option>
|
|
321
313
|
<option value="totalTokens">Tokens (Most)</option>
|
|
@@ -329,15 +321,15 @@ export default {
|
|
|
329
321
|
</div>
|
|
330
322
|
|
|
331
323
|
<!-- Requests List with Infinite Scroll -->
|
|
332
|
-
<div class="flex-1
|
|
333
|
-
<div v-if="isActivityLoading && activityRequests.length === 0" class="flex items-center justify-center h-full">
|
|
324
|
+
<div class="flex-1">
|
|
325
|
+
<div v-if="isActivityLoading && activityRequests.length === 0" class="mt-8 flex items-center justify-center h-full">
|
|
334
326
|
<div class="text-center">
|
|
335
327
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
|
|
336
328
|
<p class="mt-4 text-gray-600 dark:text-gray-400">Loading requests...</p>
|
|
337
329
|
</div>
|
|
338
330
|
</div>
|
|
339
331
|
|
|
340
|
-
<div v-else-if="activityRequests.length === 0" class="flex items-center justify-center h-full">
|
|
332
|
+
<div v-else-if="activityRequests.length === 0" class="mt-4 flex items-center justify-center h-full">
|
|
341
333
|
<p class="text-gray-500 dark:text-gray-400">No requests found</p>
|
|
342
334
|
</div>
|
|
343
335
|
|
|
@@ -347,38 +339,42 @@ export default {
|
|
|
347
339
|
<div class="flex-1 min-w-0 w-full">
|
|
348
340
|
<div class="flex flex-col sm:flex-row justify-between gap-2 mb-2">
|
|
349
341
|
<div class="flex items-center gap-2 flex-wrap">
|
|
350
|
-
<span class="text-xs px-2 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300 rounded font-medium">{{ request.model }}</span>
|
|
351
|
-
<span class="text-xs px-2 py-1 bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300 rounded font-medium">{{ request.provider }}</span>
|
|
342
|
+
<span v-if="request.model" class="text-xs px-2 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300 rounded font-medium">{{ request.model }}</span>
|
|
343
|
+
<span v-if="request.provider" class="text-xs px-2 py-1 bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300 rounded font-medium">{{ request.provider }}</span>
|
|
352
344
|
<span v-if="request.providerRef" class="text-xs px-2 py-1 bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 rounded font-medium">{{ request.providerRef }}</span>
|
|
353
345
|
<span v-if="request.finishReason" class="text-xs px-2 py-1 bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300 rounded font-medium">{{ request.finishReason }}</span>
|
|
354
346
|
</div>
|
|
355
347
|
<div class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">
|
|
356
|
-
{{ formatActivityDate(request.
|
|
348
|
+
{{ formatActivityDate(request.createdAt) }}
|
|
357
349
|
</div>
|
|
358
350
|
</div>
|
|
359
351
|
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100 truncate mb-3">
|
|
360
352
|
{{ request.title }}
|
|
361
353
|
</div>
|
|
362
354
|
|
|
363
|
-
<div class="
|
|
355
|
+
<div v-if="request.error" class="rounded-lg px-2 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 text-sm"
|
|
356
|
+
:title="request.error + '\\n' + (request.stacktrace || '')">
|
|
357
|
+
{{ request.error }}
|
|
358
|
+
</div>
|
|
359
|
+
<div v-else class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3 sm:gap-4">
|
|
364
360
|
<div :title="request.cost">
|
|
365
361
|
<div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Cost</div>
|
|
366
|
-
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{
|
|
362
|
+
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ $fmt.costLong(request.cost) }}</div>
|
|
367
363
|
</div>
|
|
368
364
|
<div class="col-span-2 sm:col-span-1">
|
|
369
365
|
<div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Tokens</div>
|
|
370
|
-
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
|
371
|
-
{{ humanifyNumber(request.inputTokens) }} -> {{ humanifyNumber(request.outputTokens) }}
|
|
372
|
-
<span v-if="request.inputCachedTokens" class="ml-1 text-xs text-gray-500 dark:text-gray-400">({{ humanifyNumber(request.inputCachedTokens) }} cached)</span>
|
|
366
|
+
<div v-if="request.inputTokens || request.outputTokens" class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
|
367
|
+
{{ $fmt.humanifyNumber(request.inputTokens || 0) }} -> {{ $fmt.humanifyNumber(request.outputTokens || 0) }}
|
|
368
|
+
<span v-if="request.inputCachedTokens" class="ml-1 text-xs text-gray-500 dark:text-gray-400">({{ $fmt.humanifyNumber(request.inputCachedTokens || 0) }} cached)</span>
|
|
373
369
|
</div>
|
|
374
370
|
</div>
|
|
375
371
|
<div>
|
|
376
372
|
<div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Duration</div>
|
|
377
|
-
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{
|
|
373
|
+
<div v-if="request.duration" class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ $fmt.humanifyMs(request.duration) }}</div>
|
|
378
374
|
</div>
|
|
379
375
|
<div>
|
|
380
376
|
<div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Speed</div>
|
|
381
|
-
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{
|
|
377
|
+
<div v-if="request.duration && request.outputTokens" class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ (request.outputTokens / (request.duration / 1000)).toFixed(1) + ' tok/s' }}</div>
|
|
382
378
|
</div>
|
|
383
379
|
</div>
|
|
384
380
|
</div>
|
|
@@ -401,6 +397,7 @@ export default {
|
|
|
401
397
|
No more requests to load
|
|
402
398
|
</div>
|
|
403
399
|
</div>
|
|
400
|
+
<div ref="scrollSentinel" class="h-4 w-full"></div>
|
|
404
401
|
</div>
|
|
405
402
|
</div>
|
|
406
403
|
</div>
|
|
@@ -408,10 +405,10 @@ export default {
|
|
|
408
405
|
</div>
|
|
409
406
|
`,
|
|
410
407
|
setup() {
|
|
408
|
+
const ctx = inject('ctx')
|
|
411
409
|
const router = useRouter()
|
|
412
410
|
const route = useRoute()
|
|
413
|
-
const
|
|
414
|
-
const { initDB } = threads
|
|
411
|
+
const analyticsData = ref()
|
|
415
412
|
|
|
416
413
|
// Initialize activeTab from URL query parameter, default to 'cost'
|
|
417
414
|
const activeTab = ref(route.query.tab || 'cost')
|
|
@@ -437,6 +434,9 @@ export default {
|
|
|
437
434
|
const selectedYear = computed(() => {
|
|
438
435
|
return route.query.year !== undefined ? parseInt(route.query.year) : currentDate.getFullYear()
|
|
439
436
|
})
|
|
437
|
+
const selectedYearMonth = computed(() => {
|
|
438
|
+
return `${selectedYear.value}-${selectedMonth.value < 10 ? '0' + selectedMonth.value : selectedMonth.value}`
|
|
439
|
+
})
|
|
440
440
|
const allDailyData = ref({}) // Store all data for filtering
|
|
441
441
|
|
|
442
442
|
// Selected day - read from URL, default to today
|
|
@@ -532,52 +532,18 @@ export default {
|
|
|
532
532
|
|
|
533
533
|
const selectedModel = ref('')
|
|
534
534
|
const selectedProvider = ref('')
|
|
535
|
-
const sortBy = ref('
|
|
535
|
+
const sortBy = ref('createdAt')
|
|
536
536
|
const filterOptions = ref({ models: [], providers: [] })
|
|
537
|
-
const
|
|
537
|
+
const scrollSentinel = ref(null)
|
|
538
|
+
let observer = null
|
|
538
539
|
|
|
539
540
|
const hasActiveFilters = computed(() => selectedModel.value || selectedProvider.value)
|
|
540
541
|
|
|
541
542
|
async function loadAnalyticsData() {
|
|
542
543
|
try {
|
|
543
|
-
const db = await initDB()
|
|
544
|
-
const tx = db.transaction(['requests'], 'readonly')
|
|
545
|
-
const store = tx.objectStore('requests')
|
|
546
|
-
const allRequests = await store.getAll()
|
|
547
|
-
|
|
548
544
|
// Group requests by date
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
let totalInputSum = 0
|
|
552
|
-
let totalOutputSum = 0
|
|
553
|
-
const yearsSet = new Set()
|
|
554
|
-
|
|
555
|
-
allRequests.forEach(req => {
|
|
556
|
-
const date = new Date(req.created * 1000)
|
|
557
|
-
const dateKey = date.toISOString().split('T')[0] // YYYY-MM-DD
|
|
558
|
-
yearsSet.add(date.getFullYear())
|
|
559
|
-
|
|
560
|
-
if (!dailyData[dateKey]) {
|
|
561
|
-
dailyData[dateKey] = {
|
|
562
|
-
cost: 0,
|
|
563
|
-
requests: 0,
|
|
564
|
-
inputTokens: 0,
|
|
565
|
-
outputTokens: 0
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
dailyData[dateKey].cost += req.cost || 0
|
|
570
|
-
dailyData[dateKey].requests += 1
|
|
571
|
-
dailyData[dateKey].inputTokens += req.inputTokens || 0
|
|
572
|
-
dailyData[dateKey].outputTokens += req.outputTokens || 0
|
|
573
|
-
|
|
574
|
-
totalCostSum += req.cost || 0
|
|
575
|
-
totalInputSum += req.inputTokens || 0
|
|
576
|
-
totalOutputSum += req.outputTokens || 0
|
|
577
|
-
})
|
|
578
|
-
|
|
579
|
-
// Store all daily data for filtering
|
|
580
|
-
allDailyData.value = dailyData
|
|
545
|
+
analyticsData.value = await ctx.requests.getSummary()
|
|
546
|
+
allDailyData.value = analyticsData.value.dailyData
|
|
581
547
|
|
|
582
548
|
// Update chart data based on selected month/year
|
|
583
549
|
updateChartData()
|
|
@@ -654,36 +620,8 @@ export default {
|
|
|
654
620
|
}
|
|
655
621
|
|
|
656
622
|
try {
|
|
657
|
-
const
|
|
658
|
-
const
|
|
659
|
-
const store = tx.objectStore('requests')
|
|
660
|
-
const allRequests = await store.getAll()
|
|
661
|
-
|
|
662
|
-
// Filter requests for the selected day
|
|
663
|
-
const dayStart = Math.floor(new Date(dateKey + 'T00:00:00Z').getTime() / 1000)
|
|
664
|
-
const dayEnd = Math.floor(new Date(dateKey + 'T23:59:59Z').getTime() / 1000)
|
|
665
|
-
|
|
666
|
-
const dayRequests = allRequests.filter(req => req.created >= dayStart && req.created <= dayEnd)
|
|
667
|
-
|
|
668
|
-
// Aggregate by model
|
|
669
|
-
const modelData = {}
|
|
670
|
-
const providerData = {}
|
|
671
|
-
|
|
672
|
-
dayRequests.forEach(req => {
|
|
673
|
-
// Model aggregation
|
|
674
|
-
if (!modelData[req.model]) {
|
|
675
|
-
modelData[req.model] = { cost: 0, count: 0 }
|
|
676
|
-
}
|
|
677
|
-
modelData[req.model].cost += req.cost || 0
|
|
678
|
-
modelData[req.model].count += 1
|
|
679
|
-
|
|
680
|
-
// Provider aggregation
|
|
681
|
-
if (!providerData[req.provider]) {
|
|
682
|
-
providerData[req.provider] = { cost: 0, count: 0 }
|
|
683
|
-
}
|
|
684
|
-
providerData[req.provider].cost += req.cost || 0
|
|
685
|
-
providerData[req.provider].count += 1
|
|
686
|
-
})
|
|
623
|
+
const dailySummary = await ctx.requests.getDailySummary(dateKey)
|
|
624
|
+
const { modelData, providerData } = dailySummary
|
|
687
625
|
|
|
688
626
|
// Prepare model pie chart data
|
|
689
627
|
const modelLabels = Object.keys(modelData).sort()
|
|
@@ -727,38 +665,8 @@ export default {
|
|
|
727
665
|
}
|
|
728
666
|
|
|
729
667
|
try {
|
|
730
|
-
const
|
|
731
|
-
const
|
|
732
|
-
const store = tx.objectStore('requests')
|
|
733
|
-
const allRequests = await store.getAll()
|
|
734
|
-
|
|
735
|
-
// Filter requests for the selected day
|
|
736
|
-
const dayStart = Math.floor(new Date(dateKey + 'T00:00:00Z').getTime() / 1000)
|
|
737
|
-
const dayEnd = Math.floor(new Date(dateKey + 'T23:59:59Z').getTime() / 1000)
|
|
738
|
-
|
|
739
|
-
const dayRequests = allRequests.filter(req => req.created >= dayStart && req.created <= dayEnd)
|
|
740
|
-
|
|
741
|
-
// Aggregate by model and provider (using tokens)
|
|
742
|
-
const modelData = {}
|
|
743
|
-
const providerData = {}
|
|
744
|
-
|
|
745
|
-
dayRequests.forEach(req => {
|
|
746
|
-
const totalTokens = (req.inputTokens || 0) + (req.outputTokens || 0)
|
|
747
|
-
|
|
748
|
-
// Model aggregation
|
|
749
|
-
if (!modelData[req.model]) {
|
|
750
|
-
modelData[req.model] = { tokens: 0, count: 0 }
|
|
751
|
-
}
|
|
752
|
-
modelData[req.model].tokens += totalTokens
|
|
753
|
-
modelData[req.model].count += 1
|
|
754
|
-
|
|
755
|
-
// Provider aggregation
|
|
756
|
-
if (!providerData[req.provider]) {
|
|
757
|
-
providerData[req.provider] = { tokens: 0, count: 0 }
|
|
758
|
-
}
|
|
759
|
-
providerData[req.provider].tokens += totalTokens
|
|
760
|
-
providerData[req.provider].count += 1
|
|
761
|
-
})
|
|
668
|
+
const dailySummary = await ctx.requests.getDailySummary(dateKey)
|
|
669
|
+
const { modelData, providerData } = dailySummary
|
|
762
670
|
|
|
763
671
|
// Prepare model pie chart data
|
|
764
672
|
const modelLabels = Object.keys(modelData).sort()
|
|
@@ -802,7 +710,7 @@ export default {
|
|
|
802
710
|
costChartInstance.destroy()
|
|
803
711
|
}
|
|
804
712
|
|
|
805
|
-
const
|
|
713
|
+
const ctx2d = costChartCanvas.value.getContext('2d')
|
|
806
714
|
const chartTypeValue = costChartType.value
|
|
807
715
|
|
|
808
716
|
// Find the index of the selected day
|
|
@@ -833,7 +741,7 @@ export default {
|
|
|
833
741
|
}]
|
|
834
742
|
}
|
|
835
743
|
|
|
836
|
-
costChartInstance = new Chart(
|
|
744
|
+
costChartInstance = new Chart(ctx2d, {
|
|
837
745
|
type: chartTypeValue,
|
|
838
746
|
data: chartDataWithColors,
|
|
839
747
|
options: {
|
|
@@ -859,14 +767,14 @@ export default {
|
|
|
859
767
|
},
|
|
860
768
|
tooltip: {
|
|
861
769
|
callbacks: {
|
|
862
|
-
title: function(context) {
|
|
770
|
+
title: function (context) {
|
|
863
771
|
const index = context[0].dataIndex
|
|
864
772
|
const dateKey = chartData.value.dateKeys[index]
|
|
865
773
|
const date = new Date(dateKey + 'T00:00:00Z')
|
|
866
774
|
return date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })
|
|
867
775
|
},
|
|
868
|
-
label: function(context) {
|
|
869
|
-
return `Cost: ${
|
|
776
|
+
label: function (context) {
|
|
777
|
+
return `Cost: ${ctx.fmt.cost(context.parsed.y)}`
|
|
870
778
|
}
|
|
871
779
|
}
|
|
872
780
|
}
|
|
@@ -875,7 +783,7 @@ export default {
|
|
|
875
783
|
y: {
|
|
876
784
|
beginAtZero: true,
|
|
877
785
|
ticks: {
|
|
878
|
-
callback: function(value) {
|
|
786
|
+
callback: function (value) {
|
|
879
787
|
return '$' + value.toFixed(4)
|
|
880
788
|
}
|
|
881
789
|
}
|
|
@@ -893,7 +801,7 @@ export default {
|
|
|
893
801
|
tokenChartInstance.destroy()
|
|
894
802
|
}
|
|
895
803
|
|
|
896
|
-
const
|
|
804
|
+
const ctx2d = tokenChartCanvas.value.getContext('2d')
|
|
897
805
|
|
|
898
806
|
// Find the index of the selected day
|
|
899
807
|
const selectedDayIndex = tokenChartData.value.dateKeys.indexOf(selectedDay.value)
|
|
@@ -944,7 +852,7 @@ export default {
|
|
|
944
852
|
]
|
|
945
853
|
}
|
|
946
854
|
|
|
947
|
-
tokenChartInstance = new Chart(
|
|
855
|
+
tokenChartInstance = new Chart(ctx2d, {
|
|
948
856
|
type: 'bar',
|
|
949
857
|
data: chartDataWithColors,
|
|
950
858
|
options: {
|
|
@@ -972,8 +880,8 @@ export default {
|
|
|
972
880
|
stacked: true,
|
|
973
881
|
beginAtZero: true,
|
|
974
882
|
ticks: {
|
|
975
|
-
callback: function(value) {
|
|
976
|
-
return humanifyNumber(value)
|
|
883
|
+
callback: function (value) {
|
|
884
|
+
return ctx.fmt.humanifyNumber(value)
|
|
977
885
|
}
|
|
978
886
|
}
|
|
979
887
|
}
|
|
@@ -985,14 +893,14 @@ export default {
|
|
|
985
893
|
},
|
|
986
894
|
tooltip: {
|
|
987
895
|
callbacks: {
|
|
988
|
-
title: function(context) {
|
|
896
|
+
title: function (context) {
|
|
989
897
|
const index = context[0].dataIndex
|
|
990
898
|
const dateKey = tokenChartData.value.dateKeys[index]
|
|
991
899
|
const date = new Date(dateKey + 'T00:00:00Z')
|
|
992
900
|
return date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })
|
|
993
901
|
},
|
|
994
|
-
label: function(context) {
|
|
995
|
-
return `${context.dataset.label}: ${humanifyNumber(context.parsed.y)}`
|
|
902
|
+
label: function (context) {
|
|
903
|
+
return `${context.dataset.label}: ${ctx.fmt.humanifyNumber(context.parsed.y)}`
|
|
996
904
|
}
|
|
997
905
|
}
|
|
998
906
|
}
|
|
@@ -1009,7 +917,7 @@ export default {
|
|
|
1009
917
|
modelPieChartInstance.destroy()
|
|
1010
918
|
}
|
|
1011
919
|
|
|
1012
|
-
const
|
|
920
|
+
const ctx2d = modelPieCanvas.value.getContext('2d')
|
|
1013
921
|
|
|
1014
922
|
// Custom plugin to draw percentage labels on pie slices
|
|
1015
923
|
const percentagePlugin = {
|
|
@@ -1036,7 +944,7 @@ export default {
|
|
|
1036
944
|
}
|
|
1037
945
|
}
|
|
1038
946
|
|
|
1039
|
-
modelPieChartInstance = new Chart(
|
|
947
|
+
modelPieChartInstance = new Chart(ctx2d, {
|
|
1040
948
|
type: 'pie',
|
|
1041
949
|
data: modelPieData.value,
|
|
1042
950
|
options: {
|
|
@@ -1049,8 +957,8 @@ export default {
|
|
|
1049
957
|
},
|
|
1050
958
|
tooltip: {
|
|
1051
959
|
callbacks: {
|
|
1052
|
-
label: function(context) {
|
|
1053
|
-
return `${context.label}: ${
|
|
960
|
+
label: function (context) {
|
|
961
|
+
return `${context.label}: ${ctx.fmt.cost(context.parsed)}`
|
|
1054
962
|
}
|
|
1055
963
|
}
|
|
1056
964
|
}
|
|
@@ -1068,7 +976,7 @@ export default {
|
|
|
1068
976
|
providerPieChartInstance.destroy()
|
|
1069
977
|
}
|
|
1070
978
|
|
|
1071
|
-
const
|
|
979
|
+
const ctx2d = providerPieCanvas.value.getContext('2d')
|
|
1072
980
|
|
|
1073
981
|
// Custom plugin to draw percentage labels on pie slices
|
|
1074
982
|
const percentagePlugin = {
|
|
@@ -1095,7 +1003,7 @@ export default {
|
|
|
1095
1003
|
}
|
|
1096
1004
|
}
|
|
1097
1005
|
|
|
1098
|
-
providerPieChartInstance = new Chart(
|
|
1006
|
+
providerPieChartInstance = new Chart(ctx2d, {
|
|
1099
1007
|
type: 'pie',
|
|
1100
1008
|
data: providerPieData.value,
|
|
1101
1009
|
options: {
|
|
@@ -1108,8 +1016,8 @@ export default {
|
|
|
1108
1016
|
},
|
|
1109
1017
|
tooltip: {
|
|
1110
1018
|
callbacks: {
|
|
1111
|
-
label: function(context) {
|
|
1112
|
-
return `${context.label}: ${
|
|
1019
|
+
label: function (context) {
|
|
1020
|
+
return `${context.label}: ${ctx.fmt.cost(context.parsed)}`
|
|
1113
1021
|
}
|
|
1114
1022
|
}
|
|
1115
1023
|
}
|
|
@@ -1127,7 +1035,7 @@ export default {
|
|
|
1127
1035
|
tokenModelPieChartInstance.destroy()
|
|
1128
1036
|
}
|
|
1129
1037
|
|
|
1130
|
-
const
|
|
1038
|
+
const ctx2d = tokenModelPieCanvas.value.getContext('2d')
|
|
1131
1039
|
|
|
1132
1040
|
// Custom plugin to draw percentage labels on pie slices
|
|
1133
1041
|
const percentagePlugin = {
|
|
@@ -1154,7 +1062,7 @@ export default {
|
|
|
1154
1062
|
}
|
|
1155
1063
|
}
|
|
1156
1064
|
|
|
1157
|
-
tokenModelPieChartInstance = new Chart(
|
|
1065
|
+
tokenModelPieChartInstance = new Chart(ctx2d, {
|
|
1158
1066
|
type: 'pie',
|
|
1159
1067
|
data: tokenModelPieData.value,
|
|
1160
1068
|
options: {
|
|
@@ -1167,8 +1075,8 @@ export default {
|
|
|
1167
1075
|
},
|
|
1168
1076
|
tooltip: {
|
|
1169
1077
|
callbacks: {
|
|
1170
|
-
label: function(context) {
|
|
1171
|
-
return `${context.label}: ${humanifyNumber(context.parsed)}`
|
|
1078
|
+
label: function (context) {
|
|
1079
|
+
return `${context.label}: ${ctx.fmt.humanifyNumber(context.parsed)}`
|
|
1172
1080
|
}
|
|
1173
1081
|
}
|
|
1174
1082
|
}
|
|
@@ -1186,7 +1094,7 @@ export default {
|
|
|
1186
1094
|
tokenProviderPieChartInstance.destroy()
|
|
1187
1095
|
}
|
|
1188
1096
|
|
|
1189
|
-
const
|
|
1097
|
+
const ctx2d = tokenProviderPieCanvas.value.getContext('2d')
|
|
1190
1098
|
|
|
1191
1099
|
// Custom plugin to draw percentage labels on pie slices
|
|
1192
1100
|
const percentagePlugin = {
|
|
@@ -1213,7 +1121,7 @@ export default {
|
|
|
1213
1121
|
}
|
|
1214
1122
|
}
|
|
1215
1123
|
|
|
1216
|
-
tokenProviderPieChartInstance = new Chart(
|
|
1124
|
+
tokenProviderPieChartInstance = new Chart(ctx2d, {
|
|
1217
1125
|
type: 'pie',
|
|
1218
1126
|
data: tokenProviderPieData.value,
|
|
1219
1127
|
options: {
|
|
@@ -1226,8 +1134,8 @@ export default {
|
|
|
1226
1134
|
},
|
|
1227
1135
|
tooltip: {
|
|
1228
1136
|
callbacks: {
|
|
1229
|
-
label: function(context) {
|
|
1230
|
-
return `${context.label}: ${humanifyNumber(context.parsed)}`
|
|
1137
|
+
label: function (context) {
|
|
1138
|
+
return `${context.label}: ${ctx.fmt.humanifyNumber(context.parsed)}`
|
|
1231
1139
|
}
|
|
1232
1140
|
}
|
|
1233
1141
|
}
|
|
@@ -1240,7 +1148,7 @@ export default {
|
|
|
1240
1148
|
// Activity tab functions
|
|
1241
1149
|
const loadActivityFilterOptions = async () => {
|
|
1242
1150
|
try {
|
|
1243
|
-
filterOptions.value = await
|
|
1151
|
+
filterOptions.value = await ctx.requests.getFilterOptions()
|
|
1244
1152
|
} catch (error) {
|
|
1245
1153
|
console.error('Failed to load filter options:', error)
|
|
1246
1154
|
}
|
|
@@ -1248,24 +1156,9 @@ export default {
|
|
|
1248
1156
|
|
|
1249
1157
|
const loadExistingThreadIds = async () => {
|
|
1250
1158
|
try {
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
// Convert to timestamp strings (threadId format)
|
|
1256
|
-
const startThreadId = startDate.getTime().toString()
|
|
1257
|
-
const endThreadId = endDate.getTime().toString()
|
|
1258
|
-
|
|
1259
|
-
const db = await initDB()
|
|
1260
|
-
const tx = db.transaction(['threads'], 'readonly')
|
|
1261
|
-
const store = tx.objectStore('threads')
|
|
1262
|
-
|
|
1263
|
-
// Use IDBKeyRange to only load threads within the month's timestamp range
|
|
1264
|
-
const range = IDBKeyRange.bound(startThreadId, endThreadId)
|
|
1265
|
-
const monthThreads = await store.getAll(range)
|
|
1266
|
-
|
|
1267
|
-
// Create a Set of existing thread IDs for the month
|
|
1268
|
-
existingThreadIds.value = new Set(monthThreads.map(thread => thread.id))
|
|
1159
|
+
existingThreadIds.value = new Set(await ctx.requests.getThreadIds({
|
|
1160
|
+
month: selectedYearMonth.value,
|
|
1161
|
+
}))
|
|
1269
1162
|
} catch (error) {
|
|
1270
1163
|
console.error('Failed to load existing thread IDs:', error)
|
|
1271
1164
|
existingThreadIds.value = new Set()
|
|
@@ -1288,28 +1181,24 @@ export default {
|
|
|
1288
1181
|
}
|
|
1289
1182
|
|
|
1290
1183
|
try {
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
sortOrder: 'desc',
|
|
1300
|
-
startDate: Math.floor(startDate.getTime() / 1000),
|
|
1301
|
-
endDate: Math.floor(endDate.getTime() / 1000)
|
|
1302
|
-
}
|
|
1184
|
+
const requests = await ctx.requests.query({
|
|
1185
|
+
model: selectedModel.value || undefined,
|
|
1186
|
+
provider: selectedProvider.value || undefined,
|
|
1187
|
+
sort: `-${sortBy.value}`,
|
|
1188
|
+
take: activityPageSize,
|
|
1189
|
+
skip: activityOffset.value,
|
|
1190
|
+
month: selectedYearMonth.value,
|
|
1191
|
+
})
|
|
1303
1192
|
|
|
1304
|
-
const
|
|
1193
|
+
const hasMore = requests.length >= activityPageSize
|
|
1305
1194
|
|
|
1306
1195
|
if (reset) {
|
|
1307
|
-
activityRequests.value =
|
|
1196
|
+
activityRequests.value = requests
|
|
1308
1197
|
} else {
|
|
1309
|
-
activityRequests.value.push(...
|
|
1198
|
+
activityRequests.value.push(...requests)
|
|
1310
1199
|
}
|
|
1311
1200
|
|
|
1312
|
-
activityHasMore.value =
|
|
1201
|
+
activityHasMore.value = hasMore
|
|
1313
1202
|
activityOffset.value += activityPageSize
|
|
1314
1203
|
} catch (error) {
|
|
1315
1204
|
console.error('Failed to load requests:', error)
|
|
@@ -1319,29 +1208,32 @@ export default {
|
|
|
1319
1208
|
}
|
|
1320
1209
|
}
|
|
1321
1210
|
|
|
1322
|
-
const
|
|
1323
|
-
if (
|
|
1211
|
+
const setupObserver = () => {
|
|
1212
|
+
if (observer) observer.disconnect()
|
|
1324
1213
|
|
|
1325
|
-
|
|
1326
|
-
|
|
1214
|
+
observer = new IntersectionObserver((entries) => {
|
|
1215
|
+
if (entries[0].isIntersecting && activityHasMore.value && !isActivityLoadingMore.value && !isActivityLoading.value) {
|
|
1216
|
+
loadActivityRequests(false)
|
|
1217
|
+
}
|
|
1218
|
+
}, { rootMargin: '200px' })
|
|
1327
1219
|
|
|
1328
|
-
if (
|
|
1329
|
-
|
|
1220
|
+
if (scrollSentinel.value) {
|
|
1221
|
+
observer.observe(scrollSentinel.value)
|
|
1330
1222
|
}
|
|
1331
1223
|
}
|
|
1332
1224
|
|
|
1333
1225
|
const clearActivityFilters = async () => {
|
|
1334
1226
|
selectedModel.value = ''
|
|
1335
1227
|
selectedProvider.value = ''
|
|
1336
|
-
sortBy.value = '
|
|
1228
|
+
sortBy.value = 'createdAt'
|
|
1337
1229
|
await loadActivityRequests(true)
|
|
1338
1230
|
}
|
|
1339
1231
|
|
|
1340
|
-
const formatActivityDate = (
|
|
1341
|
-
const date = new Date(
|
|
1342
|
-
return date.toLocaleTimeString(undefined, { hour12: false }) + ' '
|
|
1343
|
-
+ date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })
|
|
1344
|
-
|
|
1232
|
+
const formatActivityDate = (d) => {
|
|
1233
|
+
const date = new Date(d)
|
|
1234
|
+
return date.toLocaleTimeString(undefined, { hour12: false }) + ' '
|
|
1235
|
+
+ date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })
|
|
1236
|
+
|
|
1345
1237
|
}
|
|
1346
1238
|
|
|
1347
1239
|
const openThread = (threadId) => {
|
|
@@ -1349,17 +1241,12 @@ export default {
|
|
|
1349
1241
|
}
|
|
1350
1242
|
|
|
1351
1243
|
const deleteRequestLog = async (requestId) => {
|
|
1352
|
-
if (confirm(
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
await loadAnalyticsData()
|
|
1359
|
-
} catch (error) {
|
|
1360
|
-
console.error('Failed to delete request:', error)
|
|
1361
|
-
alert('Failed to delete request')
|
|
1362
|
-
}
|
|
1244
|
+
if (confirm(`Are you sure you want to delete this request log ${requestId}?`)) {
|
|
1245
|
+
await ctx.requests.deleteById(requestId)
|
|
1246
|
+
// Remove from the list
|
|
1247
|
+
activityRequests.value = activityRequests.value.filter(r => r.id !== requestId)
|
|
1248
|
+
// Reload analytics data
|
|
1249
|
+
await loadAnalyticsData()
|
|
1363
1250
|
}
|
|
1364
1251
|
}
|
|
1365
1252
|
|
|
@@ -1442,6 +1329,8 @@ export default {
|
|
|
1442
1329
|
} else if (newTab === 'activity') {
|
|
1443
1330
|
await loadActivityFilterOptions()
|
|
1444
1331
|
await loadActivityRequests(true)
|
|
1332
|
+
await nextTick()
|
|
1333
|
+
setupObserver()
|
|
1445
1334
|
}
|
|
1446
1335
|
})
|
|
1447
1336
|
|
|
@@ -1474,9 +1363,15 @@ export default {
|
|
|
1474
1363
|
if (activeTab.value === 'activity') {
|
|
1475
1364
|
await loadActivityFilterOptions()
|
|
1476
1365
|
await loadActivityRequests(true)
|
|
1366
|
+
await nextTick()
|
|
1367
|
+
setupObserver()
|
|
1477
1368
|
}
|
|
1478
1369
|
})
|
|
1479
1370
|
|
|
1371
|
+
onUnmounted(() => {
|
|
1372
|
+
if (observer) observer.disconnect()
|
|
1373
|
+
})
|
|
1374
|
+
|
|
1480
1375
|
return {
|
|
1481
1376
|
activeTab,
|
|
1482
1377
|
costChartType,
|
|
@@ -1497,9 +1392,6 @@ export default {
|
|
|
1497
1392
|
totalRequests,
|
|
1498
1393
|
totalInputTokens,
|
|
1499
1394
|
totalOutputTokens,
|
|
1500
|
-
formatCost,
|
|
1501
|
-
humanifyNumber,
|
|
1502
|
-
humanifyMs,
|
|
1503
1395
|
// Month/Year selection
|
|
1504
1396
|
selectedMonth,
|
|
1505
1397
|
selectedYear,
|
|
@@ -1514,8 +1406,8 @@ export default {
|
|
|
1514
1406
|
sortBy,
|
|
1515
1407
|
filterOptions,
|
|
1516
1408
|
hasActiveFilters,
|
|
1517
|
-
|
|
1518
|
-
|
|
1409
|
+
hasActiveFilters,
|
|
1410
|
+
scrollSentinel,
|
|
1519
1411
|
clearActivityFilters,
|
|
1520
1412
|
formatActivityDate,
|
|
1521
1413
|
threadExists,
|
|
@@ -1526,3 +1418,27 @@ export default {
|
|
|
1526
1418
|
}
|
|
1527
1419
|
}
|
|
1528
1420
|
}
|
|
1421
|
+
|
|
1422
|
+
export default {
|
|
1423
|
+
order: 20 - 100,
|
|
1424
|
+
|
|
1425
|
+
install(ctx) {
|
|
1426
|
+
ctx.components({
|
|
1427
|
+
MonthSelector,
|
|
1428
|
+
Analytics,
|
|
1429
|
+
})
|
|
1430
|
+
|
|
1431
|
+
ctx.setLeftIcons({
|
|
1432
|
+
analytics: {
|
|
1433
|
+
component: {
|
|
1434
|
+
template: `<svg @click="$ctx.togglePath('/analytics')" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M5 22a1 1 0 0 1-1-1v-8a1 1 0 0 1 2 0v8a1 1 0 0 1-1 1m5 0a1 1 0 0 1-1-1V3a1 1 0 0 1 2 0v18a1 1 0 0 1-1 1m5 0a1 1 0 0 1-1-1V9a1 1 0 0 1 2 0v12a1 1 0 0 1-1 1m5 0a1 1 0 0 1-1-1v-4a1 1 0 0 1 2 0v4a1 1 0 0 1-1 1"/></svg>`
|
|
1435
|
+
},
|
|
1436
|
+
isActive({ path }) {
|
|
1437
|
+
return path === '/analytics'
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
})
|
|
1441
|
+
|
|
1442
|
+
ctx.routes.push({ path: '/analytics', component: Analytics, meta: { title: 'Analytics' } })
|
|
1443
|
+
}
|
|
1444
|
+
}
|