/** * 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])); }