File size: 5,021 Bytes
adf912b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
"""决策台 Web UI(零依赖,stdlib http.server):粘贴任意文本,自定义问题,实时看概率条。
用法: python apps/web_console.py [port=7860]   然后打开 http://127.0.0.1:7860
"""
import sys, json, time
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from common import get_agent

HTML = r"""<!doctype html><meta charset=utf-8><title>Laya 决策台</title>
<style>
body{font:14px system-ui;margin:0;background:#0f1115;color:#e6e6e6;display:grid;grid-template-columns:1fr 1fr;gap:16px;padding:16px;height:100vh;box-sizing:border-box}
textarea{width:100%;box-sizing:border-box;background:#1a1d24;color:#eee;border:1px solid #333;border-radius:6px;padding:8px;font:13px ui-monospace,monospace}
button{background:#4f8cff;color:#fff;border:0;padding:8px 16px;border-radius:6px;font-size:14px;cursor:pointer}
select{background:#1a1d24;color:#eee;border:1px solid #333;padding:6px;border-radius:6px}
.q{margin:10px 0;padding:10px;background:#171a21;border-radius:8px}.q h3{margin:0 0 6px;font-size:14px}
.row{display:flex;align-items:center;gap:8px;margin:2px 0}.bar{height:10px;background:#4f8cff;border-radius:3px}
.lab{width:120px;text-align:right;color:#aaa}.p{width:40px;color:#aaa}
#out{overflow:auto}.meta{color:#888;font-size:12px}
</style>
<div><h2>Laya 决策台 <span class=meta>单次前向传播 · 不生成文本 · 校准概率</span></h2>
<label>模型 <select id=model><option value=multilingual>multilingual (100+ 语言)</option><option value=english>english</option><option value=typed>typed-decisions</option></select></label>
<p><b>状态 (JSON 或纯文本)</b><br><textarea id=state rows=8>{"subject": "登录一直失败", "body": "从昨天开始整个团队都登录不了后台,报 502,我们的上线被卡住了。再不解决就退订。"}</textarea></p>
<p><b>问题 (JSON)</b><br><textarea id=questions rows=16>{
  "department": {"type": "choice", "instructions": "Which team should handle this?",
    "criteria": {"billing": "invoices, refunds", "technical": "bugs, outages, login", "sales": "pricing, plans"}},
  "urgency": {"type": "score", "instructions": "How urgent is this?", "criteria": ["not urgent", "soon", "blocking"]},
  "churn_risk": {"type": "noul", "instructions": "Does the user threaten to cancel?"}
}</textarea></p>
<button onclick=run()>预测 (Ctrl+Enter)</button> <span id=t class=meta></span></div>
<div id=out></div>
<script>
async function run(){
 const body={model:model.value,state:state.value,questions:questions.value};
 t.textContent='...';
 const r=await fetch('/predict',{method:'POST',body:JSON.stringify(body)});const j=await r.json();
 if(j.error){out.innerHTML='<pre style="color:#f66">'+j.error+'</pre>';t.textContent='';return}
 t.textContent=j.ms.toFixed(0)+' ms';
 let h='';for(const [k,a] of Object.entries(j.answers)){
  h+='<div class=q><h3>'+k+' → <span style=color:#8fd>'+(a.choice??(a.score!==undefined?'score '+a.score.toFixed(2):'')??'')+(a.noul!==undefined?'P(true)='+a.noul.toFixed(2):'')+'</span>'+(a.confidence!==undefined?' <span class=meta>conf '+a.confidence.toFixed(2)+'</span>':'')+'</h3>';
  let d=a.probabilities||(a.noul!==undefined?{true:a.noul,false:1-a.noul}:{});if(a.legend)d=Object.fromEntries(Object.entries(d).map(([k,v])=>[k+' '+a.legend[k],v]));
  if(Array.isArray(d))d=Object.fromEntries(d.map((v,i)=>[i,v]));
  for(const [l,p] of Object.entries(d).sort((x,y)=>y[1]-x[1]))h+='<div class=row><span class=lab>'+l+'</span><div class=bar style=width:'+(p*300)+'px></div><span class=p>'+p.toFixed(2)+'</span></div>';
  h+='</div>'}
 h+='<pre class=meta>'+JSON.stringify(j.raw,null,1)+'</pre>';out.innerHTML=h}
document.addEventListener('keydown',e=>{if(e.ctrlKey&&e.key=='Enter')run()});
</script>"""

class H(BaseHTTPRequestHandler):
    def log_message(self, *a): pass
    def do_GET(self):
        self.send_response(200); self.send_header("Content-Type", "text/html; charset=utf-8"); self.end_headers()
        self.wfile.write(HTML.encode())
    def do_POST(self):
        n = int(self.headers.get("Content-Length", 0)); req = json.loads(self.rfile.read(n))
        try:
            st = req["state"].strip()
            try: st = json.loads(st)
            except Exception: st = {"text": st}
            qs = json.loads(req["questions"])
            agent = get_agent(req.get("model", "multilingual"))
            t = time.time(); r = agent.predict(st, qs); ms = (time.time() - t) * 1000
            body = {"answers": r["answers"], "raw": r, "ms": ms}
        except Exception as e:
            body = {"error": f"{type(e).__name__}: {e}"}
        data = json.dumps(body, ensure_ascii=False, default=str).encode()
        self.send_response(200); self.send_header("Content-Type", "application/json"); self.end_headers(); self.wfile.write(data)

if __name__ == "__main__":
    port = int(sys.argv[1]) if len(sys.argv) > 1 else 7860
    get_agent("multilingual")
    print(f"open http://127.0.0.1:{port}")
    ThreadingHTTPServer(("127.0.0.1", port), H).serve_forever()