llms-py 2.0.9__py3-none-any.whl → 2.0.11__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 (43) hide show
  1. llms.py +14 -7
  2. llms_py-2.0.11.data/data/index.html +80 -0
  3. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/llms.json +5 -5
  4. llms_py-2.0.11.data/data/ui/Avatar.mjs +28 -0
  5. llms_py-2.0.11.data/data/ui/Brand.mjs +23 -0
  6. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/ChatPrompt.mjs +101 -69
  7. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/Main.mjs +43 -183
  8. llms_py-2.0.11.data/data/ui/ModelSelector.mjs +29 -0
  9. llms_py-2.0.11.data/data/ui/ProviderStatus.mjs +105 -0
  10. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/Recents.mjs +2 -1
  11. llms_py-2.0.11.data/data/ui/SettingsDialog.mjs +374 -0
  12. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/Sidebar.mjs +11 -27
  13. llms_py-2.0.11.data/data/ui/SignIn.mjs +64 -0
  14. llms_py-2.0.11.data/data/ui/SystemPromptEditor.mjs +31 -0
  15. llms_py-2.0.11.data/data/ui/SystemPromptSelector.mjs +36 -0
  16. llms_py-2.0.11.data/data/ui/Welcome.mjs +8 -0
  17. llms_py-2.0.11.data/data/ui/ai.mjs +80 -0
  18. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/app.css +76 -10
  19. llms_py-2.0.11.data/data/ui/lib/servicestack-vue.mjs +37 -0
  20. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/markdown.mjs +9 -2
  21. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/tailwind.input.css +13 -4
  22. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/threadStore.mjs +2 -2
  23. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/typography.css +109 -1
  24. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/utils.mjs +8 -2
  25. {llms_py-2.0.9.dist-info → llms_py-2.0.11.dist-info}/METADATA +33 -25
  26. llms_py-2.0.11.dist-info/RECORD +40 -0
  27. llms_py-2.0.9.data/data/index.html +0 -64
  28. llms_py-2.0.9.data/data/ui/lib/servicestack-vue.min.mjs +0 -37
  29. llms_py-2.0.9.dist-info/RECORD +0 -30
  30. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/requirements.txt +0 -0
  31. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/App.mjs +0 -0
  32. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/fav.svg +0 -0
  33. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/lib/highlight.min.mjs +0 -0
  34. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/lib/idb.min.mjs +0 -0
  35. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/lib/marked.min.mjs +0 -0
  36. /llms_py-2.0.9.data/data/ui/lib/servicestack-client.min.mjs → /llms_py-2.0.11.data/data/ui/lib/servicestack-client.mjs +0 -0
  37. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/lib/vue-router.min.mjs +0 -0
  38. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui/lib/vue.min.mjs +0 -0
  39. {llms_py-2.0.9.data → llms_py-2.0.11.data}/data/ui.json +0 -0
  40. {llms_py-2.0.9.dist-info → llms_py-2.0.11.dist-info}/WHEEL +0 -0
  41. {llms_py-2.0.9.dist-info → llms_py-2.0.11.dist-info}/entry_points.txt +0 -0
  42. {llms_py-2.0.9.dist-info → llms_py-2.0.11.dist-info}/licenses/LICENSE +0 -0
  43. {llms_py-2.0.9.dist-info → llms_py-2.0.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,374 @@
1
+ import { ref, computed, watch, inject } from 'vue'
2
+ import { storageObject } from './utils.mjs'
3
+
4
+ const settingsKey = 'llms.settings'
5
+
6
+ export function useSettings() {
7
+ const intFields = [
8
+ 'max_completion_tokens',
9
+ 'n',
10
+ 'seed',
11
+ 'top_logprobs',
12
+ ]
13
+ const floatFields = [
14
+ 'frequency_penalty',
15
+ 'presence_penalty',
16
+ 'temperature',
17
+ 'top_p',
18
+ ]
19
+ const boolFields = [
20
+ 'enable_thinking',
21
+ 'parallel_tool_calls',
22
+ 'store',
23
+ ]
24
+ const strFields = [
25
+ 'prompt_cache_key',
26
+ 'reasoning_effort',
27
+ 'safety_identifier',
28
+ 'service_tier',
29
+ 'verbosity',
30
+ ]
31
+ const listFields = [
32
+ 'stop',
33
+ ]
34
+ const allFields = [
35
+ ...intFields,
36
+ ...floatFields,
37
+ ...boolFields,
38
+ ...strFields,
39
+ ...listFields,
40
+ ]
41
+
42
+ let settings = ref(storageObject(settingsKey))
43
+
44
+ function validSettings(localSettings) {
45
+ const to = {}
46
+ intFields.forEach(f => {
47
+ if (localSettings[f] != null && localSettings[f] !== '' && !isNaN(parseInt(localSettings[f]))) {
48
+ to[f] = parseInt(localSettings[f])
49
+ }
50
+ })
51
+ floatFields.forEach(f => {
52
+ if (localSettings[f] != null && localSettings[f] !== '' && !isNaN(parseFloat(localSettings[f]))) {
53
+ to[f] = parseFloat(localSettings[f])
54
+ }
55
+ })
56
+ boolFields.forEach(f => {
57
+ if (localSettings[f] != null && localSettings[f] !== '' && !!localSettings[f]) {
58
+ to[f] = !!localSettings[f]
59
+ }
60
+ })
61
+ strFields.forEach(f => {
62
+ if (localSettings[f] != null && localSettings[f] !== '') {
63
+ to[f] = localSettings[f]
64
+ }
65
+ })
66
+ listFields.forEach(f => {
67
+ if (localSettings[f] != null && localSettings[f] !== '') {
68
+ to[f] = Array.isArray(localSettings[f])
69
+ ? localSettings[f]
70
+ : typeof localSettings[f] == 'string'
71
+ ? localSettings[f].split(',').map(x => x.trim())
72
+ : []
73
+ }
74
+ })
75
+ return to
76
+ }
77
+
78
+ function applySettings(chatRequest) {
79
+ console.log('applySettings', JSON.stringify(settings.value, undefined, 2))
80
+ const removeFields = allFields.filter(f => !(f in settings.value))
81
+ removeFields.forEach(f => delete chatRequest[f])
82
+ Object.keys(settings.value).forEach(k => {
83
+ chatRequest[k] = settings.value[k]
84
+ })
85
+ console.log('applySettings.chatRequest', JSON.stringify(chatRequest, undefined, 2))
86
+ }
87
+
88
+ function resetSettings() {
89
+ return saveSettings({})
90
+ }
91
+
92
+ function saveSettings(localSettings) {
93
+ // console.log('saveSettings', JSON.stringify(localSettings, undefined, 2))
94
+ settings.value = validSettings(localSettings)
95
+ console.log('saveSettings.settings', JSON.stringify(settings.value, undefined, 2))
96
+ return storageObject(settingsKey, settings.value)
97
+ }
98
+
99
+ return {
100
+ allFields,
101
+ settings,
102
+ applySettings,
103
+ saveSettings,
104
+ resetSettings,
105
+ }
106
+ }
107
+
108
+ export default {
109
+ template: `
110
+ <div v-if="isOpen" class="fixed inset-0 z-50 overflow-y-auto" @click.self="close">
111
+ <div class="flex min-h-screen items-center justify-center p-4">
112
+ <!-- Backdrop -->
113
+ <div class="fixed inset-0 bg-black/40 transition-opacity" @click="close"></div>
114
+
115
+ <!-- Dialog -->
116
+ <div class="relative bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-hidden">
117
+ <!-- Header -->
118
+ <div class="flex items-center justify-between px-6 py-4 border-b border-gray-200">
119
+ <h2 class="text-xl font-semibold text-gray-900">Chat Request Settings</h2>
120
+ <button type="button" @click="close" class="text-gray-400 hover:text-gray-600">
121
+ <svg class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
122
+ <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"/>
123
+ </svg>
124
+ </button>
125
+ </div>
126
+
127
+ <!-- Content -->
128
+ <form class="px-6 py-4 overflow-y-auto max-h-[calc(90vh-140px)]" @submit.prevent="save">
129
+ <p class="text-sm text-gray-600 mb-4">
130
+ Configure default values for chat request options. Leave empty to use model defaults.
131
+ </p>
132
+
133
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
134
+ <!-- Temperature -->
135
+ <div>
136
+ <label class="block text-sm font-medium text-gray-700 mb-1">
137
+ Temperature
138
+ <span class="text-gray-500 font-normal">(0-2)</span>
139
+ </label>
140
+ <input type="number" v-model="localSettings.temperature"
141
+ step="0.1" min="0" max="2"
142
+ placeholder="e.g., 0.7"
143
+ class="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
144
+ <p class="mt-1 text-xs text-gray-500">Higher values more random, lower for more focus</p>
145
+ </div>
146
+
147
+ <!-- Max Completion Tokens -->
148
+ <div>
149
+ <label class="block text-sm font-medium text-gray-700 mb-1">
150
+ Max Completion Tokens
151
+ </label>
152
+ <input type="number" v-model="localSettings.max_completion_tokens"
153
+ step="1" min="1"
154
+ placeholder="e.g., 2048"
155
+ class="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
156
+ <p class="mt-1 text-xs text-gray-500">Max tokens for completion (inc. reasoning tokens)</p>
157
+ </div>
158
+
159
+ <!-- Seed -->
160
+ <div>
161
+ <label class="block text-sm font-medium text-gray-700 mb-1">
162
+ Seed
163
+ </label>
164
+ <input type="number" v-model="localSettings.seed"
165
+ step="1"
166
+ placeholder="e.g., 42"
167
+ class="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
168
+ <p class="mt-1 text-xs text-gray-500">For deterministic sampling (Beta feature)</p>
169
+ </div>
170
+
171
+ <!-- Top P -->
172
+ <div>
173
+ <label class="block text-sm font-medium text-gray-700 mb-1">
174
+ Top P
175
+ <span class="text-gray-500 font-normal">(0-1)</span>
176
+ </label>
177
+ <input type="number" v-model="localSettings.top_p"
178
+ step="0.1" min="0" max="1"
179
+ placeholder="e.g., 0.9"
180
+ class="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
181
+ <p class="mt-1 text-xs text-gray-500">Nucleus sampling - alternative to temperature</p>
182
+ </div>
183
+
184
+ <!-- Frequency Penalty -->
185
+ <div>
186
+ <label class="block text-sm font-medium text-gray-700 mb-1">
187
+ Frequency Penalty
188
+ <span class="text-gray-500 font-normal">(-2.0 to 2.0)</span>
189
+ </label>
190
+ <input type="number" v-model="localSettings.frequency_penalty"
191
+ step="0.1" min="-2" max="2"
192
+ placeholder="e.g., 0.5"
193
+ class="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
194
+ <p class="mt-1 text-xs text-gray-500">Penalize tokens based on frequency in text</p>
195
+ </div>
196
+
197
+ <!-- Presence Penalty -->
198
+ <div>
199
+ <label class="block text-sm font-medium text-gray-700 mb-1">
200
+ Presence Penalty
201
+ <span class="text-gray-500 font-normal">(-2.0 to 2.0)</span>
202
+ </label>
203
+ <input type="number" v-model="localSettings.presence_penalty"
204
+ step="0.1" min="-2" max="2"
205
+ placeholder="e.g., 0.5"
206
+ class="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
207
+ <p class="mt-1 text-xs text-gray-500">Penalize tokens based on presence in text</p>
208
+ </div>
209
+
210
+ <!-- Stop Sequences -->
211
+ <div>
212
+ <label for="stop" class="block text-sm font-medium text-gray-700 mb-1">
213
+ Stop Sequences
214
+ </label>
215
+ <TagInput id="stop" inputClass="h-[37px] !shadow-none"
216
+ v-model="localSettings.stop"
217
+ placeholder=""
218
+ label=""
219
+ />
220
+ <p class="mt-1 text-xs text-gray-500">Up to 4 sequences where API stops generating</p>
221
+ </div>
222
+
223
+ <!-- Reasoning Effort -->
224
+ <div>
225
+ <label class="block text-sm font-medium text-gray-700 mb-1">
226
+ Reasoning Effort
227
+ </label>
228
+ <select v-model="localSettings.reasoning_effort"
229
+ class="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
230
+ <option value="">Default</option>
231
+ <option value="minimal">Minimal</option>
232
+ <option value="low">Low</option>
233
+ <option value="medium">Medium</option>
234
+ <option value="high">High</option>
235
+ </select>
236
+ <p class="mt-1 text-xs text-gray-500">Constrains effort on reasoning for reasoning models</p>
237
+ </div>
238
+
239
+ <!-- Verbosity -->
240
+ <div>
241
+ <label class="block text-sm font-medium text-gray-700 mb-1">
242
+ Verbosity
243
+ </label>
244
+ <select v-model="localSettings.verbosity"
245
+ class="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
246
+ <option value="">Default</option>
247
+ <option value="low">Low</option>
248
+ <option value="medium">Medium</option>
249
+ <option value="high">High</option>
250
+ </select>
251
+ <p class="mt-1 text-xs text-gray-500">Constrains verbosity of model's response</p>
252
+ </div>
253
+
254
+ <!-- Service Tier -->
255
+ <div>
256
+ <label class="block text-sm font-medium text-gray-700 mb-1">
257
+ Service Tier
258
+ </label>
259
+ <input type="text" v-model="localSettings.service_tier"
260
+ placeholder="e.g., auto, default"
261
+ class="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
262
+ <p class="mt-1 text-xs text-gray-500">Processing type for serving the request</p>
263
+ </div>
264
+
265
+ <!-- Top Logprobs -->
266
+ <div>
267
+ <label class="block text-sm font-medium text-gray-700 mb-1">
268
+ Top Logprobs
269
+ <span class="text-gray-500 font-normal">(0-20)</span>
270
+ </label>
271
+ <input type="number" v-model="localSettings.top_logprobs"
272
+ step="1" min="0" max="20"
273
+ placeholder="e.g., 5"
274
+ class="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
275
+ <p class="mt-1 text-xs text-gray-500">Number of most likely tokens to return with log probs</p>
276
+ </div>
277
+
278
+ <!-- Safety Identifier -->
279
+ <div>
280
+ <label class="block text-sm font-medium text-gray-700 mb-1">
281
+ Safety Identifier
282
+ </label>
283
+ <input type="text" v-model="localSettings.safety_identifier"
284
+ placeholder="Unique user identifier"
285
+ class="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
286
+ <p class="mt-1 text-xs text-gray-500">Identifier to help detect policy violations</p>
287
+ </div>
288
+
289
+ <!-- Store -->
290
+ <div>
291
+ <label class="flex items-center">
292
+ <input type="checkbox" v-model="localSettings.store"
293
+ class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
294
+ <span class="ml-2 text-sm font-medium text-gray-700">Store Output</span>
295
+ </label>
296
+ <p class="mt-1 text-xs text-gray-500">Store output for model distillation or evals</p>
297
+ </div>
298
+
299
+ <!-- Enable Thinking -->
300
+ <div>
301
+ <label class="flex items-center">
302
+ <input type="checkbox" v-model="localSettings.enable_thinking"
303
+ class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
304
+ <span class="ml-2 text-sm font-medium text-gray-700">Enable Thinking</span>
305
+ </label>
306
+ <p class="mt-1 text-xs text-gray-500">Enable thinking mode for supported models (Qwen)</p>
307
+ </div>
308
+ </div>
309
+ </form>
310
+
311
+ <!-- Footer -->
312
+ <div class="flex items-center justify-between px-6 py-4 border-t border-gray-200 bg-gray-50">
313
+ <button type="button" @click="reset"
314
+ class="px-4 py-2 text-sm font-medium text-gray-700 hover:text-gray-900">
315
+ Reset to Defaults
316
+ </button>
317
+ <div class="flex space-x-3">
318
+ <button type="button" @click="close"
319
+ class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50">
320
+ Cancel
321
+ </button>
322
+ <button type="submit" @click="save"
323
+ class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700">
324
+ Save Settings
325
+ </button>
326
+ </div>
327
+ </div>
328
+ </div>
329
+ </div>
330
+ </div>
331
+ `,
332
+ props: {
333
+ isOpen: {
334
+ type: Boolean,
335
+ default: false
336
+ }
337
+ },
338
+ emits: ['close'],
339
+ setup(props, { emit }) {
340
+ const chatSettings = inject('chatSettings')
341
+ const { settings, saveSettings, resetSettings } = chatSettings
342
+
343
+ // Local copy for editing
344
+ const localSettings = ref(Object.assign({}, settings.value))
345
+
346
+ // Watch for dialog open to sync local settings
347
+ watch(() => props.isOpen, (isOpen) => {
348
+ if (isOpen) {
349
+ localSettings.value = Object.assign({}, settings.value)
350
+ }
351
+ })
352
+
353
+ function close() {
354
+ emit('close')
355
+ }
356
+
357
+ function save() {
358
+ saveSettings(localSettings.value)
359
+ close()
360
+ }
361
+
362
+ function reset() {
363
+ localSettings.value = resetSettings()
364
+ }
365
+
366
+ return {
367
+ localSettings,
368
+ close,
369
+ save,
370
+ reset,
371
+ }
372
+ }
373
+ }
374
+
@@ -1,6 +1,7 @@
1
- import { onMounted } from 'vue'
1
+ import { onMounted, inject } from 'vue'
2
2
  import { useRouter } from 'vue-router'
3
3
  import { useThreadStore } from './threadStore.mjs'
4
+ import Brand from './Brand.mjs'
4
5
 
5
6
  // Thread Item Component
6
7
  const ThreadItem = {
@@ -127,7 +128,7 @@ const GroupedThreads = {
127
128
  />
128
129
  </div>
129
130
  <div class="mb-4 flex w-full justify-center">
130
- <button @click="$router.push('/recents')" type="button"
131
+ <button @click="$router.push($ai.base + '/recents')" type="button"
131
132
  class="flex text-sm space-x-1 font-semibold text-gray-900 hover:text-blue-600 focus:outline-none transition-colors">
132
133
  <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>
133
134
  <span>All Chats</span>
@@ -146,31 +147,13 @@ const GroupedThreads = {
146
147
 
147
148
  const Sidebar = {
148
149
  components: {
150
+ Brand,
149
151
  GroupedThreads,
150
152
  ThreadItem,
151
153
  },
152
154
  template: `
153
155
  <div class="flex flex-col h-full bg-gray-50 border-r border-gray-200">
154
- <!-- Header -->
155
- <div class="flex-shrink-0 px-4 py-4 border-b border-gray-200 bg-white min-h-16 select-none">
156
- <div class="flex items-center justify-between">
157
- <button type="button"
158
- @click="goToInitialState"
159
- class="text-lg font-semibold text-gray-900 hover:text-blue-600 focus:outline-none transition-colors"
160
- title="Go back to initial state"
161
- >
162
- History
163
- </button>
164
- <button type="button"
165
- @click="createNewThread"
166
- class="text-gray-900 hover:text-blue-600 focus:outline-none transition-colors"
167
- title="New Chat"
168
- >
169
- <svg class="size-6" 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>
170
- </button>
171
- </div>
172
- </div>
173
-
156
+ <Brand @home="goToInitialState" @new="createNewThread" />
174
157
  <!-- Thread List -->
175
158
  <div class="flex-1 overflow-y-auto">
176
159
  <div v-if="isLoading" class="p-4 text-center text-gray-500">
@@ -187,13 +170,14 @@ const Sidebar = {
187
170
  </div>
188
171
 
189
172
  <div v-else class="py-2">
190
- <GroupedThreads :currentThread="currentThread" :groupedThreads="threadStore.getGroupedThreads(19)"
173
+ <GroupedThreads :currentThread="currentThread" :groupedThreads="threadStore.getGroupedThreads(18)"
191
174
  @select="selectThread" @delete="deleteThread" />
192
175
  </div>
193
176
  </div>
194
177
  </div>
195
178
  `,
196
179
  setup() {
180
+ const ai = inject('ai')
197
181
  const router = useRouter()
198
182
  const threadStore = useThreadStore()
199
183
  const {
@@ -212,7 +196,7 @@ const Sidebar = {
212
196
  })
213
197
 
214
198
  const selectThread = async (threadId) => {
215
- router.push(`/c/${threadId}`)
199
+ router.push(`${ai.base}/c/${threadId}`)
216
200
  }
217
201
 
218
202
  const deleteThread = async (threadId) => {
@@ -220,19 +204,19 @@ const Sidebar = {
220
204
  const wasCurrent = currentThread?.value?.id === threadId
221
205
  await deleteThreadFromStore(threadId)
222
206
  if (wasCurrent) {
223
- router.push('/')
207
+ router.push(`${ai.base}/`)
224
208
  }
225
209
  }
226
210
  }
227
211
 
228
212
  const createNewThread = async () => {
229
213
  const newThread = await createThread()
230
- router.push(`/c/${newThread.id}`)
214
+ router.push(`${ai.base}/c/${newThread.id}`)
231
215
  }
232
216
 
233
217
  const goToInitialState = () => {
234
218
  clearCurrentThread()
235
- router.push('/')
219
+ router.push(`${ai.base}/`)
236
220
  }
237
221
 
238
222
  return {
@@ -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 bg-gray-50 px-6 py-4">
4
+ <div class="max-w-6xl mx-auto">
5
+ <label class="block text-sm font-medium text-gray-700 mb-2">
6
+ System Prompt
7
+ <span v-if="selected" class="text-gray-500 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 px-3 py-2 text-sm placeholder-gray-500 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">
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,36 @@
1
+ export default {
2
+ template:`
3
+ <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
+ </button>
6
+
7
+ <Autocomplete id="prompt" :options="prompts" label=""
8
+ :modelValue="modelValue" @update:modelValue="$emit('update:modelValue', $event)"
9
+ class="w-72 xl:w-84"
10
+ :match="(x, value) => x.name.toLowerCase().includes(value.toLowerCase())"
11
+ placeholder="Select a System Prompt...">
12
+ <template #item="{ value }">
13
+ <div class="truncate max-w-72" :title="value">{{value}}</div>
14
+ </template>
15
+ </Autocomplete>
16
+
17
+ <!-- Toggle System Prompt Visibility -->
18
+ <button type="button"
19
+ @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"
22
+ :title="show ? 'Hide system prompt' : 'Show system prompt'"
23
+ >
24
+ <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>
25
+ <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>
26
+ </button>
27
+ `,
28
+ emits: ['updated', 'update:modelValue', 'toggle'],
29
+ props: {
30
+ prompts: Array,
31
+ modelValue: Object,
32
+ show: Boolean,
33
+ },
34
+ setup() {
35
+ }
36
+ }
@@ -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" 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 mb-2">{{ $ai.welcome }}</h2>
7
+ `
8
+ }