llms-py 3.0.0__py3-none-any.whl → 3.0.0b1__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 (206) hide show
  1. llms/index.html +77 -35
  2. llms/llms.json +23 -72
  3. llms/main.py +732 -1786
  4. llms/providers.json +1 -1
  5. llms/{extensions/analytics/ui/index.mjs → ui/Analytics.mjs} +238 -154
  6. llms/ui/App.mjs +60 -151
  7. llms/ui/Avatar.mjs +85 -0
  8. llms/ui/Brand.mjs +52 -0
  9. llms/ui/ChatPrompt.mjs +606 -0
  10. llms/ui/Main.mjs +873 -0
  11. llms/ui/ModelSelector.mjs +693 -0
  12. llms/ui/OAuthSignIn.mjs +92 -0
  13. llms/ui/ProviderIcon.mjs +36 -0
  14. llms/ui/ProviderStatus.mjs +105 -0
  15. llms/{extensions/app/ui → ui}/Recents.mjs +65 -91
  16. llms/ui/{modules/chat/SettingsDialog.mjs → SettingsDialog.mjs} +9 -9
  17. llms/{extensions/app/ui/index.mjs → ui/Sidebar.mjs} +58 -124
  18. llms/ui/SignIn.mjs +64 -0
  19. llms/ui/SystemPromptEditor.mjs +31 -0
  20. llms/ui/SystemPromptSelector.mjs +56 -0
  21. llms/ui/Welcome.mjs +8 -0
  22. llms/ui/ai.mjs +53 -125
  23. llms/ui/app.css +111 -1837
  24. llms/ui/lib/charts.mjs +13 -9
  25. llms/ui/lib/servicestack-vue.mjs +3 -3
  26. llms/ui/lib/vue.min.mjs +9 -10
  27. llms/ui/lib/vue.mjs +1602 -1763
  28. llms/ui/markdown.mjs +2 -10
  29. llms/ui/tailwind.input.css +80 -496
  30. llms/ui/threadStore.mjs +572 -0
  31. llms/ui/utils.mjs +117 -113
  32. llms/ui.json +1069 -0
  33. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/METADATA +1 -1
  34. llms_py-3.0.0b1.dist-info/RECORD +49 -0
  35. llms/__pycache__/__init__.cpython-312.pyc +0 -0
  36. llms/__pycache__/__init__.cpython-313.pyc +0 -0
  37. llms/__pycache__/__init__.cpython-314.pyc +0 -0
  38. llms/__pycache__/__main__.cpython-312.pyc +0 -0
  39. llms/__pycache__/__main__.cpython-314.pyc +0 -0
  40. llms/__pycache__/llms.cpython-312.pyc +0 -0
  41. llms/__pycache__/main.cpython-312.pyc +0 -0
  42. llms/__pycache__/main.cpython-313.pyc +0 -0
  43. llms/__pycache__/main.cpython-314.pyc +0 -0
  44. llms/__pycache__/plugins.cpython-314.pyc +0 -0
  45. llms/extensions/app/README.md +0 -20
  46. llms/extensions/app/__init__.py +0 -530
  47. llms/extensions/app/__pycache__/__init__.cpython-314.pyc +0 -0
  48. llms/extensions/app/__pycache__/db.cpython-314.pyc +0 -0
  49. llms/extensions/app/__pycache__/db_manager.cpython-314.pyc +0 -0
  50. llms/extensions/app/db.py +0 -644
  51. llms/extensions/app/db_manager.py +0 -195
  52. llms/extensions/app/requests.json +0 -9073
  53. llms/extensions/app/threads.json +0 -15290
  54. llms/extensions/app/ui/threadStore.mjs +0 -411
  55. llms/extensions/core_tools/CALCULATOR.md +0 -32
  56. llms/extensions/core_tools/__init__.py +0 -598
  57. llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
  58. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +0 -201
  59. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +0 -185
  60. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +0 -101
  61. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +0 -160
  62. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +0 -66
  63. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +0 -27
  64. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +0 -72
  65. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +0 -119
  66. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +0 -98
  67. llms/extensions/core_tools/ui/codemirror/doc/docs.css +0 -225
  68. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  69. llms/extensions/core_tools/ui/codemirror/lib/codemirror.css +0 -344
  70. llms/extensions/core_tools/ui/codemirror/lib/codemirror.js +0 -9884
  71. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +0 -942
  72. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +0 -118
  73. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +0 -962
  74. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +0 -62
  75. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +0 -402
  76. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +0 -40
  77. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +0 -135
  78. llms/extensions/core_tools/ui/index.mjs +0 -650
  79. llms/extensions/gallery/README.md +0 -61
  80. llms/extensions/gallery/__init__.py +0 -61
  81. llms/extensions/gallery/__pycache__/__init__.cpython-314.pyc +0 -0
  82. llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
  83. llms/extensions/gallery/db.py +0 -298
  84. llms/extensions/gallery/ui/index.mjs +0 -482
  85. llms/extensions/katex/README.md +0 -39
  86. llms/extensions/katex/__init__.py +0 -6
  87. llms/extensions/katex/__pycache__/__init__.cpython-314.pyc +0 -0
  88. llms/extensions/katex/ui/README.md +0 -125
  89. llms/extensions/katex/ui/contrib/auto-render.js +0 -338
  90. llms/extensions/katex/ui/contrib/auto-render.min.js +0 -1
  91. llms/extensions/katex/ui/contrib/auto-render.mjs +0 -244
  92. llms/extensions/katex/ui/contrib/copy-tex.js +0 -127
  93. llms/extensions/katex/ui/contrib/copy-tex.min.js +0 -1
  94. llms/extensions/katex/ui/contrib/copy-tex.mjs +0 -105
  95. llms/extensions/katex/ui/contrib/mathtex-script-type.js +0 -109
  96. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +0 -1
  97. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +0 -24
  98. llms/extensions/katex/ui/contrib/mhchem.js +0 -3213
  99. llms/extensions/katex/ui/contrib/mhchem.min.js +0 -1
  100. llms/extensions/katex/ui/contrib/mhchem.mjs +0 -3109
  101. llms/extensions/katex/ui/contrib/render-a11y-string.js +0 -887
  102. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +0 -1
  103. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +0 -800
  104. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  115. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  116. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  117. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  118. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  119. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  120. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  121. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  122. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  123. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  124. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  125. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  126. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  127. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  128. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  129. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  130. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  131. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  132. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  133. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  134. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  135. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  136. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  137. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  138. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  139. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  140. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  141. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  142. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  143. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  144. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  145. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  146. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  147. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  148. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  149. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  150. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  151. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  152. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  153. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  154. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  155. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  156. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  157. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  158. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  159. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  160. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  161. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  162. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  163. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  164. llms/extensions/katex/ui/index.mjs +0 -92
  165. llms/extensions/katex/ui/katex-swap.css +0 -1230
  166. llms/extensions/katex/ui/katex-swap.min.css +0 -1
  167. llms/extensions/katex/ui/katex.css +0 -1230
  168. llms/extensions/katex/ui/katex.js +0 -19080
  169. llms/extensions/katex/ui/katex.min.css +0 -1
  170. llms/extensions/katex/ui/katex.min.js +0 -1
  171. llms/extensions/katex/ui/katex.min.mjs +0 -1
  172. llms/extensions/katex/ui/katex.mjs +0 -18547
  173. llms/extensions/providers/__init__.py +0 -18
  174. llms/extensions/providers/__pycache__/__init__.cpython-314.pyc +0 -0
  175. llms/extensions/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
  176. llms/extensions/providers/__pycache__/chutes.cpython-314.pyc +0 -0
  177. llms/extensions/providers/__pycache__/google.cpython-314.pyc +0 -0
  178. llms/extensions/providers/__pycache__/nvidia.cpython-314.pyc +0 -0
  179. llms/extensions/providers/__pycache__/openai.cpython-314.pyc +0 -0
  180. llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
  181. llms/extensions/providers/anthropic.py +0 -229
  182. llms/extensions/providers/chutes.py +0 -155
  183. llms/extensions/providers/google.py +0 -378
  184. llms/extensions/providers/nvidia.py +0 -105
  185. llms/extensions/providers/openai.py +0 -156
  186. llms/extensions/providers/openrouter.py +0 -72
  187. llms/extensions/system_prompts/README.md +0 -22
  188. llms/extensions/system_prompts/__init__.py +0 -45
  189. llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc +0 -0
  190. llms/extensions/system_prompts/ui/index.mjs +0 -280
  191. llms/extensions/system_prompts/ui/prompts.json +0 -1067
  192. llms/extensions/tools/__init__.py +0 -5
  193. llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  194. llms/extensions/tools/ui/index.mjs +0 -204
  195. llms/providers-extra.json +0 -356
  196. llms/ui/ctx.mjs +0 -365
  197. llms/ui/index.mjs +0 -129
  198. llms/ui/modules/chat/ChatBody.mjs +0 -691
  199. llms/ui/modules/chat/index.mjs +0 -828
  200. llms/ui/modules/layout.mjs +0 -243
  201. llms/ui/modules/model-selector.mjs +0 -851
  202. llms_py-3.0.0.dist-info/RECORD +0 -202
  203. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/WHEEL +0 -0
  204. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/entry_points.txt +0 -0
  205. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
  206. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,11 @@
