llms-py 3.0.0b1__py3-none-any.whl → 3.0.0b2__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 +25 -56
- llms/llms.json +2 -2
- llms/main.py +452 -93
- llms/providers.json +1 -1
- llms/ui/App.mjs +25 -4
- llms/ui/Avatar.mjs +3 -2
- llms/ui/ChatPrompt.mjs +43 -52
- llms/ui/Main.mjs +87 -98
- llms/ui/OAuthSignIn.mjs +2 -33
- llms/ui/ProviderStatus.mjs +7 -8
- llms/ui/Recents.mjs +10 -9
- llms/ui/Sidebar.mjs +2 -1
- llms/ui/SignIn.mjs +7 -6
- llms/ui/ai.mjs +9 -41
- llms/ui/app.css +137 -138
- llms/ui/index.mjs +213 -0
- llms/ui/{ModelSelector.mjs → model-selector.mjs} +193 -200
- llms/ui/tailwind.input.css +441 -79
- llms/ui/threadStore.mjs +17 -6
- llms/ui/utils.mjs +1 -0
- {llms_py-3.0.0b1.dist-info → llms_py-3.0.0b2.dist-info}/METADATA +1 -1
- llms_py-3.0.0b2.dist-info/RECORD +58 -0
- llms/ui/SystemPromptEditor.mjs +0 -31
- llms/ui/SystemPromptSelector.mjs +0 -56
- llms_py-3.0.0b1.dist-info/RECORD +0 -49
- {llms_py-3.0.0b1.dist-info → llms_py-3.0.0b2.dist-info}/WHEEL +0 -0
- {llms_py-3.0.0b1.dist-info → llms_py-3.0.0b2.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.0b1.dist-info → llms_py-3.0.0b2.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.0b1.dist-info → llms_py-3.0.0b2.dist-info}/top_level.txt +0 -0
llms/ui/App.mjs
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import { inject, ref, onMounted, onUnmounted } from "vue"
|
|
1
|
+
import { inject, ref, watch, onMounted, onUnmounted } from "vue"
|
|
2
|
+
import { useRouter, useRoute } from "vue-router"
|
|
2
3
|
import Sidebar from "./Sidebar.mjs"
|
|
3
4
|
|
|
4
5
|
export default {
|
|
5
6
|
components: {
|
|
6
7
|
Sidebar,
|
|
7
8
|
},
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
props: ['config', 'models'],
|
|
10
|
+
setup(props) {
|
|
11
|
+
const router = useRouter()
|
|
12
|
+
const route = useRoute()
|
|
13
|
+
|
|
14
|
+
const ctx = inject('ctx')
|
|
15
|
+
const ai = ctx.ai
|
|
10
16
|
const isMobile = ref(false)
|
|
17
|
+
const modal = ref()
|
|
11
18
|
|
|
12
19
|
const checkMobile = () => {
|
|
13
20
|
const wasMobile = isMobile.value
|
|
@@ -36,13 +43,25 @@ export default {
|
|
|
36
43
|
onMounted(() => {
|
|
37
44
|
checkMobile()
|
|
38
45
|
window.addEventListener('resize', checkMobile)
|
|
46
|
+
if (route.query.open) {
|
|
47
|
+
modal.value = ctx.openModal(route.query.open)
|
|
48
|
+
}
|
|
39
49
|
})
|
|
40
50
|
|
|
41
51
|
onUnmounted(() => {
|
|
42
52
|
window.removeEventListener('resize', checkMobile)
|
|
43
53
|
})
|
|
44
54
|
|
|
45
|
-
|
|
55
|
+
function closeModal() {
|
|
56
|
+
ctx.closeModal(route.query.open)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
watch(() => route.query.open, (newVal) => {
|
|
60
|
+
modal.value = ctx.modalComponents[newVal]
|
|
61
|
+
console.log('open', newVal)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
return { ai, modal, isMobile, toggleSidebar, closeSidebar, closeModal }
|
|
46
65
|
},
|
|
47
66
|
template: `
|
|
48
67
|
<div class="flex h-screen bg-white dark:bg-gray-900">
|
|
@@ -92,6 +111,8 @@ export default {
|
|
|
92
111
|
|
|
93
112
|
<RouterView />
|
|
94
113
|
</div>
|
|
114
|
+
|
|
115
|
+
<component v-if="modal" :is="modal" @done="closeModal" />
|
|
95
116
|
</div>
|
|
96
117
|
`,
|
|
97
118
|
}
|
llms/ui/Avatar.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { computed, inject, ref, onMounted, onUnmounted } from "vue"
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
|
-
template
|
|
4
|
+
template: `
|
|
5
5
|
<div v-if="$ai.auth?.profileUrl" class="relative" ref="avatarContainer">
|
|
6
6
|
<img
|
|
7
7
|
@click.stop="toggleMenu"
|
|
@@ -31,7 +31,8 @@ export default {
|
|
|
31
31
|
</div>
|
|
32
32
|
`,
|
|
33
33
|
setup() {
|
|
34
|
-
const
|
|
34
|
+
const ctx = inject('ctx')
|
|
35
|
+
const ai = ctx.ai
|
|
35
36
|
const showMenu = ref(false)
|
|
36
37
|
const avatarContainer = ref(null)
|
|
37
38
|
|
llms/ui/ChatPrompt.mjs
CHANGED
|
@@ -146,17 +146,14 @@ export default {
|
|
|
146
146
|
model: {
|
|
147
147
|
type: Object,
|
|
148
148
|
default: null
|
|
149
|
-
},
|
|
150
|
-
systemPrompt: {
|
|
151
|
-
type: String,
|
|
152
|
-
default: ''
|
|
153
149
|
}
|
|
154
150
|
},
|
|
155
151
|
setup(props) {
|
|
156
|
-
const
|
|
152
|
+
const ctx = inject('ctx')
|
|
153
|
+
const config = ctx.state.config
|
|
154
|
+
const ai = ctx.ai
|
|
157
155
|
const chatSettings = inject('chatSettings')
|
|
158
156
|
const router = useRouter()
|
|
159
|
-
const config = inject('config')
|
|
160
157
|
const chatPrompt = inject('chatPrompt')
|
|
161
158
|
const {
|
|
162
159
|
messageText,
|
|
@@ -325,20 +322,6 @@ export default {
|
|
|
325
322
|
}
|
|
326
323
|
}
|
|
327
324
|
|
|
328
|
-
function createChatRequest() {
|
|
329
|
-
if (hasImage()) {
|
|
330
|
-
return deepClone(config.defaults.image)
|
|
331
|
-
}
|
|
332
|
-
if (hasAudio()) {
|
|
333
|
-
return deepClone(config.defaults.audio)
|
|
334
|
-
}
|
|
335
|
-
if (attachedFiles.value.length) {
|
|
336
|
-
return deepClone(config.defaults.file)
|
|
337
|
-
}
|
|
338
|
-
const text = deepClone(config.defaults.text)
|
|
339
|
-
return text
|
|
340
|
-
}
|
|
341
|
-
|
|
342
325
|
function getTextContent(chat) {
|
|
343
326
|
const textMessage = chat.messages.find(m =>
|
|
344
327
|
m.role === 'user' && Array.isArray(m.content) && m.content.some(c => c.type === 'text'))
|
|
@@ -383,17 +366,20 @@ export default {
|
|
|
383
366
|
|
|
384
367
|
// Create thread if none exists
|
|
385
368
|
if (!currentThread.value) {
|
|
386
|
-
const newThread = await threads.createThread(
|
|
369
|
+
const newThread = await threads.createThread({
|
|
370
|
+
title: 'New Chat',
|
|
371
|
+
model: props.model.id,
|
|
372
|
+
info: toModelInfo(props.model),
|
|
373
|
+
})
|
|
387
374
|
threadId = newThread.id
|
|
388
375
|
// Navigate to the new thread URL
|
|
389
376
|
router.push(`${ai.base}/c/${newThread.id}`)
|
|
390
377
|
} else {
|
|
391
378
|
threadId = currentThread.value.id
|
|
392
|
-
// Update the existing thread's model
|
|
379
|
+
// Update the existing thread's model to match current selection
|
|
393
380
|
await threads.updateThread(threadId, {
|
|
394
381
|
model: props.model.name,
|
|
395
382
|
info: toModelInfo(props.model),
|
|
396
|
-
systemPrompt: props.systemPrompt
|
|
397
383
|
})
|
|
398
384
|
}
|
|
399
385
|
|
|
@@ -448,51 +434,49 @@ export default {
|
|
|
448
434
|
isGenerating.value = true
|
|
449
435
|
|
|
450
436
|
// Construct API Request from History
|
|
451
|
-
const
|
|
437
|
+
const request = {
|
|
452
438
|
model: props.model.name,
|
|
453
439
|
messages: [],
|
|
454
440
|
metadata: {}
|
|
455
441
|
}
|
|
456
442
|
|
|
457
|
-
// Add system prompt if present
|
|
458
|
-
if (props.systemPrompt?.trim()) {
|
|
459
|
-
chatRequest.messages.push({
|
|
460
|
-
role: 'system',
|
|
461
|
-
content: props.systemPrompt // assuming system prompt is just string
|
|
462
|
-
})
|
|
463
|
-
}
|
|
464
|
-
|
|
465
443
|
// Add History
|
|
466
444
|
thread.messages.forEach(m => {
|
|
467
|
-
|
|
445
|
+
request.messages.push({
|
|
468
446
|
role: m.role,
|
|
469
447
|
content: m.content
|
|
470
448
|
})
|
|
471
449
|
})
|
|
472
450
|
|
|
473
451
|
// Apply user settings
|
|
474
|
-
applySettings(
|
|
475
|
-
|
|
452
|
+
applySettings(request)
|
|
453
|
+
request.metadata.threadId = threadId
|
|
476
454
|
|
|
477
|
-
|
|
455
|
+
const ctxRequest = {
|
|
456
|
+
request,
|
|
457
|
+
thread,
|
|
458
|
+
}
|
|
459
|
+
ctx.chatRequestFilters.forEach(f => f(ctxRequest))
|
|
460
|
+
|
|
461
|
+
console.debug('chatRequest', request)
|
|
478
462
|
|
|
479
463
|
// Send to API
|
|
480
464
|
const startTime = Date.now()
|
|
481
|
-
const
|
|
482
|
-
body: JSON.stringify(
|
|
465
|
+
const res = await ai.post('/v1/chat/completions', {
|
|
466
|
+
body: JSON.stringify(request),
|
|
483
467
|
signal: controller.signal
|
|
484
468
|
})
|
|
485
469
|
|
|
486
|
-
let
|
|
487
|
-
if (!
|
|
470
|
+
let response = null
|
|
471
|
+
if (!res.ok) {
|
|
488
472
|
errorStatus.value = {
|
|
489
|
-
errorCode: `HTTP ${
|
|
473
|
+
errorCode: `HTTP ${res.status} ${res.statusText}`,
|
|
490
474
|
message: null,
|
|
491
475
|
stackTrace: null
|
|
492
476
|
}
|
|
493
477
|
let errorBody = null
|
|
494
478
|
try {
|
|
495
|
-
errorBody = await
|
|
479
|
+
errorBody = await res.text()
|
|
496
480
|
if (errorBody) {
|
|
497
481
|
// Try to parse as JSON for better formatting
|
|
498
482
|
try {
|
|
@@ -513,8 +497,13 @@ export default {
|
|
|
513
497
|
}
|
|
514
498
|
} else {
|
|
515
499
|
try {
|
|
516
|
-
|
|
517
|
-
|
|
500
|
+
response = await res.json()
|
|
501
|
+
const ctxResponse = {
|
|
502
|
+
response,
|
|
503
|
+
thread,
|
|
504
|
+
}
|
|
505
|
+
ctx.chatResponseFilters.forEach(f => f(ctxResponse))
|
|
506
|
+
console.debug('chatResponse', JSON.stringify(response, null, 2))
|
|
518
507
|
} catch (e) {
|
|
519
508
|
errorStatus.value = {
|
|
520
509
|
errorCode: 'Error',
|
|
@@ -524,29 +513,29 @@ export default {
|
|
|
524
513
|
}
|
|
525
514
|
}
|
|
526
515
|
|
|
527
|
-
if (
|
|
516
|
+
if (response?.error) {
|
|
528
517
|
errorStatus.value ??= {
|
|
529
518
|
errorCode: 'Error',
|
|
530
519
|
}
|
|
531
|
-
errorStatus.value.message =
|
|
520
|
+
errorStatus.value.message = response.error
|
|
532
521
|
}
|
|
533
522
|
|
|
534
523
|
if (!errorStatus.value) {
|
|
535
524
|
// Add assistant response (save entire message including reasoning)
|
|
536
|
-
const assistantMessage =
|
|
525
|
+
const assistantMessage = response.choices?.[0]?.message
|
|
537
526
|
|
|
538
|
-
const usage =
|
|
527
|
+
const usage = response.usage
|
|
539
528
|
if (usage) {
|
|
540
|
-
if (
|
|
541
|
-
const [input, output] =
|
|
542
|
-
usage.duration =
|
|
529
|
+
if (response.metadata?.pricing) {
|
|
530
|
+
const [input, output] = response.metadata.pricing.split('/')
|
|
531
|
+
usage.duration = response.metadata.duration ?? (Date.now() - startTime)
|
|
543
532
|
usage.input = input
|
|
544
533
|
usage.output = output
|
|
545
534
|
usage.tokens = usage.completion_tokens
|
|
546
535
|
usage.price = usage.output
|
|
547
536
|
usage.cost = tokenCost(usage.prompt_tokens / 1_000_000 * parseFloat(input) + usage.completion_tokens / 1_000_000 * parseFloat(output))
|
|
548
537
|
}
|
|
549
|
-
await threads.logRequest(threadId, props.model,
|
|
538
|
+
await threads.logRequest(threadId, props.model, request, response)
|
|
550
539
|
}
|
|
551
540
|
await threads.addMessageToThread(threadId, assistantMessage, usage)
|
|
552
541
|
|
|
@@ -554,6 +543,8 @@ export default {
|
|
|
554
543
|
|
|
555
544
|
attachedFiles.value = []
|
|
556
545
|
// Error will be cleared when user sends next message (no auto-timeout)
|
|
546
|
+
} else {
|
|
547
|
+
ctx.chatErrorFilters.forEach(f => f(errorStatus.value))
|
|
557
548
|
}
|
|
558
549
|
} catch (error) {
|
|
559
550
|
// Check if the error is due to abort
|
llms/ui/Main.mjs
CHANGED
|
@@ -2,25 +2,63 @@ import { ref, computed, nextTick, watch, onMounted, provide, inject } from 'vue'
|
|
|
2
2
|
import { useRouter, useRoute } from 'vue-router'
|
|
3
3
|
import { useFormatters } from '@servicestack/vue'
|
|
4
4
|
import { useThreadStore } from './threadStore.mjs'
|
|
5
|
-
import {
|
|
5
|
+
import { addCopyButtons, formatCost, statsTitle, fetchCacheInfos } from './utils.mjs'
|
|
6
6
|
import { renderMarkdown } from './markdown.mjs'
|
|
7
7
|
import ChatPrompt, { useChatPrompt } from './ChatPrompt.mjs'
|
|
8
8
|
import SignIn from './SignIn.mjs'
|
|
9
9
|
import OAuthSignIn from './OAuthSignIn.mjs'
|
|
10
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
11
|
import { useSettings } from "./SettingsDialog.mjs"
|
|
15
12
|
import Welcome from './Welcome.mjs'
|
|
16
13
|
|
|
17
14
|
const { humanifyMs, humanifyNumber } = useFormatters()
|
|
18
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
|
+
|
|
19
58
|
export default {
|
|
20
59
|
components: {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
SystemPromptEditor,
|
|
60
|
+
TopBar,
|
|
61
|
+
TopPanel,
|
|
24
62
|
ChatPrompt,
|
|
25
63
|
SignIn,
|
|
26
64
|
OAuthSignIn,
|
|
@@ -29,28 +67,26 @@ export default {
|
|
|
29
67
|
},
|
|
30
68
|
template: `
|
|
31
69
|
<div class="flex flex-col h-full w-full">
|
|
32
|
-
<!-- Header with model
|
|
33
|
-
<div v-if="
|
|
70
|
+
<!-- Header with model selectors -->
|
|
71
|
+
<div v-if="$ai.hasAccess"
|
|
34
72
|
:class="!$ai.isSidebarOpen ? 'pl-6' : ''"
|
|
35
|
-
class="border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-2
|
|
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">
|
|
36
74
|
<div class="flex flex-wrap items-center justify-between w-full">
|
|
37
75
|
<ModelSelector :models="models" v-model="selectedModel" @updated="configUpdated" />
|
|
38
76
|
|
|
39
77
|
<div class="flex items-center space-x-2 pl-4">
|
|
40
|
-
<
|
|
41
|
-
:show="showSystemPrompt" @toggle="showSystemPrompt = !showSystemPrompt" />
|
|
78
|
+
<TopBar />
|
|
42
79
|
<Avatar />
|
|
43
80
|
</div>
|
|
44
81
|
</div>
|
|
45
82
|
</div>
|
|
46
83
|
|
|
47
|
-
<
|
|
48
|
-
v-model="currentSystemPrompt" :prompts="prompts" :selected="selectedPrompt" />
|
|
84
|
+
<TopPanel />
|
|
49
85
|
|
|
50
86
|
<!-- Messages Area -->
|
|
51
87
|
<div class="flex-1 overflow-y-auto" ref="messagesContainer">
|
|
52
88
|
<div class="mx-auto max-w-6xl px-4 py-6">
|
|
53
|
-
<div v-if="
|
|
89
|
+
<div v-if="!$ai.hasAccess">
|
|
54
90
|
<OAuthSignIn v-if="$ai.authType === 'oauth'" @done="$ai.signIn($event)" />
|
|
55
91
|
<SignIn v-else @done="$ai.signIn($event)" />
|
|
56
92
|
</div>
|
|
@@ -179,31 +215,32 @@ export default {
|
|
|
179
215
|
</div>
|
|
180
216
|
</div>
|
|
181
217
|
|
|
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
|
-
|
|
218
|
+
<!-- User Message with separate attachments -->
|
|
219
|
+
<div v-if="message.role !== 'assistant'">
|
|
220
|
+
<div v-html="renderMarkdown(message.content)" class="prose prose-sm max-w-none dark:prose-invert break-words"></div>
|
|
221
|
+
|
|
222
|
+
<!-- Attachments Grid -->
|
|
223
|
+
<div v-if="hasAttachments(message)" class="mt-2 flex flex-wrap gap-2">
|
|
224
|
+
<template v-for="(part, i) in getAttachments(message)" :key="i">
|
|
225
|
+
<!-- Image -->
|
|
226
|
+
<div v-if="part.type === 'image_url'" class="group relative cursor-pointer" @click="openLightbox(part.image_url.url)">
|
|
227
|
+
<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]" />
|
|
228
|
+
</div>
|
|
229
|
+
<!-- Audio -->
|
|
230
|
+
<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">
|
|
231
|
+
<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>
|
|
232
|
+
<audio controls :src="part.input_audio.data" class="h-8 w-48"></audio>
|
|
233
|
+
</div>
|
|
234
|
+
<!-- File -->
|
|
235
|
+
<a v-else-if="part.type === 'file'" :href="part.file.file_data" target="_blank"
|
|
236
|
+
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">
|
|
237
|
+
<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>
|
|
238
|
+
<span class="max-w-xs truncate">{{ part.file.filename || 'Attachment' }}</span>
|
|
239
|
+
</a>
|
|
240
|
+
</template>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
207
244
|
<div class="mt-2 text-xs opacity-70">
|
|
208
245
|
<span>{{ formatTime(message.timestamp) }}</span>
|
|
209
246
|
<span v-if="message.usage" :title="tokensTitle(message.usage)">
|
|
@@ -304,8 +341,8 @@ export default {
|
|
|
304
341
|
</div>
|
|
305
342
|
|
|
306
343
|
<!-- 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="selectedModelObj"
|
|
344
|
+
<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="selectedModelObj" />
|
|
309
346
|
</div>
|
|
310
347
|
|
|
311
348
|
<!-- Lightbox -->
|
|
@@ -323,10 +360,10 @@ export default {
|
|
|
323
360
|
</div>
|
|
324
361
|
</div>
|
|
325
362
|
`,
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const
|
|
363
|
+
setup() {
|
|
364
|
+
const ctx = inject('ctx')
|
|
365
|
+
const models = ctx.state.models
|
|
366
|
+
const config = ctx.state.config
|
|
330
367
|
const router = useRouter()
|
|
331
368
|
const route = useRoute()
|
|
332
369
|
const threads = useThreadStore()
|
|
@@ -340,28 +377,14 @@ export default {
|
|
|
340
377
|
provide('threads', threads)
|
|
341
378
|
provide('chatPrompt', chatPrompt)
|
|
342
379
|
provide('chatSettings', chatSettings)
|
|
343
|
-
const models = inject('models')
|
|
344
|
-
const config = inject('config')
|
|
345
380
|
|
|
346
|
-
const prefs =
|
|
347
|
-
|
|
348
|
-
const customPromptValue = ref('')
|
|
349
|
-
const customPrompt = {
|
|
350
|
-
id: '_custom_',
|
|
351
|
-
name: 'Custom...',
|
|
352
|
-
value: ''
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const prompts = computed(() => [customPrompt, ...config.prompts])
|
|
381
|
+
const prefs = ctx.getPrefs()
|
|
356
382
|
|
|
357
383
|
const selectedModel = ref(prefs.model || config.defaults.text.model || '')
|
|
358
384
|
const selectedModelObj = computed(() => {
|
|
359
385
|
if (!selectedModel.value || !models) return null
|
|
360
386
|
return models.find(m => m.name === selectedModel.value) || models.find(m => m.id === selectedModel.value)
|
|
361
387
|
})
|
|
362
|
-
const selectedPrompt = ref(prefs.systemPrompt || null)
|
|
363
|
-
const currentSystemPrompt = ref('')
|
|
364
|
-
const showSystemPrompt = ref(false)
|
|
365
388
|
const messagesContainer = ref(null)
|
|
366
389
|
const isExporting = ref(false)
|
|
367
390
|
const isImporting = ref(false)
|
|
@@ -396,45 +419,16 @@ export default {
|
|
|
396
419
|
selectedModel.value = thread.model
|
|
397
420
|
}
|
|
398
421
|
|
|
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
422
|
if (!newId) {
|
|
420
423
|
chatPrompt.reset()
|
|
421
424
|
}
|
|
422
425
|
nextTick(addCopyButtons)
|
|
423
426
|
}, { immediate: true })
|
|
424
427
|
|
|
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({
|
|
428
|
+
watch(() => [selectedModel.value], () => {
|
|
429
|
+
ctx.setPrefs({
|
|
435
430
|
model: selectedModel.value,
|
|
436
|
-
|
|
437
|
-
}))
|
|
431
|
+
})
|
|
438
432
|
})
|
|
439
433
|
|
|
440
434
|
async function exportThreads() {
|
|
@@ -828,15 +822,10 @@ export default {
|
|
|
828
822
|
config,
|
|
829
823
|
models,
|
|
830
824
|
threads,
|
|
831
|
-
prompts,
|
|
832
825
|
isGenerating,
|
|
833
|
-
customPromptValue,
|
|
834
826
|
currentThread,
|
|
835
827
|
selectedModel,
|
|
836
828
|
selectedModelObj,
|
|
837
|
-
selectedPrompt,
|
|
838
|
-
currentSystemPrompt,
|
|
839
|
-
showSystemPrompt,
|
|
840
829
|
messagesContainer,
|
|
841
830
|
errorStatus,
|
|
842
831
|
copying,
|
llms/ui/OAuthSignIn.mjs
CHANGED
|
@@ -45,44 +45,13 @@ export default {
|
|
|
45
45
|
`,
|
|
46
46
|
emits: ['done'],
|
|
47
47
|
setup(props, { emit }) {
|
|
48
|
-
const ai = inject('ai')
|
|
49
48
|
const errorMessage = ref(null)
|
|
50
|
-
|
|
49
|
+
|
|
51
50
|
function signInWithGitHub() {
|
|
52
51
|
// Redirect to GitHub OAuth endpoint
|
|
53
52
|
window.location.href = '/auth/github'
|
|
54
53
|
}
|
|
55
|
-
|
|
56
|
-
// Check for session token in URL (after OAuth callback redirect)
|
|
57
|
-
onMounted(async () => {
|
|
58
|
-
const urlParams = new URLSearchParams(window.location.search)
|
|
59
|
-
const sessionToken = urlParams.get('session')
|
|
60
|
-
|
|
61
|
-
if (sessionToken) {
|
|
62
|
-
try {
|
|
63
|
-
// Validate session with server
|
|
64
|
-
const response = await ai.get(`/auth/session?session=${sessionToken}`)
|
|
65
|
-
|
|
66
|
-
if (response.ok) {
|
|
67
|
-
const sessionData = await response.json()
|
|
68
|
-
|
|
69
|
-
// Clean up URL
|
|
70
|
-
const url = new URL(window.location.href)
|
|
71
|
-
url.searchParams.delete('session')
|
|
72
|
-
window.history.replaceState({}, '', url.toString())
|
|
73
|
-
|
|
74
|
-
// Emit done event with session data
|
|
75
|
-
emit('done', sessionData)
|
|
76
|
-
} else {
|
|
77
|
-
errorMessage.value = 'Failed to validate session'
|
|
78
|
-
}
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error('Session validation error:', error)
|
|
81
|
-
errorMessage.value = 'Failed to validate session'
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
})
|
|
85
|
-
|
|
54
|
+
|
|
86
55
|
return {
|
|
87
56
|
signInWithGitHub,
|
|
88
57
|
errorMessage,
|
llms/ui/ProviderStatus.mjs
CHANGED
|
@@ -31,9 +31,10 @@ export default {
|
|
|
31
31
|
`,
|
|
32
32
|
emits: ['updated'],
|
|
33
33
|
setup(props, { emit }) {
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
34
|
+
const ctx = inject('ctx')
|
|
35
|
+
const ai = ctx.ai
|
|
36
|
+
const config = ctx.state.config
|
|
37
|
+
const models = ctx.state.models
|
|
37
38
|
const showPopover = ref(false)
|
|
38
39
|
const triggerRef = ref(null)
|
|
39
40
|
const popoverRef = ref(null)
|
|
@@ -62,11 +63,9 @@ export default {
|
|
|
62
63
|
ai.getConfig(),
|
|
63
64
|
ai.getModels(),
|
|
64
65
|
])
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
Object.assign(config,
|
|
68
|
-
models.length = 0
|
|
69
|
-
newModels.forEach(m => models.push(m))
|
|
66
|
+
const config = await configRes.json()
|
|
67
|
+
const models = await modelsRes.json()
|
|
68
|
+
Object.assign(ctx.state, { config, models })
|
|
70
69
|
emit('updated')
|
|
71
70
|
renderKey.value++
|
|
72
71
|
} catch (e) {
|