diff --git a/main/xiaozhi-server/test/css/test_page.css b/main/xiaozhi-server/test/css/test_page.css index d4ad8bad..57332d0c 100644 --- a/main/xiaozhi-server/test/css/test_page.css +++ b/main/xiaozhi-server/test/css/test_page.css @@ -1028,35 +1028,174 @@ body { margin-bottom: 12px; } -.property-item { +.properties-container #addMcpPropertyBtn { + display: block; + margin: 0 auto; +} + +.properties-container #addMcpPropertyBtn:hover { + background: #4752c4; +} + +.mcp-checkbox-label { display: flex; - gap: 8px; - margin-bottom: 8px; align-items: center; + gap: 8px; + margin-top: 8px; + color: #b9bbbe; + font-size: 13px; + cursor: pointer; + justify-content: flex-start; } -.property-item input { - flex: 1; - padding: 6px 8px; - border: 1px solid #40444b; - border-radius: 4px; - background: #40444b; +.mcp-checkbox-label input[type="checkbox"] { + width: 16px; + height: 16px; + cursor: pointer; +} + +.mcp-property-name { color: white; - font-size: 12px; + font-size: 14px; + font-weight: 500; } -.remove-property { - background: #ed4245; +.mcp-property-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.mcp-properties-list { + display: flex; + flex-wrap: wrap; + gap: 12px; + min-height: 60px; + padding: 8px; +} + +.input-group-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.input-group-header label { + margin-bottom: 0; +} + +.input-group-header .properties-btn-primary { + margin: 0; +} + +.mcp-empty-state { + text-align: center; + padding: 20px; + color: #999; + font-size: 14px; + width: 100%; + display: none; +} + +.properties-btn-primary { + background: #2196f3; color: white; border: none; - padding: 4px 8px; + padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; + font-weight: 500; + transition: background-color 0.2s; + display: block; + margin: 0 auto; } -.remove-property:hover { - background: #c03537; +.properties-btn-primary:hover { + background: #2196f3; +} + +.mcp-property-card { + background: #36393f; + border: 1px solid #40444b; + border-radius: 8px; + padding: 12px; + width: 100%; + cursor: pointer; + transition: all 0.2s ease; +} + +.mcp-property-card:hover { + background: #40444b; + border-color: #5865f2; +} + +.mcp-property-card:active { + transform: none; +} + +.mcp-property-row-label { + display: flex; + align-items: flex-start; + margin-bottom: 6px; + gap: 8px; +} + +.mcp-property-label { + color: #b9bbbe; + font-size: 13px; + width: 60px; + flex-shrink: 0; + text-align: left; +} + +.mcp-property-value { + color: white; + font-size: 13px; + flex: 1; + word-break: break-all; +} + +.mcp-property-required-badge { + color: #f44336; + font-size: 12px; +} + +.mcp-property-row-action { + display: flex; + justify-content: flex-end; + margin-top: 6px; + padding-top: 6px; + border-top: 1px solid #40444b; +} + +.mcp-property-delete-btn { + background: #f44336; + color: white; + border: none; + padding: 4px 12px; + border-radius: 4px; + cursor: pointer; + font-size: 11px; + transition: background-color 0.2s; +} + +.mcp-property-delete-btn:hover { + background: #d32f2f; +} + +.mcp-property-card-optional { + background: #4f545c; + color: #b9bbbe; + font-size: 11px; + padding: 2px 8px; + border-radius: 4px; +} + +.property-modal { + max-width: 450px; } /* ==================== 音频可视化器样式 ==================== */ diff --git a/main/xiaozhi-server/test/js/core/mcp/tools.js b/main/xiaozhi-server/test/js/core/mcp/tools.js index 9d1e1e99..0d9f0976 100644 --- a/main/xiaozhi-server/test/js/core/mcp/tools.js +++ b/main/xiaozhi-server/test/js/core/mcp/tools.js @@ -105,94 +105,167 @@ function renderMcpTools() { */ function renderMcpProperties() { const container = document.getElementById('mcpPropertiesContainer'); + const emptyState = document.getElementById('mcpEmptyState'); if (!container) { return; // Container not found, skip rendering } if (mcpProperties.length === 0) { - container.innerHTML = '
暂无参数,点击下方按钮添加参数
'; + if (emptyState) { + emptyState.style.display = 'block'; + } + container.innerHTML = ''; return; } + if (emptyState) { + emptyState.style.display = 'none'; + } container.innerHTML = mcpProperties.map((prop, index) => ` -
-
- ${prop.name} - +
+
+ 参数名称 + ${prop.name}${prop.required ? ' [必填]' : ''}
-
-
- - -
-
- - -
+
+ 数据类型 + ${getTypeLabel(prop.type)}
- ${(prop.type === 'integer' || prop.type === 'number') ? ` -
-
- - -
-
- - -
+
+ 描述 + ${prop.description || '-'}
- ` : ''} -
- - +
+
-
`).join(''); } /** - * 添加参数 + * 获取数据类型标签 */ -function addMcpProperty() { - mcpProperties.push({ name: `param_${mcpProperties.length + 1}`, type: 'string', required: false, description: '' }); - renderMcpProperties(); +function getTypeLabel(type) { + const typeMap = { + 'string': '字符串', + 'integer': '整数', + 'number': '数字', + 'boolean': '布尔值', + 'array': '数组', + 'object': '对象' + }; + return typeMap[type] || type; } /** - * 更新参数 + * 添加参数 - 打开参数编辑模态框 */ -function updateMcpProperty(index, field, value) { - if (field === 'name') { - const isDuplicate = mcpProperties.some((p, i) => i !== index && p.name === value); - if (isDuplicate) { - alert('参数名称已存在,请使用不同的名称'); - renderMcpProperties(); - return; +function addMcpProperty() { + openPropertyModal(); +} + +/** + * 编辑参数 - 打开参数编辑模态框 + */ +function editMcpProperty(index) { + openPropertyModal(index); +} + +/** + * 打开参数编辑模态框 + */ +function openPropertyModal(index = null) { + const form = document.getElementById('mcpPropertyForm'); + const title = document.getElementById('mcpPropertyModalTitle'); + document.getElementById('mcpPropertyIndex').value = index !== null ? index : -1; + + if (index !== null) { + const prop = mcpProperties[index]; + title.textContent = '编辑参数'; + document.getElementById('mcpPropertyName').value = prop.name; + document.getElementById('mcpPropertyType').value = prop.type || 'string'; + document.getElementById('mcpPropertyMinimum').value = prop.minimum !== undefined ? prop.minimum : ''; + document.getElementById('mcpPropertyMaximum').value = prop.maximum !== undefined ? prop.maximum : ''; + document.getElementById('mcpPropertyDescription').value = prop.description || ''; + document.getElementById('mcpPropertyRequired').checked = prop.required || false; + } else { + title.textContent = '添加参数'; + form.reset(); + document.getElementById('mcpPropertyName').value = `param_${mcpProperties.length + 1}`; + document.getElementById('mcpPropertyType').value = 'string'; + document.getElementById('mcpPropertyMinimum').value = ''; + document.getElementById('mcpPropertyMaximum').value = ''; + document.getElementById('mcpPropertyDescription').value = ''; + document.getElementById('mcpPropertyRequired').checked = false; + } + + updatePropertyRangeVisibility(); + document.getElementById('mcpPropertyModal').style.display = 'flex'; +} + +/** + * 关闭参数编辑模态框 + */ +function closePropertyModal() { + document.getElementById('mcpPropertyModal').style.display = 'none'; +} + +/** + * 更新数值范围输入框的可见性 + */ +function updatePropertyRangeVisibility() { + const type = document.getElementById('mcpPropertyType').value; + const rangeGroup = document.getElementById('mcpPropertyRangeGroup'); + if (type === 'integer' || type === 'number') { + rangeGroup.style.display = 'block'; + } else { + rangeGroup.style.display = 'none'; + } +} + +/** + * 处理参数表单提交 + */ +function handlePropertySubmit(e) { + e.preventDefault(); + const index = parseInt(document.getElementById('mcpPropertyIndex').value); + const name = document.getElementById('mcpPropertyName').value.trim(); + const type = document.getElementById('mcpPropertyType').value; + const minimum = document.getElementById('mcpPropertyMinimum').value; + const maximum = document.getElementById('mcpPropertyMaximum').value; + const description = document.getElementById('mcpPropertyDescription').value.trim(); + const required = document.getElementById('mcpPropertyRequired').checked; + + // 检查名称重复 + const isDuplicate = mcpProperties.some((p, i) => i !== index && p.name === name); + if (isDuplicate) { + alert('参数名称已存在,请使用不同的名称'); + return; + } + + const propData = { + name, + type, + description, + required + }; + + // 数值类型添加范围限制 + if (type === 'integer' || type === 'number') { + if (minimum !== '') { + propData.minimum = parseFloat(minimum); + } + if (maximum !== '') { + propData.maximum = parseFloat(maximum); } } - mcpProperties[index][field] = value; - if (field === 'type' && value !== 'integer' && value !== 'number') { - delete mcpProperties[index].minimum; - delete mcpProperties[index].maximum; - renderMcpProperties(); + + if (index >= 0) { + mcpProperties[index] = propData; + } else { + mcpProperties.push(propData); } + + renderMcpProperties(); + closePropertyModal(); } /** @@ -215,6 +288,14 @@ function setupMcpEventListeners() { const cancelBtn = document.getElementById('cancelMcpBtn'); const form = document.getElementById('mcpToolForm'); const addPropertyBtn = document.getElementById('addMcpPropertyBtn'); + + // 参数编辑模态框相关元素 + const propertyModal = document.getElementById('mcpPropertyModal'); + const closePropertyBtn = document.getElementById('closeMcpPropertyModalBtn'); + const cancelPropertyBtn = document.getElementById('cancelMcpPropertyBtn'); + const propertyForm = document.getElementById('mcpPropertyForm'); + const propertyTypeSelect = document.getElementById('mcpPropertyType'); + // Return early if required elements don't exist (e.g., in test environment) if (!toggleBtn || !panel || !addBtn || !modal || !closeBtn || !cancelBtn || !form || !addPropertyBtn) { return; @@ -234,6 +315,17 @@ function setupMcpEventListeners() { if (e.target === modal) closeMcpModal(); }); form.addEventListener('submit', handleMcpSubmit); + + // 参数编辑模态框事件 + if (propertyModal && closePropertyBtn && cancelPropertyBtn && propertyForm && propertyTypeSelect) { + closePropertyBtn.addEventListener('click', closePropertyModal); + cancelPropertyBtn.addEventListener('click', closePropertyModal); + propertyModal.addEventListener('click', (e) => { + if (e.target === propertyModal) closePropertyModal(); + }); + propertyForm.addEventListener('submit', handlePropertySubmit); + propertyTypeSelect.addEventListener('change', updatePropertyRangeVisibility); + } } /** @@ -275,7 +367,7 @@ function openMcpModal(index = null) { mcpProperties = []; } renderMcpProperties(); - document.getElementById('mcpToolModal').style.display = 'block'; + document.getElementById('mcpToolModal').style.display = 'flex'; } /** @@ -447,4 +539,4 @@ export async function executeMcpTool(toolName, toolArgs) { } // 暴露全局方法供 HTML 内联事件调用 -window.mcpModule = { updateMcpProperty, deleteMcpProperty, editMcpTool, deleteMcpTool }; +window.mcpModule = { addMcpProperty, editMcpProperty, deleteMcpProperty, editMcpTool, deleteMcpTool }; diff --git a/main/xiaozhi-server/test/test_page.html b/main/xiaozhi-server/test/test_page.html index 9801f3d3..0b52ab8c 100644 --- a/main/xiaozhi-server/test/test_page.html +++ b/main/xiaozhi-server/test/test_page.html @@ -244,20 +244,23 @@
- +
- -
-
-
- 暂无参数,点击下方按钮添加参数 -
-
-
+
+
+ 暂无参数,点击上方按钮添加参数 +
+
+
+
+ + +