llms-py 2.0.20__py3-none-any.whl → 3.0.18__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.
- llms/__init__.py +3 -1
- llms/db.py +359 -0
- llms/{ui/Analytics.mjs → extensions/analytics/ui/index.mjs} +254 -327
- llms/extensions/app/README.md +20 -0
- llms/extensions/app/__init__.py +588 -0
- llms/extensions/app/db.py +540 -0
- llms/{ui → extensions/app/ui}/Recents.mjs +99 -73
- llms/{ui/Sidebar.mjs → extensions/app/ui/index.mjs} +139 -68
- llms/extensions/app/ui/threadStore.mjs +440 -0
- llms/extensions/computer/README.md +96 -0
- llms/extensions/computer/__init__.py +59 -0
- llms/extensions/computer/base.py +80 -0
- llms/extensions/computer/bash.py +185 -0
- llms/extensions/computer/computer.py +523 -0
- llms/extensions/computer/edit.py +299 -0
- llms/extensions/computer/filesystem.py +542 -0
- llms/extensions/computer/platform.py +461 -0
- llms/extensions/computer/run.py +37 -0
- llms/extensions/core_tools/CALCULATOR.md +32 -0
- llms/extensions/core_tools/__init__.py +599 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
- llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
- llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
- llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
- llms/extensions/core_tools/ui/codemirror/codemirror.css +344 -0
- llms/extensions/core_tools/ui/codemirror/codemirror.js +9884 -0
- llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
- llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
- llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
- llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
- llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
- llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
- llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
- llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
- llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
- llms/extensions/core_tools/ui/index.mjs +650 -0
- llms/extensions/gallery/README.md +61 -0
- llms/extensions/gallery/__init__.py +63 -0
- llms/extensions/gallery/db.py +243 -0
- llms/extensions/gallery/ui/index.mjs +482 -0
- llms/extensions/katex/README.md +39 -0
- llms/extensions/katex/__init__.py +6 -0
- llms/extensions/katex/ui/README.md +125 -0
- llms/extensions/katex/ui/contrib/auto-render.js +338 -0
- llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
- llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
- llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
- llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
- llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
- llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
- llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
- llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
- llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
- llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
- llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
- llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
- llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
- llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
- llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- llms/extensions/katex/ui/index.mjs +92 -0
- llms/extensions/katex/ui/katex-swap.css +1230 -0
- llms/extensions/katex/ui/katex-swap.min.css +1 -0
- llms/extensions/katex/ui/katex.css +1230 -0
- llms/extensions/katex/ui/katex.js +19080 -0
- llms/extensions/katex/ui/katex.min.css +1 -0
- llms/extensions/katex/ui/katex.min.js +1 -0
- llms/extensions/katex/ui/katex.min.mjs +1 -0
- llms/extensions/katex/ui/katex.mjs +18547 -0
- llms/extensions/providers/__init__.py +22 -0
- llms/extensions/providers/anthropic.py +260 -0
- llms/extensions/providers/cerebras.py +36 -0
- llms/extensions/providers/chutes.py +153 -0
- llms/extensions/providers/google.py +559 -0
- llms/extensions/providers/nvidia.py +103 -0
- llms/extensions/providers/openai.py +154 -0
- llms/extensions/providers/openrouter.py +74 -0
- llms/extensions/providers/zai.py +182 -0
- llms/extensions/skills/LICENSE +202 -0
- llms/extensions/skills/__init__.py +130 -0
- llms/extensions/skills/errors.py +25 -0
- llms/extensions/skills/models.py +39 -0
- llms/extensions/skills/parser.py +178 -0
- llms/extensions/skills/ui/index.mjs +376 -0
- llms/extensions/skills/ui/skills/create-plan/SKILL.md +74 -0
- llms/extensions/skills/validator.py +177 -0
- llms/extensions/system_prompts/README.md +22 -0
- llms/extensions/system_prompts/__init__.py +45 -0
- llms/extensions/system_prompts/ui/index.mjs +276 -0
- llms/extensions/system_prompts/ui/prompts.json +1067 -0
- llms/extensions/tools/__init__.py +67 -0
- llms/extensions/tools/ui/index.mjs +837 -0
- llms/index.html +36 -62
- llms/llms.json +180 -879
- llms/main.py +4009 -912
- llms/providers-extra.json +394 -0
- llms/providers.json +1 -0
- llms/ui/App.mjs +176 -8
- llms/ui/ai.mjs +156 -20
- llms/ui/app.css +3768 -321
- llms/ui/ctx.mjs +459 -0
- llms/ui/index.mjs +131 -0
- llms/ui/lib/chart.js +14 -0
- llms/ui/lib/charts.mjs +16 -0
- llms/ui/lib/color.js +14 -0
- llms/ui/lib/highlight.min.mjs +1243 -0
- llms/ui/lib/idb.min.mjs +8 -0
- llms/ui/lib/marked.min.mjs +8 -0
- llms/ui/lib/servicestack-client.mjs +1 -0
- llms/ui/lib/servicestack-vue.mjs +37 -0
- llms/ui/lib/vue-router.min.mjs +6 -0
- llms/ui/lib/vue.min.mjs +13 -0
- llms/ui/lib/vue.mjs +18530 -0
- llms/ui/markdown.mjs +25 -14
- llms/ui/modules/chat/ChatBody.mjs +1156 -0
- llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +74 -74
- llms/ui/modules/chat/index.mjs +995 -0
- llms/ui/modules/icons.mjs +46 -0
- llms/ui/modules/layout.mjs +271 -0
- llms/ui/modules/model-selector.mjs +811 -0
- llms/ui/tailwind.input.css +560 -78
- llms/ui/typography.css +54 -36
- llms/ui/utils.mjs +221 -92
- llms_py-3.0.18.dist-info/METADATA +49 -0
- llms_py-3.0.18.dist-info/RECORD +194 -0
- {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/WHEEL +1 -1
- {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/licenses/LICENSE +1 -2
- llms/ui/Avatar.mjs +0 -28
- llms/ui/Brand.mjs +0 -34
- llms/ui/ChatPrompt.mjs +0 -443
- llms/ui/Main.mjs +0 -740
- llms/ui/ModelSelector.mjs +0 -60
- llms/ui/ProviderIcon.mjs +0 -29
- llms/ui/ProviderStatus.mjs +0 -105
- llms/ui/SignIn.mjs +0 -64
- llms/ui/SystemPromptEditor.mjs +0 -31
- llms/ui/SystemPromptSelector.mjs +0 -36
- llms/ui/Welcome.mjs +0 -8
- llms/ui/threadStore.mjs +0 -524
- llms/ui.json +0 -1069
- llms_py-2.0.20.dist-info/METADATA +0 -931
- llms_py-2.0.20.dist-info/RECORD +0 -36
- {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/entry_points.txt +0 -0
- {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
import { inject, computed, ref, onMounted } from "vue"
|
|
2
|
+
|
|
3
|
+
let ext
|
|
4
|
+
|
|
5
|
+
function useTools(ctx) {
|
|
6
|
+
|
|
7
|
+
const availableTools = computed(() => ctx.state.tool.definitions.filter(x => x.function))
|
|
8
|
+
const toolPageHeaders = {}
|
|
9
|
+
|
|
10
|
+
function setToolPageHeaders(components) {
|
|
11
|
+
Object.assign(toolPageHeaders, components)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function selectTool({ group, tool }) {
|
|
15
|
+
ext.setPrefs({ selectedGroup: group, selectedTool: tool })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getToolDefinition(name) {
|
|
19
|
+
return ctx.state.tool.definitions.find(d => d.function?.name === name)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isToolEnabled(name) {
|
|
23
|
+
const toolDef = getToolDefinition(name)
|
|
24
|
+
if (!toolDef) return false
|
|
25
|
+
const onlyTools = ctx.prefs.onlyTools
|
|
26
|
+
if (onlyTools == null) return true
|
|
27
|
+
return Array.isArray(onlyTools) && onlyTools.includes(name)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function enableTool(name) {
|
|
31
|
+
let onlyTools = ctx.prefs.onlyTools
|
|
32
|
+
if (onlyTools == null) return // All tools are enabled
|
|
33
|
+
|
|
34
|
+
if (!Array.isArray(onlyTools)) {
|
|
35
|
+
onlyTools = [onlyTools]
|
|
36
|
+
}
|
|
37
|
+
else if (!onlyTools.includes(name)) {
|
|
38
|
+
onlyTools.push(name)
|
|
39
|
+
} else {
|
|
40
|
+
return // Already enabled
|
|
41
|
+
}
|
|
42
|
+
ctx.setPrefs({ onlyTools })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function disableTool(name) {
|
|
46
|
+
let onlyTools = ctx.prefs.onlyTools
|
|
47
|
+
if (onlyTools == null) {
|
|
48
|
+
// If currently 'All', clicking a tool means we enter custom mode with all OTHER tools selected
|
|
49
|
+
onlyTools = availableTools.value.map(t => t.function.name).filter(t => t !== name)
|
|
50
|
+
} else if (!Array.isArray(onlyTools)) {
|
|
51
|
+
onlyTools = []
|
|
52
|
+
} else {
|
|
53
|
+
onlyTools = onlyTools.filter(t => t !== name)
|
|
54
|
+
}
|
|
55
|
+
ctx.setPrefs({ onlyTools })
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function toggleTool(name, enable = null) {
|
|
59
|
+
if (enable == null) {
|
|
60
|
+
enable = !isToolEnabled(name)
|
|
61
|
+
}
|
|
62
|
+
if (enable) {
|
|
63
|
+
enableTool(name)
|
|
64
|
+
} else {
|
|
65
|
+
disableTool(name)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
availableTools,
|
|
71
|
+
toolPageHeaders,
|
|
72
|
+
setToolPageHeaders,
|
|
73
|
+
selectTool,
|
|
74
|
+
getToolDefinition,
|
|
75
|
+
isToolEnabled,
|
|
76
|
+
enableTool,
|
|
77
|
+
disableTool,
|
|
78
|
+
toggleTool,
|
|
79
|
+
get selectedGroup() { return ext.prefs.selectedGroup },
|
|
80
|
+
get selectedTool() { return ext.prefs.selectedTool },
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
const ToolResult = {
|
|
86
|
+
template: `
|
|
87
|
+
<div>
|
|
88
|
+
<div class="flex items-center gap-2 text-[10px] uppercase tracking-wider font-medium select-none">
|
|
89
|
+
<span @click="ext.setPrefs({ toolFormat: 'text' })"
|
|
90
|
+
class="cursor-pointer transition-colors"
|
|
91
|
+
:class="ext.prefs.toolFormat !== 'preview' ? 'text-gray-600 dark:text-gray-300' : 'text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'">
|
|
92
|
+
text
|
|
93
|
+
</span>
|
|
94
|
+
<span class="text-gray-300 dark:text-gray-700">|</span>
|
|
95
|
+
<span @click="ext.setPrefs({ toolFormat: 'preview' })"
|
|
96
|
+
class="cursor-pointer transition-colors"
|
|
97
|
+
:class="ext.prefs.toolFormat == 'preview' ? 'text-gray-600 dark:text-gray-300' : 'text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'">
|
|
98
|
+
preview
|
|
99
|
+
</span>
|
|
100
|
+
</div>
|
|
101
|
+
<div class="not-prose py-2">
|
|
102
|
+
<pre v-if="ext.prefs.toolFormat !== 'preview'" class="tool-output">{{ origResult }}</pre>
|
|
103
|
+
<div v-else>
|
|
104
|
+
<ViewTypes v-if="Array.isArray(result) && result[0]?.type" :results="result" />
|
|
105
|
+
<ViewType v-else :result="result" />
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
`,
|
|
110
|
+
props: {
|
|
111
|
+
result: {
|
|
112
|
+
required: true
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
setup(props) {
|
|
116
|
+
|
|
117
|
+
const origResult = computed(() => {
|
|
118
|
+
let ret = props.result
|
|
119
|
+
if (Array.isArray(props.result) && props.result.length == 1) {
|
|
120
|
+
ret = props.result[0]
|
|
121
|
+
}
|
|
122
|
+
if (ret.type) {
|
|
123
|
+
if (ret.type === "text") {
|
|
124
|
+
return ret.text
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return props.result
|
|
128
|
+
})
|
|
129
|
+
const displayResult = computed(() => {
|
|
130
|
+
try {
|
|
131
|
+
let result = typeof props.result == 'string'
|
|
132
|
+
? JSON.parse(props.result)
|
|
133
|
+
: props.result
|
|
134
|
+
if (Array.isArray(result) && result.length == 1) {
|
|
135
|
+
result = result[0]
|
|
136
|
+
}
|
|
137
|
+
if (result.type) {
|
|
138
|
+
if (result.type === "text") {
|
|
139
|
+
try {
|
|
140
|
+
return JSON.parse(result.text)
|
|
141
|
+
} catch (e) {
|
|
142
|
+
return result.text
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return result
|
|
147
|
+
} catch (e) {
|
|
148
|
+
return props.result
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
return {
|
|
152
|
+
ext,
|
|
153
|
+
origResult,
|
|
154
|
+
displayResult,
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const JsonInput = {
|
|
160
|
+
template: `
|
|
161
|
+
<div class="flex flex-col gap-1">
|
|
162
|
+
<div class="relative">
|
|
163
|
+
<textarea
|
|
164
|
+
v-model="localJson"
|
|
165
|
+
@input="validate"
|
|
166
|
+
rows="5"
|
|
167
|
+
class="w-full p-2 font-mono text-xs border rounded-md resize-y focus:outline-none focus:ring-2 transition-colors"
|
|
168
|
+
:class="error
|
|
169
|
+
? 'border-red-300 dark:border-red-700 bg-red-50 dark:bg-red-900/10 focus:ring-red-500'
|
|
170
|
+
: 'border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 focus:ring-blue-500'"
|
|
171
|
+
spellcheck="false"
|
|
172
|
+
></textarea>
|
|
173
|
+
<div v-if="isValid" class="absolute bottom-2 right-2 text-green-500 bg-white dark:bg-gray-800 rounded-full p-1 shadow-sm">
|
|
174
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
|
175
|
+
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
|
176
|
+
</svg>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
<div v-if="error" class="text-xs text-red-600 dark:text-red-400 font-medium px-1">
|
|
180
|
+
{{ error }}
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
`,
|
|
184
|
+
props: {
|
|
185
|
+
modelValue: {
|
|
186
|
+
required: true
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
emits: ['update:modelValue'],
|
|
190
|
+
setup(props, { emit }) {
|
|
191
|
+
// Initialize with formatted JSON
|
|
192
|
+
const localJson = ref(
|
|
193
|
+
props.modelValue !== undefined
|
|
194
|
+
? JSON.stringify(props.modelValue, null, 4)
|
|
195
|
+
: ''
|
|
196
|
+
)
|
|
197
|
+
const error = ref(null)
|
|
198
|
+
const isValid = ref(true)
|
|
199
|
+
|
|
200
|
+
function validate() {
|
|
201
|
+
try {
|
|
202
|
+
if (!localJson.value.trim()) {
|
|
203
|
+
// Decide if empty string is valid object/array or undefined
|
|
204
|
+
// For now, let's say empty is NOT valid if the prop expects object
|
|
205
|
+
// But maybe we can treat it as valid undefined/null?
|
|
206
|
+
// Let's enforce valid JSON.
|
|
207
|
+
if (localJson.value === '') {
|
|
208
|
+
error.value = null
|
|
209
|
+
isValid.value = true
|
|
210
|
+
emit('update:modelValue', undefined)
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const parsed = JSON.parse(localJson.value)
|
|
216
|
+
error.value = null
|
|
217
|
+
isValid.value = true
|
|
218
|
+
emit('update:modelValue', parsed)
|
|
219
|
+
} catch (e) {
|
|
220
|
+
error.value = e.message
|
|
221
|
+
isValid.value = false
|
|
222
|
+
// Do not emit invalid values
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Watch external changes only if they differ significantly from local
|
|
227
|
+
/*
|
|
228
|
+
Note: two-way binding with text representation is tricky.
|
|
229
|
+
If we watch props.modelValue, we might re-format user's in-progress typing if we aren't careful.
|
|
230
|
+
Usually better to only update localJson if the prop changes "from outside".
|
|
231
|
+
For this simple tool, initial value is likely enough, or we can watch with a deep compare check.
|
|
232
|
+
For now, let's stick to initial + internal validation.
|
|
233
|
+
*/
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
localJson,
|
|
237
|
+
error,
|
|
238
|
+
isValid,
|
|
239
|
+
validate
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const Tools = {
|
|
245
|
+
template: `
|
|
246
|
+
<div class="p-4 md:p-6 max-w-7xl mx-auto w-full relative">
|
|
247
|
+
<div v-if="Object.keys($ctx.tools.toolPageHeaders).length">
|
|
248
|
+
<div v-for="(component, key) in $ctx.tools.toolPageHeaders" :key="key">
|
|
249
|
+
<component :is="component" />
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
<div ref="refTop" class="mb-6 flex flex-col md:flex-row md:items-center justify-between gap-4">
|
|
253
|
+
<div>
|
|
254
|
+
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Tools</h1>
|
|
255
|
+
<p class="text-gray-600 dark:text-gray-400 mt-1">
|
|
256
|
+
{{ filteredTools.length }} tools available
|
|
257
|
+
</p>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div v-if="groups.length > 0" class="flex flex-wrap items-center gap-2">
|
|
261
|
+
<button @click="ext.setPrefs({ selectedGroup: 'All' })"
|
|
262
|
+
class="px-2.5 py-1 rounded-full text-xs font-medium border transition-colors select-none"
|
|
263
|
+
:class="ext.prefs.selectedGroup === 'All'
|
|
264
|
+
? 'bg-green-100 dark:bg-green-900/40 text-green-800 dark:text-green-300 border-green-300 dark:border-green-800'
|
|
265
|
+
: 'cursor-pointer bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'">
|
|
266
|
+
All
|
|
267
|
+
</button>
|
|
268
|
+
|
|
269
|
+
<div class="border-l h-4 mx-1 border-gray-300 dark:border-gray-600"></div>
|
|
270
|
+
|
|
271
|
+
<button v-for="group in groups" :key="group"
|
|
272
|
+
@click="ext.setPrefs({ selectedGroup: group})"
|
|
273
|
+
class="px-2.5 py-1 rounded-full text-xs font-medium border transition-colors select-none"
|
|
274
|
+
:class="ext.prefs.selectedGroup === group
|
|
275
|
+
? 'bg-blue-100 dark:bg-blue-900/40 text-blue-800 dark:text-blue-300 border-blue-200 dark:border-blue-800'
|
|
276
|
+
: 'cursor-pointer bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'">
|
|
277
|
+
{{ group }}
|
|
278
|
+
</button>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
<!-- Execution Form Panel -->
|
|
283
|
+
<div v-if="executingTool" class="mb-8 bg-white dark:bg-gray-800 rounded-lg border border-blue-200 dark:border-blue-800 shadow-sm overflow-hidden animate-in fade-in slide-in-from-top-4 duration-200">
|
|
284
|
+
<div class="bg-blue-50 dark:bg-blue-900/30 px-4 py-3 border-b border-blue-100 dark:border-blue-800 flex justify-between items-center">
|
|
285
|
+
<div class="flex items-center gap-2">
|
|
286
|
+
<h3 class="font-bold text-gray-900 dark:text-gray-100">Execute: <span class="font-mono text-blue-600 dark:text-blue-400">{{ executingTool.function.name }}</span></h3>
|
|
287
|
+
</div>
|
|
288
|
+
<button @click="closeExec" type="button" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
|
289
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
290
|
+
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
291
|
+
</svg>
|
|
292
|
+
</button>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
<div class="p-4 md:p-6">
|
|
296
|
+
<form ref="refForm" @submit.prevent="execTool" class="space-y-4">
|
|
297
|
+
<div v-if="Object.keys(executingTool.function.parameters?.properties || {}).length > 0" class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
298
|
+
<div v-for="(prop, name) in executingTool.function.parameters.properties" :key="name">
|
|
299
|
+
<label :for="'input-' + name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
300
|
+
{{ name }}
|
|
301
|
+
<span v-if="executingTool.function.parameters.required?.includes(name)" class="text-red-500">*</span>
|
|
302
|
+
</label>
|
|
303
|
+
|
|
304
|
+
<div v-if="prop.enum">
|
|
305
|
+
<select v-model="execForm[name]" :id="'input-' + name" class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
306
|
+
<option :value="undefined" disabled>Select...</option>
|
|
307
|
+
<option v-for="opt in prop.enum" :key="opt" :value="opt">{{ opt }}</option>
|
|
308
|
+
</select>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<div v-else-if="prop.type === 'boolean'">
|
|
312
|
+
<select v-model="execForm[name]" :id="'input-' + name" class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
313
|
+
<option :value="false">False</option>
|
|
314
|
+
<option :value="true">True</option>
|
|
315
|
+
</select>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<div v-else-if="prop.type === 'object' || prop.type === 'array'">
|
|
319
|
+
<JsonInput v-model="execForm[name]" />
|
|
320
|
+
</div>
|
|
321
|
+
|
|
322
|
+
<div v-else>
|
|
323
|
+
<input :type="prop.type === 'integer' || prop.type === 'number' ? 'number' : 'text'"
|
|
324
|
+
v-model="execForm[name]"
|
|
325
|
+
:id="'input-' + name"
|
|
326
|
+
:placeholder="prop.description"
|
|
327
|
+
:step="prop.type === 'integer' ? 1 : 0.01"
|
|
328
|
+
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
|
329
|
+
</div>
|
|
330
|
+
<p v-if="prop.description" class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ prop.description }}</p>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
<div v-else class="text-gray-500 dark:text-gray-400 italic">
|
|
334
|
+
No parameters required.
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
<div class="flex items-center gap-3 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
338
|
+
<button type="submit" :disabled="loading"
|
|
339
|
+
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed">
|
|
340
|
+
<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">
|
|
341
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
342
|
+
<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>
|
|
343
|
+
</svg>
|
|
344
|
+
{{ loading ? 'Executing...' : 'Run Tool' }}
|
|
345
|
+
</button>
|
|
346
|
+
</div>
|
|
347
|
+
</form>
|
|
348
|
+
|
|
349
|
+
<div v-if="execResult !== null || execError" class="mt-6">
|
|
350
|
+
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">Response:</h4>
|
|
351
|
+
<div v-if="execError" class="p-4 rounded-md bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 font-mono text-sm whitespace-pre-wrap">
|
|
352
|
+
{{ execError }}
|
|
353
|
+
</div>
|
|
354
|
+
<ToolResult v-else :result="execResult" />
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
|
360
|
+
<div v-for="tool in filteredTools" :key="tool.function.name" :id="'tool-' + tool.function.name"
|
|
361
|
+
class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col">
|
|
362
|
+
|
|
363
|
+
<div class="p-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 flex justify-between items-center">
|
|
364
|
+
<div class="font-bold text-lg text-gray-900 dark:text-gray-100 font-mono break-all mr-2">
|
|
365
|
+
{{ tool.function.name }}
|
|
366
|
+
</div>
|
|
367
|
+
<button @click="startExec(tool)" type="button" title="Execute Tool" class="text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 transition-colors rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 border-none">
|
|
368
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="size-6" viewBox="0 0 20 20" fill="currentColor">
|
|
369
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd" />
|
|
370
|
+
</svg>
|
|
371
|
+
</button>
|
|
372
|
+
</div>
|
|
373
|
+
|
|
374
|
+
<div class="tool-description p-4 flex-1 flex flex-col">
|
|
375
|
+
<div v-if="tool.function.description" class="text-sm text-gray-600 dark:text-gray-300 mb-4 flex-1 flex flex-col">
|
|
376
|
+
<div v-if="tool.function.description.length < 350">
|
|
377
|
+
<div v-html="$fmt.markdown(tool.function.description)"></div>
|
|
378
|
+
</div>
|
|
379
|
+
<div v-else>
|
|
380
|
+
<div class="relative transition-all duration-300 ease-in-out"
|
|
381
|
+
:class="{'max-h-[200px] overflow-hidden': !isExpanded(tool.function.name)}">
|
|
382
|
+
<div v-html="$fmt.markdown(tool.function.description)" :title="tool.function.description"></div>
|
|
383
|
+
|
|
384
|
+
<!-- Fade overlay when collapsed -->
|
|
385
|
+
<div v-if="!isExpanded(tool.function.name)"
|
|
386
|
+
class="absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-white dark:from-gray-800 to-transparent pointer-events-none">
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
|
|
390
|
+
<button @click="toggleDescription(tool.function.name)"
|
|
391
|
+
type="button"
|
|
392
|
+
class="mt-1 text-xs font-medium text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 focus:outline-none self-start">
|
|
393
|
+
{{ isExpanded(tool.function.name) ? 'Show Less' : 'Show More' }}
|
|
394
|
+
</button>
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
<p v-else class="text-sm text-gray-400 italic mb-4 flex-1">
|
|
398
|
+
No description provided
|
|
399
|
+
</p>
|
|
400
|
+
|
|
401
|
+
<div v-if="tool.function.parameters?.properties && Object.keys(tool.function.parameters.properties).length > 0">
|
|
402
|
+
<div class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Parameters</div>
|
|
403
|
+
<div class="space-y-3">
|
|
404
|
+
<div v-for="(prop, name) in tool.function.parameters.properties" :key="name" class="text-sm bg-gray-50 dark:bg-gray-700/30 rounded p-2">
|
|
405
|
+
<div class="flex flex-wrap items-baseline gap-2 mb-1">
|
|
406
|
+
<span class="font-mono font-medium text-blue-600 dark:text-blue-400">{{ name }}</span>
|
|
407
|
+
<span class="text-gray-500 text-xs">({{ prop.type }})</span>
|
|
408
|
+
<span v-if="tool.function.parameters.required?.includes(name)"
|
|
409
|
+
class="px-1.5 py-0.5 text-[10px] rounded bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400 font-medium">
|
|
410
|
+
REQUIRED
|
|
411
|
+
</span>
|
|
412
|
+
</div>
|
|
413
|
+
<div v-if="prop.description" class="text-gray-600 dark:text-gray-400 text-xs">
|
|
414
|
+
{{ prop.description }}
|
|
415
|
+
</div>
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
<div v-else class="text-sm text-gray-400 italic border-t border-gray-100 dark:border-gray-700 pt-2 mt-auto">
|
|
420
|
+
No parameters
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
</div>
|
|
425
|
+
</div>
|
|
426
|
+
`,
|
|
427
|
+
setup() {
|
|
428
|
+
const ctx = inject('ctx')
|
|
429
|
+
|
|
430
|
+
// Execution State
|
|
431
|
+
const execForm = ref({})
|
|
432
|
+
const execResult = ref(null)
|
|
433
|
+
const execError = ref(null)
|
|
434
|
+
const loading = ref(false)
|
|
435
|
+
const refForm = ref()
|
|
436
|
+
const refTop = ref()
|
|
437
|
+
|
|
438
|
+
const executingTool = computed(() => {
|
|
439
|
+
const tool = ext.prefs.selectedTool
|
|
440
|
+
if (!tool) return null
|
|
441
|
+
return ctx.state.tool.definitions.find(x => x.function.name === tool)
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
// UI State
|
|
445
|
+
const expandedDescriptions = ref({})
|
|
446
|
+
|
|
447
|
+
const groups = computed(() => Object.keys(ctx.state.tool.groups || {}))
|
|
448
|
+
|
|
449
|
+
const filteredTools = computed(() => {
|
|
450
|
+
const allTools = ctx.state.tool.definitions.filter(x => x.function)
|
|
451
|
+
if (ext.prefs.selectedGroup === 'All') return allTools
|
|
452
|
+
|
|
453
|
+
const groupTools = ctx.state.tool.groups[ext.prefs.selectedGroup] || []
|
|
454
|
+
return allTools.filter(t => groupTools.includes(t.function.name))
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
function startExec(tool) {
|
|
458
|
+
ext.setPrefs({ selectedTool: tool.function.name })
|
|
459
|
+
execForm.value = {}
|
|
460
|
+
execResult.value = null
|
|
461
|
+
execError.value = null
|
|
462
|
+
|
|
463
|
+
// Initialize defaults if any
|
|
464
|
+
if (tool.function.parameters?.properties) {
|
|
465
|
+
Object.entries(tool.function.parameters.properties).forEach(([key, prop]) => {
|
|
466
|
+
if (prop.default !== undefined) {
|
|
467
|
+
execForm.value[key] = prop.default
|
|
468
|
+
}
|
|
469
|
+
// Initialize booleans to false if likely
|
|
470
|
+
if (prop.type === 'boolean' && prop.default === undefined) {
|
|
471
|
+
// Optional: default to false? or maybe undefined is better to force user choice or let server handle it?
|
|
472
|
+
// Let's leave it undefined unless explicitly set
|
|
473
|
+
execForm.value[key] = false
|
|
474
|
+
}
|
|
475
|
+
})
|
|
476
|
+
}
|
|
477
|
+
// Scroll to top
|
|
478
|
+
// window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
479
|
+
refForm.value?.scrollIntoView({ behavior: 'smooth' })
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function closeExec() {
|
|
483
|
+
ext.setPrefs({ selectedTool: null })
|
|
484
|
+
execForm.value = {}
|
|
485
|
+
execResult.value = null
|
|
486
|
+
execError.value = null
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async function execTool() {
|
|
490
|
+
if (!executingTool.value) return
|
|
491
|
+
|
|
492
|
+
loading.value = true
|
|
493
|
+
execResult.value = null
|
|
494
|
+
execError.value = null
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
const ext = ctx.scope('tools')
|
|
498
|
+
// Filter out undefined values to avoid sending empty params that might confuse backend validation
|
|
499
|
+
// Or maybe send them as null? existing backend `tool_prop_value` handles things.
|
|
500
|
+
const payload = { ...execForm.value }
|
|
501
|
+
|
|
502
|
+
// Ensure numbers are numbers
|
|
503
|
+
if (executingTool.value.function.parameters?.properties) {
|
|
504
|
+
Object.entries(executingTool.value.function.parameters.properties).forEach(([key, prop]) => {
|
|
505
|
+
if ((prop.type === 'integer' || prop.type === 'number') && payload[key] !== '') {
|
|
506
|
+
payload[key] = Number(payload[key])
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const res = await ext.postJson('/exec/' + executingTool.value.function.name, payload)
|
|
512
|
+
if (res.error) {
|
|
513
|
+
execError.value = res.error.message
|
|
514
|
+
} else {
|
|
515
|
+
execResult.value = res.response
|
|
516
|
+
}
|
|
517
|
+
} catch (e) {
|
|
518
|
+
execError.value = e.message || 'Unknown error occurred'
|
|
519
|
+
} finally {
|
|
520
|
+
loading.value = false
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function toggleDescription(name) {
|
|
525
|
+
expandedDescriptions.value[name] = !expandedDescriptions.value[name]
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function isExpanded(name) {
|
|
529
|
+
return !!expandedDescriptions.value[name]
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
onMounted(() => {
|
|
533
|
+
if (!ext.prefs.selectedGroup) {
|
|
534
|
+
ext.setPrefs({ selectedGroup: 'All' })
|
|
535
|
+
}
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
ext,
|
|
540
|
+
refForm,
|
|
541
|
+
refTop,
|
|
542
|
+
groups,
|
|
543
|
+
filteredTools,
|
|
544
|
+
// Exec
|
|
545
|
+
executingTool,
|
|
546
|
+
execForm,
|
|
547
|
+
execResult,
|
|
548
|
+
execError,
|
|
549
|
+
loading,
|
|
550
|
+
startExec,
|
|
551
|
+
closeExec,
|
|
552
|
+
execTool,
|
|
553
|
+
// UI
|
|
554
|
+
toggleDescription,
|
|
555
|
+
isExpanded
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const ToolSelector = {
|
|
561
|
+
template: `
|
|
562
|
+
<div class="px-4 py-4 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 max-h-[80vh] overflow-y-auto">
|
|
563
|
+
|
|
564
|
+
<!-- Global Controls -->
|
|
565
|
+
<div class="flex items-center justify-between mb-4">
|
|
566
|
+
<span class="text-xs font-bold uppercase text-gray-500 tracking-wider">Include Tools</span>
|
|
567
|
+
<div class="flex items-center gap-2">
|
|
568
|
+
<button @click="$ctx.setPrefs({ onlyTools: null })"
|
|
569
|
+
class="px-3 py-1 rounded-md text-xs font-medium border transition-colors select-none"
|
|
570
|
+
:class="prefs.onlyTools == null
|
|
571
|
+
? 'bg-green-100 dark:bg-green-900/40 text-green-800 dark:text-green-300 border-green-300 dark:border-green-800'
|
|
572
|
+
: 'cursor-pointer bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'">
|
|
573
|
+
All Tools
|
|
574
|
+
</button>
|
|
575
|
+
<button @click="$ctx.setPrefs({ onlyTools:[] })"
|
|
576
|
+
class="px-3 py-1 rounded-md text-xs font-medium border transition-colors select-none"
|
|
577
|
+
:class="prefs.onlyTools?.length === 0
|
|
578
|
+
? 'bg-fuchsia-100 dark:bg-fuchsia-900/40 text-fuchsia-800 dark:text-fuchsia-300 border-fuchsia-200 dark:border-fuchsia-800'
|
|
579
|
+
: 'cursor-pointer bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'">
|
|
580
|
+
No Tools
|
|
581
|
+
</button>
|
|
582
|
+
</div>
|
|
583
|
+
</div>
|
|
584
|
+
|
|
585
|
+
<!-- Groups -->
|
|
586
|
+
<div class="space-y-3">
|
|
587
|
+
<div v-for="group in toolGroups" :key="group.name"
|
|
588
|
+
class="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
|
|
589
|
+
|
|
590
|
+
<!-- Group Header -->
|
|
591
|
+
<div class="flex items-center justify-between px-3 py-2 bg-gray-50/50 dark:bg-gray-800/50 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
|
592
|
+
@click="toggleCollapse(group.name)">
|
|
593
|
+
|
|
594
|
+
<div class="flex items-center gap-2 min-w-0">
|
|
595
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4 text-gray-400 transition-transform duration-200" :class="{ '-rotate-90': isCollapsed(group.name) }">
|
|
596
|
+
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
|
|
597
|
+
</svg>
|
|
598
|
+
<span class="font-semibold text-sm text-gray-700 dark:text-gray-200 truncate">
|
|
599
|
+
{{ group.name || 'Other Tools' }}
|
|
600
|
+
</span>
|
|
601
|
+
<span class="text-xs text-gray-400 font-mono">
|
|
602
|
+
{{ getActiveCount(group) }}/{{ group.tools.length }}
|
|
603
|
+
</span>
|
|
604
|
+
</div>
|
|
605
|
+
|
|
606
|
+
<div class="flex items-center gap-2" @click.stop>
|
|
607
|
+
<button @click="setGroupTools(group, true)" type="button"
|
|
608
|
+
title="Include All in Group"
|
|
609
|
+
class="px-2 py-0.5 rounded text-xs font-medium border transition-colors select-none"
|
|
610
|
+
:class="getActiveCount(group) === group.tools.length
|
|
611
|
+
? 'bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 border-green-300 dark:border-green-800 hover:bg-green-100 dark:hover:bg-green-900/40'
|
|
612
|
+
: 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'">
|
|
613
|
+
all
|
|
614
|
+
</button>
|
|
615
|
+
<button @click="setGroupTools(group, false)" type="button"
|
|
616
|
+
title="Include None in Group"
|
|
617
|
+
class="px-2 py-0.5 rounded text-xs font-medium border transition-colors select-none"
|
|
618
|
+
:class="getActiveCount(group) === 0
|
|
619
|
+
? 'bg-fuchsia-50 dark:bg-fuchsia-900/20 text-fuchsia-700 dark:text-fuchsia-300 border-fuchsia-200 dark:border-fuchsia-800 hover:bg-fuchsia-100 dark:hover:bg-fuchsia-900/40'
|
|
620
|
+
: 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'">
|
|
621
|
+
none
|
|
622
|
+
</button>
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
625
|
+
|
|
626
|
+
<!-- Group Body -->
|
|
627
|
+
<div v-show="!isCollapsed(group.name)" class="p-3 bg-white dark:bg-gray-900 border-t border-gray-100 dark:border-gray-800">
|
|
628
|
+
<div class="flex flex-wrap gap-2">
|
|
629
|
+
<button v-for="tool in group.tools" :key="tool.function.name" type="button"
|
|
630
|
+
@click="$tools.toggleTool(tool.function.name)"
|
|
631
|
+
:title="tool.function.description"
|
|
632
|
+
class="px-2.5 py-1 rounded-full text-xs font-medium border transition-colors select-none text-left truncate max-w-[200px]"
|
|
633
|
+
:class="$tools.isToolEnabled(tool.function.name)
|
|
634
|
+
? 'bg-blue-100 dark:bg-blue-900/40 text-blue-800 dark:text-blue-300 border-blue-200 dark:border-blue-800'
|
|
635
|
+
: 'bg-gray-50 dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'">
|
|
636
|
+
{{ tool.function.name }}
|
|
637
|
+
</button>
|
|
638
|
+
</div>
|
|
639
|
+
</div>
|
|
640
|
+
</div>
|
|
641
|
+
</div>
|
|
642
|
+
</div>
|
|
643
|
+
`,
|
|
644
|
+
setup() {
|
|
645
|
+
const ctx = inject('ctx')
|
|
646
|
+
const collapsedState = ref({})
|
|
647
|
+
|
|
648
|
+
const prefs = computed(() => ctx.prefs)
|
|
649
|
+
|
|
650
|
+
const toolGroups = computed(() => {
|
|
651
|
+
const defs = ctx.tools.availableTools.value
|
|
652
|
+
const groups = ctx.state.tool.groups || {}
|
|
653
|
+
|
|
654
|
+
const definedGroups = []
|
|
655
|
+
const usedTools = new Set()
|
|
656
|
+
|
|
657
|
+
for (const [groupName, toolNames] of Object.entries(groups)) {
|
|
658
|
+
if (!Array.isArray(toolNames)) continue
|
|
659
|
+
const tools = toolNames.map(name => defs.find(d => d.function.name === name)).filter(Boolean)
|
|
660
|
+
if (tools.length) {
|
|
661
|
+
tools.forEach(t => usedTools.add(t.function.name))
|
|
662
|
+
definedGroups.push({ name: groupName, tools })
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const otherTools = defs.filter(d => !usedTools.has(d.function.name))
|
|
667
|
+
if (otherTools.length) {
|
|
668
|
+
definedGroups.push({ name: '', tools: otherTools })
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return definedGroups
|
|
672
|
+
})
|
|
673
|
+
|
|
674
|
+
function toggleCollapse(groupName) {
|
|
675
|
+
const key = groupName || '_other_'
|
|
676
|
+
collapsedState.value[key] = !collapsedState.value[key]
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function isCollapsed(groupName) {
|
|
680
|
+
const key = groupName || '_other_'
|
|
681
|
+
return !!collapsedState.value[key]
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function setGroupTools(group, enable) {
|
|
685
|
+
const groupToolNames = group.tools.map(t => t.function.name)
|
|
686
|
+
let onlyTools = prefs.value.onlyTools
|
|
687
|
+
|
|
688
|
+
if (enable) {
|
|
689
|
+
if (onlyTools == null) return
|
|
690
|
+
const newSet = new Set(onlyTools)
|
|
691
|
+
groupToolNames.forEach(n => newSet.add(n))
|
|
692
|
+
onlyTools = Array.from(newSet)
|
|
693
|
+
if (onlyTools.length === ctx.tools.availableTools.value.length) {
|
|
694
|
+
onlyTools = null
|
|
695
|
+
}
|
|
696
|
+
} else {
|
|
697
|
+
if (onlyTools == null) {
|
|
698
|
+
onlyTools = ctx.tools.availableTools.value
|
|
699
|
+
.map(t => t.function.name)
|
|
700
|
+
.filter(n => !groupToolNames.includes(n))
|
|
701
|
+
} else {
|
|
702
|
+
onlyTools = onlyTools.filter(n => !groupToolNames.includes(n))
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
ctx.setPrefs({ onlyTools })
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function getActiveCount(group) {
|
|
710
|
+
const onlyTools = prefs.value.onlyTools
|
|
711
|
+
if (onlyTools == null) return group.tools.length
|
|
712
|
+
return group.tools.filter(t => onlyTools.includes(t.function.name)).length
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return {
|
|
716
|
+
prefs,
|
|
717
|
+
toolGroups,
|
|
718
|
+
toggleCollapse,
|
|
719
|
+
isCollapsed,
|
|
720
|
+
setGroupTools,
|
|
721
|
+
getActiveCount
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
export default {
|
|
727
|
+
order: 10 - 100,
|
|
728
|
+
|
|
729
|
+
install(ctx) {
|
|
730
|
+
ext = ctx.scope('tools')
|
|
731
|
+
|
|
732
|
+
ctx.components({
|
|
733
|
+
Tools,
|
|
734
|
+
ToolSelector,
|
|
735
|
+
ToolResult,
|
|
736
|
+
JsonInput,
|
|
737
|
+
})
|
|
738
|
+
|
|
739
|
+
ctx.setGlobals({
|
|
740
|
+
tools: useTools(ctx)
|
|
741
|
+
})
|
|
742
|
+
|
|
743
|
+
const svg = (attrs, title) => `<svg ${attrs} xmlns="http://www.w4.org/2000/svg" viewBox="0 0 24 24">${title ? "<title>" + title + "</title>" : ''}<path fill="currentColor" d="M5.33 3.272a3.5 3.5 0 0 1 4.472 4.473L20.647 18.59l-2.122 2.122L7.68 9.867a3.5 3.5 0 0 1-4.472-4.474L5.444 7.63a1.5 1.5 0 0 0 2.121-2.121zm10.367 1.883l3.182-1.768l1.414 1.415l-1.768 3.182l-1.768.353l-2.12 2.121l-1.415-1.414l2.121-2.121zm-7.071 7.778l2.121 2.122l-4.95 4.95A1.5 1.5 0 0 1 3.58 17.99l.097-.107z" /></svg>`
|
|
744
|
+
|
|
745
|
+
ctx.setLeftIcons({
|
|
746
|
+
tools: {
|
|
747
|
+
component: {
|
|
748
|
+
template: svg(`@click="$ctx.togglePath('/tools')"`),
|
|
749
|
+
},
|
|
750
|
+
isActive({ path }) {
|
|
751
|
+
return path === '/tools'
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
})
|
|
755
|
+
|
|
756
|
+
ctx.setTopIcons({
|
|
757
|
+
tools: {
|
|
758
|
+
component: {
|
|
759
|
+
template: svg([
|
|
760
|
+
`@click="$ctx.toggleTop('ToolSelector')"`,
|
|
761
|
+
`:class="$prefs.onlyTools == null ? 'text-green-600 dark:text-green-300' : $prefs.onlyTools.length ? 'text-blue-600! dark:text-blue-300!' : ''"`
|
|
762
|
+
].join(' ')),
|
|
763
|
+
// , "{{$prefs.onlyTools == null ? 'Include All Tools' : $prefs.onlyTools.length ? 'Include Selected Tools' : 'All Tools Excluded'}}"
|
|
764
|
+
},
|
|
765
|
+
isActive({ top }) {
|
|
766
|
+
return top === 'ToolSelector'
|
|
767
|
+
},
|
|
768
|
+
get title() {
|
|
769
|
+
return ctx.prefs.onlyTools == null
|
|
770
|
+
? `All Tools Included`
|
|
771
|
+
: ctx.prefs.onlyTools.length
|
|
772
|
+
? `${ctx.prefs.onlyTools.length} ${ctx.utils.pluralize('Tool', ctx.prefs.onlyTools.length)} Included`
|
|
773
|
+
: 'No Tools Included'
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
})
|
|
777
|
+
|
|
778
|
+
ctx.chatRequestFilters.push(({ request, thread, context }) => {
|
|
779
|
+
// Tool Preferences
|
|
780
|
+
const prefs = ctx.prefs
|
|
781
|
+
if (prefs.onlyTools != null) {
|
|
782
|
+
if (Array.isArray(prefs.onlyTools)) {
|
|
783
|
+
request.metadata.tools = prefs.onlyTools.length > 0
|
|
784
|
+
? prefs.onlyTools.join(',')
|
|
785
|
+
: 'none'
|
|
786
|
+
}
|
|
787
|
+
} else {
|
|
788
|
+
request.metadata.tools = 'all'
|
|
789
|
+
}
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
ctx.routes.push({ path: '/tools', component: Tools, meta: { title: 'View Tools' } })
|
|
793
|
+
ctx.setState({
|
|
794
|
+
tool: { groups: {}, definitions: [] }
|
|
795
|
+
})
|
|
796
|
+
},
|
|
797
|
+
|
|
798
|
+
async load(ctx) {
|
|
799
|
+
const api = await ext.getJson('/')
|
|
800
|
+
if (api.response) {
|
|
801
|
+
ctx.setState({ tool: api.response })
|
|
802
|
+
//console.log(ctx.state.tool)
|
|
803
|
+
} else {
|
|
804
|
+
ctx.setError(api.error)
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/* ctx.state.tool:
|
|
808
|
+
{
|
|
809
|
+
groups: {
|
|
810
|
+
"group_name": [
|
|
811
|
+
"memory_read"
|
|
812
|
+
]
|
|
813
|
+
},
|
|
814
|
+
definitions: [
|
|
815
|
+
{
|
|
816
|
+
"type": "function",
|
|
817
|
+
"function": {
|
|
818
|
+
"name": "memory_read",
|
|
819
|
+
"description": "Read a value from persistent memory.",
|
|
820
|
+
"parameters": {
|
|
821
|
+
"type": "object",
|
|
822
|
+
"properties": {
|
|
823
|
+
"key": {
|
|
824
|
+
"type": "string"
|
|
825
|
+
}
|
|
826
|
+
},
|
|
827
|
+
"required": [
|
|
828
|
+
"key"
|
|
829
|
+
]
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
],
|
|
833
|
+
}
|
|
834
|
+
*/
|
|
835
|
+
|
|
836
|
+
}
|
|
837
|
+
}
|