Merge pull request #46 from LSTM-Kirigaya/dev
Some checks failed
Build / build (ubuntu-latest) (push) Has been cancelled
Test / test (macos-latest) (push) Has been cancelled
Test / test (ubuntu-latest) (push) Has been cancelled
Test / test (windows-latest) (push) Has been cancelled

Dev
This commit is contained in:
Kirigaya Kazuto 2025-07-03 20:16:59 +08:00 committed by GitHub
commit d9928ecc5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 5569 additions and 348 deletions

View File

@ -28,3 +28,4 @@ software/**
.github .github
webpack webpack
.openmcp .openmcp
.vscode

View File

@ -1,5 +1,13 @@
# Change Log # Change Log
## [main] 0.1.9
- 增加 mook 功能可以利用随机种子或者AI生成来自动化填充测试 tool 的表单数据
- 增加工具自检功能openmcp 的 tool 下可以点击「工具模块」 右侧的 「工具自检」进入自检模式,该模式下,用户可以自己定义工具执行的拓扑顺序,然后一次性进行自动检测。
- 修复 issue #44: 完成链接跳转的平台适配
- 修复 issue #36: 完成非文件夹打开下的成功启动
- 修复 issue #45: 数组类型参数不支持
- 修复多行对话粘贴进入对话框样式异常的问题
## [main] 0.1.8 ## [main] 0.1.8
- 增加 STDIO 下的热更新,现在用户修改 mcp 代码openmcp 会自动完成一切相关功能的热更新,无需用户手动重启。 - 增加 STDIO 下的热更新,现在用户修改 mcp 代码openmcp 会自动完成一切相关功能的热更新,无需用户手动重启。
- 完成 mcpconfig.json 的导出功能,导出的 配置文件 可以通过 openmcp-sdk 框架完成低代码 agent 部署;也可以直接载入 Claude Desktop 等等 MCP 客户端中,实现 MCP 的快速部署和使用。 - 完成 mcpconfig.json 的导出功能,导出的 配置文件 可以通过 openmcp-sdk 框架完成低代码 agent 部署;也可以直接载入 Claude Desktop 等等 MCP 客户端中,实现 MCP 的快速部署和使用。

744
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "openmcp", "name": "openmcp",
"version": "0.1.8", "version": "0.1.9",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "openmcp", "name": "openmcp",
"version": "0.1.8", "version": "0.1.9",
"workspaces": [ "workspaces": [
"service", "service",
"renderer" "renderer"
@ -2998,6 +2998,290 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/d3": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
"integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/d3-array": "*",
"@types/d3-axis": "*",
"@types/d3-brush": "*",
"@types/d3-chord": "*",
"@types/d3-color": "*",
"@types/d3-contour": "*",
"@types/d3-delaunay": "*",
"@types/d3-dispatch": "*",
"@types/d3-drag": "*",
"@types/d3-dsv": "*",
"@types/d3-ease": "*",
"@types/d3-fetch": "*",
"@types/d3-force": "*",
"@types/d3-format": "*",
"@types/d3-geo": "*",
"@types/d3-hierarchy": "*",
"@types/d3-interpolate": "*",
"@types/d3-path": "*",
"@types/d3-polygon": "*",
"@types/d3-quadtree": "*",
"@types/d3-random": "*",
"@types/d3-scale": "*",
"@types/d3-scale-chromatic": "*",
"@types/d3-selection": "*",
"@types/d3-shape": "*",
"@types/d3-time": "*",
"@types/d3-time-format": "*",
"@types/d3-timer": "*",
"@types/d3-transition": "*",
"@types/d3-zoom": "*"
}
},
"node_modules/@types/d3-array": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-axis": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
"integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-brush": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
"integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-chord": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
"integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-contour": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
"integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/d3-array": "*",
"@types/geojson": "*"
}
},
"node_modules/@types/d3-delaunay": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
"integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-dispatch": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz",
"integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-drag": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-dsv": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
"integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-fetch": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
"integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/d3-dsv": "*"
}
},
"node_modules/@types/d3-force": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
"integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-format": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz",
"integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-geo": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
"integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/d3-hierarchy": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
"integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-polygon": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
"integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-quadtree": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
"integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-random": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
"integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-scale": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-scale-chromatic": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
"integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-selection": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
"integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-shape": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-time-format": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
"integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-transition": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
"integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-zoom": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/d3-interpolate": "*",
"@types/d3-selection": "*"
}
},
"node_modules/@types/debug": { "node_modules/@types/debug": {
"version": "4.1.12", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
@ -3072,6 +3356,13 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/geojson": {
"version": "7946.0.16",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/glob": { "node_modules/@types/glob": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
@ -5735,6 +6026,416 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/d3": {
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
"integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
"license": "ISC",
"dependencies": {
"d3-array": "3",
"d3-axis": "3",
"d3-brush": "3",
"d3-chord": "3",
"d3-color": "3",
"d3-contour": "4",
"d3-delaunay": "6",
"d3-dispatch": "3",
"d3-drag": "3",
"d3-dsv": "3",
"d3-ease": "3",
"d3-fetch": "3",
"d3-force": "3",
"d3-format": "3",
"d3-geo": "3",
"d3-hierarchy": "3",
"d3-interpolate": "3",
"d3-path": "3",
"d3-polygon": "3",
"d3-quadtree": "3",
"d3-random": "3",
"d3-scale": "4",
"d3-scale-chromatic": "3",
"d3-selection": "3",
"d3-shape": "3",
"d3-time": "3",
"d3-time-format": "4",
"d3-timer": "3",
"d3-transition": "3",
"d3-zoom": "3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-axis": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz",
"integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-brush": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz",
"integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-drag": "2 - 3",
"d3-interpolate": "1 - 3",
"d3-selection": "3",
"d3-transition": "3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-chord": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz",
"integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
"license": "ISC",
"dependencies": {
"d3-path": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-contour": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz",
"integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
"license": "ISC",
"dependencies": {
"d3-array": "^3.2.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-delaunay": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
"integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
"license": "ISC",
"dependencies": {
"delaunator": "5"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dispatch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-drag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-selection": "3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dsv": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
"integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
"license": "ISC",
"dependencies": {
"commander": "7",
"iconv-lite": "0.6",
"rw": "1"
},
"bin": {
"csv2json": "bin/dsv2json.js",
"csv2tsv": "bin/dsv2dsv.js",
"dsv2dsv": "bin/dsv2dsv.js",
"dsv2json": "bin/dsv2json.js",
"json2csv": "bin/json2dsv.js",
"json2dsv": "bin/json2dsv.js",
"json2tsv": "bin/json2dsv.js",
"tsv2csv": "bin/dsv2dsv.js",
"tsv2json": "bin/dsv2json.js"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dsv/node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"license": "MIT",
"engines": {
"node": ">= 10"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-fetch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz",
"integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
"license": "ISC",
"dependencies": {
"d3-dsv": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-force": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
"integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-quadtree": "1 - 3",
"d3-timer": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-geo": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
"integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
"license": "ISC",
"dependencies": {
"d3-array": "2.5.0 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-hierarchy": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
"integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-polygon": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz",
"integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-quadtree": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
"integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-random": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz",
"integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"license": "ISC",
"dependencies": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-scale-chromatic": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
"integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3",
"d3-interpolate": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
"license": "ISC",
"dependencies": {
"d3-path": "^3.1.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
"license": "ISC",
"dependencies": {
"d3-array": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time-format": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"license": "ISC",
"dependencies": {
"d3-time": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-transition": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3",
"d3-dispatch": "1 - 3",
"d3-ease": "1 - 3",
"d3-interpolate": "1 - 3",
"d3-timer": "1 - 3"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"d3-selection": "2 - 3"
}
},
"node_modules/d3-zoom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-drag": "2 - 3",
"d3-interpolate": "1 - 3",
"d3-selection": "2 - 3",
"d3-transition": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/dateformat": { "node_modules/dateformat": {
"version": "4.6.3", "version": "4.6.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
@ -5881,6 +6582,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/delaunator": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
"integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
"license": "ISC",
"dependencies": {
"robust-predicates": "^3.0.2"
}
},
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -6077,6 +6787,12 @@
"vue": "^3.2.0" "vue": "^3.2.0"
} }
}, },
"node_modules/elkjs": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.10.0.tgz",
"integrity": "sha512-v/3r+3Bl2NMrWmVoRTMBtHtWvRISTix/s9EfnsfEWApNrsmNjqgqJOispCGg46BPwIFdkag3N/HYSxJczvCm6w==",
"license": "EPL-2.0"
},
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@ -7950,6 +8666,15 @@
"license": "ISC", "license": "ISC",
"optional": true "optional": true
}, },
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/interpret": { "node_modules/interpret": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz",
@ -11291,6 +12016,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/robust-predicates": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
"integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
"license": "Unlicense"
},
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.44.0", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.0.tgz",
@ -11523,6 +12254,12 @@
"queue-microtask": "^1.2.2" "queue-microtask": "^1.2.2"
} }
}, },
"node_modules/rw": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
"license": "BSD-3-Clause"
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -14155,7 +14892,9 @@
"chalk": "^5.4.1", "chalk": "^5.4.1",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"d3": "^7.9.0",
"element-plus": "^2.9.9", "element-plus": "^2.9.9",
"elkjs": "^0.10.0",
"json-schema-faker": "^0.5.9", "json-schema-faker": "^0.5.9",
"katex": "^0.16.21", "katex": "^0.16.21",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@ -14179,6 +14918,7 @@
"@originjs/vite-plugin-commonjs": "^1.0.3", "@originjs/vite-plugin-commonjs": "^1.0.3",
"@rollup/pluginutils": "^5.1.4", "@rollup/pluginutils": "^5.1.4",
"@tsconfig/node22": "^22.0.1", "@tsconfig/node22": "^22.0.1",
"@types/d3": "^7.4.3",
"@types/markdown-it": "^14.1.2", "@types/markdown-it": "^14.1.2",
"@types/node": "^22.14.0", "@types/node": "^22.14.0",
"@types/prismjs": "^1.26.5", "@types/prismjs": "^1.26.5",

View File

@ -2,7 +2,7 @@
"name": "openmcp", "name": "openmcp",
"displayName": "OpenMCP", "displayName": "OpenMCP",
"description": "An all in one MCP Client/TestTool", "description": "An all in one MCP Client/TestTool",
"version": "0.1.8", "version": "0.1.9",
"publisher": "kirigaya", "publisher": "kirigaya",
"private": true, "private": true,
"author": { "author": {
@ -224,7 +224,7 @@
"setup": "yarn install && yarn prepare:ocr", "setup": "yarn install && yarn prepare:ocr",
"serve": "turbo serve", "serve": "turbo serve",
"build": "turbo build && tsc -p ./ && node esbuild.config.js", "build": "turbo build && tsc -p ./ && node esbuild.config.js",
"build:plugin": "yarn build && tsc && vsce package", "build:plugin": "yarn build && tsc && vsce package --allow-package-all-secrets",
"vscode:prepublish": "node esbuild.config.js", "vscode:prepublish": "node esbuild.config.js",
"compile": "tsc -p ./", "compile": "tsc -p ./",
"watch": "tsc -watch -p ./", "watch": "tsc -watch -p ./",

View File

@ -23,7 +23,9 @@
"chalk": "^5.4.1", "chalk": "^5.4.1",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"d3": "^7.9.0",
"element-plus": "^2.9.9", "element-plus": "^2.9.9",
"elkjs": "^0.10.0",
"json-schema-faker": "^0.5.9", "json-schema-faker": "^0.5.9",
"katex": "^0.16.21", "katex": "^0.16.21",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@ -47,6 +49,7 @@
"@originjs/vite-plugin-commonjs": "^1.0.3", "@originjs/vite-plugin-commonjs": "^1.0.3",
"@rollup/pluginutils": "^5.1.4", "@rollup/pluginutils": "^5.1.4",
"@tsconfig/node22": "^22.0.1", "@tsconfig/node22": "^22.0.1",
"@types/d3": "^7.4.3",
"@types/markdown-it": "^14.1.2", "@types/markdown-it": "^14.1.2",
"@types/node": "^22.14.0", "@types/node": "^22.14.0",
"@types/prismjs": "^1.26.5", "@types/prismjs": "^1.26.5",

View File

@ -3,6 +3,15 @@
--main-color: #CB81DA; --main-color: #CB81DA;
--main-dark-color: #2D323B; --main-dark-color: #2D323B;
--main-light-color: rgba(203, 129, 218, 0.7); --main-light-color: rgba(203, 129, 218, 0.7);
--main-light-color-90: rgba(203, 129, 218, 0.9);
--main-light-color-80: rgba(203, 129, 218, 0.8);
--main-light-color-70: rgba(203, 129, 218, 0.7);
--main-light-color-60: rgba(203, 129, 218, 0.6);
--main-light-color-50: rgba(203, 129, 218, 0.5);
--main-light-color-40: rgba(203, 129, 218, 0.4);
--main-light-color-30: rgba(203, 129, 218, 0.3);
--main-light-color-20: rgba(203, 129, 218, 0.2);
--main-light-color-10: rgba(203, 129, 218, 0.1);
--sidebar-width: 330px; --sidebar-width: 330px;
--right-nav-width: 50px; --right-nav-width: 50px;
--time-scale-height: 30px; --time-scale-height: 30px;

View File

@ -163,24 +163,26 @@ function getDefaultValue(property: any): string {
} }
} }
// watch watch(
// watch( () => props.modelValue,
// () => props.modelValue, (newVal) => {
// (newVal) => { //
// const currentParsed = tryParse(inputValue.value) const currentContent = editorView.value?.state.doc.toString() ?? '';
// if (!isDeepEqual(currentParsed, newVal)) { const newContent = JSON.stringify(newVal ?? {}, null, 2);
// const newContent = JSON.stringify(newVal, null, 2)
// editorView.value?.dispatch({ if (currentContent !== newContent && editorView.value) {
// changes: { editorView.value.dispatch({
// from: 0, changes: {
// to: editorView.value.state.doc.length, from: 0,
// insert: newContent to: editorView.value.state.doc.length,
// } insert: newContent
// }) }
// } });
// }, }
// { deep: true } },
// ) { deep: true }
);
// JSON // JSON
const tryParse = (value: string): any => { const tryParse = (value: string): any => {

View File

@ -58,7 +58,7 @@ export interface EnableToolItem {
} }
export interface ChatSetting { export interface ChatSetting {
modelIndex: number modelIndex?: number
systemPrompt: string systemPrompt: string
enableTools: EnableToolItem[] enableTools: EnableToolItem[]
temperature: number temperature: number

View File

@ -56,6 +56,7 @@ import { llmManager, llms } from '@/views/setting/llm';
import { mcpClientAdapter } from '@/views/connect/core'; import { mcpClientAdapter } from '@/views/connect/core';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { useMessageBridge } from '@/api/message-bridge'; import { useMessageBridge } from '@/api/message-bridge';
import { gotoWebsite } from '@/hook/util';
const { t, locale } = useI18n(); const { t, locale } = useI18n();
@ -166,11 +167,11 @@ const exportCode = async () => {
const gotoHowtoUse = () => { const gotoHowtoUse = () => {
if (locale.value === 'zh') { if (locale.value === 'zh') {
window.open('https://kirigaya.cn/openmcp/zh/sdk-tutorial/#%E4%BD%BF%E7%94%A8'); gotoWebsite('https://kirigaya.cn/openmcp/zh/sdk-tutorial/#%E4%BD%BF%E7%94%A8');
} else if (locale.value === 'ja') { } else if (locale.value === 'ja') {
window.open('https://kirigaya.cn/openmcp/ja/sdk-tutorial/#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95'); gotoWebsite('https://kirigaya.cn/openmcp/ja/sdk-tutorial/#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95');
} else { } else {
window.open('https://kirigaya.cn/openmcp/sdk-tutorial/#usage'); gotoWebsite('https://kirigaya.cn/openmcp/sdk-tutorial/#usage');
} }
} }

View File

@ -64,4 +64,15 @@ const onRadioGroupChange = () => {
</script> </script>
<style></style> <style>
.setting-button:hover {
background: var(--main-light-color, #f0f8ff);
box-shadow: 0 2px 8px 0 rgba(64,158,255,0.08);
border-color: var(--el-color-primary-light-7, #c6e2ff);
}
.setting-button:active {
transform: scale(0.95);
}
</style>

View File

@ -162,16 +162,15 @@ provide('tabStorage', tabStorage);
color: var(--el-text-color-secondary); color: var(--el-text-color-secondary);
} }
.tools-dialog-container .el-switch__core { .el-switch__core {
border: 1px solid var(--main-color) !important; border: 1px solid var(--main-color) !important;
} }
.el-switch .el-switch__action {
.tools-dialog-container .el-switch .el-switch__action {
background-color: var(--main-color); background-color: var(--main-color);
} }
.tools-dialog-container .el-switch.is-checked .el-switch__action { .el-switch.is-checked .el-switch__action {
background-color: var(--sidebar); background-color: var(--sidebar);
} }

View File

@ -234,6 +234,7 @@ function handleCompositionEnd() {
.rich-editor { .rich-editor {
min-height: 100px; min-height: 100px;
outline: none; outline: none;
white-space: pre-wrap;
} }
.rich-editor:empty::before { .rich-editor:empty::before {

View File

@ -48,6 +48,7 @@ export class TaskLoop {
private bridge: MessageBridge; private bridge: MessageBridge;
private streamingContent: Ref<string>; private streamingContent: Ref<string>;
private streamingToolCalls: Ref<ToolCall[]>; private streamingToolCalls: Ref<ToolCall[]>;
private aborted = false;
private currentChatId = ''; private currentChatId = '';
private onError: (error: IErrorMssage) => void = (msg) => { }; private onError: (error: IErrorMssage) => void = (msg) => { };
@ -318,6 +319,7 @@ export class TaskLoop {
}); });
this.streamingContent.value = ''; this.streamingContent.value = '';
this.streamingToolCalls.value = []; this.streamingToolCalls.value = [];
this.aborted = true;
} }
/** /**
@ -545,6 +547,7 @@ export class TaskLoop {
maxEpochs = 50, maxEpochs = 50,
verbose = 0 verbose = 0
} = this.taskOptions || {}; } = this.taskOptions || {};
this.aborted = false;
for (let i = 0; i < maxEpochs; ++i) { for (let i = 0; i < maxEpochs; ++i) {
@ -570,6 +573,12 @@ export class TaskLoop {
// 发送请求 // 发送请求
const doConverationResult = await this.doConversation(chatData, toolcallIndexAdapter); const doConverationResult = await this.doConversation(chatData, toolcallIndexAdapter);
// 如果在调用过程中出发了 abort则直接中断
if (this.aborted) {
this.aborted = false;
break;
}
// 如果存在需要调度的工具 // 如果存在需要调度的工具
if (this.streamingToolCalls.value.length > 0) { if (this.streamingToolCalls.value.length > 0) {
@ -597,8 +606,19 @@ export class TaskLoop {
// ready to call tools // ready to call tools
toolCall = this.consumeToolCalls(toolCall); toolCall = this.consumeToolCalls(toolCall);
if (this.aborted) {
this.aborted = false;
break;
}
let toolCallResult = await handleToolCalls(toolCall); let toolCallResult = await handleToolCalls(toolCall);
if (this.aborted) {
this.aborted = false;
break;
}
// hook : finish call tools // hook : finish call tools
toolCallResult = this.consumeToolCalleds(toolCallResult); toolCallResult = this.consumeToolCalleds(toolCallResult);
@ -656,6 +676,11 @@ export class TaskLoop {
} }
} }
if (this.aborted) {
this.aborted = false;
break;
}
} else if (this.streamingContent.value) { } else if (this.streamingContent.value) {
tabStorage.messages.push({ tabStorage.messages.push({
role: 'assistant', role: 'assistant',

View File

@ -3,29 +3,18 @@
<div class="tabs-container"> <div class="tabs-container">
<el-scrollbar> <el-scrollbar>
<div class="scroll-tabs-container"> <div class="scroll-tabs-container">
<span <span class="tab" v-for="(tab, index) of tabs.content" :key="tab.id"
class="tab" :class="{ 'active-tab': tabs.activeIndex === index }" @click="setActiveTab(index)">
v-for="(tab, index) of tabs.content"
:key="tab.id"
:class="{ 'active-tab': tabs.activeIndex === index }"
@click="setActiveTab(index)"
>
<span> <span>
<span :class="`iconfont ${tab.icon}`"></span> <span :class="`iconfont ${tab.icon}`"></span>
<span class="tab-name">{{ tab.name }}</span> <span class="tab-name">{{ tab.name }}</span>
</span> </span>
<span <span class="iconfont icon-close" @click.stop="closeTab(index)"></span>
class="iconfont icon-close"
@click.stop="closeTab(index)"
></span>
</span> </span>
</div> </div>
</el-scrollbar> </el-scrollbar>
<span <span class="add-button iconfont icon-add" @click="pageAddNewTab">
class="add-button iconfont icon-add"
@click="pageAddNewTab"
>
</span> </span>
</div> </div>
@ -121,6 +110,11 @@ function setActiveTab(index: number) {
position: relative; position: relative;
} }
.tabs-container .tab:active {
transform: scale(0.95);
transition: var(--animation-3s);
}
.tabs-container .tab>span:first-child { .tabs-container .tab>span:first-child {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -4,7 +4,7 @@
<div class="left"> <div class="left">
<h2> <h2>
<span class="iconfont icon-chat"></span> <span class="iconfont icon-chat"></span>
提示词模块 {{ t('prompt-module') }}
</h2> </h2>
<PromptTemplates :tab-id="props.tabId"></PromptTemplates> <PromptTemplates :tab-id="props.tabId"></PromptTemplates>
@ -24,6 +24,9 @@ import { defineProps } from 'vue';
import PromptTemplates from './prompt-templates.vue'; import PromptTemplates from './prompt-templates.vue';
import PromptReader from './prompt-reader.vue'; import PromptReader from './prompt-reader.vue';
import PromptLogger from './prompt-logger.vue'; import PromptLogger from './prompt-logger.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
tabId: { tabId: {

View File

@ -8,23 +8,27 @@
<span>prompts/list</span> <span>prompts/list</span>
<span @click.stop="reloadPrompts(client, { first: false })" class="iconfont icon-restart"></span> <span @click.stop="reloadPrompts(client, { first: false })" class="iconfont icon-restart"></span>
</h3> </h3>
</template> </template>
<!-- body --> <!-- body -->
<div class="prompt-template-container-scrollbar"> <div class="prompt-template-container-scrollbar">
<el-scrollbar height="500px"> <el-scrollbar height="fit-content" v-if="(client.promptTemplates?.size || 0) > 0">
<div class="prompt-template-container"> <div class="prompt-template-container">
<div class="item" <div class="item"
:class="{ 'active': props.tabId >= 0 && tabStorage.currentPromptName === template.name }" :class="{ 'active': props.tabId >= 0 && tabStorage.currentPromptName === template.name }"
v-for="template of client.promptTemplates?.values()" :key="template.name" v-for="template of client.promptTemplates?.values()" :key="template.name"
@click="handleClick(template)"> @click="handleClick(template)">
<span>{{ template.name }}</span> <span class="prompt-title">{{ template.name }}</span>
<span>{{ template.description || '' }}</span> <span class="prompt-description">{{ template.description || '' }}</span>
</div> </div>
</div> </div>
</el-scrollbar> </el-scrollbar>
<div v-else style="padding: 10px;">
<div class="empty-description">
<span class="iconfont icon-empty" style="font-size: 22px; opacity: 0.4; margin-right: 6px;"></span>
<span style="opacity: 0.6;">No prompts found.</span>
</div>
</div>
</div> </div>
</el-collapse-item> </el-collapse-item>
</el-collapse> </el-collapse>
@ -126,8 +130,8 @@ onMounted(async () => {
user-select: none; user-select: none;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: space-between; align-items: flex-start;
transition: var(--animation-3s); transition: var(--animation-3s);
} }
@ -136,24 +140,40 @@ onMounted(async () => {
transition: var(--animation-3s); transition: var(--animation-3s);
} }
.prompt-template-container>.item:active {
transform: scale(0.95);
transition: var(--animation-3s);
}
.prompt-template-container>.item.active { .prompt-template-container>.item.active {
background-color: var(--main-light-color); background-color: var(--main-light-color);
transition: var(--animation-3s); transition: var(--animation-3s);
} }
.prompt-template-container>.item>span:first-child { .prompt-title {
max-width: 200px; font-weight: bold;
max-width: 250px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.prompt-template-container>.item>span:last-child { .prompt-description {
opacity: 0.6; opacity: 0.6;
font-size: 12.5px; font-size: 12.5px;
max-width: 200px; max-width: 250px;
overflow: hidden; overflow: visible;
text-overflow: ellipsis; text-overflow: unset;
white-space: nowrap; white-space: normal;
word-break: break-all;
}
.empty-description {
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-placeholder, #bbb);
font-size: 15px;
min-height: 40px;
} }
</style> </style>

View File

@ -115,7 +115,7 @@ const formRules = computed<FormRules>(() => {
currentResource.value?.params.forEach(param => { currentResource.value?.params.forEach(param => {
rules[param] = [ rules[param] = [
{ {
message: `${param} 是必填字段`, message: `${param} ` + t('is-required'),
trigger: 'blur' trigger: 'blur'
} }
] ]

View File

@ -12,19 +12,22 @@
<!-- body --> <!-- body -->
<div class="resource-template-container-scrollbar"> <div class="resource-template-container-scrollbar">
<el-scrollbar height="500px" v-if="(client.resourceTemplates?.size || 0) > 0"> <el-scrollbar height="fit-content" v-if="(client.resourceTemplates?.size || 0) > 0">
<div class="resource-template-container"> <div class="resource-template-container">
<div class="item" <div class="item"
:class="{ 'active': props.tabId >= 0 && tabStorage.currentType === 'template' && tabStorage.currentResourceName === template.name }" :class="{ 'active': props.tabId >= 0 && tabStorage.currentType === 'template' && tabStorage.currentResourceName === template.name }"
v-for="template of client.resourceTemplates?.values()" :key="template.name" v-for="template of client.resourceTemplates?.values()" :key="template.name"
@click="handleClick(template)"> @click="handleClick(template)">
<span>{{ template.name }}</span> <span class="resource-title">{{ template.name }}</span>
<span>{{ template.description || '' }}</span> <span class="resource-description">{{ template.description || '' }}</span>
</div> </div>
</div> </div>
</el-scrollbar> </el-scrollbar>
<div v-else style="padding: 10px;"> <div v-else style="padding: 10px;">
empty <div class="empty-description">
<span class="iconfont icon-empty" style="font-size: 22px; opacity: 0.4; margin-right: 6px;"></span>
<span style="opacity: 0.6;">No resource templates found.</span>
</div>
</div> </div>
</div> </div>
</el-collapse-item> </el-collapse-item>
@ -144,8 +147,8 @@ h3.resource-template .iconfont.icon-restart:hover {
user-select: none; user-select: none;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: space-between; align-items: flex-start;
transition: var(--animation-3s); transition: var(--animation-3s);
} }
@ -154,24 +157,40 @@ h3.resource-template .iconfont.icon-restart:hover {
transition: var(--animation-3s); transition: var(--animation-3s);
} }
.resource-template-container > .item:active {
transform: scale(0.95);
transition: var(--animation-3s);
}
.resource-template-container > .item.active { .resource-template-container > .item.active {
background-color: var(--main-light-color); background-color: var(--main-light-color);
transition: var(--animation-3s); transition: var(--animation-3s);
} }
.resource-template-container>.item>span:first-child { .resource-title {
max-width: 200px; font-weight: bold;
max-width: 250px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.resource-template-container>.item>span:last-child { .resource-description {
opacity: 0.6; opacity: 0.6;
font-size: 12.5px; font-size: 12.5px;
max-width: 200px; max-width: 250px;
overflow: hidden; overflow: visible;
text-overflow: ellipsis; text-overflow: unset;
white-space: nowrap; white-space: normal;
word-break: break-all;
}
.empty-description {
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-placeholder, #bbb);
font-size: 15px;
min-height: 40px;
} }
</style> </style>

View File

@ -12,17 +12,23 @@
<!-- body --> <!-- body -->
<div class="resource-template-container-scrollbar"> <div class="resource-template-container-scrollbar">
<el-scrollbar height="500px"> <el-scrollbar height="fit-content" v-if="(client.resources?.size || 0) > 0">
<div class="resource-template-container"> <div class="resource-template-container">
<div class="item" <div class="item"
:class="{ 'active': props.tabId >= 0 && tabStorage.currentType === 'resource' && tabStorage.currentResourceName === resource.name }" :class="{ 'active': props.tabId >= 0 && tabStorage.currentType === 'resource' && tabStorage.currentResourceName === resource.name }"
v-for="resource of client.resources?.values()" :key="resource.uri" v-for="resource of client.resources?.values()" :key="resource.uri"
@click="handleClick(resource)"> @click="handleClick(resource)">
<span>{{ resource.name }}</span> <span class="resource-title">{{ resource.name }}</span>
<span>{{ resource.mimeType }}</span> <span class="resource-description">{{ resource.mimeType }}</span>
</div> </div>
</div> </div>
</el-scrollbar> </el-scrollbar>
<div v-else style="padding: 10px;">
<div class="empty-description">
<span class="iconfont icon-empty" style="font-size: 22px; opacity: 0.4; margin-right: 6px;"></span>
<span style="opacity: 0.6;">No resources found.</span>
</div>
</div>
</div> </div>
</el-collapse-item> </el-collapse-item>
</el-collapse> </el-collapse>
@ -146,8 +152,8 @@ h3.resource-template .iconfont.icon-restart:hover {
user-select: none; user-select: none;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: space-between; align-items: flex-start;
transition: var(--animation-3s); transition: var(--animation-3s);
} }
@ -161,19 +167,36 @@ h3.resource-template .iconfont.icon-restart:hover {
transition: var(--animation-3s); transition: var(--animation-3s);
} }
.resource-template-container>.item>span:first-child { .resource-template-container > .item:active {
max-width: 200px; transform: scale(0.95);
transition: var(--animation-3s);
}
.resource-title {
font-weight: bold;
max-width: 250px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.resource-template-container>.item>span:last-child { .resource-description {
opacity: 0.6; opacity: 0.6;
font-size: 12.5px; font-size: 12.5px;
max-width: 200px; max-width: 250px;
overflow: hidden; /* Remove ellipsis and allow full text wrap */
text-overflow: ellipsis; overflow: visible;
white-space: nowrap; text-overflow: unset;
white-space: normal;
word-break: break-all;
}
.empty-description {
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-placeholder, #bbb);
font-size: 15px;
min-height: 40px;
} }
</style> </style>

