llms-py 3.0.0b5__py3-none-any.whl → 3.0.0b7__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/{ui/modules/analytics.mjs → extensions/analytics/ui/index.mjs} +4 -2
- llms/extensions/core_tools/__init__.py +358 -0
- llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/gallery/__init__.py +61 -0
- llms/extensions/gallery/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
- llms/extensions/gallery/db.py +298 -0
- llms/extensions/gallery/ui/index.mjs +480 -0
- llms/extensions/providers/__init__.py +18 -0
- llms/extensions/providers/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/{providers → extensions/providers}/__pycache__/anthropic.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/chutes.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/google.cpython-314.pyc +0 -0
- llms/{providers → extensions/providers}/__pycache__/nvidia.cpython-314.pyc +0 -0
- llms/{providers → extensions/providers}/__pycache__/openai.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
- llms/{providers → extensions/providers}/anthropic.py +1 -4
- llms/{providers → extensions/providers}/chutes.py +21 -18
- llms/{providers → extensions/providers}/google.py +99 -27
- llms/{providers → extensions/providers}/nvidia.py +6 -8
- llms/{providers → extensions/providers}/openai.py +3 -6
- llms/{providers → extensions/providers}/openrouter.py +12 -10
- llms/extensions/system_prompts/__init__.py +45 -0
- llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/system_prompts/ui/index.mjs +284 -0
- llms/extensions/system_prompts/ui/prompts.json +1067 -0
- llms/{ui/modules/tools.mjs → extensions/tools/ui/index.mjs} +4 -2
- llms/llms.json +17 -1
- llms/main.py +407 -175
- llms/providers-extra.json +0 -32
- llms/ui/App.mjs +17 -18
- llms/ui/ai.mjs +10 -3
- llms/ui/app.css +1553 -24
- llms/ui/ctx.mjs +70 -12
- llms/ui/index.mjs +13 -8
- llms/ui/modules/chat/ChatBody.mjs +11 -248
- llms/ui/modules/chat/HomeTools.mjs +254 -0
- llms/ui/modules/chat/SettingsDialog.mjs +1 -1
- llms/ui/modules/chat/index.mjs +278 -174
- llms/ui/modules/layout.mjs +2 -26
- llms/ui/modules/model-selector.mjs +1 -1
- llms/ui/modules/threads/index.mjs +5 -11
- llms/ui/modules/threads/threadStore.mjs +56 -2
- llms/ui/utils.mjs +21 -3
- {llms_py-3.0.0b5.dist-info → llms_py-3.0.0b7.dist-info}/METADATA +1 -1
- llms_py-3.0.0b7.dist-info/RECORD +80 -0
- llms/providers/__pycache__/chutes.cpython-314.pyc +0 -0
- llms/providers/__pycache__/google.cpython-314.pyc +0 -0
- llms/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
- llms_py-3.0.0b5.dist-info/RECORD +0 -66
- {llms_py-3.0.0b5.dist-info → llms_py-3.0.0b7.dist-info}/WHEEL +0 -0
- {llms_py-3.0.0b5.dist-info → llms_py-3.0.0b7.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.0b5.dist-info → llms_py-3.0.0b7.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.0b5.dist-info → llms_py-3.0.0b7.dist-info}/top_level.txt +0 -0
llms/ui/ctx.mjs
CHANGED
|
@@ -19,6 +19,9 @@ export class ExtensionScope {
|
|
|
19
19
|
setPrefs(o) {
|
|
20
20
|
storageObject(this.storageKey, Object.assign(this.prefs, o))
|
|
21
21
|
}
|
|
22
|
+
savePrefs() {
|
|
23
|
+
storageObject(this.storageKey, this.prefs)
|
|
24
|
+
}
|
|
22
25
|
get(url, options) {
|
|
23
26
|
return this.ctx.ai.get(combinePaths(this.baseUrl, url), options)
|
|
24
27
|
}
|
|
@@ -28,6 +31,9 @@ export class ExtensionScope {
|
|
|
28
31
|
post(url, options) {
|
|
29
32
|
return this.ctx.ai.post(combinePaths(this.baseUrl, url), options)
|
|
30
33
|
}
|
|
34
|
+
async postForm(url, options) {
|
|
35
|
+
return await this.ctx.ai.postForm(combinePaths(this.baseUrl, url), options)
|
|
36
|
+
}
|
|
31
37
|
async postJson(url, options) {
|
|
32
38
|
return this.ctx.ai.postJson(combinePaths(this.baseUrl, url), options)
|
|
33
39
|
}
|
|
@@ -55,6 +61,8 @@ export class AppContext {
|
|
|
55
61
|
this.left = {}
|
|
56
62
|
this.layout = reactive(storageObject(`llms.layout`))
|
|
57
63
|
this.prefs = reactive(storageObject(ai.prefsKey))
|
|
64
|
+
this._onRouterBeforeEach = []
|
|
65
|
+
this._onClass = []
|
|
58
66
|
|
|
59
67
|
if (!Array.isArray(this.layout.hide)) {
|
|
60
68
|
this.layout.hide = []
|
|
@@ -182,24 +190,74 @@ export class AppContext {
|
|
|
182
190
|
layoutVisible(key) {
|
|
183
191
|
return !this.layout.hide.includes(key)
|
|
184
192
|
}
|
|
185
|
-
toggleTop(name) {
|
|
186
|
-
|
|
187
|
-
|
|
193
|
+
toggleTop(name, toggle) {
|
|
194
|
+
if (toggle === false) {
|
|
195
|
+
this.layout.top = undefined
|
|
196
|
+
} else if (toggle === true) {
|
|
197
|
+
this.layout.top = name
|
|
198
|
+
} else {
|
|
199
|
+
this.layout.top = this.layout.top == name ? undefined : name
|
|
200
|
+
}
|
|
188
201
|
storageObject(`llms.layout`, this.layout)
|
|
202
|
+
console.log('toggleTop', name, toggle, this.layout.top, this.layout.top === name)
|
|
203
|
+
return this.layout.top === name
|
|
189
204
|
}
|
|
190
|
-
togglePath(path) {
|
|
205
|
+
togglePath(path, toggle) {
|
|
191
206
|
const currentPath = this.router.currentRoute.value?.path
|
|
192
|
-
console.log('togglePath', path, currentPath)
|
|
193
|
-
if (currentPath
|
|
194
|
-
|
|
195
|
-
|
|
207
|
+
console.log('togglePath', path, currentPath, toggle)
|
|
208
|
+
if (currentPath != path) {
|
|
209
|
+
if (toggle === undefined) {
|
|
210
|
+
toggle = true
|
|
211
|
+
}
|
|
196
212
|
this.router.push({ path })
|
|
197
213
|
}
|
|
214
|
+
this.toggleLayout('left', toggle)
|
|
215
|
+
return toggle
|
|
198
216
|
}
|
|
199
|
-
getJson(url, options) {
|
|
200
|
-
return this.ai.getJson(url, options)
|
|
217
|
+
async getJson(url, options) {
|
|
218
|
+
return await this.ai.getJson(url, options)
|
|
219
|
+
}
|
|
220
|
+
async post(url, options) {
|
|
221
|
+
return await this.ai.post(url, options)
|
|
222
|
+
}
|
|
223
|
+
async postForm(url, options) {
|
|
224
|
+
return await this.ai.postForm(url, options)
|
|
225
|
+
}
|
|
226
|
+
async postJson(url, options) {
|
|
227
|
+
return await this.ai.postJson(url, options)
|
|
201
228
|
}
|
|
202
|
-
|
|
203
|
-
|
|
229
|
+
to(route) {
|
|
230
|
+
if (typeof route == 'string') {
|
|
231
|
+
route = route.startsWith(this.ai.base)
|
|
232
|
+
? route
|
|
233
|
+
: combinePaths(this.ai.base, route)
|
|
234
|
+
const path = { path: route }
|
|
235
|
+
console.log('to', path)
|
|
236
|
+
this.router.push(path)
|
|
237
|
+
} else {
|
|
238
|
+
route.path = route.path.startsWith(this.ai.base)
|
|
239
|
+
? route.path
|
|
240
|
+
: combinePaths(this.ai.base, route.path)
|
|
241
|
+
console.log('to', route)
|
|
242
|
+
this.router.push(route)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Events
|
|
247
|
+
onRouterBeforeEach(callback) {
|
|
248
|
+
this._onRouterBeforeEach.push(callback)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
onClass(callback) {
|
|
252
|
+
this._onClass.push(callback)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
cls(id, cls) {
|
|
256
|
+
if (this._onClass.length) {
|
|
257
|
+
this._onClass.forEach(callback => {
|
|
258
|
+
cls = callback(id, cls) ?? cls
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
return cls
|
|
204
262
|
}
|
|
205
263
|
}
|
llms/ui/index.mjs
CHANGED
|
@@ -8,8 +8,6 @@ import LayoutModule from './modules/layout.mjs'
|
|
|
8
8
|
import ChatModule from './modules/chat/index.mjs'
|
|
9
9
|
import ThreadsModule from './modules/threads/index.mjs'
|
|
10
10
|
import ModelSelectorModule from './modules/model-selector.mjs'
|
|
11
|
-
import AnalyticsModule from './modules/analytics.mjs'
|
|
12
|
-
import ToolsModule from './modules/tools.mjs'
|
|
13
11
|
import { utilsFunctions, utilsFormatters } from './utils.mjs'
|
|
14
12
|
import { markdownFormatters } from './markdown.mjs'
|
|
15
13
|
import { AppContext } from './ctx.mjs'
|
|
@@ -22,8 +20,6 @@ const BuiltInModules = {
|
|
|
22
20
|
ChatModule,
|
|
23
21
|
ThreadsModule,
|
|
24
22
|
ModelSelectorModule,
|
|
25
|
-
AnalyticsModule,
|
|
26
|
-
ToolsModule,
|
|
27
23
|
}
|
|
28
24
|
|
|
29
25
|
|
|
@@ -48,13 +44,17 @@ export async function createContext() {
|
|
|
48
44
|
ctx.modules = await Promise.all(validExtensions.map(async extension => {
|
|
49
45
|
try {
|
|
50
46
|
const module = await import(extension.path)
|
|
51
|
-
|
|
47
|
+
const order = module.default.order || 0
|
|
48
|
+
return { extension, module, order }
|
|
52
49
|
} catch (e) {
|
|
53
50
|
console.error(`Failed to load extension module ${extension.name}:`, e)
|
|
54
51
|
return null
|
|
55
52
|
}
|
|
56
53
|
}))
|
|
57
54
|
|
|
55
|
+
// sort modules by order
|
|
56
|
+
ctx.modules.sort((a, b) => a.order - b.order)
|
|
57
|
+
|
|
58
58
|
const installedModules = []
|
|
59
59
|
|
|
60
60
|
// Install built-in modules sequentially
|
|
@@ -103,10 +103,15 @@ export async function createContext() {
|
|
|
103
103
|
document.title = title
|
|
104
104
|
return true
|
|
105
105
|
})
|
|
106
|
+
ctx._onRouterBeforeEach.forEach(ctx.router.beforeEach)
|
|
106
107
|
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
if (ai.hasAccess) {
|
|
109
|
+
if (ctx.layout.path && location.pathname === '/' && !location.search) {
|
|
110
|
+
console.log('redirecting to saved path: ', ctx.layout.path)
|
|
111
|
+
ctx.router.push({ path: ctx.layout.path })
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
ctx.router.push({ path: '/' })
|
|
110
115
|
}
|
|
111
116
|
|
|
112
117
|
const loadModules = installedModules.filter(x => x.module.default && x.module.default.load)
|
|
@@ -14,58 +14,7 @@ export default {
|
|
|
14
14
|
<!-- Welcome message when no thread is selected -->
|
|
15
15
|
<div v-else-if="!currentThread" class="text-center py-12">
|
|
16
16
|
<Welcome />
|
|
17
|
-
|
|
18
|
-
<!-- Chat input for new conversation -->
|
|
19
|
-
<!-- Moved to bottom input area -->
|
|
20
|
-
<div class="h-2"></div>
|
|
21
|
-
|
|
22
|
-
<!-- Export/Import buttons -->
|
|
23
|
-
<div class="mt-2 flex space-x-3 justify-center items-center">
|
|
24
|
-
<button type="button"
|
|
25
|
-
@click="(e) => e.altKey ? exportRequests() : exportThreads()"
|
|
26
|
-
:disabled="isExporting"
|
|
27
|
-
:title="'Export ' + threads?.threads?.value?.length + ' conversations'"
|
|
28
|
-
class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
29
|
-
>
|
|
30
|
-
<svg v-if="!isExporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
31
|
-
<path fill="currentColor" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z"></path>
|
|
32
|
-
</svg>
|
|
33
|
-
<svg v-else class="size-5 mr-1 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
34
|
-
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
35
|
-
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
36
|
-
</svg>
|
|
37
|
-
{{ isExporting ? 'Exporting...' : 'Export' }}
|
|
38
|
-
</button>
|
|
39
|
-
|
|
40
|
-
<button type="button"
|
|
41
|
-
@click="triggerImport"
|
|
42
|
-
:disabled="isImporting"
|
|
43
|
-
title="Import conversations from JSON file"
|
|
44
|
-
class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
45
|
-
>
|
|
46
|
-
<svg v-if="!isImporting" class="size-5 mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
47
|
-
<path fill="currentColor" d="m14 12l-4-4v3H2v2h8v3m10 2V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v3h2V6h12v12H6v-3H4v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2"/>
|
|
48
|
-
</svg>
|
|
49
|
-
<svg v-else class="size-5 mr-1 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
50
|
-
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
51
|
-
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
52
|
-
</svg>
|
|
53
|
-
{{ isImporting ? 'Importing...' : 'Import' }}
|
|
54
|
-
</button>
|
|
55
|
-
|
|
56
|
-
<!-- Hidden file input for import -->
|
|
57
|
-
<input
|
|
58
|
-
ref="fileInput"
|
|
59
|
-
type="file"
|
|
60
|
-
accept=".json"
|
|
61
|
-
@change="handleFileImport"
|
|
62
|
-
class="hidden"
|
|
63
|
-
/>
|
|
64
|
-
|
|
65
|
-
<DarkModeToggle />
|
|
66
|
-
|
|
67
|
-
</div>
|
|
68
|
-
|
|
17
|
+
<HomeTools />
|
|
69
18
|
</div>
|
|
70
19
|
|
|
71
20
|
<!-- Messages -->
|
|
@@ -228,6 +177,15 @@ export default {
|
|
|
228
177
|
</template>
|
|
229
178
|
</div>
|
|
230
179
|
|
|
180
|
+
<!-- Assistant Audios -->
|
|
181
|
+
<div v-if="message.audios && message.audios.length > 0" class="mt-2 flex flex-wrap gap-2">
|
|
182
|
+
<template v-for="(audio, i) in message.audios" :key="i">
|
|
183
|
+
<div v-if="audio.type === 'audio_url'" 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">
|
|
184
|
+
<audio controls :src="resolveUrl(audio.audio_url.url)" class="h-8 w-64"></audio>
|
|
185
|
+
</div>
|
|
186
|
+
</template>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
231
189
|
<!-- User Message with separate attachments -->
|
|
232
190
|
<div v-else-if="message.role !== 'assistant' && message.role !== 'tool'">
|
|
233
191
|
<div v-html="$fmt.markdown(message.content)" class="prose prose-sm max-w-none dark:prose-invert break-words"></div>
|
|
@@ -355,7 +313,7 @@ export default {
|
|
|
355
313
|
</div>
|
|
356
314
|
|
|
357
315
|
<!-- Input Area -->
|
|
358
|
-
<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">
|
|
316
|
+
<div v-if="$ai.hasAccess" :class="$ctx.cls('chat-input', 'flex-shrink-0 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-6 py-4')">
|
|
359
317
|
<ChatPrompt :model="$chat.getSelectedModel()" />
|
|
360
318
|
</div>
|
|
361
319
|
|
|
@@ -395,9 +353,6 @@ export default {
|
|
|
395
353
|
return models.find(m => m.name === selectedModel.value) || models.find(m => m.id === selectedModel.value)
|
|
396
354
|
})
|
|
397
355
|
const messagesContainer = ref(null)
|
|
398
|
-
const isExporting = ref(false)
|
|
399
|
-
const isImporting = ref(false)
|
|
400
|
-
const fileInput = ref(null)
|
|
401
356
|
const copying = ref(null)
|
|
402
357
|
const lightboxUrl = ref(null)
|
|
403
358
|
|
|
@@ -446,191 +401,6 @@ export default {
|
|
|
446
401
|
model: selectedModel.value,
|
|
447
402
|
})
|
|
448
403
|
})
|
|
449
|
-
|
|
450
|
-
async function exportThreads() {
|
|
451
|
-
if (isExporting.value) return
|
|
452
|
-
|
|
453
|
-
isExporting.value = true
|
|
454
|
-
try {
|
|
455
|
-
// Load all threads from IndexedDB
|
|
456
|
-
await threads.loadThreads()
|
|
457
|
-
const allThreads = threads.threads.value
|
|
458
|
-
|
|
459
|
-
// Create export data with metadata
|
|
460
|
-
const exportData = {
|
|
461
|
-
exportedAt: new Date().toISOString(),
|
|
462
|
-
version: '1.0',
|
|
463
|
-
source: 'llmspy',
|
|
464
|
-
threadCount: allThreads.length,
|
|
465
|
-
threads: allThreads
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Create and download JSON file
|
|
469
|
-
const jsonString = JSON.stringify(exportData, null, 2)
|
|
470
|
-
const blob = new Blob([jsonString], { type: 'application/json' })
|
|
471
|
-
const url = URL.createObjectURL(blob)
|
|
472
|
-
|
|
473
|
-
const link = document.createElement('a')
|
|
474
|
-
link.href = url
|
|
475
|
-
link.download = `llmsthreads-export-${new Date().toISOString().split('T')[0]}.json`
|
|
476
|
-
document.body.appendChild(link)
|
|
477
|
-
link.click()
|
|
478
|
-
document.body.removeChild(link)
|
|
479
|
-
URL.revokeObjectURL(url)
|
|
480
|
-
|
|
481
|
-
} catch (error) {
|
|
482
|
-
console.error('Failed to export threads:', error)
|
|
483
|
-
alert('Failed to export threads: ' + error.message)
|
|
484
|
-
} finally {
|
|
485
|
-
isExporting.value = false
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
async function exportRequests() {
|
|
490
|
-
if (isExporting.value) return
|
|
491
|
-
|
|
492
|
-
isExporting.value = true
|
|
493
|
-
try {
|
|
494
|
-
// Load all threads from IndexedDB
|
|
495
|
-
const allRequests = await threads.getAllRequests()
|
|
496
|
-
|
|
497
|
-
// Create export data with metadata
|
|
498
|
-
const exportData = {
|
|
499
|
-
exportedAt: new Date().toISOString(),
|
|
500
|
-
version: '1.0',
|
|
501
|
-
source: 'llmspy',
|
|
502
|
-
requestsCount: allRequests.length,
|
|
503
|
-
requests: allRequests
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Create and download JSON file
|
|
507
|
-
const jsonString = JSON.stringify(exportData, null, 2)
|
|
508
|
-
const blob = new Blob([jsonString], { type: 'application/json' })
|
|
509
|
-
const url = URL.createObjectURL(blob)
|
|
510
|
-
|
|
511
|
-
const link = document.createElement('a')
|
|
512
|
-
link.href = url
|
|
513
|
-
link.download = `llmsrequests-export-${new Date().toISOString().split('T')[0]}.json`
|
|
514
|
-
document.body.appendChild(link)
|
|
515
|
-
link.click()
|
|
516
|
-
document.body.removeChild(link)
|
|
517
|
-
URL.revokeObjectURL(url)
|
|
518
|
-
|
|
519
|
-
} catch (error) {
|
|
520
|
-
console.error('Failed to export requests:', error)
|
|
521
|
-
alert('Failed to export requests: ' + error.message)
|
|
522
|
-
} finally {
|
|
523
|
-
isExporting.value = false
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function triggerImport() {
|
|
528
|
-
if (isImporting.value) return
|
|
529
|
-
fileInput.value?.click()
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
async function handleFileImport(event) {
|
|
533
|
-
const file = event.target.files?.[0]
|
|
534
|
-
if (!file) return
|
|
535
|
-
|
|
536
|
-
isImporting.value = true
|
|
537
|
-
var importType = 'threads'
|
|
538
|
-
try {
|
|
539
|
-
const text = await file.text()
|
|
540
|
-
const importData = JSON.parse(text)
|
|
541
|
-
importType = importData.threads
|
|
542
|
-
? 'threads'
|
|
543
|
-
: importData.requests
|
|
544
|
-
? 'requests'
|
|
545
|
-
: 'unknown'
|
|
546
|
-
|
|
547
|
-
// Import threads one by one
|
|
548
|
-
let importedCount = 0
|
|
549
|
-
let existingCount = 0
|
|
550
|
-
|
|
551
|
-
const db = await threads.initDB()
|
|
552
|
-
|
|
553
|
-
if (importData.threads) {
|
|
554
|
-
if (!Array.isArray(importData.threads)) {
|
|
555
|
-
throw new Error('Invalid import file: missing or invalid threads array')
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const threadIds = new Set(await threads.getAllThreadIds())
|
|
559
|
-
|
|
560
|
-
for (const threadData of importData.threads) {
|
|
561
|
-
if (!threadData.id) {
|
|
562
|
-
console.warn('Skipping thread without ID:', threadData)
|
|
563
|
-
continue
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
try {
|
|
567
|
-
// Check if thread already exists
|
|
568
|
-
const existingThread = threadIds.has(threadData.id)
|
|
569
|
-
if (existingThread) {
|
|
570
|
-
existingCount++
|
|
571
|
-
} else {
|
|
572
|
-
// Add new thread directly to IndexedDB
|
|
573
|
-
const tx = db.transaction(['threads'], 'readwrite')
|
|
574
|
-
await tx.objectStore('threads').add(threadData)
|
|
575
|
-
await tx.complete
|
|
576
|
-
importedCount++
|
|
577
|
-
}
|
|
578
|
-
} catch (error) {
|
|
579
|
-
console.error('Failed to import thread:', threadData.id, error)
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// Reload threads to reflect changes
|
|
584
|
-
await threads.loadThreads()
|
|
585
|
-
|
|
586
|
-
alert(`Import completed!\nNew threads: ${importedCount}\nExisting threads: ${existingCount}`)
|
|
587
|
-
}
|
|
588
|
-
if (importData.requests) {
|
|
589
|
-
if (!Array.isArray(importData.requests)) {
|
|
590
|
-
throw new Error('Invalid import file: missing or invalid requests array')
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
const requestIds = new Set(await threads.getAllRequestIds())
|
|
594
|
-
|
|
595
|
-
for (const requestData of importData.requests) {
|
|
596
|
-
if (!requestData.id) {
|
|
597
|
-
console.warn('Skipping request without ID:', requestData)
|
|
598
|
-
continue
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
try {
|
|
602
|
-
// Check if request already exists
|
|
603
|
-
const existingRequest = requestIds.has(requestData.id)
|
|
604
|
-
if (existingRequest) {
|
|
605
|
-
existingCount++
|
|
606
|
-
} else {
|
|
607
|
-
// Add new request directly to IndexedDB
|
|
608
|
-
const db = await threads.initDB()
|
|
609
|
-
const tx = db.transaction(['requests'], 'readwrite')
|
|
610
|
-
await tx.objectStore('requests').add(requestData)
|
|
611
|
-
await tx.complete
|
|
612
|
-
importedCount++
|
|
613
|
-
}
|
|
614
|
-
} catch (error) {
|
|
615
|
-
console.error('Failed to import request:', requestData.id, error)
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
alert(`Import completed!\nNew requests: ${importedCount}\nExisting requests: ${existingCount}`)
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
} catch (error) {
|
|
623
|
-
console.error('Failed to import ' + importType + ':', error)
|
|
624
|
-
alert('Failed to import ' + importType + ': ' + error.message)
|
|
625
|
-
} finally {
|
|
626
|
-
isImporting.value = false
|
|
627
|
-
// Clear the file input
|
|
628
|
-
if (fileInput.value) {
|
|
629
|
-
fileInput.value.value = ''
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
404
|
function configUpdated() {
|
|
635
405
|
console.log('configUpdated', selectedModel.value, models.length, models.includes(selectedModel.value))
|
|
636
406
|
if (selectedModel.value && !models.includes(selectedModel.value)) {
|
|
@@ -884,13 +654,6 @@ export default {
|
|
|
884
654
|
editMessage,
|
|
885
655
|
cancelRequest,
|
|
886
656
|
configUpdated,
|
|
887
|
-
exportThreads,
|
|
888
|
-
exportRequests,
|
|
889
|
-
isExporting,
|
|
890
|
-
triggerImport,
|
|
891
|
-
handleFileImport,
|
|
892
|
-
isImporting,
|
|
893
|
-
fileInput,
|
|
894
657
|
tokensTitle,
|
|
895
658
|
getAttachments,
|
|
896
659
|
hasAttachments,
|