llms-py 3.0.0b2__py3-none-any.whl → 3.0.0b3__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.
Files changed (51) hide show
  1. llms/__pycache__/main.cpython-314.pyc +0 -0
  2. llms/index.html +2 -1
  3. llms/llms.json +50 -17
  4. llms/main.py +484 -544
  5. llms/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
  6. llms/providers/__pycache__/chutes.cpython-314.pyc +0 -0
  7. llms/providers/__pycache__/google.cpython-314.pyc +0 -0
  8. llms/providers/__pycache__/nvidia.cpython-314.pyc +0 -0
  9. llms/providers/__pycache__/openai.cpython-314.pyc +0 -0
  10. llms/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
  11. llms/providers/anthropic.py +189 -0
  12. llms/providers/chutes.py +152 -0
  13. llms/providers/google.py +306 -0
  14. llms/providers/nvidia.py +107 -0
  15. llms/providers/openai.py +159 -0
  16. llms/providers/openrouter.py +70 -0
  17. llms/providers-extra.json +356 -0
  18. llms/providers.json +1 -1
  19. llms/ui/App.mjs +132 -60
  20. llms/ui/ai.mjs +76 -10
  21. llms/ui/app.css +1 -4962
  22. llms/ui/ctx.mjs +196 -0
  23. llms/ui/index.mjs +75 -171
  24. llms/ui/lib/charts.mjs +9 -13
  25. llms/ui/markdown.mjs +6 -0
  26. llms/ui/{Analytics.mjs → modules/analytics.mjs} +76 -64
  27. llms/ui/{Main.mjs → modules/chat/ChatBody.mjs} +56 -133
  28. llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +8 -8
  29. llms/ui/{ChatPrompt.mjs → modules/chat/index.mjs} +239 -45
  30. llms/ui/modules/layout.mjs +267 -0
  31. llms/ui/modules/model-selector.mjs +851 -0
  32. llms/ui/{Recents.mjs → modules/threads/Recents.mjs} +0 -2
  33. llms/ui/{Sidebar.mjs → modules/threads/index.mjs} +46 -44
  34. llms/ui/{threadStore.mjs → modules/threads/threadStore.mjs} +10 -7
  35. llms/ui/utils.mjs +82 -123
  36. {llms_py-3.0.0b2.dist-info → llms_py-3.0.0b3.dist-info}/METADATA +1 -1
  37. llms_py-3.0.0b3.dist-info/RECORD +65 -0
  38. llms/ui/Avatar.mjs +0 -86
  39. llms/ui/Brand.mjs +0 -52
  40. llms/ui/OAuthSignIn.mjs +0 -61
  41. llms/ui/ProviderIcon.mjs +0 -36
  42. llms/ui/ProviderStatus.mjs +0 -104
  43. llms/ui/SignIn.mjs +0 -65
  44. llms/ui/Welcome.mjs +0 -8
  45. llms/ui/model-selector.mjs +0 -686
  46. llms/ui.json +0 -1069
  47. llms_py-3.0.0b2.dist-info/RECORD +0 -58
  48. {llms_py-3.0.0b2.dist-info → llms_py-3.0.0b3.dist-info}/WHEEL +0 -0
  49. {llms_py-3.0.0b2.dist-info → llms_py-3.0.0b3.dist-info}/entry_points.txt +0 -0
  50. {llms_py-3.0.0b2.dist-info → llms_py-3.0.0b3.dist-info}/licenses/LICENSE +0 -0
  51. {llms_py-3.0.0b2.dist-info → llms_py-3.0.0b3.dist-info}/top_level.txt +0 -0
llms/ui/App.mjs CHANGED
@@ -1,42 +1,119 @@
1
- import { inject, ref, watch, onMounted, onUnmounted } from "vue"
1
+ import { ref, computed, watch, inject, onMounted, onUnmounted } from "vue"
2
2
  import { useRouter, useRoute } from "vue-router"