View File

@ -0,0 +1,160 @@
<template>
<div class="diagram-item-record" v-if="props.dataView && props.dataView.tool">
<div class="item-header">
<span class="item-title">{{ props.dataView.tool.name }}</span>
<span class="item-status" :class="props.dataView.status">{{ props.dataView.status }}</span>
</div>
<div class="item-desc">{{ props.dataView.tool.description }}</div>
<div v-if="props.dataView.function !== undefined" class="item-result">
<div class="item-label">Function</div>
<div class="item-json">{{ props.dataView.function.name }}</div>
</div>
<div v-if="props.dataView.function !== undefined" class="item-result">
<span class="item-label">Arguments</span>
<json-render :json="props.dataView.function.arguments" />
</div>
<div v-if="props.dataView.result !== undefined" class="item-result">
<span class="item-label">Result</span>
<template v-if="Array.isArray(props.dataView.result)">
<div v-for="(item, idx) in props.dataView.result" :key="idx" class="result-block"
:class="[props.dataView.status]">
<pre class="item-json"
v-if="typeof item === 'object' && item.text !== undefined">{{ item.text }}</pre>
<pre class="item-json" v-else>{{ formatJson(item) }}</pre>
</div>
</template>
<pre class="item-json"
v-else-if="typeof props.dataView.result === 'string'">{{ props.dataView.result }}</pre>
<pre class="item-json" v-else>{{ formatJson(props.dataView.result) }}</pre>
</div>
</div>
<div v-else class="diagram-item-record">
<div class="item-header">
<span class="item-title">No Tool Selected</span>
</div>
<div class="item-desc">Please select a tool to view its details.</div>
</div>
</template>
<script setup lang="ts">
import type { PropType } from 'vue';
import type { NodeDataView } from './diagram';
import JsonRender from '@/components/json-render/index.vue';
const props = defineProps({
dataView: {
type: Object as PropType<NodeDataView | undefined | null>,
required: true
}
})
function formatJson(obj: any) {
try {
return JSON.stringify(obj, null, 2)
} catch {
return String(obj)
}
}
</script>
<style scoped>
.diagram-item-record {
padding: 14px 18px;
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
font-size: 15px;
max-width: 1000px;
word-break: break-all;
}
.item-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
}
.item-title {
font-weight: bold;
font-size: 17px;
color: var(--main-color, #409EFF);
}
.item-status {
font-size: 13px;
padding: 2px 10px;
border-radius: 12px;
margin-left: 8px;
text-transform: capitalize;
}
.item-status.running {
color: #2196f3;
}
.item-status.success {
color: #43a047;
}
.item-status.error {
color: #e53935;
}
.item-status.waiting {
color: #aaa;
}
.item-status.default {
color: #888;
}
.item-desc {
margin-bottom: 8px;
opacity: 0.8;
font-size: 14px;
}
.item-label {
font-weight: 500;
margin-right: 4px;
color: var(--main-color, #409EFF);
}
.item-json {
border-radius: 4px;
padding: 6px 10px;
font-size: 13px;
font-family: var(--code-font-family, monospace);
margin: 2px 0 8px 0;
white-space: pre-wrap;
word-break: break-all;
overflow-x: auto;
max-width: 100%;
box-sizing: border-box;
}
.item-result {
margin-top: 6px;
}
.result-block {
margin-bottom: 6px;
border-radius: .5em;
margin: 5px 0;
overflow-x: auto;
max-width: 100%;
}
.result-block.error {
background-color: rgba(245, 108, 108, 0.5);
}
.result-block.success {
background-color: rgba(67, 160, 71, 0.5);
}
</style>

View File

@ -0,0 +1,248 @@
import type { ElkNode } from 'elkjs/lib/elk-api';
import { MessageState, TaskLoop } from '../../chat/core/task-loop';
import type { Reactive } from 'vue';
import type { ChatStorage } from '../../chat/chat-box/chat';
import { ElMessage } from 'element-plus';
import type { ToolItem } from '@/hook/type';
import I18n from '@/i18n';
import type { ChatCompletionChunk } from 'openai/resources/index.mjs';
const { t } = I18n.global;
export interface Edge {
id: string;
sources: string[];
targets: string[];
sections?: any; // { startPoint: { x, y }, endPoint: { x,
}
export type Node = ElkNode & {
[key: string]: any;
width: number;
height: number;
id: string;
};
export interface DiagramState {
nodes: Node[];
edges: Edge[];
selectedNodeId: string | null;
dataView: Map<string, NodeDataView>;
[key: string]: any;
}
export interface CanConnectResult {
canConnect: boolean;
reason?: string;
}
export interface NodeDataView {
tool: ToolItem;
status: 'default' | 'running' | 'waiting' | 'success' | 'error';
function?: ChatCompletionChunk.Choice.Delta.ToolCall.Function;
result?: any;
}
export interface DiagramContext {
reset: () => void,
render: () => void,
state?: DiagramState,
setCaption: (value: string) => void
}
/**
* @description
*/
export function invalidConnectionDetector(state: DiagramState, d: Node): CanConnectResult {
const from = state.selectedNodeId;
const to = d.id;
if (!from) {
return { canConnect: false, reason: t('not-select-begin-node') };
}
if (from === to) {
return { canConnect: false, reason: '' };
}
// 建立邻接表
const adjacencyList: Record<string, Set<string>> = {};
state.edges.forEach(edge => {
const src = edge.sources[0];
const tgt = edge.targets[0];
if (!adjacencyList[src]) {
adjacencyList[src] = new Set();
}
adjacencyList[src].add(tgt);
});
// DFS 检测是否存在
function hasPath(current: string, target: string, visited: Set<string>): boolean {
if (current === target) return true;
visited.add(current);
const neighbors = adjacencyList[current] || new Set();
for (const neighbor of neighbors) {
if (!visited.has(neighbor)) {
if (hasPath(neighbor, target, visited)) {
return true;
}
}
}
return false;
}
if (hasPath(to, from, new Set())) {
return { canConnect: false, reason: t('can-make-loop') };
}
if (hasPath(from, to, new Set())) {
return { canConnect: false, reason: t('this-is-repeat-connection') };
}
return {
canConnect: true
}
}
/**
* @description id数组
* @returns string[][] id数组
*/
export function topoSortParallel(state: DiagramState): string[][] {
// 统计每个节点的入度
const inDegree: Record<string, number> = {};
state.nodes.forEach(node => {
inDegree[node.id] = 0;
});
state.edges.forEach(edge => {
const tgt = edge.targets[0];
if (tgt in inDegree) {
inDegree[tgt]++;
}
});
// 初始化队列收集所有入度为0的节点
const result: string[][] = [];
let queue: string[] = Object.keys(inDegree).filter(id => inDegree[id] === 0);
const visited = new Set<string>();
while (queue.length > 0) {
// 当前层可以并行的节点
result.push([...queue]);
const nextQueue: string[] = [];
for (const id of queue) {
visited.add(id);
// 遍历所有以当前节点为源的边,减少目标节点的入度
state.edges.forEach(edge => {
if (edge.sources[0] === id) {
const tgt = edge.targets[0];
inDegree[tgt]--;
// 如果目标节点入度为0且未访问过加入下一层
if (inDegree[tgt] === 0 && !visited.has(tgt)) {
nextQueue.push(tgt);
}
}
});
}
queue = nextQueue;
}
// 检查是否有环
if (visited.size !== state.nodes.length) {
throw new Error('图中存在环,无法进行拓扑排序');
}
return result;
}
export async function makeNodeTest(
dataView: Reactive<NodeDataView>,
enableXmlWrapper: boolean,
prompt: string | null = null,
context: DiagramContext
) {
if (!dataView.tool.inputSchema) {
return;
}
dataView.status = 'running';
context.render();
try {
const loop = new TaskLoop({ maxEpochs: 1 });
const usePrompt = (prompt || 'please call the tool {tool} to make some test').replace('{tool}', dataView.tool.name);
const chatStorage = {
messages: [],
settings: {
temperature: 0.6,
systemPrompt: '',
enableTools: [{
name: dataView.tool.name,
description: dataView.tool.description,
inputSchema: dataView.tool.inputSchema,
enabled: true
}],
enableWebSearch: false,
contextLength: 5,
enableXmlWrapper,
parallelToolCalls: false
}
} as ChatStorage;
loop.setMaxEpochs(1);
let aiMockJson: any = undefined;
loop.registerOnToolCall(toolCall => {
dataView.function = toolCall.function;
if (toolCall.function?.name === dataView.tool?.name) {
try {
const toolArgs = JSON.parse(toolCall.function?.arguments || '{}');
aiMockJson = toolArgs;
} catch (e) {
// ElMessage.error('AI 生成的 JSON 解析错误');
dataView.status = 'error';
dataView.result = t('ai-gen-error-json');
context.render();
loop.abort();
}
} else {
// ElMessage.error('AI 调用了未知的工具');
dataView.status = 'error';
dataView.result = t('ai-invoke-unknown-tool') + ' ' + toolCall.function?.name;
context.render();
loop.abort();
}
return toolCall;
});
loop.registerOnToolCalled(toolCalled => {
if (toolCalled.state === MessageState.Success) {
dataView.status = 'success';
dataView.result = toolCalled.content;
} else {
dataView.status = 'error';
dataView.result = toolCalled.content;
}
loop.abort();
return toolCalled;
})
loop.registerOnError(error => {
dataView.status = 'error';
dataView.result = error;
context.render();
});
await loop.start(chatStorage, usePrompt);
} finally {
if (dataView.status === 'running') {
dataView.status = 'success';
context.render();
}
}
};

View File

@ -0,0 +1,654 @@
<template>
<div style="display: flex; align-items: center; gap: 16px;">
<div ref="svgContainer" class="diagram-container"></div>
<template v-for="(node, index) in state.nodes" :key="node.id + '-popup'">
<transition name="collapse-from-top" mode="out-in">
<div
v-show="state.hoverNodeId === node.id && state.dataView.get(node.id)?.status !== 'waiting'"
@mouseenter="setHoverItem(node.id)"
@mouseleave="clearHoverItem()"
:style="getNodePopupStyle(node)"
class="node-popup"
>
<el-scrollbar height="100%" width="100%">
<DiagramItemRecord :data-view="state.dataView.get(node.id)"/>
</el-scrollbar>
</div>
</transition>
</template>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick, reactive, inject } from 'vue';
import * as d3 from 'd3';
import ELK from 'elkjs/lib/elk.bundled.js';
import { mcpClientAdapter } from '@/views/connect/core';
import { invalidConnectionDetector, type Edge, type Node, type NodeDataView } from './diagram';
import { ElMessage } from 'element-plus';
import DiagramItemRecord from './diagram-item-record.vue';
import { useI18n } from 'vue-i18n';
import type { ToolStorage } from '../tools';
import { tabs } from '../../panel';
const { t } = useI18n();
const props = defineProps({
tabId: {
type: Number,
required: true
}
});
const svgContainer = ref<HTMLDivElement | null>(null);
let prevNodes: any[] = [];
let prevEdges: any[] = [];
const state = reactive({
nodes: [] as Node[],
edges: [] as Edge[],
selectedNodeId: null as string | null,
draggingNodeId: null as string | null,
hoverNodeId: null as string | null,
offset: { x: 0, y: 0 },
dataView: new Map<string, NodeDataView>
});
const tab = tabs.content[props.tabId];
const tabStorage = tab.storage as ToolStorage;
const autoDetectDiagram = tabStorage.autoDetectDiagram;
if (autoDetectDiagram) {
// tabStorage.autoDetectDiagram dataView state
autoDetectDiagram.views?.forEach(item => {
state.dataView.set(item.tool.name, {
tool: item.tool,
status: item.status || 'waiting',
result: item.result || null
});
});
} else {
tabStorage.autoDetectDiagram = {
edges: [],
views: []
};
}
console.log(tabStorage.autoDetectDiagram!.views);
console.log(state.dataView);
let cancelHoverHandler: NodeJS.Timeout | undefined = undefined;
const setHoverItem = (id: string) => {
if (cancelHoverHandler) {
clearTimeout(cancelHoverHandler);
}
state.hoverNodeId = id;
}
const clearHoverItem = () => {
cancelHoverHandler = setTimeout(() => {
if (cancelHoverHandler) {
clearTimeout(cancelHoverHandler);
}
if (state.hoverNodeId) {
state.hoverNodeId = null;
}
}, 300);
};
const getAllTools = async () => {
const items = [];
for (const client of mcpClientAdapter.clients) {
const clientTools = await client.getTools();
items.push(...clientTools.values());
}
return items;
};
const recomputeLayout = async () => {
const elk = new ELK();
const elkGraph = {
id: 'root',
layoutOptions: {
'elk.direction': 'DOWN',
'elk.spacing.nodeNode': '40',
'elk.layered.spacing.nodeNodeBetweenLayers': '40'
},
children: state.nodes,
edges: state.edges
};
const layout = await elk.layout(elkGraph) as unknown as Node;
state.nodes.forEach((n, i) => {
const ln = layout.children?.find(c => c.id === n.id);
if (ln) {
n.x = ln.x;
n.y = ln.y;
n.width = ln.width || 200; //
n.height = ln.height || 64; //
}
});
state.edges = layout.edges || [];
// tabStorage
tabStorage.autoDetectDiagram!.edges = state.edges.map(edge => ({
id: edge.id,
sources: edge.sources || [],
targets: edge.targets || []
}));
return layout;
};
const drawDiagram = async () => {
const tools = await getAllTools();
//
const nodes = [] as Node[];
const edges = [] as Edge[];
// edges
const reservedEdges = autoDetectDiagram?.edges;
if (reservedEdges) {
for (const edge of reservedEdges) {
if (edge.sources && edge.targets && edge.sources.length > 0 && edge.targets.length > 0) {
edges.push({
id: edge.id,
sources: edge.sources || [],
targets: edge.targets || [],
});
}
}
} else {
for (let i = 0; i < tools.length - 1; ++i) {
const prev = tools[i];
const next = tools[i + 1];
edges.push({
id: prev.name + '-' + next.name,
sources: [prev.name],
targets: [next.name]
})
}
}
for (const tool of tools) {
nodes.push({
id: tool.name,
width: 200,
height: 64, //
labels: [{ text: tool.name || 'Tool' }]
});
if (!state.dataView.has(tool.name)) {
// dataView
state.dataView.set(tool.name, {
tool,
status: 'waiting'
});
}
}
state.edges = edges;
state.nodes = nodes;
//
await recomputeLayout();
// svg
renderSvg();
};
function renderSvg() {
const prevNodeMap = new Map(prevNodes.map(n => [n.id, n]));
const prevEdgeMap = new Map(prevEdges.map(e => [e.id, e]));
// xx
const xs = state.nodes.map(n => (n.x || 0));
const minX = Math.min(...xs);
const maxX = Math.max(...xs.map((x, i) => x + (state.nodes[i].width || 160)));
const contentWidth = maxX - minX;
const svgWidth = Math.max(contentWidth + 120, 400); // 120
const offsetX = (svgWidth - contentWidth) / 2 - minX;
const height = Math.max(...state.nodes.map(n => (n.y || 0) + (n.height || 48)), 300) + 60;
// svg
let svg = d3.select(svgContainer.value).select('svg');
if (svg.empty()) {
svg = d3
.select(svgContainer.value)
.append('svg')
.attr('width', svgWidth)
.attr('height', height)
.style('user-select', 'none') as any;
} else {
svg.attr('width', svgWidth).attr('height', height);
svg.selectAll('defs').remove();
}
// Arrow marker
svg
.append('defs')
.append('marker')
.attr('id', 'arrow')
.attr('viewBox', '0 0 8 8')
.attr('refX', 6)
.attr('refY', 4)
.attr('markerWidth', 5)
.attr('markerHeight', 5)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 0 0 L 8 4 L 0 8 z')
.attr('fill', 'var(--main-color)');
// 1. / main group
let mainGroup = svg.select('g.main-group');
if (mainGroup.empty()) {
mainGroup = svg.append('g').attr('class', 'main-group') as any;
}
mainGroup
.transition()
.duration(600)
.attr('transform', `translate(${offsetX}, 0)`);
// Draw edges with enter animation
const allSections: { id: string, section: any }[] = [];
(state.edges || []).forEach(edge => {
const sections = edge.sections || [];
sections.forEach((section: any, idx: number) => {
allSections.push({
id: (edge.id || '') + '-' + (section.id || idx),
section
});
});
});
const edgeSelection = mainGroup.selectAll<SVGLineElement, any>('.edge')
.data(allSections, d => d.id);
edgeSelection.exit().remove();
const edgeEnter = edgeSelection.enter()
.append('line')
.attr('class', 'edge')
.attr('x1', d => {
const prev = prevEdgeMap.get(d.id);
return prev && prev.sections && prev.sections[0]
? prev.sections[0].startPoint.x + 30
: d.section.startPoint.x + 30;
})
.attr('y1', d => {
const prev = prevEdgeMap.get(d.id);
return prev && prev.sections && prev.sections[0]
? prev.sections[0].startPoint.y + 30
: d.section.startPoint.y + 30;
})
.attr('x2', d => {
const prev = prevEdgeMap.get(d.id);
return prev && prev.sections && prev.sections[0]
? prev.sections[0].endPoint.x + 30
: d.section.endPoint.x + 30;
})
.attr('y2', d => {
const prev = prevEdgeMap.get(d.id);
return prev && prev.sections && prev.sections[0]
? prev.sections[0].endPoint.y + 30
: d.section.endPoint.y + 30;
})
.attr('stroke', 'var(--main-color)')
.attr('stroke-width', 2.5)
.attr('marker-end', 'url(#arrow)')
.attr('opacity', 0);
edgeEnter
.transition()
.duration(600)
.attr('opacity', 1)
.attr('x1', d => d.section.startPoint.x + 30)
.attr('y1', d => d.section.startPoint.y + 30)
.attr('x2', d => d.section.endPoint.x + 30)
.attr('y2', d => d.section.endPoint.y + 30);
// update + transition opacity
edgeSelection.merge(edgeEnter)
.transition()
.duration(600)
.ease(d3.easeCubicInOut)
.attr('x1', d => d.section.startPoint.x + 30)
.attr('y1', d => d.section.startPoint.y + 30)
.attr('x2', d => d.section.endPoint.x + 30)
.attr('y2', d => d.section.endPoint.y + 30)
.attr('opacity', 1);
// --- ---
const nodeGroup = mainGroup.selectAll<SVGGElement, any>('.node')
.data(state.nodes, d => d.id);
nodeGroup.exit().remove();
// enter
const nodeGroupEnter = nodeGroup.enter()
.append('g')
.attr('class', 'node')
.attr('transform', d => {
const prev = prevNodeMap.get(d.id);
if (prev) {
return `translate(${(prev.x || 0) + 30}, ${(prev.y || 0) + 30})`;
}
return `translate(${(d.x || 0) + 30}, ${(d.y || 0) + 30})`;
})
.style('cursor', 'pointer')
.attr('opacity', 0)
.on('mousedown', null)
.on('mouseup', function (event, d) {
event.stopPropagation();
if (state.selectedNodeId) {
const { canConnect, reason } = invalidConnectionDetector(state, d);
console.log(reason);
if (reason) {
ElMessage.warning(reason);
}
if (canConnect) {
state.edges.push({
id: `e${state.selectedNodeId}_${d.id}_${Date.now()}`,
sources: [state.selectedNodeId],
targets: [d.id]
});
state.selectedNodeId = null;
recomputeLayout().then(renderSvg);
} else {
//
state.selectedNodeId = null;
renderSvg();
}
context.setCaption('');
} else {
state.selectedNodeId = d.id;
renderSvg();
context.setCaption(t('select-node-define-test-tomo'));
}
state.draggingNodeId = null;
})
.on('mouseover', function (event, d) {
setHoverItem(d.id);
d3.select(this).select('rect')
.transition()
.duration(200)
.attr('stroke', 'var(--main-color)')
.attr('stroke-width', 2);
})
.on('mouseout', function (event, d) {
clearHoverItem();
if (state.selectedNodeId === d.id) return;
d3.select(this).select('rect')
.transition()
.duration(200)
.attr('stroke', 'var(--main-light-color-10)')
.attr('stroke-width', 1);
});
nodeGroupEnter.append('rect')
.attr('width', (d: any) => d.width)
.attr('height', (d: any) => d.height)
.attr('rx', 16)
.attr('fill', 'var(--main-light-color-20)')
.attr('stroke', d => state.selectedNodeId === d.id ? 'var(--main-color)' : 'var(--main-light-color-10)')
.attr('stroke-width', 2);
//
nodeGroupEnter.append('text')
.attr('x', d => d.width / 2)
.attr('y', d => d.height / 2 - 6) //
.attr('text-anchor', 'middle')
.attr('font-size', 16)
.attr('fill', 'var(--main-color)')
.attr('font-weight', 600)
.text(d => d.labels?.[0]?.text || 'Tool');
nodeGroupEnter.append('g').attr('class', 'node-status');
// enter+update
const nodeStatusGroup = nodeGroup.merge(nodeGroupEnter).select('.node-status');
//
nodeStatusGroup.each(function (d) {
const g = d3.select(this);
g.selectAll('*').remove(); //
const status = state.dataView.get(d.id)?.status || 'waiting';
if (status === 'running') {
g.append('circle')
.attr('cx', d.width / 2 - 32)
.attr('cy', d.height - 16)
.attr('r', 6)
.attr('fill', 'none')
.attr('stroke', 'var(--main-color)')
.attr('stroke-width', 3)
.attr('stroke-dasharray', 20)
.attr('stroke-dashoffset', 0)
.append('animateTransform')
.attr('attributeName', 'transform')
.attr('attributeType', 'XML')
.attr('type', 'rotate')
.attr('from', `0 ${(d.width / 2 - 32)} ${(d.height - 16)}`)
.attr('to', `360 ${(d.width / 2 - 32)} ${(d.height - 16)}`)
.attr('dur', '1s')
.attr('repeatCount', 'indefinite');
g.append('text')
.attr('x', d.width / 2 - 16)
.attr('y', d.height - 12)
.attr('font-size', 13)
.attr('fill', 'var(--main-color)')
.text('running');
} else if (status === 'waiting') {
g.append('circle')
.attr('cx', d.width / 2 - 32)
.attr('cy', d.height - 16)
.attr('r', 6)
.attr('fill', 'none')
.attr('stroke', '#bdbdbd')
.attr('stroke-width', 3);
g.append('text')
.attr('x', d.width / 2 - 16)
.attr('y', d.height - 12)
.attr('font-size', 13)
.attr('fill', '#bdbdbd')
.text('waiting');
} else if (status === 'success') {
g.append('circle')
.attr('cx', d.width / 2 - 32)
.attr('cy', d.height - 16)
.attr('r', 6)
.attr('fill', 'none')
.attr('stroke', '#4caf50')
.attr('stroke-width', 3);
g.append('text')
.attr('x', d.width / 2 - 16)
.attr('y', d.height - 12)
.attr('font-size', 13)
.attr('fill', '#4caf50')
.text('success');
} else if (status === 'error') {
g.append('circle')
.attr('cx', d.width / 2 - 32)
.attr('cy', d.height - 16)
.attr('r', 6)
.attr('fill', 'none')
.attr('stroke', '#f44336')
.attr('stroke-width', 3);
g.append('text')
.attr('x', d.width / 2 - 16)
.attr('y', d.height - 12)
.attr('font-size', 13)
.attr('fill', '#f44336')
.text('error');
}
});
// enter
nodeGroupEnter
.transition()
.duration(600)
.attr('opacity', 1)
.attr('transform', d => `translate(${(d.x || 0) + 30}, ${(d.y || 0) + 30})`);
// update
nodeGroup
.transition()
.duration(600)
.ease(d3.easeCubicInOut)
.attr('transform', d => `translate(${(d.x || 0) + 30}, ${(d.y || 0) + 30})`);
//
nodeGroup.select('rect')
.transition()
.duration(400)
.attr('stroke', d => state.selectedNodeId === d.id ? 'var(--main-color)' : 'var(--main-light-color-10)');
//
svg.selectAll<SVGLineElement, any>('.edge')
.on('mouseover', function () {
d3.select(this)
.transition()
.duration(200)
.attr('stroke', 'var(--main-color)')
.attr('stroke-width', 4.5);
context.setCaption(t('click-edge-to-delete'));
})
.on('mouseout', function () {
d3.select(this)
.transition()
.duration(200)
.attr('stroke', 'var(--main-color)')
.attr('stroke-width', 2.5);
context.setCaption('');
})
.on('click', function (event, d) {
// edge
state.edges = state.edges.filter(e => {
// edge
if (e.sections) {
// section
return !e.sections.some((section: any, idx: number) =>
((e.id || '') + '-' + (section.id || idx)) === d.id
);
}
// edge
return e.id !== d.id && e.id !== d.section?.id;
});
recomputeLayout().then(renderSvg);
event.stopPropagation();
});
//
prevNodes = state.nodes.map(n => ({ ...n }));
prevEdges = (state.edges || []).map(e => ({ ...e, sections: e.sections ? e.sections.map((s: any) => ({ ...s })) : [] }));
}
//
function resetConnections() {
if (!state.nodes.length) return;
const edges = [];
for (let i = 0; i < state.nodes.length - 1; ++i) {
const prev = state.nodes[i];
const next = state.nodes[i + 1];
edges.push({
id: prev.id + '-' + next.id,
sources: [prev.id],
targets: [next.id]
});
}
state.edges = edges;
recomputeLayout().then(renderSvg);
}
const context = inject('context') as any;
context.reset = resetConnections;
context.state = state;
context.render = renderSvg;
onMounted(() => {
nextTick(drawDiagram);
});
// 4.
function getNodePopupStyle(node: any): any {
// svg
// offsetXnode.xnode.y
const marginX = 50;
const marginY = 80;
const popupWidth = 300;
const popupHeight = 500;
let left = (node.x || 0) + (node.width || 160) + 100;
let top = (node.y || 0) + 30;
//
const container = svgContainer.value;
let containerWidth = 1200, containerHeight = 800; //
if (container) {
const rect = container.getBoundingClientRect();
containerWidth = rect.width;
containerHeight = rect.height;
}
// left top
left = Math.max(marginX, Math.min(left, containerWidth - popupWidth - marginX));
top = Math.max(marginY, Math.min(top, containerHeight - popupHeight - marginY));
return {
position: 'absolute',
left: `${left}px`,
top: `${top}px`,
width: `${popupWidth}px`,
height: `${popupHeight}px`
};
}
</script>
<style>
.diagram-container {
width: 100%;
min-height: 200px;
display: flex;
justify-content: center;
align-items: flex-start;
border-radius: 8px;
padding: 24px 0;
overflow-x: auto;
}
.node-popup {
position: absolute;
background: var(--background);
border: 1px solid var(--main-color);
border-radius: 8px;
padding: 8px 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
white-space: nowrap;
z-index: 10;
}
/* 旋转动画 */
.status-running-circle {
animation: spin 1s linear infinite;
transform-origin: center;
}
@keyframes spin {
100% {
transform: rotate(360deg);
}
}
</style>

View File

@ -0,0 +1,173 @@
<template>
<el-dialog v-model="showDialog" width="800px" class="no-padding-dialog">
<template #header>
<div style="display: flex; align-items: center;">
<span>Tool Diagram</span>
&ensp;
<el-button size="small" type="primary" @click="() => context.reset()">{{ t("reset") }}</el-button>
<!-- 自检程序弹出表单 -->
<el-popover placement="top" width="350" trigger="click" v-model:visible="testFormVisible">
<template #reference>
<el-button size="small" type="primary">
{{ t('start-auto-detect') }}
</el-button>
</template>
<el-input type="textarea" v-model="testPrompt" :rows="2" style="margin-bottom: 8px;"
placeholder="请输入 prompt" />
<div style="display: flex; align-items: center; margin-bottom: 8px;">
<el-switch v-model="enableXmlWrapper" style="margin-right: 8px;" />
<span :style="{
opacity: enableXmlWrapper ? 1 : 0.7,
color: enableXmlWrapper ? 'var(--main-color)' : undefined
}">XML</span>
</div>
<div style="text-align: right;">
<el-button size="small" @click="testFormVisible = false">{{ t("cancel") }}</el-button>
<el-button size="small" type="primary" @click="onTestConfirm">
{{ t("confirm") }}
</el-button>
</div>
</el-popover>
</div>
</template>
<el-scrollbar height="80vh">
<Diagram :tab-id="props.tabId" />
</el-scrollbar>
<transition name="main-fade" mode="out-in">
<div class="caption" v-show="showCaption">
{{ caption }}
</div>
</transition>
</el-dialog>
</template>
<script setup lang="ts">
import { computed, nextTick, provide, ref } from 'vue';
import Diagram from './diagram.vue';
import { makeNodeTest, topoSortParallel, type DiagramContext, type DiagramState } from './diagram';
import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
import type { ToolStorage } from '../tools';
import { tabs } from '../../panel';
const { t } = useI18n();
const caption = ref('');
const showCaption = ref(false);
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
tabId: {
type: Number,
required: true
}
});
const emit = defineEmits(['update:modelValue']);
const showDialog = computed({
get: () => props.modelValue,
set: v => emit('update:modelValue', v)
});
function setCaption(text: string) {
caption.value = text;
if (caption.value) {
nextTick(() => {
showCaption.value = true;
});
} else {
nextTick(() => {
showCaption.value = false;
});
}
}
const context: DiagramContext = {
reset: () => { },
render: () => { },
state: undefined,
setCaption
};
provide('context', context);
const tab = tabs.content[props.tabId];
const tabStorage = tab.storage as ToolStorage;
const autoDetectDiagram = tabStorage.autoDetectDiagram;
if (autoDetectDiagram) {
// ...
} else {
tabStorage.autoDetectDiagram = {
edges: [],
views: []
};
}
//
const testFormVisible = ref(false);
const enableXmlWrapper = ref(false);
const testPrompt = ref('please call the tool {tool} to make some test');
async function onTestConfirm() {
testFormVisible.value = false;
// enableXmlWrapper.value testPrompt.value
const state = context.state;
tabStorage.autoDetectDiagram!.views = [];
if (state) {
const dispatches = topoSortParallel(state);
for (const nodeIds of dispatches) {
for (const id of nodeIds) {
const view = state.dataView.get(id);
if (view) {
await makeNodeTest(view, enableXmlWrapper.value, testPrompt.value, context)
tabStorage.autoDetectDiagram!.views!.push({
tool: view.tool,
status: view.status,
function: view.function,
result: view.result
});
}
}
}
} else {
ElMessage.error('error');
}
}
</script>
<style>
.no-padding-dialog {
margin-top: 30px !important;
}
.no-padding-dialog .caption {
position: absolute;
left: 20px;
bottom: 10px;
margin: 0 auto;
width: fit-content;
min-height: 32px;
background: rgba(245, 247, 250, 0.05);
border-radius: 8px;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.06);
color: var(--main-color);
font-size: 15px;
display: flex;
align-items: center;
justify-content: center;
padding: 6px 16px;
z-index: 10;
transition: background 0.2s;
}
</style>

