llms-py 2.0.9__py3-none-any.whl → 2.0.11__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.py +14 -7
- llms_py-2.0.11.data/data/index.html +80 -0
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/llms.json +5 -5
- llms_py-2.0.11.data/data/ui/Avatar.mjs +28 -0
- llms_py-2.0.11.data/data/ui/Brand.mjs +23 -0
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/ChatPrompt.mjs +101 -69
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/Main.mjs +43 -183
- llms_py-2.0.11.data/data/ui/ModelSelector.mjs +29 -0
- llms_py-2.0.11.data/data/ui/ProviderStatus.mjs +105 -0
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/Recents.mjs +2 -1
- llms_py-2.0.11.data/data/ui/SettingsDialog.mjs +374 -0
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/Sidebar.mjs +11 -27
- llms_py-2.0.11.data/data/ui/SignIn.mjs +64 -0
- llms_py-2.0.11.data/data/ui/SystemPromptEditor.mjs +31 -0
- llms_py-2.0.11.data/data/ui/SystemPromptSelector.mjs +36 -0
- llms_py-2.0.11.data/data/ui/Welcome.mjs +8 -0
- llms_py-2.0.11.data/data/ui/ai.mjs +80 -0
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/app.css +76 -10
- llms_py-2.0.11.data/data/ui/lib/servicestack-vue.mjs +37 -0
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/markdown.mjs +9 -2
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/tailwind.input.css +13 -4
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/threadStore.mjs +2 -2
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/typography.css +109 -1
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/utils.mjs +8 -2
- {llms_py-2.0.9.dist-info → llms_py-2.0.11.dist-info}/METADATA +33 -25
- llms_py-2.0.11.dist-info/RECORD +40 -0
- llms_py-2.0.9.data/data/index.html +0 -64
- llms_py-2.0.9.data/data/ui/lib/servicestack-vue.min.mjs +0 -37
- llms_py-2.0.9.dist-info/RECORD +0 -30
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/requirements.txt +0 -0
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/App.mjs +0 -0
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/fav.svg +0 -0
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/lib/highlight.min.mjs +0 -0
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/lib/idb.min.mjs +0 -0
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/lib/marked.min.mjs +0 -0
- /llms_py-2.0.9.data/data/ui/lib/servicestack-client.min.mjs → /llms_py-2.0.11.data/data/ui/lib/servicestack-client.mjs +0 -0
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/lib/vue-router.min.mjs +0 -0
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/lib/vue.min.mjs +0 -0
- {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui.json +0 -0
- {llms_py-2.0.9.dist-info → llms_py-2.0.11.dist-info}/WHEEL +0 -0
- {llms_py-2.0.9.dist-info → llms_py-2.0.11.dist-info}/entry_points.txt +0 -0
- {llms_py-2.0.9.dist-info → llms_py-2.0.11.dist-info}/licenses/LICENSE +0 -0
- {llms_py-2.0.9.dist-info → llms_py-2.0.11.dist-info}/top_level.txt +0 -0
|
@@ -1,197 +1,54 @@
|
|
|
1
|
-
import { ref, computed, nextTick, watch, onMounted,
|
|
1
|
+
import { ref, computed, nextTick, watch, onMounted, provide, inject } from 'vue'
|
|
2
2
|
import { useRouter, useRoute } from 'vue-router'
|
|
3
3
|
import { useThreadStore } from './threadStore.mjs'
|
|
4
4
|
import { storageObject, addCopyButtons } from './utils.mjs'
|
|
5
5
|
import { renderMarkdown } from './markdown.mjs'
|
|
6
6
|
import ChatPrompt, { useChatPrompt } from './ChatPrompt.mjs'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<div class="cursor-pointer flex items-center" :title="'Enabled:\\n' + (config.status.enabled||[]).map(x => ' ' + x).join('\\n')">
|
|
15
|
-
<svg class="size-4 text-green-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="currentColor"/></svg>
|
|
16
|
-
<span class="text-green-700">{{(config.status.enabled||[]).length}}</span>
|
|
17
|
-
</div>
|
|
18
|
-
<div class="cursor-pointer flex items-center" :title="'Disabled:\\n' + (config.status.disabled||[]).map(x => ' ' + x).join('\\n')">
|
|
19
|
-
<svg class="size-4 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="currentColor"/></svg>
|
|
20
|
-
<span class="text-red-700">{{(config.status.disabled||[]).length}}</span>
|
|
21
|
-
</div>
|
|
22
|
-
</button>
|
|
23
|
-
<div v-if="showPopover" ref="popoverRef" class="absolute right-0 mt-2 w-72 max-h-116 overflow-y-auto bg-white border border-gray-200 rounded-md shadow-lg z-10">
|
|
24
|
-
<div class="divide-y divide-gray-100">
|
|
25
|
-
<div v-for="p in allProviders" :key="p" class="flex items-center justify-between px-3 py-2">
|
|
26
|
-
<label :for="'chk_' + p" class="cursor-pointer text-sm text-gray-900 truncate mr-2" :title="p">{{ p }}</label>
|
|
27
|
-
<div @click="onToggle(p, !isEnabled(p))" class="group relative inline-flex h-5 w-10 shrink-0 items-center justify-center rounded-full outline-offset-2 outline-green-600 has-focus-visible:outline-2">
|
|
28
|
-
<span class="absolute mx-auto h-4 w-9 rounded-full bg-gray-200 inset-ring inset-ring-gray-900/5 transition-colors duration-200 ease-in-out group-has-checked:bg-green-600" />
|
|
29
|
-
<span class="absolute left-0 size-5 rounded-full border border-gray-300 bg-white shadow-xs transition-transform duration-200 ease-in-out group-has-checked:translate-x-5" />
|
|
30
|
-
<input :id="'chk_' + p" type="checkbox" :checked="isEnabled(p)" class="cursor-pointer absolute inset-0 appearance-none focus:outline-hidden" aria-label="Use setting" name="setting" />
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
`,
|
|
37
|
-
emits: ['updated'],
|
|
38
|
-
setup(props, { emit }) {
|
|
39
|
-
const config = inject('config')
|
|
40
|
-
const models = inject('models')
|
|
41
|
-
const showPopover = ref(false)
|
|
42
|
-
const triggerRef = ref(null)
|
|
43
|
-
const popoverRef = ref(null)
|
|
44
|
-
const pending = ref({})
|
|
45
|
-
const renderKey = ref(0)
|
|
46
|
-
const allProviders = computed(() => config.status?.all)
|
|
47
|
-
const isEnabled = (p) => config.status.enabled.includes(p)
|
|
48
|
-
const togglePopover = () => showPopover.value = !showPopover.value
|
|
49
|
-
|
|
50
|
-
const onToggle = async (provider, enable) => {
|
|
51
|
-
pending.value = { ...pending.value, [provider]: true }
|
|
52
|
-
try {
|
|
53
|
-
const res = await fetch(`/providers/${encodeURIComponent(provider)}`, {
|
|
54
|
-
method: 'POST',
|
|
55
|
-
headers: { 'Content-Type': 'application/json' },
|
|
56
|
-
body: JSON.stringify(enable ? { enable: true } : { disable: true })
|
|
57
|
-
})
|
|
58
|
-
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`)
|
|
59
|
-
const json = await res.json()
|
|
60
|
-
config.status.enabled = json.enabled || []
|
|
61
|
-
config.status.disabled = json.disabled || []
|
|
62
|
-
if (json.feedback) {
|
|
63
|
-
alert(json.feedback)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const [configRes, modelsRes] = await Promise.all([
|
|
68
|
-
fetch('/ui.json'),
|
|
69
|
-
fetch('/models'),
|
|
70
|
-
])
|
|
71
|
-
const newConfig = await configRes.json()
|
|
72
|
-
const newModels = await modelsRes.json()
|
|
73
|
-
Object.assign(config, newConfig)
|
|
74
|
-
models.length = 0
|
|
75
|
-
newModels.forEach(m => models.push(m))
|
|
76
|
-
emit('updated')
|
|
77
|
-
renderKey.value++
|
|
78
|
-
} catch (e) {
|
|
79
|
-
alert(`Failed to reload config: ${e.message}`)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
} catch (e) {
|
|
83
|
-
alert(`Failed to ${enable ? 'enable' : 'disable'} ${provider}: ${e.message}`)
|
|
84
|
-
} finally {
|
|
85
|
-
pending.value = { ...pending.value, [provider]: false }
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const onDocClick = (e) => {
|
|
90
|
-
const t = e.target
|
|
91
|
-
if (triggerRef.value?.contains(t)) return
|
|
92
|
-
if (popoverRef.value?.contains(t)) return
|
|
93
|
-
showPopover.value = false
|
|
94
|
-
}
|
|
95
|
-
onMounted(() => document.addEventListener('click', onDocClick))
|
|
96
|
-
onUnmounted(() => document.removeEventListener('click', onDocClick))
|
|
97
|
-
return {
|
|
98
|
-
renderKey,
|
|
99
|
-
config,
|
|
100
|
-
models,
|
|
101
|
-
showPopover,
|
|
102
|
-
triggerRef,
|
|
103
|
-
popoverRef,
|
|
104
|
-
allProviders,
|
|
105
|
-
isEnabled,
|
|
106
|
-
togglePopover,
|
|
107
|
-
onToggle,
|
|
108
|
-
pending,
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
7
|
+
import SignIn from './SignIn.mjs'
|
|
8
|
+
import Avatar from './Avatar.mjs'
|
|
9
|
+
import ModelSelector from './ModelSelector.mjs'
|
|
10
|
+
import SystemPromptSelector from './SystemPromptSelector.mjs'
|
|
11
|
+
import SystemPromptEditor from './SystemPromptEditor.mjs'
|
|
12
|
+
import { useSettings } from "./SettingsDialog.mjs"
|
|
13
|
+
import Welcome from './Welcome.mjs'
|
|
112
14
|
|
|
113
15
|
export default {
|
|
114
16
|
components: {
|
|
17
|
+
ModelSelector,
|
|
18
|
+
SystemPromptSelector,
|
|
19
|
+
SystemPromptEditor,
|
|
115
20
|
ChatPrompt,
|
|
116
|
-
|
|
21
|
+
SignIn,
|
|
22
|
+
Avatar,
|
|
23
|
+
Welcome,
|
|
117
24
|
},
|
|
118
25
|
template: `
|
|
119
26
|
<div class="flex flex-col h-full w-full">
|
|
120
27
|
<!-- Header with model and prompt selectors -->
|
|
121
28
|
<div class="border-b border-gray-200 bg-white px-2 py-2 w-full min-h-16">
|
|
122
29
|
<div class="flex items-center justify-between w-full">
|
|
123
|
-
|
|
124
|
-
<div class="pl-1 flex space-x-2">
|
|
125
|
-
<Autocomplete id="model" :options="models" v-model="selectedModel" label=""
|
|
126
|
-
class="w-72 xl:w-84"
|
|
127
|
-
:match="(x, value) => x.toLowerCase().includes(value.toLowerCase())"
|
|
128
|
-
placeholder="Select Model...">
|
|
129
|
-
<template #item="name">
|
|
130
|
-
<div class="truncate max-w-72" :title="name">{{name}}</div>
|
|
131
|
-
</template>
|
|
132
|
-
</Autocomplete>
|
|
133
|
-
<ProviderStatus @updated="configUpdated" />
|
|
134
|
-
</div>
|
|
30
|
+
<ModelSelector :models="models" v-model="selectedModel" @updated="configUpdated" />
|
|
135
31
|
|
|
136
|
-
<!-- System Prompt Selector -->
|
|
137
32
|
<div class="flex items-center space-x-2">
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<Autocomplete id="prompt" :options="prompts" v-model="selectedPrompt" label=""
|
|
143
|
-
class="w-72 xl:w-84"
|
|
144
|
-
:match="(x, value) => x.name.toLowerCase().includes(value.toLowerCase())"
|
|
145
|
-
placeholder="Select a System Prompt...">
|
|
146
|
-
<template #item="{ name }">
|
|
147
|
-
<div class="truncate max-w-72" :title="name">{{name}}</div>
|
|
148
|
-
</template>
|
|
149
|
-
</Autocomplete>
|
|
150
|
-
|
|
151
|
-
<!-- Toggle System Prompt Visibility -->
|
|
152
|
-
<button
|
|
153
|
-
@click="showSystemPrompt = !showSystemPrompt"
|
|
154
|
-
:class="showSystemPrompt ? 'text-blue-700' : 'text-gray-600'"
|
|
155
|
-
class="p-1 rounded-md hover:bg-blue-100 hover:text-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
|
156
|
-
:title="showSystemPrompt ? 'Hide system prompt' : 'Show system prompt'"
|
|
157
|
-
>
|
|
158
|
-
<svg v-if="!showSystemPrompt" class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="currentColor" d="M33.62 17.53c-3.37-6.23-9.28-10-15.82-10S5.34 11.3 2 17.53l-.28.47l.26.48c3.37 6.23 9.28 10 15.82 10s12.46-3.72 15.82-10l.26-.48Zm-15.82 8.9C12.17 26.43 7 23.29 4 18c3-5.29 8.17-8.43 13.8-8.43S28.54 12.72 31.59 18c-3.05 5.29-8.17 8.43-13.79 8.43"/><path fill="currentColor" d="M18.09 11.17A6.86 6.86 0 1 0 25 18a6.86 6.86 0 0 0-6.91-6.83m0 11.72A4.86 4.86 0 1 1 23 18a4.87 4.87 0 0 1-4.91 4.89"/><path fill="none" d="M0 0h36v36H0z"/></svg>
|
|
159
|
-
<svg v-else class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="currentColor" d="M25.19 20.4a6.8 6.8 0 0 0 .43-2.4a6.86 6.86 0 0 0-6.86-6.86a6.8 6.8 0 0 0-2.37.43L18 13.23a5 5 0 0 1 .74-.06A4.87 4.87 0 0 1 23.62 18a5 5 0 0 1-.06.74Z" class="clr-i-outline clr-i-outline-path-1"/><path fill="currentColor" d="M34.29 17.53c-3.37-6.23-9.28-10-15.82-10a16.8 16.8 0 0 0-5.24.85L14.84 10a14.8 14.8 0 0 1 3.63-.47c5.63 0 10.75 3.14 13.8 8.43a17.8 17.8 0 0 1-4.37 5.1l1.42 1.42a19.9 19.9 0 0 0 5-6l.26-.48Z"/><path fill="currentColor" d="m4.87 5.78l4.46 4.46a19.5 19.5 0 0 0-6.69 7.29l-.26.47l.26.48c3.37 6.23 9.28 10 15.82 10a16.9 16.9 0 0 0 7.37-1.69l5 5l1.75-1.5l-26-26Zm9.75 9.75l6.65 6.65a4.8 4.8 0 0 1-2.5.72A4.87 4.87 0 0 1 13.9 18a4.8 4.8 0 0 1 .72-2.47m-1.45-1.45a6.85 6.85 0 0 0 9.55 9.55l1.6 1.6a14.9 14.9 0 0 1-5.86 1.2c-5.63 0-10.75-3.14-13.8-8.43a17.3 17.3 0 0 1 6.12-6.3Z"/><path fill="none" d="M0 0h36v36H0z"/></svg>
|
|
160
|
-
</button>
|
|
33
|
+
<SystemPromptSelector :prompts="prompts" v-model="selectedPrompt"
|
|
34
|
+
:show="showSystemPrompt" @toggle="showSystemPrompt = !showSystemPrompt" />
|
|
35
|
+
<Avatar />
|
|
161
36
|
</div>
|
|
162
37
|
</div>
|
|
163
38
|
</div>
|
|
164
39
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
<div class="max-w-6xl mx-auto">
|
|
168
|
-
<label class="block text-sm font-medium text-gray-700 mb-2">
|
|
169
|
-
System Prompt
|
|
170
|
-
<span v-if="selectedPrompt" class="text-gray-500 font-normal">
|
|
171
|
-
({{ prompts.find(p => p.id === selectedPrompt.id)?.name || 'Custom' }})
|
|
172
|
-
</span>
|
|
173
|
-
</label>
|
|
174
|
-
<textarea
|
|
175
|
-
v-model="currentSystemPrompt"
|
|
176
|
-
placeholder="Enter a system prompt to guide AI's behavior..."
|
|
177
|
-
rows="6"
|
|
178
|
-
class="block w-full resize-vertical rounded-md border border-gray-300 px-3 py-2 text-sm placeholder-gray-500 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
179
|
-
></textarea>
|
|
180
|
-
<div class="mt-2 text-xs text-gray-500">
|
|
181
|
-
You can modify this system prompt before sending messages. Changes will only apply to new conversations.
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
40
|
+
<SystemPromptEditor v-if="showSystemPrompt"
|
|
41
|
+
v-model="currentSystemPrompt" :prompts="prompts" :selected="selectedPrompt" />
|
|
185
42
|
|
|
186
43
|
<!-- Messages Area -->
|
|
187
44
|
<div class="flex-1 overflow-y-auto" ref="messagesContainer">
|
|
188
45
|
<div class="mx-auto max-w-6xl px-4 py-6">
|
|
46
|
+
<div v-if="$ai.requiresAuth && !$ai.auth">
|
|
47
|
+
<SignIn @done="$ai.signIn($event)" />
|
|
48
|
+
</div>
|
|
189
49
|
<!-- Welcome message when no thread is selected -->
|
|
190
|
-
<div v-if="!currentThread" class="text-center py-12">
|
|
191
|
-
<
|
|
192
|
-
<svg class="size-20 text-gray-700" 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>
|
|
193
|
-
</div>
|
|
194
|
-
<h2 class="text-2xl font-semibold text-gray-900 mb-2">Welcome to llms.py</h2>
|
|
50
|
+
<div v-else-if="!currentThread" class="text-center py-12">
|
|
51
|
+
<Welcome />
|
|
195
52
|
|
|
196
53
|
<!-- Chat input for new conversation -->
|
|
197
54
|
<div class="max-w-2xl mx-auto">
|
|
@@ -338,7 +195,7 @@ export default {
|
|
|
338
195
|
</div>
|
|
339
196
|
|
|
340
197
|
<!-- Error message bubble -->
|
|
341
|
-
<div v-if="
|
|
198
|
+
<div v-if="errorStatus" class="flex items-start space-x-3">
|
|
342
199
|
<!-- Avatar outside the bubble -->
|
|
343
200
|
<div class="flex-shrink-0">
|
|
344
201
|
<div class="w-8 h-8 rounded-full bg-red-600 text-white flex items-center justify-center text-sm font-medium">
|
|
@@ -350,13 +207,14 @@ export default {
|
|
|
350
207
|
<div class="max-w-[85%] rounded-lg px-4 py-3 bg-red-50 border border-red-200 text-red-800 shadow-sm">
|
|
351
208
|
<div class="flex items-start space-x-2">
|
|
352
209
|
<div class="flex-1 min-w-0">
|
|
353
|
-
<div class="text-base font-medium mb-1">{{ errorStatus || 'Error' }}</div>
|
|
354
|
-
<div class="text-
|
|
355
|
-
|
|
356
|
-
|
|
210
|
+
<div class="text-base font-medium mb-1">{{ errorStatus?.errorCode || 'Error' }}</div>
|
|
211
|
+
<div v-if="errorStatus?.message" class="text-base mb-1">{{ errorStatus.message }}</div>
|
|
212
|
+
<div v-if="errorStatus?.stackTrace" class="text-sm whitespace-pre-wrap break-words max-h-80 overflow-y-auto font-mono p-2 rounded">
|
|
213
|
+
{{ errorStatus.stackTrace }}
|
|
214
|
+
</div>
|
|
357
215
|
</div>
|
|
358
216
|
<button type="button"
|
|
359
|
-
@click="
|
|
217
|
+
@click="errorStatus = null"
|
|
360
218
|
class="text-red-400 hover:text-red-600 flex-shrink-0"
|
|
361
219
|
>
|
|
362
220
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
@@ -379,22 +237,24 @@ export default {
|
|
|
379
237
|
props: {
|
|
380
238
|
},
|
|
381
239
|
setup(props) {
|
|
240
|
+
const ai = inject('ai')
|
|
382
241
|
const router = useRouter()
|
|
383
242
|
const route = useRoute()
|
|
384
243
|
const threads = useThreadStore()
|
|
385
244
|
const { currentThread } = threads
|
|
386
245
|
const chatPrompt = useChatPrompt()
|
|
246
|
+
const chatSettings = useSettings()
|
|
387
247
|
const {
|
|
388
248
|
errorStatus,
|
|
389
|
-
errorMessage,
|
|
390
249
|
isGenerating,
|
|
391
250
|
} = chatPrompt
|
|
392
251
|
provide('threads', threads)
|
|
393
252
|
provide('chatPrompt', chatPrompt)
|
|
253
|
+
provide('chatSettings', chatSettings)
|
|
394
254
|
const models = inject('models')
|
|
395
255
|
const config = inject('config')
|
|
396
256
|
|
|
397
|
-
const prefs = storageObject(
|
|
257
|
+
const prefs = storageObject(ai.prefsKey)
|
|
398
258
|
|
|
399
259
|
const customPromptValue = ref('')
|
|
400
260
|
const customPrompt = {
|
|
@@ -406,7 +266,7 @@ export default {
|
|
|
406
266
|
const prompts = computed(() => [customPrompt, ...config.prompts])
|
|
407
267
|
|
|
408
268
|
const selectedModel = ref(prefs.model || config.defaults.text.model || '')
|
|
409
|
-
const selectedPrompt = ref(prefs.systemPrompt ||
|
|
269
|
+
const selectedPrompt = ref(prefs.systemPrompt || null)
|
|
410
270
|
const currentSystemPrompt = ref('')
|
|
411
271
|
const showSystemPrompt = ref(false)
|
|
412
272
|
const messagesContainer = ref(null)
|
|
@@ -448,8 +308,9 @@ export default {
|
|
|
448
308
|
currentSystemPrompt.value = thread.systemPrompt
|
|
449
309
|
}
|
|
450
310
|
} else {
|
|
451
|
-
|
|
452
|
-
|
|
311
|
+
// Preserve existing selected prompt
|
|
312
|
+
// selectedPrompt.value = null
|
|
313
|
+
// currentSystemPrompt.value = ''
|
|
453
314
|
}
|
|
454
315
|
}
|
|
455
316
|
|
|
@@ -468,7 +329,7 @@ export default {
|
|
|
468
329
|
}, { immediate: true })
|
|
469
330
|
|
|
470
331
|
watch(() => [selectedModel.value, selectedPrompt.value], () => {
|
|
471
|
-
localStorage.setItem(
|
|
332
|
+
localStorage.setItem(ai.prefsKey, JSON.stringify({
|
|
472
333
|
model: selectedModel.value,
|
|
473
334
|
systemPrompt: selectedPrompt.value
|
|
474
335
|
}))
|
|
@@ -487,7 +348,7 @@ export default {
|
|
|
487
348
|
const exportData = {
|
|
488
349
|
exportedAt: new Date().toISOString(),
|
|
489
350
|
version: '1.0',
|
|
490
|
-
source: '
|
|
351
|
+
source: 'ServiceStack.AI.Chat',
|
|
491
352
|
threadCount: allThreads.length,
|
|
492
353
|
threads: allThreads
|
|
493
354
|
}
|
|
@@ -499,7 +360,7 @@ export default {
|
|
|
499
360
|
|
|
500
361
|
const link = document.createElement('a')
|
|
501
362
|
link.href = url
|
|
502
|
-
link.download = `
|
|
363
|
+
link.download = `aichat-threads-export-${new Date().toISOString().split('T')[0]}.json`
|
|
503
364
|
document.body.appendChild(link)
|
|
504
365
|
link.click()
|
|
505
366
|
document.body.removeChild(link)
|
|
@@ -661,7 +522,6 @@ export default {
|
|
|
661
522
|
showSystemPrompt,
|
|
662
523
|
messagesContainer,
|
|
663
524
|
errorStatus,
|
|
664
|
-
errorMessage,
|
|
665
525
|
formatTime,
|
|
666
526
|
renderMarkdown,
|
|
667
527
|
isReasoningExpanded,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import ProviderStatus from "./ProviderStatus.mjs";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
components: {
|
|
5
|
+
ProviderStatus,
|
|
6
|
+
},
|
|
7
|
+
template:`
|
|
8
|
+
<!-- Model Selector -->
|
|
9
|
+
<div class="pl-1 flex space-x-2">
|
|
10
|
+
<Autocomplete id="model" :options="models" label=""
|
|
11
|
+
:modelValue="modelValue" @update:modelValue="$emit('update:modelValue', $event)"
|
|
12
|
+
class="w-72 xl:w-84"
|
|
13
|
+
:match="(x, value) => x.toLowerCase().includes(value.toLowerCase())"
|
|
14
|
+
placeholder="Select Model...">
|
|
15
|
+
<template #item="{ value }">
|
|
16
|
+
<div class="truncate max-w-72" :title="value">{{value}}</div>
|
|
17
|
+
</template>
|
|
18
|
+
</Autocomplete>
|
|
19
|
+
<ProviderStatus @updated="$emit('updated', $event)" />
|
|
20
|
+
</div>
|
|
21
|
+
`,
|
|
22
|
+
emits: ['updated', 'update:modelValue'],
|
|
23
|
+
props: {
|
|
24
|
+
models: Array,
|
|
25
|
+
modelValue: String,
|
|
26
|
+
},
|
|
27
|
+
setup() {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ref, computed, inject, onMounted, onUnmounted } from "vue"
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
template:`
|
|
5
|
+
<div v-if="$ai.isAdmin" ref="triggerRef" class="relative" :key="renderKey">
|
|
6
|
+
<button type="button" @click="togglePopover"
|
|
7
|
+
class="mt-1 flex space-x-2 items-center text-sm font-semibold select-none rounded-sm py-2 px-3 border border-transparent hover:bg-gray-50 hover:shadow hover:border-gray-200">
|
|
8
|
+
<span class="text-gray-600" :title="models.length + ' models from ' + (config.status.enabled||[]).length + ' enabled providers'">{{models.length}}</span>
|
|
9
|
+
<div class="cursor-pointer flex items-center" :title="'Enabled:\\n' + (config.status.enabled||[]).map(x => ' ' + x).join('\\n')">
|
|
10
|
+
<svg class="size-4 text-green-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="currentColor"/></svg>
|
|
11
|
+
<span class="text-green-700">{{(config.status.enabled||[]).length}}</span>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="cursor-pointer flex items-center" :title="'Disabled:\\n' + (config.status.disabled||[]).map(x => ' ' + x).join('\\n')">
|
|
14
|
+
<svg class="size-4 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="currentColor"/></svg>
|
|
15
|
+
<span class="text-red-700">{{(config.status.disabled||[]).length}}</span>
|
|
16
|
+
</div>
|
|
17
|
+
</button>
|
|
18
|
+
<div v-if="showPopover" ref="popoverRef" class="absolute right-0 mt-2 w-72 max-h-120 overflow-y-auto bg-white border border-gray-200 rounded-md shadow-lg z-10">
|
|
19
|
+
<div class="divide-y divide-gray-100">
|
|
20
|
+
<div v-for="p in allProviders" :key="p" class="flex items-center justify-between px-3 py-2">
|
|
21
|
+
<label :for="'chk_' + p" class="cursor-pointer text-sm text-gray-900 truncate mr-2" :title="p">{{ p }}</label>
|
|
22
|
+
<div @click="onToggle(p, !isEnabled(p))" class="cursor-pointer group relative inline-flex h-5 w-10 shrink-0 items-center justify-center rounded-full outline-offset-2 outline-green-600 has-focus-visible:outline-2">
|
|
23
|
+
<span class="absolute mx-auto h-4 w-9 rounded-full bg-gray-200 inset-ring inset-ring-gray-900/5 transition-colors duration-200 ease-in-out group-has-checked:bg-green-600" />
|
|
24
|
+
<span class="absolute left-0 size-5 rounded-full border border-gray-300 bg-white shadow-xs transition-transform duration-200 ease-in-out group-has-checked:translate-x-5" />
|
|
25
|
+
<input :id="'chk_' + p" type="checkbox" :checked="isEnabled(p)" class="switch cursor-pointer absolute inset-0 appearance-none focus:outline-hidden" aria-label="Use setting" name="setting" />
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
`,
|
|
32
|
+
emits: ['updated'],
|
|
33
|
+
setup(props, { emit }) {
|
|
34
|
+
const ai = inject('ai')
|
|
35
|
+
const config = inject('config')
|
|
36
|
+
const models = inject('models')
|
|
37
|
+
const showPopover = ref(false)
|
|
38
|
+
const triggerRef = ref(null)
|
|
39
|
+
const popoverRef = ref(null)
|
|
40
|
+
const pending = ref({})
|
|
41
|
+
const renderKey = ref(0)
|
|
42
|
+
const allProviders = computed(() => config.status?.all)
|
|
43
|
+
const isEnabled = (p) => config.status.enabled.includes(p)
|
|
44
|
+
const togglePopover = () => showPopover.value = !showPopover.value
|
|
45
|
+
|
|
46
|
+
const onToggle = async (provider, enable) => {
|
|
47
|
+
pending.value = { ...pending.value, [provider]: true }
|
|
48
|
+
try {
|
|
49
|
+
const res = await ai.post(`/providers/${encodeURIComponent(provider)}`, {
|
|
50
|
+
body: JSON.stringify(enable ? { enable: true } : { disable: true })
|
|
51
|
+
})
|
|
52
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`)
|
|
53
|
+
const json = await res.json()
|
|
54
|
+
config.status.enabled = json.enabled || []
|
|
55
|
+
config.status.disabled = json.disabled || []
|
|
56
|
+
if (json.feedback) {
|
|
57
|
+
alert(json.feedback)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const [configRes, modelsRes] = await Promise.all([
|
|
62
|
+
ai.getConfig(),
|
|
63
|
+
ai.getModels(),
|
|
64
|
+
])
|
|
65
|
+
const newConfig = await configRes.json()
|
|
66
|
+
const newModels = await modelsRes.json()
|
|
67
|
+
Object.assign(config, newConfig)
|
|
68
|
+
models.length = 0
|
|
69
|
+
newModels.forEach(m => models.push(m))
|
|
70
|
+
emit('updated')
|
|
71
|
+
renderKey.value++
|
|
72
|
+
} catch (e) {
|
|
73
|
+
alert(`Failed to reload config: ${e.message}`)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
} catch (e) {
|
|
77
|
+
alert(`Failed to ${enable ? 'enable' : 'disable'} ${provider}: ${e.message}`)
|
|
78
|
+
} finally {
|
|
79
|
+
pending.value = { ...pending.value, [provider]: false }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const onDocClick = (e) => {
|
|
84
|
+
const t = e.target
|
|
85
|
+
if (triggerRef.value?.contains(t)) return
|
|
86
|
+
if (popoverRef.value?.contains(t)) return
|
|
87
|
+
showPopover.value = false
|
|
88
|
+
}
|
|
89
|
+
onMounted(() => document.addEventListener('click', onDocClick))
|
|
90
|
+
onUnmounted(() => document.removeEventListener('click', onDocClick))
|
|
91
|
+
return {
|
|
92
|
+
renderKey,
|
|
93
|
+
config,
|
|
94
|
+
models,
|
|
95
|
+
showPopover,
|
|
96
|
+
triggerRef,
|
|
97
|
+
popoverRef,
|
|
98
|
+
allProviders,
|
|
99
|
+
isEnabled,
|
|
100
|
+
togglePopover,
|
|
101
|
+
onToggle,
|
|
102
|
+
pending,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -46,6 +46,7 @@ const RecentResults = {
|
|
|
46
46
|
q: String
|
|
47
47
|
},
|
|
48
48
|
setup(props) {
|
|
49
|
+
const ai = inject('ai')
|
|
49
50
|
const router = useRouter()
|
|
50
51
|
const config = inject('config')
|
|
51
52
|
const { threads, loadThreads } = useThreadStore()
|
|
@@ -127,7 +128,7 @@ const RecentResults = {
|
|
|
127
128
|
return ''
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
const open = (id) => router.push(
|
|
131
|
+
const open = (id) => router.push(`${ai.base}/c/${id}`)
|
|
131
132
|
const formatDate = (iso) => new Date(iso).toLocaleString()
|
|
132
133
|
|
|
133
134
|
return {
|