llms-py 3.0.0b2__py3-none-any.whl → 3.0.0b3__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 +2 -1
- llms/llms.json +50 -17
- llms/main.py +484 -544
- llms/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
- llms/providers/__pycache__/chutes.cpython-314.pyc +0 -0
- llms/providers/__pycache__/google.cpython-314.pyc +0 -0
- llms/providers/__pycache__/nvidia.cpython-314.pyc +0 -0
- llms/providers/__pycache__/openai.cpython-314.pyc +0 -0
- llms/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
- llms/providers/anthropic.py +189 -0
- llms/providers/chutes.py +152 -0
- llms/providers/google.py +306 -0
- llms/providers/nvidia.py +107 -0
- llms/providers/openai.py +159 -0
- llms/providers/openrouter.py +70 -0
- llms/providers-extra.json +356 -0
- llms/providers.json +1 -1
- llms/ui/App.mjs +132 -60
- llms/ui/ai.mjs +76 -10
- llms/ui/app.css +1 -4962
- llms/ui/ctx.mjs +196 -0
- llms/ui/index.mjs +75 -171
- llms/ui/lib/charts.mjs +9 -13
- llms/ui/markdown.mjs +6 -0
- llms/ui/{Analytics.mjs → modules/analytics.mjs} +76 -64
- llms/ui/{Main.mjs → modules/chat/ChatBody.mjs} +56 -133
- llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +8 -8
- llms/ui/{ChatPrompt.mjs → modules/chat/index.mjs} +239 -45
- llms/ui/modules/layout.mjs +267 -0
- llms/ui/modules/model-selector.mjs +851 -0
- llms/ui/{Recents.mjs → modules/threads/Recents.mjs} +0 -2
- llms/ui/{Sidebar.mjs → modules/threads/index.mjs} +46 -44
- llms/ui/{threadStore.mjs → modules/threads/threadStore.mjs} +10 -7
- llms/ui/utils.mjs +82 -123
- {llms_py-3.0.0b2.dist-info → llms_py-3.0.0b3.dist-info}/METADATA +1 -1
- llms_py-3.0.0b3.dist-info/RECORD +65 -0
- llms/ui/Avatar.mjs +0 -86
- llms/ui/Brand.mjs +0 -52
- llms/ui/OAuthSignIn.mjs +0 -61
- llms/ui/ProviderIcon.mjs +0 -36
- llms/ui/ProviderStatus.mjs +0 -104
- llms/ui/SignIn.mjs +0 -65
- llms/ui/Welcome.mjs +0 -8
- llms/ui/model-selector.mjs +0 -686
- llms/ui.json +0 -1069
- llms_py-3.0.0b2.dist-info/RECORD +0 -58
- {llms_py-3.0.0b2.dist-info → llms_py-3.0.0b3.dist-info}/WHEEL +0 -0
- {llms_py-3.0.0b2.dist-info → llms_py-3.0.0b3.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.0b2.dist-info → llms_py-3.0.0b3.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.0b2.dist-info → llms_py-3.0.0b3.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ref, onMounted, watch, inject } from 'vue'
|
|
2
2
|
import { useRouter, useRoute } from 'vue-router'
|
|
3
3
|
import { useThreadStore } from './threadStore.mjs'
|
|
4
|
-
import { renderMarkdown } from './markdown.mjs'
|
|
5
4
|
|
|
6
5
|
const RecentResults = {
|
|
7
6
|
template: `
|
|
@@ -140,7 +139,6 @@ const RecentResults = {
|
|
|
140
139
|
snippet,
|
|
141
140
|
open,
|
|
142
141
|
formatDate,
|
|
143
|
-
renderMarkdown,
|
|
144
142
|
onScroll,
|
|
145
143
|
}
|
|
146
144
|
}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { onMounted, inject } from 'vue'
|
|
2
2
|
import { useRouter } from 'vue-router'
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import Brand from './Brand.mjs'
|
|
6
|
-
import { statsTitle, formatCost } from './utils.mjs'
|
|
7
|
-
|
|
8
|
-
const { humanifyNumber, humanifyMs } = useFormatters()
|
|
3
|
+
import ThreadStore from './threadStore.mjs'
|
|
4
|
+
import Recents from './Recents.mjs'
|
|
9
5
|
|
|
10
6
|
// Thread Item Component
|
|
11
7
|
const ThreadItem = {
|
|
@@ -21,10 +17,10 @@ const ThreadItem = {
|
|
|
21
17
|
{{ thread.title }}
|
|
22
18
|
</div>
|
|
23
19
|
<div class="text-xs text-gray-500 dark:text-gray-400 truncate">
|
|
24
|
-
<span>{{
|
|
25
|
-
<span v-if="thread.stats?.inputTokens" :title="statsTitle(thread.stats)">
|
|
26
|
-
• {{ humanifyNumber(thread.stats.inputTokens + thread.stats.outputTokens) }} toks
|
|
27
|
-
{{ thread.stats.cost ? ' ' +
|
|
20
|
+
<span>{{ $fmt.relativeTime(thread.updatedAt) }} • {{ thread.messages.length }} msgs</span>
|
|
21
|
+
<span v-if="thread.stats?.inputTokens" :title="$fmt.statsTitle(thread.stats)">
|
|
22
|
+
• {{ $fmt.humanifyNumber(thread.stats.inputTokens + thread.stats.outputTokens) }} toks
|
|
23
|
+
{{ thread.stats.cost ? ' ' + $fmt.cost(thread.stats.cost) : '' }}
|
|
28
24
|
</span>
|
|
29
25
|
</div>
|
|
30
26
|
<div v-if="thread.model" class="text-xs text-blue-600 dark:text-blue-400 truncate">
|
|
@@ -60,32 +56,12 @@ const ThreadItem = {
|
|
|
60
56
|
emits: ['select', 'delete'],
|
|
61
57
|
|
|
62
58
|
setup() {
|
|
63
|
-
const formatRelativeTime = (timestamp) => {
|
|
64
|
-
const now = new Date()
|
|
65
|
-
const date = new Date(timestamp)
|
|
66
|
-
const diffInSeconds = Math.floor((now - date) / 1000)
|
|
67
|
-
|
|
68
|
-
if (diffInSeconds < 60) return 'Just now'
|
|
69
|
-
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`
|
|
70
|
-
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`
|
|
71
|
-
if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`
|
|
72
|
-
|
|
73
|
-
return date.toLocaleDateString()
|
|
74
|
-
}
|
|
75
|
-
|
|
76
59
|
return {
|
|
77
|
-
formatRelativeTime,
|
|
78
|
-
humanifyNumber,
|
|
79
|
-
statsTitle,
|
|
80
|
-
formatCost,
|
|
81
60
|
}
|
|
82
61
|
}
|
|
83
62
|
}
|
|
84
63
|
|
|
85
64
|
const GroupedThreads = {
|
|
86
|
-
components: {
|
|
87
|
-
ThreadItem,
|
|
88
|
-
},
|
|
89
65
|
template: `
|
|
90
66
|
<!-- Today -->
|
|
91
67
|
<div v-if="groupedThreads.today.length > 0" class="mb-4">
|
|
@@ -156,15 +132,10 @@ const GroupedThreads = {
|
|
|
156
132
|
emits: ['select', 'delete'],
|
|
157
133
|
}
|
|
158
134
|
|
|
159
|
-
const
|
|
160
|
-
components: {
|
|
161
|
-
Brand,
|
|
162
|
-
GroupedThreads,
|
|
163
|
-
ThreadItem,
|
|
164
|
-
},
|
|
135
|
+
const ThreadsSidebar = {
|
|
165
136
|
template: `
|
|
166
|
-
<div class="flex flex-col h-full
|
|
167
|
-
<Brand @home="goToInitialState" @
|
|
137
|
+
<div class="flex flex-col h-full">
|
|
138
|
+
<Brand @home="goToInitialState" @toggle-sidebar="$emit('toggle-sidebar')" />
|
|
168
139
|
<!-- Thread List -->
|
|
169
140
|
<div class="flex-1 overflow-y-auto">
|
|
170
141
|
<div v-if="isLoading" class="p-4 text-center text-gray-500 dark:text-gray-400">
|
|
@@ -180,8 +151,19 @@ const Sidebar = {
|
|
|
180
151
|
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">Start a new chat to begin</p>
|
|
181
152
|
</div>
|
|
182
153
|
|
|
183
|
-
<div v-else class="py-2">
|
|
184
|
-
|
|
154
|
+
<div v-else class="relative py-2">
|
|
155
|
+
|
|
156
|
+
<div class="flex items-center space-x-2 absolute top-2 right-2">
|
|
157
|
+
<button type="button"
|
|
158
|
+
@click="createNewThread"
|
|
159
|
+
class="text-gray-900 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none transition-colors"
|
|
160
|
+
title="New Chat"
|
|
161
|
+
>
|
|
162
|
+
<svg class="size-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></g></svg>
|
|
163
|
+
</button>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<GroupedThreads :currentThread="currentThread" :groupedThreads="$threads.getGroupedThreads(50)"
|
|
185
167
|
@select="selectThread" @delete="deleteThread" />
|
|
186
168
|
</div>
|
|
187
169
|
</div>
|
|
@@ -192,7 +174,6 @@ const Sidebar = {
|
|
|
192
174
|
const ctx = inject('ctx')
|
|
193
175
|
const ai = ctx.ai
|
|
194
176
|
const router = useRouter()
|
|
195
|
-
const threadStore = useThreadStore()
|
|
196
177
|
const {
|
|
197
178
|
threads,
|
|
198
179
|
currentThread,
|
|
@@ -202,7 +183,7 @@ const Sidebar = {
|
|
|
202
183
|
createThread,
|
|
203
184
|
deleteThread: deleteThreadFromStore,
|
|
204
185
|
clearCurrentThread
|
|
205
|
-
} =
|
|
186
|
+
} = ctx.threads
|
|
206
187
|
|
|
207
188
|
onMounted(async () => {
|
|
208
189
|
await loadThreads()
|
|
@@ -241,7 +222,6 @@ const Sidebar = {
|
|
|
241
222
|
}
|
|
242
223
|
|
|
243
224
|
return {
|
|
244
|
-
threadStore,
|
|
245
225
|
threads,
|
|
246
226
|
currentThread,
|
|
247
227
|
isLoading,
|
|
@@ -255,4 +235,26 @@ const Sidebar = {
|
|
|
255
235
|
}
|
|
256
236
|
}
|
|
257
237
|
|
|
258
|
-
export default
|
|
238
|
+
export default {
|
|
239
|
+
install(ctx) {
|
|
240
|
+
ctx.components({
|
|
241
|
+
ThreadsSidebar,
|
|
242
|
+
ThreadItem,
|
|
243
|
+
GroupedThreads,
|
|
244
|
+
Recents,
|
|
245
|
+
})
|
|
246
|
+
ctx.routes.push(...[
|
|
247
|
+
{ path: '/recents', component: Recents },
|
|
248
|
+
])
|
|
249
|
+
ThreadStore.install(ctx)
|
|
250
|
+
|
|
251
|
+
ctx.setLayout({
|
|
252
|
+
left: 'ThreadsSidebar',
|
|
253
|
+
})
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
async load(ctx) {
|
|
257
|
+
const { initDB } = ctx.threads
|
|
258
|
+
await initDB()
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ref, computed, unref } from 'vue'
|
|
2
2
|
import { openDB } from 'idb'
|
|
3
|
-
import { nextId
|
|
3
|
+
import { nextId } from '../../utils.mjs'
|
|
4
4
|
|
|
5
5
|
// Thread store for managing chat threads with IndexedDB
|
|
6
6
|
const threads = ref([])
|
|
@@ -10,12 +10,6 @@ const isLoading = ref(false)
|
|
|
10
10
|
let db = null
|
|
11
11
|
let ctx = null
|
|
12
12
|
|
|
13
|
-
export default {
|
|
14
|
-
install(context) {
|
|
15
|
-
ctx = context
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
13
|
// Initialize IndexedDB
|
|
20
14
|
async function initDB() {
|
|
21
15
|
if (db) return db
|
|
@@ -581,3 +575,12 @@ export function useThreadStore() {
|
|
|
581
575
|
getAllThreadIds,
|
|
582
576
|
}
|
|
583
577
|
}
|
|
578
|
+
|
|
579
|
+
export default {
|
|
580
|
+
install(context) {
|
|
581
|
+
ctx = context
|
|
582
|
+
ctx.setGlobals({
|
|
583
|
+
threads: useThreadStore()
|
|
584
|
+
})
|
|
585
|
+
}
|
|
586
|
+
}
|
llms/ui/utils.mjs
CHANGED
|
@@ -1,37 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const cacheUrlInfo = {}
|
|
4
|
-
|
|
5
|
-
export function getCacheInfo(url) {
|
|
6
|
-
return cacheUrlInfo[url]
|
|
7
|
-
}
|
|
8
|
-
export async function fetchCacheInfos(urls) {
|
|
9
|
-
const infos = {}
|
|
10
|
-
const fetchInfos = []
|
|
11
|
-
for (const url of urls) {
|
|
12
|
-
const info = getCacheInfo(url)
|
|
13
|
-
if (info) {
|
|
14
|
-
infos[url] = info
|
|
15
|
-
} else {
|
|
16
|
-
fetchInfos.push(fetch(url + "?info"))
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
const responses = await Promise.all(fetchInfos)
|
|
20
|
-
for (let i = 0; i < urls.length; i++) {
|
|
21
|
-
try {
|
|
22
|
-
const info = await responses[i].json()
|
|
23
|
-
setCacheInfo(urls[i], info)
|
|
24
|
-
infos[urls[i]] = info
|
|
25
|
-
} catch (e) {
|
|
26
|
-
console.error('Failed to fetch info for', urls[i], e)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return infos
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function setCacheInfo(url, info) {
|
|
33
|
-
cacheUrlInfo[url] = info
|
|
34
|
-
}
|
|
1
|
+
import { rightPart } from "@servicestack/client"
|
|
35
2
|
|
|
36
3
|
export function toJsonArray(json) {
|
|
37
4
|
try {
|
|
@@ -83,19 +50,6 @@ export function fileToDataUri(file) {
|
|
|
83
50
|
})
|
|
84
51
|
}
|
|
85
52
|
|
|
86
|
-
export async function uploadFile(file) {
|
|
87
|
-
const formData = new FormData()
|
|
88
|
-
formData.append('file', file)
|
|
89
|
-
const response = await fetch('/upload', {
|
|
90
|
-
method: 'POST',
|
|
91
|
-
body: formData
|
|
92
|
-
})
|
|
93
|
-
if (!response.ok) {
|
|
94
|
-
throw new Error(`Upload failed: ${response.statusText}`)
|
|
95
|
-
}
|
|
96
|
-
return response.json()
|
|
97
|
-
}
|
|
98
|
-
|
|
99
53
|
export function serializedClone(obj) {
|
|
100
54
|
try {
|
|
101
55
|
return JSON.parse(JSON.stringify(obj))
|
|
@@ -105,6 +59,7 @@ export function serializedClone(obj) {
|
|
|
105
59
|
}
|
|
106
60
|
}
|
|
107
61
|
|
|
62
|
+
|
|
108
63
|
export function deepClone(o) {
|
|
109
64
|
if (o === null || typeof o !== 'object') return o
|
|
110
65
|
|
|
@@ -126,94 +81,82 @@ export function deepClone(o) {
|
|
|
126
81
|
return serializedClone(o)
|
|
127
82
|
}
|
|
128
83
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const { id, name, provider, cost, modalities } = model
|
|
132
|
-
return deepClone({ id, name, provider, cost, modalities })
|
|
133
|
-
}
|
|
84
|
+
const currFmt2 = new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 2 })
|
|
85
|
+
const currFmt6 = new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 6 })
|
|
134
86
|
|
|
135
|
-
|
|
136
|
-
export function tokenCost(price) {
|
|
87
|
+
export function tokenCost(price, tokens = 1) {
|
|
137
88
|
if (!price) return ''
|
|
138
|
-
var ret =
|
|
89
|
+
var ret = currFmt2.format(parseFloat(price) * tokens)
|
|
139
90
|
return ret.endsWith('.00') ? ret.slice(0, -3) : ret
|
|
140
91
|
}
|
|
92
|
+
export function tokenCostLong(price, tokens = 1) {
|
|
93
|
+
if (!price) return ''
|
|
94
|
+
const ret = currFmt6.format(parseFloat(price) * tokens)
|
|
95
|
+
return ret.endsWith('.000000') ? ret.slice(0, -7) : ret
|
|
96
|
+
}
|
|
141
97
|
export function formatCost(cost) {
|
|
142
98
|
if (!cost) return ''
|
|
143
|
-
return
|
|
99
|
+
return currFmt2.format(parseFloat(cost))
|
|
144
100
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
101
|
+
|
|
102
|
+
// Accessible in views via $fmt
|
|
103
|
+
export function utilsFormatters() {
|
|
104
|
+
function relativeTime(timestamp) {
|
|
105
|
+
const now = new Date()
|
|
106
|
+
const date = new Date(timestamp)
|
|
107
|
+
const diffInSeconds = Math.floor((now - date) / 1000)
|
|
108
|
+
|
|
109
|
+
if (diffInSeconds < 60) return 'Just now'
|
|
110
|
+
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`
|
|
111
|
+
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`
|
|
112
|
+
if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`
|
|
113
|
+
|
|
114
|
+
return date.toLocaleDateString()
|
|
156
115
|
}
|
|
157
|
-
|
|
158
|
-
|
|
116
|
+
function costLong(cost) {
|
|
117
|
+
if (!cost) return ''
|
|
118
|
+
const ret = currFmt6.format(parseFloat(cost))
|
|
119
|
+
return ret.endsWith('.000000') ? ret.slice(0, -7) : ret
|
|
159
120
|
}
|
|
160
|
-
|
|
161
|
-
title
|
|
121
|
+
function statsTitle(stats) {
|
|
122
|
+
let title = []
|
|
123
|
+
// Each stat on its own line
|
|
124
|
+
if (stats.cost) {
|
|
125
|
+
title.push(`Total Cost: ${costLong(stats.cost)}`)
|
|
126
|
+
}
|
|
127
|
+
if (stats.inputTokens) {
|
|
128
|
+
title.push(`Input Tokens: ${stats.inputTokens}`)
|
|
129
|
+
}
|
|
130
|
+
if (stats.outputTokens) {
|
|
131
|
+
title.push(`Output Tokens: ${stats.outputTokens}`)
|
|
132
|
+
}
|
|
133
|
+
if (stats.requests) {
|
|
134
|
+
title.push(`Requests: ${stats.requests}`)
|
|
135
|
+
}
|
|
136
|
+
if (stats.duration) {
|
|
137
|
+
title.push(`Duration: ${stats.duration}ms`)
|
|
138
|
+
}
|
|
139
|
+
return title.join('\n')
|
|
162
140
|
}
|
|
163
|
-
return title.join('\n')
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const svg = {
|
|
167
|
-
clipboard: `<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none"><path d="M8 5H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1M8 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M8 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m0 0h2a2 2 0 0 1 2 2v3m2 4H10m0 0l3-3m-3 3l3 3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></g></svg>`,
|
|
168
|
-
check: `<svg class="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>`,
|
|
169
|
-
}
|
|
170
141
|
|
|
171
|
-
function
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
label.classList.remove('hidden')
|
|
176
|
-
label.innerHTML = 'copied'
|
|
177
|
-
btn.classList.add('border-gray-600', 'bg-gray-700')
|
|
178
|
-
btn.classList.remove('border-gray-700')
|
|
179
|
-
btn.innerHTML = svg.check
|
|
180
|
-
navigator.clipboard.writeText(code.innerText)
|
|
181
|
-
setTimeout(() => {
|
|
182
|
-
label.classList.add('hidden')
|
|
183
|
-
label.innerHTML = ''
|
|
184
|
-
btn.innerHTML = svg.clipboard
|
|
185
|
-
btn.classList.remove('border-gray-600', 'bg-gray-700')
|
|
186
|
-
btn.classList.add('border-gray-700')
|
|
187
|
-
}, 2000)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export function addCopyButtonToCodeBlocks(sel) {
|
|
191
|
-
globalThis.copyBlock ??= copyBlock
|
|
192
|
-
//console.log('addCopyButtonToCodeBlocks', sel, [...$$(sel)].length)
|
|
193
|
-
|
|
194
|
-
$$(sel).forEach(code => {
|
|
195
|
-
let pre = code.parentElement;
|
|
196
|
-
if (pre.classList.contains('group')) return
|
|
197
|
-
pre.classList.add('relative', 'group')
|
|
198
|
-
|
|
199
|
-
const div = createElement('div', { attrs: { className: 'opacity-0 group-hover:opacity-100 transition-opacity duration-100 flex absolute right-2 -mt-1 select-none' } })
|
|
200
|
-
const label = createElement('div', { attrs: { className: 'hidden font-sans p-1 px-2 mr-1 rounded-md border border-gray-600 bg-gray-700 text-gray-400' } })
|
|
201
|
-
const btn = createElement('button', {
|
|
202
|
-
attrs: {
|
|
203
|
-
type: 'button',
|
|
204
|
-
className: 'p-1 rounded-md border block text-gray-500 hover:text-gray-400 border-gray-700 hover:border-gray-600',
|
|
205
|
-
onclick: 'copyBlock(this)'
|
|
206
|
-
}
|
|
142
|
+
function time(timestamp) {
|
|
143
|
+
return new Date(timestamp).toLocaleTimeString([], {
|
|
144
|
+
hour: '2-digit',
|
|
145
|
+
minute: '2-digit'
|
|
207
146
|
})
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
div.appendChild(btn)
|
|
211
|
-
pre.insertBefore(div, code)
|
|
212
|
-
})
|
|
213
|
-
}
|
|
147
|
+
}
|
|
148
|
+
|
|
214
149
|
|
|
215
|
-
|
|
216
|
-
|
|
150
|
+
return {
|
|
151
|
+
currFmt: currFmt2,
|
|
152
|
+
tokenCost,
|
|
153
|
+
tokenCostLong,
|
|
154
|
+
cost: formatCost,
|
|
155
|
+
costLong,
|
|
156
|
+
statsTitle,
|
|
157
|
+
relativeTime,
|
|
158
|
+
time,
|
|
159
|
+
}
|
|
217
160
|
}
|
|
218
161
|
|
|
219
162
|
/**
|
|
@@ -226,4 +169,20 @@ export const nextId = (() => {
|
|
|
226
169
|
last = (now > last) ? now : last + 1
|
|
227
170
|
return last
|
|
228
171
|
}
|
|
229
|
-
})();
|
|
172
|
+
})();
|
|
173
|
+
|
|
174
|
+
export function utilsFunctions() {
|
|
175
|
+
return {
|
|
176
|
+
nextId,
|
|
177
|
+
deepClone,
|
|
178
|
+
toJsonArray,
|
|
179
|
+
toJsonObject,
|
|
180
|
+
storageArray,
|
|
181
|
+
storageObject,
|
|
182
|
+
fileToBase64,
|
|
183
|
+
fileToDataUri,
|
|
184
|
+
serializedClone,
|
|
185
|
+
deepClone,
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: llms-py
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.0b3
|
|
4
4
|
Summary: A lightweight CLI tool and OpenAI-compatible server for querying multiple Large Language Model (LLM) providers
|
|
5
5
|
Home-page: https://github.com/ServiceStack/llms
|
|
6
6
|
Author: ServiceStack
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
llms/__init__.py,sha256=DKwTZDsyYL_wHe7yvLw49Nf8PSgPSyWaeVdotUqSvrQ,84
|
|
2
|
+
llms/__main__.py,sha256=hrBulHIt3lmPm1BCyAEVtB6DQ0Hvc3gnIddhHCmJasg,151
|
|
3
|
+
llms/index.html,sha256=gABCGg8ALHyB8OSH745HXx7U0SrXuNtJry9buUbVDik,2117
|
|
4
|
+
llms/llms.json,sha256=j7CmFdMpzNeo0nCPHHR4djYfzH_AFKKYIAH3cL3TKT8,10641
|
|
5
|
+
llms/main.py,sha256=7Ih99-KuA6fBmrytED6099YFORUVDARWXav2p4bLDOk,124887
|
|
6
|
+
llms/providers-extra.json,sha256=w7_5gB0YUPK0PJNeViM7vRDfNGChXUKMHfGHenVxEkM,10165
|
|
7
|
+
llms/providers.json,sha256=x9Y_17h0aqeFMn3pnbjcZ5SW-RJyxHXH8ZjsUQ3L8aA,253843
|
|
8
|
+
llms/__pycache__/__init__.cpython-312.pyc,sha256=lg2oFc0aKgj536NOJxcIpbCpEWi47ptF8NufPphgUUk,204
|
|
9
|
+
llms/__pycache__/__init__.cpython-313.pyc,sha256=DvmykIYAjV4Xjv5NeC70BA0JIX8qtwEWP2dfzmOBrps,173
|
|
10
|
+
llms/__pycache__/__init__.cpython-314.pyc,sha256=9CxeBz41D_-ezno6pIOsnoMAFzdRX2uZjKkBLUm25RQ,206
|
|
11
|
+
llms/__pycache__/__main__.cpython-312.pyc,sha256=fcu9LVl5nfdSzg9HqoKyDGoDvENeIXKkjygN7FENEGE,321
|
|
12
|
+
llms/__pycache__/__main__.cpython-314.pyc,sha256=IFxtGVpJq_3whKuM5Ln7YMweKFNbHVp8M7VSrLK5h2A,324
|
|
13
|
+
llms/__pycache__/llms.cpython-312.pyc,sha256=S5dFI79JdUe2dQW4ogdB-CCNhudQeFaFGcfKxgJGBms,72080
|
|
14
|
+
llms/__pycache__/main.cpython-312.pyc,sha256=HrqApYAiiBmYN73HIx_Hl2-Xm1Gy7I_0zuR5j86qoRM,104940
|
|
15
|
+
llms/__pycache__/main.cpython-313.pyc,sha256=6NQ__SJ2rC9ItFLKLHL5ewb5RqxLzZabwgczA9wZd-w,74814
|
|
16
|
+
llms/__pycache__/main.cpython-314.pyc,sha256=MYiVCsD1BdoR2lyKK-jmGtT0Y9O_3E7rfPaqJp04CnI,161849
|
|
17
|
+
llms/__pycache__/plugins.cpython-314.pyc,sha256=fer8nTkidG_vQSx80tL2bAvMS0opDom93bewjseFcyg,3560
|
|
18
|
+
llms/providers/anthropic.py,sha256=KBwIPkGFkNCJxlLAY5MzFpEj7AB4d0WwpaOfZfvCrk0,8091
|
|
19
|
+
llms/providers/chutes.py,sha256=TTDfWviHOoNfk2cXnY5UJK_fciRJ2drfz48u67TDmCM,6171
|
|
20
|
+
llms/providers/google.py,sha256=aK9t7iPLmziUx7q6blF_G2MQUHFA1h8t461euxWWtVc,15104
|
|
21
|
+
llms/providers/nvidia.py,sha256=HRcI1-fgMGq5_9s6br9YHVVOIaXRtOGCgUPSCHXB-_0,4355
|
|
22
|
+
llms/providers/openai.py,sha256=zQqxXyF9v7CplmzebFIXd-Xfj7AcP_bIK6G8fPZh2p8,6355
|
|
23
|
+
llms/providers/openrouter.py,sha256=unXvR2KMsosWJe8_7k-JhDM8V_XtVUtnNO2psgtkTNk,3055
|
|
24
|
+
llms/providers/__pycache__/anthropic.cpython-314.pyc,sha256=tRLB3xJoPi0dLXY85JgXcD_DHIwhBcScx08NiHTiwdw,9158
|
|
25
|
+
llms/providers/__pycache__/chutes.cpython-314.pyc,sha256=TFAoyWV3rwvmcVi17ACgdqPY0Vh_kITFJ-IOGAtZp8w,7404
|
|
26
|
+
llms/providers/__pycache__/google.cpython-314.pyc,sha256=mprVq3i-Ma9La9EgUcUh7AXUaPWTBn6STM-w2LZN2Tc,13858
|
|
27
|
+
llms/providers/__pycache__/nvidia.cpython-314.pyc,sha256=pE2P5sz0EjRp3DYy-MiEAf_kq3Ns03Q0f-DQGa8UOXQ,6155
|
|
28
|
+
llms/providers/__pycache__/openai.cpython-314.pyc,sha256=-OnbDtGalUx3NtRU_sAwRL_TvrnKOworTliwS164IUY,8705
|
|
29
|
+
llms/providers/__pycache__/openrouter.cpython-314.pyc,sha256=5zAegKxy40P1uX87T9KhaE5s54UXSq9fjsG0i2n84tc,5078
|
|
30
|
+
llms/ui/App.mjs,sha256=W_rDLlK26t-GbKxSxh5X8727FDaOt7_rPTWnaVw7Cq0,7174
|
|
31
|
+
llms/ui/ai.mjs,sha256=sOzBWe2wpNC7jNI4qZJUp7PsyoT0Wj2awJ2L0nfVvDA,5315
|
|
32
|
+
llms/ui/app.css,sha256=11ENCAUhHgC6wuzHccryCaQseZEOjpdh-c_kLDdIAIo,84747
|
|
33
|
+
llms/ui/ctx.mjs,sha256=p-GEB9rBlU5Vag84dDN0b-4YUJ0jBxqJQPEVGVarj9E,6134
|
|
34
|
+
llms/ui/fav.svg,sha256=_R6MFeXl6wBFT0lqcUxYQIDWgm246YH_3hSTW0oO8qw,734
|
|
35
|
+
llms/ui/index.mjs,sha256=J5g0JXuKeMwS65xr6mMivqUeEtojcQV2hsp5QCqcI4U,3820
|
|
36
|
+
llms/ui/markdown.mjs,sha256=WcYnT7xZFOc-TXbghVGSBjQJdLGTXq9vZ1LEIaYdcao,6611
|
|
37
|
+
llms/ui/tailwind.input.css,sha256=5PiDdc5nyPE9Fheg_PgLnz3RR3C0o5x6M3M-KWVFo-4,14403
|
|
38
|
+
llms/ui/typography.css,sha256=6o7pbMIamRVlm2GfzSStpcOG4T5eFCK_WcQ3RIHKAsU,19587
|
|
39
|
+
llms/ui/utils.mjs,sha256=RN7vYsN-rlaVUzIY91yly9seCeA9V9dtXmEtKzcb9Bs,5276
|
|
40
|
+
llms/ui/lib/chart.js,sha256=dx8FdDX0Rv6OZtZjr9FQh5h-twFsKjfnb-FvFlQ--cU,196176
|
|
41
|
+
llms/ui/lib/charts.mjs,sha256=OPFAifcA4MPRv8pw6I7glVh7Xccv8guio0k35zPBDIY,926
|
|
42
|
+
llms/ui/lib/color.js,sha256=DDG7Pr-qzJHTPISZNSqP_qJR8UflKHEc_56n6xrBugQ,8273
|
|
43
|
+
llms/ui/lib/highlight.min.mjs,sha256=sG7wq8bF-IKkfie7S4QSyh5DdHBRf0NqQxMOEH8-MT0,127458
|
|
44
|
+
llms/ui/lib/idb.min.mjs,sha256=CeTXyV4I_pB5vnibvJuyXdMs0iVF2ZL0Z7cdm3w_QaI,3853
|
|
45
|
+
llms/ui/lib/marked.min.mjs,sha256=QRHb_VZugcBJRD2EP6gYlVFEsJw5C2fQ8ImMf_pA2_s,39488
|
|
46
|
+
llms/ui/lib/servicestack-client.mjs,sha256=UVafVbzhJ_0N2lzv7rlzIbzwnWpoqXxGk3N3FSKgOOc,54534
|
|
47
|
+
llms/ui/lib/servicestack-vue.mjs,sha256=unTA8lM0tKy2PwZiJ8UEvrTuGmei8jNZnmmuQ5MKyV4,216753
|
|
48
|
+
llms/ui/lib/vue-router.min.mjs,sha256=fR30GHoXI1u81zyZ26YEU105pZgbbAKSXbpnzFKIxls,30418
|
|
49
|
+
llms/ui/lib/vue.min.mjs,sha256=iXh97m5hotl0eFllb3aoasQTImvp7mQoRJ_0HoxmZkw,163811
|
|
50
|
+
llms/ui/lib/vue.mjs,sha256=dS8LKOG01t9CvZ04i0tbFXHqFXOO_Ha4NmM3BytjQAs,537071
|
|
51
|
+
llms/ui/modules/analytics.mjs,sha256=ZV5ybl4jnzRtHMV04V3OIX-HgiHd2aby3IHzEemkucs,73909
|
|
52
|
+
llms/ui/modules/layout.mjs,sha256=WTy85_8rSyBQooo2f5baw98RzNhpecw4pDpfhEgKv4s,12105
|
|
53
|
+
llms/ui/modules/model-selector.mjs,sha256=dekgCdaqoWX_9fnpI8mhd6lU25JV7vxBoVQVVuF2p-I,67474
|
|
54
|
+
llms/ui/modules/chat/ChatBody.mjs,sha256=GzHimS-haJPF7oqcqViTlOLWgardbetfNGMarTXBWxI,43368
|
|
55
|
+
llms/ui/modules/chat/SettingsDialog.mjs,sha256=N_Xnd9eXkXuw8cVaKR77k16TgOitB9btInW8Qp-WK7U,19929
|
|
56
|
+
llms/ui/modules/chat/index.mjs,sha256=wQWTo54kGRcA68wCjSY-VOPheQTGlxmAOn-LGR76Qnk,33765
|
|
57
|
+
llms/ui/modules/threads/Recents.mjs,sha256=xiN5K9SYc0oJug60m6g_lPVw-75lNkp1ATuVUYgIgX8,8826
|
|
58
|
+
llms/ui/modules/threads/index.mjs,sha256=0ZwVb6W2BT3mwK5bwKgtlJEGJ9IAnQr7qk3XNG1LnFU,11397
|
|
59
|
+
llms/ui/modules/threads/threadStore.mjs,sha256=cGm6g4dn5q0u_K6Bu5xuoAmBuURquubK7R4ahSKNC_E,16886
|
|
60
|
+
llms_py-3.0.0b3.dist-info/licenses/LICENSE,sha256=bus9cuAOWeYqBk2OuhSABVV1P4z7hgrEFISpyda_H5w,1532
|
|
61
|
+
llms_py-3.0.0b3.dist-info/METADATA,sha256=1iKo8N7Owhqbsr3nemrVRlcmZl07jLlkQtT-Q2vTzFw,2193
|
|
62
|
+
llms_py-3.0.0b3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
63
|
+
llms_py-3.0.0b3.dist-info/entry_points.txt,sha256=WswyE7PfnkZMIxboC-MS6flBD6wm-CYU7JSUnMhqMfM,40
|
|
64
|
+
llms_py-3.0.0b3.dist-info/top_level.txt,sha256=gC7hk9BKSeog8gyg-EM_g2gxm1mKHwFRfK-10BxOsa4,5
|
|
65
|
+
llms_py-3.0.0b3.dist-info/RECORD,,
|
llms/ui/Avatar.mjs
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { computed, inject, ref, onMounted, onUnmounted } from "vue"
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
template: `
|
|
5
|
-
<div v-if="$ai.auth?.profileUrl" class="relative" ref="avatarContainer">
|
|
6
|
-
<img
|
|
7
|
-
@click.stop="toggleMenu"
|
|
8
|
-
:src="$ai.auth.profileUrl"
|
|
9
|
-
:title="authTitle"
|
|
10
|
-
class="size-8 rounded-full cursor-pointer hover:ring-2 hover:ring-gray-300"
|
|
11
|
-
/>
|
|
12
|
-
<div
|
|
13
|
-
v-if="showMenu"
|
|
14
|
-
@click.stop
|
|
15
|
-
class="absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 rounded-md shadow-lg py-1 z-50 border border-gray-200 dark:border-gray-700"
|
|
16
|
-
>
|
|
17
|
-
<div class="px-4 py-2 text-sm text-gray-700 dark:text-gray-300 border-b border-gray-200 dark:border-gray-700">
|
|
18
|
-
<div class="font-medium whitespace-nowrap overflow-hidden text-ellipsis">{{ $ai.auth.displayName || $ai.auth.userName }}</div>
|
|
19
|
-
<div class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap overflow-hidden text-ellipsis">{{ $ai.auth.email }}</div>
|
|
20
|
-
</div>
|
|
21
|
-
<button type="button"
|
|
22
|
-
@click="handleLogout"
|
|
23
|
-
class="w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center whitespace-nowrap"
|
|
24
|
-
>
|
|
25
|
-
<svg class="w-4 h-4 mr-2 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
26
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path>
|
|
27
|
-
</svg>
|
|
28
|
-
Sign Out
|
|
29
|
-
</button>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
`,
|
|
33
|
-
setup() {
|
|
34
|
-
const ctx = inject('ctx')
|
|
35
|
-
const ai = ctx.ai
|
|
36
|
-
const showMenu = ref(false)
|
|
37
|
-
const avatarContainer = ref(null)
|
|
38
|
-
|
|
39
|
-
const authTitle = computed(() => {
|
|
40
|
-
if (!ai.auth) return ''
|
|
41
|
-
const { userId, userName, displayName, bearerToken, roles } = ai.auth
|
|
42
|
-
const name = userName || displayName
|
|
43
|
-
const prefix = roles && roles.includes('Admin') ? 'Admin' : 'Name'
|
|
44
|
-
const sb = [
|
|
45
|
-
name ? `${prefix}: ${name}` : '',
|
|
46
|
-
`API Key: ${bearerToken}`,
|
|
47
|
-
`${userId}`,
|
|
48
|
-
]
|
|
49
|
-
return sb.filter(x => x).join('\n')
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
function toggleMenu() {
|
|
53
|
-
showMenu.value = !showMenu.value
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function handleLogout() {
|
|
57
|
-
showMenu.value = false
|
|
58
|
-
await ai.signOut()
|
|
59
|
-
// Reload the page to show sign-in screen
|
|
60
|
-
window.location.reload()
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Close menu when clicking outside
|
|
64
|
-
const handleClickOutside = (event) => {
|
|
65
|
-
if (showMenu.value && avatarContainer.value && !avatarContainer.value.contains(event.target)) {
|
|
66
|
-
showMenu.value = false
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
onMounted(() => {
|
|
71
|
-
document.addEventListener('click', handleClickOutside)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
onUnmounted(() => {
|
|
75
|
-
document.removeEventListener('click', handleClickOutside)
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
authTitle,
|
|
80
|
-
handleLogout,
|
|
81
|
-
showMenu,
|
|
82
|
-
toggleMenu,
|
|
83
|
-
avatarContainer,
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
llms/ui/Brand.mjs
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
template:`
|
|
3
|
-
<div class="flex-shrink-0 pl-2 pr-4 py-4 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 min-h-16 select-none">
|
|
4
|
-
<div class="flex items-center justify-between">
|
|
5
|
-
<div class="flex items-center space-x-2">
|
|
6
|
-
<button type="button"
|
|
7
|
-
@click="$emit('toggle-sidebar')"
|
|
8
|
-
class="group relative text-gray-500 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none transition-colors"
|
|
9
|
-
title="Collapse sidebar"
|
|
10
|
-
>
|
|
11
|
-
<div class="relative size-5">
|
|
12
|
-
<!-- Default sidebar icon -->
|
|
13
|
-
<svg class="absolute inset-0 group-hover:hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
14
|
-
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
15
|
-
<line x1="9" y1="3" x2="9" y2="21"></line>
|
|
16
|
-
</svg>
|
|
17
|
-
<!-- Hover state: |← icon -->
|
|
18
|
-
<svg class="absolute inset-0 hidden group-hover:block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="m10.071 4.929l1.414 1.414L6.828 11H16v2H6.828l4.657 4.657l-1.414 1.414L3 12zM18.001 19V5h2v14z"/></svg>
|
|
19
|
-
</div>
|
|
20
|
-
</button>
|
|
21
|
-
|
|
22
|
-
<button type="button"
|
|
23
|
-
@click="$emit('home')"
|
|
24
|
-
class="text-lg font-semibold text-gray-900 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none transition-colors"
|
|
25
|
-
title="Go back to initial state"
|
|
26
|
-
>
|
|
27
|
-
History
|
|
28
|
-
</button>
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
<div class="flex items-center space-x-2">
|
|
32
|
-
<button type="button"
|
|
33
|
-
@click="$emit('analytics')"
|
|
34
|
-
class="text-gray-900 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none transition-colors"
|
|
35
|
-
title="Analytics"
|
|
36
|
-
>
|
|
37
|
-
<svg class="size-6" 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>
|
|
38
|
-
</button>
|
|
39
|
-
|
|
40
|
-
<button type="button"
|
|
41
|
-
@click="$emit('new')"
|
|
42
|
-
class="text-gray-900 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none transition-colors"
|
|
43
|
-
title="New Chat"
|
|
44
|
-
>
|
|
45
|
-
<svg class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></g></svg>
|
|
46
|
-
</button>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
`,
|
|
51
|
-
emits:['home','new','analytics','toggle-sidebar'],
|
|
52
|
-
}
|