View File

@ -1,29 +1,41 @@
<template> <template>
<el-scrollbar height="100%"> <el-scrollbar height="100%">
<AutoDetector :tab-id="props.tabId" />
<div class="tool-module"> <div class="tool-module">
<div class="left"> <div class="left">
<h2> <h2>
<span class="iconfont icon-tool"></span> <span class="iconfont icon-tool"></span>
工具模块 {{ t('tool-module') }}
<el-button
style="font-size: 12px;"
@click="showAutoDetector = true"
>
{{ t('tool-self-detect') }}
</el-button>
</h2> </h2>
<ToolList :tab-id="props.tabId"></ToolList> <ToolList :tab-id="props.tabId"></ToolList>
</div> </div>
<div class="right"> <div class="right">
<ToolExecutor :tab-id="props.tabId"></ToolExecutor> <ToolExecutor :tab-id="props.tabId"></ToolExecutor>
<ToolLogger :tab-id="props.tabId"></ToolLogger> <ToolLogger :tab-id="props.tabId"></ToolLogger>
</div> </div>
</div> </div>
<AutoDetector
v-model="showAutoDetector"
:tab-id="props.tabId"
/>
</el-scrollbar> </el-scrollbar>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineProps } from 'vue'; import { defineProps, ref } from 'vue';
import ToolList from './tool-list.vue'; import ToolList from './tool-list.vue';
import ToolExecutor from './tool-executor.vue'; import ToolExecutor from './tool-executor.vue';
import ToolLogger from './tool-logger.vue'; import ToolLogger from './tool-logger.vue';
import AutoDetector from './auto-detector/index.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
tabId: { tabId: {
@ -31,6 +43,8 @@ const props = defineProps({
required: true required: true
} }
}); });
const showAutoDetector = ref(false);
</script> </script>
<style scoped> <style scoped>

