llms-py 3.0.0b1__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__/__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/index.html +27 -57
- llms/llms.json +48 -15
- llms/main.py +923 -624
- 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 +150 -57
- llms/ui/ai.mjs +84 -50
- llms/ui/app.css +1 -4963
- llms/ui/ctx.mjs +196 -0
- llms/ui/index.mjs +117 -0
- 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} +91 -179
- llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +8 -8
- llms/ui/{ChatPrompt.mjs → modules/chat/index.mjs} +281 -96
- llms/ui/modules/layout.mjs +267 -0
- llms/ui/modules/model-selector.mjs +851 -0
- llms/ui/{Recents.mjs → modules/threads/Recents.mjs} +10 -11
- llms/ui/{Sidebar.mjs → modules/threads/index.mjs} +48 -45
- llms/ui/{threadStore.mjs → modules/threads/threadStore.mjs} +21 -7
- llms/ui/tailwind.input.css +441 -79
- llms/ui/utils.mjs +83 -123
- {llms_py-3.0.0b1.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 -85
- llms/ui/Brand.mjs +0 -52
- llms/ui/ModelSelector.mjs +0 -693
- llms/ui/OAuthSignIn.mjs +0 -92
- llms/ui/ProviderIcon.mjs +0 -36
- 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.json +0 -1069
- llms_py-3.0.0b1.dist-info/RECORD +0 -49
- {llms_py-3.0.0b1.dist-info → llms_py-3.0.0b3.dist-info}/WHEEL +0 -0
- {llms_py-3.0.0b1.dist-info → llms_py-3.0.0b3.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.0b1.dist-info → llms_py-3.0.0b3.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.0b1.dist-info → llms_py-3.0.0b3.dist-info}/top_level.txt +0 -0
|
@@ -1,56 +1,13 @@
|
|
|
1
|
-
import { ref, computed, nextTick, watch, onMounted,
|
|
1
|
+
import { ref, computed, nextTick, watch, onMounted, onUnmounted, inject } from 'vue'
|
|
2
2
|
import { useRouter, useRoute } from 'vue-router'
|
|
3
|
-
import { useFormatters } from '@servicestack/vue'
|
|
4
|
-
import { useThreadStore } from './threadStore.mjs'
|
|
5
|
-
import { storageObject, addCopyButtons, formatCost, statsTitle, fetchCacheInfos } from './utils.mjs'
|
|
6
|
-
import { renderMarkdown } from './markdown.mjs'
|
|
7
|
-
import ChatPrompt, { useChatPrompt } from './ChatPrompt.mjs'
|
|
8
|
-
import SignIn from './SignIn.mjs'
|
|
9
|
-
import OAuthSignIn from './OAuthSignIn.mjs'
|
|
10
|
-
import Avatar from './Avatar.mjs'
|
|
11
|
-
import ModelSelector from './ModelSelector.mjs'
|
|
12
|
-
import SystemPromptSelector from './SystemPromptSelector.mjs'
|
|
13
|
-
import SystemPromptEditor from './SystemPromptEditor.mjs'
|
|
14
|
-
import { useSettings } from "./SettingsDialog.mjs"
|
|
15
|
-
import Welcome from './Welcome.mjs'
|
|
16
|
-
|
|
17
|
-
const { humanifyMs, humanifyNumber } = useFormatters()
|
|
18
3
|
|
|
19
4
|
export default {
|
|
20
|
-
components: {
|
|
21
|
-
ModelSelector,
|
|
22
|
-
SystemPromptSelector,
|
|
23
|
-
SystemPromptEditor,
|
|
24
|
-
ChatPrompt,
|
|
25
|
-
SignIn,
|
|
26
|
-
OAuthSignIn,
|
|
27
|
-
Avatar,
|
|
28
|
-
Welcome,
|
|
29
|
-
},
|
|
30
5
|
template: `
|
|
31
|
-
<div class="flex flex-col h-full
|
|
32
|
-
<!-- Header with model and prompt selectors (hidden when auth required and not authenticated) -->
|
|
33
|
-
<div v-if="!($ai.requiresAuth && !$ai.auth)"
|
|
34
|
-
:class="!$ai.isSidebarOpen ? 'pl-6' : ''"
|
|
35
|
-
class="border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-2 py-2 w-full min-h-16">
|
|
36
|
-
<div class="flex flex-wrap items-center justify-between w-full">
|
|
37
|
-
<ModelSelector :models="models" v-model="selectedModel" @updated="configUpdated" />
|
|
38
|
-
|
|
39
|
-
<div class="flex items-center space-x-2 pl-4">
|
|
40
|
-
<SystemPromptSelector :prompts="prompts" v-model="selectedPrompt"
|
|
41
|
-
:show="showSystemPrompt" @toggle="showSystemPrompt = !showSystemPrompt" />
|
|
42
|
-
<Avatar />
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
<SystemPromptEditor v-if="showSystemPrompt && !($ai.requiresAuth && !$ai.auth)"
|
|
48
|
-
v-model="currentSystemPrompt" :prompts="prompts" :selected="selectedPrompt" />
|
|
49
|
-
|
|
6
|
+
<div class="flex flex-col h-full">
|
|
50
7
|
<!-- Messages Area -->
|
|
51
8
|
<div class="flex-1 overflow-y-auto" ref="messagesContainer">
|
|
52
9
|
<div class="mx-auto max-w-6xl px-4 py-6">
|
|
53
|
-
<div v-if="
|
|
10
|
+
<div v-if="!$ai.hasAccess">
|
|
54
11
|
<OAuthSignIn v-if="$ai.authType === 'oauth'" @done="$ai.signIn($event)" />
|
|
55
12
|
<SignIn v-else @done="$ai.signIn($event)" />
|
|
56
13
|
</div>
|
|
@@ -113,6 +70,13 @@ export default {
|
|
|
113
70
|
|
|
114
71
|
<!-- Messages -->
|
|
115
72
|
<div v-else class="space-y-6">
|
|
73
|
+
<div v-if="currentThread.messages.length && currentThread.model" class="absolute -mt-2">
|
|
74
|
+
<span @click="$chat.setSelectedModel({ name: currentThread.model})"
|
|
75
|
+
class="flex items-center cursor-pointer px-1.5 py-0.5 text-xs rounded text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-gray-100 transition-colors border hover:border-gray-300 dark:hover:border-gray-700">
|
|
76
|
+
<ProviderIcon class="size-4 mr-1" :provider="$chat.getProviderForModel(currentThread.model)" />
|
|
77
|
+
{{currentThread.model}}
|
|
78
|
+
</span>
|
|
79
|
+
</div>
|
|
116
80
|
<div
|
|
117
81
|
v-for="message in currentThread.messages"
|
|
118
82
|
:key="message.id"
|
|
@@ -163,7 +127,7 @@ export default {
|
|
|
163
127
|
|
|
164
128
|
<div
|
|
165
129
|
v-if="message.role === 'assistant'"
|
|
166
|
-
v-html="
|
|
130
|
+
v-html="$fmt.markdown(message.content)"
|
|
167
131
|
class="prose prose-sm max-w-none dark:prose-invert"
|
|
168
132
|
></div>
|
|
169
133
|
|
|
@@ -174,43 +138,53 @@ export default {
|
|
|
174
138
|
<span>{{ isReasoningExpanded(message.id) ? 'Hide reasoning' : 'Show reasoning' }}</span>
|
|
175
139
|
</button>
|
|
176
140
|
<div v-if="isReasoningExpanded(message.id)" class="mt-2 rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 p-2">
|
|
177
|
-
<div v-if="typeof message.reasoning === 'string'" v-html="
|
|
141
|
+
<div v-if="typeof message.reasoning === 'string'" v-html="$fmt.markdown(message.reasoning)" class="prose prose-xs max-w-none dark:prose-invert"></div>
|
|
178
142
|
<pre v-else class="text-xs whitespace-pre-wrap overflow-x-auto text-gray-900 dark:text-gray-100">{{ formatReasoning(message.reasoning) }}</pre>
|
|
179
143
|
</div>
|
|
180
144
|
</div>
|
|
181
145
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
146
|
+
<!-- Assistant Images -->
|
|
147
|
+
<div v-if="message.images && message.images.length > 0" class="mt-2 flex flex-wrap gap-2">
|
|
148
|
+
<template v-for="(img, i) in message.images" :key="i">
|
|
149
|
+
<div v-if="img.type === 'image_url'" class="group relative cursor-pointer" @click="openLightbox(resolveUrl(img.image_url.url))">
|
|
150
|
+
<img :src="resolveUrl(img.image_url.url)" class="max-w-[400px] max-h-96 rounded-lg border border-gray-200 dark:border-gray-700 object-contain bg-gray-50 dark:bg-gray-900 shadow-sm transition-transform hover:scale-[1.02]" />
|
|
151
|
+
</div>
|
|
152
|
+
</template>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<!-- User Message with separate attachments -->
|
|
156
|
+
<div v-if="message.role !== 'assistant'">
|
|
157
|
+
<div v-html="$fmt.markdown(message.content)" class="prose prose-sm max-w-none dark:prose-invert break-words"></div>
|
|
158
|
+
|
|
159
|
+
<!-- Attachments Grid -->
|
|
160
|
+
<div v-if="hasAttachments(message)" class="mt-2 flex flex-wrap gap-2">
|
|
161
|
+
<template v-for="(part, i) in getAttachments(message)" :key="i">
|
|
162
|
+
<!-- Image -->
|
|
163
|
+
<div v-if="part.type === 'image_url'" class="group relative cursor-pointer" @click="openLightbox(part.image_url.url)">
|
|
164
|
+
<img :src="part.image_url.url" class="max-w-[400px] max-h-96 rounded-lg border border-gray-200 dark:border-gray-700 object-contain bg-gray-50 dark:bg-gray-900 shadow-sm transition-transform hover:scale-[1.02]" />
|
|
165
|
+
</div>
|
|
166
|
+
<!-- Audio -->
|
|
167
|
+
<div v-else-if="part.type === 'input_audio'" class="flex items-center gap-2 p-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
|
168
|
+
<svg class="w-5 h-5 text-gray-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>
|
|
169
|
+
<audio controls :src="part.input_audio.data" class="h-8 w-48"></audio>
|
|
170
|
+
</div>
|
|
171
|
+
<!-- File -->
|
|
172
|
+
<a v-else-if="part.type === 'file'" :href="part.file.file_data" target="_blank"
|
|
173
|
+
class="flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors text-sm text-blue-600 dark:text-blue-400 hover:underline">
|
|
174
|
+
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
|
|
175
|
+
<span class="max-w-xs truncate">{{ part.file.filename || 'Attachment' }}</span>
|
|
176
|
+
</a>
|
|
177
|
+
</template>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
207
181
|
<div class="mt-2 text-xs opacity-70">
|
|
208
|
-
<span>{{
|
|
182
|
+
<span>{{ $fmt.time(message.timestamp) }}</span>
|
|
209
183
|
<span v-if="message.usage" :title="tokensTitle(message.usage)">
|
|
210
184
|
•
|
|
211
|
-
{{ humanifyNumber(message.usage.tokens) }} tokens
|
|
185
|
+
{{ $fmt.humanifyNumber(message.usage.tokens) }} tokens
|
|
212
186
|
<span v-if="message.usage.cost">· {{ message.usage.cost }}</span>
|
|
213
|
-
<span v-if="message.usage.duration"> in {{ humanifyMs(message.usage.duration) }}</span>
|
|
187
|
+
<span v-if="message.usage.duration"> in {{ $fmt.humanifyMs(message.usage.duration) }}</span>
|
|
214
188
|
</span>
|
|
215
189
|
</div>
|
|
216
190
|
</div>
|
|
@@ -237,8 +211,8 @@ export default {
|
|
|
237
211
|
</div>
|
|
238
212
|
|
|
239
213
|
<div v-if="currentThread.stats && currentThread.stats.outputTokens" class="text-center text-gray-500 dark:text-gray-400 text-sm">
|
|
240
|
-
<span :title="statsTitle(currentThread.stats)">
|
|
241
|
-
{{ currentThread.stats.cost ?
|
|
214
|
+
<span :title="$fmt.statsTitle(currentThread.stats)">
|
|
215
|
+
{{ currentThread.stats.cost ? $fmt.costLong(currentThread.stats.cost) + ' for ' : '' }} {{ $fmt.humanifyNumber(currentThread.stats.inputTokens) }} → {{ $fmt.humanifyNumber(currentThread.stats.outputTokens) }} tokens over {{ currentThread.stats.requests }} request{{currentThread.stats.requests===1?'':'s'}} in {{ $fmt.humanifyMs(currentThread.stats.duration) }}
|
|
242
216
|
</span>
|
|
243
217
|
</div>
|
|
244
218
|
|
|
@@ -304,64 +278,45 @@ export default {
|
|
|
304
278
|
</div>
|
|
305
279
|
|
|
306
280
|
<!-- Input Area -->
|
|
307
|
-
<div class="flex-shrink-0 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-6 py-4">
|
|
308
|
-
<ChatPrompt :model="
|
|
281
|
+
<div v-if="$ai.hasAccess" class="flex-shrink-0 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-6 py-4">
|
|
282
|
+
<ChatPrompt :model="$chat.getSelectedModel()" />
|
|
309
283
|
</div>
|
|
310
284
|
|
|
311
285
|
<!-- Lightbox -->
|
|
312
|
-
<div v-if="lightboxUrl" class="fixed inset-0 z-[100] bg-black/90 flex items-center justify-center p-4 cursor-pointer"
|
|
286
|
+
<div v-if="lightboxUrl" class="fixed inset-0 z-[100] bg-black/90 flex items-center justify-center p-4 cursor-pointer"
|
|
287
|
+
@click="closeLightbox">
|
|
288
|
+
<button type="button" @click="closeLightbox"
|
|
289
|
+
class="absolute top-4 right-4 text-white/70 hover:text-white p-2 rounded-full hover:bg-white/10 transition-colors z-[101]"
|
|
290
|
+
title="Close">
|
|
291
|
+
<svg class="size-8" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
292
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
293
|
+
</svg>
|
|
294
|
+
</button>
|
|
313
295
|
<div class="relative max-w-full max-h-full">
|
|
314
296
|
<img :src="lightboxUrl" class="max-w-full max-h-[90vh] object-contain rounded-sm shadow-2xl" @click.stop />
|
|
315
|
-
<button type="button" @click="closeLightbox"
|
|
316
|
-
class="absolute -top-12 right-0 text-white/70 hover:text-white p-2 rounded-full bg-white/10 hover:bg-white/20 transition-colors"
|
|
317
|
-
title="Close">
|
|
318
|
-
<svg class="size-8" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
319
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
320
|
-
</svg>
|
|
321
|
-
</button>
|
|
322
297
|
</div>
|
|
323
298
|
</div>
|
|
324
299
|
</div>
|
|
325
300
|
`,
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const
|
|
301
|
+
setup() {
|
|
302
|
+
const ctx = inject('ctx')
|
|
303
|
+
const models = ctx.state.models
|
|
304
|
+
const config = ctx.state.config
|
|
305
|
+
const threads = ctx.threads
|
|
306
|
+
const chatPrompt = ctx.chat
|
|
307
|
+
const { currentThread } = threads
|
|
308
|
+
const { errorStatus, isGenerating } = ctx.chat
|
|
309
|
+
|
|
330
310
|
const router = useRouter()
|
|
331
311
|
const route = useRoute()
|
|
332
|
-
const threads = useThreadStore()
|
|
333
|
-
const { currentThread } = threads
|
|
334
|
-
const chatPrompt = useChatPrompt()
|
|
335
|
-
const chatSettings = useSettings()
|
|
336
|
-
const {
|
|
337
|
-
errorStatus,
|
|
338
|
-
isGenerating,
|
|
339
|
-
} = chatPrompt
|
|
340
|
-
provide('threads', threads)
|
|
341
|
-
provide('chatPrompt', chatPrompt)
|
|
342
|
-
provide('chatSettings', chatSettings)
|
|
343
|
-
const models = inject('models')
|
|
344
|
-
const config = inject('config')
|
|
345
|
-
|
|
346
|
-
const prefs = storageObject(ai.prefsKey)
|
|
347
|
-
|
|
348
|
-
const customPromptValue = ref('')
|
|
349
|
-
const customPrompt = {
|
|
350
|
-
id: '_custom_',
|
|
351
|
-
name: 'Custom...',
|
|
352
|
-
value: ''
|
|
353
|
-
}
|
|
354
312
|
|
|
355
|
-
const
|
|
313
|
+
const prefs = ctx.getPrefs()
|
|
356
314
|
|
|
357
315
|
const selectedModel = ref(prefs.model || config.defaults.text.model || '')
|
|
358
316
|
const selectedModelObj = computed(() => {
|
|
359
317
|
if (!selectedModel.value || !models) return null
|
|
360
318
|
return models.find(m => m.name === selectedModel.value) || models.find(m => m.id === selectedModel.value)
|
|
361
319
|
})
|
|
362
|
-
const selectedPrompt = ref(prefs.systemPrompt || null)
|
|
363
|
-
const currentSystemPrompt = ref('')
|
|
364
|
-
const showSystemPrompt = ref(false)
|
|
365
320
|
const messagesContainer = ref(null)
|
|
366
321
|
const isExporting = ref(false)
|
|
367
322
|
const isImporting = ref(false)
|
|
@@ -376,6 +331,13 @@ export default {
|
|
|
376
331
|
lightboxUrl.value = null
|
|
377
332
|
}
|
|
378
333
|
|
|
334
|
+
const resolveUrl = (url) => {
|
|
335
|
+
if (url && url.startsWith('~')) {
|
|
336
|
+
return '/' + url
|
|
337
|
+
}
|
|
338
|
+
return ctx.ai.resolveUrl(url)
|
|
339
|
+
}
|
|
340
|
+
|
|
379
341
|
// Auto-scroll to bottom when new messages arrive
|
|
380
342
|
const scrollToBottom = async () => {
|
|
381
343
|
await nextTick()
|
|
@@ -396,45 +358,16 @@ export default {
|
|
|
396
358
|
selectedModel.value = thread.model
|
|
397
359
|
}
|
|
398
360
|
|
|
399
|
-
// Sync System Prompt selection from thread
|
|
400
|
-
if (thread) {
|
|
401
|
-
const norm = s => (s || '').replace(/\s+/g, ' ').trim()
|
|
402
|
-
const tsp = norm(thread.systemPrompt || '')
|
|
403
|
-
if (tsp) {
|
|
404
|
-
const match = config.prompts.find(p => norm(p.value) === tsp)
|
|
405
|
-
if (match) {
|
|
406
|
-
selectedPrompt.value = match
|
|
407
|
-
currentSystemPrompt.value = match.value.replace(/\n/g, ' ')
|
|
408
|
-
} else {
|
|
409
|
-
selectedPrompt.value = customPrompt
|
|
410
|
-
currentSystemPrompt.value = thread.systemPrompt
|
|
411
|
-
}
|
|
412
|
-
} else {
|
|
413
|
-
// Preserve existing selected prompt
|
|
414
|
-
// selectedPrompt.value = null
|
|
415
|
-
// currentSystemPrompt.value = ''
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
361
|
if (!newId) {
|
|
420
362
|
chatPrompt.reset()
|
|
421
363
|
}
|
|
422
|
-
nextTick(addCopyButtons)
|
|
364
|
+
nextTick(ctx.chat.addCopyButtons)
|
|
423
365
|
}, { immediate: true })
|
|
424
366
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
// If using a custom prompt, keep whatever is already in currentSystemPrompt
|
|
428
|
-
if (newPrompt && newPrompt.id === '_custom_') return
|
|
429
|
-
const prompt = newPrompt && config.prompts.find(p => p.id === newPrompt.id)
|
|
430
|
-
currentSystemPrompt.value = prompt ? prompt.value.replace(/\n/g, ' ') : ''
|
|
431
|
-
}, { immediate: true })
|
|
432
|
-
|
|
433
|
-
watch(() => [selectedModel.value, selectedPrompt.value], () => {
|
|
434
|
-
localStorage.setItem(ai.prefsKey, JSON.stringify({
|
|
367
|
+
watch(() => [selectedModel.value], () => {
|
|
368
|
+
ctx.setPrefs({
|
|
435
369
|
model: selectedModel.value,
|
|
436
|
-
|
|
437
|
-
}))
|
|
370
|
+
})
|
|
438
371
|
})
|
|
439
372
|
|
|
440
373
|
async function exportThreads() {
|
|
@@ -628,14 +561,6 @@ export default {
|
|
|
628
561
|
}
|
|
629
562
|
}
|
|
630
563
|
|
|
631
|
-
// Format timestamp
|
|
632
|
-
const formatTime = (timestamp) => {
|
|
633
|
-
return new Date(timestamp).toLocaleTimeString([], {
|
|
634
|
-
hour: '2-digit',
|
|
635
|
-
minute: '2-digit'
|
|
636
|
-
})
|
|
637
|
-
}
|
|
638
|
-
|
|
639
564
|
// Reasoning collapse state and helpers
|
|
640
565
|
const expandedReasoning = ref(new Set())
|
|
641
566
|
const isReasoningExpanded = (id) => expandedReasoning.value.has(id)
|
|
@@ -727,7 +652,7 @@ export default {
|
|
|
727
652
|
text = message.content
|
|
728
653
|
}
|
|
729
654
|
|
|
730
|
-
const infos = await fetchCacheInfos(getCacheInfos)
|
|
655
|
+
const infos = await ctx.ai.fetchCacheInfos(getCacheInfos)
|
|
731
656
|
// replace name with info.name
|
|
732
657
|
for (let i = 0; i < files.length; i++) {
|
|
733
658
|
const url = files[i]?.url
|
|
@@ -807,41 +732,32 @@ export default {
|
|
|
807
732
|
let title = []
|
|
808
733
|
if (usage.tokens && usage.price) {
|
|
809
734
|
const msg = parseFloat(usage.price) > 0
|
|
810
|
-
? `${usage.tokens} tokens @ ${usage.price} = ${
|
|
735
|
+
? `${usage.tokens} tokens @ ${usage.price} = ${ctx.fmt.tokenCostLong(usage.price, usage.tokens)}`
|
|
811
736
|
: `${usage.tokens} tokens`
|
|
812
737
|
const duration = usage.duration ? ` in ${usage.duration}ms` : ''
|
|
813
738
|
title.push(msg + duration)
|
|
814
739
|
}
|
|
815
740
|
return title.join('\n')
|
|
816
741
|
}
|
|
817
|
-
const numFmt = new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD', minimumFractionDigits: 6 })
|
|
818
|
-
function tokenCost(price, tokens) {
|
|
819
|
-
if (!price || !tokens) return ''
|
|
820
|
-
return numFmt.format(parseFloat(price) * tokens)
|
|
821
|
-
}
|
|
822
742
|
|
|
743
|
+
let sub
|
|
823
744
|
onMounted(() => {
|
|
824
|
-
|
|
745
|
+
sub = ctx.events.subscribe(`keydown:Escape`, closeLightbox)
|
|
746
|
+
setTimeout(ctx.chat.addCopyButtons, 1)
|
|
825
747
|
})
|
|
748
|
+
onUnmounted(() => sub?.unsubscribe())
|
|
826
749
|
|
|
827
750
|
return {
|
|
828
751
|
config,
|
|
829
752
|
models,
|
|
830
753
|
threads,
|
|
831
|
-
prompts,
|
|
832
754
|
isGenerating,
|
|
833
|
-
customPromptValue,
|
|
834
755
|
currentThread,
|
|
835
756
|
selectedModel,
|
|
836
757
|
selectedModelObj,
|
|
837
|
-
selectedPrompt,
|
|
838
|
-
currentSystemPrompt,
|
|
839
|
-
showSystemPrompt,
|
|
840
758
|
messagesContainer,
|
|
841
759
|
errorStatus,
|
|
842
760
|
copying,
|
|
843
|
-
formatTime,
|
|
844
|
-
renderMarkdown,
|
|
845
761
|
isReasoningExpanded,
|
|
846
762
|
toggleReasoning,
|
|
847
763
|
formatReasoning,
|
|
@@ -858,16 +774,12 @@ export default {
|
|
|
858
774
|
isImporting,
|
|
859
775
|
fileInput,
|
|
860
776
|
tokensTitle,
|
|
861
|
-
humanifyMs,
|
|
862
|
-
humanifyNumber,
|
|
863
|
-
formatCost,
|
|
864
|
-
formatCost,
|
|
865
|
-
statsTitle,
|
|
866
777
|
getAttachments,
|
|
867
778
|
hasAttachments,
|
|
868
779
|
lightboxUrl,
|
|
869
780
|
openLightbox,
|
|
870
781
|
closeLightbox,
|
|
782
|
+
resolveUrl,
|
|
871
783
|
}
|
|
872
784
|
}
|
|
873
785
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ref, computed, watch, inject } from 'vue'
|
|
2
|
-
import { storageObject } from '
|
|
2
|
+
import { storageObject } from '../../utils.mjs'
|
|
3
3
|
|
|
4
4
|
const settingsKey = 'llms.settings'
|
|
5
5
|
|
|
@@ -40,7 +40,7 @@ export function useSettings() {
|
|
|
40
40
|
]
|
|
41
41
|
|
|
42
42
|
let settings = ref(storageObject(settingsKey))
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
function validSettings(localSettings) {
|
|
45
45
|
const to = {}
|
|
46
46
|
intFields.forEach(f => {
|
|
@@ -65,9 +65,9 @@ export function useSettings() {
|
|
|
65
65
|
})
|
|
66
66
|
listFields.forEach(f => {
|
|
67
67
|
if (localSettings[f] != null && localSettings[f] !== '') {
|
|
68
|
-
to[f] = Array.isArray(localSettings[f])
|
|
68
|
+
to[f] = Array.isArray(localSettings[f])
|
|
69
69
|
? localSettings[f]
|
|
70
|
-
: typeof localSettings[f] == 'string'
|
|
70
|
+
: typeof localSettings[f] == 'string'
|
|
71
71
|
? localSettings[f].split(',').map(x => x.trim())
|
|
72
72
|
: []
|
|
73
73
|
}
|
|
@@ -88,7 +88,7 @@ export function useSettings() {
|
|
|
88
88
|
function resetSettings() {
|
|
89
89
|
return saveSettings({})
|
|
90
90
|
}
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
function saveSettings(localSettings) {
|
|
93
93
|
// console.log('saveSettings', JSON.stringify(localSettings, undefined, 2))
|
|
94
94
|
settings.value = validSettings(localSettings)
|
|
@@ -337,9 +337,9 @@ export default {
|
|
|
337
337
|
},
|
|
338
338
|
emits: ['close'],
|
|
339
339
|
setup(props, { emit }) {
|
|
340
|
-
const
|
|
341
|
-
const { settings, saveSettings, resetSettings } =
|
|
342
|
-
|
|
340
|
+
const ctx = inject('ctx')
|
|
341
|
+
const { settings, saveSettings, resetSettings } = ctx.chat.settings
|
|
342
|
+
|
|
343
343
|
// Local copy for editing
|
|
344
344
|
const localSettings = ref(Object.assign({}, settings.value))
|
|
345
345
|
|