llms-py 2.0.20__py3-none-any.whl → 3.0.10__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 (190) hide show
  1. llms/__init__.py +3 -1
  2. llms/db.py +359 -0
  3. llms/{ui/Analytics.mjs → extensions/analytics/ui/index.mjs} +254 -327
  4. llms/extensions/app/README.md +20 -0
  5. llms/extensions/app/__init__.py +589 -0
  6. llms/extensions/app/db.py +536 -0
  7. llms/{ui → extensions/app/ui}/Recents.mjs +99 -73
  8. llms/{ui/Sidebar.mjs → extensions/app/ui/index.mjs} +139 -68
  9. llms/extensions/app/ui/threadStore.mjs +433 -0
  10. llms/extensions/core_tools/CALCULATOR.md +32 -0
  11. llms/extensions/core_tools/__init__.py +637 -0
  12. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
  13. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
  14. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
  15. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
  16. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
  17. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
  18. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
  19. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
  20. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
  21. llms/extensions/core_tools/ui/codemirror/codemirror.css +344 -0
  22. llms/extensions/core_tools/ui/codemirror/codemirror.js +9884 -0
  23. llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
  24. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  25. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
  26. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
  27. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
  28. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
  29. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
  30. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
  31. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
  32. llms/extensions/core_tools/ui/index.mjs +650 -0
  33. llms/extensions/gallery/README.md +61 -0
  34. llms/extensions/gallery/__init__.py +63 -0
  35. llms/extensions/gallery/db.py +243 -0
  36. llms/extensions/gallery/ui/index.mjs +482 -0
  37. llms/extensions/katex/README.md +39 -0
  38. llms/extensions/katex/__init__.py +6 -0
  39. llms/extensions/katex/ui/README.md +125 -0
  40. llms/extensions/katex/ui/contrib/auto-render.js +338 -0
  41. llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
  42. llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
  43. llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
  44. llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
  45. llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
  46. llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
  47. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
  48. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
  49. llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
  50. llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
  51. llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
  52. llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
  53. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
  54. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
  55. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  56. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  57. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  58. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  59. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  60. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  61. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  62. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  63. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  64. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  65. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  66. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  67. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  68. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  69. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  70. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  71. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  72. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  73. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  74. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  75. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  76. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  77. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  78. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  79. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  80. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  81. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  82. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  83. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  84. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  85. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  86. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  87. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  88. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  89. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  90. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  91. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  92. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  93. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  94. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  95. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  96. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  97. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  98. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  99. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  100. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  101. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  102. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  103. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  104. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  115. llms/extensions/katex/ui/index.mjs +92 -0
  116. llms/extensions/katex/ui/katex-swap.css +1230 -0
  117. llms/extensions/katex/ui/katex-swap.min.css +1 -0
  118. llms/extensions/katex/ui/katex.css +1230 -0
  119. llms/extensions/katex/ui/katex.js +19080 -0
  120. llms/extensions/katex/ui/katex.min.css +1 -0
  121. llms/extensions/katex/ui/katex.min.js +1 -0
  122. llms/extensions/katex/ui/katex.min.mjs +1 -0
  123. llms/extensions/katex/ui/katex.mjs +18547 -0
  124. llms/extensions/providers/__init__.py +22 -0
  125. llms/extensions/providers/anthropic.py +233 -0
  126. llms/extensions/providers/cerebras.py +37 -0
  127. llms/extensions/providers/chutes.py +153 -0
  128. llms/extensions/providers/google.py +481 -0
  129. llms/extensions/providers/nvidia.py +103 -0
  130. llms/extensions/providers/openai.py +154 -0
  131. llms/extensions/providers/openrouter.py +74 -0
  132. llms/extensions/providers/zai.py +182 -0
  133. llms/extensions/system_prompts/README.md +22 -0
  134. llms/extensions/system_prompts/__init__.py +45 -0
  135. llms/extensions/system_prompts/ui/index.mjs +280 -0
  136. llms/extensions/system_prompts/ui/prompts.json +1067 -0
  137. llms/extensions/tools/__init__.py +144 -0
  138. llms/extensions/tools/ui/index.mjs +706 -0
  139. llms/index.html +36 -62
  140. llms/llms.json +180 -879
  141. llms/main.py +3640 -899
  142. llms/providers-extra.json +394 -0
  143. llms/providers.json +1 -0
  144. llms/ui/App.mjs +176 -8
  145. llms/ui/ai.mjs +156 -20
  146. llms/ui/app.css +3161 -244
  147. llms/ui/ctx.mjs +412 -0
  148. llms/ui/index.mjs +131 -0
  149. llms/ui/lib/chart.js +14 -0
  150. llms/ui/lib/charts.mjs +16 -0
  151. llms/ui/lib/color.js +14 -0
  152. llms/ui/lib/highlight.min.mjs +1243 -0
  153. llms/ui/lib/idb.min.mjs +8 -0
  154. llms/ui/lib/marked.min.mjs +8 -0
  155. llms/ui/lib/servicestack-client.mjs +1 -0
  156. llms/ui/lib/servicestack-vue.mjs +37 -0
  157. llms/ui/lib/vue-router.min.mjs +6 -0
  158. llms/ui/lib/vue.min.mjs +13 -0
  159. llms/ui/lib/vue.mjs +18530 -0
  160. llms/ui/markdown.mjs +25 -14
  161. llms/ui/modules/chat/ChatBody.mjs +976 -0
  162. llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +74 -74
  163. llms/ui/modules/chat/index.mjs +991 -0
  164. llms/ui/modules/icons.mjs +46 -0
  165. llms/ui/modules/layout.mjs +271 -0
  166. llms/ui/modules/model-selector.mjs +811 -0
  167. llms/ui/tailwind.input.css +550 -78
  168. llms/ui/typography.css +54 -36
  169. llms/ui/utils.mjs +197 -92
  170. llms_py-3.0.10.dist-info/METADATA +49 -0
  171. llms_py-3.0.10.dist-info/RECORD +177 -0
  172. {llms_py-2.0.20.dist-info → llms_py-3.0.10.dist-info}/licenses/LICENSE +1 -2
  173. llms/ui/Avatar.mjs +0 -28
  174. llms/ui/Brand.mjs +0 -34
  175. llms/ui/ChatPrompt.mjs +0 -443
  176. llms/ui/Main.mjs +0 -740
  177. llms/ui/ModelSelector.mjs +0 -60
  178. llms/ui/ProviderIcon.mjs +0 -29
  179. llms/ui/ProviderStatus.mjs +0 -105
  180. llms/ui/SignIn.mjs +0 -64
  181. llms/ui/SystemPromptEditor.mjs +0 -31
  182. llms/ui/SystemPromptSelector.mjs +0 -36
  183. llms/ui/Welcome.mjs +0 -8
  184. llms/ui/threadStore.mjs +0 -524
  185. llms/ui.json +0 -1069
  186. llms_py-2.0.20.dist-info/METADATA +0 -931
  187. llms_py-2.0.20.dist-info/RECORD +0 -36
  188. {llms_py-2.0.20.dist-info → llms_py-3.0.10.dist-info}/WHEEL +0 -0
  189. {llms_py-2.0.20.dist-info → llms_py-3.0.10.dist-info}/entry_points.txt +0 -0
  190. {llms_py-2.0.20.dist-info → llms_py-3.0.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,650 @@
1
+ import { ref, onMounted, watch, inject, nextTick } from "vue"
2
+ import { ApiResult, createErrorStatus } from "@servicestack/client"
3
+
4
+ let ext
5
+
6
+ const languages = {
7
+ python: {
8
+ name: 'Python',
9
+ mime: 'text/x-python',
10
+ default: 'print("Hello, Python!")\n',
11
+ },
12
+ javascript: {
13
+ name: 'JavaScript',
14
+ mime: 'text/javascript',
15
+ default: 'console.log("Hello, JavaScript!");\n',
16
+ },
17
+ typescript: {
18
+ name: 'TypeScript',
19
+ mime: 'text/typescript',
20
+ default: 'const msg: string = "Hello, TypeScript!";\nconsole.log(msg);\n',
21
+ },
22
+ csharp: {
23
+ name: 'C#',
24
+ mime: 'text/x-csharp',
25
+ default: 'Console.WriteLine("Hello, C#!");\n',
26
+ },
27
+ }
28
+
29
+ const CodePage = {
30
+ template: `
31
+ <div class="flex flex-col h-full w-full">
32
+ <component :is="'style'">
33
+ .CodeMirror { height: 100% !important; }
34
+ </component>
35
+ <!-- Toolbar -->
36
+ <div class="flex items-center justify-between p-2 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 shrink-0">
37
+ <div class="flex items-center space-x-1">
38
+ <button v-for="lang in Object.keys(languages)" :key="lang" type="button" @click="language = lang"
39
+ class="px-2.5 py-1 rounded-full text-xs font-medium border transition-colors select-none capitalize"
40
+ :class="language === lang
41
+ ? 'bg-blue-100 dark:bg-blue-900/40 text-blue-800 dark:text-blue-300 border-blue-200 dark:border-blue-800'
42
+ : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700'">
43
+ {{ languages[lang].name }}
44
+ </button>
45
+ </div>
46
+ <div class="flex items-center space-x-2">
47
+ <button @click="toggleOutput" class="p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-500" :title="showOutput ? 'Hide Output' : 'Show Output'">
48
+ <svg v-if="showOutput" xmlns="http://www.w3.org/2000/svg" class="size-5" viewBox="0 0 24 24"><path fill="currentColor" d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2m0 16H3v-3h18zm0-5H3V5h18z"/></svg>
49
+ <svg v-else xmlns="http://www.w3.org/2000/svg" class="size-5" viewBox="0 0 24 24"><path fill="currentColor" d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2m0 16H3V5h18z"/></svg>
50
+ </button>
51
+ <button @click="runCode" type="button" :disabled="loading" class="px-4 py-1.5 bg-blue-600 text-white rounded hover:bg-blue-700 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed flex items-center shadow-sm transition-colors">
52
+ <svg v-if="loading" class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
53
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
54
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
55
+ </svg>
56
+ <span v-else>Run</span>
57
+ <svg v-if="!loading" class="ml-1 size-5" fill="none" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M19.266 13.516a1.917 1.917 0 0 0 0-3.032A35.8 35.8 0 0 0 9.35 5.068l-.653-.232c-1.248-.443-2.567.401-2.736 1.69a42.5 42.5 0 0 0 0 10.948c.17 1.289 1.488 2.133 2.736 1.69l.653-.232a35.8 35.8 0 0 0 9.916-5.416"/></svg>
58
+ </button>
59
+ </div>
60
+ </div>
61
+
62
+ <!-- Main Content -->
63
+ <div class="flex-1 flex flex-col min-h-0">
64
+ <!-- Code Editor -->
65
+ <div class="flex-1 overflow-hidden relative">
66
+ <!-- The div CodeMirror attaches to. We use absolute positioning to ensure it takes full space of parent -->
67
+ <div ref="refInput" class="absolute inset-0 h-full w-full text-base"></div>
68
+ </div>
69
+
70
+ <!-- Output Pane -->
71
+ <div v-if="showOutput" class="h-1/3 min-h-[150px] border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 flex flex-col font-mono text-sm overflow-hidden shrink-0 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)] z-10">
72
+ <div class="px-2 py-1 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 text-xs font-semibold text-gray-500 uppercase flex justify-between items-center select-none">
73
+ <span>Output</span>
74
+ <div class="flex items-center">
75
+ <span v-if="resultStatus" class="mr-2 px-2 py-0.5 rounded text-[10px]" :class="resultStatusColor">{{ resultStatus }}</span>
76
+ <button @click="showOutput=false" type="button" class="hover:text-gray-700 dark:hover:text-gray-300">
77
+ <svg xmlns="http://www.w3.org/2000/svg" class="size-4" viewBox="0 0 24 24"><path fill="currentColor" 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>
78
+ </button>
79
+ </div>
80
+ </div>
81
+ <div class="flex-1 overflow-auto p-2 whitespace-pre-wrap font-mono relative">
82
+ <div v-if="loading" class="absolute inset-0 bg-white/50 dark:bg-gray-900/50 flex items-center justify-center z-10 transition-opacity">
83
+ <div class="animate-pulse text-blue-500">Executing...</div>
84
+ </div>
85
+ <div v-if="!stdout && !stderr && !resultStatus && !loading" class="text-gray-400 italic p-4 text-center">
86
+ Press CTRL+ENTER or click Run to execute code.
87
+ </div>
88
+ <div v-if="stdout" class="text-gray-800 dark:text-gray-300">{{ stdout }}</div>
89
+ <div v-if="stderr" class="text-red-600 dark:text-red-400 mt-2 border-t border-red-200 dark:border-red-900 pt-2">{{ stderr }}</div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ `,
95
+ setup() {
96
+ let cm
97
+ const refInput = ref()
98
+ const language = ref(localStorage.getItem('llms.tools.lastLanguage') || 'python')
99
+ const code = ref(localStorage.getItem(`llms.tools.${language.value}`) || '')
100
+ const stdout = ref('')
101
+ const stderr = ref('')
102
+ const loading = ref(false)
103
+ const resultStatus = ref('')
104
+ const resultStatusColor = ref('')
105
+ const showOutput = ref(true)
106
+
107
+ const loadCode = (lang) => {
108
+ const saved = localStorage.getItem(`llms.tools.${lang}`)
109
+ // Default snippets if empty
110
+ if (!saved || Object.values(languages).some(l => l.default.trim() === saved.trim())) {
111
+ return languages[lang].default
112
+ }
113
+ return saved
114
+ }
115
+
116
+ // Initial load
117
+ code.value = loadCode(language.value)
118
+
119
+
120
+
121
+ watch(language, (newLang, oldLang) => {
122
+ // Save old language code
123
+ if (oldLang && cm) {
124
+ const currentContent = cm.getValue()
125
+ localStorage.setItem(`llms.tools.${oldLang}`, currentContent)
126
+ }
127
+ localStorage.setItem('llms.tools.lastLanguage', newLang)
128
+
129
+ // Load new language code
130
+ code.value = loadCode(newLang)
131
+ if (cm) {
132
+ cm.setValue(code.value)
133
+ cm.setOption('mode', languages[newLang].mime)
134
+ }
135
+
136
+ // Clear output on language switch
137
+ stdout.value = ''
138
+ stderr.value = ''
139
+ resultStatus.value = ''
140
+ })
141
+
142
+ function setError(status) {
143
+ if (!status) return
144
+ if (typeof status == 'string') {
145
+ status = {
146
+ message: status,
147
+ errorCode: 'Error'
148
+ }
149
+ }
150
+ stderr.value = status.message
151
+ resultStatus.value = status.errorCode || 'Error'
152
+ resultStatusColor.value = 'text-red-600 bg-red-100 dark:text-red-400 dark:bg-red-900'
153
+ }
154
+
155
+ const toggleOutput = () => {
156
+ showOutput.value = !showOutput.value
157
+ nextTick(() => {
158
+ if (cm) cm.refresh()
159
+ })
160
+ }
161
+
162
+ const runCode = async () => {
163
+ if (loading.value) return
164
+
165
+ if (!showOutput.value) {
166
+ showOutput.value = true
167
+ nextTick(() => {
168
+ if (cm) cm.refresh()
169
+ })
170
+ }
171
+
172
+ // Save before run
173
+ if (cm) {
174
+ code.value = cm.getValue()
175
+ }
176
+ localStorage.setItem(`llms.tools.${language.value}`, code.value)
177
+
178
+ loading.value = true
179
+ stdout.value = ''
180
+ stderr.value = ''
181
+ resultStatus.value = ''
182
+ let api
183
+
184
+ try {
185
+ const res = await ext.post(`/code/${language.value}/run`, {
186
+ body: code.value
187
+ })
188
+ if (!res.ok) {
189
+ api = new ApiResult({ error: createErrorStatus(`HTTP ${res.status} ${res.statusText}`) })
190
+ } else {
191
+ const response = await res.json()
192
+ api = new ApiResult({ response })
193
+ }
194
+ } catch (e) {
195
+ api = new ApiResult({ error: createErrorStatus(e.message) })
196
+ }
197
+
198
+ if (api.response) {
199
+ const result = api.response
200
+ stdout.value = result.stdout || ''
201
+ stderr.value = result.stderr || ''
202
+
203
+ if (result.returncode === 0) {
204
+ resultStatus.value = 'Success'
205
+ resultStatusColor.value = 'text-green-600 bg-green-100 dark:text-green-400 dark:bg-green-900'
206
+ } else {
207
+ resultStatus.value = `Exit Code: ${result.returncode}`
208
+ resultStatusColor.value = 'text-red-600 bg-red-100 dark:text-red-400 dark:bg-red-900'
209
+ }
210
+ }
211
+ else if (api.error) {
212
+ setError(api.error)
213
+ }
214
+
215
+ loading.value = false
216
+ }
217
+
218
+ onMounted(() => {
219
+ // Ensure CodeMirror is global
220
+ if (typeof CodeMirror === 'undefined') {
221
+ console.error('CodeMirror is not loaded')
222
+ return
223
+ }
224
+
225
+ cm = CodeMirror(refInput.value, {
226
+ lineNumbers: true,
227
+ styleActiveLine: true,
228
+ matchBrackets: true,
229
+ mode: languages[language.value].mime,
230
+ theme: 'ctp-mocha', // using the theme from existing code
231
+ value: code.value,
232
+ extraKeys: {
233
+ "Ctrl-Enter": () => runCode(),
234
+ "Cmd-Enter": () => runCode(), // Mac support
235
+ },
236
+ tabSize: 4,
237
+ indentUnit: 4,
238
+ lineWrapping: false, // Code editors usually don't wrap by default, but customizable
239
+ })
240
+
241
+ cm.on('change', () => {
242
+ code.value = cm.getValue()
243
+ localStorage.setItem(`llms.tools.${language.value}`, code.value)
244
+ })
245
+
246
+ // Fix layout issues when resizing
247
+ window.addEventListener('resize', () => {
248
+ cm.refresh()
249
+ })
250
+ })
251
+
252
+ return {
253
+ languages,
254
+ refInput,
255
+ stdout,
256
+ stderr,
257
+ loading,
258
+ resultStatus,
259
+ resultStatusColor,
260
+ language,
261
+ code,
262
+ showOutput,
263
+ toggleOutput,
264
+ runCode,
265
+ }
266
+ }
267
+ }
268
+
269
+ const CalcPage = {
270
+ template: `
271
+ <div class="flex flex-col h-full w-full bg-white dark:bg-gray-900 text-base">
272
+ <!-- Header/Input Area -->
273
+ <div class="p-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 shrink-0">
274
+ <div class="max-w-3xl mx-auto w-full">
275
+ <form @submit.prevent="calculate" class="relative">
276
+ <input
277
+ ref="inputRef"
278
+ v-model="expression"
279
+ type="text"
280
+ placeholder="Type an expression (e.g. 1 + 2 * 3) and press Enter"
281
+ class="w-full px-4 py-3 pr-12 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none shadow-sm transition-all"
282
+ :disabled="loading"
283
+ autofocus
284
+ />
285
+ <button
286
+ type="submit"
287
+ :disabled="loading || !expression.trim()"
288
+ class="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-gray-600 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
289
+ title="Calculate"
290
+ >
291
+ <svg v-if="loading" class="animate-spin size-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
292
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
293
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
294
+ </svg>
295
+ <svg v-else class="size-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" d="M16 14H8m8-4H8"/><circle cx="12" cy="12" r="10"/></g></svg>
296
+ </button>
297
+ </form>
298
+ <div v-if="error" class="mt-2 text-sm text-red-600 dark:text-red-400">
299
+ {{ error }}
300
+ </div>
301
+ </div>
302
+ </div>
303
+
304
+ <!-- History List -->
305
+ <div class="flex-1 overflow-auto p-4">
306
+ <div class="max-w-3xl mx-auto w-full space-y-3">
307
+ <div v-if="history.length === 0" class="text-center text-gray-400 dark:text-gray-500 py-10 italic">
308
+ No calculation history.
309
+ </div>
310
+
311
+ <div v-for="(item, index) in history" :key="index" class="group bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4 shadow-sm hover:shadow-md transition-all">
312
+ <div class="flex items-center justify-between gap-4">
313
+ <div class="flex-1 space-y-1 min-w-0">
314
+ <!-- Expression -->
315
+ <div class="flex items-center gap-2 group/expr cursor-pointer" @click="useResult(item.expression, item, 'expr')">
316
+ <span
317
+ class="font-mono text-gray-500 dark:text-gray-400 group-hover/expr:text-blue-600 dark:group-hover/expr:text-blue-400 transition-colors select-none"
318
+ title="Click to copy & use"
319
+ >
320
+ {{ item.expression }} =
321
+ </span>
322
+ <button
323
+ type="button"
324
+ class="opacity-0 group-hover/expr:opacity-100 p-1 text-gray-400 group-hover/expr:text-blue-500 transition-opacity"
325
+ title="Copy expression"
326
+ >
327
+ <svg v-if="item.copiedExpr" class="size-3.5 text-green-600 dark:text-green-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="m9.55 18l-5.7-5.7l1.425-1.425L9.55 15.15l9.175-9.175L20.15 7.4z"/></svg>
328
+ <svg v-else xmlns="http://www.w3.org/2000/svg" class="size-3.5" viewBox="0 0 24 24"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2m0 16H8V7h11z"/></svg>
329
+ </button>
330
+ </div>
331
+
332
+ <!-- Answer -->
333
+ <div class="flex items-center gap-2 group/ans cursor-pointer" @click="useResult(item.answer, item, 'ans')">
334
+ <span
335
+ class="font-mono text-xl font-semibold text-gray-900 dark:text-white group-hover/ans:text-blue-600 dark:group-hover/ans:text-blue-400 transition-colors break-all"
336
+ title="Click to copy & use"
337
+ >
338
+ {{ item.answer }}
339
+ </span>
340
+ <button
341
+ type="button"
342
+ class="opacity-0 group-hover/ans:opacity-100 p-1 text-gray-400 group-hover/ans:text-blue-500 transition-opacity"
343
+ title="Copy answer"
344
+ >
345
+ <svg v-if="item.copiedAns" class="size-4 text-green-600 dark:text-green-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="m9.55 18l-5.7-5.7l1.425-1.425L9.55 15.15l9.175-9.175L20.15 7.4z"/></svg>
346
+ <svg v-else xmlns="http://www.w3.org/2000/svg" class="size-4" viewBox="0 0 24 24"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2m0 16H8V7h11z"/></svg>
347
+ </button>
348
+ </div>
349
+ </div>
350
+
351
+ <!-- Delete Button -->
352
+ <button
353
+ type="button"
354
+ @click="remove(index)"
355
+ class="opacity-0 group-hover:opacity-100 p-2 text-gray-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/30 rounded-md transition-all"
356
+ title="Delete"
357
+ >
358
+ <svg class="size-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
359
+ </button>
360
+ </div>
361
+ </div>
362
+
363
+ <div v-if="history.length" class="flex justify-center pt-4">
364
+ <button
365
+ type="button"
366
+ @click="clearAll"
367
+ class="text-sm text-gray-500 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-300"
368
+ >
369
+ clear all history
370
+ </button>
371
+ </div>
372
+
373
+ <!-- Features (Operators & Functions) -->
374
+ <div v-if="features.operators?.length || features.functions?.length" class="mt-12 mb-6 space-y-4">
375
+ <!-- Numbers -->
376
+ <div>
377
+ <div class="flex flex-wrap gap-2">
378
+ <button
379
+ v-for="num in features.numbers"
380
+ :key="num"
381
+ type="button"
382
+ @click="insert(num)"
383
+ class="px-3 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-blue-100 dark:hover:bg-blue-900/30 text-gray-700 dark:text-gray-300 hover:text-blue-700 dark:hover:text-blue-300 border border-gray-200 dark:border-gray-700 rounded text-sm font-mono transition-colors"
384
+ :title="'insert number ' + num"
385
+ >
386
+ {{ num }}
387
+ </button>
388
+ <span class="px-1 py-1 text-gray-400 dark:text-gray-600">|</span>
389
+ <button
390
+ v-for="c in features.constants"
391
+ :key="c"
392
+ type="button"
393
+ @click="insert(c)"
394
+ class="px-3 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-blue-100 dark:hover:bg-blue-900/30 text-gray-700 dark:text-gray-300 hover:text-blue-700 dark:hover:text-blue-300 border border-gray-200 dark:border-gray-700 rounded text-sm font-mono transition-colors"
395
+ :title="'insert constant ' + c"
396
+ >
397
+ {{ c }}
398
+ </button>
399
+ </div>
400
+ </div>
401
+
402
+ <!-- Operators -->
403
+ <div v-if="features.operators?.length">
404
+ <div class="flex flex-wrap gap-2">
405
+ <button
406
+ v-for="op in features.operators"
407
+ :key="op"
408
+ type="button"
409
+ @click="insert(op)"
410
+ class="px-3 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-blue-100 dark:hover:bg-blue-900/30 text-gray-700 dark:text-gray-300 hover:text-blue-700 dark:hover:text-blue-300 border border-gray-200 dark:border-gray-700 rounded text-sm font-mono transition-colors"
411
+ :title="'insert operator ' + op"
412
+ >
413
+ {{ op }}
414
+ </button>
415
+ </div>
416
+ </div>
417
+
418
+ <!-- Functions -->
419
+ <div v-if="features.functions?.length">
420
+ <h3 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Functions</h3>
421
+ <div class="flex flex-wrap gap-2">
422
+ <button
423
+ v-for="func in features.functions"
424
+ :key="func"
425
+ type="button"
426
+ @click="wrapWithFunction(func)"
427
+ class="px-3 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-purple-100 dark:hover:bg-purple-900/30 text-gray-700 dark:text-gray-300 hover:text-purple-700 dark:hover:text-purple-300 border border-gray-200 dark:border-gray-700 rounded text-sm font-mono transition-colors"
428
+ :title="'use function ' + func"
429
+ >
430
+ {{ func }}
431
+ </button>
432
+ </div>
433
+ </div>
434
+ </div>
435
+ </div>
436
+ </div>
437
+ </div>
438
+ `,
439
+ setup() {
440
+ const ctx = inject('ctx')
441
+ const expression = ref('')
442
+ const history = ref([])
443
+ const loading = ref(false)
444
+ const error = ref('')
445
+ const inputRef = ref()
446
+ const features = ref({ functions: [] })
447
+
448
+ // Load history from localStorage
449
+ try {
450
+ const saved = localStorage.getItem('llms.tools.calc.history')
451
+ if (saved) {
452
+ history.value = JSON.parse(saved)
453
+ }
454
+ } catch (e) {
455
+ console.error('Failed to load history', e)
456
+ }
457
+
458
+ function setExpr(result) {
459
+ if (Array.isArray(result)) {
460
+ expression.value = JSON.stringify(result)
461
+ } else {
462
+ expression.value = String(result)
463
+ }
464
+ }
465
+
466
+ const saveHistory = () => {
467
+ localStorage.setItem('llms.tools.calc.history', JSON.stringify(history.value))
468
+ }
469
+
470
+ const calculate = async () => {
471
+ if (!expression.value.trim() || loading.value) return
472
+
473
+ loading.value = true
474
+ error.value = ''
475
+ const expr = expression.value
476
+
477
+ const res = await ext.post('/calc', {
478
+ body: expr
479
+ })
480
+
481
+ const api = await ext.createJsonResult(res)
482
+
483
+ if (api.response) {
484
+ // Add to history (newest first)
485
+ history.value.unshift({
486
+ expression: expr,
487
+ answer: api.response.result,
488
+ timestamp: Date.now()
489
+ })
490
+
491
+ // Keep history size reasonable
492
+ if (history.value.length > 50) {
493
+ history.value = history.value.slice(0, 50)
494
+ }
495
+
496
+ saveHistory()
497
+ setExpr(api.response.result)
498
+ } else {
499
+ error.value = api.error.message
500
+ }
501
+
502
+ loading.value = false
503
+ // Refocus input and move cursor to end
504
+ nextTick(() => {
505
+ if (inputRef.value) {
506
+ inputRef.value.focus()
507
+ const len = inputRef.value.value.length
508
+ inputRef.value.setSelectionRange(len, len)
509
+ }
510
+ })
511
+ }
512
+
513
+ const populate = (result) => {
514
+ setExpr(result)
515
+ inputRef.value?.focus()
516
+ }
517
+
518
+ const insert = (text) => {
519
+ expression.value += String(text)
520
+ inputRef.value?.focus()
521
+ }
522
+
523
+ const wrapWithFunction = (fn) => {
524
+ const input = inputRef.value
525
+ if (!input) return
526
+
527
+ const start = input.selectionStart
528
+ const end = input.selectionEnd
529
+ const val = expression.value
530
+
531
+ if (start !== end) {
532
+ // Wrap selection
533
+ const selected = val.substring(start, end)
534
+ const before = val.substring(0, start)
535
+ const after = val.substring(end)
536
+ expression.value = `${before}${fn}(${selected})${after}`
537
+
538
+ nextTick(() => {
539
+ input.focus()
540
+ // Position cursor after the closing parenthesis
541
+ const newPos = start + fn.length + 1 + selected.length + 1
542
+ input.setSelectionRange(newPos, newPos)
543
+ })
544
+ } else if (val) {
545
+ // Wrap entire expression
546
+ expression.value = `${fn}(${val})`
547
+ nextTick(() => {
548
+ input.focus()
549
+ // Position cursor at end
550
+ const len = expression.value.length
551
+ input.setSelectionRange(len, len)
552
+ })
553
+ } else {
554
+ // Just insert empty function
555
+ expression.value = `${fn}()`
556
+ nextTick(() => {
557
+ input.focus()
558
+ // Position cursor inside parentheses
559
+ const pos = fn.length + 1
560
+ input.setSelectionRange(pos, pos)
561
+ })
562
+ }
563
+ }
564
+
565
+ const copy = (text) => {
566
+ navigator.clipboard.writeText(String(text))
567
+ }
568
+
569
+ const useResult = (text, item, type) => {
570
+ populate(text)
571
+ const str = String(text)
572
+ copy(str)
573
+
574
+ // Set temporary success state
575
+ if (type === 'expr') item.copiedExpr = true
576
+ else if (type === 'ans') item.copiedAns = true
577
+
578
+ setTimeout(() => {
579
+ if (type === 'expr') item.copiedExpr = false
580
+ else if (type === 'ans') item.copiedAns = false
581
+ }, 2000)
582
+
583
+ ctx.toast('Copied to clipboard')
584
+ }
585
+
586
+ const remove = (index) => {
587
+ history.value.splice(index, 1)
588
+ saveHistory()
589
+ }
590
+
591
+ const clearAll = () => {
592
+ if (confirm('Are you sure you want to clear all history?')) {
593
+ history.value = []
594
+ saveHistory()
595
+ }
596
+ }
597
+
598
+ onMounted(async () => {
599
+ const api = await ext.getJson('/calc')
600
+ features.value = api.response
601
+ console.log(features.value)
602
+ })
603
+
604
+ return {
605
+ expression,
606
+ history,
607
+ loading,
608
+ error,
609
+ inputRef,
610
+ calculate,
611
+ useResult,
612
+ remove,
613
+ clearAll,
614
+ features,
615
+ insert,
616
+ wrapWithFunction,
617
+ }
618
+ }
619
+ }
620
+
621
+ export default {
622
+ install(ctx) {
623
+ ext = ctx.scope('core_tools')
624
+
625
+ ctx.setLeftIcons({
626
+ code: {
627
+ component: {
628
+ template: `<svg @click="$ctx.togglePath('/code')" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none"><path d="M0 0h24v24H0z"/><path fill="currentColor" d="M14.486 3.143a1 1 0 0 1 .692 1.233l-4.43 15.788a1 1 0 0 1-1.926-.54l4.43-15.788a1 1 0 0 1 1.234-.693M7.207 7.05a1 1 0 0 1 0 1.414L3.672 12l3.535 3.535a1 1 0 1 1-1.414 1.415L1.55 12.707a1 1 0 0 1 0-1.414L5.793 7.05a1 1 0 0 1 1.414 0m9.586 1.414a1 1 0 1 1 1.414-1.414l4.243 4.243a1 1 0 0 1 0 1.414l-4.243 4.243a1 1 0 0 1-1.414-1.415L20.328 12z"/></g></svg>`
629
+ },
630
+ isActive({ path }) {
631
+ return path === '/code'
632
+ },
633
+ title: 'Run Code',
634
+ },
635
+ calc: {
636
+ component: {
637
+ template: `<svg @click="$ctx.togglePath('/calc')" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"><path d="M11.5.5h-9a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1v-11a1 1 0 0 0-1-1m-10 5h11"/><path d="M4.25 8.5a.25.25 0 0 1 0-.5m0 .5a.25.25 0 0 0 0-.5M7 8.5A.25.25 0 0 1 7 8m0 .5A.25.25 0 0 0 7 8m2.75.5a.25.25 0 0 1 0-.5m0 .5a.25.25 0 0 0 0-.5m-5.5 3a.25.25 0 1 1 0-.5m0 .5a.25.25 0 1 0 0-.5M7 11a.25.25 0 1 1 0-.5m0 .5a.25.25 0 1 0 0-.5m2.75.5a.25.25 0 1 1 0-.5m0 .5a.25.25 0 1 0 0-.5M10 3H9"/></g></svg>`,
638
+ },
639
+ isActive({ path }) {
640
+ return path === '/calc'
641
+ },
642
+ title: 'Calculator',
643
+ }
644
+ })
645
+
646
+ ctx.routes.push({ path: '/code', component: CodePage, meta: { title: 'Run Code' } })
647
+ ctx.routes.push({ path: '/calc', component: CalcPage, meta: { title: 'Calculator' } })
648
+
649
+ }
650
+ }