View File

@ -0,0 +1,128 @@
<template>
<div class="swim-pool">
<transition-group name="lane-move" tag="div">
<div v-for="(lane, laneIdx) in lanes" :key="lane.id" class="swim-lane" @dragover.prevent
@drop="onDrop(laneIdx)">
<div class="lane-title">Group {{ laneIdx + 1 }}</div>
<div v-for="tool in lane.tools" :key="tool.name" class="tool-card" draggable="true"
@dragstart="onDragStart(tool, laneIdx)">
{{ tool.name }}
</div>
</div>
</transition-group>
</div>
</template>
<script setup lang="ts">
import type { ToolItem } from '@/hook/type';
import { mcpClientAdapter } from '@/views/connect/core';
import { ref, onMounted } from 'vue';
//
interface Tool {
id: string;
name: string;
}
interface Lane {
id: string;
tools: ToolItem[];
}
//
const tools = ref<ToolItem[]>([]);
//
const lanes = ref<Lane[]>([]);
//
const getAllTools = async () => {
const items = [];
for (const client of mcpClientAdapter.clients) {
const clientTools = await client.getTools();
items.push(...clientTools.values());
}
return items;
};
//
onMounted(async () => {
tools.value = await getAllTools();
console.log(tools.value);
lanes.value = tools.value.map((tool, idx) => ({
id: `lane-${idx}`,
tools: [tool]
}));
});
//
let dragInfo: { tool: ToolItem | null; fromLane: number } = { tool: null, fromLane: -1 };
//
function onDragStart(tool: ToolItem, fromLane: number) {
dragInfo = { tool, fromLane };
}
//
function onDrop(toLane: number) {
if (dragInfo.tool && dragInfo.fromLane !== -1 && dragInfo.fromLane !== toLane) {
//
lanes.value[dragInfo.fromLane].tools = lanes.value[dragInfo.fromLane].tools.filter(
t => t.name !== dragInfo.tool!.name
);
//
lanes.value[toLane].tools.push(dragInfo.tool);
//
if (lanes.value[dragInfo.fromLane].tools.length === 0) {
lanes.value.splice(dragInfo.fromLane, 1);
}
//
lanes.value = lanes.value.map((lane, idx) => ({
...lane,
// id
id: `lane-${idx}`
}));
}
dragInfo = { tool: null, fromLane: -1 };
}
</script>
<style scoped>
.swim-pool {
display: flex;
flex-direction: column;
gap: 12px;
}
.swim-lane {
border: 1px solid var(--sidebar);
min-height: 60px;
margin-bottom: 10px;
padding: 8px;
border-radius: 8px;
transition: box-shadow 0.3s;
box-shadow: 0 2px 8px 0 rgba(0,0,0,0.08);
}
.lane-title {
font-weight: bold;
margin-bottom: 6px;
}
.tool-card {
border: 1px solid var(--main-color);
border-radius: 4px;
padding: 4px 12px;
margin-bottom: 4px;
cursor: grab;
user-select: none;
}
/* 动画样式 */
.lane-move-move {
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
</style>

View File

@ -20,9 +20,16 @@
<el-switch v-else-if="property.type === 'boolean'" active-text="true" inactive-text="false" <el-switch v-else-if="property.type === 'boolean'" active-text="true" inactive-text="false"
v-model="tabStorage.formData[name]" /> v-model="tabStorage.formData[name]" />
<el-input-tag
v-else-if="property.type === 'array'"
v-model="tabStorage.formData[name]"
:placeholder="property.description || t('enter') + ' ' + (property.title || name) + ' (逗号分隔)'"
/>
<k-input-object v-else-if="property.type === 'object'" v-model="tabStorage.formData[name]" <k-input-object v-else-if="property.type === 'object'" v-model="tabStorage.formData[name]"
:schema="property" :schema="property"
:placeholder="property.description || t('enter') + ' ' + (property.title || name)" /> :placeholder="property.description || t('enter') + ' ' + (property.title || name)" />
</el-form-item> </el-form-item>
</template> </template>
@ -33,9 +40,35 @@
<el-button @click="resetForm"> <el-button @click="resetForm">
{{ t('reset') }} {{ t('reset') }}
</el-button> </el-button>
<el-button @click="generateMockData"> <el-button @click="generateMockData" :loading="mockLoading"
:disabled="loading || aiMockLoading || mockLoading">
{{ 'mook' }} {{ 'mook' }}
</el-button> </el-button>
<el-popover placement="top" width="350" trigger="click" v-model:visible="aiPromptVisible">
<template #reference>
<el-button :loading="aiMockLoading" :disabled="loading || aiMockLoading || mockLoading">
{{ 'AI' }}
</el-button>
</template>
<div style="margin-bottom: 8px; font-weight: bold;">
{{ t('edit-ai-mook-prompt') }}
</div>
<el-input type="textarea" v-model="aiMookPrompt" :rows="2" style="margin-bottom: 8px;" />
<div style="display: flex; align-items: center; margin-bottom: 8px;">
<el-switch
v-model="enableXmlWrapper"
style="margin-right: 8px;"
/>
<span style="opacity: 0.7;">XML</span>
</div>
<div style="text-align: right;">
<el-button size="small" @click="aiPromptVisible = false">{{ t('cancel') }}</el-button>
<el-button size="small" type="primary" :loading="aiMockLoading" @click="onAIMookConfirm">
{{ t('confirm') }}
</el-button>
</div>
</el-popover>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
@ -44,7 +77,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent, defineProps, watch, ref, computed } from 'vue'; import { defineComponent, defineProps, watch, ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import type { FormInstance, FormRules } from 'element-plus'; import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { tabs } from '../panel'; import { tabs } from '../panel';
import type { ToolStorage } from './tools'; import type { ToolStorage } from './tools';
import { getDefaultValue, normaliseJavascriptType } from '@/hook/mcp'; import { getDefaultValue, normaliseJavascriptType } from '@/hook/mcp';
@ -52,9 +85,12 @@ import { getDefaultValue, normaliseJavascriptType } from '@/hook/mcp';
import KInputObject from '@/components/k-input-object/index.vue'; import KInputObject from '@/components/k-input-object/index.vue';
import { mcpClientAdapter } from '@/views/connect/core'; import { mcpClientAdapter } from '@/views/connect/core';
import { JSONSchemaFaker } from 'json-schema-faker'; import { JSONSchemaFaker } from 'json-schema-faker';
import { faker } from '@faker-js/faker';
defineComponent({ name: 'tool-executor' }); defineComponent({ name: 'tool-executor' });
const mockLoading = ref(false);
const aiMockLoading = ref(false);
const aiPromptVisible = ref(false);
const enableXmlWrapper = ref(false);
const { t } = useI18n(); const { t } = useI18n();
@ -86,6 +122,7 @@ const currentTool = computed(() => {
} }
}); });
const aiMookPrompt = ref(`please call the tool ${currentTool.value?.name || ''} to make some test`);
const formRules = computed<FormRules>(() => { const formRules = computed<FormRules>(() => {
const rules: FormRules = {}; const rules: FormRules = {};
@ -97,7 +134,7 @@ const formRules = computed<FormRules>(() => {
rules[name] = [ rules[name] = [
{ {
required: true, required: true,
message: `${property.title || name} 是必填字段`, message: `${property.title || name} ` + t("is-required"),
trigger: 'blur' trigger: 'blur'
} }
]; ];
@ -119,7 +156,8 @@ const initFormData = () => {
Object.entries(currentTool.value.inputSchema.properties).forEach(([name, property]) => { Object.entries(currentTool.value.inputSchema.properties).forEach(([name, property]) => {
newSchemaDataForm[name] = getDefaultValue(property); newSchemaDataForm[name] = getDefaultValue(property);
const originType = normaliseJavascriptType(typeof tabStorage.formData[name]); const rawType = Array.isArray(tabStorage.formData[name]) ? 'array' : typeof tabStorage.formData[name];
const originType = normaliseJavascriptType(rawType);
if (tabStorage.formData[name] !== undefined && originType === property.type) { if (tabStorage.formData[name] !== undefined && originType === property.type) {
newSchemaDataForm[name] = tabStorage.formData[name]; newSchemaDataForm[name] = tabStorage.formData[name];
@ -133,24 +171,92 @@ const resetForm = () => {
formRef.value?.resetFields(); formRef.value?.resetFields();
}; };
import { TaskLoop } from '@/components/main-panel/chat/core/task-loop';
import type { ChatStorage } from '../chat/chat-box/chat';
const onAIMookConfirm = async () => {
aiPromptVisible.value = false;
await generateAIMockData(aiMookPrompt.value);
};
const generateAIMockData = async (prompt?: string) => {
if (!currentTool.value?.inputSchema) return;
aiMockLoading.value = true;
try {
const loop = new TaskLoop({ maxEpochs: 1 });
const usePrompt = prompt || `please call the tool ${currentTool.value.name} to make some test`;
const chatStorage = {
messages: [],
settings: {
temperature: 0.6,
systemPrompt: '',
enableTools: [{
name: currentTool.value.name,
description: currentTool.value.description,
inputSchema: currentTool.value.inputSchema,
enabled: true
}],
enableWebSearch: false,
contextLength: 5,
enableXmlWrapper: enableXmlWrapper.value,
parallelToolCalls: false
}
} as ChatStorage;
loop.setMaxEpochs(1);
let aiMockJson: any = undefined;
loop.registerOnToolCall(toolCall => {
console.log(toolCall);
if (toolCall.function?.name === currentTool.value?.name) {
try {
const toolArgs = JSON.parse(toolCall.function?.arguments || '{}');
aiMockJson = toolArgs;
} catch (e) {
ElMessage.error('AI 生成的 JSON 解析错误');
}
} else {
ElMessage.error('AI 调用了未知的工具');
}
loop.abort();
return toolCall;
});
loop.registerOnError(error => {
ElMessage.error(error + '');
});
await loop.start(chatStorage, usePrompt);
if (aiMockJson && typeof aiMockJson === 'object') {
Object.keys(aiMockJson).forEach(key => {
tabStorage.formData[key] = aiMockJson[key];
});
formRef.value?.clearValidate?.();
}
} finally {
aiMockLoading.value = false;
}
};
const generateMockData = async () => { const generateMockData = async () => {
if (!currentTool.value?.inputSchema) return; if (!currentTool.value?.inputSchema) return;
mockLoading.value = true;
// fakerjson-schema-faker try {
JSONSchemaFaker.option({ JSONSchemaFaker.option({
useDefaultValue: true, useDefaultValue: true,
alwaysFakeOptionals: true alwaysFakeOptionals: true
}); });
const mockData = await JSONSchemaFaker.resolve(currentTool.value.inputSchema as any) as any;
// mock Object.keys(mockData).forEach(key => {
const mockData = await JSONSchemaFaker.resolve(currentTool.value.inputSchema as any); tabStorage.formData[key] = mockData[key];
});
console.log(mockData); formRef.value?.clearValidate?.();
} finally {
// mockLoading.value = false;
// Object.keys(mockData).forEach(key => { }
// tabStorage.formData[key] = mockData[key];
// });
}; };
async function handleExecute() { async function handleExecute() {
@ -165,10 +271,15 @@ async function handleExecute() {
} }
} }
watch(currentTool, (tool) => {
aiMookPrompt.value = `please call the tool ${tool?.name || ''} to make some test`;
});
watch(() => tabStorage.currentToolName, () => { watch(() => tabStorage.currentToolName, () => {
initFormData(); initFormData();
resetForm(); resetForm();
}, { immediate: true }); }, { immediate: true });
</script> </script>
<style> <style>
@ -191,4 +302,14 @@ watch(() => tabStorage.currentToolName, () => {
.tool-executor-container .el-switch__core { .tool-executor-container .el-switch__core {
border: 1px solid var(--main-color) !important; border: 1px solid var(--main-color) !important;
} }
.el-button:active {
transform: scale(0.95);
transition: transform 0.08s;
}
.el-tag.el-tag--info {
background-color: var(--main-color);
}
</style> </style>

View File

@ -19,6 +19,8 @@
<!-- body --> <!-- body -->
<div class="tool-list-container-scrollbar"> <div class="tool-list-container-scrollbar">
<el-scrollbar height="fit-content"> <el-scrollbar height="fit-content">
<div class="tool-list-container-scrollbar">
<el-scrollbar height="fit-content" v-if="(client.tools?.size || 0) > 0">
<div class="tool-list-container"> <div class="tool-list-container">
<div class="item" :class="{ 'active': tabStorage.currentToolName === tool.name }" <div class="item" :class="{ 'active': tabStorage.currentToolName === tool.name }"
v-for="tool of client.tools?.values()" :key="tool.name" @click="handleClick(tool)"> v-for="tool of client.tools?.values()" :key="tool.name" @click="handleClick(tool)">
@ -28,6 +30,15 @@
</div> </div>
</div> </div>
</el-scrollbar> </el-scrollbar>
<div v-else style="padding: 10px;">
<div class="empty-description">
<span class="iconfont icon-empty"
style="font-size: 22px; opacity: 0.4; margin-right: 6px;"></span>
<span style="opacity: 0.6;">No tools found.</span>
</div>
</div>
</div>
</el-scrollbar>
</div> </div>
</el-collapse-item> </el-collapse-item>
</el-collapse> </el-collapse>
@ -117,6 +128,11 @@ onMounted(async () => {
transition: var(--animation-3s); transition: var(--animation-3s);
} }
.tool-list-container>.item:active {
transform: scale(0.95);
transition: var(--animation-3s);
}
.tool-list-container>.item:hover { .tool-list-container>.item:hover {
background-color: var(--main-light-color); background-color: var(--main-light-color);
transition: var(--animation-3s); transition: var(--animation-3s);
@ -158,4 +174,13 @@ onMounted(async () => {
opacity: 0.6; opacity: 0.6;
font-size: 12.5px; font-size: 12.5px;
} }
.empty-description {
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-placeholder, #bbb);
font-size: 15px;
min-height: 40px;
}
</style> </style>

View File

@ -20,8 +20,14 @@
<div v-else> <div v-else>
<!-- 展示原本的信息 --> <!-- 展示原本的信息 -->
<template v-if="!showRawJson"> <template v-if="!showRawJson && tabStorage.lastToolCallResponse">
{{tabStorage.lastToolCallResponse?.content.map(c => c.text).join('\n')}} <div
v-for="(c, idx) in tabStorage.lastToolCallResponse!.content"
:key="idx"
class="tool-call-block"
>
<pre class="tool-call-text">{{ c.text }}</pre>
</div>
</template> </template>
<!-- 展示 json --> <!-- 展示 json -->
@ -105,4 +111,21 @@ const showRawJson = ref(false);
padding: 5px 9px; padding: 5px 9px;
border-radius: .5em; border-radius: .5em;
} }
.tool-call-block {
margin-bottom: 12px;
padding: 10px 12px;
background: rgba(0,0,0,0.04);
border-radius: 6px;
box-shadow: 0 1px 2px rgba(0,0,0,0.03);
}
.tool-call-text {
font-family: var(--code-font-family, monospace);
font-size: 15px;
white-space: pre-wrap;
word-break: break-all;
margin: 0;
color: var(--el-text-color-primary, #222);
}
</style> </style>

View File

@ -1,8 +1,14 @@
import type { ToolCallResponse } from '@/hook/type'; import type { ToolCallResponse } from '@/hook/type';
import type { Edge, Node, NodeDataView } from './auto-detector/diagram';
export interface ToolStorage { export interface ToolStorage {
activeNames: any[]; activeNames: any[];
currentToolName: string; currentToolName: string;
lastToolCallResponse?: ToolCallResponse | string; lastToolCallResponse?: ToolCallResponse | string;
formData: Record<string, any>; formData: Record<string, any>;
autoDetectDiagram?: {
edges?: Edge[];
views?: NodeDataView[];
[key: string]: any;
}
} }

View File

@ -11,6 +11,8 @@ export function getDefaultValue(property: TypeAble): any {
return false; return false;
} else if (property.type === 'object') { } else if (property.type === 'object') {
return {}; return {};
} else if (property.type === 'array') {
return [];
} else { } else {
return ''; return '';
} }
@ -26,6 +28,8 @@ export function normaliseJavascriptType(type: string) {
return 'boolean'; return 'boolean';
case 'string': case 'string':
return 'string'; return 'string';
case 'array':
return 'array';
default: default:
return type; return type;
} }

View File

@ -91,3 +91,15 @@ export function getImageBlobUrlByBase64(base64String: string, mimeType: string,
} }
return blobUrl; return blobUrl;
} }
export function gotoWebsite(url: string) {
const platform = getPlatform();
const bridge = useMessageBridge();
if (platform === 'vscode') {
// For VSCode, use the webview API to open external links
bridge.commandRequest('vscode/openExternal', { url });
} else if (platform === 'web') {
// For web, use the standard window.open method
window.open(url, '_blank');
}
}

View File

@ -183,5 +183,17 @@
"export": "تصدير", "export": "تصدير",
"export-filename": "اسم ملف التصدير", "export-filename": "اسم ملف التصدير",
"how-to-use": "كيفية الاستخدام؟", "how-to-use": "كيفية الاستخدام؟",
"is-required": "هو حقل مطلوب" "is-required": "هو حقل مطلوب",
"edit-ai-mook-prompt": "تحرير إشارات AI Mook",
"start-auto-detect": "بدء عملية الفحص الذاتي",
"tool-module": "وحدة الأدوات",
"prompt-module": "وحدة المطالبات",
"not-select-begin-node": "لم يتم تحديد عقدة البداية",
"can-make-loop": "سيؤدي الاتصال إلى تكوين حلقة",
"this-is-repeat-connection": "هذا رابط مكرر",
"ai-gen-error-json": "خطأ في تحليل JSON الذي تم إنشاؤه بواسطة الذكاء الاصطناعي",
"ai-invoke-unknown-tool": "استدعت الذكاء الاصطناعي أداة غير معروفة",
"click-edge-to-delete": "انقر على الحافة للحذف",
"select-node-define-test-tomo": "اختر عقدة أخرى لتحديد طوبولوجيا الاختبار",
"tool-self-detect": "الفحص الذاتي للأداة"
} }

View File

@ -183,5 +183,17 @@
"export": "Exportieren", "export": "Exportieren",
"export-filename": "Exportdateiname", "export-filename": "Exportdateiname",
"how-to-use": "Wie benutzt man?", "how-to-use": "Wie benutzt man?",
"is-required": "ist ein Pflichtfeld" "is-required": "ist ein Pflichtfeld",
"edit-ai-mook-prompt": "AI Mook-Prompts bearbeiten",
"start-auto-detect": "Selbsttest starten",
"tool-module": "Werkzeugmodul",
"prompt-module": "Aufforderungsmodul",
"not-select-begin-node": "Kein Startknoten ausgewählt",
"can-make-loop": "Die Verbindung wird eine Schleife bilden",
"this-is-repeat-connection": "Dies ist ein doppelter Link",
"ai-gen-error-json": "Fehler beim Parsen von KI-generiertem JSON",
"ai-invoke-unknown-tool": "KI hat ein unbekanntes Tool aufgerufen",
"click-edge-to-delete": "Klicken Sie auf die Kante, um sie zu löschen",
"select-node-define-test-tomo": "Wählen Sie einen anderen Knoten aus, um die Testtopologie zu definieren",
"tool-self-detect": "Werkzeug-Selbsttest"
} }

View File

@ -183,5 +183,17 @@
"export": "Export", "export": "Export",
"export-filename": "Export filename", "export-filename": "Export filename",
"how-to-use": "How to use?", "how-to-use": "How to use?",
"is-required": "is a required field" "is-required": "is a required field",
"edit-ai-mook-prompt": "Edit AI Mook prompts",
"start-auto-detect": "Start self-check",
"tool-module": "Tool module",
"prompt-module": "Prompt Module",
"not-select-begin-node": "No starting node selected",
"can-make-loop": "The connection will form a loop",
"this-is-repeat-connection": "This is a duplicate link",
"ai-gen-error-json": "AI-generated JSON parsing error",
"ai-invoke-unknown-tool": "AI called an unknown tool",
"click-edge-to-delete": "Click the edge to delete",
"select-node-define-test-tomo": "Select another node to define the test topology",
"tool-self-detect": "Tool Self-Check"
} }

View File

@ -183,5 +183,17 @@
"export": "Exporter", "export": "Exporter",
"export-filename": "Nom du fichier d'exportation", "export-filename": "Nom du fichier d'exportation",
"how-to-use": "Comment utiliser ?", "how-to-use": "Comment utiliser ?",
"is-required": "est un champ obligatoire" "is-required": "est un champ obligatoire",
"edit-ai-mook-prompt": "Modifier les invites AI Mook",
"start-auto-detect": "Démarrer l'autovérification",
"tool-module": "Module d'outils",
"prompt-module": "Module d'invite",
"not-select-begin-node": "Aucun nœud de départ sélectionné",
"can-make-loop": "La connexion formera une boucle",
"this-is-repeat-connection": "Ceci est un lien en double",
"ai-gen-error-json": "Erreur d'analyse JSON générée par IA",
"ai-invoke-unknown-tool": "L'IA a appelé un outil inconnu",
"click-edge-to-delete": "Cliquez sur le bord pour supprimer",
"select-node-define-test-tomo": "Sélectionnez un autre nœud pour définir la topologie de test",
"tool-self-detect": "Auto-vérification de l'outil"
} }