3
- import Sidebar from "./Sidebar.mjs"
3
+ import { AppContext } from "./ctx.mjs"
4
+
5
+ // Vertical Sidebar Icons
6
+ const LeftBar = {
7
+ template: `
8
+ <div class="flex flex-col space-y-2 pt-2.5 px-1">
9
+ <div v-for="(icon, id) in $ctx.left" :key="id" class="relative flex items-center justify-center">
10
+ <component :is="icon.component"
11
+ class="size-7 p-1 cursor-pointer text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 rounded block"
12
+ :class="{ 'bg-gray-200 dark:bg-gray-700' : icon.isActive({ ...$layout }) }"
13
+ @mouseenter="tooltip = icon.id"
14
+ @mouseleave="tooltip = ''"
15
+ />
16
+ <div v-if="tooltip === icon.id && !icon.isActive({ ...$layout })"
17
+ class="absolute left-full top-1/2 -translate-y-1/2 ml-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" style="z-index: 60">
18
+ {{icon.name}}
19
+ </div>
20
+ </div>
21
+ </div>
22
+ `,
23
+ setup() {
24
+ const tooltip = ref('')
25
+ return {
26
+ tooltip,
27
+ }
28
+ }
29
+ }
30
+
31
+ const LeftPanel = {
32
+ template: `
33
+ <div v-if="component" class="flex flex-col h-full border-r border-gray-200 dark:border-gray-700">
34
+ <button type="button" @click="$emit('toggle-sidebar')" class="absolute top-2 right-2 p-1 rounded-md text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 lg:hidden z-20">
35
+ <svg class="size-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
36
+ </button>
37
+ <component :is="component" />
38
+ </div>
39
+ `,
40
+ setup() {
41
+ /**@type {AppContext} */
42
+ const ctx = inject('ctx')
43
+ const component = computed(() => ctx.component(ctx.layout.left))
44
+ return {
45
+ component,
46
+ }
47
+ }
48
+ }
49
+
50
+ const TopBar = {
51
+ template: `
52
+ <div class="flex space-x-2">
53
+ <div v-for="(icon, id) in $ctx.top" :key="id" class="relative flex items-center justify-center">
54
+ <component :is="icon.component"
55
+ class="size-7 p-1 cursor-pointer text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 block"
56
+ :class="{ 'bg-gray-100 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded' : icon.isActive({ ...$layout }) }"
57
+ @mouseenter="tooltip = icon.id"
58
+ @mouseleave="tooltip = ''"
59
+ />
60
+ <div v-if="tooltip === icon.id && !icon.isActive({ ...$layout })"
61
+ 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"
62
+ :class="last2.includes(id) ? 'right-0' : 'left-1/2 -translate-x-1/2'">
63
+ {{icon.name}}
64
+ </div>
65
+ </div>
66
+ </div>
67
+ `,
68
+ setup() {
69
+ const tooltip = ref('')
70
+ const last2 = ref(Object.keys($ctx.top).slice(-2))
71
+ return {
72
+ tooltip,
73
+ last2,
74
+ }
75
+ }
76
+ }
77
+
78
+ const TopPanel = {
79
+ template: `
80
+ <component v-if="component" :is="component" class="mb-2" />
81
+ `,
82
+ setup() {
83
+ /**@type {AppContext} */
84
+ const ctx = inject('ctx')
85
+ const component = computed(() => ctx.component(ctx.layout.top))
86
+ return {
87
+ component,
88
+ }
89
+ }
90
+ }
4
91
 
