diff --git a/wwwroot/api/cockpit_chat.dspy b/wwwroot/api/cockpit_chat.dspy index e879df9..c8dd2e6 100644 --- a/wwwroot/api/cockpit_chat.dspy +++ b/wwwroot/api/cockpit_chat.dspy @@ -1,4 +1,4 @@ -# cockpit_chat.dspy - SDLC Agent Loop v3.1 (streaming NDJSON) +# cockpit_chat.dspy - SDLC Agent Loop v3.1 import aiohttp, json, os action = (params_kw or {}).get('action', 'list_messages') @@ -26,7 +26,7 @@ TOOLS_TEXT = json.dumps(TOOLS, ensure_ascii=False) def _security_scan(text): t = (text or '').lower() - for kw in ('drop table','truncate','rm -rf','密钥','api_key'): + for kw in ('drop table','truncate','rm -rf','密钥','api_key'): if kw in t: return True, '危险操作' return False, '' @@ -95,7 +95,6 @@ def _parse(raw): except: pass return {"action":"reply","message":raw} -# Widget helpers def _w_text(t): return {"widgettype":"Text","options":{"text":t,"css":"agent-text"}} def _w_card(title, body, kind="success"): colors = {"success":"#10b981","error":"#ef4444","reply":"#6366f1","progress":"#f59e0b"} @@ -194,52 +193,49 @@ if action == 'send_message': uid = await get_user() org_id = await get_userorgid() or '0' + widgets = [] - async def agent_stream(): - async with DBPools().sqlorContext(dbname) as sor: - ctx = await _load_ctx(sor, uid) - model = await _sel_model(sor, user_model_id) - if not model: - yield json.dumps({"type":"widget","widget":_w_card("❌ 错误",_w_text("无可用LLM"),"error")},ensure_ascii=False)+'\n' - return + async with DBPools().sqlorContext(dbname) as sor: + ctx = await _load_ctx(sor, uid) + model = await _sel_model(sor, user_model_id) + if not model: return json.dumps({"success":True,"agent_reply":"❌ 没有可用的LLM模型配置"},ensure_ascii=False) - repos_str = ', '.join([r['n'] for r in ctx['repos']]) or '无' - env_text = f"项目: {ctx['pname'] or '未选择'}\n仓库: {repos_str}" - system = AGENT_PROMPT.replace('__TOOLS__',TOOLS_TEXT).replace('__ENV__',env_text) - msgs = [{"role":"system","content":system}] + repos_str = ', '.join([r['n'] for r in ctx['repos']]) or '无' + env_text = f"项目: {ctx['pname'] or '未选择'}\n仓库: {repos_str}" + system = AGENT_PROMPT.replace('__TOOLS__',TOOLS_TEXT).replace('__ENV__',env_text) + msgs = [{"role":"system","content":system}] - history = await sor.sqlExe("SELECT role,content FROM pipeline_conversations ORDER BY created_at ASC LIMIT 20",{}) - for h in (history or []): - msgs.append({"role":'user' if getattr(h,'role','')=='user' else 'assistant',"content":getattr(h,'content','')}) - msgs.append({"role":"user","content":message_text}) + history = await sor.sqlExe("SELECT role,content FROM pipeline_conversations ORDER BY created_at ASC LIMIT 20",{}) + for h in (history or []): + msgs.append({"role":'user' if getattr(h,'role','')=='user' else 'assistant',"content":getattr(h,'content','')}) + msgs.append({"role":"user","content":message_text}) - await sor.C('pipeline_conversations',{'id':getID(),'role':'user','content':message_text,'msg_type':'text','org_id':org_id,'created_by':uid}) + await sor.C('pipeline_conversations',{'id':getID(),'role':'user','content':message_text,'msg_type':'text','org_id':org_id,'created_by':uid}) - agent_reply = '' - for turn in range(10): - raw = await _call_llm(model, msgs, 0.4) - act = _parse(raw) - if act.get('action') == 'reply': - agent_reply = act.get('message', raw); break - if act.get('action') == 'tool_call': - tool = act.get('tool','') - label = _tool_labels.get(tool,tool) - yield json.dumps({"type":"widget","widget":_w_progress(f"🔄 {label}")},ensure_ascii=False)+'\n' - result = await _exec_tool(sor, tool, act.get('params',{}), ctx, uid, org_id) - ok = result.startswith("OK:") - yield json.dumps({"type":"widget","widget":_w_card( - f"{'✅' if ok else '❌'} {label}", _w_text(result), - "success" if ok else "error")},ensure_ascii=False)+'\n' - msgs.append({"role":"assistant","content":raw}) - msgs.append({"role":"user","content":f"工具 {tool} 结果:\n{result}"}) - continue - agent_reply = raw; break + agent_reply = '' + for turn in range(10): + raw = await _call_llm(model, msgs, 0.4) + act = _parse(raw) + if act.get('action') == 'reply': + agent_reply = act.get('message', raw); break + if act.get('action') == 'tool_call': + tool = act.get('tool','') + label = _tool_labels.get(tool,tool) + widgets.append(_w_progress(f"🔄 {label}")) + result = await _exec_tool(sor, tool, act.get('params',{}), ctx, uid, org_id) + ok = result.startswith("OK:") + widgets.append(_w_card(f"{'✅' if ok else '❌'} {label}", _w_text(result), "success" if ok else "error")) + msgs.append({"role":"assistant","content":raw}) + msgs.append({"role":"user","content":f"工具 {tool} 结果:\n{result}"}) + continue + agent_reply = raw; break - if not agent_reply: agent_reply = "处理超时" - await sor.C('pipeline_conversations',{'id':getID(),'role':'agent','content':agent_reply,'msg_type':'text','org_id':org_id,'created_by':'system'}) - yield json.dumps({"type":"widget","widget":_w_card("🤖 驾驶舱 Agent",_w_text(agent_reply),"reply")},ensure_ascii=False)+'\n' + if not agent_reply: agent_reply = "处理超时,请简化需求。" + widgets.append(_w_card("🤖 驾驶舱 Agent", _w_text(agent_reply), "reply")) - return await stream_response(request, agent_stream, 'application/x-ndjson') + await sor.C('pipeline_conversations',{'id':getID(),'role':'agent','content':agent_reply,'msg_type':'text','org_id':org_id,'created_by':'system'}) + + return json.dumps({"success":True,"agent_reply":agent_reply,"widgets":widgets},ensure_ascii=False) elif action == 'list_messages': async with DBPools().sqlorContext(dbname) as sor: