llms-py 3.0.0b2__py3-none-any.whl → 3.0.0b4__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 +65 -28
- 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} +59 -135
- llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +8 -8
- llms/ui/{ChatPrompt.mjs → modules/chat/index.mjs} +242 -46
- 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.0b4.dist-info}/METADATA +1 -1
- llms_py-3.0.0b4.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.0b4.dist-info}/WHEEL +0 -0
- {llms_py-3.0.0b2.dist-info → llms_py-3.0.0b4.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.0b2.dist-info → llms_py-3.0.0b4.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.0b2.dist-info → llms_py-3.0.0b4.dist-info}/top_level.txt +0 -0
|
@@ -1,88 +1,9 @@
|
|
|
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 { 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 { useSettings } from "./SettingsDialog.mjs"
|
|
12
|
-
import Welcome from './Welcome.mjs'
|
|
13
|
-
|
|
14
|
-
const { humanifyMs, humanifyNumber } = useFormatters()
|
|
15
|
-
|
|
16
|
-
const TopBar = {
|
|
17
|
-
template: `
|
|
18
|
-
<div class="flex space-x-2">
|
|
19
|
-
<div v-for="(ext, index) in extensions" :key="ext.id" class="relative flex items-center justify-center">
|
|
20
|
-
<component :is="ext.topBarIcon"
|
|
21
|
-
class="size-7 p-1 cursor-pointer text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 block"
|
|
22
|
-
:class="{ 'bg-gray-100 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded' : ext.isActive($layout.top) }"
|
|
23
|
-
@mouseenter="tooltip = ext.name"
|
|
24
|
-
@mouseleave="tooltip = ''"
|
|
25
|
-
/>
|
|
26
|
-
<div v-if="tooltip === ext.name"
|
|
27
|
-
class="absolute top-full mt-2 px-2 py-1 text-xs text-white bg-gray-900 dark:bg-gray-800 rounded shadow-md z-50 whitespace-nowrap pointer-events-none"
|
|
28
|
-
:class="index <= extensions.length - 1 ? 'right-0' : 'left-1/2 -translate-x-1/2'">
|
|
29
|
-
{{ext.name}}
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
`,
|
|
34
|
-
setup() {
|
|
35
|
-
const ctx = inject('ctx')
|
|
36
|
-
const tooltip = ref('')
|
|
37
|
-
const extensions = computed(() => ctx.extensions.filter(x => x.topBarIcon))
|
|
38
|
-
return {
|
|
39
|
-
extensions,
|
|
40
|
-
tooltip,
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const TopPanel = {
|
|
46
|
-
template: `
|
|
47
|
-
<component v-if="component" :is="component" />
|
|
48
|
-
`,
|
|
49
|
-
setup() {
|
|
50
|
-
const ctx = inject('ctx')
|
|
51
|
-
const component = computed(() => ctx.component(ctx.layout.top))
|
|
52
|
-
return {
|
|
53
|
-
component,
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
3
|
|
|
58
4
|
export default {
|
|
59
|
-
components: {
|
|
60
|
-
TopBar,
|
|
61
|
-
TopPanel,
|
|
62
|
-
ChatPrompt,
|
|
63
|
-
SignIn,
|
|
64
|
-
OAuthSignIn,
|
|
65
|
-
Avatar,
|
|
66
|
-
Welcome,
|
|
67
|
-
},
|
|
68
5
|
template: `
|
|
69
|
-
<div class="flex flex-col h-full
|
|
70
|
-
<!-- Header with model selectors -->
|
|
71
|
-
<div v-if="$ai.hasAccess"
|
|
72
|
-
:class="!$ai.isSidebarOpen ? 'pl-6' : ''"
|
|
73
|
-
class="flex items-center border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-2 w-full min-h-16">
|
|
74
|
-
<div class="flex flex-wrap items-center justify-between w-full">
|
|
75
|
-
<ModelSelector :models="models" v-model="selectedModel" @updated="configUpdated" />
|
|
76
|
-
|
|
77
|
-
<div class="flex items-center space-x-2 pl-4">
|
|
78
|
-
<TopBar />
|
|
79
|
-
<Avatar />
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
<TopPanel />
|
|
85
|
-
|
|
6
|
+
<div class="flex flex-col h-full">
|
|
86
7
|
<!-- Messages Area -->
|
|
87
8
|
<div class="flex-1 overflow-y-auto" ref="messagesContainer">
|
|
88
9
|
<div class="mx-auto max-w-6xl px-4 py-6">
|
|
@@ -148,7 +69,14 @@ export default {
|
|
|
148
69
|
</div>
|
|
149
70
|
|
|
150
71
|
<!-- Messages -->
|
|
151
|
-
<div v-else class="space-y-
|
|
72
|
+
<div v-else class="space-y-2">
|
|
73
|
+
<div v-if="currentThread?.messages.length && currentThread?.model" class="flex items-center justify-center select-none">
|
|
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>
|
|
152
80
|
<div
|
|
153
81
|
v-for="message in currentThread.messages"
|
|
154
82
|
:key="message.id"
|
|
@@ -199,7 +127,7 @@ export default {
|
|
|
199
127
|
|
|
200
128
|
<div
|
|
201
129
|
v-if="message.role === 'assistant'"
|
|
202
|
-
v-html="
|
|
130
|
+
v-html="$fmt.markdown(message.content)"
|
|
203
131
|
class="prose prose-sm max-w-none dark:prose-invert"
|
|
204
132
|
></div>
|
|
205
133
|
|
|
@@ -210,14 +138,23 @@ export default {
|
|
|
210
138
|
<span>{{ isReasoningExpanded(message.id) ? 'Hide reasoning' : 'Show reasoning' }}</span>
|
|
211
139
|
</button>
|
|
212
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">
|
|
213
|
-
<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>
|
|
214
142
|
<pre v-else class="text-xs whitespace-pre-wrap overflow-x-auto text-gray-900 dark:text-gray-100">{{ formatReasoning(message.reasoning) }}</pre>
|
|
215
143
|
</div>
|
|
216
144
|
</div>
|
|
217
145
|
|
|
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
|
+
|
|
218
155
|
<!-- User Message with separate attachments -->
|
|
219
156
|
<div v-if="message.role !== 'assistant'">
|
|
220
|
-
<div v-html="
|
|
157
|
+
<div v-html="$fmt.markdown(message.content)" class="prose prose-sm max-w-none dark:prose-invert break-words"></div>
|
|
221
158
|
|
|
222
159
|
<!-- Attachments Grid -->
|
|
223
160
|
<div v-if="hasAttachments(message)" class="mt-2 flex flex-wrap gap-2">
|
|
@@ -241,13 +178,14 @@ export default {
|
|
|
241
178
|
</div>
|
|
242
179
|
</div>
|
|
243
180
|
|
|
244
|
-
<div class="mt-2 text-xs opacity-70">
|
|
245
|
-
<span>{{
|
|
181
|
+
<div class="mt-2 text-xs opacity-70">
|
|
182
|
+
<span v-if="message.model" @click="$chat.setSelectedModel({ name: message.model })" title="Select model"><span class="cursor-pointer hover:underline">{{ message.model }}</span> • </span>
|
|
183
|
+
<span>{{ $fmt.time(message.timestamp) }}</span>
|
|
246
184
|
<span v-if="message.usage" :title="tokensTitle(message.usage)">
|
|
247
185
|
•
|
|
248
|
-
{{ humanifyNumber(message.usage.tokens) }} tokens
|
|
186
|
+
{{ $fmt.humanifyNumber(message.usage.tokens) }} tokens
|
|
249
187
|
<span v-if="message.usage.cost">· {{ message.usage.cost }}</span>
|
|
250
|
-
<span v-if="message.usage.duration"> in {{ humanifyMs(message.usage.duration) }}</span>
|
|
188
|
+
<span v-if="message.usage.duration"> in {{ $fmt.humanifyMs(message.usage.duration) }}</span>
|
|
251
189
|
</span>
|
|
252
190
|
</div>
|
|
253
191
|
</div>
|
|
@@ -274,8 +212,8 @@ export default {
|
|
|
274
212
|
</div>
|
|
275
213
|
|
|
276
214
|
<div v-if="currentThread.stats && currentThread.stats.outputTokens" class="text-center text-gray-500 dark:text-gray-400 text-sm">
|
|
277
|
-
<span :title="statsTitle(currentThread.stats)">
|
|
278
|
-
{{ currentThread.stats.cost ?
|
|
215
|
+
<span :title="$fmt.statsTitle(currentThread.stats)">
|
|
216
|
+
{{ 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) }}
|
|
279
217
|
</span>
|
|
280
218
|
</div>
|
|
281
219
|
|
|
@@ -342,20 +280,21 @@ export default {
|
|
|
342
280
|
|
|
343
281
|
<!-- Input Area -->
|
|
344
282
|
<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">
|
|
345
|
-
<ChatPrompt :model="
|
|
283
|
+
<ChatPrompt :model="$chat.getSelectedModel()" />
|
|
346
284
|
</div>
|
|
347
285
|
|
|
348
286
|
<!-- Lightbox -->
|
|
349
|
-
<div v-if="lightboxUrl" class="fixed inset-0 z-[100] bg-black/90 flex items-center justify-center p-4 cursor-pointer"
|
|
287
|
+
<div v-if="lightboxUrl" class="fixed inset-0 z-[100] bg-black/90 flex items-center justify-center p-4 cursor-pointer"
|
|
288
|
+
@click="closeLightbox">
|
|
289
|
+
<button type="button" @click="closeLightbox"
|
|
290
|
+
class="absolute top-4 right-4 text-white/70 hover:text-white p-2 rounded-full hover:bg-white/10 transition-colors z-[101]"
|
|
291
|
+
title="Close">
|
|
292
|
+
<svg class="size-8" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
293
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
294
|
+
</svg>
|
|
295
|
+
</button>
|
|
350
296
|
<div class="relative max-w-full max-h-full">
|
|
351
297
|
<img :src="lightboxUrl" class="max-w-full max-h-[90vh] object-contain rounded-sm shadow-2xl" @click.stop />
|
|
352
|
-
<button type="button" @click="closeLightbox"
|
|
353
|
-
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"
|
|
354
|
-
title="Close">
|
|
355
|
-
<svg class="size-8" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
356
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
357
|
-
</svg>
|
|
358
|
-
</button>
|
|
359
298
|
</div>
|
|
360
299
|
</div>
|
|
361
300
|
</div>
|
|
@@ -364,19 +303,13 @@ export default {
|
|
|
364
303
|
const ctx = inject('ctx')
|
|
365
304
|
const models = ctx.state.models
|
|
366
305
|
const config = ctx.state.config
|
|
306
|
+
const threads = ctx.threads
|
|
307
|
+
const chatPrompt = ctx.chat
|
|
308
|
+
const { currentThread } = threads
|
|
309
|
+
const { errorStatus, isGenerating } = ctx.chat
|
|
310
|
+
|
|
367
311
|
const router = useRouter()
|
|
368
312
|
const route = useRoute()
|
|
369
|
-
const threads = useThreadStore()
|
|
370
|
-
const { currentThread } = threads
|
|
371
|
-
const chatPrompt = useChatPrompt()
|
|
372
|
-
const chatSettings = useSettings()
|
|
373
|
-
const {
|
|
374
|
-
errorStatus,
|
|
375
|
-
isGenerating,
|
|
376
|
-
} = chatPrompt
|
|
377
|
-
provide('threads', threads)
|
|
378
|
-
provide('chatPrompt', chatPrompt)
|
|
379
|
-
provide('chatSettings', chatSettings)
|
|
380
313
|
|
|
381
314
|
const prefs = ctx.getPrefs()
|
|
382
315
|
|
|
@@ -399,6 +332,13 @@ export default {
|
|
|
399
332
|
lightboxUrl.value = null
|
|
400
333
|
}
|
|
401
334
|
|
|
335
|
+
const resolveUrl = (url) => {
|
|
336
|
+
if (url && url.startsWith('~')) {
|
|
337
|
+
return '/' + url
|
|
338
|
+
}
|
|
339
|
+
return ctx.ai.resolveUrl(url)
|
|
340
|
+
}
|
|
341
|
+
|
|
402
342
|
// Auto-scroll to bottom when new messages arrive
|
|
403
343
|
const scrollToBottom = async () => {
|
|
404
344
|
await nextTick()
|
|
@@ -422,7 +362,7 @@ export default {
|
|
|
422
362
|
if (!newId) {
|
|
423
363
|
chatPrompt.reset()
|
|
424
364
|
}
|
|
425
|
-
nextTick(addCopyButtons)
|
|
365
|
+
nextTick(ctx.chat.addCopyButtons)
|
|
426
366
|
}, { immediate: true })
|
|
427
367
|
|
|
428
368
|
watch(() => [selectedModel.value], () => {
|
|
@@ -622,14 +562,6 @@ export default {
|
|
|
622
562
|
}
|
|
623
563
|
}
|
|
624
564
|
|
|
625
|
-
// Format timestamp
|
|
626
|
-
const formatTime = (timestamp) => {
|
|
627
|
-
return new Date(timestamp).toLocaleTimeString([], {
|
|
628
|
-
hour: '2-digit',
|
|
629
|
-
minute: '2-digit'
|
|
630
|
-
})
|
|
631
|
-
}
|
|
632
|
-
|
|
633
565
|
// Reasoning collapse state and helpers
|
|
634
566
|
const expandedReasoning = ref(new Set())
|
|
635
567
|
const isReasoningExpanded = (id) => expandedReasoning.value.has(id)
|
|
@@ -721,7 +653,7 @@ export default {
|
|
|
721
653
|
text = message.content
|
|
722
654
|
}
|
|
723
655
|
|
|
724
|
-
const infos = await fetchCacheInfos(getCacheInfos)
|
|
656
|
+
const infos = await ctx.ai.fetchCacheInfos(getCacheInfos)
|
|
725
657
|
// replace name with info.name
|
|
726
658
|
for (let i = 0; i < files.length; i++) {
|
|
727
659
|
const url = files[i]?.url
|
|
@@ -801,22 +733,20 @@ export default {
|
|
|
801
733
|
let title = []
|
|
802
734
|
if (usage.tokens && usage.price) {
|
|
803
735
|
const msg = parseFloat(usage.price) > 0
|
|
804
|
-
? `${usage.tokens} tokens @ ${usage.price} = ${
|
|
736
|
+
? `${usage.tokens} tokens @ ${usage.price} = ${ctx.fmt.tokenCostLong(usage.price, usage.tokens)}`
|
|
805
737
|
: `${usage.tokens} tokens`
|
|
806
738
|
const duration = usage.duration ? ` in ${usage.duration}ms` : ''
|
|
807
739
|
title.push(msg + duration)
|
|
808
740
|
}
|
|
809
741
|
return title.join('\n')
|
|
810
742
|
}
|
|
811
|
-
const numFmt = new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD', minimumFractionDigits: 6 })
|
|
812
|
-
function tokenCost(price, tokens) {
|
|
813
|
-
if (!price || !tokens) return ''
|
|
814
|
-
return numFmt.format(parseFloat(price) * tokens)
|
|
815
|
-
}
|
|
816
743
|
|
|
744
|
+
let sub
|
|
817
745
|
onMounted(() => {
|
|
818
|
-
|
|
746
|
+
sub = ctx.events.subscribe(`keydown:Escape`, closeLightbox)
|
|
747
|
+
setTimeout(ctx.chat.addCopyButtons, 1)
|
|
819
748
|
})
|
|
749
|
+
onUnmounted(() => sub?.unsubscribe())
|
|
820
750
|
|
|
821
751
|
return {
|
|
822
752
|
config,
|
|
@@ -829,8 +759,6 @@ export default {
|
|
|
829
759
|
messagesContainer,
|
|
830
760
|
errorStatus,
|
|
831
761
|
copying,
|
|
832
|
-
formatTime,
|
|
833
|
-
renderMarkdown,
|
|
834
762
|
isReasoningExpanded,
|
|
835
763
|
toggleReasoning,
|
|
836
764
|
formatReasoning,
|
|
@@ -847,16 +775,12 @@ export default {
|
|
|
847
775
|
isImporting,
|
|
848
776
|
fileInput,
|
|
849
777
|
tokensTitle,
|
|
850
|
-
humanifyMs,
|
|
851
|
-
humanifyNumber,
|
|
852
|
-
formatCost,
|
|
853
|
-
formatCost,
|
|
854
|
-
statsTitle,
|
|
855
778
|
getAttachments,
|
|
856
779
|
hasAttachments,
|
|
857
780
|
lightboxUrl,
|
|
858
781
|
openLightbox,
|
|
859
782
|
closeLightbox,
|
|
783
|
+
resolveUrl,
|
|
860
784
|
}
|
|
861
785
|
}
|
|
862
786
|
}
|
|
@@ -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
|
|