View File

@ -183,5 +183,17 @@
"export": "エクスポート", "export": "エクスポート",
"export-filename": "エクスポートファイル名", "export-filename": "エクスポートファイル名",
"how-to-use": "使用方法", "how-to-use": "使用方法",
"is-required": "は必須フィールドです" "is-required": "は必須フィールドです",
"edit-ai-mook-prompt": "AI Mookプロンプトを編集",
"start-auto-detect": "自己診断を開始",
"tool-module": "ツールモジュール",
"prompt-module": "プロンプトモジュール",
"not-select-begin-node": "開始ノードが選択されていません",
"can-make-loop": "接続によりループが形成されます",
"this-is-repeat-connection": "これは重複したリンクです",
"ai-gen-error-json": "AI生成JSONの解析エラー",
"ai-invoke-unknown-tool": "AIが未知のツールを呼び出しました",
"click-edge-to-delete": "クリックして削除",
"select-node-define-test-tomo": "テストトポロジを定義するために別のノードを選択してください",
"tool-self-detect": "ツール自己診断"
} }

View File

@ -183,5 +183,17 @@
"export": "내보내기", "export": "내보내기",
"export-filename": "내보내기 파일 이름", "export-filename": "내보내기 파일 이름",
"how-to-use": "사용 방법?", "how-to-use": "사용 방법?",
"is-required": "는 필수 필드입니다" "is-required": "는 필수 필드입니다",
"edit-ai-mook-prompt": "AI Mook 프롬프트 편집",
"start-auto-detect": "자체 점검 시작",
"tool-module": "도구 모듈",
"prompt-module": "프롬프트 모듈",
"not-select-begin-node": "시작 노드가 선택되지 않았습니다",
"can-make-loop": "연결이 루프를 형성합니다",
"this-is-repeat-connection": "이것은 중복된 링크입니다",
"ai-gen-error-json": "AI 생성 JSON 구문 분석 오류",
"ai-invoke-unknown-tool": "AI가 알 수 없는 도구를 호출했습니다",
"click-edge-to-delete": "가장자리를 클릭하여 삭제",
"select-node-define-test-tomo": "테스트 토폴로지를 정의하려면 다른 노드를 선택하세요",
"tool-self-detect": "도구 자체 점검"
} }

