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,13 +1,115 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
import { ref, computed, watch, nextTick, inject } from 'vue'
|
|
2
3
|
import { useRouter } from 'vue-router'
|
|
3
|
-
import { lastRightPart } from
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import { $$, createElement, lastRightPart } from "@servicestack/client"
|
|
5
|
+
import SettingsDialog, { useSettings } from './SettingsDialog.mjs'
|
|
6
|
+
import ChatBody from './ChatBody.mjs'
|
|
7
|
+
import { AppContext } from '../../ctx.mjs'
|
|
6
8
|
|
|
7
9
|
const imageExts = 'png,webp,jpg,jpeg,gif,bmp,svg,tiff,ico'.split(',')
|
|
8
10
|
const audioExts = 'mp3,wav,ogg,flac,m4a,opus,webm'.split(',')
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
/* Example image generation request: https://openrouter.ai/docs/guides/overview/multimodal/image-generation
|
|
13
|
+
{
|
|
14
|
+
"model": "google/gemini-2.5-flash-image-preview",
|
|
15
|
+
"messages": [
|
|
16
|
+
{
|
|
17
|
+
"role": "user",
|
|
18
|
+
"content": "Create a picture of a nano banana dish in a fancy restaurant with a Gemini theme"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"modalities": ["image", "text"],
|
|
22
|
+
"image_config": {
|
|
23
|
+
"aspect_ratio": "16:9"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
Example response:
|
|
27
|
+
{
|
|
28
|
+
"choices": [
|
|
29
|
+
{
|
|
30
|
+
"message": {
|
|
31
|
+
"role": "assistant",
|
|
32
|
+
"content": "I've generated a beautiful sunset image for you.",
|
|
33
|
+
"images": [
|
|
34
|
+
{
|
|
35
|
+
"type": "image_url",
|
|
36
|
+
"image_url": {
|
|
37
|
+
"url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
*/
|
|
46
|
+
const imageAspectRatios = {
|
|
47
|
+
'1024×1024': '1:1',
|
|
48
|
+
'832×1248': '2:3',
|
|
49
|
+
'1248×832': '3:2',
|
|
50
|
+
'864×1184': '3:4',
|
|
51
|
+
'1184×864': '4:3',
|
|
52
|
+
'896×1152': '4:5',
|
|
53
|
+
'1152×896': '5:4',
|
|
54
|
+
'768×1344': '9:16',
|
|
55
|
+
'1344×768': '16:9',
|
|
56
|
+
'1536×672': '21:9',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const svg = {
|
|
60
|
+
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>`,
|
|
61
|
+
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>`,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function copyBlock(btn) {
|
|
65
|
+
// console.log('copyBlock',btn)
|
|
66
|
+
const label = btn.previousElementSibling
|
|
67
|
+
const code = btn.parentElement.nextElementSibling
|
|
68
|
+
label.classList.remove('hidden')
|
|
69
|
+
label.innerHTML = 'copied'
|
|
70
|
+
btn.classList.add('border-gray-600', 'bg-gray-700')
|
|
71
|
+
btn.classList.remove('border-gray-700')
|
|
72
|
+
btn.innerHTML = svg.check
|
|
73
|
+
navigator.clipboard.writeText(code.innerText)
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
label.classList.add('hidden')
|
|
76
|
+
label.innerHTML = ''
|
|
77
|
+
btn.innerHTML = svg.clipboard
|
|
78
|
+
btn.classList.remove('border-gray-600', 'bg-gray-700')
|
|
79
|
+
btn.classList.add('border-gray-700')
|
|
80
|
+
}, 2000)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function addCopyButtonToCodeBlocks(sel) {
|
|
84
|
+
globalThis.copyBlock ??= copyBlock
|
|
85
|
+
//console.log('addCopyButtonToCodeBlocks', sel, [...$$(sel)].length)
|
|
86
|
+
|
|
87
|
+
$$(sel).forEach(code => {
|
|
88
|
+
let pre = code.parentElement;
|
|
89
|
+
if (pre.classList.contains('group')) return
|
|
90
|
+
pre.classList.add('relative', 'group')
|
|
91
|
+
|
|
92
|
+
const div = createElement('div', { attrs: { className: 'opacity-0 group-hover:opacity-100 transition-opacity duration-100 flex absolute right-2 -mt-1 select-none' } })
|
|
93
|
+
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' } })
|
|
94
|
+
const btn = createElement('button', {
|
|
95
|
+
attrs: {
|
|
96
|
+
type: 'button',
|
|
97
|
+
className: 'p-1 rounded-md border block text-gray-500 hover:text-gray-400 border-gray-700 hover:border-gray-600',
|
|
98
|
+
onclick: 'copyBlock(this)'
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
btn.innerHTML = svg.clipboard
|
|
102
|
+
div.appendChild(label)
|
|
103
|
+
div.appendChild(btn)
|
|
104
|
+
pre.insertBefore(div, code)
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function addCopyButtons() {
|
|
109
|
+
addCopyButtonToCodeBlocks('.prose pre>code')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function useChatPrompt(ctx) {
|
|
11
113
|
const messageText = ref('')
|
|
12
114
|
const attachedFiles = ref([])
|
|
13
115
|
const isGenerating = ref(false)
|
|
@@ -39,6 +141,30 @@ export function useChatPrompt() {
|
|
|
39
141
|
abortController.value = null
|
|
40
142
|
}
|
|
41
143
|
|
|
144
|
+
const settings = useSettings()
|
|
145
|
+
|
|
146
|
+
function getModel(name) {
|
|
147
|
+
return ctx.state.models.find(x => x.name === name) ?? ctx.state.models.find(x => x.id === name)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getSelectedModel() {
|
|
151
|
+
const candidates = [ctx.state.selectedModel, ctx.state.config.defaults.text.model]
|
|
152
|
+
return candidates.map(name => name && getModel(name)).find(x => !!x)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function setSelectedModel(model) {
|
|
156
|
+
ctx.setState({
|
|
157
|
+
selectedModel: model.name
|
|
158
|
+
})
|
|
159
|
+
ctx.setPrefs({
|
|
160
|
+
model: model.name
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function getProviderForModel(model) {
|
|
165
|
+
return getModel(model)?.provider
|
|
166
|
+
}
|
|
167
|
+
|
|
42
168
|
return {
|
|
43
169
|
messageText,
|
|
44
170
|
attachedFiles,
|
|
@@ -55,10 +181,16 @@ export function useChatPrompt() {
|
|
|
55
181
|
// hasText,
|
|
56
182
|
reset,
|
|
57
183
|
cancel,
|
|
184
|
+
settings,
|
|
185
|
+
addCopyButtons,
|
|
186
|
+
getModel,
|
|
187
|
+
getSelectedModel,
|
|
188
|
+
setSelectedModel,
|
|
189
|
+
getProviderForModel,
|
|
58
190
|
}
|
|
59
191
|
}
|
|
60
192
|
|
|
61
|
-
|
|
193
|
+
const ChatPrompt = {
|
|
62
194
|
template: `
|
|
63
195
|
<div class="mx-auto max-w-3xl">
|
|
64
196
|
<SettingsDialog :isOpen="showSettings" @close="showSettings = false" />
|
|
@@ -125,20 +257,31 @@ export default {
|
|
|
125
257
|
</button>
|
|
126
258
|
</div>
|
|
127
259
|
|
|
128
|
-
<!--
|
|
129
|
-
<div
|
|
130
|
-
<div
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
<
|
|
134
|
-
|
|
260
|
+
<!-- Attachments & Image Options -->
|
|
261
|
+
<div class="mt-2 flex justify-between items-start gap-2">
|
|
262
|
+
<div class="flex flex-wrap gap-2">
|
|
263
|
+
<div v-for="(f,i) in attachedFiles" :key="i" class="flex items-center gap-2 px-2 py-1 rounded-md border border-gray-300 dark:border-gray-600 text-xs text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-800">
|
|
264
|
+
<span class="truncate max-w-48" :title="f.name">{{ f.name }}</span>
|
|
265
|
+
<button type="button" class="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200" @click="removeAttachment(i)" title="Remove Attachment">
|
|
266
|
+
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
|
267
|
+
</button>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<!-- Image Aspect Ratio Selector -->
|
|
272
|
+
<div v-if="canGenerateImages" class="min-w-[120px]">
|
|
273
|
+
<select name="aspect_ratio" v-model="$state.selectedAspectRatio"
|
|
274
|
+
class="block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-xs text-gray-700 dark:text-gray-300 pl-2 pr-6 py-1 focus:ring-blue-500 focus:border-blue-500">
|
|
275
|
+
<option v-for="(ratio, size) in imageAspectRatios" :key="size" :value="size">
|
|
276
|
+
{{ size }} ({{ ratio }})
|
|
277
|
+
</option>
|
|
278
|
+
</select>
|
|
135
279
|
</div>
|
|
136
280
|
</div>
|
|
137
281
|
|
|
138
282
|
<div v-if="!model" class="mt-2 text-sm text-red-600 dark:text-red-400">
|
|
139
283
|
Please select a model
|
|
140
284
|
</div>
|
|
141
|
-
</div>
|
|
142
285
|
</div>
|
|
143
286
|
</div>
|
|
144
287
|
`,
|
|
@@ -152,9 +295,8 @@ export default {
|
|
|
152
295
|
const ctx = inject('ctx')
|
|
153
296
|
const config = ctx.state.config
|
|
154
297
|
const ai = ctx.ai
|
|
155
|
-
const chatSettings = inject('chatSettings')
|
|
156
298
|
const router = useRouter()
|
|
157
|
-
const chatPrompt =
|
|
299
|
+
const chatPrompt = ctx.chat
|
|
158
300
|
const {
|
|
159
301
|
messageText,
|
|
160
302
|
attachedFiles,
|
|
@@ -163,17 +305,21 @@ export default {
|
|
|
163
305
|
hasImage,
|
|
164
306
|
hasAudio,
|
|
165
307
|
hasFile,
|
|
166
|
-
editingMessageId
|
|
308
|
+
editingMessageId,
|
|
167
309
|
} = chatPrompt
|
|
168
|
-
const threads =
|
|
310
|
+
const threads = ctx.threads
|
|
169
311
|
const {
|
|
170
312
|
currentThread,
|
|
171
|
-
} = threads
|
|
313
|
+
} = ctx.threads
|
|
172
314
|
|
|
173
315
|
const fileInput = ref(null)
|
|
174
316
|
const refMessage = ref(null)
|
|
175
317
|
const showSettings = ref(false)
|
|
176
|
-
const { applySettings } =
|
|
318
|
+
const { applySettings } = ctx.chat.settings
|
|
319
|
+
|
|
320
|
+
const canGenerateImages = computed(() => {
|
|
321
|
+
return props.model?.modalities?.output?.includes('image')
|
|
322
|
+
})
|
|
177
323
|
|
|
178
324
|
// File attachments (+) handlers
|
|
179
325
|
const triggerFilePicker = () => {
|
|
@@ -185,7 +331,7 @@ export default {
|
|
|
185
331
|
// Upload files immediately
|
|
186
332
|
const uploadedFiles = await Promise.all(files.map(async f => {
|
|
187
333
|
try {
|
|
188
|
-
const response = await uploadFile(f)
|
|
334
|
+
const response = await ctx.ai.uploadFile(f)
|
|
189
335
|
const metadata = {
|
|
190
336
|
url: response.url,
|
|
191
337
|
name: f.name,
|
|
@@ -231,24 +377,6 @@ export default {
|
|
|
231
377
|
attachedFiles.value.splice(i, 1)
|
|
232
378
|
}
|
|
233
379
|
|
|
234
|
-
// Helper function to add files and set default message
|
|
235
|
-
const addFilesAndSetMessage = (files) => {
|
|
236
|
-
if (files.length === 0) return
|
|
237
|
-
|
|
238
|
-
attachedFiles.value.push(...files)
|
|
239
|
-
|
|
240
|
-
// Set default message text if empty
|
|
241
|
-
if (!messageText.value.trim()) {
|
|
242
|
-
if (hasImage()) {
|
|
243
|
-
messageText.value = getTextContent(config.defaults.image)
|
|
244
|
-
} else if (hasAudio()) {
|
|
245
|
-
messageText.value = getTextContent(config.defaults.audio)
|
|
246
|
-
} else {
|
|
247
|
-
messageText.value = getTextContent(config.defaults.file)
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
380
|
// Handle paste events for clipboard images, audio, and files
|
|
253
381
|
const onPaste = async (e) => {
|
|
254
382
|
// Use the paste event's clipboardData directly (works best for paste events)
|
|
@@ -328,9 +456,16 @@ export default {
|
|
|
328
456
|
return textMessage?.content.find(c => c.type === 'text')?.text || ''
|
|
329
457
|
}
|
|
330
458
|
|
|
459
|
+
function toModelInfo(model) {
|
|
460
|
+
if (!model) return undefined
|
|
461
|
+
const { id, name, provider, cost, modalities } = model
|
|
462
|
+
return ctx.utils.deepClone({ id, name, provider, cost, modalities })
|
|
463
|
+
}
|
|
464
|
+
|
|
331
465
|
// Send message
|
|
332
466
|
const sendMessage = async () => {
|
|
333
|
-
if (!messageText.value.trim()
|
|
467
|
+
if (!messageText.value.trim() && !hasImage() && !hasAudio() && !hasFile()) return
|
|
468
|
+
if (isGenerating.value || !props.model) return
|
|
334
469
|
|
|
335
470
|
// Clear any existing error message
|
|
336
471
|
errorStatus.value = null
|
|
@@ -360,6 +495,7 @@ export default {
|
|
|
360
495
|
// Create AbortController for this request
|
|
361
496
|
const controller = new AbortController()
|
|
362
497
|
chatPrompt.abortController.value = controller
|
|
498
|
+
const model = props.model.name
|
|
363
499
|
|
|
364
500
|
try {
|
|
365
501
|
let threadId
|
|
@@ -368,7 +504,7 @@ export default {
|
|
|
368
504
|
if (!currentThread.value) {
|
|
369
505
|
const newThread = await threads.createThread({
|
|
370
506
|
title: 'New Chat',
|
|
371
|
-
model
|
|
507
|
+
model,
|
|
372
508
|
info: toModelInfo(props.model),
|
|
373
509
|
})
|
|
374
510
|
threadId = newThread.id
|
|
@@ -378,7 +514,7 @@ export default {
|
|
|
378
514
|
threadId = currentThread.value.id
|
|
379
515
|
// Update the existing thread's model to match current selection
|
|
380
516
|
await threads.updateThread(threadId, {
|
|
381
|
-
model
|
|
517
|
+
model,
|
|
382
518
|
info: toModelInfo(props.model),
|
|
383
519
|
})
|
|
384
520
|
}
|
|
@@ -424,7 +560,8 @@ export default {
|
|
|
424
560
|
if (!isDuplicate) {
|
|
425
561
|
await threads.addMessageToThread(threadId, {
|
|
426
562
|
role: 'user',
|
|
427
|
-
content: content
|
|
563
|
+
content: content,
|
|
564
|
+
model: props.model.name,
|
|
428
565
|
})
|
|
429
566
|
// Reload thread after adding message
|
|
430
567
|
thread = await threads.getThread(threadId)
|
|
@@ -435,7 +572,7 @@ export default {
|
|
|
435
572
|
|
|
436
573
|
// Construct API Request from History
|
|
437
574
|
const request = {
|
|
438
|
-
model
|
|
575
|
+
model,
|
|
439
576
|
messages: [],
|
|
440
577
|
metadata: {}
|
|
441
578
|
}
|
|
@@ -450,6 +587,14 @@ export default {
|
|
|
450
587
|
|
|
451
588
|
// Apply user settings
|
|
452
589
|
applySettings(request)
|
|
590
|
+
|
|
591
|
+
if (canGenerateImages.value) {
|
|
592
|
+
request.image_config = {
|
|
593
|
+
aspect_ratio: imageAspectRatios[ctx.state.selectedAspectRatio] || '1:1'
|
|
594
|
+
}
|
|
595
|
+
request.modalities = ["image", "text"]
|
|
596
|
+
}
|
|
597
|
+
|
|
453
598
|
request.metadata.threadId = threadId
|
|
454
599
|
|
|
455
600
|
const ctxRequest = {
|
|
@@ -533,10 +678,11 @@ export default {
|
|
|
533
678
|
usage.output = output
|
|
534
679
|
usage.tokens = usage.completion_tokens
|
|
535
680
|
usage.price = usage.output
|
|
536
|
-
usage.cost = tokenCost(usage.prompt_tokens / 1_000_000 * parseFloat(input) + usage.completion_tokens / 1_000_000 * parseFloat(output))
|
|
681
|
+
usage.cost = ctx.fmt.tokenCost(usage.prompt_tokens / 1_000_000 * parseFloat(input) + usage.completion_tokens / 1_000_000 * parseFloat(output))
|
|
537
682
|
}
|
|
538
683
|
await threads.logRequest(threadId, props.model, request, response)
|
|
539
684
|
}
|
|
685
|
+
assistantMessage.model = props.model.name
|
|
540
686
|
await threads.addMessageToThread(threadId, assistantMessage, usage)
|
|
541
687
|
|
|
542
688
|
nextTick(addCopyButtons)
|
|
@@ -574,6 +720,10 @@ export default {
|
|
|
574
720
|
//messageText.value += '\n'
|
|
575
721
|
}
|
|
576
722
|
|
|
723
|
+
watch(() => ctx.state.selectedAspectRatio, newValue => {
|
|
724
|
+
ctx.setPrefs({ aspectRatio: newValue })
|
|
725
|
+
})
|
|
726
|
+
|
|
577
727
|
return {
|
|
578
728
|
isGenerating,
|
|
579
729
|
attachedFiles,
|
|
@@ -592,6 +742,52 @@ export default {
|
|
|
592
742
|
sendMessage,
|
|
593
743
|
cancelRequest,
|
|
594
744
|
addNewLine,
|
|
745
|
+
imageAspectRatios,
|
|
746
|
+
canGenerateImages,
|
|
595
747
|
}
|
|
596
748
|
}
|
|
597
|
-
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
export default {
|
|
752
|
+
/**@param {AppContext} ctx */
|
|
753
|
+
install(ctx) {
|
|
754
|
+
const Home = ChatBody
|
|
755
|
+
ctx.components({
|
|
756
|
+
SettingsDialog,
|
|
757
|
+
ChatPrompt,
|
|
758
|
+
ChatBody,
|
|
759
|
+
Home,
|
|
760
|
+
})
|
|
761
|
+
ctx.setGlobals({
|
|
762
|
+
chat: useChatPrompt(ctx)
|
|
763
|
+
})
|
|
764
|
+
|
|
765
|
+
ctx.setLeftIcons({
|
|
766
|
+
chat: {
|
|
767
|
+
component: {
|
|
768
|
+
template: `<svg @click="$ctx.togglePath('/')" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="currentColor" d="M8 2.19c3.13 0 5.68 2.25 5.68 5s-2.55 5-5.68 5a5.7 5.7 0 0 1-1.89-.29l-.75-.26l-.56.56a14 14 0 0 1-2 1.55a.13.13 0 0 1-.07 0v-.06a6.58 6.58 0 0 0 .15-4.29a5.25 5.25 0 0 1-.55-2.16c0-2.77 2.55-5 5.68-5M8 .94c-3.83 0-6.93 2.81-6.93 6.27a6.4 6.4 0 0 0 .64 2.64a5.53 5.53 0 0 1-.18 3.48a1.32 1.32 0 0 0 2 1.5a15 15 0 0 0 2.16-1.71a6.8 6.8 0 0 0 2.31.36c3.83 0 6.93-2.81 6.93-6.27S11.83.94 8 .94"/><ellipse cx="5.2" cy="7.7" fill="currentColor" rx=".8" ry=".75"/><ellipse cx="8" cy="7.7" fill="currentColor" rx=".8" ry=".75"/><ellipse cx="10.8" cy="7.7" fill="currentColor" rx=".8" ry=".75"/></svg>`,
|
|
769
|
+
},
|
|
770
|
+
isActive({ path }) {
|
|
771
|
+
return path === '/' || path.startsWith('/c/')
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
})
|
|
775
|
+
|
|
776
|
+
const title = 'Chat'
|
|
777
|
+
ctx.setState({
|
|
778
|
+
title
|
|
779
|
+
})
|
|
780
|
+
|
|
781
|
+
const meta = { title }
|
|
782
|
+
ctx.routes.push(...[
|
|
783
|
+
{ path: '/', component: Home, meta },
|
|
784
|
+
{ path: '/c/:id', component: ChatBody, meta },
|
|
785
|
+
])
|
|
786
|
+
|
|
787
|
+
const prefs = ctx.getPrefs()
|
|
788
|
+
if (prefs.model) {
|
|
789
|
+
ctx.state.selectedModel = prefs.model
|
|
790
|
+
}
|
|
791
|
+
ctx.state.selectedAspectRatio = prefs.aspectRatio || '1:1'
|
|
792
|
+
}
|
|
793
|
+
}
|