llms-py 2.0.28__py3-none-any.whl → 2.0.30__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/main.py +74 -4
- llms/ui/Analytics.mjs +50 -47
- llms/ui/App.mjs +76 -4
- llms/ui/Brand.mjs +27 -9
- llms/ui/ChatPrompt.mjs +53 -7
- llms/ui/Main.mjs +19 -4
- llms/ui/ModelSelector.mjs +26 -7
- llms/ui/Sidebar.mjs +7 -2
- llms/ui/SystemPromptSelector.mjs +22 -2
- llms/ui/ai.mjs +2 -1
- llms/ui/app.css +280 -0
- llms/ui/lib/servicestack-vue.mjs +4 -4
- {llms_py-2.0.28.dist-info → llms_py-2.0.30.dist-info}/METADATA +3 -3
- {llms_py-2.0.28.dist-info → llms_py-2.0.30.dist-info}/RECORD +18 -18
- {llms_py-2.0.28.dist-info → llms_py-2.0.30.dist-info}/WHEEL +0 -0
- {llms_py-2.0.28.dist-info → llms_py-2.0.30.dist-info}/entry_points.txt +0 -0
- {llms_py-2.0.28.dist-info → llms_py-2.0.30.dist-info}/licenses/LICENSE +0 -0
- {llms_py-2.0.28.dist-info → llms_py-2.0.30.dist-info}/top_level.txt +0 -0
llms/ui/Main.mjs
CHANGED
|
@@ -30,11 +30,13 @@ export default {
|
|
|
30
30
|
template: `
|
|
31
31
|
<div class="flex flex-col h-full w-full">
|
|
32
32
|
<!-- Header with model and prompt selectors (hidden when auth required and not authenticated) -->
|
|
33
|
-
<div v-if="!($ai.requiresAuth && !$ai.auth)"
|
|
34
|
-
|
|
33
|
+
<div v-if="!($ai.requiresAuth && !$ai.auth)"
|
|
34
|
+
:class="!$ai.isSidebarOpen ? 'pl-6' : ''"
|
|
35
|
+
class="border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-2 py-2 w-full min-h-16">
|
|
36
|
+
<div class="flex flex-wrap items-center justify-between w-full">
|
|
35
37
|
<ModelSelector :models="models" v-model="selectedModel" @updated="configUpdated" />
|
|
36
38
|
|
|
37
|
-
<div class="flex items-center space-x-2">
|
|
39
|
+
<div class="flex items-center space-x-2 pl-4">
|
|
38
40
|
<SystemPromptSelector :prompts="prompts" v-model="selectedPrompt"
|
|
39
41
|
:show="showSystemPrompt" @toggle="showSystemPrompt = !showSystemPrompt" />
|
|
40
42
|
<Avatar />
|
|
@@ -218,7 +220,7 @@ export default {
|
|
|
218
220
|
</div>
|
|
219
221
|
|
|
220
222
|
<!-- Loading indicator -->
|
|
221
|
-
<div v-if="isGenerating" class="flex items-start space-x-3">
|
|
223
|
+
<div v-if="isGenerating" class="flex items-start space-x-3 group">
|
|
222
224
|
<!-- Avatar outside the bubble -->
|
|
223
225
|
<div class="flex-shrink-0">
|
|
224
226
|
<div class="w-8 h-8 rounded-full bg-gray-600 dark:bg-gray-500 text-white flex items-center justify-center text-sm font-medium">
|
|
@@ -234,6 +236,13 @@ export default {
|
|
|
234
236
|
<div class="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
|
|
235
237
|
</div>
|
|
236
238
|
</div>
|
|
239
|
+
|
|
240
|
+
<!-- Cancel button -->
|
|
241
|
+
<button type="button" @click="cancelRequest"
|
|
242
|
+
class="px-3 py-1 rounded text-sm 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 border border-transparent hover:border-red-300 dark:hover:border-red-600 transition-all"
|
|
243
|
+
title="Cancel request">
|
|
244
|
+
cancel
|
|
245
|
+
</button>
|
|
237
246
|
</div>
|
|
238
247
|
|
|
239
248
|
<!-- Error message bubble -->
|
|
@@ -741,6 +750,11 @@ export default {
|
|
|
741
750
|
editingMessage.value = null
|
|
742
751
|
}
|
|
743
752
|
|
|
753
|
+
// Cancel pending request
|
|
754
|
+
const cancelRequest = () => {
|
|
755
|
+
chatPrompt.cancel()
|
|
756
|
+
}
|
|
757
|
+
|
|
744
758
|
function tokensTitle(usage) {
|
|
745
759
|
let title = []
|
|
746
760
|
if (usage.tokens && usage.price) {
|
|
@@ -790,6 +804,7 @@ export default {
|
|
|
790
804
|
editMessage,
|
|
791
805
|
saveEditedMessage,
|
|
792
806
|
cancelEdit,
|
|
807
|
+
cancelRequest,
|
|
793
808
|
configUpdated,
|
|
794
809
|
exportThreads,
|
|
795
810
|
exportRequests,
|
llms/ui/ModelSelector.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ref, onMounted, onUnmounted } from "vue"
|
|
1
2
|
import ProviderStatus from "./ProviderStatus.mjs"
|
|
2
3
|
import ProviderIcon from "./ProviderIcon.mjs"
|
|
3
4
|
|
|
@@ -9,15 +10,16 @@ export default {
|
|
|
9
10
|
template:`
|
|
10
11
|
<!-- Model Selector -->
|
|
11
12
|
<div class="pl-1 flex space-x-2">
|
|
12
|
-
<Autocomplete id="model" :options="models" label=""
|
|
13
|
+
<Autocomplete ref="refSelector" id="model" :options="models" label=""
|
|
13
14
|
:modelValue="modelValue" @update:modelValue="$emit('update:modelValue', $event)"
|
|
14
15
|
class="w-72 xl:w-84"
|
|
15
16
|
:match="(x, value) => x.id.toLowerCase().includes(value.toLowerCase())"
|
|
16
17
|
placeholder="Select Model...">
|
|
17
18
|
<template #item="{ id, provider, provider_model, pricing }">
|
|
18
|
-
<div :key="id + provider + provider_model"
|
|
19
|
+
<div :key="id + provider + provider_model"
|
|
20
|
+
class="group truncate max-w-68 xl:max-w-72 flex justify-between">
|
|
19
21
|
<span :title="id">{{id}}</span>
|
|
20
|
-
<
|
|
22
|
+
<div class="hidden md:flex items-center space-x-1">
|
|
21
23
|
<span v-if="pricing && (parseFloat(pricing.input) == 0 && parseFloat(pricing.input) == 0)">
|
|
22
24
|
<span class="text-xs text-gray-500 dark:text-gray-400" title="Free to use">FREE</span>
|
|
23
25
|
</span>
|
|
@@ -27,10 +29,10 @@ export default {
|
|
|
27
29
|
·
|
|
28
30
|
{{tokenPrice(pricing.output)}} M
|
|
29
31
|
</span>
|
|
30
|
-
<span :title="provider_model + ' from ' + provider">
|
|
31
|
-
<ProviderIcon :provider="provider" />
|
|
32
|
+
<span class="min-w-6" :title="provider_model + ' from ' + provider">
|
|
33
|
+
<ProviderIcon class="hidden xl:inline" :provider="provider" />
|
|
32
34
|
</span>
|
|
33
|
-
</
|
|
35
|
+
</div>
|
|
34
36
|
</div>
|
|
35
37
|
</template>
|
|
36
38
|
</Autocomplete>
|
|
@@ -52,8 +54,25 @@ export default {
|
|
|
52
54
|
return ret.endsWith('.00') ? ret.slice(0, -3) : ret
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
const refSelector = ref()
|
|
58
|
+
|
|
59
|
+
function collapse(e) {
|
|
60
|
+
// call toggle when clicking outside of the Autocomplete component
|
|
61
|
+
if (refSelector.value && !refSelector.value.$el.contains(e.target)) {
|
|
62
|
+
refSelector.value.toggle(false)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
onMounted(() => {
|
|
67
|
+
document.addEventListener('click', collapse)
|
|
68
|
+
})
|
|
69
|
+
onUnmounted(() => {
|
|
70
|
+
document.removeEventListener('click', collapse)
|
|
71
|
+
})
|
|
72
|
+
|
|
55
73
|
return {
|
|
56
|
-
|
|
74
|
+
refSelector,
|
|
75
|
+
tokenPrice,
|
|
57
76
|
}
|
|
58
77
|
}
|
|
59
78
|
}
|
llms/ui/Sidebar.mjs
CHANGED
|
@@ -164,7 +164,7 @@ const Sidebar = {
|
|
|
164
164
|
},
|
|
165
165
|
template: `
|
|
166
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" />
|
|
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
170
|
<div v-if="isLoading" class="p-4 text-center text-gray-500 dark:text-gray-400">
|
|
@@ -187,7 +187,8 @@ const Sidebar = {
|
|
|
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/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
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
|
|
|
7
|
-
<Autocomplete id="prompt" :options="prompts" label=""
|
|
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 }">
|
|
@@ -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/ai.mjs
CHANGED
|
@@ -6,7 +6,7 @@ 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.30',
|
|
10
10
|
base,
|
|
11
11
|
prefsKey,
|
|
12
12
|
welcome: 'Welcome to llms.py',
|
|
@@ -14,6 +14,7 @@ export const o = {
|
|
|
14
14
|
requiresAuth: false,
|
|
15
15
|
authType: 'apikey', // 'oauth' or 'apikey' - controls which SignIn component to use
|
|
16
16
|
headers,
|
|
17
|
+
isSidebarOpen: true, // Shared sidebar state (default open for lg+ screens)
|
|
17
18
|
|
|
18
19
|
resolveUrl(url){
|
|
19
20
|
return url.startsWith('http') || url.startsWith('/v1') ? url : base + url
|