feat: 完成第二阶段开发\n\n- 添加 scene_management 工具\n- 添加 prefab_management 工具\n- 优化面板布局和响应式设计\n- 添加滚动条支持\n- 移除旧的 create_scene 和 create_prefab 工具\n- 更新 README 文档

This commit is contained in:
火焰库拉
2026-01-31 19:36:55 +08:00
parent 3b2e78eee7
commit 157b99290d
5 changed files with 462 additions and 689 deletions

View File

@@ -1,359 +1,93 @@
<div class="mcp-container">
<!-- 标签页 -->
<div class="tabs">
<ui-button id="tabMain" class="tab-button active">Main</ui-button>
<ui-button id="tabTest" class="tab-button">Tool Test</ui-button>
</div>
<div class="tabs">
<ui-button id="tabMain" class="tab-button active">Main</ui-button>
<ui-button id="tabTest" class="tab-button">Tool Test</ui-button>
</div>
<!-- 主面板内容 -->
<div id="panelMain" class="tab-content active">
<div class="toolbar">
<div class="ctrl-group">
<span>Port:</span>
<ui-input id="portInput" value="3456"></ui-input>
<ui-button id="btnToggle" class="green">Start</ui-button>
</div>
<div id="panelMain" class="tab-content active">
<div class="toolbar">
<div class="ctrl-group">
<span>Port:</span>
<ui-input id="portInput" style="width: 60px;"></ui-input>
<ui-button id="btnToggle">Start</ui-button>
</div>
<div class="ctrl-group" style="margin-left: 10px">
<ui-checkbox id="autoStartCheck">Auto Start</ui-checkbox>
</div>
<div class="spacer"></div>
<ui-button id="btnClear" class="transparent">Clear</ui-button>
<ui-button id="btnCopy" class="transparent">Copy All</ui-button>
</div>
<div id="logConsole" class="log-view"></div>
</div>
<!-- 新增的自动启动勾选框 -->
<div class="ctrl-group" style="margin-left: 15px">
<ui-checkbox id="autoStartCheck">Auto Start</ui-checkbox>
</div>
<div id="panelTest" class="tab-content">
<div class="test-layout">
<div class="left-panel" id="testLeftPanel">
<div class="form-item">
<label>工具名称:</label>
<ui-input id="toolName" placeholder="选中下方列表填充"></ui-input>
</div>
<label>可用工具列表:</label>
<div class="tools-list" id="toolsList"></div>
</div>
<div class="spacer"></div>
<ui-button id="btnClear" class="transparent">Clear</ui-button>
<ui-button id="btnCopy" class="transparent">Copy All</ui-button>
</div>
<!-- 拖动条 -->
<div class="resizer" id="testResizer"></div>
<!-- 日志区域 -->
<div id="logConsole" class="log-view"></div>
</div>
<!-- 测试面板内容 -->
<div id="panelTest" class="tab-content">
<div class="test-container">
<div class="test-layout">
<!-- 左侧工具列表 -->
<div class="left-panel">
<div class="tool-name-section">
<label for="toolName">工具名称:</label>
<ui-input id="toolName" placeholder="例如: manage_components"></ui-input>
</div>
<div class="tools-list" id="toolsList"></div>
</div>
<!-- 右侧输入输出 -->
<div class="right-panel">
<div class="form-group">
<label for="toolParams">工具参数 (JSON格式):</label>
<textarea id="toolParams" placeholder="例如: {
\"nodeId\": \"节点UUID\",
\"action\": \"add\",
\"componentType\": \"cc.Button\"
}"></textarea>
</div>
<div class="button-group">
<ui-button id="testBtn" class="primary">测试工具</ui-button>
<ui-button id="listToolsBtn" class="secondary">获取工具列表</ui-button>
<ui-button id="clearBtn" class="secondary">清空结果</ui-button>
</div>
<div class="result">
<h2>测试结果:</h2>
<textarea id="resultContent" placeholder="点击\"测试工具\"按钮开始测试"></textarea>
</div>
</div>
</div>
</div>
</div>
<div class="right-panel">
<div class="flex-v">
<label>工具参数 (JSON):</label>
<textarea id="toolParams" spellcheck="false" placeholder='{}'></textarea>
</div>
<div class="button-group">
<ui-button id="testBtn" class="green">测试工具</ui-button>
<ui-button id="listToolsBtn">刷新列表</ui-button>
<ui-button id="clearTestBtn">清空结果</ui-button>
</div>
<div class="flex-v">
<label>测试结果:</label>
<textarea id="resultContent" readonly spellcheck="false"></textarea>
</div>
</div>
</div>
</div>
</div>
<style>
:host {
height: 100%;
display: flex;
background-color: #2d2d2d;
}
.mcp-container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 5px;
box-sizing: border-box;
}
/* 标签页样式 */
.tabs {
display: flex;
border-bottom: 1px solid #444;
margin-bottom: 10px;
flex-shrink: 0;
}
.tab-button {
padding: 8px 16px;
margin-right: 2px;
border: none;
background-color: #333;
color: #ccc;
cursor: pointer;
border-bottom: 2px solid transparent;
}
.tab-button.active {
background-color: #2d2d2d;
color: #fff;
border-bottom-color: #4CAF50;
}
.tab-content {
display: none;
flex: 1;
flex-direction: column;
}
.tab-content.active {
display: flex;
}
:host { height: 100%; display: flex; background-color: #2d2d2d; overflow: hidden; }
.mcp-container { display: flex; flex-direction: column; width: 100%; height: 100%; padding: 5px; box-sizing: border-box; }
.tabs { display: flex; border-bottom: 1px solid #444; margin-bottom: 5px; flex-shrink: 0; }
.tab-button { padding: 4px 12px; margin-right: 2px; background: #333; }
.tab-button.active { background: #4CAF50; color: white; }
.tab-content { display: none; flex: 1; flex-direction: column; min-height: 0; overflow: hidden; }
.tab-content.active { display: flex; }
/* 主面板样式 */
.toolbar {
display: flex;
align-items: center;
padding-bottom: 5px;
border-bottom: 1px solid #444;
flex-shrink: 0; /* 禁止头部压缩 */
}
.ctrl-group {
display: flex;
align-items: center;
gap: 5px;
}
.spacer {
flex-grow: 1;
}
/* Main Panel */
.toolbar { display: flex; align-items: center; padding: 5px 0; gap: 5px; flex-shrink: 0; }
.spacer { flex: 1; }
.log-view { flex: 1; background: #1a1a1a; margin-top: 5px; overflow-y: auto; padding: 8px; font-family: monospace; font-size: 11px; -webkit-user-select: text; min-height: 0; }
.log-item { border-left: 4px solid #555; padding-left: 8px; margin-bottom: 3px; white-space: pre-wrap; word-break: break-all; }
.log-item.info { border-left-color: #61afef; color: #abb2bf; }
.log-item.success { border-left-color: #98c379; color: #98c379; }
.log-item.warn { border-left-color: #e5c07b; color: #e5c07b; }
.log-item.error { border-left-color: #e06c75; color: #e06c75; }
.log-item.mcp { border-left-color: #c678dd; color: #d19a66; background: rgba(198, 120, 221, 0.05); }
.time { color: #5c6370; margin-right: 8px; }
.log-view {
flex: 1; /* 自动撑满剩余空间 */
background: #1a1a1a;
margin-top: 5px;
border-radius: 2px;
overflow-y: auto;
overflow-x: hidden;
padding: 8px;
font-family: "Consolas", "Monaco", monospace;
font-size: 12px;
/* 【关键】允许鼠标选中文字 */
-webkit-user-select: text;
cursor: text;
}
.log-item {
margin-bottom: 3px;
line-height: 1.4;
border-left: 4px solid #444;
padding-left: 8px;
word-wrap: break-word;
white-space: pre-wrap;
}
/* 颜色修正 */
.time {
color: #5c6370;
margin-right: 8px;
font-weight: normal;
user-select: none;
}
.info {
border-left-color: #61afef;
color: #abb2bf;
}
.success {
border-left-color: #98c379;
color: #98c379;
}
.warn {
border-left-color: #e5c07b;
color: #e5c07b;
}
.error {
border-left-color: #e06c75;
color: #e06c75;
}
.mcp {
border-left-color: #c678dd;
color: #d19a66;
}
/* 测试面板样式 */
.test-container {
display: flex;
flex-direction: column;
height: 100%;
padding: 10px;
box-sizing: border-box;
}
.test-layout {
display: flex;
height: 100%;
gap: 15px;
}
.left-panel {
width: 300px;
flex-shrink: 0;
display: flex;
flex-direction: column;
height: 100%;
background-color: #333;
border-radius: 4px;
padding: 10px;
}
.right-panel {
flex: 1;
display: flex;
flex-direction: column;
gap: 15px;
height: 100%;
min-height: 0;
}
.left-panel .tool-name-section {
margin-bottom: 10px;
}
.left-panel .tools-list {
flex: 1;
border: 1px solid #444;
border-radius: 4px;
padding: 5px;
background-color: #222;
overflow-y: auto;
min-height: 0;
}
.left-panel label {
display: block;
margin-bottom: 5px;
color: #ccc;
font-size: 12px;
}
.left-panel ui-input {
width: 100%;
padding: 8px;
border: 1px solid #444;
border-radius: 4px;
font-size: 12px;
box-sizing: border-box;
background-color: #222;
color: #fff;
margin-bottom: 10px;
}
.right-panel .form-group {
margin-bottom: 0;
}
.right-panel .result {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
margin-bottom: 0;
}
.right-panel .result textarea {
flex: 1;
min-height: 100px;
}
.right-panel .button-group {
flex-shrink: 0;
}
.form-group {
margin-bottom: 15px;
background-color: #333;
padding: 10px;
border-radius: 4px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: #ccc;
font-size: 12px;
}
.form-group ui-input,
.form-group textarea {
width: 100%;
padding: 8px;
border: 1px solid #444;
border-radius: 4px;
font-size: 12px;
box-sizing: border-box;
background-color: #222;
color: #fff;
}
.form-group textarea {
height: 150px;
resize: vertical;
font-family: 'Courier New', monospace;
}
.tool-item {
padding: 5px;
margin-bottom: 5px;
background-color: #333;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
color: #ccc;
}
.tool-item:hover {
background-color: #444;
}
.button-group {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.button-group ui-button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
font-weight: bold;
}
.button-group ui-button.primary {
background-color: #4CAF50;
color: white;
}
.button-group ui-button.secondary {
background-color: #2196F3;
color: white;
}
.result {
flex: 1;
background-color: #333;
padding: 15px;
border-radius: 4px;
}
.result h2 {
color: #ccc;
font-size: 14px;
margin-bottom: 10px;
}
.result textarea {
width: 100%;
height: 100%;
background-color: #222;
color: #fff;
border: 1px solid #444;
border-radius: 4px;
padding: 10px;
font-family: 'Courier New', monospace;
font-size: 12px;
box-sizing: border-box;
resize: vertical;
}
</style>
/* Test Panel */
.test-layout { display: flex; flex: 1; min-height: 0; }
.left-panel { width: 250px; min-width: 150px; max-width: 500px; display: flex; flex-direction: column; flex-shrink: 0; min-height: 0; }
.resizer { width: 6px; cursor: col-resize; background: #1a1a1a; flex-shrink: 0; }
.resizer:hover { background: #4CAF50; }
.right-panel { flex: 1; display: flex; flex-direction: column; min-width: 0; padding-left: 5px; }
.tools-list { flex: 1; background: #222; border: 1px solid #444; overflow-y: auto; margin-top: 5px; }
.tool-item { padding: 6px; border-bottom: 1px solid #333; cursor: pointer; font-size: 11px; }
.tool-item:hover { background: #444; }
.flex-v { display: flex; flex-direction: column; flex: 1; min-height: 0; }
textarea { width: 100%; background: #222; color: #ccc; border: 1px solid #444; padding: 5px; font-family: monospace; resize: none; }
#toolParams { height: 120px; flex-shrink: 0; }
#resultContent { flex: 1; }
.button-group { display: flex; gap: 5px; padding: 5px 0; }
label { font-size: 11px; color: #888; margin: 4px 0; }
</style>

View File

@@ -1,333 +1,152 @@
"use strict";
const fs = require("fs");
Editor.Panel.extend({
style: fs.readFileSync(Editor.url("packages://mcp-bridge/panel/index.html"), "utf-8"),
template: fs.readFileSync(Editor.url("packages://mcp-bridge/panel/index.html"), "utf-8"),
style: fs.readFileSync(Editor.url("packages://mcp-bridge/panel/index.html"), "utf-8"),
template: fs.readFileSync(Editor.url("packages://mcp-bridge/panel/index.html"), "utf-8"),
messages: {
"mcp-bridge:on-log"(event, log) {
this.renderLog(log);
},
"mcp-bridge:state-changed"(event, config) {
this.updateUI(config.active);
},
},
messages: {
"mcp-bridge:on-log"(event, log) { this.renderLog(log); },
"mcp-bridge:state-changed"(event, config) { this.updateUI(config.active); }
},
ready() {
const portInput = this.shadowRoot.querySelector("#portInput");
const btnToggle = this.shadowRoot.querySelector("#btnToggle");
const autoStartCheck = this.shadowRoot.querySelector("#autoStartCheck");
const btnClear = this.shadowRoot.querySelector("#btnClear");
const btnCopy = this.shadowRoot.querySelector("#btnCopy");
const logView = this.shadowRoot.querySelector("#logConsole");
ready() {
const root = this.shadowRoot;
// 获取元素
const els = {
port: root.querySelector("#portInput"),
btnToggle: root.querySelector("#btnToggle"),
autoStart: root.querySelector("#autoStartCheck"),
logView: root.querySelector("#logConsole"),
tabMain: root.querySelector("#tabMain"),
tabTest: root.querySelector("#tabTest"),
panelMain: root.querySelector("#panelMain"),
panelTest: root.querySelector("#panelTest"),
toolName: root.querySelector("#toolName"),
toolParams: root.querySelector("#toolParams"),
toolsList: root.querySelector("#toolsList"),
testBtn: root.querySelector("#testBtn"),
listBtn: root.querySelector("#listToolsBtn"),
clearBtn: root.querySelector("#clearTestBtn"),
result: root.querySelector("#resultContent"),
left: root.querySelector("#testLeftPanel"),
resizer: root.querySelector("#testResizer")
};
// 标签页元素
const tabMain = this.shadowRoot.querySelector("#tabMain");
const tabTest = this.shadowRoot.querySelector("#tabTest");
const panelMain = this.shadowRoot.querySelector("#panelMain");
const panelTest = this.shadowRoot.querySelector("#panelTest");
// 1. 初始化状态
Editor.Ipc.sendToMain("mcp-bridge:get-server-state", (err, data) => {
if (data) {
els.port.value = data.config.port;
els.autoStart.value = data.autoStart;
this.updateUI(data.config.active);
els.logView.innerHTML = "";
data.logs.forEach(l => this.renderLog(l));
}
});
// 测试面板元素
const toolNameInput = this.shadowRoot.querySelector("#toolName");
const toolParamsTextarea = this.shadowRoot.querySelector("#toolParams");
const toolsList = this.shadowRoot.querySelector("#toolsList");
const testBtn = this.shadowRoot.querySelector("#testBtn");
const listToolsBtn = this.shadowRoot.querySelector("#listToolsBtn");
const clearBtn = this.shadowRoot.querySelector("#clearBtn");
const resultContent = this.shadowRoot.querySelector("#resultContent");
// 2. 标签切换
els.tabMain.addEventListener("confirm", () => {
els.tabMain.classList.add("active"); els.tabTest.classList.remove("active");
els.panelMain.classList.add("active"); els.panelTest.classList.remove("active");
});
els.tabTest.addEventListener("confirm", () => {
els.tabTest.classList.add("active"); els.tabMain.classList.remove("active");
els.panelTest.classList.add("active"); els.panelMain.classList.remove("active");
this.fetchTools(els);
});
let tools = [];
const API_BASE = 'http://localhost:3456';
// 3. 基础功能
els.btnToggle.addEventListener("confirm", () => {
Editor.Ipc.sendToMain("mcp-bridge:toggle-server", parseInt(els.port.value));
});
root.querySelector("#btnClear").addEventListener("confirm", () => {
els.logView.innerHTML = ""; Editor.Ipc.sendToMain("mcp-bridge:clear-logs");
});
root.querySelector("#btnCopy").addEventListener("confirm", () => {
require("electron").clipboard.writeText(els.logView.innerText);
Editor.success("Logs Copied");
});
els.autoStart.addEventListener("change", (e) => {
Editor.Ipc.sendToMain("mcp-bridge:set-auto-start", e.target.value);
});
// 初始化
Editor.Ipc.sendToMain("mcp-bridge:get-server-state", (err, data) => {
if (data) {
portInput.value = data.config.port;
this.updateUI(data.config.active);
data.logs.forEach((log) => this.renderLog(log));
}
});
// 4. 测试页功能
els.listBtn.addEventListener("confirm", () => this.fetchTools(els));
els.clearBtn.addEventListener("confirm", () => { els.result.value = ""; });
els.testBtn.addEventListener("confirm", () => this.runTest(els));
// 标签页切换
tabMain.addEventListener("confirm", () => {
tabMain.classList.add("active");
tabTest.classList.remove("active");
panelMain.classList.add("active");
panelTest.classList.remove("active");
});
// 5. 【修复】拖拽逻辑
if (els.resizer && els.left) {
els.resizer.addEventListener('mousedown', (e) => {
e.preventDefault();
const startX = e.clientX;
const startW = els.left.offsetWidth;
const onMove = (ev) => { els.left.style.width = (startW + (ev.clientX - startX)) + "px"; };
const onUp = () => {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
document.body.style.cursor = 'default';
};
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
document.body.style.cursor = 'col-resize';
});
}
},
tabTest.addEventListener("confirm", () => {
tabTest.classList.add("active");
tabMain.classList.remove("active");
panelTest.classList.add("active");
panelMain.classList.remove("active");
// 自动获取工具列表
this.getToolsList();
});
fetchTools(els) {
const url = `http://localhost:${els.port.value}/list-tools`;
fetch(url).then(r => r.json()).then(data => {
els.toolsList.innerHTML = "";
data.tools.forEach(t => {
const item = document.createElement('div');
item.className = 'tool-item';
item.textContent = t.name;
item.onclick = () => {
els.toolName.value = t.name;
els.toolParams.value = JSON.stringify(this.getExample(t.name), null, 2);
};
els.toolsList.appendChild(item);
});
els.result.value = `Loaded ${data.tools.length} tools.`;
}).catch(e => { els.result.value = "Error: " + e.message; });
},
btnToggle.addEventListener("confirm", () => {
Editor.Ipc.sendToMain("mcp-bridge:toggle-server", parseInt(portInput.value));
});
runTest(els) {
const url = `http://localhost:${els.port.value}/call-tool`;
const body = { name: els.toolName.value, arguments: JSON.parse(els.toolParams.value || "{}") };
els.result.value = "Testing...";
fetch(url, { method: 'POST', body: JSON.stringify(body) })
.then(r => r.json())
.then(d => { els.result.value = JSON.stringify(d, null, 2); })
.catch(e => { els.result.value = "Error: " + e.message; });
},
btnClear.addEventListener("confirm", () => {
logView.innerHTML = "";
Editor.Ipc.sendToMain("mcp-bridge:clear-logs");
});
getExample(name) {
const examples = {
"set_node_name": { "id": "UUID", "newName": "Hello" },
"update_node_transform": { "id": "UUID", "x": 0, "y": 0, "color": "#FF0000" },
"create_node": { "name": "Node", "type": "sprite", "parentId": "" },
"open_scene": { "url": "db://assets/Scene.fire" }
};
return examples[name] || {};
},
btnCopy.addEventListener("confirm", () => {
require("electron").clipboard.writeText(logView.innerText);
Editor.success("All logs copied!");
});
renderLog(log) {
const view = this.shadowRoot.querySelector("#logConsole");
if (!view) return;
const atBottom = view.scrollHeight - view.scrollTop <= view.clientHeight + 50;
const el = document.createElement("div");
el.className = `log-item ${log.type}`;
el.innerHTML = `<span class="time">${log.time}</span><span class="msg">${log.content}</span>`;
view.appendChild(el);
if (atBottom) view.scrollTop = view.scrollHeight;
},
Editor.Ipc.sendToMain("mcp-bridge:get-server-state", (err, data) => {
if (data) {
portInput.value = data.config.port;
this.updateUI(data.config.active);
// 设置自动启动复选框状态
autoStartCheck.value = data.autoStart;
data.logs.forEach((log) => this.renderLog(log));
}
});
autoStartCheck.addEventListener("change", (event) => {
// event.target.value 在 ui-checkbox 中是布尔值
Editor.Ipc.sendToMain("mcp-bridge:set-auto-start", event.target.value);
});
// 测试面板事件
testBtn.addEventListener("confirm", () => this.testTool());
listToolsBtn.addEventListener("confirm", () => this.getToolsList());
clearBtn.addEventListener("confirm", () => this.clearResult());
// 获取工具列表
this.getToolsList = function() {
this.showResult('获取工具列表中...');
fetch(`${API_BASE}/list-tools`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.tools) {
tools = data.tools;
this.displayToolsList(tools);
this.showResult(`成功获取 ${tools.length} 个工具`, 'success');
} else {
this.showResult('获取工具列表失败:未找到工具数据', 'error');
}
})
.catch(error => {
this.showResult(`获取工具列表失败:${error.message}`, 'error');
});
};
// 显示工具列表
this.displayToolsList = function(tools) {
toolsList.innerHTML = '';
tools.forEach(tool => {
const toolItem = document.createElement('div');
toolItem.className = 'tool-item';
toolItem.textContent = `${tool.name} - ${tool.description}`;
toolItem.addEventListener('click', () => {
toolNameInput.value = tool.name;
// 尝试填充示例参数
this.fillExampleParams(tool);
});
toolsList.appendChild(toolItem);
});
};
// 填充示例参数
this.fillExampleParams = function(tool) {
let exampleParams = {};
switch (tool.name) {
case 'get_selected_node':
case 'save_scene':
case 'get_scene_hierarchy':
exampleParams = {};
break;
case 'set_node_name':
exampleParams = {
"id": "节点UUID",
"newName": "新节点名称"
};
break;
case 'update_node_transform':
exampleParams = {
"id": "节点UUID",
"x": 100,
"y": 100,
"scaleX": 1,
"scaleY": 1
};
break;
case 'create_scene':
exampleParams = {
"sceneName": "NewScene"
};
break;
case 'create_prefab':
exampleParams = {
"nodeId": "节点UUID",
"prefabName": "NewPrefab"
};
break;
case 'open_scene':
exampleParams = {
"url": "db://assets/NewScene.fire"
};
break;
case 'create_node':
exampleParams = {
"name": "NewNode",
"parentId": "父节点UUID",
"type": "empty"
};
break;
case 'manage_components':
exampleParams = {
"nodeId": "节点UUID",
"action": "add",
"componentType": "cc.Button"
};
break;
case 'manage_script':
exampleParams = {
"action": "create",
"path": "db://assets/scripts/TestScript.ts",
"content": "const { ccclass, property } = cc._decorator;\n\n@ccclass\nexport default class TestScript extends cc.Component {\n // LIFE-CYCLE CALLBACKS:\n\n onLoad () {}\n\n start () {}\n\n update (dt) {}\n}"
};
break;
case 'batch_execute':
exampleParams = {
"operations": [
{
"tool": "get_selected_node",
"params": {}
}
]
};
break;
case 'manage_asset':
exampleParams = {
"action": "create",
"path": "db://assets/test.txt",
"content": "Hello, MCP!"
};
break;
}
toolParamsTextarea.value = JSON.stringify(exampleParams, null, 2);
};
// 测试工具
this.testTool = function() {
const toolName = toolNameInput.value.trim();
const toolParamsStr = toolParamsTextarea.value.trim();
if (!toolName) {
this.showResult('请输入工具名称', 'error');
return;
}
let toolParams;
try {
toolParams = toolParamsStr ? JSON.parse(toolParamsStr) : {};
} catch (error) {
this.showResult(`参数格式错误:${error.message}`, 'error');
return;
}
this.showResult('测试工具中...');
fetch(`${API_BASE}/call-tool`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: toolName,
arguments: toolParams
})
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.error) {
this.showResult(`测试失败:${data.error}`, 'error');
} else {
this.showResult(JSON.stringify(data, null, 2), 'success');
}
})
.catch(error => {
this.showResult(`测试失败:${error.message}`, 'error');
});
};
// 显示结果
this.showResult = function(message, type = 'info') {
resultContent.value = message;
// 移除旧样式
resultContent.className = '';
// 添加新样式
if (type === 'error' || type === 'success') {
resultContent.className = type;
}
};
// 清空结果
this.clearResult = function() {
this.showResult('点击"测试工具"按钮开始测试');
};
},
renderLog(log) {
const logView = this.shadowRoot.querySelector("#logConsole");
if (!logView) return;
// 记录当前滚动条位置
const isAtBottom = logView.scrollHeight - logView.scrollTop <= logView.clientHeight + 50;
const el = document.createElement("div");
el.className = `log-item ${log.type}`;
el.innerHTML = `<span class="time">${log.time}</span><span class="msg">${log.content}</span>`;
logView.appendChild(el);
// 如果用户正在向上翻看,不自动滚动;否则自动滚到底部
if (isAtBottom) {
logView.scrollTop = logView.scrollHeight;
}
},
updateUI(isActive) {
const btnToggle = this.shadowRoot.querySelector("#btnToggle");
if (!btnToggle) return;
btnToggle.innerText = isActive ? "Stop" : "Start";
btnToggle.style.backgroundColor = isActive ? "#aa4444" : "#44aa44";
},
});
updateUI(active) {
const btn = this.shadowRoot.querySelector("#btnToggle");
if (!btn) return;
btn.innerText = active ? "Stop" : "Start";
btn.style.backgroundColor = active ? "#aa4444" : "#44aa44";
}
});