/**
* ave-test-cap.js — 🧪 能力试跑
*
* 单独测一个能力(不跑整条流程):
* 1. 选能力 → 自动带出「预检」(用哪个供应商/模型/参数/密钥是否就绪)
* 2. 按 params_schema 填参数
* 3. 🔍 预检(**零费用**,只校验配置)| ▶️ 真跑(**可能产生费用**,二次确认)
*
* 真跑当前支持:文本/图片 → 视频(可灵 kling-v3);其余能力给出"实现未接入 + 位置"
*/
const API = '/api/ave';
let _caps = [];
let _cats = [];
let _cur = null;
export async function loadView(container) {
container.innerHTML = `
🧪 能力试跑
单独测一个能力,不用跑整条流程
💡 用法:先点「🔍 预检」确认配置(用哪个供应商/模型、参数是什么、密钥是否就绪)—— 预检零费用;
确认无误后再点「▶️ 真跑」实际调用(可能产生费用,会二次确认)。
`;
await loadCaps();
document.getElementById('tcCat').addEventListener('change', () => fillCapSelect());
document.getElementById('tcCap').addEventListener('change', () => selectCap());
}
async function loadCaps() {
try {
const r = await fetch(`${API}/capabilities`);
const d = await r.json();
_caps = d.capabilities || [];
_cats = d.categories || [];
const c = document.getElementById('tcCat');
c.innerHTML = `` +
_cats.map(x => ``).join('');
fillCapSelect();
} catch (e) {
document.getElementById('tcBody').innerHTML = `❌ 加载能力失败:${esc(e.message)}
`;
}
}
function fillCapSelect() {
const cat = document.getElementById('tcCat').value;
const list = cat ? _caps.filter(c => c.category === cat) : _caps;
const s = document.getElementById('tcCap');
const keep = s.value;
s.innerHTML = list.map(c => ``).join('');
if (list.some(c => c.id === keep)) s.value = keep;
selectCap();
}
function statusIcon(st) { return { ready: '✅', partial: '🟡', planned: '⚪' }[st] || '⚪'; }
function selectCap() {
const cid = document.getElementById('tcCap').value;
_cur = _caps.find(c => c.id === cid) || null;
renderForm();
}
function renderForm() {
const el = document.getElementById('tcBody');
if (!_cur) { el.innerHTML = ''; return; }
const c = _cur;
const defaults = c.defaults || {};
const provs = c.providers || [];
const provId = defaults.provider || (provs[0] || {}).id || '';
const prov = provs.find(p => p.id === provId) || {};
// 参数表单(按 params_schema)
const schema = c.params_schema || {};
const fields = Object.entries(schema).map(([k, s]) => {
const v = defaults[k] ?? s.default ?? '';
const label = `${esc(s.label || k)}${s.required ? ' *' : ''}`;
if (s.type === 'enum') {
return ``;
}
if (s.type === 'number') {
return ``;
}
return ``;
}).join('');
// 常用输入(prompt / text 等,schema 里没有也给一个)
const extra = (!schema.prompt && !schema.text) ? `
` : '';
el.innerHTML = `
${esc(c.name)}
${esc(c.id)}
${esc(c.description || '')}
${statusIcon(c.status)} ${esc(c.status)}
${extra}
${fields || '
(该能力无可调参数)
'}
密钥位置:${esc(prov.key_ref || '—')} · 实现:${esc((c.impl || '').slice(0, 40))}
`;
// 供应商切换 → 更新模型显示
document.getElementById('tcProv')?.addEventListener('change', (e) => {
const p = provs.find(x => x.id === e.target.value) || {};
const m = document.getElementById('tcModel');
if (m) m.value = p.model || '';
});
}
window._tcRun = async (dryRun) => {
if (!_cur) return;
const params = {};
document.querySelectorAll('[data-p]').forEach(el => {
let v = el.value;
if (el.type === 'number') v = Number(v);
params[el.dataset.p] = v;
});
const pt = document.getElementById('tcPrompt');
if (pt && pt.value.trim()) params.prompt = pt.value.trim();
const prov = document.getElementById('tcProv')?.value;
if (prov) params.provider = prov;
if (!dryRun) {
const warn = `▶️ 真跑「${_cur.name}」\n\n供应商:${prov}\n参数:${JSON.stringify(params).slice(0, 180)}\n\n⚠️ 这会实际调用外部 API,可能产生费用。\n确认继续?`;
if (!confirm(warn)) return;
}
const el = document.getElementById('tcResult');
el.innerHTML = `${dryRun ? '🔍 预检中…' : '▶️ 执行中(可能需要 1~2 分钟)…'}
`;
try {
const r = await fetch(`${API}/capabilities/${encodeURIComponent(_cur.id)}/test`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ params, dry_run: dryRun }),
});
const d = await r.json();
if (d.status !== 'ok') {
el.innerHTML = `❌ ${esc(d.detail || '失败')}
`;
return;
}
const p = d.preflight || {};
if (dryRun || d.not_implemented) {
el.innerHTML = `
${d.not_implemented ? 'ℹ️ 预检通过(真跑未接入)' : '✅ 预检完成'}
供应商:${esc(p.provider_name)} ${p.key_ready ? '(✅ 密钥已配)' : '(⚠️ 密钥未配 → 去「🔑 API 配置」)'}
模型:${esc(p.model || '—')} · 执行:${esc(p.execution)}
参数:${esc(JSON.stringify(p.params))}
${esc(d.message || '')}
`;
} else {
el.innerHTML = `
✅ ${esc(d.message || '试跑完成')}
产物:${esc(d.output || '')}
${(d.output || '').endsWith('.mp4') ? `
` : ''}
`;
}
} catch (e) {
el.innerHTML = `❌ ${esc(e.message)}
`;
}
};
function esc(s) {
return String(s ?? '').replace(/[&<>"']/g, m =>
({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[m]));
}