View File

@ -183,5 +183,17 @@
"export": "Экспорт", "export": "Экспорт",
"export-filename": "Имя файла экспорта", "export-filename": "Имя файла экспорта",
"how-to-use": "Как использовать?", "how-to-use": "Как использовать?",
"is-required": "является обязательным полем" "is-required": "является обязательным полем",
"edit-ai-mook-prompt": "Редактировать подсказки AI Mook",
"start-auto-detect": "Запустить самопроверку",
"tool-module": "Модуль инструментов",
"prompt-module": "Модуль подсказок",
"not-select-begin-node": "Начальный узел не выбран",
"can-make-loop": "Соединение образует петлю",
"this-is-repeat-connection": "Это повторяющаяся ссылка",
"ai-gen-error-json": "Ошибка разбора JSON, созданного ИИ",
"ai-invoke-unknown-tool": "ИИ вызвал неизвестный инструмент",
"click-edge-to-delete": "Нажмите на край, чтобы удалить",
"select-node-define-test-tomo": "Выберите другой узел для определения тестовой топологии",
"tool-self-detect": "Самопроверка инструмента"
} }

View File

@ -183,5 +183,17 @@
"export": "导出", "export": "导出",
"export-filename": "导出文件名", "export-filename": "导出文件名",
"how-to-use": "如何使用?", "how-to-use": "如何使用?",
"is-required": "是必填字段" "is-required": "是必填字段",
"edit-ai-mook-prompt": "编辑 AI Mook 提示词",
"start-auto-detect": "开启自检程序",
"tool-module": "工具模块",
"prompt-module": "提示词模块",
"not-select-begin-node": "未选择起始节点",
"can-make-loop": "连接会形成环路",
"this-is-repeat-connection": "这是一个重复的连接",
"ai-gen-error-json": "AI 生成的 JSON 解析错误",
"ai-invoke-unknown-tool": "AI 调用了未知的工具",
"click-edge-to-delete": "点击边以删除",
"select-node-define-test-tomo": "选择另一个节点以定义测试拓扑",
"tool-self-detect": "工具自检"
} }

