add codemirror
This commit is contained in:
parent
97f89b0833
commit
34bc18085a
@ -7,6 +7,7 @@
|
||||
- 解决 issue#21 vscode插件界面bug,在高度有限情况下无法通过滚动完全显示连接按钮。
|
||||
- 解决 issue#21 最后一个标签页关闭并恢复默认页面。
|
||||
- 解决 issue#22 工具模块UI异常,现在 openmcp 支持解析 pydantic 进行 typing 的 python mcp 了。
|
||||
- 优化对象输入框,现在对象输入框具有语法高亮和受限度的自动补全了。
|
||||
|
||||
## [main] 0.1.1
|
||||
- 修复 SSH 连接 Ubuntu 的情况下的部分 bug
|
||||
|
185
package-lock.json
generated
185
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "openmcp",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "openmcp",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"workspaces": [
|
||||
"service",
|
||||
"renderer",
|
||||
@ -510,6 +510,109 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
|
||||
"integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/commands": {
|
||||
"version": "6.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
|
||||
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"@codemirror/view": "^6.27.0",
|
||||
"@lezer/common": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-json": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
|
||||
"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/json": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.0.tgz",
|
||||
"integrity": "sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
"@lezer/common": "^1.1.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0",
|
||||
"style-mod": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lint": {
|
||||
"version": "6.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
|
||||
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.35.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/search": {
|
||||
"version": "6.5.11",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
|
||||
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
||||
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/theme-one-dark": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
|
||||
"integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/highlight": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.37.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.1.tgz",
|
||||
"integrity": "sha512-Qy4CAUwngy/VQkEz0XzMKVRcckQuqLYWKqVpDDDghBe5FSXSqfVrJn49nw3ePZHxRUz4nRmb05Lgi+9csWo4eg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.5.0",
|
||||
"crelt": "^1.0.6",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
@ -1460,6 +1563,41 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/common": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lezer/highlight": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/lr": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@malept/cross-spawn-promise": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz",
|
||||
@ -1515,6 +1653,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.1.tgz",
|
||||
@ -4209,6 +4353,21 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/codemirror": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
|
||||
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/commands": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/search": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@ -4549,6 +4708,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@ -9996,6 +10161,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/style-mod": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
||||
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sumchecker": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
|
||||
@ -10883,6 +11054,12 @@
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wasm-feature-detect": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz",
|
||||
@ -11367,6 +11544,10 @@
|
||||
"name": "@openmcp/renderer",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.18.6",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"codemirror": "^6.0.1",
|
||||
"core-js": "^3.8.3",
|
||||
"element-plus": "^2.9.9",
|
||||
"katex": "^0.16.21",
|
||||
|
@ -16,6 +16,10 @@
|
||||
"type-check": "vue-tsc --build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.18.6",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"codemirror": "^6.0.1",
|
||||
"core-js": "^3.8.3",
|
||||
"element-plus": "^2.9.9",
|
||||
"katex": "^0.16.21",
|
||||
|
@ -221,4 +221,10 @@ a {
|
||||
|
||||
.el-dropdown-menu__item:hover {
|
||||
background-color: var(--background) !important;
|
||||
}
|
||||
|
||||
/* codemirror */
|
||||
.ͼo,
|
||||
.ͼo .cm-gutters {
|
||||
background-color: transparent !important;
|
||||
}
|
@ -1,147 +1,199 @@
|
||||
<template>
|
||||
<div class="k-input-object">
|
||||
<textarea ref="textareaRef" v-model="inputValue" class="k-input-object__textarea"
|
||||
:class="{ 'is-invalid': isInvalid }" @input="handleInput" @blur="handleBlur"
|
||||
@keydown="handleKeydown"
|
||||
:placeholder="props.placeholder"
|
||||
></textarea>
|
||||
</div>
|
||||
<div v-if="errorMessage" class="k-input-object__error">
|
||||
{{ errorMessage }}
|
||||
<div :ref="el => editorContainer = el" class="k-input-object__editor"></div>
|
||||
<div v-if="errorMessage" class="k-input-object__error">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch, nextTick } from 'vue';
|
||||
import { debounce } from 'lodash';
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, watch, type PropType } from 'vue'
|
||||
import { EditorView, basicSetup } from 'codemirror'
|
||||
import type { Completion, CompletionContext } from "@codemirror/autocomplete"
|
||||
import { jsonLanguage } from "@codemirror/lang-json"
|
||||
|
||||
export default defineComponent({
|
||||
name: 'KInputObject',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入 JSON 对象'
|
||||
},
|
||||
debounceTime: {
|
||||
type: Number,
|
||||
default: 500
|
||||
}
|
||||
import { json } from '@codemirror/lang-json'
|
||||
import { oneDark } from '@codemirror/theme-one-dark'
|
||||
import { debounce } from 'lodash'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
emits: ['update:modelValue', 'parse-error'],
|
||||
setup(props, { emit }) {
|
||||
const textareaRef = ref<HTMLTextAreaElement | null>(null)
|
||||
const inputValue = ref<string>(JSON.stringify(props.modelValue, null, 2))
|
||||
const isInvalid = ref<boolean>(false)
|
||||
const errorMessage = ref<string>('')
|
||||
|
||||
// 防抖处理输入
|
||||
const debouncedParse = debounce((value: string) => {
|
||||
if (value.trim() === '') {
|
||||
errorMessage.value = '';
|
||||
isInvalid.value = false;
|
||||
emit('update:modelValue', undefined);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
isInvalid.value = false;
|
||||
errorMessage.value = '';
|
||||
emit('update:modelValue', parsed);
|
||||
} catch (error) {
|
||||
isInvalid.value = true;
|
||||
errorMessage.value = 'JSON 解析错误: ' + (error as Error).message;
|
||||
emit('parse-error', error);
|
||||
}
|
||||
}, props.debounceTime)
|
||||
|
||||
const handleInput = () => {
|
||||
debouncedParse(inputValue.value)
|
||||
}
|
||||
|
||||
const handleBlur = () => {
|
||||
// 立即执行最后一次防抖
|
||||
debouncedParse.flush()
|
||||
}
|
||||
|
||||
// 当外部 modelValue 变化时更新输入框内容
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
const currentParsed = tryParse(inputValue.value)
|
||||
if (!isDeepEqual(currentParsed, newVal)) {
|
||||
inputValue.value = JSON.stringify(newVal, null, 2)
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 辅助函数:尝试解析 JSON
|
||||
const tryParse = (value: string): any => {
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:深度比较对象
|
||||
const isDeepEqual = (obj1: any, obj2: any): boolean => {
|
||||
return JSON.stringify(obj1) === JSON.stringify(obj2)
|
||||
}
|
||||
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === '{') {
|
||||
event.preventDefault();
|
||||
const start = textareaRef.value!.selectionStart;
|
||||
const end = textareaRef.value!.selectionEnd;
|
||||
const value = inputValue.value;
|
||||
const newValue = value.substring(0, start) + '{\n \n}' + value.substring(end);
|
||||
inputValue.value = newValue;
|
||||
nextTick(() => {
|
||||
textareaRef.value!.setSelectionRange(start + 2, start + 2);
|
||||
});
|
||||
} else if (event.key === '"') {
|
||||
event.preventDefault();
|
||||
const start = textareaRef.value!.selectionStart;
|
||||
const end = textareaRef.value!.selectionEnd;
|
||||
const value = inputValue.value;
|
||||
const newValue = value.substring(0, start) + '""' + value.substring(end);
|
||||
inputValue.value = newValue;
|
||||
nextTick(() => {
|
||||
textareaRef.value!.setSelectionRange(start + 1, start + 1);
|
||||
});
|
||||
} else if (event.key === 'Tab') {
|
||||
event.preventDefault();
|
||||
const start = textareaRef.value!.selectionStart;
|
||||
const end = textareaRef.value!.selectionEnd;
|
||||
const value = inputValue.value;
|
||||
const newValue = value.substring(0, start) + ' ' + value.substring(end);
|
||||
inputValue.value = newValue;
|
||||
nextTick(() => {
|
||||
textareaRef.value!.setSelectionRange(start + 1, start + 1);
|
||||
});
|
||||
} else if (event.key === 'Enter' && inputValue.value.trim() === '') {
|
||||
event.preventDefault();
|
||||
inputValue.value = '{}';
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
textareaRef,
|
||||
inputValue,
|
||||
isInvalid,
|
||||
errorMessage,
|
||||
handleInput,
|
||||
handleBlur,
|
||||
handleKeydown,
|
||||
props
|
||||
}
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入 JSON 对象'
|
||||
},
|
||||
debounceTime: {
|
||||
type: Number,
|
||||
default: 500
|
||||
},
|
||||
schema: {
|
||||
type: Object as PropType<{
|
||||
type?: string;
|
||||
properties?: Record<string, {
|
||||
type: string;
|
||||
description?: string;
|
||||
default?: any;
|
||||
enum?: any[];
|
||||
}>;
|
||||
required?: string[];
|
||||
}>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'parse-error'])
|
||||
|
||||
const editorContainer = ref<any>(null);
|
||||
const editorView = ref<EditorView | null>(null);
|
||||
const isInvalid = ref(false);
|
||||
const errorMessage = ref('');
|
||||
|
||||
const inputValue = ref<string>(JSON.stringify(props.modelValue, null, 2));
|
||||
|
||||
// 防抖处理输入
|
||||
const debouncedParse = debounce((value: string) => {
|
||||
if (value.trim() === '') {
|
||||
errorMessage.value = '';
|
||||
isInvalid.value = false;
|
||||
emit('update:modelValue', undefined);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
isInvalid.value = false;
|
||||
errorMessage.value = '';
|
||||
emit('update:modelValue', parsed);
|
||||
} catch (error) {
|
||||
isInvalid.value = true;
|
||||
errorMessage.value = 'JSON 解析错误: ' + (error as Error).message;
|
||||
emit('parse-error', error);
|
||||
}
|
||||
}, props.debounceTime);
|
||||
|
||||
onMounted(() => {
|
||||
if (editorContainer.value) {
|
||||
const extensions = [
|
||||
basicSetup,
|
||||
json(),
|
||||
oneDark,
|
||||
EditorView.updateListener.of(update => {
|
||||
if (update.docChanged) {
|
||||
const value = update.state.doc.toString()
|
||||
debouncedParse(value)
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
// 如果schema不为空,添加自动补全
|
||||
if (Object.keys(props.schema).length > 0) {
|
||||
extensions.push(
|
||||
jsonLanguage.data.of({
|
||||
autocomplete: getJsonCompletion(props.schema)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
editorView.value = new EditorView({
|
||||
doc: JSON.stringify(props.modelValue, null, 2),
|
||||
extensions,
|
||||
parent: editorContainer.value
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 添加自动补全函数
|
||||
function getJsonCompletion(schema: any) {
|
||||
return (context: CompletionContext) => {
|
||||
// 检查当前输入是否是标点符号
|
||||
const charBefore = context.state.sliceDoc(context.pos - 1, context.pos)
|
||||
if (/[,.{}[\]:]/.test(charBefore)) return null
|
||||
|
||||
const word = context.matchBefore(/\w*/)
|
||||
if (!word) return null
|
||||
|
||||
// 检查当前是否在字符串内
|
||||
const state = context.state
|
||||
const pos = context.pos
|
||||
const line = state.doc.lineAt(pos)
|
||||
const textBefore = line.text.slice(0, pos - line.from)
|
||||
|
||||
// 如果前面有奇数个双引号,说明在字符串内,不触发补全
|
||||
const quoteCount = (textBefore.match(/"/g) || []).length
|
||||
if (quoteCount % 2 !== 0) return null
|
||||
|
||||
const completions: Completion[] = []
|
||||
|
||||
// 处理对象属性补全
|
||||
if (schema.properties) {
|
||||
Object.entries(schema.properties).forEach(([key, value]) => {
|
||||
completions.push({
|
||||
label: key,
|
||||
type: "property",
|
||||
apply: `"${key}": ${getDefaultValue(value as any)}`
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
from: word.from,
|
||||
options: completions,
|
||||
validFor: /^\w*$/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取默认值函数
|
||||
function getDefaultValue(property: any): string {
|
||||
if (property.default !== undefined) {
|
||||
return JSON.stringify(property.default)
|
||||
}
|
||||
switch (property.type) {
|
||||
case 'string': return '""'
|
||||
case 'number': return '0'
|
||||
case 'boolean': return 'false'
|
||||
case 'object': return '{}'
|
||||
case 'array': return '[]'
|
||||
default: return 'null'
|
||||
}
|
||||
}
|
||||
|
||||
// 修改 watch 逻辑
|
||||
// watch(
|
||||
// () => props.modelValue,
|
||||
// (newVal) => {
|
||||
// const currentParsed = tryParse(inputValue.value)
|
||||
// if (!isDeepEqual(currentParsed, newVal)) {
|
||||
// const newContent = JSON.stringify(newVal, null, 2)
|
||||
// editorView.value?.dispatch({
|
||||
// changes: {
|
||||
// from: 0,
|
||||
// to: editorView.value.state.doc.length,
|
||||
// insert: newContent
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
// { deep: true }
|
||||
// )
|
||||
|
||||
// 辅助函数:尝试解析 JSON
|
||||
const tryParse = (value: string): any => {
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:深度比较对象
|
||||
const isDeepEqual = (obj1: any, obj2: any): boolean => {
|
||||
return JSON.stringify(obj1) === JSON.stringify(obj2)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -151,6 +203,7 @@ export default defineComponent({
|
||||
border-radius: .5em;
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.k-input-object__textarea {
|
||||
@ -174,6 +227,24 @@ export default defineComponent({
|
||||
border-color: var(--el-color-error);
|
||||
}
|
||||
|
||||
.k-input-object__error {
|
||||
color: var(--el-color-error);
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.k-input-object__editor {
|
||||
width: 100%;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background-color: var(--el-bg-color-overlay);
|
||||
}
|
||||
|
||||
.k-input-object__editor.is-invalid {
|
||||
border-color: var(--el-color-error);
|
||||
}
|
||||
|
||||
.k-input-object__error {
|
||||
color: var(--el-color-error);
|
||||
font-size: 12px;
|
||||
|
@ -38,6 +38,7 @@
|
||||
<k-input-object
|
||||
v-else-if="property.type === 'object'"
|
||||
v-model="tabStorage.formData[name]"
|
||||
:schema="property"
|
||||
:placeholder="property.description || t('enter') + ' ' + (property.title || name)"
|
||||
/>
|
||||
</el-form-item>
|
||||
@ -84,14 +85,9 @@ if (!tabStorage.formData) {
|
||||
tabStorage.formData = {};
|
||||
}
|
||||
|
||||
console.log(tabStorage.formData);
|
||||
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const loading = ref(false);
|
||||
|
||||
|
||||
|
||||
const currentTool = computed(() => {
|
||||
for (const client of mcpClientAdapter.clients) {
|
||||
const tool = client.tools?.get(tabStorage.currentToolName);
|
||||
|
Loading…
x
Reference in New Issue
Block a user