1
1
  import { onMounted, inject } from 'vue'
2
2
  import { useRouter } from 'vue-router'
3
- import { appendQueryString } from '@servicestack/client'
4
- import ThreadStore from './threadStore.mjs'
5
- import Recents from './Recents.mjs'
3
+ import { useFormatters } from '@servicestack/vue'
4
+ import { useThreadStore } from './threadStore.mjs'
5
+ import Brand from './Brand.mjs'
6
+ import { statsTitle, formatCost } from './utils.mjs'
6
7
 
7
- let ext
8
+ const { humanifyNumber, humanifyMs } = useFormatters()
8
9
 
9
10
  // Thread Item Component
10
11
  const ThreadItem = {
@@ -20,10 +21,10 @@ const ThreadItem = {
20
21
  {{ thread.title }}
21
22
  </div>
22
23
  <div class="text-xs text-gray-500 dark:text-gray-400 truncate">
23
- <span>{{ $fmt.relativeTime(thread.updatedAt) }} • {{ thread.messages.length }} msgs</span>
24
- <span v-if="thread.stats?.inputTokens" :title="$fmt.statsTitle(thread.stats)">
25
- &#8226; {{ $fmt.humanifyNumber(thread.stats.inputTokens + thread.stats.outputTokens) }} toks
26
- {{ thread.stats.cost ? ' ' + $fmt.cost(thread.stats.cost) : '' }}
24
+ <span>{{ formatRelativeTime(thread.updatedAt) }} • {{ thread.messages.length }} msgs</span>
25
+ <span v-if="thread.stats?.inputTokens" :title="statsTitle(thread.stats)">
26
+ &#8226; {{ humanifyNumber(thread.stats.inputTokens + thread.stats.outputTokens) }} toks
27
+ {{ thread.stats.cost ? ' ' + formatCost(thread.stats.cost) : '' }}
27
28
  </span>
28
29
  </div>
29
30
  <div v-if="thread.model" class="text-xs text-blue-600 dark:text-blue-400 truncate">
@@ -59,12 +60,32 @@ const ThreadItem = {
59
60
  emits: ['select', 'delete'],
60
61
 
61
62
  setup() {
63
+ const formatRelativeTime = (timestamp) => {
64
+ const now = new Date()
65
+ const date = new Date(timestamp)
66
+ const diffInSeconds = Math.floor((now - date) / 1000)
67
+
68
+ if (diffInSeconds < 60) return 'Just now'
69
+ if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`
70
+ if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`
71
+ if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`
72
+
73
+ return date.toLocaleDateString()
74
+ }
75
+
62
76
  return {
77
+ formatRelativeTime,
78
+ humanifyNumber,
79
+ statsTitle,
80
+ formatCost,
63
81
  }
64
82
  }
65
83
  }
66
84
 
67
85
  const GroupedThreads = {
86
+ components: {
87
+ ThreadItem,
88
+ },
68
89
  template: `
69
90
  <!-- Today -->
70
91
  <div v-if="groupedThreads.today.length > 0" class="mb-4">
@@ -73,20 +94,7 @@ const GroupedThreads = {
73
94
  v-for="thread in groupedThreads.today"
74
95
  :key="thread.id"
75
96
  :thread="thread"
76
- :is-active="currentThread?.id == thread.id"
77
- @select="$emit('select', $event)"
78
- @delete="$emit('delete', $event)"
79
- />
80
- </div>
81
-
82
- <!-- Yesterday -->
83
- <div v-if="groupedThreads.yesterday.length > 0" class="mb-4">
84
- <h3 class="px-4 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider select-none">Yesterday</h3>
85
- <ThreadItem
86
- v-for="thread in groupedThreads.yesterday"
87
- :key="thread.id"
88
- :thread="thread"
89
- :is-active="currentThread?.id == thread.id"
97
+ :is-active="currentThread?.id === thread.id"
90
98
  @select="$emit('select', $event)"
91
99
  @delete="$emit('delete', $event)"
92
100
  />
@@ -99,7 +107,7 @@ const GroupedThreads = {
99
107
  v-for="thread in groupedThreads.lastWeek"
100
108
  :key="thread.id"
101
109
  :thread="thread"
102
- :is-active="currentThread?.id == thread.id"
110
+ :is-active="currentThread?.id === thread.id"
103
111
  @select="$emit('select', $event)"
104
112
  @delete="$emit('delete', $event)"
105
113
  />
@@ -112,7 +120,7 @@ const GroupedThreads = {
112
120
  v-for="thread in groupedThreads.lastMonth"
113
121
  :key="thread.id"
114
122
  :thread="thread"
115
- :is-active="currentThread?.id == thread.id"
123
+ :is-active="currentThread?.id === thread.id"
116
124
  @select="$emit('select', $event)"
117
125
  @delete="$emit('delete', $event)"
118
126
  />
@@ -125,7 +133,7 @@ const GroupedThreads = {
125
133
  v-for="thread in monthThreads"
126
134
  :key="thread.id"
127
135
  :thread="thread"
128
- :is-active="currentThread?.id == thread.id"
136
+ :is-active="currentThread?.id === thread.id"
129
137
  @select="$emit('select', $event)"
130
138
  @delete="$emit('delete', $event)"
131
139
  />
@@ -148,10 +156,15 @@ const GroupedThreads = {
148
156
  emits: ['select', 'delete'],
149
157
  }
150
158
 
151
- const ThreadsSidebar = {
159
+ const Sidebar = {
160
+ components: {
161
+ Brand,
162
+ GroupedThreads,
163
+ ThreadItem,
164
+ },
152
165
  template: `
153
- <div class="flex flex-col h-full">
154
- <Brand />
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')" />
155
168
  <!-- Thread List -->
156
169
  <div class="flex-1 overflow-y-auto">
157
170
  <div v-if="isLoading" class="p-4 text-center text-gray-500 dark:text-gray-400">
@@ -167,28 +180,18 @@ const ThreadsSidebar = {
167
180
  <p class="text-xs text-gray-400 dark:text-gray-500 mt-1">Start a new chat to begin</p>
168
181
  </div>
169
182
 
170
- <div v-else class="relative py-2">
171
-
172
- <div class="flex items-center space-x-2 absolute top-2 right-2">
173
- <button type="button"
174
- @click="createNewThread"
175
- class="text-gray-900 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none transition-colors"
176
- title="New Chat"
177
- >
178
- <svg class="size-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></g></svg>
179
- </button>
180
- </div>
181
-
182
- <GroupedThreads :currentThread="currentThread" :groupedThreads="$threads.getGroupedThreads(50)"
183
+ <div v-else class="py-2">
184
+ <GroupedThreads :currentThread="currentThread" :groupedThreads="threadStore.getGroupedThreads(18)"
183
185
  @select="selectThread" @delete="deleteThread" />
184
186
  </div>
185
187
  </div>
186
188
  </div>
187
189
  `,
188
- setup(props) {
189
- const ctx = inject('ctx')
190
- const ai = ctx.ai
190
+ emits: ['thread-selected', 'toggle-sidebar'],
191
+ setup(props, { emit }) {
192
+ const ai = inject('ai')
191
193
  const router = useRouter()
194
+ const threadStore = useThreadStore()
192
195
  const {
193
196
  threads,
194
197
  currentThread,
@@ -198,7 +201,7 @@ const ThreadsSidebar = {
198
201
  createThread,
199
202
  deleteThread: deleteThreadFromStore,
200
203
  clearCurrentThread
201
- } = ctx.threads
204
+ } = threadStore
202
205
 
203
206
  onMounted(async () => {
204
207
  await loadThreads()
@@ -206,6 +209,7 @@ const ThreadsSidebar = {
206
209
 
207
210
  const selectThread = async (threadId) => {
208
211
  router.push(`${ai.base}/c/${threadId}`)
212
+ emit('thread-selected')
209
213
  }
210
214
 
211
215
  const deleteThread = async (threadId) => {
@@ -219,19 +223,24 @@ const ThreadsSidebar = {
219
223
  }
220
224
 
221
225
  const createNewThread = async () => {
222
- ctx.threads.startNewThread({ title: 'New Chat', model: ctx.chat.getSelectedModel(), redirect: true })
226
+ const newThread = await createThread()
227
+ router.push(`${ai.base}/c/${newThread.id}`)
228
+ emit('thread-selected')
223
229
  }
224
230
 
225
231
  const goToInitialState = () => {
226
232
  clearCurrentThread()
227
- ctx.to(`/`)
233
+ router.push(`${ai.base}/`)
234
+ emit('thread-selected')
228
235
  }
229
236
 
230
237
  const goToAnalytics = () => {
231
- ctx.to(`/analytics`)
238
+ router.push(`${ai.base}/analytics`)
239
+ emit('thread-selected')
232
240
  }
233
241
 
234
242
  return {
243
+ threadStore,
235
244
  threads,
236
245
  currentThread,
237
246
  isLoading,
@@ -245,79 +254,4 @@ const ThreadsSidebar = {
245
254
  }
246
255
  }
247
256
 
248
- function useRequests(ext) {
249
- async function query(query) {
250
- return (await ext.getJson(appendQueryString(`/requests`, query))).response || []
251
- }
252
- async function deleteById(requestId) {
253
- if (!requestId) {
254
- throw new Error('Request ID is required')
255
- }
256
- return await ext.deleteJson(`/requests/${requestId}`)
257
- }
258
-
259
- async function getThreadIds(query) {
260
- return (await ext.getJson(appendQueryString(`/requests?fields=threadId&not_null=threadId&as=column&take=10000`, query))).response || []
261
- }
262
-
263
- async function getSummary() {
264
- return (await ext.getJson(`/requests/summary`)).response
265
- }
266
- async function getDailySummary(day) {
267
- return (await ext.getJson(`/requests/summary/${day}`)).response
268
- }
269
-
270
- // Get unique values for filter options
271
- async function getFilterOptions() {
272
- const results = await query({
273
- select: 'distinct',
274
- fields: 'model,provider',
275
- not_null: 'model,provider',
276
- })
277
-
278
- if (results) {
279
- const models = [...new Set(results.map(r => r.model).filter(m => m))].sort()
280
- const providers = [...new Set(results.map(r => r.provider).filter(p => p))].sort()
281
- console.log('getFilterOptions', models, providers)
282
- return {
283
- models,
284
- providers
285
- }
286
- }
287
- }
288
-
289
- return {
290
- query,
291
- deleteById,
292
- getThreadIds,
293
- getSummary,
294
- getDailySummary,
295
- getFilterOptions,
296
- }
297
- }
298
-
299
- export default {
300
- order: -100,
301
-
302
- install(ctx) {
303
- ext = ctx.scope('app')
304
- ctx.components({
305
- ThreadsSidebar,
306
- ThreadItem,
307
- GroupedThreads,
308
- Recents,
309
- })
310
- ctx.routes.push(...[
311
- { path: '/recents', component: Recents },
312
- ])
313
- ThreadStore.install(ctx)
314
-
315
- ctx.setGlobals({
316
- requests: useRequests(ext)
317
- })
318
-
319
- ctx.setLayout({
320
- left: 'ThreadsSidebar',
321
- })
322
- }
323
- }
257
+ export default Sidebar
llms/ui/SignIn.mjs ADDED
@@ -0,0 +1,64 @@
1
+ import { inject, ref } from "vue"
2
+ import { toJsonObject } from "./utils.mjs"
3
+
4
+ export default {
5
+ template: `
6
+ <div class="min-h-full -mt-12 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
7
+ <div class="sm:mx-auto sm:w-full sm:max-w-md">
8
+ <h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900 dark:text-gray-50">
9
+ Sign In
10
+ </h2>
11
+ </div>
12
+ <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
13
+ <ErrorSummary v-if="errorSummary" class="mb-3" :status="errorSummary" />
14
+ <div class="bg-white dark:bg-black py-8 px-4 shadow sm:rounded-lg sm:px-10">
15
+ <form @submit.prevent="submit">
16
+ <div class="flex flex-1 flex-col justify-between">
17
+ <div class="space-y-6">
18
+ <fieldset class="grid grid-cols-12 gap-6">
19
+ <div class="w-full col-span-12">
20
+ <TextInput id="apiKey" name="apiKey" label="API Key" v-model="apiKey" />
21
+ </div>
22
+ </fieldset>
23
+ </div>
24
+ </div>
25
+ <div class="mt-8">
26
+ <PrimaryButton class="w-full">Sign In</PrimaryButton>
27
+ </div>
28
+ </form>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ `,
33
+ emits: ['done'],
34
+ setup(props, { emit }) {
35
+ const ai = inject('ai')
36
+ const apiKey = ref('')
37
+ const errorSummary = ref()
38
+ async function submit() {
39
+ const r = await ai.get('/auth', {
40
+ headers: {
41
+ 'Authorization': `Bearer ${apiKey.value}`
42
+ },
43
+ })
44
+ const txt = await r.text()
45
+ const json = toJsonObject(txt)
46
+ // console.log('json', json)
47
+ if (r.ok) {
48
+ json.apiKey = apiKey.value
49
+ emit('done', json)
50
+ } else {
51
+ errorSummary.value = json.responseStatus || {
52
+ errorCode: "Unauthorized",
53
+ message: 'Invalid API Key'
54
+ }
55
+ }
56
+ }
57
+
58
+ return {
59
+ apiKey,
60
+ submit,
61
+ errorSummary,
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,31 @@
1
+ export default {
2
+ template:`
3
+ <div class="border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 px-6 py-4">
4
+ <div class="max-w-6xl mx-auto">
5
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
6
+ System Prompt
7
+ <span v-if="selected" class="text-gray-500 dark:text-gray-400 font-normal">
8
+ ({{ prompts.find(p => p.id === selected.id)?.name || 'Custom' }})
9
+ </span>
10
+ </label>
11
+ <textarea
12
+ :value="modelValue" @input="$emit('update:modelValue', $event.target.value)"
13
+ placeholder="Enter a system prompt to guide AI's behavior..."
14
+ rows="6"
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
+ ></textarea>
17
+ <div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
18
+ You can modify this system prompt before sending messages. Changes will only apply to new conversations.
19
+ </div>
20
+ </div>
21
+ </div>
22
+ `,
23
+ emits: ['update:modelValue'],
24
+ props: {
25
+ prompts: Array,
26
+ selected: Object,
27
+ modelValue: String,
28
+ },
29
+ setup() {
30
+ }
31
+ }
@@ -0,0 +1,56 @@
1
+ import { ref, onMounted, onUnmounted } from "vue"
2
+ export default {
3
+ template:`
4
+ <button v-if="modelValue" type="button" title="Clear System Prompt" @click="$emit('update:modelValue', null)">
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>
6
+ </button>
7
+
8
+ <Autocomplete ref="refSelector" id="prompt" :options="prompts" label=""
9
+ :modelValue="modelValue" @update:modelValue="$emit('update:modelValue', $event)"
10
+ class="w-68 xl:w-84"
11
+ :match="(x, value) => x.name.toLowerCase().includes(value.toLowerCase())"
12
+ placeholder="Select a System Prompt...">
13
+ <template #item="{ value }">
14
+ <div class="truncate max-w-72" :title="value">{{value}}</div>
15
+ </template>
16
+ </Autocomplete>
17
+
18
+ <!-- Toggle System Prompt Visibility -->
19
+ <button type="button"
20
+ @click="$emit('toggle')"
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"
23
+ :title="show ? 'Hide system prompt' : 'Show system prompt'"
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>
26
+ <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>
27
+ </button>
28
+ `,
29
+ emits: ['updated', 'update:modelValue', 'toggle'],
30
+ props: {
31
+ prompts: Array,
32
+ modelValue: Object,
33
+ show: Boolean,
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
+ }
55
+ }
56
+ }
llms/ui/Welcome.mjs ADDED
@@ -0,0 +1,8 @@
1
+ export default {
2
+ template: `
3
+ <div class="mb-2 flex justify-center">
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
+ </div>
6
+ <h2 class="text-2xl font-semibold text-gray-900 dark:text-gray-100 mb-2">{{ $ai.welcome }}</h2>
7
+ `
8
+ }