llms-py 3.0.0b7__py3-none-any.whl → 3.0.0b9__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 (157) hide show
  1. llms/__pycache__/main.cpython-314.pyc +0 -0
  2. llms/extensions/analytics/ui/index.mjs +51 -162
  3. llms/extensions/app/__init__.py +519 -0
  4. llms/extensions/app/__pycache__/__init__.cpython-314.pyc +0 -0
  5. llms/extensions/app/__pycache__/db.cpython-314.pyc +0 -0
  6. llms/extensions/app/__pycache__/db_manager.cpython-314.pyc +0 -0
  7. llms/extensions/app/db.py +643 -0
  8. llms/extensions/app/db_manager.py +195 -0
  9. llms/extensions/app/requests.json +9073 -0
  10. llms/extensions/app/threads.json +15290 -0
  11. llms/{ui/modules/threads → extensions/app/ui}/Recents.mjs +82 -55
  12. llms/{ui/modules/threads → extensions/app/ui}/index.mjs +78 -9
  13. llms/extensions/app/ui/threadStore.mjs +407 -0
  14. llms/extensions/core_tools/__init__.py +272 -32
  15. llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
  16. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
  17. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
  18. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
  19. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
  20. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
  21. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
  22. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
  23. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
  24. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
  25. llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
  26. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  27. llms/extensions/core_tools/ui/codemirror/lib/codemirror.css +344 -0
  28. llms/extensions/core_tools/ui/codemirror/lib/codemirror.js +9884 -0
  29. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
  30. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
  31. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
  32. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
  33. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
  34. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
  35. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
  36. llms/extensions/core_tools/ui/index.mjs +650 -0
  37. llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
  38. llms/extensions/gallery/db.py +4 -4
  39. llms/extensions/gallery/ui/index.mjs +2 -1
  40. llms/extensions/katex/__init__.py +6 -0
  41. llms/extensions/katex/__pycache__/__init__.cpython-314.pyc +0 -0
  42. llms/extensions/katex/ui/README.md +125 -0
  43. llms/extensions/katex/ui/contrib/auto-render.js +338 -0
  44. llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
  45. llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
  46. llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
  47. llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
  48. llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
  49. llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
  50. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
  51. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
  52. llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
  53. llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
  54. llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
  55. llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
  56. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
  57. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
  58. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  59. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  60. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  61. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  62. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  63. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  64. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  65. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  66. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  67. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  68. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  69. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  70. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  71. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  72. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  73. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  74. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  75. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  76. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  77. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  78. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  79. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  80. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  81. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  82. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  83. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  84. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  85. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  86. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  87. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  88. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  89. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  90. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  91. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  92. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  93. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  94. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  95. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  96. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  97. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  98. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  99. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  100. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  101. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  102. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  103. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  104. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  115. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  116. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  117. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  118. llms/extensions/katex/ui/index.mjs +92 -0
  119. llms/extensions/katex/ui/katex-swap.css +1230 -0
  120. llms/extensions/katex/ui/katex-swap.min.css +1 -0
  121. llms/extensions/katex/ui/katex.css +1230 -0
  122. llms/extensions/katex/ui/katex.js +19080 -0
  123. llms/extensions/katex/ui/katex.min.css +1 -0
  124. llms/extensions/katex/ui/katex.min.js +1 -0
  125. llms/extensions/katex/ui/katex.min.mjs +1 -0
  126. llms/extensions/katex/ui/katex.mjs +18547 -0
  127. llms/extensions/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
  128. llms/extensions/providers/anthropic.py +44 -1
  129. llms/extensions/system_prompts/ui/index.mjs +2 -1
  130. llms/extensions/tools/__init__.py +5 -0
  131. llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  132. llms/extensions/tools/ui/index.mjs +8 -8
  133. llms/index.html +26 -38
  134. llms/llms.json +4 -1
  135. llms/main.py +492 -103
  136. llms/ui/App.mjs +2 -3
  137. llms/ui/ai.mjs +29 -13
  138. llms/ui/app.css +255 -289
  139. llms/ui/ctx.mjs +84 -6
  140. llms/ui/index.mjs +4 -6
  141. llms/ui/lib/vue.min.mjs +10 -9
  142. llms/ui/lib/vue.mjs +1796 -1635
  143. llms/ui/markdown.mjs +4 -2
  144. llms/ui/modules/chat/ChatBody.mjs +90 -86
  145. llms/ui/modules/chat/HomeTools.mjs +0 -242
  146. llms/ui/modules/chat/index.mjs +103 -170
  147. llms/ui/modules/model-selector.mjs +2 -2
  148. llms/ui/tailwind.input.css +35 -1
  149. llms/ui/utils.mjs +12 -0
  150. {llms_py-3.0.0b7.dist-info → llms_py-3.0.0b9.dist-info}/METADATA +1 -1
  151. llms_py-3.0.0b9.dist-info/RECORD +198 -0
  152. llms/ui/modules/threads/threadStore.mjs +0 -640
  153. llms_py-3.0.0b7.dist-info/RECORD +0 -80
  154. {llms_py-3.0.0b7.dist-info → llms_py-3.0.0b9.dist-info}/WHEEL +0 -0
  155. {llms_py-3.0.0b7.dist-info → llms_py-3.0.0b9.dist-info}/entry_points.txt +0 -0
  156. {llms_py-3.0.0b7.dist-info → llms_py-3.0.0b9.dist-info}/licenses/LICENSE +0 -0
  157. {llms_py-3.0.0b7.dist-info → llms_py-3.0.0b9.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
 
2
2
  import { ref, computed, watch, nextTick, inject } from 'vue'
3
3
  import { useRouter } from 'vue-router'
4
- import { $$, createElement, lastRightPart, ApiResult, createErrorStatus } from "@servicestack/client"
4
+ import { $$, createElement, lastRightPart, ApiResult, createErrorStatus, pick } from "@servicestack/client"
5
5
  import SettingsDialog, { useSettings } from './SettingsDialog.mjs'
6
6
  import ChatBody from './ChatBody.mjs'
7
7
  import HomeTools from './HomeTools.mjs'
@@ -118,33 +118,17 @@ export function addCopyButtons() {
118
118
  export function useChatPrompt(ctx) {
119
119
  const messageText = ref('')
120
120
  const attachedFiles = ref([])
121
- const isGenerating = ref(false)
122
- const errorStatus = ref(null)
123
- const abortController = ref(null)
124
121
  const hasImage = () => attachedFiles.value.some(f => imageExts.includes(lastRightPart(f.name, '.')))
125
122
  const hasAudio = () => attachedFiles.value.some(f => audioExts.includes(lastRightPart(f.name, '.')))
126
123
  const hasFile = () => attachedFiles.value.length > 0
127
- // const hasText = () => !hasImage() && !hasAudio() && !hasFile()
128
124
 
129
- const editingMessageId = ref(null)
125
+ const editingMessage = ref(null)
130
126
 
131
127
  function reset() {
132
128
  // Ensure initial state is ready to accept input
133
- isGenerating.value = false
134
129
  attachedFiles.value = []
135
130
  messageText.value = ''
136
- abortController.value = null
137
- editingMessageId.value = null
138
- }
139
-
140
- function cancel() {
141
- // Cancel the pending request
142
- if (abortController.value) {
143
- abortController.value.abort()
144
- }
145
- // Reset UI state
146
- isGenerating.value = false
147
- abortController.value = null
131
+ editingMessage.value = null
148
132
  }
149
133
 
150
134
  const settings = useSettings()
@@ -351,9 +335,6 @@ export function useChatPrompt(ctx) {
351
335
  if (msg.role === 'assistant') {
352
336
  msg.model = model.name // tag with model
353
337
  }
354
- if (store) {
355
- await ctx.threads.addMessageToThread(threadId, msg)
356
- }
357
338
  }
358
339
  }
359
340
 
@@ -371,11 +352,6 @@ export function useChatPrompt(ctx) {
371
352
  usage.price = usage.output
372
353
  usage.cost = ctx.fmt.tokenCost(usage.prompt_tokens / 1_000_000 * parseFloat(input) + usage.completion_tokens / 1_000_000 * parseFloat(output))
373
354
  }
374
- await ctx.threads.logRequest(threadId, model, request, response)
375
- }
376
- if (store) {
377
- assistantMessage.model = model.name
378
- await ctx.threads.addMessageToThread(threadId, assistantMessage, usage)
379
355
  }
380
356
 
381
357
  nextTick(addCopyButtons)
@@ -409,19 +385,11 @@ export function useChatPrompt(ctx) {
409
385
  applySettings,
410
386
  messageText,
411
387
  attachedFiles,
412
- errorStatus,
413
- isGenerating,
414
- abortController,
415
- editingMessageId,
416
- get generating() {
417
- return isGenerating.value
418
- },
388
+ editingMessage,
419
389
  hasImage,
420
390
  hasAudio,
421
391
  hasFile,
422
- // hasText,
423
392
  reset,
424
- cancel,
425
393
  settings,
426
394
  addCopyButtons,
427
395
  getModel,
@@ -446,7 +414,7 @@ const ChatPrompt = {
446
414
  <div>
447
415
  <button type="button"
448
416
  @click="triggerFilePicker"
449
- :disabled="isGenerating || !model"
417
+ :disabled="$threads.isWatchingThread.value || !model"
450
418
  class="size-8 flex items-center justify-center rounded-md border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed"
451
419
  title="Attach image or audio">
452
420
  <svg class="size-5" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 256 256">
@@ -460,7 +428,7 @@ const ChatPrompt = {
460
428
  </div>
461
429
  <div>
462
430
  <button type="button" title="Settings" @click="showSettings = true"
463
- :disabled="isGenerating || !model"
431
+ :disabled="$threads.watchingThread || !model"
464
432
  class="size-8 flex items-center justify-center rounded-md border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed">
465
433
  <svg class="size-4 text-gray-600 dark:text-gray-400 disabled:text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 256 256"><path d="M40,88H73a32,32,0,0,0,62,0h81a8,8,0,0,0,0-16H135a32,32,0,0,0-62,0H40a8,8,0,0,0,0,16Zm64-24A16,16,0,1,1,88,80,16,16,0,0,1,104,64ZM216,168H199a32,32,0,0,0-62,0H40a8,8,0,0,0,0,16h97a32,32,0,0,0,62,0h17a8,8,0,0,0,0-16Zm-48,24a16,16,0,1,1,16-16A16,16,0,0,1,168,192Z"></path></svg>
466
434
  </button>
@@ -486,16 +454,16 @@ const ChatPrompt = {
486
454
  ? 'border-blue-500 bg-blue-50 dark:bg-blue-900/30 ring-1 ring-blue-500'
487
455
  : 'border-gray-300 dark:border-gray-600 focus:border-blue-500 focus:ring-blue-500'
488
456
  ]"
489
- :disabled="isGenerating || !model"
457
+ :disabled="$threads.watchingThread || !model"
490
458
  ></textarea>
491
- <button v-if="!isGenerating" title="Send (Enter)" type="button"
459
+ <button v-if="!$threads.watchingThread" title="Send (Enter)" type="button"
492
460
  @click="sendMessage"
493
- :disabled="!messageText.trim() || isGenerating || !model"
461
+ :disabled="!messageText.trim() || $threads.watchingThread || !model"
494
462
  class="absolute bottom-2 right-2 size-8 flex items-center justify-center rounded-md border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed disabled:border-gray-200 dark:disabled:border-gray-700 transition-colors">
495
463
  <svg class="size-5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path stroke-dasharray="20" stroke-dashoffset="20" d="M12 21l0 -17.5"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.2s" values="20;0"/></path><path stroke-dasharray="12" stroke-dashoffset="12" d="M12 3l7 7M12 3l-7 7"><animate fill="freeze" attributeName="stroke-dashoffset" begin="0.2s" dur="0.2s" values="12;0"/></path></g></svg>
496
464
  </button>
497
465
  <button v-else title="Cancel request" type="button"
498
- @click="cancelRequest"
466
+ @click="$threads.cancelThread()"
499
467
  class="absolute bottom-2 right-2 size-8 flex items-center justify-center rounded-md border border-red-300 dark:border-red-600 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 transition-colors">
500
468
  <svg class="size-5" 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">
501
469
  <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
@@ -506,7 +474,7 @@ const ChatPrompt = {
506
474
  <!-- Attachments & Image Options -->
507
475
  <div class="mt-2 flex justify-between items-start gap-2">
508
476
  <div class="flex flex-wrap gap-2">
509
- <div v-for="(f,i) in attachedFiles" :key="i" class="flex items-center gap-2 px-2 py-1 rounded-md border border-gray-300 dark:border-gray-600 text-xs text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-800">
477
+ <div v-for="(f,i) in $chat.attachedFiles.value" :key="i" class="flex items-center gap-2 px-2 py-1 rounded-md border border-gray-300 dark:border-gray-600 text-xs text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-800">
510
478
  <span class="truncate max-w-48" :title="f.name">{{ f.name }}</span>
511
479
  <button type="button" class="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200" @click="removeAttachment(i)" title="Remove Attachment">
512
480
  <svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
@@ -528,6 +496,7 @@ const ChatPrompt = {
528
496
  <div v-if="!model" class="mt-2 text-sm text-red-600 dark:text-red-400">
529
497
  Please select a model
530
498
  </div>
499
+ </div>
531
500
  </div>
532
501
  </div>
533
502
  `,
@@ -540,23 +509,13 @@ const ChatPrompt = {
540
509
  setup(props) {
541
510
  const ctx = inject('ctx')
542
511
  const config = ctx.state.config
543
- const router = useRouter()
544
- const chatPrompt = ctx.chat
545
512
  const {
546
513
  messageText,
547
- attachedFiles,
548
- isGenerating,
549
- errorStatus,
550
514
  hasImage,
551
515
  hasAudio,
552
516
  hasFile,
553
- editingMessageId,
554
517
  getTextContent,
555
- } = chatPrompt
556
- const threads = ctx.threads
557
- const {
558
- currentThread,
559
- } = ctx.threads
518
+ } = ctx.chat
560
519
 
561
520
  const fileInput = ref(null)
562
521
  const refMessage = ref(null)
@@ -580,7 +539,7 @@ const ChatPrompt = {
580
539
  type: f.type,
581
540
  width: response.width,
582
541
  height: response.height,
583
- threadId: currentThread.value?.id,
542
+ threadId: ctx.threads.currentThread.value?.id,
584
543
  created: Date.now()
585
544
  }
586
545
 
@@ -589,22 +548,21 @@ const ChatPrompt = {
589
548
  file: f // Keep original file for preview/fallback if needed
590
549
  }
591
550
  } catch (error) {
592
- console.error('File upload failed:', error)
593
- errorStatus.value = {
551
+ ctx.setError({
594
552
  errorCode: 'Upload Failed',
595
553
  message: `Failed to upload ${f.name}: ${error.message}`
596
- }
554
+ })
597
555
  return null
598
556
  }
599
557
  }))
600
558
 
601
- attachedFiles.value.push(...uploadedFiles.filter(f => f))
559
+ ctx.chat.attachedFiles.value.push(...uploadedFiles.filter(f => f))
602
560
  }
603
561
 
604
562
  // allow re-selecting the same file
605
563
  if (fileInput.value) fileInput.value.value = ''
606
564
 
607
- if (!messageText.value.trim()) {
565
+ if (!messageText.value?.trim()) {
608
566
  if (hasImage()) {
609
567
  messageText.value = getTextContent(config.defaults.image)
610
568
  } else if (hasAudio()) {
@@ -615,7 +573,7 @@ const ChatPrompt = {
615
573
  }
616
574
  }
617
575
  const removeAttachment = (i) => {
618
- attachedFiles.value.splice(i, 1)
576
+ ctx.chat.attachedFiles.value.splice(i, 1)
619
577
  }
620
578
 
621
579
  // Handle paste events for clipboard images, audio, and files
@@ -693,132 +651,110 @@ const ChatPrompt = {
693
651
 
694
652
  // Send message
695
653
  const sendMessage = async () => {
696
- if (!messageText.value.trim() && !hasImage() && !hasAudio() && !hasFile()) return
697
- if (isGenerating.value || !props.model) return
654
+ if (!messageText.value?.trim() && !hasImage() && !hasAudio() && !hasFile()) return
655
+ if (ctx.threads.isWatchingThread.value || !props.model) return
698
656
 
699
- // Clear any existing error message
700
- errorStatus.value = null
657
+ ctx.clearError()
701
658
 
702
659
  // 1. Construct Structured Content (Text + Attachments)
703
660
  let text = messageText.value.trim()
704
661
 
705
-
706
662
  messageText.value = ''
707
- let content = ctx.chat.createContent({ text, files: attachedFiles.value })
708
-
709
- // Create AbortController for this request
710
- const controller = new AbortController()
711
- chatPrompt.abortController.value = controller
712
- const model = props.model.name
663
+ let content = ctx.chat.createContent({ text, files: ctx.chat.attachedFiles.value })
713
664
 
714
665
  let thread
715
666
 
716
- try {
717
- // Create thread if none exists
718
- if (!currentThread.value) {
719
- thread = await ctx.threads.startNewThread({ model: props.model })
720
- } else {
721
- let threadId = currentThread.value.id
722
- // Update the existing thread's model to match current selection
723
- await threads.updateThread(threadId, {
724
- model,
725
- info: ctx.utils.toModelInfo(props.model),
726
- })
667
+ // Create thread if none exists
668
+ if (!ctx.threads.currentThread.value) {
669
+ thread = await ctx.threads.startNewThread({ model: props.model })
670
+ } else {
671
+ thread = ctx.threads.currentThread.value
672
+ }
673
+
674
+ let threadId = thread.id
675
+ let messages = thread.messages || []
676
+ if (!threadId) {
677
+ console.error('No thread ID found', thread, ctx.threads.currentThread.value)
678
+ return
679
+ }
727
680
 
728
- // Get the thread to check for duplicates
729
- thread = await threads.getThread(threadId)
681
+ // Handle Editing / Redo Logic
682
+ const editingMessage = ctx.chat.editingMessage.value
683
+ if (editingMessage) {
684
+ let messageIndex = messages.findIndex(m => m.timestamp === editingMessage)
685
+ if (messageIndex == -1) {
686
+ messageIndex = messages.findLastIndex(m => m.role === 'user')
730
687
  }
688
+ console.log('Editing message', editingMessage, messageIndex, messages)
731
689
 
732
- let threadId = thread.id
733
-
734
- // Handle Editing / Redo Logic
735
- if (editingMessageId.value) {
736
- // Check if message still exists
737
- const messageExists = thread.messages.find(m => m.id === editingMessageId.value)
738
- if (messageExists) {
739
- // Update the message content
740
- await threads.updateMessageInThread(threadId, editingMessageId.value, { content })
741
- // Redo from this message (clears subsequent)
742
- await threads.redoMessageFromThread(threadId, editingMessageId.value)
743
-
744
- // Clear editing state
745
- editingMessageId.value = null
746
- } else {
747
- // Fallback if message was deleted
748
- editingMessageId.value = null
749
- }
750
- // Refresh thread state
751
- thread = await threads.getThread(threadId)
690
+ if (messageIndex >= 0) {
691
+ messages[messageIndex].content = content
692
+ // Truncate messages to only include up to the edited message
693
+ messages.length = messageIndex + 1
752
694
  } else {
753
- // Regular Send Logic
754
- const lastMessage = thread.messages[thread.messages.length - 1]
755
-
756
- // Check duplicate based on text content extracted from potential array
757
- const getLastText = (msgContent) => {
758
- if (typeof msgContent === 'string') return msgContent
759
- if (Array.isArray(msgContent)) return msgContent.find(c => c.type === 'text')?.text || ''
760
- return ''
761
- }
762
- const newText = text // content[0].text
763
- const lastText = lastMessage && lastMessage.role === 'user' ? getLastText(lastMessage.content) : null
764
-
765
- const isDuplicate = lastText === newText
766
-
767
- // Add user message only if it's not a duplicate
768
- // Note: We are saving the FULL STRUCTURED CONTENT array here
769
- if (!isDuplicate) {
770
- await threads.addMessageToThread(threadId, {
771
- role: 'user',
772
- content: content,
773
- model: props.model.name,
774
- })
775
- // Reload thread after adding message
776
- thread = await threads.getThread(threadId)
777
- }
695
+ messages.push({
696
+ timestamp: new Date().valueOf(),
697
+ role: 'user',
698
+ content,
699
+ })
778
700
  }
701
+ } else {
702
+ // Regular Send Logic
703
+ const lastMessage = messages[messages.length - 1]
704
+
705
+ // Check duplicate based on text content extracted from potential array
706
+ const getLastText = (msgContent) => {
707
+ if (typeof msgContent === 'string') return msgContent
708
+ if (Array.isArray(msgContent)) return msgContent.find(c => c.type === 'text')?.text || ''
709
+ return ''
710
+ }
711
+ const newText = text // content[0].text
712
+ const lastText = lastMessage && lastMessage.role === 'user' ? getLastText(lastMessage.content) : null
713
+ const isDuplicate = lastText === newText
714
+
715
+ // Add user message only if it's not a duplicate
716
+ // Note: We are saving the FULL STRUCTURED CONTENT array here
717
+ if (!isDuplicate) {
718
+ messages.push({
719
+ timestamp: new Date().valueOf(),
720
+ role: 'user',
721
+ content,
722
+ })
723
+ }
724
+ }
779
725
 
780
- isGenerating.value = true
781
-
782
- const request = ctx.chat.createRequest({ model: props.model })
726
+ const request = ctx.chat.createRequest({ model: props.model })
783
727
 
784
- // Add History
785
- thread?.messages.forEach(m => {
786
- request.messages.push({
787
- role: m.role,
788
- content: m.content
789
- })
790
- })
791
- request.metadata.threadId = thread.id
728
+ // Add Thread History
729
+ messages.forEach(m => {
730
+ request.messages.push(m)
731
+ })
792
732
 
793
- const api = await ctx.chat.completion({ request, thread, controller, store: true })
794
- if (api.response) {
795
- // success
796
- attachedFiles.value = []
797
- } else {
798
- errorStatus.value = api.error
799
- }
733
+ // Update Thread Title if not set or is default
734
+ if (!thread.title || thread.title === 'New Chat' || request.title === 'New Chat') {
735
+ request.title = text.length > 100
736
+ ? text.slice(0, 100) + '...'
737
+ : text
738
+ console.debug(`changing thread title from '${thread.title}' to '${request.title}'`)
739
+ } else {
740
+ console.debug(`thread title is '${thread.title}'`, request.title)
741
+ }
800
742
 
801
- } catch (error) {
802
- // Check if the error is due to abort
803
- if (error.name === 'AbortError') {
804
- console.log('Request was cancelled by user')
805
- // Don't show error for cancelled requests
806
- } else {
807
- // Re-throw other errors to be handled by outer catch
808
- throw error
809
- }
810
- } finally {
811
- isGenerating.value = false
812
- chatPrompt.abortController.value = null
813
- // Restore focus to the textarea
814
- nextTick(() => {
815
- refMessage.value?.focus()
816
- })
743
+ const api = await ctx.threads.queueChat(threadId, request)
744
+ if (api.response) {
745
+ // success
746
+ ctx.chat.editingMessage.value = null
747
+ ctx.chat.attachedFiles.value = []
748
+ thread = api.response
749
+ ctx.threads.replaceThread(thread)
750
+ } else {
751
+ ctx.setError(api.error)
817
752
  }
818
- }
819
753
 
820
- const cancelRequest = () => {
821
- chatPrompt.cancel()
754
+ // Restore focus to the textarea
755
+ nextTick(() => {
756
+ refMessage.value?.focus()
757
+ })
822
758
  }
823
759
 
824
760
  const addNewLine = () => {
@@ -839,8 +775,6 @@ const ChatPrompt = {
839
775
  })
840
776
 
841
777
  return {
842
- isGenerating,
843
- attachedFiles,
844
778
  messageText,
845
779
  fileInput,
846
780
  refMessage,
@@ -854,7 +788,6 @@ const ChatPrompt = {
854
788
  onDrop,
855
789
  removeAttachment,
856
790
  sendMessage,
857
- cancelRequest,
858
791
  addNewLine,
859
792
  imageAspectRatios,
860
793
  }
@@ -350,7 +350,7 @@ const ModelSelectorModal = {
350
350
  @click="selectModel(model)"
351
351
  :class="[
352
352
  'relative text-left p-4 rounded-lg border transition-all group',
353
- modelValue === model.name
353
+ $state.selectedModel === model.name
354
354
  ? 'border-blue-500 bg-blue-50 dark:bg-blue-900/30 ring-2 ring-blue-500/50'
355
355
  : 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700/50'
356
356
  ]">
@@ -560,7 +560,7 @@ const ModelSelectorModal = {
560
560
  }
561
561
 
562
562
  // Filter by search query
563
- if (prefs.value.query.trim()) {
563
+ if (prefs.value.query?.trim()) {
564
564
  const query = prefs.value.query.toLowerCase()
565
565
  result = result.filter(m =>
566
566
  m.name?.toLowerCase().includes(query) ||
@@ -2,6 +2,7 @@
2
2
  @import "tailwindcss";
3
3
  @source "./lib/servicestack-vue.mjs";
4
4
  @source "../../extensions";
5
+ @source "../../llms-home/extensions";
5
6
 
6
7
  @custom-variant dark (&:where(.dark, .dark *));
7
8
 
@@ -162,6 +163,7 @@
162
163
  word-break: break-all !important;
163
164
  border: none !important;
164
165
  border-radius: 0 !important;
166
+ overflow: auto !important;
165
167
  }
166
168
 
167
169
  /* highlight.js - vs.css */
@@ -319,6 +321,10 @@
319
321
  background-color: #282c34 !important;
320
322
  }
321
323
 
324
+ .prose :where(h1):not(:where([class~="not-prose"] *)) {
325
+ font-weight: 700 !important;
326
+ }
327
+
322
328
  .hljs-comment,
323
329
  .hljs-quote {
324
330
  color: rgb(148 163 184);
@@ -336,7 +342,7 @@
336
342
  padding: .8571429em 1.1428571em;
337
343
  max-width: calc(100vw - 1rem);
338
344
  min-width: fit-content;
339
- background-color: #282c34 !important;
345
+ /* background-color: #282c34 !important; overrides CodeMirror */
340
346
  }
341
347
 
342
348
  pre code.hljs {
@@ -651,4 +657,32 @@
651
657
  left: 0;
652
658
  }
653
659
 
660
+
661
+ @media (min-width: 640px) {
662
+ .message pre {
663
+ max-width: 500px !important;
664
+ }
665
+ }
666
+
667
+ @media (min-width: 768px) {
668
+ .message pre {
669
+ max-width: 600px !important;
670
+ }
671
+ }
672
+
673
+ @media (min-width: 1024px) {
674
+
675
+ .message pre,
676
+ .message .prose pre {
677
+ max-width: 700px !important;
678
+ }
679
+ }
680
+
681
+ @media (min-width: 1280px) {
682
+
683
+ .message pre,
684
+ .message .prose pre {
685
+ max-width: 800px !important;
686
+ }
687
+ }
654
688
  }
llms/ui/utils.mjs CHANGED
@@ -111,6 +111,17 @@ export function formatCost(cost) {
111
111
  if (!cost) return ''
112
112
  return currFmt2.format(parseFloat(cost))
113
113
  }
114
+ export function tokensTitle(usage) {
115
+ let title = []
116
+ if (usage.tokens && usage.price) {
117
+ const msg = parseFloat(usage.price) > 0
118
+ ? `${usage.tokens} tokens @ ${usage.price} = ${tokenCostLong(usage.price, usage.tokens)}`
119
+ : `${usage.tokens} tokens`
120
+ const duration = usage.duration ? ` in ${usage.duration}ms` : ''
121
+ title.push(msg + duration)
122
+ }
123
+ return title.join('\n')
124
+ }
114
125
 
115
126
  // Accessible in views via $fmt
116
127
  export function utilsFormatters() {
@@ -172,6 +183,7 @@ export function utilsFormatters() {
172
183
  currFmt: currFmt2,
173
184
  tokenCost,
174
185
  tokenCostLong,
186
+ tokensTitle,
175
187
  cost: formatCost,
176
188
  costLong,
177
189
  statsTitle,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llms-py
3
- Version: 3.0.0b7
3
+ Version: 3.0.0b9
4
4
  Summary: A lightweight CLI tool and OpenAI-compatible server for querying multiple Large Language Model (LLM) providers
5
5
  Home-page: https://github.com/ServiceStack/llms
6
6
  Author: ServiceStack