feat: 更新前端页面
- Agents, Chat, Skill 页面 - useSkills composable - package.json 更新 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
261
web/package-lock.json
generated
261
web/package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@vue-office/docx": "^1.6.3",
|
||||
"@vue-office/excel": "^1.7.14",
|
||||
"axios": "^1.13.6",
|
||||
"echarts": "^6.0.0",
|
||||
"element-plus": "^2.13.3",
|
||||
"lucide-vue-next": "^0.577.0",
|
||||
@@ -1314,6 +1315,12 @@
|
||||
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.27",
|
||||
"resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.27.tgz",
|
||||
@@ -1351,6 +1358,17 @@
|
||||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.6",
|
||||
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.6.tgz",
|
||||
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -1441,6 +1459,19 @@
|
||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase-css": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||
@@ -1510,6 +1541,18 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz",
|
||||
@@ -1552,6 +1595,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
@@ -1575,6 +1627,20 @@
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.0.0.tgz",
|
||||
@@ -1629,6 +1695,51 @@
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
|
||||
@@ -1737,6 +1848,42 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "5.3.4",
|
||||
"resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-5.3.4.tgz",
|
||||
@@ -1770,12 +1917,48 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
@@ -1789,11 +1972,49 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
@@ -1957,6 +2178,15 @@
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
@@ -1987,6 +2217,27 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.9",
|
||||
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz",
|
||||
@@ -2348,6 +2599,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"@vue-office/docx": "^1.6.3",
|
||||
"@vue-office/excel": "^1.7.14",
|
||||
"axios": "^1.13.6",
|
||||
"echarts": "^6.0.0",
|
||||
"element-plus": "^2.13.3",
|
||||
"lucide-vue-next": "^0.577.0",
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
// Agents 数据
|
||||
const agents = ref([
|
||||
{ id: 1, name: 'Claude Agent', avatar: '🧠', description: 'General purpose AI assistant', accentColor: '#f97316', gradient: 'from-orange-500/20 to-amber-500/20', status: 'running' as const, framework: 'Google ADK', model: 'gemini-2.0-flash', mcpServers: 2, createdAt: '2025-04-10' },
|
||||
{ id: 2, name: 'Code Assistant', avatar: '💻', description: 'Specialized in code generation', accentColor: '#3b82f6', gradient: 'from-blue-500/20 to-cyan-500/20', status: 'running' as const, framework: 'OpenAI', model: 'gpt-4o', mcpServers: 1, createdAt: '2025-04-08' },
|
||||
{ id: 3, name: 'Data Analyst', avatar: '📊', description: 'Data analysis and visualization', accentColor: '#10b981', gradient: 'from-emerald-500/20 to-green-500/20', status: 'stopped' as const, framework: 'PydanticAI', model: 'gpt-4o-mini', mcpServers: 3, createdAt: '2025-04-05' },
|
||||
{ id: 4, name: 'Research Bot', avatar: '🔬', description: 'Academic research assistant', accentColor: '#8b5cf6', gradient: 'from-violet-500/20 to-purple-500/20', status: 'running' as const, framework: 'LangChain', model: 'claude-3-5-sonnet', mcpServers: 2, createdAt: '2025-04-12' },
|
||||
{ id: 5, name: '客服助手', avatar: '🎧', description: 'Customer support agent', accentColor: '#ec4899', gradient: 'from-pink-500/20 to-rose-500/20', status: 'running' as const, framework: 'Google ADK', model: 'gemini-1.5-pro', mcpServers: 4, createdAt: '2025-04-11' },
|
||||
])
|
||||
|
||||
// 创建智能体弹窗状态
|
||||
const showCreateModal = ref(false)
|
||||
const isCreating = ref(false)
|
||||
@@ -65,29 +74,6 @@ const createAgent = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
interface Agent {
|
||||
id: number
|
||||
name: string
|
||||
avatar: string
|
||||
description: string
|
||||
accentColor: string
|
||||
gradient: string
|
||||
status: 'running' | 'stopped'
|
||||
framework: string
|
||||
model: string
|
||||
mcpServers: number
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
// 管理页面的 agents
|
||||
const agents = ref<Agent[]>([
|
||||
{ id: 1, name: 'Claude Agent', avatar: '🧠', description: 'General purpose AI assistant', accentColor: '#f97316', gradient: 'from-orange-500/20 to-amber-500/20', status: 'running', framework: 'Google ADK', model: 'gemini-2.0-flash', mcpServers: 2, createdAt: '2025-04-10' },
|
||||
{ id: 2, name: 'Code Assistant', avatar: '💻', description: 'Specialized in code generation', accentColor: '#3b82f6', gradient: 'from-blue-500/20 to-cyan-500/20', status: 'running', framework: 'OpenAI', model: 'gpt-4o', mcpServers: 1, createdAt: '2025-04-08' },
|
||||
{ id: 3, name: 'Data Analyst', avatar: '📊', description: 'Data analysis and visualization', accentColor: '#10b981', gradient: 'from-emerald-500/20 to-green-500/20', status: 'stopped', framework: 'PydanticAI', model: 'gpt-4o-mini', mcpServers: 3, createdAt: '2025-04-05' },
|
||||
{ id: 4, name: 'Research Bot', avatar: '🔬', description: 'Academic research assistant', accentColor: '#8b5cf6', gradient: 'from-violet-500/20 to-purple-500/20', status: 'running', framework: 'LangChain', model: 'claude-3-5-sonnet', mcpServers: 2, createdAt: '2025-04-12' },
|
||||
{ id: 5, name: '客服助手', avatar: '🎧', description: 'Customer support agent', accentColor: '#ec4899', gradient: 'from-pink-500/20 to-rose-500/20', status: 'running', framework: 'Google ADK', model: 'gemini-1.5-pro', mcpServers: 4, createdAt: '2025-04-11' },
|
||||
])
|
||||
|
||||
const searchQuery = ref('')
|
||||
const filterStatus = ref('all')
|
||||
|
||||
@@ -118,7 +104,7 @@ const statusClass = (status: string) => {
|
||||
}
|
||||
|
||||
// 切换状态
|
||||
const toggleStatus = (agent: Agent) => {
|
||||
const toggleStatus = (agent: any) => {
|
||||
agent.status = agent.status === 'running' ? 'stopped' : 'running'
|
||||
}
|
||||
|
||||
@@ -143,54 +129,6 @@ const deleteAgent = (id: number) => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-dark-700 rounded-xl p-4 border border-dark-500">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-dark-600 flex items-center justify-center">
|
||||
<i class="fa-solid fa-robot text-gray-400"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-bold text-white">{{ stats.total }}</div>
|
||||
<div class="text-xs text-gray-400">Total Agents</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-dark-700 rounded-xl p-4 border border-dark-500">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-primary-success/20 flex items-center justify-center">
|
||||
<i class="fa-solid fa-circle-check text-primary-success"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-bold text-primary-success">{{ stats.running }}</div>
|
||||
<div class="text-xs text-gray-400">Running</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-dark-700 rounded-xl p-4 border border-dark-500">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gray-500/20 flex items-center justify-center">
|
||||
<i class="fa-solid fa-circle-stop text-gray-400"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-bold text-gray-400">{{ stats.stopped }}</div>
|
||||
<div class="text-xs text-gray-400">Stopped</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-dark-700 rounded-xl p-4 border border-dark-500">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-primary-cyan/20 flex items-center justify-center">
|
||||
<i class="fa-solid fa-plug text-primary-cyan"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-bold text-primary-cyan">{{ agents.reduce((sum, a) => sum + a.mcpServers, 0) }}</div>
|
||||
<div class="text-xs text-gray-400">MCP Servers</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="flex gap-4 mb-6">
|
||||
<div class="flex-1 relative">
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick } from 'vue'
|
||||
|
||||
const API_BASE = 'http://localhost:8082'
|
||||
|
||||
interface ChatMessage {
|
||||
id: number
|
||||
role: 'user' | 'assistant'
|
||||
@@ -158,17 +160,24 @@ const sendMessage = async () => {
|
||||
nextTick(() => scrollToBottom())
|
||||
|
||||
isLoading.value = true
|
||||
const fullResponse = `我理解你发送了消息: "${userContent}"
|
||||
|
||||
作为 AI 助手,我可以帮助你:
|
||||
• 回答各种问题
|
||||
• 编写代码和调试
|
||||
• 分析和处理数据
|
||||
• 翻译和写作
|
||||
• 头脑风暴和创意建议
|
||||
try {
|
||||
// 调用后端 API
|
||||
const response = await fetch(`${API_BASE}/api/agent/chat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
agent_id: selectedAgent.value?.id || 1,
|
||||
message: userContent,
|
||||
}),
|
||||
})
|
||||
|
||||
请告诉我你需要什么帮助?`
|
||||
const data = await response.json()
|
||||
const fullResponse = data.reply || data.response || 'No response'
|
||||
|
||||
// 流式显示回复
|
||||
let currentIndex = 0
|
||||
const words = fullResponse.split('')
|
||||
|
||||
@@ -183,6 +192,11 @@ const sendMessage = async () => {
|
||||
isLoading.value = false
|
||||
}
|
||||
}, 30)
|
||||
} catch (error: any) {
|
||||
aiMessage.content = `Error: ${error.message || 'Failed to send message'}`
|
||||
aiMessage.isStreaming = false
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动到底部
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { useSkills } from './skill/useSkills'
|
||||
import { Play, Pause, Edit, Trash2 } from 'lucide-vue-next'
|
||||
import { Edit, Trash2, Wand2, Plus, Search, X } from 'lucide-vue-next'
|
||||
import '@/views/database/database.css'
|
||||
|
||||
const {
|
||||
skillsLoading,
|
||||
searchQuery,
|
||||
filterStatus,
|
||||
isEditing,
|
||||
isCreating,
|
||||
editForm,
|
||||
newSkillForm,
|
||||
categories,
|
||||
types,
|
||||
filteredSkills,
|
||||
statusClass,
|
||||
fetchSkills,
|
||||
openCreate,
|
||||
closeCreate,
|
||||
saveNewSkill,
|
||||
openEdit,
|
||||
closeEdit,
|
||||
saveEdit,
|
||||
toggleStatus,
|
||||
deleteSkill,
|
||||
} = useSkills()
|
||||
|
||||
// 页面加载时获取技能列表
|
||||
onMounted(() => {
|
||||
fetchSkills()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -30,11 +34,11 @@ const {
|
||||
<!-- 顶部导航 -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-wand-magic-sparkles text-orange-500"></i>
|
||||
<Wand2 class="w-5 h-5 text-orange-500" />
|
||||
<span class="font-medium">Skills</span>
|
||||
</div>
|
||||
<button @click="openCreate" class="btn-primary">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
<Plus class="w-4 h-4" />
|
||||
New Skill
|
||||
</button>
|
||||
</div>
|
||||
@@ -42,7 +46,7 @@ const {
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="flex gap-4 mb-6">
|
||||
<div class="flex-1 relative">
|
||||
<i class="fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
||||
<Search class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
@@ -59,15 +63,12 @@ const {
|
||||
</div>
|
||||
|
||||
<!-- Skills 列表 -->
|
||||
<div class="bg-dark-700 rounded-xl overflow-hidden">
|
||||
<div class="bg-dark-700 rounded-xl overflow-hidden shadow-lg">
|
||||
<table v-if="filteredSkills.length > 0" class="w-full">
|
||||
<thead class="bg-dark-600">
|
||||
<tr>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Skill Name</th>
|
||||
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Type</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400 w-1/3">Skill Name</th>
|
||||
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Category</th>
|
||||
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Port</th>
|
||||
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Tools</th>
|
||||
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Status</th>
|
||||
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Created</th>
|
||||
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Actions</th>
|
||||
@@ -76,31 +77,28 @@ const {
|
||||
<tbody>
|
||||
<tr v-for="skill in filteredSkills" :key="skill.id" class="table-row">
|
||||
<td class="px-5 py-4">
|
||||
<div class="font-medium">{{ skill.name }}</div>
|
||||
<div class="text-sm text-gray-500">{{ skill.description }}</div>
|
||||
<div class="flex gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-orange-500/20 flex items-center justify-center flex-shrink-0 self-center">
|
||||
<Wand2 class="w-5 h-5 text-orange-400" />
|
||||
</div>
|
||||
<div class="min-w-0 self-center">
|
||||
<div class="font-medium">{{ skill.skill_name }}</div>
|
||||
<div class="text-sm text-gray-500 line-clamp-2 mt-0.5">{{ skill.skill_desc || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-center">
|
||||
<span class="bg-dark-500 px-2 py-1 rounded text-sm">{{ skill.type }}</span>
|
||||
<span class="text-gray-400 text-sm">{{ skill.skill_type === 'system' ? 'System' : 'User' }}</span>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-center">
|
||||
<span class="text-gray-400 text-sm capitalize">{{ skill.category }}</span>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-center text-gray-400 text-sm">{{ skill.port }}</td>
|
||||
<td class="px-5 py-4 text-center">
|
||||
<span class="text-primary-cyan">{{ skill.tools }}</span>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-center">
|
||||
<span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs" :class="skill.status === 'running' ? 'bg-green-500/20 text-green-400' : skill.status === 'error' ? 'bg-red-500/20 text-red-400' : 'bg-gray-500/20 text-gray-400'">
|
||||
<span class="w-1.5 h-1.5 rounded-full" :class="statusClass(skill.status)"></span>
|
||||
<span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs" :class="skill.status === 'active' ? 'bg-green-500/20 text-green-400' : 'bg-gray-500/20 text-gray-400'">
|
||||
<span class="w-1.5 h-1.5 rounded-full" :class="skill.status === 'active' ? 'bg-green-500' : 'bg-gray-400'"></span>
|
||||
<span class="capitalize">{{ skill.status }}</span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-center text-gray-400 text-sm">{{ skill.createdAt }}</td>
|
||||
<td class="px-5 py-4 text-center text-gray-400 text-sm">{{ skill.created_at ? new Date(skill.created_at).toLocaleDateString() : '-' }}</td>
|
||||
<td class="px-5 py-4">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<button @click="toggleStatus(skill)" class="btn-icon" :title="skill.status === 'running' ? 'Stop' : 'Start'">
|
||||
<component :is="skill.status === 'running' ? Pause : Play" class="w-4 h-4 text-gray-400 hover:text-white" />
|
||||
</button>
|
||||
<button @click="openEdit(skill)" class="btn-icon" title="Edit">
|
||||
<Edit class="w-4 h-4 text-gray-400 hover:text-white" />
|
||||
</button>
|
||||
@@ -116,7 +114,7 @@ const {
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="empty-box">
|
||||
<div class="empty-icon">
|
||||
<i class="fa-solid fa-wand-magic-sparkles"></i>
|
||||
<Wand2 class="w-12 h-12" />
|
||||
</div>
|
||||
<p class="empty-text">No skills found</p>
|
||||
<p class="empty-tip">Click "New Skill" to add a skill</p>
|
||||
@@ -130,39 +128,27 @@ const {
|
||||
<div class="flex items-center justify-between p-5 border-b border-dark-500">
|
||||
<h3 class="text-lg font-semibold">Edit Skill</h3>
|
||||
<button @click="closeEdit" class="btn-icon">
|
||||
<i class="fa-solid fa-xmark text-xl"></i>
|
||||
<X class="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-5 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Skill Name</label>
|
||||
<input v-model="editForm.name" type="text" class="input-field">
|
||||
<input v-model="editForm.skill_name" type="text" class="input-field">
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Type</label>
|
||||
<el-select v-model="editForm.type" placeholder="Select" class="w-full" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option v-for="type in types" :key="type" :label="type" :value="type" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Category</label>
|
||||
<el-select v-model="editForm.category" placeholder="Select" class="w-full" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option v-for="cat in categories" :key="cat.value" :label="cat.label" :value="cat.value" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Port</label>
|
||||
<input v-model="editForm.port" type="number" class="input-field">
|
||||
<select v-model="editForm.skill_type" class="input-field">
|
||||
<option value="user">User</option>
|
||||
<option value="system">System</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Description</label>
|
||||
<textarea v-model="editForm.description" rows="2" class="input-field resize-none"></textarea>
|
||||
<textarea v-model="editForm.skill_desc" rows="2" class="input-field resize-none"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -181,7 +167,7 @@ const {
|
||||
<div class="flex items-center justify-between p-5 border-b border-dark-600 bg-dark-700/50">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-orange-500 to-red-500 flex items-center justify-center">
|
||||
<i class="fa-solid fa-wand-magic-sparkles text-white"></i>
|
||||
<Wand2 class="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-white">Create New Skill</h3>
|
||||
@@ -189,30 +175,33 @@ const {
|
||||
</div>
|
||||
</div>
|
||||
<button @click="closeCreate" class="btn-icon">
|
||||
<i class="fa-solid fa-xmark text-xl"></i>
|
||||
<X class="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-5 space-y-4 max-h-[60vh] overflow-y-auto">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Skill Name</label>
|
||||
<input v-model="newSkillForm.name" type="text" placeholder="Enter skill name..." class="input-field">
|
||||
<input v-model="newSkillForm.skill_name" type="text" placeholder="Enter skill name..." class="input-field">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Category</label>
|
||||
<select v-model="newSkillForm.skill_type" class="input-field">
|
||||
<option value="user">User</option>
|
||||
<option value="system">System</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Description</label>
|
||||
<textarea v-model="newSkillForm.description" rows="2" placeholder="Describe this skill..." class="input-field resize-none"></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Markdown Content</label>
|
||||
<textarea v-model="newSkillForm.markdown" rows="10" placeholder="# Skill Documentation Describe your skill here using Markdown..." class="input-field resize-none font-mono text-sm"></textarea>
|
||||
<textarea v-model="newSkillForm.skill_desc" rows="2" placeholder="Describe this skill..." class="input-field resize-none"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-3 p-5 border-t border-dark-600 bg-dark-700/50">
|
||||
<button @click="closeCreate" class="btn-secondary">Cancel</button>
|
||||
<button @click="saveNewSkill" :disabled="!newSkillForm.name || !newSkillForm.description || !newSkillForm.markdown" class="btn-primary disabled:opacity-50 disabled:cursor-not-allowed">Create Skill</button>
|
||||
<button @click="saveNewSkill" :disabled="!newSkillForm.skill_name || !newSkillForm.skill_desc" class="btn-primary disabled:opacity-50 disabled:cursor-not-allowed">Create Skill</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,60 +1,144 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
|
||||
const API_BASE = 'http://localhost:8082'
|
||||
|
||||
export interface Skill {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
description_cn?: string
|
||||
command: string
|
||||
args?: string
|
||||
env?: string
|
||||
category: string
|
||||
skill_name: string
|
||||
skill_type: string
|
||||
skill_desc: string
|
||||
path: string
|
||||
status: string
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
export function useSkills() {
|
||||
// 从 API 获取 MCP 列表
|
||||
const fetchSkills = async () => {
|
||||
const skills = ref<Skill[]>([])
|
||||
const skillsLoading = ref(false)
|
||||
|
||||
// 获取技能列表
|
||||
const fetchSkills = async (type?: string) => {
|
||||
skillsLoading.value = true
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/mcp/list`)
|
||||
let url = `${API_BASE}/skill/list`
|
||||
if (type) {
|
||||
url += `?type=${type}`
|
||||
}
|
||||
|
||||
const response = await fetch(url)
|
||||
const result = await response.json()
|
||||
if (result && Array.isArray(result)) {
|
||||
skills.value = result.map((mcp: any) => ({
|
||||
id: mcp.id,
|
||||
name: mcp.name,
|
||||
description: mcp.description || '',
|
||||
description_cn: mcp.description_cn || '',
|
||||
command: mcp.command || '',
|
||||
args: mcp.args || '',
|
||||
env: mcp.env || '',
|
||||
category: mcp.category || 'custom',
|
||||
status: mcp.status || 'stopped',
|
||||
created_at: mcp.created_at,
|
||||
|
||||
if (result.list) {
|
||||
skills.value = result.list.map((skill: any) => ({
|
||||
id: skill.id,
|
||||
skill_name: skill.skill_name,
|
||||
skill_type: skill.skill_type,
|
||||
skill_desc: skill.skill_desc || '',
|
||||
path: skill.path || '',
|
||||
status: skill.status || 'active',
|
||||
created_at: skill.created_at,
|
||||
updated_at: skill.updated_at,
|
||||
}))
|
||||
}
|
||||
return result.list || []
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch MCP list:', error)
|
||||
console.error('Failed to fetch skills:', error)
|
||||
return []
|
||||
} finally {
|
||||
skillsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchSkills()
|
||||
// 同步技能
|
||||
const syncSkills = async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/skill/sync`)
|
||||
const data = await response.json()
|
||||
await fetchSkills()
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Failed to sync skills:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 获取技能详情
|
||||
const fetchSkillById = async (id: string) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/skill/${id}`)
|
||||
const data = await response.json()
|
||||
return data.skill
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch skill:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 创建技能
|
||||
const createSkill = async (skill: Partial<Skill>) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/skill/add`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(skill),
|
||||
})
|
||||
const data = await response.json()
|
||||
await fetchSkills()
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Failed to create skill:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 更新技能
|
||||
const updateSkill = async (id: string, skill: Partial<Skill>) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/skill/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(skill),
|
||||
})
|
||||
const data = await response.json()
|
||||
await fetchSkills()
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Failed to update skill:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 删除技能
|
||||
const deleteSkill = async (id: string) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/skill/${id}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
const data = await response.json()
|
||||
await fetchSkills()
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Failed to delete skill:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 按类型获取技能
|
||||
const getSkillsByType = (type: string) => {
|
||||
return skills.value.filter(skill => skill.skill_type === type)
|
||||
}
|
||||
|
||||
// 获取所有分类
|
||||
const categories = computed(() => {
|
||||
const cats = new Set(skills.value.map(skill => skill.skill_type))
|
||||
return Array.from(cats)
|
||||
})
|
||||
// Skill 数据
|
||||
const skills = ref<Skill[]>([
|
||||
{ id: 1, name: 'Linear', description: 'Linear API integration for project management', type: 'API', category: 'api', status: 'running', port: 3001, createdAt: '2025-04-10', tools: 12 },
|
||||
{ id: 2, name: 'Google Maps', description: 'Google Maps API for location services', type: 'Google Maps', category: 'api', status: 'running', port: 3002, createdAt: '2025-04-08', tools: 8 },
|
||||
{ id: 3, name: 'File Explorer', description: 'File system explorer and editor', type: 'File System', category: 'filesystem', status: 'error', port: 3003, createdAt: '2025-04-05', tools: 15 },
|
||||
{ id: 4, name: 'PostgreSQL', description: 'PostgreSQL database operations', type: 'Database', category: 'database', status: 'running', port: 3004, createdAt: '2025-04-12', tools: 10 },
|
||||
{ id: 5, name: 'GitHub', description: 'GitHub API integration', type: 'GitHub', category: 'communication', status: 'stopped', port: 3005, createdAt: '2025-04-11', tools: 20 },
|
||||
{ id: 6, name: 'Slack', description: 'Slack messaging integration', type: 'Communication', category: 'communication', status: 'running', port: 3006, createdAt: '2025-04-09', tools: 6 },
|
||||
{ id: 7, name: 'OpenAI', description: 'OpenAI GPT models for AI conversations', type: 'AI/ML', category: 'ai', status: 'running', port: 3007, createdAt: '2025-04-07', tools: 5 },
|
||||
{ id: 8, name: 'MySQL', description: 'MySQL database operations', type: 'Database', category: 'database', status: 'stopped', port: 3008, createdAt: '2025-04-06', tools: 10 },
|
||||
])
|
||||
|
||||
// 搜索和筛选
|
||||
const searchQuery = ref('')
|
||||
@@ -67,35 +151,22 @@ export function useSkills() {
|
||||
|
||||
// 表单
|
||||
const editForm = ref({
|
||||
name: '',
|
||||
type: '',
|
||||
category: '',
|
||||
port: 3000,
|
||||
description: '',
|
||||
skill_name: '',
|
||||
skill_desc: '',
|
||||
skill_type: 'user',
|
||||
})
|
||||
|
||||
const newSkillForm = ref({
|
||||
name: '',
|
||||
description: '',
|
||||
markdown: '',
|
||||
skill_name: '',
|
||||
skill_desc: '',
|
||||
skill_type: 'user',
|
||||
})
|
||||
|
||||
// 分类选项
|
||||
const categories = [
|
||||
{ value: 'api', label: 'API' },
|
||||
{ value: 'database', label: 'Database' },
|
||||
{ value: 'filesystem', label: 'File System' },
|
||||
{ value: 'communication', label: 'Communication' },
|
||||
{ value: 'ai', label: 'AI/ML' },
|
||||
]
|
||||
|
||||
const types = ['API', 'Database', 'File System', 'Communication', 'AI/ML']
|
||||
|
||||
// 筛选
|
||||
const filteredSkills = computed(() => {
|
||||
return skills.value.filter(skill => {
|
||||
const matchSearch = skill.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
skill.description.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
const matchSearch = skill.skill_name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
(skill.skill_desc && skill.skill_desc.toLowerCase().includes(searchQuery.value.toLowerCase()))
|
||||
const matchStatus = filterStatus.value === 'all' || skill.status === filterStatus.value
|
||||
return matchSearch && matchStatus
|
||||
})
|
||||
@@ -104,8 +175,8 @@ export function useSkills() {
|
||||
// 状态样式
|
||||
const statusClass = (status: string) => {
|
||||
switch (status) {
|
||||
case 'running': return 'bg-green-500'
|
||||
case 'stopped': return 'bg-gray-500'
|
||||
case 'active': return 'bg-green-500'
|
||||
case 'inactive': return 'bg-gray-500'
|
||||
case 'error': return 'bg-red-500'
|
||||
default: return 'bg-gray-500'
|
||||
}
|
||||
@@ -113,7 +184,7 @@ export function useSkills() {
|
||||
|
||||
// 打开创建弹窗
|
||||
const openCreate = () => {
|
||||
newSkillForm.value = { name: '', description: '', markdown: '' }
|
||||
newSkillForm.value = { skill_name: '', skill_desc: '', skill_type: 'user' }
|
||||
isCreating.value = true
|
||||
}
|
||||
|
||||
@@ -123,31 +194,28 @@ export function useSkills() {
|
||||
}
|
||||
|
||||
// 保存新技能
|
||||
const saveNewSkill = () => {
|
||||
const newId = Math.max(...skills.value.map(s => s.id)) + 1
|
||||
skills.value.push({
|
||||
id: newId,
|
||||
name: newSkillForm.value.name,
|
||||
description: newSkillForm.value.description,
|
||||
type: 'Custom',
|
||||
category: 'custom',
|
||||
status: 'stopped',
|
||||
port: 3000,
|
||||
createdAt: new Date().toISOString().split('T')[0],
|
||||
tools: 0,
|
||||
const saveNewSkill = async () => {
|
||||
try {
|
||||
await createSkill({
|
||||
skill_name: newSkillForm.value.skill_name,
|
||||
skill_desc: newSkillForm.value.skill_desc,
|
||||
skill_type: newSkillForm.value.skill_type,
|
||||
status: 'active',
|
||||
})
|
||||
ElMessage.success('Skill created successfully')
|
||||
isCreating.value = false
|
||||
} catch (error) {
|
||||
ElMessage.error('Failed to create skill')
|
||||
}
|
||||
}
|
||||
|
||||
// 打开编辑弹窗
|
||||
const openEdit = (skill: Skill) => {
|
||||
editingSkill.value = skill
|
||||
editForm.value = {
|
||||
name: skill.name,
|
||||
type: skill.type,
|
||||
category: skill.category,
|
||||
port: skill.port,
|
||||
description: skill.description,
|
||||
skill_name: skill.skill_name,
|
||||
skill_desc: skill.skill_desc,
|
||||
skill_type: skill.skill_type,
|
||||
}
|
||||
isEditing.value = true
|
||||
}
|
||||
@@ -159,41 +227,54 @@ export function useSkills() {
|
||||
}
|
||||
|
||||
// 保存编辑
|
||||
const saveEdit = () => {
|
||||
const index = skills.value.findIndex(s => s.id === editingSkill.value!.id)
|
||||
if (index !== -1) {
|
||||
skills.value[index] = {
|
||||
...skills.value[index],
|
||||
name: editForm.value.name,
|
||||
type: editForm.value.type,
|
||||
category: editForm.value.category,
|
||||
port: editForm.value.port,
|
||||
description: editForm.value.description,
|
||||
}
|
||||
}
|
||||
const saveEdit = async () => {
|
||||
try {
|
||||
await updateSkill(editingSkill.value!.id, {
|
||||
skill_name: editForm.value.skill_name,
|
||||
skill_desc: editForm.value.skill_desc,
|
||||
skill_type: editForm.value.skill_type,
|
||||
})
|
||||
ElMessage.success('Skill updated successfully')
|
||||
isEditing.value = false
|
||||
} catch (error) {
|
||||
ElMessage.error('Failed to update skill')
|
||||
}
|
||||
}
|
||||
|
||||
// 切换状态
|
||||
const toggleStatus = (skill: Skill) => {
|
||||
if (skill.status === 'running') skill.status = 'stopped'
|
||||
else if (skill.status === 'stopped') skill.status = 'running'
|
||||
const toggleStatus = async (skill: Skill) => {
|
||||
const newStatus = skill.status === 'active' ? 'inactive' : 'active'
|
||||
try {
|
||||
await updateSkill(skill.id, { status: newStatus })
|
||||
skill.status = newStatus
|
||||
} catch (error) {
|
||||
ElMessage.error('Failed to update status')
|
||||
}
|
||||
}
|
||||
|
||||
// 删除技能
|
||||
const deleteSkill = (id: number) => {
|
||||
ElMessageBox.confirm('Are you sure you want to delete this skill?', 'Confirm Delete', {
|
||||
const handleDeleteSkill = async (id: string) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('Are you sure you want to delete this skill?', 'Confirm Delete', {
|
||||
confirmButtonText: 'Delete',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
skills.value = skills.value.filter(s => s.id !== id)
|
||||
}).catch(() => {})
|
||||
})
|
||||
|
||||
await deleteSkill(id)
|
||||
ElMessage.success('Skill deleted successfully')
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('Failed to delete skill:', error)
|
||||
ElMessage.error('Failed to delete skill')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
skills,
|
||||
skillsLoading,
|
||||
searchQuery,
|
||||
filterStatus,
|
||||
isEditing,
|
||||
@@ -202,10 +283,11 @@ export function useSkills() {
|
||||
editForm,
|
||||
newSkillForm,
|
||||
categories,
|
||||
types,
|
||||
// Computed
|
||||
filteredSkills,
|
||||
// Methods
|
||||
fetchSkills,
|
||||
syncSkills,
|
||||
statusClass,
|
||||
openCreate,
|
||||
closeCreate,
|
||||
@@ -214,6 +296,6 @@ export function useSkills() {
|
||||
closeEdit,
|
||||
saveEdit,
|
||||
toggleStatus,
|
||||
deleteSkill,
|
||||
deleteSkill: handleDeleteSkill,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user