llms-py 2.0.34__py3-none-any.whl → 3.0.0__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 (207) hide show
  1. llms/__init__.py +3 -1
  2. llms/__pycache__/__init__.cpython-312.pyc +0 -0
  3. llms/__pycache__/__init__.cpython-313.pyc +0 -0
  4. llms/__pycache__/__init__.cpython-314.pyc +0 -0
  5. llms/__pycache__/__main__.cpython-312.pyc +0 -0
  6. llms/__pycache__/__main__.cpython-314.pyc +0 -0
  7. llms/__pycache__/llms.cpython-312.pyc +0 -0
  8. llms/__pycache__/main.cpython-312.pyc +0 -0
  9. llms/__pycache__/main.cpython-313.pyc +0 -0
  10. llms/__pycache__/main.cpython-314.pyc +0 -0
  11. llms/__pycache__/plugins.cpython-314.pyc +0 -0
  12. llms/{ui/Analytics.mjs → extensions/analytics/ui/index.mjs} +154 -238
  13. llms/extensions/app/README.md +20 -0
  14. llms/extensions/app/__init__.py +530 -0
  15. llms/extensions/app/__pycache__/__init__.cpython-314.pyc +0 -0
  16. llms/extensions/app/__pycache__/db.cpython-314.pyc +0 -0
  17. llms/extensions/app/__pycache__/db_manager.cpython-314.pyc +0 -0
  18. llms/extensions/app/db.py +644 -0
  19. llms/extensions/app/db_manager.py +195 -0
  20. llms/extensions/app/requests.json +9073 -0
  21. llms/extensions/app/threads.json +15290 -0
  22. llms/{ui → extensions/app/ui}/Recents.mjs +91 -65
  23. llms/{ui/Sidebar.mjs → extensions/app/ui/index.mjs} +124 -58
  24. llms/extensions/app/ui/threadStore.mjs +411 -0
  25. llms/extensions/core_tools/CALCULATOR.md +32 -0
  26. llms/extensions/core_tools/__init__.py +598 -0
  27. llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
  28. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
  29. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
  30. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
  31. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
  32. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
  33. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
  34. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
  35. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
  36. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
  37. llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
  38. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  39. llms/extensions/core_tools/ui/codemirror/lib/codemirror.css +344 -0
  40. llms/extensions/core_tools/ui/codemirror/lib/codemirror.js +9884 -0
  41. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
  42. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
  43. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
  44. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
  45. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
  46. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
  47. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
  48. llms/extensions/core_tools/ui/index.mjs +650 -0
  49. llms/extensions/gallery/README.md +61 -0
  50. llms/extensions/gallery/__init__.py +61 -0
  51. llms/extensions/gallery/__pycache__/__init__.cpython-314.pyc +0 -0
  52. llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
  53. llms/extensions/gallery/db.py +298 -0
  54. llms/extensions/gallery/ui/index.mjs +482 -0
  55. llms/extensions/katex/README.md +39 -0
  56. llms/extensions/katex/__init__.py +6 -0
  57. llms/extensions/katex/__pycache__/__init__.cpython-314.pyc +0 -0
  58. llms/extensions/katex/ui/README.md +125 -0
  59. llms/extensions/katex/ui/contrib/auto-render.js +338 -0
  60. llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
  61. llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
  62. llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
  63. llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
  64. llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
  65. llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
  66. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
  67. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
  68. llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
  69. llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
  70. llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
  71. llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
  72. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
  73. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
  74. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  75. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  76. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  77. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  78. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  79. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  80. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  81. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  82. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  83. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  84. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  85. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  86. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  87. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  88. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  89. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  90. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  91. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  92. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  93. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  94. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  95. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  96. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  97. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  98. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  99. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  100. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  101. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  102. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  103. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  104. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  115. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  116. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  117. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  118. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  119. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  120. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  121. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  122. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  123. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  124. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  125. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  126. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  127. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  128. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  129. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  130. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  131. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  132. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  133. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  134. llms/extensions/katex/ui/index.mjs +92 -0
  135. llms/extensions/katex/ui/katex-swap.css +1230 -0
  136. llms/extensions/katex/ui/katex-swap.min.css +1 -0
  137. llms/extensions/katex/ui/katex.css +1230 -0
  138. llms/extensions/katex/ui/katex.js +19080 -0
  139. llms/extensions/katex/ui/katex.min.css +1 -0
  140. llms/extensions/katex/ui/katex.min.js +1 -0
  141. llms/extensions/katex/ui/katex.min.mjs +1 -0
  142. llms/extensions/katex/ui/katex.mjs +18547 -0
  143. llms/extensions/providers/__init__.py +18 -0
  144. llms/extensions/providers/__pycache__/__init__.cpython-314.pyc +0 -0
  145. llms/extensions/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
  146. llms/extensions/providers/__pycache__/chutes.cpython-314.pyc +0 -0
  147. llms/extensions/providers/__pycache__/google.cpython-314.pyc +0 -0
  148. llms/extensions/providers/__pycache__/nvidia.cpython-314.pyc +0 -0
  149. llms/extensions/providers/__pycache__/openai.cpython-314.pyc +0 -0
  150. llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
  151. llms/extensions/providers/anthropic.py +229 -0
  152. llms/extensions/providers/chutes.py +155 -0
  153. llms/extensions/providers/google.py +378 -0
  154. llms/extensions/providers/nvidia.py +105 -0
  155. llms/extensions/providers/openai.py +156 -0
  156. llms/extensions/providers/openrouter.py +72 -0
  157. llms/extensions/system_prompts/README.md +22 -0
  158. llms/extensions/system_prompts/__init__.py +45 -0
  159. llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc +0 -0
  160. llms/extensions/system_prompts/ui/index.mjs +280 -0
  161. llms/extensions/system_prompts/ui/prompts.json +1067 -0
  162. llms/extensions/tools/__init__.py +5 -0
  163. llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  164. llms/extensions/tools/ui/index.mjs +204 -0
  165. llms/index.html +35 -77
  166. llms/llms.json +357 -1186
  167. llms/main.py +2847 -999
  168. llms/providers-extra.json +356 -0
  169. llms/providers.json +1 -0
  170. llms/ui/App.mjs +151 -60
  171. llms/ui/ai.mjs +132 -60
  172. llms/ui/app.css +2173 -161
  173. llms/ui/ctx.mjs +365 -0
  174. llms/ui/index.mjs +129 -0
  175. llms/ui/lib/charts.mjs +9 -13
  176. llms/ui/lib/servicestack-vue.mjs +3 -3
  177. llms/ui/lib/vue.min.mjs +10 -9
  178. llms/ui/lib/vue.mjs +1796 -1635
  179. llms/ui/markdown.mjs +18 -7
  180. llms/ui/modules/chat/ChatBody.mjs +691 -0
  181. llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +9 -9
  182. llms/ui/modules/chat/index.mjs +828 -0
  183. llms/ui/modules/layout.mjs +243 -0
  184. llms/ui/modules/model-selector.mjs +851 -0
  185. llms/ui/tailwind.input.css +496 -80
  186. llms/ui/utils.mjs +161 -93
  187. {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/METADATA +1 -1
  188. llms_py-3.0.0.dist-info/RECORD +202 -0
  189. llms/ui/Avatar.mjs +0 -85
  190. llms/ui/Brand.mjs +0 -52
  191. llms/ui/ChatPrompt.mjs +0 -590
  192. llms/ui/Main.mjs +0 -823
  193. llms/ui/ModelSelector.mjs +0 -78
  194. llms/ui/OAuthSignIn.mjs +0 -92
  195. llms/ui/ProviderIcon.mjs +0 -30
  196. llms/ui/ProviderStatus.mjs +0 -105
  197. llms/ui/SignIn.mjs +0 -64
  198. llms/ui/SystemPromptEditor.mjs +0 -31
  199. llms/ui/SystemPromptSelector.mjs +0 -56
  200. llms/ui/Welcome.mjs +0 -8
  201. llms/ui/threadStore.mjs +0 -563
  202. llms/ui.json +0 -1069
  203. llms_py-2.0.34.dist-info/RECORD +0 -48
  204. {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/WHEEL +0 -0
  205. {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/entry_points.txt +0 -0
  206. {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/licenses/LICENSE +0 -0
  207. {llms_py-2.0.34.dist-info → llms_py-3.0.0.dist-info}/top_level.txt +0 -0
llms/ui/App.mjs CHANGED
@@ -1,97 +1,188 @@
1
- import { inject, ref, onMounted, onUnmounted } from "vue"
2
- import Sidebar from "./Sidebar.mjs"
1
+ import { ref, computed, watch, inject, onMounted, onUnmounted } from "vue"
2
+ import { useRouter, useRoute } from "vue-router"
3
+ import { AppContext } from "./ctx.mjs"
4
+
5
+ // Vertical Sidebar Icons
6
+ const LeftBar = {
7
+ template: `
8
+ <div class="select-none 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.title ?? 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" 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="select-none flex space-x-1">
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 border border-transparent"
56
+ :class="{ 'bg-gray-100 dark:bg-gray-800 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.title ?? 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
+ }
3
91
 
4
92
  export default {
5
93
  components: {
6
- Sidebar,
94
+ LeftBar,
95
+ LeftPanel,
96
+ TopBar,
97
+ TopPanel,
7
98
  },
8
99
  setup() {
9
- const ai = inject('ai')
100
+ const router = useRouter()
101
+ const route = useRoute()
102
+
103
+ /**@type {AppContext} */
104
+ const ctx = inject('ctx')
105
+ const ai = ctx.ai
10
106
  const isMobile = ref(false)
107
+ const modal = ref()
11
108
 
12
109
  const checkMobile = () => {
13
- const wasMobile = isMobile.value
14
- isMobile.value = window.innerWidth < 1024 // lg breakpoint
110
+ //const wasMobile = isMobile.value
111
+ isMobile.value = window.innerWidth < 640 // sm breakpoint
15
112
 
113
+ //console.log('checkMobile', wasMobile, isMobile.value)
16
114
  // Only auto-adjust sidebar state when transitioning between mobile/desktop
17
- if (wasMobile !== isMobile.value) {
18
- if (isMobile.value) {
19
- ai.isSidebarOpen = false
20
- } else {
21
- ai.isSidebarOpen = true
22
- }
23
- }
24
- }
25
-
26
- const toggleSidebar = () => {
27
- ai.isSidebarOpen = !ai.isSidebarOpen
28
- }
29
-
30
- const closeSidebar = () => {
31
115
  if (isMobile.value) {
32
- ai.isSidebarOpen = false
116
+ ctx.toggleLayout('left', false)
33
117
  }
34
118
  }
35
119
 
36
120
  onMounted(() => {
37
121
  checkMobile()
38
122
  window.addEventListener('resize', checkMobile)
123
+ if (route.query.open) {
124
+ modal.value = ctx.openModal(route.query.open)
125
+ }
39
126
  })
40
127
 
41
128
  onUnmounted(() => {
42
129
  window.removeEventListener('resize', checkMobile)
43
130
  })
44
131
 
45
- return { ai, isMobile, toggleSidebar, closeSidebar }
132
+ function closeModal() {
133
+ ctx.closeModal(route.query.open)
134
+ }
135
+
136
+ watch(() => route.query.open, (newVal) => {
137
+ modal.value = ctx.modalComponents[newVal]
138
+ console.log('open', newVal, modal.value)
139
+ })
140
+
141
+ watch(() => ctx.state.selectedModel, (newVal) => {
142
+ ctx.chat.setSelectedModel(ctx.chat.getModel(newVal))
143
+ })
144
+
145
+ return { ai, modal, isMobile, closeModal }
46
146
  },
47
147
  template: `
48
- <div class="flex h-screen bg-white dark:bg-gray-900">
148
+ <div class="flex h-screen">
49
149
  <!-- Mobile Overlay -->
50
- <div
51
- v-if="isMobile && ai.isSidebarOpen && !(ai.requiresAuth && !ai.auth)"
52
- @click="closeSidebar"
53
- class="fixed inset-0 bg-black/50 z-40 lg:hidden"
150
+ <div v-if="isMobile && $ctx.layoutVisible('left') && $ai.hasAccess"
151
+ @click="$ctx.toggleLayout('left')"
152
+ :class="$ctx.cls('mobile-overlay', 'fixed inset-0 bg-black/50 z-40 lg:hidden')"
54
153
  ></div>
55
154
 
56
- <!-- Sidebar (hidden when auth required and not authenticated) -->
57
- <div
58
- v-if="!(ai.requiresAuth && !ai.auth) && ai.isSidebarOpen"
59
- :class="[
60
- 'transition-transform duration-300 ease-in-out z-50',
61
- 'w-72 xl:w-80 flex-shrink-0',
62
- 'lg:relative',
63
- 'fixed inset-y-0 left-0'
64
- ]"
65
- >
66
- <Sidebar @thread-selected="closeSidebar" @toggle-sidebar="toggleSidebar" />
155
+ <div v-if="$ai.hasAccess" id="sidebar" :class="$ctx.cls('sidebar', 'z-100 relative flex bg-gray-50 dark:bg-gray-800')">
156
+ <LeftBar id="left-bar" />
157
+ <LeftPanel id="left-panel"
158
+ v-if="$ai.hasAccess && $ctx.layoutVisible('left')"
159
+ :class="[
160
+ 'transition-transform duration-300 ease-in-out z-50',
161
+ 'w-72 xl:w-80 flex-shrink-0',
162
+ 'lg:relative',
163
+ 'fixed inset-y-0 left-[2.25rem] lg:left-0',
164
+ 'bg-gray-50 dark:bg-gray-800'
165
+ ]"
166
+ />
67
167
  </div>
68
168
 
69
169
  <!-- Main Area -->
70
- <div class="flex-1 flex flex-col">
71
- <!-- Collapsed Sidebar Toggle Button -->
72
- <div
73
- v-if="!(ai.requiresAuth && !ai.auth) && !ai.isSidebarOpen"
74
- class="fixed top-4 left-0"
75
- >
76
- <button type="button"
77
- @click="toggleSidebar"
78
- 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"
79
- title="Open sidebar"
80
- >
81
- <div class="relative w-5 h-5">
82
- <!-- Default sidebar icon -->
83
- <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">
84
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
85
- <line x1="9" y1="3" x2="9" y2="21"></line>
86
- </svg>
87
- <!-- Hover state: |→ icon -->
88
- <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>
170
+ <div id="main" :class="$ctx.cls('main', 'flex-1 flex flex-col')">
171
+ <div id="main-inner" :class="$ctx.cls('main-inner', 'flex flex-col h-full w-full overflow-hidden')">
172
+ <div v-if="$ai.hasAccess" id="header" :class="$ctx.cls('header', 'py-1 pr-1 flex items-center justify-between shrink-0')">
173
+ <div>
174
+ <ModelSelector :models="$state.models" v-model="$state.selectedModel" />
89
175
  </div>
90
- </button>
176
+ <TopBar id="top-bar" />
177
+ </div>
178
+ <TopPanel v-if="$ai.hasAccess" id="top-panel" :class="$ctx.cls('top-panel', 'shrink-0')" />
179
+ <div id="page" :class="$ctx.cls('page', 'flex-1 overflow-y-auto min-h-0 flex flex-col')">
180
+ <RouterView class="h-full" />
181
+ </div>
91
182
  </div>
92
-
93
- <RouterView />
94
183
  </div>
184
+
185
+ <component v-if="modal" :is="modal" :class="$ctx.cls('modal', '!z-[200]')" @done="closeModal" />
95
186
  </div>
96
187
  `,
97
188
  }
llms/ui/ai.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  import { reactive } from "vue"
2
- import { useThreadStore } from "./threadStore.mjs"
2
+ import { ApiResult } from "@servicestack/client"
3
3
 
4
4
  const base = ''
5
5
  const headers = { 'Accept': 'application/json' }
6
6
  const prefsKey = 'llms.prefs'
7
7
 
8
8
  export const o = {
9
- version: '2.0.34',
9
+ version: '3.0.0',
10
10
  base,
11
11
  prefsKey,
12
12
  welcome: 'Welcome to llms.py',
@@ -15,24 +15,88 @@ export const o = {
15
15
  authType: 'apikey', // 'oauth' or 'apikey' - controls which SignIn component to use
16
16
  headers,
17
17
  isSidebarOpen: true, // Shared sidebar state (default open for lg+ screens)
18
-
19
- resolveUrl(url){
18
+ cacheUrlInfo: {},
19
+
20
+ get hasAccess() {
21
+ return !this.requiresAuth || this.auth
22
+ },
23
+
24
+ resolveUrl(url) {
20
25
  return url.startsWith('http') || url.startsWith('/v1') ? url : base + url
21
26
  },
22
27
  get(url, options) {
23
- return fetch(this.resolveUrl(url), {
28
+ return fetch(this.resolveUrl(url), {
24
29
  ...options,
25
30
  headers: Object.assign({}, this.headers, options?.headers),
26
31
  })
27
32
  },
28
- post(url, options) {
29
- return fetch(this.resolveUrl(url), {
33
+ async getJson(url, options) {
34
+ const res = await this.get(url, options)
35
+ return await this.createJsonResult(res, url)
36
+ },
37
+ async post(url, options) {
38
+ return await fetch(this.resolveUrl(url), {
30
39
  method: 'POST',
31
40
  ...options,
32
- headers: Object.assign({'Content-Type': 'application/json'}, this.headers, options?.headers),
41
+ headers: Object.assign({ 'Content-Type': 'application/json' }, this.headers, options?.headers),
42
+ })
43
+ },
44
+ async postForm(url, options) {
45
+ return await fetch(this.resolveUrl(url), {
46
+ method: 'POST',
47
+ ...options,
48
+ headers: Object.assign({}, options?.headers),
49
+ })
50
+ },
51
+ async postJson(url, options) {
52
+ const res = await this.post(url, options)
53
+ return await this.createJsonResult(res, url)
54
+ },
55
+ async createJsonResult(res, msg = null) {
56
+ let txt = ''
57
+ try {
58
+ txt = await res.text()
59
+ const response = JSON.parse(txt)
60
+ if (response?.responseStatus?.errorCode) {
61
+ return new ApiResult({ error: response.responseStatus })
62
+ }
63
+ if (!res.ok) {
64
+ return new ApiResult({ error: { errorCode: 'Error', message: res.statusText } })
65
+ }
66
+ return new ApiResult({ response })
67
+ } catch (e) {
68
+ console.error('Failed to parse JSON', e, msg, txt)
69
+ const responseStatus = {
70
+ errorCode: 'Error',
71
+ message: `${e.message ?? e}`,
72
+ stackTrace: msg ? `${msg}\n${txt}` : txt,
73
+ }
74
+ return { responseStatus }
75
+ }
76
+ },
77
+ createErrorStatus({ message, errorCode, stackTrace, errors, meta }) {
78
+ const ret = {
79
+ errorCode: errorCode || 'Error',
80
+ message: message,
81
+ }
82
+ if (stackTrace) {
83
+ ret.stackTrace = stackTrace
84
+ }
85
+ if (errors && Array.isArray(errors)) {
86
+ ret.errors = errors
87
+ }
88
+ if (meta) {
89
+ ret.meta = meta
90
+ }
91
+ return ret
92
+ },
93
+ createErrorResult(e) {
94
+ return new ApiResult({
95
+ error: e.errorCode
96
+ ? this.createErrorStatus(e)
97
+ : this.createErrorStatus({ message: `${e.message ?? e}` })
33
98
  })
34
99
  },
35
-
36
100
  async getConfig() {
37
101
  return this.get('/config')
38
102
  },
@@ -40,63 +104,45 @@ export const o = {
40
104
  return this.get('/models')
41
105
  },
42
106
  async getAuth() {
43
- return this.requiresAuth
107
+ return this.requiresAuth
44
108
  ? this.get('/auth')
45
- : new Promise(resolve => resolve({ json: () => ({ responseStatus: { errorCode: '!requiresAuth' } })}))
109
+ : new Promise(resolve => resolve({ json: () => ({ responseStatus: { errorCode: '!requiresAuth' } }) }))
46
110
  },
47
111
  get isAdmin() {
48
112
  return !this.requiresAuth || this.auth && this.auth.roles?.includes('Admin')
49
113
  },
50
-
114
+
51
115
  signIn(auth) {
52
116
  this.auth = auth
53
117
  if (auth?.apiKey) {
54
118
  this.headers.Authorization = `Bearer ${auth.apiKey}`
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
119
  } else {
60
120
  if (this.headers.Authorization) {
61
121
  delete this.headers.Authorization
62
122
  }
63
- if (this.headers['X-Session-Token']) {
64
- delete this.headers['X-Session-Token']
65
- }
66
123
  }
67
124
  },
68
125
  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
- }
126
+ try {
127
+ await this.post('/auth/logout')
128
+ } catch (error) {
129
+ console.error('Logout error:', error)
80
130
  }
81
131
  this.auth = null
82
132
  if (this.headers.Authorization) {
83
133
  delete this.headers.Authorization
84
134
  }
85
- if (this.headers['X-Session-Token']) {
86
- delete this.headers['X-Session-Token']
87
- }
88
- localStorage.removeItem('llms:auth')
89
135
  },
90
- async init() {
136
+ async init(ctx) {
91
137
  // Load models and prompts
92
- const { initDB } = useThreadStore()
93
- const [_, configRes, modelsRes] = await Promise.all([
94
- initDB(),
138
+ const [configRes, modelsRes, extensionsRes] = await Promise.all([
95
139
  this.getConfig(),
96
140
  this.getModels(),
141
+ this.get('/ext'),
97
142
  ])
98
143
  const config = await configRes.json()
99
144
  const models = await modelsRes.json()
145
+ const extensions = await extensionsRes.json()
100
146
 
101
147
  // Update auth settings from server config
102
148
  if (config.requiresAuth != null) {
@@ -106,25 +152,6 @@ export const o = {
106
152
  this.authType = config.authType
107
153
  }
108
154
 
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
155
  // Get auth status
129
156
  const authRes = await this.getAuth()
130
157
  const auth = this.requiresAuth
@@ -132,14 +159,59 @@ export const o = {
132
159
  : null
133
160
  if (auth?.responseStatus?.errorCode) {
134
161
  console.error(auth.responseStatus.errorCode, auth.responseStatus.message)
135
- // Clear invalid session from localStorage
136
- localStorage.removeItem('llms:auth')
137
162
  } else {
138
163
  this.signIn(auth)
139
164
  }
140
- return { config, models, auth }
165
+ return { config, models, extensions, auth }
166
+ },
167
+
168
+
169
+ async uploadFile(file) {
170
+ const formData = new FormData()
171
+ formData.append('file', file)
172
+ const response = await fetch(this.resolveUrl('/upload'), {
173
+ method: 'POST',
174
+ body: formData
175
+ })
176
+ if (!response.ok) {
177
+ throw new Error(`Upload failed: ${response.statusText}`)
178
+ }
179
+ return response.json()
180
+ },
181
+
182
+
183
+ getCacheInfo(url) {
184
+ return this.cacheUrlInfo[url]
185
+ },
186
+ async fetchCacheInfos(urls) {
187
+ const infos = {}
188
+ const fetchInfos = []
189
+ for (const url of urls) {
190
+ const info = this.getCacheInfo(url)
191
+ if (info) {
192
+ infos[url] = info
193
+ } else {
194
+ fetchInfos.push(fetch(this.resolveUrl(url + "?info")))
195
+ }
196
+ }
197
+ const responses = await Promise.all(fetchInfos)
198
+ for (let i = 0; i < urls.length; i++) {
199
+ try {
200
+ const info = await responses[i].json()
201
+ this.setCacheInfo(urls[i], info)
202
+ infos[urls[i]] = info
203
+ } catch (e) {
204
+ console.error('Failed to fetch info for', urls[i], e)
205
+ }
206
+ }
207
+ return infos
208
+ },
209
+ setCacheInfo(url, info) {
210
+ this.cacheUrlInfo[url] = info
141
211
  }
212
+
142
213
  }
143
214
 
215
+
144
216
  let ai = reactive(o)
145
217
  export default ai