View File

@ -183,5 +183,17 @@
"export": "導出", "export": "導出",
"export-filename": "導出文件名", "export-filename": "導出文件名",
"how-to-use": "如何使用?", "how-to-use": "如何使用?",
"is-required": "是必填欄位" "is-required": "是必填欄位",
"edit-ai-mook-prompt": "編輯AI Mook提示詞",
"start-auto-detect": "開啟自檢程序",
"tool-module": "工具模組",
"prompt-module": "提示詞模組",
"not-select-begin-node": "未選擇起始節點",
"can-make-loop": "連接會形成環路",
"this-is-repeat-connection": "這是一個重複的連結",
"ai-gen-error-json": "AI 生成的 JSON 解析錯誤",
"ai-invoke-unknown-tool": "AI調用了未知的工具",
"click-edge-to-delete": "點擊邊緣以刪除",
"select-node-define-test-tomo": "選擇另一個節點以定義測試拓撲",
"tool-self-detect": "工具自檢"
} }

View File

@ -27,7 +27,7 @@
<div style="display: inline-flex;"> <div style="display: inline-flex;">
<el-button class="join-qq" type="primary" <el-button class="join-qq" type="primary"
@click="joinQQGroup('https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD')"> @click="gotoWebsite('https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD')">
<span class="iconfont icon-QQ"></span> <span class="iconfont icon-QQ"></span>
{{ t('join-discussion') }} {{ t('join-discussion') }}
</el-button> </el-button>
@ -42,24 +42,17 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { gotoWebsite } from '@/hook/util';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
const { t } = useI18n(); const { t } = useI18n();
const version = '0.1.8'; const version = '0.1.9';
const author = 'LSTM-Kirigaya (锦恢)'; const author = 'LSTM-Kirigaya (锦恢)';
defineComponent({ name: 'about' }); defineComponent({ name: 'about' });
function joinQQGroup(url: string) {
window.open(url, '_blank');
}
function gotoWebsite(url: string) {
window.open(url, '_blank');
}
</script> </script>
<style> <style>

