llms-py 3.0.0__py3-none-any.whl → 3.0.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- llms/__pycache__/main.cpython-314.pyc +0 -0
- llms/index.html +37 -26
- llms/llms.json +21 -70
- llms/main.py +731 -1426
- llms/providers.json +1 -1
- llms/{extensions/analytics/ui/index.mjs → ui/Analytics.mjs} +238 -154
- llms/ui/App.mjs +63 -133
- llms/ui/Avatar.mjs +86 -0
- llms/ui/Brand.mjs +52 -0
- llms/ui/ChatPrompt.mjs +597 -0
- llms/ui/Main.mjs +862 -0
- llms/ui/OAuthSignIn.mjs +61 -0
- llms/ui/ProviderIcon.mjs +36 -0
- llms/ui/ProviderStatus.mjs +104 -0
- llms/{extensions/app/ui → ui}/Recents.mjs +57 -82
- llms/ui/{modules/chat/SettingsDialog.mjs → SettingsDialog.mjs} +9 -9
- llms/{extensions/app/ui/index.mjs → ui/Sidebar.mjs} +57 -122
- llms/ui/SignIn.mjs +65 -0
- llms/ui/Welcome.mjs +8 -0
- llms/ui/ai.mjs +13 -117
- llms/ui/app.css +49 -1776
- llms/ui/index.mjs +171 -87
- llms/ui/lib/charts.mjs +13 -9
- llms/ui/lib/servicestack-vue.mjs +3 -3
- llms/ui/lib/vue.min.mjs +9 -10
- llms/ui/lib/vue.mjs +1602 -1763
- llms/ui/markdown.mjs +2 -10
- llms/ui/model-selector.mjs +686 -0
- llms/ui/tailwind.input.css +1 -55
- llms/ui/threadStore.mjs +583 -0
- llms/ui/utils.mjs +118 -113
- llms/ui.json +1069 -0
- {llms_py-3.0.0.dist-info → llms_py-3.0.0b2.dist-info}/METADATA +1 -1
- llms_py-3.0.0b2.dist-info/RECORD +58 -0
- llms/extensions/app/README.md +0 -20
- llms/extensions/app/__init__.py +0 -530
- llms/extensions/app/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/app/__pycache__/db.cpython-314.pyc +0 -0
- llms/extensions/app/__pycache__/db_manager.cpython-314.pyc +0 -0
- llms/extensions/app/db.py +0 -644
- llms/extensions/app/db_manager.py +0 -195
- llms/extensions/app/requests.json +0 -9073
- llms/extensions/app/threads.json +0 -15290
- llms/extensions/app/ui/threadStore.mjs +0 -411
- llms/extensions/core_tools/CALCULATOR.md +0 -32
- llms/extensions/core_tools/__init__.py +0 -598
- llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +0 -201
- llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +0 -185
- llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +0 -101
- llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +0 -160
- llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +0 -66
- llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +0 -27
- llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +0 -72
- llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +0 -119
- llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +0 -98
- llms/extensions/core_tools/ui/codemirror/doc/docs.css +0 -225
- llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
- llms/extensions/core_tools/ui/codemirror/lib/codemirror.css +0 -344
- llms/extensions/core_tools/ui/codemirror/lib/codemirror.js +0 -9884
- llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +0 -942
- llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +0 -118
- llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +0 -962
- llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +0 -62
- llms/extensions/core_tools/ui/codemirror/mode/python/python.js +0 -402
- llms/extensions/core_tools/ui/codemirror/theme/dracula.css +0 -40
- llms/extensions/core_tools/ui/codemirror/theme/mocha.css +0 -135
- llms/extensions/core_tools/ui/index.mjs +0 -650
- llms/extensions/gallery/README.md +0 -61
- llms/extensions/gallery/__init__.py +0 -61
- llms/extensions/gallery/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
- llms/extensions/gallery/db.py +0 -298
- llms/extensions/gallery/ui/index.mjs +0 -482
- llms/extensions/katex/README.md +0 -39
- llms/extensions/katex/__init__.py +0 -6
- llms/extensions/katex/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/katex/ui/README.md +0 -125
- llms/extensions/katex/ui/contrib/auto-render.js +0 -338
- llms/extensions/katex/ui/contrib/auto-render.min.js +0 -1
- llms/extensions/katex/ui/contrib/auto-render.mjs +0 -244
- llms/extensions/katex/ui/contrib/copy-tex.js +0 -127
- llms/extensions/katex/ui/contrib/copy-tex.min.js +0 -1
- llms/extensions/katex/ui/contrib/copy-tex.mjs +0 -105
- llms/extensions/katex/ui/contrib/mathtex-script-type.js +0 -109
- llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +0 -1
- llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +0 -24
- llms/extensions/katex/ui/contrib/mhchem.js +0 -3213
- llms/extensions/katex/ui/contrib/mhchem.min.js +0 -1
- llms/extensions/katex/ui/contrib/mhchem.mjs +0 -3109
- llms/extensions/katex/ui/contrib/render-a11y-string.js +0 -887
- llms/extensions/katex/ui/contrib/render-a11y-string.min.js +0 -1
- llms/extensions/katex/ui/contrib/render-a11y-string.mjs +0 -800
- llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- llms/extensions/katex/ui/index.mjs +0 -92
- llms/extensions/katex/ui/katex-swap.css +0 -1230
- llms/extensions/katex/ui/katex-swap.min.css +0 -1
- llms/extensions/katex/ui/katex.css +0 -1230
- llms/extensions/katex/ui/katex.js +0 -19080
- llms/extensions/katex/ui/katex.min.css +0 -1
- llms/extensions/katex/ui/katex.min.js +0 -1
- llms/extensions/katex/ui/katex.min.mjs +0 -1
- llms/extensions/katex/ui/katex.mjs +0 -18547
- llms/extensions/providers/__init__.py +0 -18
- llms/extensions/providers/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/chutes.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/google.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/nvidia.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/openai.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
- llms/extensions/providers/anthropic.py +0 -229
- llms/extensions/providers/chutes.py +0 -155
- llms/extensions/providers/google.py +0 -378
- llms/extensions/providers/nvidia.py +0 -105
- llms/extensions/providers/openai.py +0 -156
- llms/extensions/providers/openrouter.py +0 -72
- llms/extensions/system_prompts/README.md +0 -22
- llms/extensions/system_prompts/__init__.py +0 -45
- llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/system_prompts/ui/index.mjs +0 -280
- llms/extensions/system_prompts/ui/prompts.json +0 -1067
- llms/extensions/tools/__init__.py +0 -5
- llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/tools/ui/index.mjs +0 -204
- llms/providers-extra.json +0 -356
- llms/ui/ctx.mjs +0 -365
- llms/ui/modules/chat/ChatBody.mjs +0 -691
- llms/ui/modules/chat/index.mjs +0 -828
- llms/ui/modules/layout.mjs +0 -243
- llms/ui/modules/model-selector.mjs +0 -851
- llms_py-3.0.0.dist-info/RECORD +0 -202
- {llms_py-3.0.0.dist-info → llms_py-3.0.0b2.dist-info}/WHEEL +0 -0
- {llms_py-3.0.0.dist-info → llms_py-3.0.0b2.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.0.dist-info → llms_py-3.0.0b2.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.0.dist-info → llms_py-3.0.0b2.dist-info}/top_level.txt +0 -0
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
import { ref, watch, nextTick, computed
|
|
1
|
+
import { ref, onMounted, watch, nextTick, computed } from 'vue'
|
|
2
2
|
import { useRouter, useRoute } from 'vue-router'
|
|
3
|
+
import { useFormatters } from "@servicestack/vue"
|
|
3
4
|
import { leftPart } from '@servicestack/client'
|
|
4
5
|
import { Chart, registerables } from "chart.js"
|
|
6
|
+
import { useThreadStore } from './threadStore.mjs'
|
|
7
|
+
import { formatCost } from './utils.mjs'
|
|
5
8
|
Chart.register(...registerables)
|
|
6
9
|
|
|
10
|
+
const { humanifyNumber, humanifyMs } = useFormatters()
|
|
11
|
+
|
|
7
12
|
export const colors = [
|
|
8
|
-
{ background: 'rgba(54, 162, 235, 0.2)',
|
|
9
|
-
{ background: 'rgba(255, 99, 132, 0.2)',
|
|
13
|
+
{ background: 'rgba(54, 162, 235, 0.2)', border: 'rgb(54, 162, 235)' }, //blue
|
|
14
|
+
{ background: 'rgba(255, 99, 132, 0.2)', border: 'rgb(255, 99, 132)' },
|
|
10
15
|
{ background: 'rgba(153, 102, 255, 0.2)', border: 'rgb(153, 102, 255)' },
|
|
11
|
-
{ background: 'rgba(54, 162, 235, 0.2)',
|
|
12
|
-
{ background: 'rgba(255, 159, 64, 0.2)',
|
|
13
|
-
{ background: 'rgba(67, 56, 202, 0.2)',
|
|
14
|
-
{ background: 'rgba(255, 99, 132, 0.2)',
|
|
15
|
-
{ background: 'rgba(14, 116, 144, 0.2)',
|
|
16
|
-
{ background: 'rgba(162, 28, 175, 0.2)',
|
|
16
|
+
{ background: 'rgba(54, 162, 235, 0.2)', border: 'rgb(54, 162, 235)' },
|
|
17
|
+
{ background: 'rgba(255, 159, 64, 0.2)', border: 'rgb(255, 159, 64)' },
|
|
18
|
+
{ background: 'rgba(67, 56, 202, 0.2)', border: 'rgb(67, 56, 202)' },
|
|
19
|
+
{ background: 'rgba(255, 99, 132, 0.2)', border: 'rgb(255, 99, 132)' },
|
|
20
|
+
{ background: 'rgba(14, 116, 144, 0.2)', border: 'rgb(14, 116, 144)' },
|
|
21
|
+
{ background: 'rgba(162, 28, 175, 0.2)', border: 'rgb(162, 28, 175)' },
|
|
17
22
|
{ background: 'rgba(201, 203, 207, 0.2)', border: 'rgb(201, 203, 207)' },
|
|
18
23
|
]
|
|
19
24
|
|
|
20
25
|
const MonthSelector = {
|
|
21
|
-
template
|
|
26
|
+
template:`
|
|
22
27
|
<div class="flex flex-col sm:flex-row gap-2 sm:gap-4 items-stretch sm:items-center w-full sm:w-auto">
|
|
23
28
|
<!-- Months Row -->
|
|
24
29
|
<div class="flex gap-1 sm:gap-2 flex-wrap justify-center overflow-x-auto">
|
|
@@ -46,7 +51,7 @@ const MonthSelector = {
|
|
|
46
51
|
</div>
|
|
47
52
|
`,
|
|
48
53
|
props: {
|
|
49
|
-
dailyData:
|
|
54
|
+
dailyData: Array,
|
|
50
55
|
},
|
|
51
56
|
setup(props) {
|
|
52
57
|
const router = useRouter()
|
|
@@ -104,9 +109,12 @@ const MonthSelector = {
|
|
|
104
109
|
}
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
export
|
|
112
|
+
export default {
|
|
113
|
+
components: {
|
|
114
|
+
MonthSelector,
|
|
115
|
+
},
|
|
108
116
|
template: `
|
|
109
|
-
<div class="flex flex-col w-full">
|
|
117
|
+
<div class="flex flex-col h-full w-full">
|
|
110
118
|
<!-- Header -->
|
|
111
119
|
<div class="border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-2 sm:px-4 py-3">
|
|
112
120
|
<div
|
|
@@ -150,14 +158,14 @@ export const Analytics = {
|
|
|
150
158
|
</div>
|
|
151
159
|
|
|
152
160
|
<!-- Content -->
|
|
153
|
-
<div class="flex-1 bg-gray-50 dark:bg-gray-900" :class="activeTab === 'activity' ? 'p-0' : 'p-4'">
|
|
161
|
+
<div class="flex-1 overflow-auto bg-gray-50 dark:bg-gray-900" :class="activeTab === 'activity' ? 'p-0' : 'p-4'">
|
|
154
162
|
|
|
155
|
-
<div :class="activeTab === 'activity' ? '' : 'max-w-6xl mx-auto'">
|
|
163
|
+
<div :class="activeTab === 'activity' ? 'h-full' : 'max-w-6xl mx-auto'">
|
|
156
164
|
<!-- Stats Summary (hidden for Activity tab) -->
|
|
157
165
|
<div v-if="activeTab !== 'activity'" class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
|
158
166
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
|
159
167
|
<div class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Cost</div>
|
|
160
|
-
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{
|
|
168
|
+
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{ formatCost(totalCost) }}</div>
|
|
161
169
|
</div>
|
|
162
170
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
|
163
171
|
<div class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Requests</div>
|
|
@@ -165,11 +173,11 @@ export const Analytics = {
|
|
|
165
173
|
</div>
|
|
166
174
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
|
167
175
|
<div class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Input Tokens</div>
|
|
168
|
-
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{
|
|
176
|
+
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{ humanifyNumber(totalInputTokens) }}</div>
|
|
169
177
|
</div>
|
|
170
178
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
|
171
179
|
<div class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Output Tokens</div>
|
|
172
|
-
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{
|
|
180
|
+
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-1">{{ humanifyNumber(totalOutputTokens) }}</div>
|
|
173
181
|
</div>
|
|
174
182
|
</div>
|
|
175
183
|
|
|
@@ -216,11 +224,11 @@ export const Analytics = {
|
|
|
216
224
|
{{ new Date(selectedDay).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' }) }}
|
|
217
225
|
</div>
|
|
218
226
|
<div class="flex flex-wrap gap-x-2 gap-y-1">
|
|
219
|
-
<span>{{
|
|
227
|
+
<span>{{ formatCost(allDailyData[selectedDay]?.cost || 0) }}</span>
|
|
220
228
|
<span>·</span>
|
|
221
229
|
<span>{{ allDailyData[selectedDay]?.requests || 0 }} Requests</span>
|
|
222
230
|
<span>·</span>
|
|
223
|
-
<span>{{
|
|
231
|
+
<span>{{ humanifyNumber(allDailyData[selectedDay]?.inputTokens || 0) }} -> {{ humanifyNumber(allDailyData[selectedDay]?.outputTokens || 0) }} Tokens</span>
|
|
224
232
|
</div>
|
|
225
233
|
</div>
|
|
226
234
|
|
|
@@ -283,7 +291,7 @@ export const Analytics = {
|
|
|
283
291
|
</div>
|
|
284
292
|
|
|
285
293
|
<!-- Activity Tab - Full Page Layout -->
|
|
286
|
-
<div v-if="activeTab === 'activity'" class="flex flex-col bg-white dark:bg-gray-800">
|
|
294
|
+
<div v-if="activeTab === 'activity'" class="h-full flex flex-col bg-white dark:bg-gray-800">
|
|
287
295
|
<!-- Filters Bar -->
|
|
288
296
|
<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">
|
|
289
297
|
<div class="flex flex-wrap gap-2 sm:gap-4 items-end">
|
|
@@ -307,7 +315,7 @@ export const Analytics = {
|
|
|
307
315
|
|
|
308
316
|
<div class="flex flex-col flex-1 min-w-[140px] sm:flex-initial">
|
|
309
317
|
<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">
|
|
310
|
-
<option value="
|
|
318
|
+
<option value="created">Date (Newest)</option>
|
|
311
319
|
<option value="cost">Cost (Highest)</option>
|
|
312
320
|
<option value="duration">Duration (Longest)</option>
|
|
313
321
|
<option value="totalTokens">Tokens (Most)</option>
|
|
@@ -321,15 +329,15 @@ export const Analytics = {
|
|
|
321
329
|
</div>
|
|
322
330
|
|
|
323
331
|
<!-- Requests List with Infinite Scroll -->
|
|
324
|
-
<div class="flex-1">
|
|
325
|
-
<div v-if="isActivityLoading && activityRequests.length === 0" class="
|
|
332
|
+
<div class="flex-1 overflow-y-auto" @scroll="onActivityScroll" ref="activityScrollContainer">
|
|
333
|
+
<div v-if="isActivityLoading && activityRequests.length === 0" class="flex items-center justify-center h-full">
|
|
326
334
|
<div class="text-center">
|
|
327
335
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
|
|
328
336
|
<p class="mt-4 text-gray-600 dark:text-gray-400">Loading requests...</p>
|
|
329
337
|
</div>
|
|
330
338
|
</div>
|
|
331
339
|
|
|
332
|
-
<div v-else-if="activityRequests.length === 0" class="
|
|
340
|
+
<div v-else-if="activityRequests.length === 0" class="flex items-center justify-center h-full">
|
|
333
341
|
<p class="text-gray-500 dark:text-gray-400">No requests found</p>
|
|
334
342
|
</div>
|
|
335
343
|
|
|
@@ -339,42 +347,38 @@ export const Analytics = {
|
|
|
339
347
|
<div class="flex-1 min-w-0 w-full">
|
|
340
348
|
<div class="flex flex-col sm:flex-row justify-between gap-2 mb-2">
|
|
341
349
|
<div class="flex items-center gap-2 flex-wrap">
|
|
342
|
-
<span
|
|
343
|
-
<span
|
|
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>
|
|
344
352
|
<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>
|
|
345
353
|
<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>
|
|
346
354
|
</div>
|
|
347
355
|
<div class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">
|
|
348
|
-
{{ formatActivityDate(request.
|
|
356
|
+
{{ formatActivityDate(request.created) }}
|
|
349
357
|
</div>
|
|
350
358
|
</div>
|
|
351
359
|
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100 truncate mb-3">
|
|
352
360
|
{{ request.title }}
|
|
353
361
|
</div>
|
|
354
362
|
|
|
355
|
-
<div
|
|
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">
|
|
363
|
+
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3 sm:gap-4">
|
|
360
364
|
<div :title="request.cost">
|
|
361
365
|
<div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Cost</div>
|
|
362
|
-
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{
|
|
366
|
+
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ formatCost(request.cost) }}</div>
|
|
363
367
|
</div>
|
|
364
368
|
<div class="col-span-2 sm:col-span-1">
|
|
365
369
|
<div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Tokens</div>
|
|
366
|
-
<div
|
|
367
|
-
{{
|
|
368
|
-
<span v-if="request.inputCachedTokens" class="ml-1 text-xs text-gray-500 dark:text-gray-400">({{
|
|
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>
|
|
369
373
|
</div>
|
|
370
374
|
</div>
|
|
371
375
|
<div>
|
|
372
376
|
<div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Duration</div>
|
|
373
|
-
<div
|
|
377
|
+
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ request.duration ? humanifyMs(request.duration) : '—' }}</div>
|
|
374
378
|
</div>
|
|
375
379
|
<div>
|
|
376
380
|
<div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Speed</div>
|
|
377
|
-
<div
|
|
381
|
+
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ request.duration && request.outputTokens ? (request.outputTokens / (request.duration / 1000)).toFixed(1) + ' tok/s' : '—' }}</div>
|
|
378
382
|
</div>
|
|
379
383
|
</div>
|
|
380
384
|
</div>
|
|
@@ -397,7 +401,6 @@ export const Analytics = {
|
|
|
397
401
|
No more requests to load
|
|
398
402
|
</div>
|
|
399
403
|
</div>
|
|
400
|
-
<div ref="scrollSentinel" class="h-4 w-full"></div>
|
|
401
404
|
</div>
|
|
402
405
|
</div>
|
|
403
406
|
</div>
|
|
@@ -405,10 +408,10 @@ export const Analytics = {
|
|
|
405
408
|
</div>
|
|
406
409
|
`,
|
|
407
410
|
setup() {
|
|
408
|
-
const ctx = inject('ctx')
|
|
409
411
|
const router = useRouter()
|
|
410
412
|
const route = useRoute()
|
|
411
|
-
const
|
|
413
|
+
const threads = useThreadStore()
|
|
414
|
+
const { initDB } = threads
|
|
412
415
|
|
|
413
416
|
// Initialize activeTab from URL query parameter, default to 'cost'
|
|
414
417
|
const activeTab = ref(route.query.tab || 'cost')
|
|
@@ -434,9 +437,6 @@ export const Analytics = {
|
|
|
434
437
|
const selectedYear = computed(() => {
|
|
435
438
|
return route.query.year !== undefined ? parseInt(route.query.year) : currentDate.getFullYear()
|
|
436
439
|
})
|
|
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,18 +532,52 @@ export const Analytics = {
|
|
|
532
532
|
|
|
533
533
|
const selectedModel = ref('')
|
|
534
534
|
const selectedProvider = ref('')
|
|
535
|
-
const sortBy = ref('
|
|
535
|
+
const sortBy = ref('created')
|
|
536
536
|
const filterOptions = ref({ models: [], providers: [] })
|
|
537
|
-
const
|
|
538
|
-
let observer = null
|
|
537
|
+
const activityScrollContainer = ref(null)
|
|
539
538
|
|
|
540
539
|
const hasActiveFilters = computed(() => selectedModel.value || selectedProvider.value)
|
|
541
540
|
|
|
542
541
|
async function loadAnalyticsData() {
|
|
543
542
|
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
|
+
|
|
544
548
|
// Group requests by date
|
|
545
|
-
|
|
546
|
-
|
|
549
|
+
const dailyData = {}
|
|
550
|
+
let totalCostSum = 0
|
|
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
|
|
547
581
|
|
|
548
582
|
// Update chart data based on selected month/year
|
|
549
583
|
updateChartData()
|
|
@@ -620,8 +654,36 @@ export const Analytics = {
|
|
|
620
654
|
}
|
|
621
655
|
|
|
622
656
|
try {
|
|
623
|
-
const
|
|
624
|
-
const
|
|
657
|
+
const db = await initDB()
|
|
658
|
+
const tx = db.transaction(['requests'], 'readonly')
|
|
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
|
+
})
|
|
625
687
|
|
|
626
688
|
// Prepare model pie chart data
|
|
627
689
|
const modelLabels = Object.keys(modelData).sort()
|
|
@@ -665,8 +727,38 @@ export const Analytics = {
|
|
|
665
727
|
}
|
|
666
728
|
|
|
667
729
|
try {
|
|
668
|
-
const
|
|
669
|
-
const
|
|
730
|
+
const db = await initDB()
|
|
731
|
+
const tx = db.transaction(['requests'], 'readonly')
|
|
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
|
+
})
|
|
670
762
|
|
|
671
763
|
// Prepare model pie chart data
|
|
672
764
|
const modelLabels = Object.keys(modelData).sort()
|
|
@@ -710,7 +802,7 @@ export const Analytics = {
|
|
|
710
802
|
costChartInstance.destroy()
|
|
711
803
|
}
|
|
712
804
|
|
|
713
|
-
const
|
|
805
|
+
const ctx = costChartCanvas.value.getContext('2d')
|
|
714
806
|
const chartTypeValue = costChartType.value
|
|
715
807
|
|
|
716
808
|
// Find the index of the selected day
|
|
@@ -741,7 +833,7 @@ export const Analytics = {
|
|
|
741
833
|
}]
|
|
742
834
|
}
|
|
743
835
|
|
|
744
|
-
costChartInstance = new Chart(
|
|
836
|
+
costChartInstance = new Chart(ctx, {
|
|
745
837
|
type: chartTypeValue,
|
|
746
838
|
data: chartDataWithColors,
|
|
747
839
|
options: {
|
|
@@ -767,14 +859,14 @@ export const Analytics = {
|
|
|
767
859
|
},
|
|
768
860
|
tooltip: {
|
|
769
861
|
callbacks: {
|
|
770
|
-
title: function
|
|
862
|
+
title: function(context) {
|
|
771
863
|
const index = context[0].dataIndex
|
|
772
864
|
const dateKey = chartData.value.dateKeys[index]
|
|
773
865
|
const date = new Date(dateKey + 'T00:00:00Z')
|
|
774
866
|
return date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })
|
|
775
867
|
},
|
|
776
|
-
label: function
|
|
777
|
-
return `Cost: ${
|
|
868
|
+
label: function(context) {
|
|
869
|
+
return `Cost: ${formatCost(context.parsed.y)}`
|
|
778
870
|
}
|
|
779
871
|
}
|
|
780
872
|
}
|
|
@@ -783,7 +875,7 @@ export const Analytics = {
|
|
|
783
875
|
y: {
|
|
784
876
|
beginAtZero: true,
|
|
785
877
|
ticks: {
|
|
786
|
-
callback: function
|
|
878
|
+
callback: function(value) {
|
|
787
879
|
return '$' + value.toFixed(4)
|
|
788
880
|
}
|
|
789
881
|
}
|
|
@@ -801,7 +893,7 @@ export const Analytics = {
|
|
|
801
893
|
tokenChartInstance.destroy()
|
|
802
894
|
}
|
|
803
895
|
|
|
804
|
-
const
|
|
896
|
+
const ctx = tokenChartCanvas.value.getContext('2d')
|
|
805
897
|
|
|
806
898
|
// Find the index of the selected day
|
|
807
899
|
const selectedDayIndex = tokenChartData.value.dateKeys.indexOf(selectedDay.value)
|
|
@@ -852,7 +944,7 @@ export const Analytics = {
|
|
|
852
944
|
]
|
|
853
945
|
}
|
|
854
946
|
|
|
855
|
-
tokenChartInstance = new Chart(
|
|
947
|
+
tokenChartInstance = new Chart(ctx, {
|
|
856
948
|
type: 'bar',
|
|
857
949
|
data: chartDataWithColors,
|
|
858
950
|
options: {
|
|
@@ -880,8 +972,8 @@ export const Analytics = {
|
|
|
880
972
|
stacked: true,
|
|
881
973
|
beginAtZero: true,
|
|
882
974
|
ticks: {
|
|
883
|
-
callback: function
|
|
884
|
-
return
|
|
975
|
+
callback: function(value) {
|
|
976
|
+
return humanifyNumber(value)
|
|
885
977
|
}
|
|
886
978
|
}
|
|
887
979
|
}
|
|
@@ -893,14 +985,14 @@ export const Analytics = {
|
|
|
893
985
|
},
|
|
894
986
|
tooltip: {
|
|
895
987
|
callbacks: {
|
|
896
|
-
title: function
|
|
988
|
+
title: function(context) {
|
|
897
989
|
const index = context[0].dataIndex
|
|
898
990
|
const dateKey = tokenChartData.value.dateKeys[index]
|
|
899
991
|
const date = new Date(dateKey + 'T00:00:00Z')
|
|
900
992
|
return date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })
|
|
901
993
|
},
|
|
902
|
-
label: function
|
|
903
|
-
return `${context.dataset.label}: ${
|
|
994
|
+
label: function(context) {
|
|
995
|
+
return `${context.dataset.label}: ${humanifyNumber(context.parsed.y)}`
|
|
904
996
|
}
|
|
905
997
|
}
|
|
906
998
|
}
|
|
@@ -917,7 +1009,7 @@ export const Analytics = {
|
|
|
917
1009
|
modelPieChartInstance.destroy()
|
|
918
1010
|
}
|
|
919
1011
|
|
|
920
|
-
const
|
|
1012
|
+
const ctx = modelPieCanvas.value.getContext('2d')
|
|
921
1013
|
|
|
922
1014
|
// Custom plugin to draw percentage labels on pie slices
|
|
923
1015
|
const percentagePlugin = {
|
|
@@ -944,7 +1036,7 @@ export const Analytics = {
|
|
|
944
1036
|
}
|
|
945
1037
|
}
|
|
946
1038
|
|
|
947
|
-
modelPieChartInstance = new Chart(
|
|
1039
|
+
modelPieChartInstance = new Chart(ctx, {
|
|
948
1040
|
type: 'pie',
|
|
949
1041
|
data: modelPieData.value,
|
|
950
1042
|
options: {
|
|
@@ -957,8 +1049,8 @@ export const Analytics = {
|
|
|
957
1049
|
},
|
|
958
1050
|
tooltip: {
|
|
959
1051
|
callbacks: {
|
|
960
|
-
label: function
|
|
961
|
-
return `${context.label}: ${
|
|
1052
|
+
label: function(context) {
|
|
1053
|
+
return `${context.label}: ${formatCost(context.parsed)}`
|
|
962
1054
|
}
|
|
963
1055
|
}
|
|
964
1056
|
}
|
|
@@ -976,7 +1068,7 @@ export const Analytics = {
|
|
|
976
1068
|
providerPieChartInstance.destroy()
|
|
977
1069
|
}
|
|
978
1070
|
|
|
979
|
-
const
|
|
1071
|
+
const ctx = providerPieCanvas.value.getContext('2d')
|
|
980
1072
|
|
|
981
1073
|
// Custom plugin to draw percentage labels on pie slices
|
|
982
1074
|
const percentagePlugin = {
|
|
@@ -1003,7 +1095,7 @@ export const Analytics = {
|
|
|
1003
1095
|
}
|
|
1004
1096
|
}
|
|
1005
1097
|
|
|
1006
|
-
providerPieChartInstance = new Chart(
|
|
1098
|
+
providerPieChartInstance = new Chart(ctx, {
|
|
1007
1099
|
type: 'pie',
|
|
1008
1100
|
data: providerPieData.value,
|
|
1009
1101
|
options: {
|
|
@@ -1016,8 +1108,8 @@ export const Analytics = {
|
|
|
1016
1108
|
},
|
|
1017
1109
|
tooltip: {
|
|
1018
1110
|
callbacks: {
|
|
1019
|
-
label: function
|
|
1020
|
-
return `${context.label}: ${
|
|
1111
|
+
label: function(context) {
|
|
1112
|
+
return `${context.label}: ${formatCost(context.parsed)}`
|
|
1021
1113
|
}
|
|
1022
1114
|
}
|
|
1023
1115
|
}
|
|
@@ -1035,7 +1127,7 @@ export const Analytics = {
|
|
|
1035
1127
|
tokenModelPieChartInstance.destroy()
|
|
1036
1128
|
}
|
|
1037
1129
|
|
|
1038
|
-
const
|
|
1130
|
+
const ctx = tokenModelPieCanvas.value.getContext('2d')
|
|
1039
1131
|
|
|
1040
1132
|
// Custom plugin to draw percentage labels on pie slices
|
|
1041
1133
|
const percentagePlugin = {
|
|
@@ -1062,7 +1154,7 @@ export const Analytics = {
|
|
|
1062
1154
|
}
|
|
1063
1155
|
}
|
|
1064
1156
|
|
|
1065
|
-
tokenModelPieChartInstance = new Chart(
|
|
1157
|
+
tokenModelPieChartInstance = new Chart(ctx, {
|
|
1066
1158
|
type: 'pie',
|
|
1067
1159
|
data: tokenModelPieData.value,
|
|
1068
1160
|
options: {
|
|
@@ -1075,8 +1167,8 @@ export const Analytics = {
|
|
|
1075
1167
|
},
|
|
1076
1168
|
tooltip: {
|
|
1077
1169
|
callbacks: {
|
|
1078
|
-
label: function
|
|
1079
|
-
return `${context.label}: ${
|
|
1170
|
+
label: function(context) {
|
|
1171
|
+
return `${context.label}: ${humanifyNumber(context.parsed)}`
|
|
1080
1172
|
}
|
|
1081
1173
|
}
|
|
1082
1174
|
}
|
|
@@ -1094,7 +1186,7 @@ export const Analytics = {
|
|
|
1094
1186
|
tokenProviderPieChartInstance.destroy()
|
|
1095
1187
|
}
|
|
1096
1188
|
|
|
1097
|
-
const
|
|
1189
|
+
const ctx = tokenProviderPieCanvas.value.getContext('2d')
|
|
1098
1190
|
|
|
1099
1191
|
// Custom plugin to draw percentage labels on pie slices
|
|
1100
1192
|
const percentagePlugin = {
|
|
@@ -1121,7 +1213,7 @@ export const Analytics = {
|
|
|
1121
1213
|
}
|
|
1122
1214
|
}
|
|
1123
1215
|
|
|
1124
|
-
tokenProviderPieChartInstance = new Chart(
|
|
1216
|
+
tokenProviderPieChartInstance = new Chart(ctx, {
|
|
1125
1217
|
type: 'pie',
|
|
1126
1218
|
data: tokenProviderPieData.value,
|
|
1127
1219
|
options: {
|
|
@@ -1134,8 +1226,8 @@ export const Analytics = {
|
|
|
1134
1226
|
},
|
|
1135
1227
|
tooltip: {
|
|
1136
1228
|
callbacks: {
|
|
1137
|
-
label: function
|
|
1138
|
-
return `${context.label}: ${
|
|
1229
|
+
label: function(context) {
|
|
1230
|
+
return `${context.label}: ${humanifyNumber(context.parsed)}`
|
|
1139
1231
|
}
|
|
1140
1232
|
}
|
|
1141
1233
|
}
|
|
@@ -1148,7 +1240,7 @@ export const Analytics = {
|
|
|
1148
1240
|
// Activity tab functions
|
|
1149
1241
|
const loadActivityFilterOptions = async () => {
|
|
1150
1242
|
try {
|
|
1151
|
-
filterOptions.value = await
|
|
1243
|
+
filterOptions.value = await threads.getFilterOptions()
|
|
1152
1244
|
} catch (error) {
|
|
1153
1245
|
console.error('Failed to load filter options:', error)
|
|
1154
1246
|
}
|
|
@@ -1156,9 +1248,24 @@ export const Analytics = {
|
|
|
1156
1248
|
|
|
1157
1249
|
const loadExistingThreadIds = async () => {
|
|
1158
1250
|
try {
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1251
|
+
// Calculate date range for selected month/year
|
|
1252
|
+
const startDate = new Date(selectedYear.value, selectedMonth.value - 1, 1)
|
|
1253
|
+
const endDate = new Date(selectedYear.value, selectedMonth.value, 0, 23, 59, 59)
|
|
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))
|
|
1162
1269
|
} catch (error) {
|
|
1163
1270
|
console.error('Failed to load existing thread IDs:', error)
|
|
1164
1271
|
existingThreadIds.value = new Set()
|
|
@@ -1181,24 +1288,28 @@ export const Analytics = {
|
|
|
1181
1288
|
}
|
|
1182
1289
|
|
|
1183
1290
|
try {
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1291
|
+
// Calculate date range for selected month/year
|
|
1292
|
+
const startDate = new Date(selectedYear.value, selectedMonth.value - 1, 1)
|
|
1293
|
+
const endDate = new Date(selectedYear.value, selectedMonth.value, 0, 23, 59, 59)
|
|
1294
|
+
|
|
1295
|
+
const filters = {
|
|
1296
|
+
model: selectedModel.value || null,
|
|
1297
|
+
provider: selectedProvider.value || null,
|
|
1298
|
+
sortBy: sortBy.value,
|
|
1299
|
+
sortOrder: 'desc',
|
|
1300
|
+
startDate: Math.floor(startDate.getTime() / 1000),
|
|
1301
|
+
endDate: Math.floor(endDate.getTime() / 1000)
|
|
1302
|
+
}
|
|
1192
1303
|
|
|
1193
|
-
const
|
|
1304
|
+
const result = await threads.getRequests(filters, activityPageSize, activityOffset.value)
|
|
1194
1305
|
|
|
1195
1306
|
if (reset) {
|
|
1196
|
-
activityRequests.value = requests
|
|
1307
|
+
activityRequests.value = result.requests
|
|
1197
1308
|
} else {
|
|
1198
|
-
activityRequests.value.push(...requests)
|
|
1309
|
+
activityRequests.value.push(...result.requests)
|
|
1199
1310
|
}
|
|
1200
1311
|
|
|
1201
|
-
activityHasMore.value = hasMore
|
|
1312
|
+
activityHasMore.value = result.hasMore
|
|
1202
1313
|
activityOffset.value += activityPageSize
|
|
1203
1314
|
} catch (error) {
|
|
1204
1315
|
console.error('Failed to load requests:', error)
|
|
@@ -1208,32 +1319,29 @@ export const Analytics = {
|
|
|
1208
1319
|
}
|
|
1209
1320
|
}
|
|
1210
1321
|
|
|
1211
|
-
const
|
|
1212
|
-
if (
|
|
1322
|
+
const onActivityScroll = async () => {
|
|
1323
|
+
if (!activityScrollContainer.value) return
|
|
1213
1324
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
loadActivityRequests(false)
|
|
1217
|
-
}
|
|
1218
|
-
}, { rootMargin: '200px' })
|
|
1325
|
+
const { scrollTop, scrollHeight, clientHeight } = activityScrollContainer.value
|
|
1326
|
+
const isNearBottom = scrollHeight - scrollTop - clientHeight < 200
|
|
1219
1327
|
|
|
1220
|
-
if (
|
|
1221
|
-
|
|
1328
|
+
if (isNearBottom && activityHasMore.value && !isActivityLoadingMore.value && !isActivityLoading.value) {
|
|
1329
|
+
await loadActivityRequests(false)
|
|
1222
1330
|
}
|
|
1223
1331
|
}
|
|
1224
1332
|
|
|
1225
1333
|
const clearActivityFilters = async () => {
|
|
1226
1334
|
selectedModel.value = ''
|
|
1227
1335
|
selectedProvider.value = ''
|
|
1228
|
-
sortBy.value = '
|
|
1336
|
+
sortBy.value = 'created'
|
|
1229
1337
|
await loadActivityRequests(true)
|
|
1230
1338
|
}
|
|
1231
1339
|
|
|
1232
|
-
const formatActivityDate = (
|
|
1233
|
-
const date = new Date(
|
|
1234
|
-
return date.toLocaleTimeString(undefined, { hour12: false }) + ' '
|
|
1235
|
-
+ date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })
|
|
1236
|
-
|
|
1340
|
+
const formatActivityDate = (timestamp) => {
|
|
1341
|
+
const date = new Date(timestamp * 1000)
|
|
1342
|
+
return date.toLocaleTimeString(undefined, { hour12: false }) + ' '
|
|
1343
|
+
+ date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })
|
|
1344
|
+
|
|
1237
1345
|
}
|
|
1238
1346
|
|
|
1239
1347
|
const openThread = (threadId) => {
|
|
@@ -1241,12 +1349,17 @@ export const Analytics = {
|
|
|
1241
1349
|
}
|
|
1242
1350
|
|
|
1243
1351
|
const deleteRequestLog = async (requestId) => {
|
|
1244
|
-
if (confirm(
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1352
|
+
if (confirm('Are you sure you want to delete this request log?')) {
|
|
1353
|
+
try {
|
|
1354
|
+
await threads.deleteRequest(requestId)
|
|
1355
|
+
// Remove from the list
|
|
1356
|
+
activityRequests.value = activityRequests.value.filter(r => r.id !== requestId)
|
|
1357
|
+
// Reload analytics data
|
|
1358
|
+
await loadAnalyticsData()
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
console.error('Failed to delete request:', error)
|
|
1361
|
+
alert('Failed to delete request')
|
|
1362
|
+
}
|
|
1250
1363
|
}
|
|
1251
1364
|
}
|
|
1252
1365
|
|
|
@@ -1329,8 +1442,6 @@ export const Analytics = {
|
|
|
1329
1442
|
} else if (newTab === 'activity') {
|
|
1330
1443
|
await loadActivityFilterOptions()
|
|
1331
1444
|
await loadActivityRequests(true)
|
|
1332
|
-
await nextTick()
|
|
1333
|
-
setupObserver()
|
|
1334
1445
|
}
|
|
1335
1446
|
})
|
|
1336
1447
|
|
|
@@ -1363,15 +1474,9 @@ export const Analytics = {
|
|
|
1363
1474
|
if (activeTab.value === 'activity') {
|
|
1364
1475
|
await loadActivityFilterOptions()
|
|
1365
1476
|
await loadActivityRequests(true)
|
|
1366
|
-
await nextTick()
|
|
1367
|
-
setupObserver()
|
|
1368
1477
|
}
|
|
1369
1478
|
})
|
|
1370
1479
|
|
|
1371
|
-
onUnmounted(() => {
|
|
1372
|
-
if (observer) observer.disconnect()
|
|
1373
|
-
})
|
|
1374
|
-
|
|
1375
1480
|
return {
|
|
1376
1481
|
activeTab,
|
|
1377
1482
|
costChartType,
|
|
@@ -1392,6 +1497,9 @@ export const Analytics = {
|
|
|
1392
1497
|
totalRequests,
|
|
1393
1498
|
totalInputTokens,
|
|
1394
1499
|
totalOutputTokens,
|
|
1500
|
+
formatCost,
|
|
1501
|
+
humanifyNumber,
|
|
1502
|
+
humanifyMs,
|
|
1395
1503
|
// Month/Year selection
|
|
1396
1504
|
selectedMonth,
|
|
1397
1505
|
selectedYear,
|
|
@@ -1406,8 +1514,8 @@ export const Analytics = {
|
|
|
1406
1514
|
sortBy,
|
|
1407
1515
|
filterOptions,
|
|
1408
1516
|
hasActiveFilters,
|
|
1409
|
-
|
|
1410
|
-
|
|
1517
|
+
activityScrollContainer,
|
|
1518
|
+
onActivityScroll,
|
|
1411
1519
|
clearActivityFilters,
|
|
1412
1520
|
formatActivityDate,
|
|
1413
1521
|
threadExists,
|
|
@@ -1418,27 +1526,3 @@ export const Analytics = {
|
|
|
1418
1526
|
}
|
|
1419
1527
|
}
|
|
1420
1528
|
}
|
|
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
|
-
}
|