| 1234567891011121314151617181920212223242526272829303132333435 |
- export function softwareReviewHTML(token: string) {
- return `<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>LambdaAgentPaaS · 候选重构审阅</title>
- <style>body{margin:0;background:#f2f6fc;color:#19324e;font:15px -apple-system,BlinkMacSystemFont,"PingFang SC",sans-serif}header{background:#142e50;color:white;padding:24px 5%}header small{color:#96c9fa}main{max-width:1150px;margin:26px auto;padding:0 24px}h1{font-size:25px;margin:9px 0}h2{font-size:20px}p{line-height:1.7;color:#6b7d92}.bar,.card{background:white;border:1px solid #d9e3ef;border-radius:13px;padding:20px;margin:15px 0}.steps{display:flex;gap:12px;flex-wrap:wrap}.steps b{padding:10px 15px;border-radius:9px;background:#e6effa}.steps b.active{background:#2167b8;color:white}.line{display:flex;gap:12px;align-items:center;justify-content:space-between;flex-wrap:wrap}.candidate>div{min-width:0;overflow-wrap:anywhere}.candidate{display:grid;grid-template-columns:28px minmax(0,1fr) auto;gap:12px;align-items:start}.candidate input{width:20px;height:20px}button,select{font:inherit;border:1px solid #bacee5;border-radius:8px;background:white;padding:9px 14px;color:#254b7b;cursor:pointer}button.primary{background:#2167b8;color:white}button:disabled{opacity:.45;cursor:default}.tag{display:inline-block;background:#eaf1fa;color:#55779e;border-radius:6px;padding:5px 8px;font-size:12px}.muted{font-size:12px;color:#7a8da3}.path{overflow-wrap:anywhere;font:12px ui-monospace,monospace;margin:8px 0;color:#527ba6}pre{white-space:pre-wrap;overflow-wrap:anywhere;background:#f1f5fa;padding:15px;border-radius:8px;font:12px/1.7 ui-monospace,monospace;max-height:340px;overflow:auto}.diff{display:grid;grid-template-columns:1fr 1fr;gap:12px}.diff>div{min-width:0}.before{background:#fff0f0}.after{background:#eaf7ef}.error{background:#ffeded;color:#9b3e45;padding:14px;border-radius:8px}.notice{background:#fff5de;padding:14px;border-radius:8px;color:#86631c}summary{cursor:pointer;color:#426c9e}.stats{font-size:16px;color:#2d639e}.sticky{position:sticky;bottom:0;box-shadow:0 -4px 14px #203e5412}.hidden{display:none}@media(max-width:700px){.diff{grid-template-columns:1fr}.candidate{grid-template-columns:25px 1fr}.candidate select{grid-column:2}main{padding:0 14px}}</style>
- <header><small>λ LambdaAgentPaaS / SOFTWARE</small><h1>先选要改什么,再确认怎样改</h1><div>本体分析 → 候选选择 → 计划审阅 → 隔离执行与验证</div></header><main><div id="error"></div><div id="state"></div><div id="body"></div></main>
- <script>
- const token=${JSON.stringify(token)};
- let state, revision=-1, dirty=false, decisions={};
- const esc=x=>String(x??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
- const phaseNames={analyzing:'正在分析',analysis_failed:'分析失败',awaiting_selection:'等待勾选',selected:'等待生成计划',awaiting_confirmation:'等待确认计划',running:'正在执行验证',awaiting_review:'验证通过,待质量验收',blocked:'基线未通过',verification_failed:'修改后验证失败',execution_failed:'执行失败'};
- const titles={large_method:'方法职责与体量待评估',assignment_in_condition:'条件中的状态更新待评估'};
- async function request(path,body){const r=await fetch('/api/'+path,{method:body?'POST':'GET',headers:{'Content-Type':'application/json','X-Review-Token':token},body:body?JSON.stringify(body):undefined});const data=await r.json();if(!r.ok)throw Error(data.detail||data.error||'请求失败');return data}
- function render(){
- const canSelect=['awaiting_selection','selected','awaiting_confirmation'].includes(state.phase);
- const stage=state.phase==='analyzing'?0:state.phase==='awaiting_selection'?1:['selected','awaiting_confirmation'].includes(state.phase)?2:3;
- document.querySelector('#state').innerHTML='<div class="steps">'+['构建与分析','候选选择','确认计划','执行与验证'].map((s,i)=>'<b class="'+(i===stage?'active':'')+'">'+(i+1)+' · '+s+'</b>').join('')+'</div><div class="bar"><div class="line"><strong>'+esc(state.project)+'</strong><span class="tag">'+esc(phaseNames[state.phase]||state.phase)+'</span></div><p class="stats">已分析 '+(state.coverage.analyzed_files??0)+' / '+(state.coverage.java_files??'—')+' 个 Java 文件 · '+state.candidates.length+' 条候选</p><div class="muted">候选是待审线索,不等于已确认缺陷。规则仅覆盖部分模式;跨分片影响与测试提示不完整。</div>'+(state.coverage.batches?.some(b=>b.truncated)?'<div class="notice">部分批次候选达到返回上限;当前清单不是完整诊断结果。</div>':'')+'</div>';
- let html=state.error?'<div class="error">'+esc(state.error)+'</div>':'';
- if(['execution_failed','blocked','verification_failed'].includes(state.phase))html+='<div class="notice">本次未完成验收。可以保留失败证据并返回规划,重新生成提案后仍需确认;不会自动重试执行。<br><button id="reopen">保留失败记录,返回规划</button></div>';
- if(state.phase==='selected')html+='<div class="notice">选择已保存。回到 OpenCode,让 software_review 读取 context 并生成计划与具体补丁。尚未修改源码。</div>';
- html+=state.candidates.map(c=>'<article class="card candidate"><input type="checkbox" aria-label="选择 '+esc(c.symbol)+'" data-id="'+c.id+'" '+(decisions[c.id]==='selected'?'checked':'')+' '+(!canSelect?'disabled':'')+'><div><strong>'+esc(titles[c.kind]||c.kind)+'</strong><div class="path">'+esc(c.path)+':'+c.method_range.start+'–'+c.method_range.end+'</div><div>'+esc(c.symbol)+'</div><p>'+esc(c.suggestion)+'</p><span class="tag">'+c.metrics.method_lines+' 行 · '+c.metrics.branch_points+' 个分支点</span> <span class="tag">测试提示 '+c.test_hints.length+' 项,未证明覆盖</span><details><summary>源码依据、关联范围与测试提示</summary><pre>'+esc(JSON.stringify({source_excerpt:c.source_excerpt,excerpt_truncated:c.excerpt_truncated,evidence:c.evidence,affected_paths:c.affected_paths,test_hints:c.test_hints},null,2))+'</pre></details></div><select data-decision="'+c.id+'" '+(!canSelect?'disabled':'')+'>'+[['deferred','暂缓'],['retained','保留现状'],['selected','本次处理']].map(a=>'<option value="'+a[0]+'" '+(decisions[c.id]===a[0]?'selected':'')+'>'+a[1]+'</option>').join('')+'</select></article>').join('');
- if(canSelect)html+='<div class="bar sticky line"><span>本次选择 <b id="count">'+Object.values(decisions).filter(v=>v==='selected').length+'</b> 项;其余保留在清单中</span><button id="save" class="primary" '+(!dirty?'disabled':'')+'>保存选择,交给智能体规划</button></div>';
- if(state.proposal){const p=state.proposal;html+='<section class="card"><h2>确认这一版执行计划</h2><p>'+esc(p.plan.goal)+'</p><div class="notice">只修改隔离副本,原仓库保留。当前支持同一批次内选定方法的修改;新增文件、扩大范围和补测试应另行规划。通过验证后仍需质量验收。</div><h3>'+ (p.ontology_binding?'绑定的本体节点':'旧提案的 focus(尚未通过节点绑定校验)') +'</h3><pre>'+esc(p.plan.focus.join('\\n'))+'</pre><p>'+ (p.ontology_binding?'仅节点绑定已校验,构建和行为测试以实际执行结果为准。':'此处保留原始失败提案,不能作为已绑定节点使用。请返回规划重新提交。') +'</p><h3>允许修改</h3><pre>'+esc(p.plan.allowed_files.join('\\n'))+'</pre><h3>必须保持的行为</h3><pre>'+esc(p.plan.invariants.join('\\n'))+'</pre><h3>执行环境预检</h3><pre>'+esc(JSON.stringify(p.readiness||{status:'未在提案阶段检查'},null,2))+'</pre><p>工具可用不代表依赖齐全或测试通过;实际结果以基线执行为准。</p><h3>冻结的验证配置</h3><pre>'+esc(JSON.stringify(p.plan.verification,null,2))+'</pre>'+p.edits.map(e=>'<h3>'+esc(e.path)+'</h3><div class="diff"><div><b>修改前</b><pre class="before">'+esc(e.old)+'</pre></div><div><b>修改后</b><pre class="after">'+esc(e.new)+'</pre></div></div>').join('')+'<button id="execute" class="primary" '+(state.phase!=='awaiting_confirmation'||dirty||p.readiness?.status==='blocked'?'disabled':'')+'>确认当前计划并开始执行</button></section>'}
- if(state.execution)html+='<section class="card"><h2>实际执行结果</h2><p>原仓库是否保持不变:'+(state.execution.source_unchanged?'是':'未确认')+'</p><pre>'+esc(JSON.stringify(state.execution.result,null,2))+'</pre><div class="path">'+esc(state.execution.transaction)+'</div><p>awaiting_review 表示测试阶段通过,尚未完成独立质量验收;候选未自动标为已解决。</p></section>';
- if(!state.candidates.length&&state.phase!=='analyzing')html+='<div class="card">未返回受支持的候选。不能据此认定仓库没有问题。</div>';
- document.querySelector('#body').innerHTML=html;
- function change(id,value){decisions[id]=value;dirty=true;document.querySelector('#save').disabled=false;document.querySelector('#count').textContent=Object.values(decisions).filter(v=>v==='selected').length;const e=document.querySelector('#execute');if(e)e.disabled=true;document.querySelector('[data-id="'+id+'"]').checked=value==='selected';document.querySelector('[data-decision="'+id+'"]').value=value}
- document.querySelectorAll('[data-id]').forEach(e=>e.onchange=()=>change(e.dataset.id,e.checked?'selected':'deferred'));
- document.querySelectorAll('[data-decision]').forEach(e=>e.onchange=()=>change(e.dataset.decision,e.value));
- const save=document.querySelector('#save');if(save)save.onclick=()=>act('selection',{revision:state.revision,decisions});
- const reopen=document.querySelector('#reopen');if(reopen)reopen.onclick=()=>act('reopen',{revision:state.revision});
- const execute=document.querySelector('#execute');if(execute)execute.onclick=()=>act('execute',{revision:state.revision,version:state.proposal.version});
- }
- async function act(path,body){document.querySelectorAll('button').forEach(b=>b.disabled=true);try{state=await request(path,body);revision=state.revision;dirty=false;decisions={...state.decisions};render();document.querySelector('#error').textContent=''}catch(e){document.querySelector('#error').innerHTML='<div class="error">'+esc(e.message)+'。请重新读取状态,不要重复提交执行。</div>';revision=-1;dirty=false}}
- async function poll(){try{const next=await request('review');if(next.revision!==revision){if(dirty){document.querySelector('#error').innerHTML='<div class="notice">服务端状态已变化。当前选择尚未保存,保存时将检查版本;重新加载页面可丢弃本地选择。</div>'}else{state=next;revision=state.revision;decisions={...state.decisions};render()}}}catch(e){document.querySelector('#error').innerHTML='<div class="error">'+esc(e.message)+'</div>';document.querySelectorAll('button').forEach(b=>b.disabled=true);revision=-1}setTimeout(poll,2000)}poll();
- </script></html>`
- }
|