llms-py 2.0.18__py3-none-any.whl → 2.0.33__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/index.html +17 -1
- llms/llms.json +1132 -1075
- llms/main.py +561 -103
- llms/ui/Analytics.mjs +115 -104
- llms/ui/App.mjs +81 -4
- llms/ui/Avatar.mjs +61 -4
- llms/ui/Brand.mjs +29 -11
- llms/ui/ChatPrompt.mjs +163 -16
- llms/ui/Main.mjs +177 -94
- llms/ui/ModelSelector.mjs +28 -10
- llms/ui/OAuthSignIn.mjs +92 -0
- llms/ui/ProviderStatus.mjs +12 -12
- llms/ui/Recents.mjs +13 -13
- llms/ui/SettingsDialog.mjs +65 -65
- llms/ui/Sidebar.mjs +24 -19
- llms/ui/SystemPromptEditor.mjs +5 -5
- llms/ui/SystemPromptSelector.mjs +26 -6
- llms/ui/Welcome.mjs +2 -2
- llms/ui/ai.mjs +69 -5
- llms/ui/app.css +548 -34
- llms/ui/lib/servicestack-vue.mjs +9 -9
- llms/ui/markdown.mjs +8 -8
- llms/ui/tailwind.input.css +2 -0
- llms/ui/threadStore.mjs +39 -0
- llms/ui/typography.css +54 -36
- {llms_py-2.0.18.dist-info → llms_py-2.0.33.dist-info}/METADATA +403 -47
- llms_py-2.0.33.dist-info/RECORD +48 -0
- {llms_py-2.0.18.dist-info → llms_py-2.0.33.dist-info}/licenses/LICENSE +1 -2
- 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_py-2.0.18.dist-info/RECORD +0 -56
- {llms_py-2.0.18.dist-info → llms_py-2.0.33.dist-info}/WHEEL +0 -0
- {llms_py-2.0.18.dist-info → llms_py-2.0.33.dist-info}/entry_points.txt +0 -0
- {llms_py-2.0.18.dist-info → llms_py-2.0.33.dist-info}/top_level.txt +0 -0
llms/ui/Sidebar.mjs
CHANGED
|
@@ -12,22 +12,22 @@ const ThreadItem = {
|
|
|
12
12
|
template: `
|
|
13
13
|
<div
|
|
14
14
|
class="group relative mx-2 mb-1 rounded-md cursor-pointer transition-colors border border-transparent"
|
|
15
|
-
:class="isActive ? 'bg-blue-100 border-blue-200' : 'hover:bg-gray-100'"
|
|
15
|
+
:class="isActive ? 'bg-blue-100 dark:bg-blue-900 border-blue-200 dark:border-blue-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800'"
|
|
16
16
|
@click="$emit('select', thread.id)"
|
|
17
17
|
>
|
|
18
18
|
<div class="flex items-center px-3 py-2">
|
|
19
19
|
<div class="flex-1 min-w-0">
|
|
20
|
-
<div class="text-sm font-medium text-gray-900 truncate" :title="thread.title">
|
|
20
|
+
<div class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate" :title="thread.title">
|
|
21
21
|
{{ thread.title }}
|
|
22
22
|
</div>
|
|
23
|
-
<div class="text-xs text-gray-500 truncate">
|
|
23
|
+
<div class="text-xs text-gray-500 dark:text-gray-400 truncate">
|
|
24
24
|
<span>{{ formatRelativeTime(thread.updatedAt) }} • {{ thread.messages.length }} msgs</span>
|
|
25
25
|
<span v-if="thread.stats?.inputTokens" :title="statsTitle(thread.stats)">
|
|
26
26
|
• {{ humanifyNumber(thread.stats.inputTokens + thread.stats.outputTokens) }} toks
|
|
27
27
|
{{ thread.stats.cost ? ' ' + formatCost(thread.stats.cost) : '' }}
|
|
28
28
|
</span>
|
|
29
29
|
</div>
|
|
30
|
-
<div v-if="thread.model" class="text-xs text-blue-600 truncate">
|
|
30
|
+
<div v-if="thread.model" class="text-xs text-blue-600 dark:text-blue-400 truncate">
|
|
31
31
|
{{ thread.model }}
|
|
32
32
|
</div>
|
|
33
33
|
</div>
|
|
@@ -35,7 +35,7 @@ const ThreadItem = {
|
|
|
35
35
|
<!-- Delete button (shown on hover) -->
|
|
36
36
|
<button type="button"
|
|
37
37
|
@click.stop="$emit('delete', thread.id)"
|
|
38
|
-
class="opacity-0 group-hover:opacity-100 ml-2 p-1 rounded text-gray-400 hover:text-red-600 hover:bg-red-50 transition-all"
|
|
38
|
+
class="opacity-0 group-hover:opacity-100 ml-2 p-1 rounded text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 transition-all"
|
|
39
39
|
title="Delete conversation"
|
|
40
40
|
>
|
|
41
41
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -89,7 +89,7 @@ const GroupedThreads = {
|
|
|
89
89
|
template: `
|
|
90
90
|
<!-- Today -->
|
|
91
91
|
<div v-if="groupedThreads.today.length > 0" class="mb-4">
|
|
92
|
-
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider select-none">Today</h3>
|
|
92
|
+
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider select-none">Today</h3>
|
|
93
93
|
<ThreadItem
|
|
94
94
|
v-for="thread in groupedThreads.today"
|
|
95
95
|
:key="thread.id"
|
|
@@ -102,7 +102,7 @@ const GroupedThreads = {
|
|
|
102
102
|
|
|
103
103
|
<!-- Last 7 Days -->
|
|
104
104
|
<div v-if="groupedThreads.lastWeek.length > 0" class="mb-4">
|
|
105
|
-
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider select-none">Last 7 Days</h3>
|
|
105
|
+
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider select-none">Last 7 Days</h3>
|
|
106
106
|
<ThreadItem
|
|
107
107
|
v-for="thread in groupedThreads.lastWeek"
|
|
108
108
|
:key="thread.id"
|
|
@@ -115,7 +115,7 @@ const GroupedThreads = {
|
|
|
115
115
|
|
|
116
116
|
<!-- Last 30 Days -->
|
|
117
117
|
<div v-if="groupedThreads.lastMonth.length > 0" class="mb-4">
|
|
118
|
-
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider select-none">Last 30 Days</h3>
|
|
118
|
+
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider select-none">Last 30 Days</h3>
|
|
119
119
|
<ThreadItem
|
|
120
120
|
v-for="thread in groupedThreads.lastMonth"
|
|
121
121
|
:key="thread.id"
|
|
@@ -128,7 +128,7 @@ const GroupedThreads = {
|
|
|
128
128
|
|
|
129
129
|
<!-- Older (grouped by month/year) -->
|
|
130
130
|
<div v-for="(monthThreads, monthKey) in groupedThreads.older" :key="monthKey" class="mb-4">
|
|
131
|
-
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider select-none">{{ monthKey }}</h3>
|
|
131
|
+
<h3 class="px-4 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider select-none">{{ monthKey }}</h3>
|
|
132
132
|
<ThreadItem
|
|
133
133
|
v-for="thread in monthThreads"
|
|
134
134
|
:key="thread.id"
|
|
@@ -140,7 +140,7 @@ const GroupedThreads = {
|
|
|
140
140
|
</div>
|
|
141
141
|
<div class="mb-4 flex w-full justify-center">
|
|
142
142
|
<button @click="$router.push($ai.base + '/recents')" type="button"
|
|
143
|
-
class="flex text-sm space-x-1 font-semibold text-gray-900 hover:text-blue-600 focus:outline-none transition-colors">
|
|
143
|
+
class="flex text-sm space-x-1 font-semibold text-gray-900 dark:text-gray-100 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none transition-colors">
|
|
144
144
|
<svg class="size-5" 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"></path><ellipse cx="5.2" cy="7.7" fill="currentColor" rx=".8" ry=".75"></ellipse><ellipse cx="8" cy="7.7" fill="currentColor" rx=".8" ry=".75"></ellipse><ellipse cx="10.8" cy="7.7" fill="currentColor" rx=".8" ry=".75"></ellipse></svg>
|
|
145
145
|
<span>All Chats</span>
|
|
146
146
|
</button>
|
|
@@ -163,31 +163,32 @@ const Sidebar = {
|
|
|
163
163
|
ThreadItem,
|
|
164
164
|
},
|
|
165
165
|
template: `
|
|
166
|
-
<div class="flex flex-col h-full bg-gray-50 border-r border-gray-200">
|
|
167
|
-
<Brand @home="goToInitialState" @new="createNewThread" @analytics="goToAnalytics" />
|
|
166
|
+
<div class="flex flex-col h-full bg-gray-50 dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700">
|
|
167
|
+
<Brand @home="goToInitialState" @new="createNewThread" @analytics="goToAnalytics" @toggle-sidebar="$emit('toggle-sidebar')" />
|
|
168
168
|
<!-- Thread List -->
|
|
169
169
|
<div class="flex-1 overflow-y-auto">
|
|
170
|
-
<div v-if="isLoading" class="p-4 text-center text-gray-500">
|
|
171
|
-
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600 mx-auto"></div>
|
|
170
|
+
<div v-if="isLoading" class="p-4 text-center text-gray-500 dark:text-gray-400">
|
|
171
|
+
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600 dark:border-blue-400 mx-auto"></div>
|
|
172
172
|
<p class="mt-2 text-sm">Loading threads...</p>
|
|
173
173
|
</div>
|
|
174
174
|
|
|
175
|
-
<div v-else-if="threads.length === 0" class="p-4 text-center text-gray-500">
|
|
175
|
+
<div v-else-if="threads.length === 0" class="p-4 text-center text-gray-500 dark:text-gray-400">
|
|
176
176
|
<div class="mb-2 flex justify-center">
|
|
177
177
|
<svg class="size-8" 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>
|
|
178
178
|
</div>
|
|
179
179
|
<p class="text-sm">No conversations yet</p>
|
|
180
|
-
<p class="text-xs text-gray-400 mt-1">Start a new chat to begin</p>
|
|
180
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">Start a new chat to begin</p>
|
|
181
181
|
</div>
|
|
182
182
|
|
|
183
183
|
<div v-else class="py-2">
|
|
184
|
-
<GroupedThreads :currentThread="currentThread" :groupedThreads="threadStore.getGroupedThreads(18)"
|
|
185
|
-
@select="selectThread" @delete="deleteThread" />
|
|
184
|
+
<GroupedThreads :currentThread="currentThread" :groupedThreads="threadStore.getGroupedThreads(18)"
|
|
185
|
+
@select="selectThread" @delete="deleteThread" />
|
|
186
186
|
</div>
|
|
187
187
|
</div>
|
|
188
188
|
</div>
|
|
189
189
|
`,
|
|
190
|
-
|
|
190
|
+
emits: ['thread-selected', 'toggle-sidebar'],
|
|
191
|
+
setup(props, { emit }) {
|
|
191
192
|
const ai = inject('ai')
|
|
192
193
|
const router = useRouter()
|
|
193
194
|
const threadStore = useThreadStore()
|
|
@@ -208,6 +209,7 @@ const Sidebar = {
|
|
|
208
209
|
|
|
209
210
|
const selectThread = async (threadId) => {
|
|
210
211
|
router.push(`${ai.base}/c/${threadId}`)
|
|
212
|
+
emit('thread-selected')
|
|
211
213
|
}
|
|
212
214
|
|
|
213
215
|
const deleteThread = async (threadId) => {
|
|
@@ -223,15 +225,18 @@ const Sidebar = {
|
|
|
223
225
|
const createNewThread = async () => {
|
|
224
226
|
const newThread = await createThread()
|
|
225
227
|
router.push(`${ai.base}/c/${newThread.id}`)
|
|
228
|
+
emit('thread-selected')
|
|
226
229
|
}
|
|
227
230
|
|
|
228
231
|
const goToInitialState = () => {
|
|
229
232
|
clearCurrentThread()
|
|
230
233
|
router.push(`${ai.base}/`)
|
|
234
|
+
emit('thread-selected')
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
const goToAnalytics = () => {
|
|
234
238
|
router.push(`${ai.base}/analytics`)
|
|
239
|
+
emit('thread-selected')
|
|
235
240
|
}
|
|
236
241
|
|
|
237
242
|
return {
|
llms/ui/SystemPromptEditor.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
template:`
|
|
3
|
-
<div class="border-b border-gray-200 bg-gray-50 px-6 py-4">
|
|
3
|
+
<div class="border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 px-6 py-4">
|
|
4
4
|
<div class="max-w-6xl mx-auto">
|
|
5
|
-
<label class="block text-sm font-medium text-gray-700 mb-2">
|
|
5
|
+
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
6
6
|
System Prompt
|
|
7
|
-
<span v-if="selected" class="text-gray-500 font-normal">
|
|
7
|
+
<span v-if="selected" class="text-gray-500 dark:text-gray-400 font-normal">
|
|
8
8
|
({{ prompts.find(p => p.id === selected.id)?.name || 'Custom' }})
|
|
9
9
|
</span>
|
|
10
10
|
</label>
|
|
@@ -12,9 +12,9 @@ export default {
|
|
|
12
12
|
:value="modelValue" @input="$emit('update:modelValue', $event.target.value)"
|
|
13
13
|
placeholder="Enter a system prompt to guide AI's behavior..."
|
|
14
14
|
rows="6"
|
|
15
|
-
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"
|
|
15
|
+
class="block w-full resize-vertical rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 px-3 py-2 text-sm placeholder-gray-500 dark:placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
16
16
|
></textarea>
|
|
17
|
-
<div class="mt-2 text-xs text-gray-500">
|
|
17
|
+
<div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
|
18
18
|
You can modify this system prompt before sending messages. Changes will only apply to new conversations.
|
|
19
19
|
</div>
|
|
20
20
|
</div>
|
llms/ui/SystemPromptSelector.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { ref, onMounted, onUnmounted } from "vue"
|
|
1
2
|
export default {
|
|
2
3
|
template:`
|
|
3
4
|
<button v-if="modelValue" type="button" title="Clear System Prompt" @click="$emit('update:modelValue', null)">
|
|
4
|
-
<svg class="size-4 text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z"/></svg>
|
|
5
|
+
<svg class="size-4 text-gray-500 dark:text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z"/></svg>
|
|
5
6
|
</button>
|
|
6
|
-
|
|
7
|
-
<Autocomplete id="prompt" :options="prompts" label=""
|
|
7
|
+
|
|
8
|
+
<Autocomplete ref="refSelector" id="prompt" :options="prompts" label=""
|
|
8
9
|
:modelValue="modelValue" @update:modelValue="$emit('update:modelValue', $event)"
|
|
9
|
-
class="w-
|
|
10
|
+
class="w-68 xl:w-84"
|
|
10
11
|
:match="(x, value) => x.name.toLowerCase().includes(value.toLowerCase())"
|
|
11
12
|
placeholder="Select a System Prompt...">
|
|
12
13
|
<template #item="{ value }">
|
|
@@ -17,8 +18,8 @@ export default {
|
|
|
17
18
|
<!-- Toggle System Prompt Visibility -->
|
|
18
19
|
<button type="button"
|
|
19
20
|
@click="$emit('toggle')"
|
|
20
|
-
:class="show ? 'text-blue-700' : 'text-gray-600'"
|
|
21
|
-
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"
|
|
21
|
+
:class="show ? 'text-blue-700 dark:text-blue-400' : 'text-gray-600 dark:text-gray-400'"
|
|
22
|
+
class="p-1 rounded-md hover:bg-blue-100 dark:hover:bg-blue-900/30 hover:text-blue-700 dark:hover:text-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
|
22
23
|
:title="show ? 'Hide system prompt' : 'Show system prompt'"
|
|
23
24
|
>
|
|
24
25
|
<svg v-if="!show" 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>
|
|
@@ -32,5 +33,24 @@ export default {
|
|
|
32
33
|
show: Boolean,
|
|
33
34
|
},
|
|
34
35
|
setup() {
|
|
36
|
+
const refSelector = ref()
|
|
37
|
+
|
|
38
|
+
function collapse(e) {
|
|
39
|
+
// call toggle when clicking outside of the Autocomplete component
|
|
40
|
+
if (refSelector.value && !refSelector.value.$el.contains(e.target)) {
|
|
41
|
+
refSelector.value.toggle(false)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
onMounted(() => {
|
|
46
|
+
document.addEventListener('click', collapse)
|
|
47
|
+
})
|
|
48
|
+
onUnmounted(() => {
|
|
49
|
+
document.removeEventListener('click', collapse)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
refSelector,
|
|
54
|
+
}
|
|
35
55
|
}
|
|
36
56
|
}
|
llms/ui/Welcome.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
template: `
|
|
3
3
|
<div class="mb-2 flex justify-center">
|
|
4
|
-
<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>
|
|
4
|
+
<svg class="size-20 text-gray-700 dark:text-gray-300" 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>
|
|
5
5
|
</div>
|
|
6
|
-
<h2 class="text-2xl font-semibold text-gray-900 mb-2">{{ $ai.welcome }}</h2>
|
|
6
|
+
<h2 class="text-2xl font-semibold text-gray-900 dark:text-gray-100 mb-2">{{ $ai.welcome }}</h2>
|
|
7
7
|
`
|
|
8
8
|
}
|
llms/ui/ai.mjs
CHANGED
|
@@ -6,13 +6,15 @@ const headers = { 'Accept': 'application/json' }
|
|
|
6
6
|
const prefsKey = 'llms.prefs'
|
|
7
7
|
|
|
8
8
|
export const o = {
|
|
9
|
-
version: '2.0.
|
|
9
|
+
version: '2.0.33',
|
|
10
10
|
base,
|
|
11
11
|
prefsKey,
|
|
12
12
|
welcome: 'Welcome to llms.py',
|
|
13
13
|
auth: null,
|
|
14
14
|
requiresAuth: false,
|
|
15
|
+
authType: 'apikey', // 'oauth' or 'apikey' - controls which SignIn component to use
|
|
15
16
|
headers,
|
|
17
|
+
isSidebarOpen: true, // Shared sidebar state (default open for lg+ screens)
|
|
16
18
|
|
|
17
19
|
resolveUrl(url){
|
|
18
20
|
return url.startsWith('http') || url.startsWith('/v1') ? url : base + url
|
|
@@ -50,26 +52,88 @@ export const o = {
|
|
|
50
52
|
this.auth = auth
|
|
51
53
|
if (auth?.apiKey) {
|
|
52
54
|
this.headers.Authorization = `Bearer ${auth.apiKey}`
|
|
53
|
-
|
|
55
|
+
//localStorage.setItem('llms:auth', JSON.stringify({ apiKey: auth.apiKey }))
|
|
56
|
+
} else if (auth?.sessionToken) {
|
|
57
|
+
this.headers['X-Session-Token'] = auth.sessionToken
|
|
58
|
+
localStorage.setItem('llms:auth', JSON.stringify({ sessionToken: auth.sessionToken }))
|
|
59
|
+
} else {
|
|
60
|
+
if (this.headers.Authorization) {
|
|
61
|
+
delete this.headers.Authorization
|
|
62
|
+
}
|
|
63
|
+
if (this.headers['X-Session-Token']) {
|
|
64
|
+
delete this.headers['X-Session-Token']
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
async signOut() {
|
|
69
|
+
if (this.auth?.sessionToken) {
|
|
70
|
+
// Call logout endpoint for OAuth sessions
|
|
71
|
+
try {
|
|
72
|
+
await this.post('/auth/logout', {
|
|
73
|
+
headers: {
|
|
74
|
+
'X-Session-Token': this.auth.sessionToken
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('Logout error:', error)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
this.auth = null
|
|
82
|
+
if (this.headers.Authorization) {
|
|
54
83
|
delete this.headers.Authorization
|
|
55
84
|
}
|
|
85
|
+
if (this.headers['X-Session-Token']) {
|
|
86
|
+
delete this.headers['X-Session-Token']
|
|
87
|
+
}
|
|
88
|
+
localStorage.removeItem('llms:auth')
|
|
56
89
|
},
|
|
57
90
|
async init() {
|
|
58
91
|
// Load models and prompts
|
|
59
92
|
const { initDB } = useThreadStore()
|
|
60
|
-
const [_, configRes, modelsRes
|
|
93
|
+
const [_, configRes, modelsRes] = await Promise.all([
|
|
61
94
|
initDB(),
|
|
62
95
|
this.getConfig(),
|
|
63
96
|
this.getModels(),
|
|
64
|
-
this.getAuth(),
|
|
65
97
|
])
|
|
66
98
|
const config = await configRes.json()
|
|
67
99
|
const models = await modelsRes.json()
|
|
68
|
-
|
|
100
|
+
|
|
101
|
+
// Update auth settings from server config
|
|
102
|
+
if (config.requiresAuth != null) {
|
|
103
|
+
this.requiresAuth = config.requiresAuth
|
|
104
|
+
}
|
|
105
|
+
if (config.authType != null) {
|
|
106
|
+
this.authType = config.authType
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Try to restore session from localStorage
|
|
110
|
+
if (this.requiresAuth) {
|
|
111
|
+
const storedAuth = localStorage.getItem('llms:auth')
|
|
112
|
+
if (storedAuth) {
|
|
113
|
+
try {
|
|
114
|
+
const authData = JSON.parse(storedAuth)
|
|
115
|
+
if (authData.sessionToken) {
|
|
116
|
+
this.headers['X-Session-Token'] = authData.sessionToken
|
|
117
|
+
}
|
|
118
|
+
// else if (authData.apiKey) {
|
|
119
|
+
// this.headers.Authorization = `Bearer ${authData.apiKey}`
|
|
120
|
+
// }
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.error('Failed to restore auth from localStorage:', e)
|
|
123
|
+
localStorage.removeItem('llms:auth')
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Get auth status
|
|
129
|
+
const authRes = await this.getAuth()
|
|
130
|
+
const auth = this.requiresAuth
|
|
69
131
|
? await authRes.json()
|
|
70
132
|
: null
|
|
71
133
|
if (auth?.responseStatus?.errorCode) {
|
|
72
134
|
console.error(auth.responseStatus.errorCode, auth.responseStatus.message)
|
|
135
|
+
// Clear invalid session from localStorage
|
|
136
|
+
localStorage.removeItem('llms:auth')
|
|
73
137
|
} else {
|
|
74
138
|
this.signIn(auth)
|
|
75
139
|
}
|