224 lines
21 KiB
HTML
224 lines
21 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>端云协同 LLM 协作系统</title>
|
||
<style>
|
||
:root{--bg:#0f1115;--panel:#171a21;--panel2:#1d212b;--line:#2a2f3a;--txt:#e6e9ef;--muted:#8b93a3;--acc:#4f8cff;--acc2:#23c46d;--warn:#f5a623;--bad:#ff5c5c;--mono:Consolas,"Courier New",monospace}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{background:var(--bg);color:var(--txt);font:14px/1.6 -apple-system,"Segoe UI","Microsoft YaHei",sans-serif;height:100vh;display:flex;flex-direction:column}
|
||
header{display:flex;align-items:center;gap:16px;padding:12px 20px;background:var(--panel);border-bottom:1px solid var(--line)}
|
||
header h1{font-size:16px;font-weight:600}
|
||
header .sub{color:var(--muted);font-size:12px}
|
||
.pill{font-size:11px;padding:2px 8px;border-radius:10px;border:1px solid var(--line);color:var(--muted)}
|
||
nav{display:flex;gap:4px;padding:8px 20px;background:var(--panel);border-bottom:1px solid var(--line)}
|
||
nav button{background:none;border:1px solid transparent;color:var(--muted);padding:6px 14px;border-radius:8px;cursor:pointer;font-size:13px}
|
||
nav button.active{color:var(--txt);background:var(--panel2);border-color:var(--line)}
|
||
nav button:hover{color:var(--txt)}
|
||
main{flex:1;overflow:auto;padding:16px 20px}
|
||
.panel{display:none;max-width:1200px;margin:0 auto}
|
||
.panel.active{display:block}
|
||
.chatrow{display:flex;gap:16px;align-items:flex-start}
|
||
.chatbox{flex:1}
|
||
.prompt{display:flex;gap:8px;margin-bottom:12px}
|
||
textarea{flex:1;resize:vertical;min-height:70px;background:var(--panel2);border:1px solid var(--line);color:var(--txt);border-radius:10px;padding:10px;font-size:14px;font-family:inherit}
|
||
textarea:focus{outline:none;border-color:var(--acc)}
|
||
button.primary{background:var(--acc);color:#fff;border:none;border-radius:10px;padding:10px 20px;font-size:14px;cursor:pointer}
|
||
button.primary:disabled{opacity:.5;cursor:not-allowed}
|
||
.card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px;margin-bottom:14px}
|
||
.card h3{font-size:13px;color:var(--muted);margin-bottom:10px;font-weight:600;letter-spacing:.5px}
|
||
.resp{white-space:pre-wrap;word-break:break-word;font-size:14px}
|
||
.badges{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0}
|
||
.b{font-size:11px;padding:2px 8px;border-radius:10px}
|
||
.b.ok{background:rgba(35,196,109,.15);color:var(--acc2);border:1px solid rgba(35,196,109,.4)}
|
||
.b.fast{background:rgba(79,140,255,.15);color:var(--acc);border:1px solid rgba(79,140,255,.4)}
|
||
.b.warn{background:rgba(245,166,35,.15);color:var(--warn);border:1px solid rgba(245,166,35,.4)}
|
||
.b.err{background:rgba(255,92,92,.15);color:var(--bad);border:1px solid rgba(255,92,92,.4)}
|
||
.b.plain{background:var(--panel2);color:var(--muted);border:1px solid var(--line)}
|
||
.chips{display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}
|
||
.chip{font-size:11px;font-family:var(--mono);background:var(--panel2);color:var(--muted);padding:2px 7px;border-radius:6px;border:1px solid var(--line)}
|
||
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px}
|
||
.stat{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px}
|
||
.stat .v{font-size:22px;font-weight:700}
|
||
.stat .k{font-size:12px;color:var(--muted);margin-top:2px}
|
||
.step{display:flex;gap:8px;align-items:baseline;padding:6px 0;border-bottom:1px dashed var(--line)}
|
||
.step:last-child{border:none}
|
||
.step .id{font-family:var(--mono);color:var(--acc);font-weight:600}
|
||
.step .st{font-size:11px;margin-left:auto}
|
||
.done{color:var(--acc2)}.fail{color:var(--bad)}.block{color:var(--warn)}
|
||
details{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:10px 12px;margin-bottom:8px}
|
||
summary{cursor:pointer;font-weight:600;font-size:13px}
|
||
pre.trace{background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:10px;font-family:var(--mono);font-size:12px;overflow:auto;color:var(--muted)}
|
||
.kw{font-family:var(--mono);color:var(--acc)}
|
||
.row-btns{display:flex;gap:8px;margin-top:8px;flex-wrap:wrap}
|
||
button.small{background:var(--panel2);border:1px solid var(--line);color:var(--txt);border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer}
|
||
button.small:hover{border-color:var(--acc)}
|
||
input,select{background:var(--panel2);border:1px solid var(--line);color:var(--txt);border-radius:8px;padding:6px 10px;font-size:13px}
|
||
.muted{color:var(--muted)}
|
||
.history{max-height:220px;overflow:auto;margin-top:8px}
|
||
table{width:100%;border-collapse:collapse;font-size:13px}
|
||
.form-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}
|
||
.form-grid label{display:flex;flex-direction:column;gap:5px;font-size:12px;color:var(--muted)}
|
||
.form-grid input,.form-grid select{width:100%}
|
||
th,td{text-align:left;padding:8px;border-bottom:1px solid var(--line)}
|
||
th{color:var(--muted);font-weight:600}
|
||
footer{padding:8px 20px;color:var(--muted);font-size:11px;border-top:1px solid var(--line);background:var(--panel)}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<h1>⚙️ 端云协同 LLM 协作系统</h1>
|
||
<span class="sub">Architect(大模型 API)+ Worker(本地小模型)+ 交流文本协议</span>
|
||
<span class="pill" id="healthPill">…</span>
|
||
</header>
|
||
<nav>
|
||
<button data-tab="chat" class="active">💬 对话</button>
|
||
<button data-tab="work">🔗 协作过程</button>
|
||
<button data-tab="review">🧑💻 人工检验</button>
|
||
<button data-tab="metrics">📊 指标</button>
|
||
<button data-tab="settings">⚙️ 模型设置</button>
|
||
</nav>
|
||
<main>
|
||
<!-- 对话 -->
|
||
<section class="panel active" id="panel-chat">
|
||
<div class="chatrow">
|
||
<div class="chatbox">
|
||
<div class="prompt">
|
||
<textarea id="query" placeholder="输入你的问题,例如:用 Python 写一个快速排序并分析复杂度…"></textarea>
|
||
<button class="primary" id="sendBtn">发送</button>
|
||
</div>
|
||
<div class="row-btns">
|
||
<button class="small" id="legacyBtn">🔁 与 v1 legacy 对照</button>
|
||
<button class="small" id="sampleBtn">🎲 示例</button>
|
||
</div>
|
||
<div id="chatResult"></div>
|
||
<div id="legacyResult" style="margin-top:12px"></div>
|
||
</div>
|
||
<div id="workspacePanel" style="flex:1;max-width:480px"></div>
|
||
</div>
|
||
</section>
|
||
<!-- 协作过程 -->
|
||
<section class="panel" id="panel-work">
|
||
<div class="row-btns" style="margin-bottom:10px">
|
||
<input id="workRid" placeholder="输入 request_id,留空用最近一次" style="width:300px">
|
||
<button class="small" id="loadWorkBtn">加载交流文本</button>
|
||
</div>
|
||
<div id="workResult"></div>
|
||
</section>
|
||
<!-- 人工检验 -->
|
||
<section class="panel" id="panel-review">
|
||
<div class="row-btns" style="margin-bottom:10px">
|
||
<select id="reviewStatus"><option value="">全部</option><option value="pending">待审</option><option value="reviewed">已审</option></select>
|
||
<button class="small" id="refreshReview">刷新</button>
|
||
</div>
|
||
<div id="reviewResult"></div>
|
||
</section>
|
||
<!-- 指标 -->
|
||
<section class="panel" id="panel-metrics">
|
||
<div class="row-btns" style="margin-bottom:10px"><button class="small" id="refreshMetrics">刷新</button></div>
|
||
<div id="metricsResult"></div>
|
||
</section>
|
||
<!-- 模型设置 -->
|
||
<section class="panel" id="panel-settings">
|
||
<div class="card">
|
||
<h3>⚙️ 内置小模型(Worker)</h3>
|
||
<div class="form-grid">
|
||
<label>后端 backend
|
||
<select id="set-worker-backend">
|
||
<option value="llama_server">llama_server(内置本地模型)</option>
|
||
<option value="openai">openai/api(Ollama / vLLM 等兼容端点)</option>
|
||
<option value="mock">mock(演示,零运行时)</option>
|
||
</select>
|
||
</label>
|
||
<label>模型路径/名称 <input id="set-worker-model" type="text" placeholder="models/xxx.gguf 或模型名"></label>
|
||
<label>端点 base_url(openai 用) <input id="set-worker-baseurl" type="text" placeholder="http://127.0.0.1:11434/v1"></label>
|
||
<label>端口(llama_server) <input id="set-worker-port" type="number"></label>
|
||
<label>temperature <input id="set-worker-temperature" type="number" step="0.1"></label>
|
||
<label>max_fix_attempts <input id="set-worker-fix" type="number"></label>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<h3>🧠 大模型(Architect / API)</h3>
|
||
<div class="form-grid">
|
||
<label>model <input id="set-arch-model" type="text"></label>
|
||
<label>base_url <input id="set-arch-baseurl" type="text"></label>
|
||
<label>api_key_env <input id="set-arch-keyenv" type="text"></label>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<h3>🔀 管线 pipeline</h3>
|
||
<div class="form-grid">
|
||
<label>fast_path(快路径直答) <input id="set-pipe-fast" type="checkbox"></label>
|
||
<label>rounds_cap <input id="set-pipe-rounds" type="number"></label>
|
||
<label>api_token_cap <input id="set-pipe-tokens" type="number"></label>
|
||
<label>breach_policy
|
||
<select id="set-pipe-breach">
|
||
<option value="architect_do">architect_do(兜底代做)</option>
|
||
<option value="local_only">local_only(本地降级)</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="row-btns">
|
||
<button class="primary" id="saveSettingsBtn">保存并生效</button>
|
||
<button class="small" id="resetSettingsBtn">恢复默认</button>
|
||
<span class="muted" id="settingsMsg"></span>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
<footer>端云协同 LLM 协作系统 · v2 · 交流文本协议 · 北极星:token 下降 ≥80%</footer>
|
||
<script>
|
||
const $=id=>document.getElementById(id);
|
||
let lastRequestId=null;
|
||
const esc=s=>{const d=document.createElement("div");d.textContent=s==null?"":String(s);return d.innerHTML;};
|
||
function el(tag,cls,txt){const n=document.createElement(tag);if(cls)n.className=cls;if(txt!=null)n.textContent=txt;return n;}
|
||
function setTab(t){document.querySelectorAll("nav button").forEach(b=>b.classList.toggle("active",b.dataset.tab===t));document.querySelectorAll(".panel").forEach(p=>p.classList.toggle("active",p.id==="panel-"+t));}
|
||
document.querySelectorAll("nav button").forEach(b=>b.onclick=()=>{setTab(b.dataset.tab);if(b.dataset.tab==="review")loadReview();if(b.dataset.tab==="metrics")loadMetrics();if(b.dataset.tab==="settings")loadSettings();});
|
||
|
||
// ---- 健康状态 ----
|
||
async function health(){try{const r=await fetch("/health");const d=await r.json();$("healthPill").textContent="健康 · "+d.domains.length+" 领域";}catch(e){$("healthPill").textContent="离线";}}
|
||
|
||
// ---- 对话 ----
|
||
async function sendChat(){const q=$("query").value.trim();if(!q)return;const btn=$("sendBtn");btn.disabled=true;btn.textContent="运行中…";$("chatResult").innerHTML="";$("legacyResult").innerHTML="";$("workspacePanel").innerHTML="";try{const r=await fetch("/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({query:q})});const d=await r.json();lastRequestId=d.request_id||null;renderChatResult(d);if(d.request_id)loadWorkspace(d.request_id);}catch(e){$("chatResult").appendChild(el("div","resp","请求失败:"+e.message));}finally{btn.disabled=false;btn.textContent="发送";}}
|
||
function renderChatResult(d){const c=$("chatResult");c.innerHTML="";const card=el("div","card");card.appendChild(el("h3","","响应"));const b=$("badges0")||el("div","badges");b.innerHTML="";const st=(d.status||"done");b.appendChild(chip(st==="fast_path"?"⚡ 快路径":"🤝 协作",st==="fast_path"?"b fast":(st==="done"?"b ok":"b warn")));b.appendChild(chip("status: "+esc(st),"b plain"));b.appendChild(chip("model: "+esc(d.model_used),"b plain"));b.appendChild(chip("输入 "+d.api_input_tokens+" / 输出 "+d.api_output_tokens+" token","b plain"));b.appendChild(chip("耗时 "+d.latency_ms+" ms","b plain"));b.appendChild(chip("rounds "+d.rounds_used,"b plain"));b.id="badges0";card.appendChild(b);const resp=el("div","resp",d.response||"(空)");card.appendChild(resp);if(d.error)card.appendChild(el("div","resp muted","⚠️ "+d.error));const route=el("div","chips");(d.route||[]).forEach(r=>route.appendChild(el("span","chip",r)));card.appendChild(el("h3","","路由轨迹"));card.appendChild(route);card.appendChild(el("div","muted","request_id: "+(d.request_id||"-")+"(见右侧协作过程)"));c.appendChild(card);}
|
||
function chip(txt,cls){const s=el("span","b "+cls,txt);return s;}
|
||
|
||
// ---- 协作过程 / 交流文本 ----
|
||
async function loadWorkspace(rid){try{const r=await fetch("/runs/"+rid+"/workspace");if(!r.ok)throw new Error("无 workspace");const ws=await r.json();renderWorkspace($("workspacePanel"),ws);if($("workRid"))$("workRid").value=rid;renderWorkTab(ws);}catch(e){$("workspacePanel").appendChild(el("div","muted","(该请求无交流文本:"+e.message+")"));}}
|
||
function renderWorkspace(host,ws){host.innerHTML="";const card=el("div","card");card.appendChild(el("h3","","🤝 交流文本 · "+(ws.request_id||"")));const b=ws.brief||{};card.appendChild(el("div","resp","🎯 "+(b.goal||"")));const plan=el("div","");(b.plan||[]).forEach(p=>{const row=el("div","step");row.appendChild(el("span","id",p.id));row.appendChild(el("span","",p.task||""));plan.appendChild(row);});card.appendChild(el("h3","","计划 (plan)"));card.appendChild(plan);card.appendChild(el("h3","","进度 (progress)"));const pg=el("div","");(ws.progress||[]).forEach(p=>{const row=el("div","step");row.appendChild(el("span","id",p.step));row.appendChild(el("span","",p.summary||""));const st=el("span","st "+(p.status==="done"?"done":p.status==="failed"?"fail":"block"),p.status);row.appendChild(st);pg.appendChild(row);});card.appendChild(pg);if((ws.issues||[]).length){card.appendChild(el("h3","","问题 (issues)"));(ws.issues||[]).forEach(i=>{const dt=el("details","");dt.appendChild(el("summary","","#"+(i.id||"")+" · step "+(i.step||"")));dt.appendChild(el("div","resp","observed: "+(i.observed||"")+"\nask: "+(i.ask||"")));card.appendChild(dt);});}if((ws.decisions||[]).length){card.appendChild(el("h3","","裁决 (decisions)"));(ws.decisions||[]).forEach(dc=>{const dt=el("details","");dt.appendChild(el("summary","","#"+esc(dc.ref)+" · 裁决"));dt.appendChild(el("div","resp",dc.reply||""));card.appendChild(dt);});}if((ws.archive||[]).length){card.appendChild(el("h3","","归档 (archive)"));const ar=el("div","resp");(ws.archive||[]).forEach(a=>ar.appendChild(el("div","muted","▸ "+a)));card.appendChild(ar);}host.appendChild(card);}
|
||
function renderWorkTab(ws){const host=$("workResult");if(!host)return;host.innerHTML="";renderWorkspace(host,ws);}
|
||
async function loadWorkByRid(){const rid=$("workRid").value.trim()||lastRequestId;if(!rid){$("workResult").innerHTML="<div class=muted>没有 request_id</div>";return;}await loadWorkspace(rid);}
|
||
|
||
// ---- 人工检验 ----
|
||
async function loadReview(){const st=$("reviewStatus").value;const r=await fetch("/review/queue?limit=50&status="+st);const rows=await r.json();const host=$("reviewResult");host.innerHTML="";if(!rows.length){host.appendChild(el("div","muted","暂无记录"));return;}rows.forEach(x=>{const card=el("div","card");card.appendChild(el("h3","","#"+(x.id||"")+" · "+(x.request_id||"")+" · "+(x.status||"")));card.appendChild(el("div","resp",x.query||""));card.appendChild(el("div","muted","tags: "+(x.tags||[]).join(", ")+" · reason: "+(x.reason||"")));if(x.verdict)card.appendChild(el("div","muted","verdict: "+x.verdict+(x.correction?" · correction: "+x.correction:"")));if(x.status==="pending"){const btns=el("div","row-btns");["approve","edit","reject"].forEach(v=>{const b=el("button","small",v);b.onclick=()=>submitReview(x.id,v);btns.appendChild(b);});card.appendChild(btns);}host.appendChild(card);});}
|
||
async function submitReview(id,verdict){let correction=null;if(verdict==="edit"){correction=prompt("修正内容:");if(correction==null)return;}const r=await fetch("/review/"+id+"?verdict="+verdict+(correction?"&correction="+encodeURIComponent(correction):""),{method:"POST"});const d=await r.json();if(d.ok)loadReview();else alert(d.detail||"提交失败");}
|
||
|
||
// ---- 指标 ----
|
||
async function loadMetrics(){const r=await fetch("/metrics");const d=await r.json();const host=$("metricsResult");host.innerHTML="";const v2=d.v2||{};const grid=el("div","grid");stat(grid,"总请求",v2.total_requests||0);stat(grid,"快路径命中率",((v2.fast_path_rate||0)*100).toFixed(1)+"%");stat(grid,"熔断次数",v2.breach_count||0);stat(grid,"API 输入 token",v2.api_tokens?v2.api_tokens.input:0);stat(grid,"API 输出 token",v2.api_tokens?v2.api_tokens.output:0);stat(grid,"回合数均值",v2.rounds_used?v2.rounds_used.avg:0);host.appendChild(grid);const card=el("div","card");card.appendChild(el("h3","","状态分布"));const tb=el("table","");const tr=el("tr","");["status","count"].forEach(h=>tr.appendChild(el("th","",h)));tb.appendChild(tr);Object.entries(v2.status_distribution||{}).forEach(([k,v])=>{const r=el("tr","");r.appendChild(el("td","",k));r.appendChild(el("td","",v));tb.appendChild(r);});card.appendChild(tb);host.appendChild(card);const rc=el("div","card");rc.appendChild(el("h3","","回合数分布"));const tb2=el("table","");const tr2=el("tr","");["rounds","count"].forEach(h=>tr2.appendChild(el("th","",h)));tb2.appendChild(tr2);Object.entries((v2.rounds_used||{}).distribution||{}).forEach(([k,v])=>{const r=el("tr","");r.appendChild(el("td","",k));r.appendChild(el("td","",v));tb2.appendChild(r);});rc.appendChild(tb2);host.appendChild(rc);}
|
||
function stat(grid,v,k){const s=el("div","stat");s.appendChild(el("div","v",v));s.appendChild(el("div","k",k));grid.appendChild(s);}
|
||
|
||
// ---- v1 legacy 对照 ----
|
||
async function sendLegacy(){const q=$("query").value.trim();if(!q)return;const host=$("legacyResult");host.innerHTML="";try{const r=await fetch("/chat/legacy",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({query:q})});const d=await r.json();const card=el("div","card");card.appendChild(el("h3","","🔁 v1 legacy(规则路由)"));card.appendChild(el("div","resp",d.response||""));const chips=el("div","chips");(d.route||[]).forEach(x=>chips.appendChild(el("span","chip",x)));card.appendChild(chips);card.appendChild(el("div","muted","domain: "+(d.domain||"")+" · conf "+(d.confidence||"")+" · latency "+(d.latency_ms||0)+" ms"));host.appendChild(card);}catch(e){host.appendChild(el("div","muted","legacy 调用失败:"+e.message));}}
|
||
|
||
// ---- 示例 ----
|
||
const SAMPLES=["用 Python 实现快速排序,并分析时间与空间复杂度","求解方程 x^2 - 5x + 6 = 0","高血压患者日常饮食需要注意什么","解释一下深度学习中的注意力机制"];
|
||
function sample(){$("query").value=SAMPLES[Math.floor(Math.random()*SAMPLES.length)];}
|
||
|
||
// ---- 模型设置 ----
|
||
async function loadSettings(){try{const r=await fetch("/config");const s=await r.json();const w=s.worker||{};$("set-worker-backend").value=w.backend||"llama_server";$("set-worker-model").value=w.model||"";$("set-worker-baseurl").value=w.base_url||"";$("set-worker-port").value=w.port||8901;$("set-worker-temperature").value=w.temperature||0.3;$("set-worker-fix").value=w.max_fix_attempts||2;const a=s.architect||{};$("set-arch-model").value=a.model||"";$("set-arch-baseurl").value=a.base_url||"";$("set-arch-keyenv").value=a.api_key_env||"";const p=s.pipeline||{};$("set-pipe-fast").checked=!!p.fast_path;$("set-pipe-rounds").value=p.rounds_cap||6;$("set-pipe-tokens").value=p.api_token_cap||8000;$("set-pipe-breach").value=p.breach_policy||"architect_do";$("settingsMsg").textContent="";}catch(e){$("settingsMsg").textContent="读取失败:"+e.message;}}
|
||
async function saveSettings(){const patch={worker:{backend:$("set-worker-backend").value,model:$("set-worker-model").value,base_url:$("set-worker-baseurl").value,port:parseInt($("set-worker-port").value||8901),temperature:parseFloat($("set-worker-temperature").value||0.3),max_fix_attempts:parseInt($("set-worker-fix").value||2)},architect:{model:$("set-arch-model").value,base_url:$("set-arch-baseurl").value,api_key_env:$("set-arch-keyenv").value},pipeline:{fast_path:$("set-pipe-fast").checked,rounds_cap:parseInt($("set-pipe-rounds").value||6),api_token_cap:parseInt($("set-pipe-tokens").value||8000),breach_policy:$("set-pipe-breach").value}};try{const r=await fetch("/config",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(patch)});const d=await r.json();$("settingsMsg").textContent=r.ok?"✅ 已保存并重建管线":"❌ "+(d.detail||"保存失败");}catch(e){$("settingsMsg").textContent="保存失败:"+e.message;}}
|
||
async function resetSettings(){try{await fetch("/config/reset",{method:"POST"});$("settingsMsg").textContent="已恢复默认设置";loadSettings();}catch(e){$("settingsMsg").textContent="重置失败:"+e.message;}}
|
||
$("saveSettingsBtn").onclick=saveSettings;
|
||
$("resetSettingsBtn").onclick=resetSettings;
|
||
$("sendBtn").onclick=sendChat;
|
||
$("legacyBtn").onclick=sendLegacy;
|
||
$("sampleBtn").onclick=sample;
|
||
$("loadWorkBtn").onclick=loadWorkByRid;
|
||
$("refreshReview").onclick=loadReview;
|
||
$("refreshMetrics").onclick=loadMetrics;
|
||
$("query").addEventListener("keydown",e=>{if(e.key==="Enter"&&(e.ctrlKey||e.metaKey))sendChat();});
|
||
health();loadMetrics();
|
||
</script>
|
||
</body>
|
||
</html> |