pipeline-sdlc/wwwroot/api/cockpit_chat_v2.dspy
2026-08-11 14:49:19 +08:00

123 lines
5.3 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# cockpit_chat_v2.dspy - SDLC Agent v2 (AgentExecutor 驱动)
# 替代 cockpit_chat.dspy使用 pipeline-core/pipeline-service v2 架构
import aiohttp
action = (params_kw or {}).get('action', 'send_message')
msg = ''
if action != 'send_message':
p = params_kw or {}
msg = (p.get('message_text') or '').strip()
if not msg:
inner = p.get('params', {})
if isinstance(inner, dict):
msg = (inner.get('prompt') or inner.get('message_text') or '').strip()
if msg:
action = 'send_message'
dbname = get_module_dbname('pipeline-sdlc')
# ── Widget helpers ──
def _w_text(t, css='agent-text'):
return {"widgettype":"Text","options":{"text":t,"halign":"left","css":css}}
def _w_progress(t):
return {"widgettype":"Text","options":{"text":t,"halign":"left","style":{"color":"#f0a040","fontStyle":"italic"}}}
def _w_md(t):
return {"widgettype":"MdWidget","options":{"text":t}}
def _w_card(title, body, kind="info"):
colors = {"success":"#4caf50","error":"#f44336","info":"#2196f3","warn":"#ff9800"}
color = colors.get(kind, colors["info"])
title_w = {"widgettype":"Text","options":{"text":title,"halign":"left","style":{"fontWeight":"bold","color":color,"marginBottom":"4px"}}}
body_w = body if isinstance(body, dict) else _w_text(str(body))
return {"widgettype":"VBox","subwidgets":[title_w, body_w],
"options":{"css":"agent-card","style":{"borderLeft":"3px solid "+color,"paddingLeft":"8px","marginBottom":"8px"}}}
if action == 'send_message':
prompt = msg
# ── 意图识别:停止类指令 ──
stop_keywords = ['停止', '取消', '停下', '终止', '停', 'stop', 'cancel', 'abort']
if any(kw == prompt.strip().lower() or kw in prompt.strip().lower() for kw in stop_keywords):
async def _stop_stream():
yield json.dumps(_w_md("已停止当前任务。"), ensure_ascii=False) + '\n'
return await stream_response(request, _stop_stream, 'text/plain; charset=utf-8')
uid = await get_user()
if not uid:
uid = 'user-01' # 测试兼容
# ── 加载上下文 ──
ctx = {'pid': '', 'name': ''}
async with DBPools().sqlorContext(dbname) as sor:
recs = await sor.sqlExe(
"SELECT current_project_id FROM pipeline_agent_settings WHERE user_id=${u}$",
{"u": uid})
if recs:
ctx['pid'] = getattr(recs[0], 'current_project_id', '') or ''
if ctx['pid']:
proj_recs = await sor.sqlExe(
"SELECT name FROM sd_projects WHERE id=${p}$", {"p": ctx['pid']})
if proj_recs:
ctx['name'] = getattr(proj_recs[0], 'name', '')
# ── 加载 v2 引擎 ──
from pipeline_core.agent_config import load_agent_config
from pipeline_service.agent_loop_v2 import AgentExecutor
config = await load_agent_config(project_id=ctx['pid'])
executor = AgentExecutor(
config=config,
project_id=ctx['pid'],
user_id=uid,
)
async def agent_stream():
# 项目上下文
if ctx['name']:
d = json.dumps(_w_progress("项目: " + ctx['name']), ensure_ascii=False) + '\n'
yield d
# 运行 AgentExecutor
async for chunk in executor.run(prompt):
data = json.loads(chunk)
t = data.get('type', '')
if t == 'progress':
d = json.dumps(_w_progress(data.get('message', '')), ensure_ascii=False) + '\n'
elif t == 'auto_tool':
d = json.dumps(_w_progress("🔄 " + data.get('message', '')), ensure_ascii=False) + '\n'
elif t == 'debug':
d = '' # skip debug in UI
continue
elif t == 'tool_call':
tool = data.get('tool', '')
d = json.dumps(_w_card("调用: " + tool, "参数: " + json.dumps(data.get('params', {}), ensure_ascii=False), "info"), ensure_ascii=False) + '\n'
elif t == 'tool_result':
tool = data.get('tool', '')
result = data.get('result', '')
good = not result.startswith('ERROR') and not result.startswith('未知工具')
d = json.dumps(_w_card(tool + " 结果", result[:600], "success" if good else "error"), ensure_ascii=False) + '\n'
elif t == 'reply':
d = json.dumps(_w_md(data.get('message', '')), ensure_ascii=False) + '\n'
elif t == 'error':
d = json.dumps(_w_card("错误", data.get('message', ''), "error"), ensure_ascii=False) + '\n'
else:
d = chunk # 透传
yield d
return await stream_response(request, agent_stream, 'text/plain; charset=utf-8')
elif action == 'list_messages':
uid = await get_user()
if not uid:
return json.dumps({"error":"请先登录"}, ensure_ascii=False)
async with DBPools().sqlorContext(dbname) as sor:
ms = await sor.sqlExe(
"SELECT role,content,created_at FROM pipeline_conversations ORDER BY created_at ASC LIMIT 100", {})
result = [{"role":getattr(m,'role',''),"content":getattr(m,'content','')} for m in (ms or [])]
return json.dumps({"success":True,"messages":result}, ensure_ascii=False, default=str)
else:
return json.dumps({"error":"Unknown: " + str(action)}, ensure_ascii=False)