View File

@ -108,6 +108,7 @@ function chooseDebugMode(index: number) {
.welcome-container > span { .welcome-container > span {
flex: 1 1 calc(50% - 100px); flex: 1 1 calc(50% - 100px);
box-sizing: border-box; box-sizing: border-box;
transition: .3s transform ease-in-out;
} }
.debug-option { .debug-option {

View File

@ -32,6 +32,11 @@ export function onGeneralColorChange(colorString: string) {
document?.documentElement.style.setProperty( document?.documentElement.style.setProperty(
'--main-light-color', `rgba(${r}, ${g}, ${b}, 0.7)`); '--main-light-color', `rgba(${r}, ${g}, ${b}, 0.7)`);
for (let i = 1; i <= 9; ++ i) {
document?.documentElement.style.setProperty(
`--main-light-color-${i}0`, `rgba(${r}, ${g}, ${b}, 0.${i})`);
}
} }
export const predefinedColors = [ export const predefinedColors = [

File diff suppressed because one or more lines are too long

View File

@ -105,6 +105,12 @@ export function revealOpenMcpWebviewPanel(
exportFile(data.filename, data.content); exportFile(data.filename, data.content);
break; break;
case 'vscode/openExternal':
if (data.url) {
vscode.env.openExternal(vscode.Uri.parse(data.url));
}
break;
case 'vscode/clipboard/writeText': case 'vscode/clipboard/writeText':
vscode.env.clipboard.writeText(data.text); vscode.env.clipboard.writeText(data.text);
break; break;

690
yarn.lock

File diff suppressed because it is too large Load Diff