5
92
  export default {
6
93
  components: {
7
- Sidebar,
94
+ LeftBar,
95
+ LeftPanel,
96
+ TopBar,
97
+ TopPanel,
8
98
  },
9
- props: ['config', 'models'],
10
- setup(props) {
99
+ setup() {
11
100
  const router = useRouter()
12
101
  const route = useRoute()
13
102
 
103
+ /**@type {AppContext} */
14
104
  const ctx = inject('ctx')
15
105
  const ai = ctx.ai
16
106
  const isMobile = ref(false)
17
107
  const modal = ref()
18
108
 
19
109
  const checkMobile = () => {
20
- const wasMobile = isMobile.value
21
- isMobile.value = window.innerWidth < 1024 // lg breakpoint
110
+ //const wasMobile = isMobile.value
111
+ isMobile.value = window.innerWidth < 640 // sm breakpoint
22
112
 
113
+ //console.log('checkMobile', wasMobile, isMobile.value)
23
114
  // Only auto-adjust sidebar state when transitioning between mobile/desktop
24
- if (wasMobile !== isMobile.value) {
25
- if (isMobile.value) {
26
- ai.isSidebarOpen = false
27
- } else {
28
- ai.isSidebarOpen = true
29
- }
30
- }
31
- }
32
-
33
- const toggleSidebar = () => {
34
- ai.isSidebarOpen = !ai.isSidebarOpen
35
- }
36
-
37
- const closeSidebar = () => {
38
115
  if (isMobile.value) {
39
- ai.isSidebarOpen = false
116
+ ctx.toggleLayout('left', false)
40
117
  }
41
118
  }
42
119
 
@@ -58,61 +135,56 @@ export default {
58
135
 
59
136
  watch(() => route.query.open, (newVal) => {
60
137
  modal.value = ctx.modalComponents[newVal]
61
- console.log('open', newVal)
138
+ console.log('open', newVal, modal.value)
62
139
  })
63
140
 
64
- return { ai, modal, isMobile, toggleSidebar, closeSidebar, closeModal }
141
+ watch(() => ctx.state.selectedModel, (newVal) => {
142
+ ctx.chat.setSelectedModel(ctx.chat.getModel(newVal))
143
+ })
144
+
145
+ return { ai, modal, isMobile, closeModal }
65
146
  },
66
147
  template: `
67
- <div class="flex h-screen bg-white dark:bg-gray-900">
148
+ <div class="flex h-screen">
68
149
  <!-- Mobile Overlay -->
69
150
  <div
70
- v-if="isMobile && ai.isSidebarOpen && !(ai.requiresAuth && !ai.auth)"
71
- @click="closeSidebar"
151
+ v-if="isMobile && $ctx.layoutVisible('left') && $ai.hasAccess"
152
+ @click="$ctx.toggleLayout('left')"
72
153
  class="fixed inset-0 bg-black/50 z-40 lg:hidden"
73
154
  ></div>
74
155
 
75
- <!-- Sidebar (hidden when auth required and not authenticated) -->
76
- <div
77
- v-if="!(ai.requiresAuth && !ai.auth) && ai.isSidebarOpen"
78
- :class="[
79
- 'transition-transform duration-300 ease-in-out z-50',
80
- 'w-72 xl:w-80 flex-shrink-0',
81
- 'lg:relative',
82
- 'fixed inset-y-0 left-0'
83
- ]"
84
- >
85
- <Sidebar @thread-selected="closeSidebar" @toggle-sidebar="toggleSidebar" />
156
+ <div id="sidebar" class="z-100 relative flex bg-gray-50 dark:bg-gray-800">
157
+ <LeftBar />
158
+ <LeftPanel
159
+ v-if="$ai.hasAccess && $ctx.layoutVisible('left')"
160
+ :class="[
161
+ 'transition-transform duration-300 ease-in-out z-50',
162
+ 'w-72 xl:w-80 flex-shrink-0',
163
+ 'lg:relative',
164
+ 'fixed inset-y-0 left-[2.25rem] lg:left-0',
165
+ 'bg-gray-50 dark:bg-gray-800'
166
+ ]"
167
+ @thread-selected="$ctx.toggleLayout('left',false)" @toggle-sidebar="$ctx.toggleLayout('left')"
168
+ />
86
169
  </div>
87
170
 
88
171
  <!-- Main Area -->
89
172
  <div class="flex-1 flex flex-col">
90
- <!-- Collapsed Sidebar Toggle Button -->
91
- <div
92
- v-if="!(ai.requiresAuth && !ai.auth) && !ai.isSidebarOpen"
93
- class="fixed top-4 left-0"
94
- >
95
- <button type="button"
96
- @click="toggleSidebar"
97
- class="group p-1 text-gray-500 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
98
- title="Open sidebar"
99
- >
100
- <div class="relative w-5 h-5">
101
- <!-- Default sidebar icon -->
102
- <svg class="absolute inset-0 group-hover:hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
103
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
104
- <line x1="9" y1="3" x2="9" y2="21"></line>
105
- </svg>
106
- <!-- Hover state: |→ icon -->
107
- <svg class="absolute inset-0 hidden group-hover:block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="m17.172 11l-4.657-4.657l1.414-1.414L21 12l-7.071 7.071l-1.414-1.414L17.172 13H8v-2zM4 19V5h2v14z"/></svg>
173
+ <div class="flex flex-col h-full w-full overflow-hidden">
174
+ <div class="py-1 pr-1 flex items-center justify-between shrink-0">
175
+ <div>
176
+ <ModelSelector :models="$state.models" v-model="$state.selectedModel" />
108
177
  </div>
109
- </button>
178
+ <TopBar />
179
+ </div>
180
+ <TopPanel class="shrink-0" />
181
+ <div class="flex-1 overflow-hidden min-h-0 flex flex-col">
182
+ <RouterView class="h-full" />
183
+ </div>
110
184
  </div>
111
-
112
- <RouterView />
113
185
  </div>
114
186
 
115
- <component v-if="modal" :is="modal" @done="closeModal" />
187
+ <component v-if="modal" :is="modal" class="!z-[200]" @done="closeModal" />
116
188
  </div>
117
189
  `,
118
190
  }
llms/ui/ai.mjs CHANGED
@@ -1,12 +1,11 @@
1
1
  import { reactive } from "vue"
2
- import { useThreadStore } from "./threadStore.mjs"
3
2
 
4
3
  const base = ''
5
4
  const headers = { 'Accept': 'application/json' }
6
5
  const prefsKey = 'llms.prefs'
7
6
 
8
7
  export const o = {
9
- version: '3.0.0b2',
8
+ version: '3.0.0b3',
10
9
  base,
11
10
  prefsKey,
12
11
  welcome: 'Welcome to llms.py',
@@ -15,6 +14,7 @@ export const o = {
15
14
  authType: 'apikey', // 'oauth' or 'apikey' - controls which SignIn component to use
16
15
  headers,
17
16
  isSidebarOpen: true, // Shared sidebar state (default open for lg+ screens)
17
+ cacheUrlInfo: {},
18
18
 
19
19
  get hasAccess() {
20
20
  return !this.requiresAuth || this.auth
@@ -29,6 +29,17 @@ export const o = {
29
29
  headers: Object.assign({}, this.headers, options?.headers),
30
30
  })
31
31
  },
32
+ async getJson(url, options) {
33
+ const res = await this.get(url, options)
34
+ let txt = ''
35
+ try {
36
+ txt = await res.text()
37
+ return JSON.parse(txt)
38
+ } catch (e) {
39
+ console.error('Failed to parse JSON from GET', url, e, txt)
40
+ return { responseStatus: { errorCode: 'Error', message: `GET failed: ${e.message ?? e}` } }
41
+ }
42
+ },
32
43
  post(url, options) {
33
44
  return fetch(this.resolveUrl(url), {
34
45
  method: 'POST',
@@ -36,6 +47,17 @@ export const o = {
36
47
  headers: Object.assign({ 'Content-Type': 'application/json' }, this.headers, options?.headers),
37
48
  })
38
49
  },
50
+ async postJson(url, options) {
51
+ const res = await this.post(url, options)
52
+ let txt = ''
53
+ try {
54
+ txt = await res.text()
55
+ return JSON.parse(txt)
56
+ } catch (e) {
57
+ console.error('Failed to parse JSON from POST', url, e, txt)
58
+ return { responseStatus: { errorCode: 'Error', message: `POST failed: ${e.message ?? e}` } }
59
+ }
60
+ },
39
61
 
40
62
  async getConfig() {
41
63
  return this.get('/config')
@@ -72,18 +94,17 @@ export const o = {
72
94
  if (this.headers.Authorization) {
73
95
  delete this.headers.Authorization
74
96
  }
75
- localStorage.removeItem('llms:auth')
76
97
  },
77
- async init() {
98
+ async init(ctx) {
78
99
  // Load models and prompts
79
- const { initDB } = useThreadStore()
80
- const [_, configRes, modelsRes] = await Promise.all([
81
- initDB(),
100
+ const [configRes, modelsRes, extensionsRes] = await Promise.all([
82
101
  this.getConfig(),
83
102
  this.getModels(),
103
+ this.get('/ext'),
84
104
  ])
85
105
  const config = await configRes.json()
86
106
  const models = await modelsRes.json()
107
+ const extensions = await extensionsRes.json()
87
108
 
88
109
  // Update auth settings from server config
89
110
  if (config.requiresAuth != null) {
@@ -100,14 +121,59 @@ export const o = {
100
121
  : null
101
122
  if (auth?.responseStatus?.errorCode) {
102
123
  console.error(auth.responseStatus.errorCode, auth.responseStatus.message)
103
- // Clear invalid session from localStorage
104
- localStorage.removeItem('llms:auth')
105
124
  } else {
106
125
  this.signIn(auth)
107
126
  }
108
- return { config, models, auth }
127
+ return { config, models, extensions, auth }
128
+ },
129
+
130
+
131
+ async uploadFile(file) {
132
+ const formData = new FormData()
133
+ formData.append('file', file)
134
+ const response = await fetch(this.resolveUrl('/upload'), {
135
+ method: 'POST',
136
+ body: formData
137
+ })
138
+ if (!response.ok) {
139
+ throw new Error(`Upload failed: ${response.statusText}`)
140
+ }
141
+ return response.json()
142
+ },
143
+
144
+
145
+ getCacheInfo(url) {
146
+ return this.cacheUrlInfo[url]
147
+ },
148
+ async fetchCacheInfos(urls) {
149
+ const infos = {}
150
+ const fetchInfos = []
151
+ for (const url of urls) {
152
+ const info = this.getCacheInfo(url)
153
+ if (info) {
154
+ infos[url] = info
155
+ } else {
156
+ fetchInfos.push(fetch(this.resolveUrl(url + "?info")))
157
+ }
158
+ }
159
+ const responses = await Promise.all(fetchInfos)
160
+ for (let i = 0; i < urls.length; i++) {
161
+ try {
162
+ const info = await responses[i].json()
163
+ this.setCacheInfo(urls[i], info)
164
+ infos[urls[i]] = info
165
+ } catch (e) {
166
+ console.error('Failed to fetch info for', urls[i], e)
167
+ }
168
+ }
169
+ return infos
170
+ },
171
+ setCacheInfo(url, info) {
172
+ this.cacheUrlInfo[url] = info
109
173
  }
174
+
110
175
  }
111
176
 
177
+
112
178
  let ai = reactive(o)
113
179
  export default ai