114 lines
4.5 KiB
TypeScript
114 lines
4.5 KiB
TypeScript
// @ts-nocheck
|
|
|
|
|
|
import { getToolNameLabel } from '../ui/sidebar.js';
|
|
|
|
let _toolSeq = 0;
|
|
|
|
export function createToolCard(parentEl, opts) {
|
|
const { name, args, protocol = 'mcp' } = opts;
|
|
const seq = ++_toolSeq;
|
|
|
|
const el = document.createElement('div');
|
|
const isMeta = name === 'metro_search_tools' || name === 'metro_get_tool_detail';
|
|
el.className = `tool-card running${isMeta ? ' meta' : ''}`;
|
|
el.dataset.seq = seq;
|
|
|
|
el.innerHTML = `
|
|
<div class="tool-header">
|
|
<span class="tool-icon"></span>
|
|
<span class="tool-name" data-role="name" title="${escapeHtml(name)}">正在调用 ${escapeHtml(getToolNameLabel(name))}</span>
|
|
<span class="tool-protocol ${protocol}">${protocol.toUpperCase()}</span>
|
|
<span class="tool-status running" data-role="status">运行中</span>
|
|
<span class="tool-chevron">▶</span>
|
|
</div>
|
|
<div class="tool-body">
|
|
<div class="tool-section" data-role="progress-section" style="display:none;">
|
|
<div class="tool-section-label">进度</div>
|
|
<div class="tool-progress" data-role="progress"></div>
|
|
</div>
|
|
<div class="tool-section">
|
|
<div class="tool-section-label">参数</div>
|
|
<div class="tool-section-content" data-role="args">${escapeHtml(JSON.stringify(args, null, 2))}</div>
|
|
</div>
|
|
<div class="tool-section" data-role="result-section" style="display:none;">
|
|
<div class="tool-section-label">结果</div>
|
|
<div class="tool-section-content" data-role="result"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
parentEl.appendChild(el);
|
|
|
|
const nameEl = el.querySelector('[data-role="name"]');
|
|
const statusEl = el.querySelector('[data-role="status"]');
|
|
const progressSection = el.querySelector('[data-role="progress-section"]');
|
|
const progressEl = el.querySelector('[data-role="progress"]');
|
|
const resultSection = el.querySelector('[data-role="result-section"]');
|
|
const resultEl = el.querySelector('[data-role="result"]');
|
|
|
|
const label = getToolNameLabel(name);
|
|
|
|
el.querySelector('.tool-header').addEventListener('click', () => {
|
|
el.classList.toggle('expanded');
|
|
});
|
|
|
|
return {
|
|
el,
|
|
setStatus(status, summary) {
|
|
el.classList.remove('running');
|
|
|
|
if (status === 'success' || status === 'error') {
|
|
progressSection.style.display = 'none';
|
|
}
|
|
if (status === 'success') {
|
|
nameEl.textContent = label;
|
|
el.classList.add('success');
|
|
statusEl.className = 'tool-status success';
|
|
statusEl.textContent = summary || '完成';
|
|
} else if (status === 'error') {
|
|
nameEl.textContent = label;
|
|
el.classList.add('error');
|
|
statusEl.className = 'tool-status error';
|
|
statusEl.textContent = summary || '失败';
|
|
}
|
|
},
|
|
setResult(result, isError) {
|
|
resultSection.style.display = '';
|
|
const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
resultEl.textContent = text;
|
|
if (text.length > 400) {
|
|
resultEl.classList.add('collapsed');
|
|
resultEl.addEventListener('click', () => {
|
|
resultEl.classList.remove('collapsed');
|
|
}, { once: true });
|
|
}
|
|
},
|
|
updateProgress(progress) {
|
|
|
|
if (!progress) return;
|
|
progressSection.style.display = '';
|
|
el.classList.add('expanded');
|
|
let html = '';
|
|
if (progress.stage) html += `<div class="progress-stage">${escapeHtml(progress.stage)}</div>`;
|
|
if (progress.total && progress.current) {
|
|
const pct = Math.round((progress.current / progress.total) * 100);
|
|
html += `<div class="progress-bar"><div class="progress-bar-fill" style="width:${pct}%"></div></div>`;
|
|
html += `<div class="progress-count">${progress.current}/${progress.total}(${pct}%)</div>`;
|
|
}
|
|
if (progress.detail) html += `<div class="progress-detail">${escapeHtml(progress.detail)}</div>`;
|
|
progressEl.innerHTML = html;
|
|
},
|
|
};
|
|
}
|
|
|
|
function escapeHtml(s) {
|
|
if (s == null) return '';
|
|
return String(s)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|