@@ -0,0 +1,167 @@
<!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 : 14 px / 1.6 - apple-system , "Segoe UI" , "Microsoft YaHei" , sans-serif ; height : 100 vh ; display : flex ; flex-direction : column }
header { display : flex ; align-items : center ; gap : 16 px ; padding : 12 px 20 px ; background : var ( - - panel ) ; border-bottom : 1 px solid var ( - - line ) }
header h1 { font-size : 16 px ; font-weight : 600 }
header . sub { color : var ( - - muted ) ; font-size : 12 px }
. pill { font-size : 11 px ; padding : 2 px 8 px ; border-radius : 10 px ; border : 1 px solid var ( - - line ) ; color : var ( - - muted ) }
nav { display : flex ; gap : 4 px ; padding : 8 px 20 px ; background : var ( - - panel ) ; border-bottom : 1 px solid var ( - - line ) }
nav button { background : none ; border : 1 px solid transparent ; color : var ( - - muted ) ; padding : 6 px 14 px ; border-radius : 8 px ; cursor : pointer ; font-size : 13 px }
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 : 16 px 20 px }
. panel { display : none ; max-width : 1200 px ; margin : 0 auto }
. panel . active { display : block }
. chatrow { display : flex ; gap : 16 px ; align-items : flex-start }
. chatbox { flex : 1 }
. prompt { display : flex ; gap : 8 px ; margin-bottom : 12 px }
textarea { flex : 1 ; resize : vertical ; min-height : 70 px ; background : var ( - - panel2 ) ; border : 1 px solid var ( - - line ) ; color : var ( - - txt ) ; border-radius : 10 px ; padding : 10 px ; font-size : 14 px ; font-family : inherit }
textarea : focus { outline : none ; border-color : var ( - - acc ) }
button . primary { background : var ( - - acc ) ; color : #fff ; border : none ; border-radius : 10 px ; padding : 10 px 20 px ; font-size : 14 px ; cursor : pointer }
button . primary : disabled { opacity : .5 ; cursor : not-allowed }
. card { background : var ( - - panel ) ; border : 1 px solid var ( - - line ) ; border-radius : 12 px ; padding : 16 px ; margin-bottom : 14 px }
. card h3 { font-size : 13 px ; color : var ( - - muted ) ; margin-bottom : 10 px ; font-weight : 600 ; letter-spacing : .5 px }
. resp { white-space : pre-wrap ; word-break : break-word ; font-size : 14 px }
. badges { display : flex ; flex-wrap : wrap ; gap : 6 px ; margin : 10 px 0 }
. b { font-size : 11 px ; padding : 2 px 8 px ; border-radius : 10 px }
. b . ok { background : rgba ( 35 , 196 , 109 , .15 ) ; color : var ( - - acc2 ) ; border : 1 px solid rgba ( 35 , 196 , 109 , .4 ) }
. b . fast { background : rgba ( 79 , 140 , 255 , .15 ) ; color : var ( - - acc ) ; border : 1 px solid rgba ( 79 , 140 , 255 , .4 ) }
. b . warn { background : rgba ( 245 , 166 , 35 , .15 ) ; color : var ( - - warn ) ; border : 1 px solid rgba ( 245 , 166 , 35 , .4 ) }
. b . err { background : rgba ( 255 , 92 , 92 , .15 ) ; color : var ( - - bad ) ; border : 1 px solid rgba ( 255 , 92 , 92 , .4 ) }
. b . plain { background : var ( - - panel2 ) ; color : var ( - - muted ) ; border : 1 px solid var ( - - line ) }
. chips { display : flex ; flex-wrap : wrap ; gap : 4 px ; margin-top : 8 px }
. chip { font-size : 11 px ; font-family : var ( - - mono ) ; background : var ( - - panel2 ) ; color : var ( - - muted ) ; padding : 2 px 7 px ; border-radius : 6 px ; border : 1 px solid var ( - - line ) }
. grid { display : grid ; grid-template-columns : repeat ( auto - fill , minmax ( 200 px , 1 fr ) ) ; gap : 12 px }
. stat { background : var ( - - panel ) ; border : 1 px solid var ( - - line ) ; border-radius : 12 px ; padding : 14 px }
. stat . v { font-size : 22 px ; font-weight : 700 }
. stat . k { font-size : 12 px ; color : var ( - - muted ) ; margin-top : 2 px }
. step { display : flex ; gap : 8 px ; align-items : baseline ; padding : 6 px 0 ; border-bottom : 1 px dashed var ( - - line ) }
. step : last-child { border : none }
. step . id { font-family : var ( - - mono ) ; color : var ( - - acc ) ; font-weight : 600 }
. step . st { font-size : 11 px ; margin-left : auto }
. done { color : var ( - - acc2 ) } . fail { color : var ( - - bad ) } . block { color : var ( - - warn ) }
details { background : var ( - - panel ) ; border : 1 px solid var ( - - line ) ; border-radius : 10 px ; padding : 10 px 12 px ; margin-bottom : 8 px }
summary { cursor : pointer ; font-weight : 600 ; font-size : 13 px }
pre . trace { background : var ( - - bg ) ; border : 1 px solid var ( - - line ) ; border-radius : 8 px ; padding : 10 px ; font-family : var ( - - mono ) ; font-size : 12 px ; overflow : auto ; color : var ( - - muted ) }
. kw { font-family : var ( - - mono ) ; color : var ( - - acc ) }
. row-btns { display : flex ; gap : 8 px ; margin-top : 8 px ; flex-wrap : wrap }
button . small { background : var ( - - panel2 ) ; border : 1 px solid var ( - - line ) ; color : var ( - - txt ) ; border-radius : 8 px ; padding : 6 px 12 px ; font-size : 12 px ; cursor : pointer }
button . small : hover { border-color : var ( - - acc ) }
input , select { background : var ( - - panel2 ) ; border : 1 px solid var ( - - line ) ; color : var ( - - txt ) ; border-radius : 8 px ; padding : 6 px 10 px ; font-size : 13 px }
. muted { color : var ( - - muted ) }
. history { max-height : 220 px ; overflow : auto ; margin-top : 8 px }
table { width : 100 % ; border-collapse : collapse ; font-size : 13 px }
th , td { text-align : left ; padding : 8 px ; border-bottom : 1 px solid var ( - - line ) }
th { color : var ( - - muted ) ; font-weight : 600 }
footer { padding : 8 px 20 px ; color : var ( - - muted ) ; font-size : 11 px ; border-top : 1 px 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 >
< / 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 >
< / 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 ( ) ; } ) ;
// ---- 健康状态 ----
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 ) ] ; }
$ ( "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 >