feat: Phase 1 — intent recognition, guided conversation, context bar
This commit is contained in:
parent
84df91d9eb
commit
97e2c6b2f4
@ -47,12 +47,57 @@ async def _load_agent_settings(sor, uid):
|
||||
}
|
||||
|
||||
|
||||
async def _load_context(sor, uid):
|
||||
"""Load session context: current project/iteration."""
|
||||
settings = await _load_agent_settings(sor, uid)
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT current_project_id, current_iteration_id FROM sd_agent_settings WHERE user_id=${uid}$",
|
||||
{"uid": uid}
|
||||
)
|
||||
ctx = {'project_id': '', 'iteration_id': '', 'project_name': '', 'iteration_name': ''}
|
||||
if recs:
|
||||
r = recs[0]
|
||||
ctx['project_id'] = getattr(r, 'current_project_id', '') or ''
|
||||
ctx['iteration_id'] = getattr(r, 'current_iteration_id', '') or ''
|
||||
if ctx['project_id']:
|
||||
projs = await sor.sqlExe(
|
||||
"SELECT name FROM sd_projects WHERE id=${pid}$", {"pid": ctx['project_id']}
|
||||
)
|
||||
if projs:
|
||||
ctx['project_name'] = getattr(projs[0], 'name', '')
|
||||
if ctx['iteration_id']:
|
||||
iters = await sor.sqlExe(
|
||||
"SELECT iteration_name FROM sd_iterations WHERE id=${iid}$", {"iid": ctx['iteration_id']}
|
||||
)
|
||||
if iters:
|
||||
ctx['iteration_name'] = getattr(iters[0], 'iteration_name', '')
|
||||
return ctx
|
||||
|
||||
|
||||
async def _save_context(sor, uid, project_id, iteration_id):
|
||||
"""Save session context."""
|
||||
existing = await sor.sqlExe(
|
||||
"SELECT id FROM sd_agent_settings WHERE user_id=${uid}$", {"uid": uid}
|
||||
)
|
||||
if existing:
|
||||
await sor.sqlExe(
|
||||
"UPDATE sd_agent_settings SET current_project_id=${pid}$, current_iteration_id=${iid}$ WHERE user_id=${uid}$",
|
||||
{"pid": project_id or '', "iid": iteration_id or '', "uid": uid}
|
||||
)
|
||||
else:
|
||||
await sor.C('sd_agent_settings', {
|
||||
'id': getID(), 'user_id': uid,
|
||||
'current_project_id': project_id or '',
|
||||
'current_iteration_id': iteration_id or '',
|
||||
})
|
||||
|
||||
|
||||
async def _select_model(sor, preferred_llm_id, has_files):
|
||||
"""Select best model: prefer user choice, then multimodal if files, else first active text."""
|
||||
# If user has preferred model, use it
|
||||
# If user has preferred model, use it (match by id or name)
|
||||
if preferred_llm_id:
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, name, provider, model_id, api_base, api_key, capabilities FROM llm WHERE id=${lid}$ AND status='active'",
|
||||
"SELECT id, name, provider, model_id, api_base, api_key, capabilities FROM llm WHERE (id=${lid}$ OR name=${lid}$) AND status='active'",
|
||||
{"lid": preferred_llm_id}
|
||||
)
|
||||
if recs:
|
||||
@ -174,88 +219,206 @@ async def _call_llm(model_info, messages, temperature):
|
||||
return content
|
||||
|
||||
|
||||
INTENT_PROMPT = """你是一个开发产线意图分类器。分析用户输入,返回 JSON。
|
||||
|
||||
意图类型:
|
||||
- new_project: 创建新项目
|
||||
- select_project: 切换到已有项目
|
||||
- new_iteration: 在当前项目下创建新迭代
|
||||
- new_task: 提交开发任务(需关联项目/迭代)
|
||||
- add_bug: 报告Bug
|
||||
- query: 查询当前状态
|
||||
- chat: 开发相关的一般对话
|
||||
- out_of_scope: 完全无关软件开发
|
||||
|
||||
当前上下文:项目={ctx},迭代={iter}
|
||||
|
||||
返回纯JSON(不要markdown包裹):
|
||||
{"intent":"...","confidence":0.8,"project_name":"...","iteration_name":"...","title":"...","description":"...","missing_info":"..."}"""
|
||||
|
||||
|
||||
async def _classify_intent(model_info, message, ctx, history_msgs):
|
||||
"""Classify user intent using LLM."""
|
||||
ctx_str = ctx.get('project_name', '') or '无'
|
||||
iter_str = ctx.get('iteration_name', '') or '无'
|
||||
prompt = INTENT_PROMPT.replace('{ctx}', ctx_str).replace('{iter}', iter_str)
|
||||
msgs = [{"role": "system", "content": prompt}]
|
||||
for h in history_msgs[-4:]:
|
||||
msgs.append(h)
|
||||
msgs.append({"role": "user", "content": message})
|
||||
raw = await _call_llm(model_info, msgs, 0.2)
|
||||
raw = raw.strip()
|
||||
if raw.startswith('```'):
|
||||
raw = raw.split('\n', 1)[1].rsplit('```', 1)[0]
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except Exception:
|
||||
return {"intent": "chat", "confidence": 0.5, "missing_info": ""}
|
||||
|
||||
|
||||
async def _find_project(sor, name, org_id):
|
||||
"""Find project by name."""
|
||||
if not name: return None
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, name FROM sd_projects WHERE name=${name}$ AND org_id=${oid}$",
|
||||
{"name": name, "oid": org_id})
|
||||
return recs[0] if recs else None
|
||||
|
||||
|
||||
SCOPE_GUIDE = """我可以帮你:
|
||||
📁 项目管理 — "创建电商平台项目" / "切换到XXX项目"
|
||||
🔄 迭代管理 — "创建Sprint3" / "查看迭代进度"
|
||||
📝 提交任务 — "设计用户表结构" / "实现登录API"
|
||||
🐛 Bug管理 — "登录页报500" / "我的Bug列表"
|
||||
📊 查询 — "当前项目进度" / "有哪些迭代"
|
||||
请描述你的需求。"""
|
||||
|
||||
|
||||
# ==================== ACTION HANDLERS ====================
|
||||
|
||||
if action == 'send_message':
|
||||
iteration_id = (params_kw or {}).get('iteration_id', '')
|
||||
task_id = (params_kw or {}).get('task_id', '')
|
||||
message_text = (params_kw or {}).get('message_text', '').strip()
|
||||
model_id = (params_kw or {}).get('model_id', '')
|
||||
user_model_id = (params_kw or {}).get('model_id', '')
|
||||
file_paths_raw = (params_kw or {}).get('file_paths', '[]')
|
||||
debug(f'send_message: model_id={model_id} iteration_id={iteration_id} msg_len={len(message_text)}')
|
||||
debug(f'send_message: model_id={user_model_id} iteration_id={iteration_id} msg_len={len(message_text)}')
|
||||
|
||||
if not message_text:
|
||||
return json.dumps({"error": "message_text is required"}, ensure_ascii=False)
|
||||
|
||||
# Parse file paths
|
||||
has_files = False
|
||||
try:
|
||||
fps = json.loads(file_paths_raw)
|
||||
has_files = len(fps) > 0
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
uid = await get_user()
|
||||
org_id = await get_userorgid() or '0'
|
||||
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
# 1. Save user message
|
||||
msg_id = getID()
|
||||
await sor.C('sd_conversations', {
|
||||
'id': msg_id,
|
||||
'iteration_id': iteration_id or '',
|
||||
'task_id': task_id or '',
|
||||
'step_name': '',
|
||||
'role': 'user',
|
||||
'content': message_text,
|
||||
'attachments': file_paths_raw,
|
||||
'msg_type': 'text',
|
||||
'org_id': '0',
|
||||
'created_by': uid
|
||||
})
|
||||
|
||||
# 2. Load agent settings
|
||||
# 1. Load context and model
|
||||
ctx = await _load_context(sor, uid)
|
||||
settings = await _load_agent_settings(sor, uid)
|
||||
|
||||
# 3. Select model (preferred → auto by file type → first active)
|
||||
selected_llm_id = model_id or settings['llm_id']
|
||||
model_info = await _select_model(sor, selected_llm_id, has_files)
|
||||
selected_llm_id = user_model_id or settings['llm_id']
|
||||
model_info = await _select_model(sor, selected_llm_id, False)
|
||||
if not model_info:
|
||||
return json.dumps({"error": "No active LLM model configured"}, ensure_ascii=False)
|
||||
|
||||
# 4. Build context
|
||||
messages = await _build_context(
|
||||
sor, iteration_id, task_id,
|
||||
settings['max_context'], settings['system_prompt']
|
||||
# 2. Classify intent
|
||||
history = await sor.sqlExe(
|
||||
"SELECT role, content FROM sd_conversations WHERE iteration_id=${iid}$ OR iteration_id='' ORDER BY created_at DESC LIMIT 4",
|
||||
{"iid": iteration_id or ctx.get('iteration_id', '')}
|
||||
)
|
||||
# Append current user message
|
||||
messages.append({"role": "user", "content": message_text})
|
||||
history_msgs = []
|
||||
for h in reversed(history):
|
||||
role = 'user' if getattr(h, 'role', '') == 'user' else 'assistant'
|
||||
history_msgs.append({"role": role, "content": getattr(h, 'content', '')})
|
||||
|
||||
intent = await _classify_intent(model_info, message_text, ctx, history_msgs)
|
||||
debug(f'intent: {intent}')
|
||||
|
||||
# 5. Call LLM
|
||||
try:
|
||||
agent_reply = await _call_llm(model_info, messages, settings['temperature'])
|
||||
except Exception as e:
|
||||
agent_reply = f"抱歉,模型调用失败: {str(e)[:200]}"
|
||||
# 3. Route by intent
|
||||
intent_type = intent.get('intent', 'chat')
|
||||
confidence = intent.get('confidence', 0.5)
|
||||
|
||||
# 6. Save agent response
|
||||
if intent_type == 'out_of_scope' or (intent_type == 'chat' and confidence < 0.6 and not ctx['project_id']):
|
||||
agent_reply = SCOPE_GUIDE
|
||||
elif intent.get('missing_info') and confidence < 0.7:
|
||||
agent_reply = f"让我确认一下:{intent.get('missing_info', '请提供更多信息')}"
|
||||
elif intent_type == 'new_project':
|
||||
pname = intent.get('project_name', '') or message_text[:50]
|
||||
proj = await _find_project(sor, pname, org_id)
|
||||
if proj:
|
||||
agent_reply = f"项目「{pname}」已存在。已切换到该项目。"
|
||||
await _save_context(sor, uid, proj.id, '')
|
||||
else:
|
||||
pid = getID()
|
||||
await sor.C('sd_projects', {
|
||||
'id': pid, 'name': pname, 'description': intent.get('description', ''),
|
||||
'project_type': 'software', 'org_id': org_id, 'created_by': uid, 'status': 'active'
|
||||
})
|
||||
# Auto-create default iteration
|
||||
iid = getID()
|
||||
await sor.C('sd_iterations', {
|
||||
'id': iid, 'project_id': pid, 'iteration_name': '默认迭代',
|
||||
'iteration_type': 'sprint', 'org_id': org_id, 'created_by': uid, 'status': 'active'
|
||||
})
|
||||
await _save_context(sor, uid, pid, iid)
|
||||
agent_reply = f"✅ 项目「{pname}」已创建,默认迭代已就绪。现在可以提交任务了。"
|
||||
elif intent_type == 'select_project':
|
||||
pname = intent.get('project_name', '')
|
||||
proj = await _find_project(sor, pname, org_id)
|
||||
if proj:
|
||||
await _save_context(sor, uid, proj.id, ctx['iteration_id'])
|
||||
agent_reply = f"已切换到项目「{pname}」。"
|
||||
else:
|
||||
agent_reply = f"未找到项目「{pname}」。请先创建项目后再切换。"
|
||||
elif intent_type == 'new_task':
|
||||
pid = ctx['project_id']
|
||||
iid = intent.get('iteration_name', '') or ctx['iteration_id']
|
||||
if not pid:
|
||||
agent_reply = "请先指定项目。「创建XXX项目」或「切换到XXX项目」"
|
||||
elif not iid:
|
||||
agent_reply = "请指定迭代。「创建Sprint1」或「切换到XXX迭代」"
|
||||
else:
|
||||
title = intent.get('title', '') or message_text[:100]
|
||||
task_params = {'description': intent.get('description', ''), 'input_text': message_text}
|
||||
try:
|
||||
result = await pipeline_submit(org_id, 'sdlc_general', uid, title, task_params)
|
||||
rd = json.loads(result)
|
||||
if rd.get('success'):
|
||||
task_id = rd.get('task_id', '')
|
||||
await _save_context(sor, uid, pid, iid)
|
||||
agent_reply = f"✅ 任务「{title}」已提交({task_id}),产线开始执行。"
|
||||
else:
|
||||
agent_reply = f"任务提交失败:{rd.get('message', '未知错误')}"
|
||||
except Exception as e:
|
||||
agent_reply = f"任务提交失败:{str(e)[:200]}"
|
||||
elif intent_type == 'add_bug':
|
||||
pid = ctx['project_id']
|
||||
iid = ctx['iteration_id']
|
||||
if not pid:
|
||||
agent_reply = "请先指定项目后再报告Bug。"
|
||||
else:
|
||||
bid = getID()
|
||||
await sor.C('sd_bugs', {
|
||||
'id': bid, 'iteration_id': iid or '', 'title': intent.get('title', '') or message_text[:100],
|
||||
'description': intent.get('description', ''), 'severity': 'major', 'priority': 'P1',
|
||||
'status': 'open', 'reporter_type': 'human', 'reporter_id': uid, 'created_at': curDateString()
|
||||
})
|
||||
agent_reply = f"🐛 Bug已记录({bid}):{message_text[:100]}"
|
||||
elif intent_type == 'query':
|
||||
ctx_info = []
|
||||
if ctx['project_name']:
|
||||
ctx_info.append(f"当前项目:{ctx['project_name']}")
|
||||
if ctx['iteration_name']:
|
||||
ctx_info.append(f"当前迭代:{ctx['iteration_name']}")
|
||||
if ctx_info:
|
||||
agent_reply = '\n'.join(ctx_info) + '\n\n请描述具体想查询什么(如:任务列表、Bug列表等)'
|
||||
else:
|
||||
agent_reply = "当前未选择项目。请先「创建XXX项目」或「切换到XXX项目」。"
|
||||
else:
|
||||
# chat: general conversation
|
||||
messages = await _build_context(sor, iteration_id or ctx['iteration_id'], '',
|
||||
settings['max_context'], settings['system_prompt'])
|
||||
messages.append({"role": "user", "content": message_text})
|
||||
try:
|
||||
agent_reply = await _call_llm(model_info, messages, settings['temperature'])
|
||||
except Exception as e:
|
||||
agent_reply = f"抱歉,模型调用失败: {str(e)[:200]}"
|
||||
|
||||
# 4. Save conversation
|
||||
msg_id = getID()
|
||||
await sor.C('sd_conversations', {
|
||||
'id': msg_id, 'iteration_id': iteration_id or ctx.get('iteration_id', ''),
|
||||
'task_id': '', 'step_name': '', 'role': 'user', 'content': message_text,
|
||||
'attachments': file_paths_raw, 'msg_type': 'text', 'org_id': org_id, 'created_by': uid
|
||||
})
|
||||
agent_msg_id = getID()
|
||||
await sor.C('sd_conversations', {
|
||||
'id': agent_msg_id,
|
||||
'iteration_id': iteration_id or '',
|
||||
'task_id': task_id or '',
|
||||
'step_name': '',
|
||||
'role': 'agent',
|
||||
'content': agent_reply,
|
||||
'attachments': '[]',
|
||||
'msg_type': 'text',
|
||||
'org_id': '0',
|
||||
'created_by': 'system'
|
||||
'id': agent_msg_id, 'iteration_id': iteration_id or ctx.get('iteration_id', ''),
|
||||
'task_id': '', 'step_name': '', 'role': 'agent', 'content': agent_reply,
|
||||
'attachments': '[]', 'msg_type': 'text', 'org_id': org_id, 'created_by': 'system'
|
||||
})
|
||||
|
||||
return json.dumps({
|
||||
"success": True,
|
||||
"message_id": msg_id,
|
||||
"agent_reply": agent_reply,
|
||||
"model_used": model_info.name,
|
||||
"success": True, "message_id": msg_id, "agent_reply": agent_reply,
|
||||
"model_used": model_info.name, "intent": intent_type,
|
||||
"context": {"project_name": ctx.get('project_name', ''), "iteration_name": ctx.get('iteration_name', '')}
|
||||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
|
||||
@ -19,13 +19,13 @@
|
||||
{
|
||||
"widgettype": "Title2",
|
||||
"options": {
|
||||
"text": "\u5f00\u53d1\u4ea7\u7ebf\u9a7e\u9a76\u8231"
|
||||
"text": "开发产线驾驶舱"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "AI\u9a71\u52a8\u7684\u5bf9\u8bdd\u5f0f\u5f00\u53d1",
|
||||
"text": "AI驱动的对话式开发",
|
||||
"cfontsize": 0.9,
|
||||
"color": "#94a3b8"
|
||||
}
|
||||
@ -44,7 +44,7 @@
|
||||
"name": "model_id",
|
||||
"uitype": "code",
|
||||
"label": "",
|
||||
"placeholder": "\u9009\u62e9\u6a21\u578b",
|
||||
"placeholder": "选择模型",
|
||||
"cwidth": 12,
|
||||
"dataurl": "/pipeline-sdlc/api/cockpit_model_options.dspy",
|
||||
"valueField": "model_id",
|
||||
@ -61,7 +61,7 @@
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"name": "model_config",
|
||||
"label": "\u6a21\u578b\u914d\u7f6e",
|
||||
"label": "模型配置",
|
||||
"css": "small"
|
||||
},
|
||||
"binds": [
|
||||
@ -70,7 +70,7 @@
|
||||
"event": "click",
|
||||
"actiontype": "script",
|
||||
"target": "self",
|
||||
"script": "var pw=new bricks.PopupWindow({title:'\u6a21\u578b\u914d\u7f6e',cwidth:36,cheight:26,auto_open:true});bricks.widgetBuild({widgettype:'urlwidget',options:{url:'/pipeline_core/llm/index.ui',method:'GET'}},pw.content_w).then(function(w){if(w)pw.content_w.add_widget(w);});"
|
||||
"script": "var pw=new bricks.PopupWindow({title:'模型配置',cwidth:36,cheight:26,auto_open:true});bricks.widgetBuild({widgettype:'urlwidget',options:{url:'/pipeline_core/llm/index.ui',method:'GET'}},pw.content_w).then(function(w){if(w)pw.content_w.add_widget(w);});"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -95,6 +95,36 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"id": "context_bar",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"padding": "4px 24px 4px 24px",
|
||||
"cheight": 2.5,
|
||||
"gap": "12px",
|
||||
"alignItems": "center",
|
||||
"bgcolor": "#e8f0fe"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "📁 当前项目:--",
|
||||
"cfontsize": 0.85,
|
||||
"color": "#1e40af"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "🔄 当前迭代:--",
|
||||
"cfontsize": 0.85,
|
||||
"color": "#1e40af"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
@ -130,7 +160,7 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "\u9009\u62e9\u4e00\u4e2a\u8fed\u4ee3\u540e\uff0c\u5728\u4e0b\u65b9\u8f93\u5165\u4f60\u7684\u9700\u6c42\u3002",
|
||||
"text": "选择一个迭代后,在下方输入你的需求。",
|
||||
"cfontsize": 1,
|
||||
"color": "#64748b"
|
||||
}
|
||||
@ -138,7 +168,7 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "Agent \u5c06\u81ea\u52a8\u5206\u6790\u9700\u6c42\u3001\u751f\u6210\u8bbe\u8ba1\u3001\u9a71\u52a8\u4ea7\u7ebf\u6267\u884c\u3002",
|
||||
"text": "Agent 将自动分析需求、生成设计、驱动产线执行。",
|
||||
"cfontsize": 0.85,
|
||||
"color": "#94a3b8"
|
||||
}
|
||||
@ -163,7 +193,7 @@
|
||||
"event": "inputed",
|
||||
"actiontype": "script",
|
||||
"target": "self",
|
||||
"script": "var p=params.prompt;var files=params.add_files||[];var iid='';try{var iter=bricks.getWidgetById('current_iteration_id',bricks.app);if(iter)iid=iter.options.value||'';}catch(e){}var mid='';try{var ms=bricks.getWidgetById('model_selector',bricks.app);if(ms){var f=ms.form_element;if(f){var el=f.querySelector('[name=model_id]')||f.querySelector('select');if(el)mid=el.value||'';}}}catch(e){}var chat=bricks.getWidgetById('chat_scroll',bricks.app);var at=null;if(chat){var ub=new bricks.HBox({width:'100%'});var um=new bricks.VBox({width:'85%',alignSelf:'flex-end',bgcolor:'#dbeafe',borderRadius:'12px',padding:'12px 16px',marginBottom:'10px',gap:'4px'});um.add_widget(new bricks.Text({text:'\\u4f60',cfontsize:0.75,color:'#2563eb',fontWeight:'bold'}));um.add_widget(new bricks.Text({text:p,cfontsize:0.95,color:'#1e293b',whiteSpace:'pre-wrap'}));ub.add_widget(new bricks.VBox({css:'filler'}));ub.add_widget(um);ub.add_widget(new bricks.Svg({rate:2,url:bricks_resource('imgs/chat-user.svg')}));chat.add_widget(ub);var ab=new bricks.HBox({width:'100%'});var am=new bricks.VBox({width:'85%',alignSelf:'flex-start',bgcolor:'#e8f0fe',borderRadius:'12px',padding:'12px 16px',marginBottom:'10px',gap:'4px'});am.add_widget(new bricks.Text({text:'Agent',cfontsize:0.75,color:'#3b82f6',fontWeight:'bold'}));at=new bricks.Text({text:'\\u6b63\\u5728\\u5206\\u6790\\u9700\\u6c42...',cfontsize:0.85,color:'#64748b'});am.add_widget(at);ab.add_widget(new bricks.Svg({rate:2,url:bricks_resource('imgs/llm.svg')}));ab.add_widget(am);ab.add_widget(new bricks.VBox({css:'filler'}));chat.add_widget(ab);}var body='message_text='+encodeURIComponent(p)+'&iteration_id='+encodeURIComponent(iid)+'&model_id='+encodeURIComponent(mid)+'&action=send_message&file_paths='+encodeURIComponent(JSON.stringify(files.map(function(f){return f.name||f})));fetch('/pipeline-sdlc/api/cockpit_chat.dspy',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:body}).then(function(r){return r.json()}).then(function(r){if(r.success){if(at)at.set_text(r.agent_reply||'\\u5df2\\u5904\\u7406');var s=bricks.getWidgetById('stats_row',bricks.app);if(s)s.render({});}else{if(at)at.set_text('\\u9519\\u8bef: '+(r.error||'\\u672a\\u77e5\\u9519\\u8bef'));}});"
|
||||
"script": "var p=params.prompt;var files=params.add_files||[];var iid='';try{var iter=bricks.getWidgetById('current_iteration_id',bricks.app);if(iter)iid=iter.options.value||'';}catch(e){}var mid='';try{var el=document.querySelector('#model_selector select')||document.querySelector('select');if(el)mid=el.value||'';}catch(e){}var chat=bricks.getWidgetById('chat_scroll',bricks.app);var at=null;if(chat){var ub=new bricks.HBox({width:'100%'});var um=new bricks.VBox({width:'85%',alignSelf:'flex-end',bgcolor:'#dbeafe',borderRadius:'12px',padding:'12px 16px',marginBottom:'10px',gap:'4px'});um.add_widget(new bricks.Text({text:'\\u4f60',cfontsize:0.75,color:'#2563eb',fontWeight:'bold'}));um.add_widget(new bricks.Text({text:p,cfontsize:0.95,color:'#1e293b',whiteSpace:'pre-wrap'}));ub.add_widget(new bricks.VBox({css:'filler'}));ub.add_widget(um);ub.add_widget(new bricks.Svg({rate:2,url:bricks_resource('imgs/chat-user.svg')}));chat.add_widget(ub);var ab=new bricks.HBox({width:'100%'});var am=new bricks.VBox({width:'85%',alignSelf:'flex-start',bgcolor:'#e8f0fe',borderRadius:'12px',padding:'12px 16px',marginBottom:'10px',gap:'4px'});am.add_widget(new bricks.Text({text:'Agent',cfontsize:0.75,color:'#3b82f6',fontWeight:'bold'}));at=new bricks.Text({text:'\\u6b63\\u5728\\u5206\\u6790\\u9700\\u6c42...',cfontsize:0.85,color:'#64748b'});am.add_widget(at);ab.add_widget(new bricks.Svg({rate:2,url:bricks_resource('imgs/llm.svg')}));ab.add_widget(am);ab.add_widget(new bricks.VBox({css:'filler'}));chat.add_widget(ab);}var body='message_text='+encodeURIComponent(p)+'&iteration_id='+encodeURIComponent(iid)+'&model_id='+encodeURIComponent(mid)+'&action=send_message&file_paths='+encodeURIComponent(JSON.stringify(files.map(function(f){return f.name||f})));fetch('/pipeline-sdlc/api/cockpit_chat.dspy',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:body}).then(function(r){return r.json()}).then(function(r){if(r.success){if(at)at.set_text(r.agent_reply||'\\u5df2\\u5904\\u7406');var s=bricks.getWidgetById('stats_row',bricks.app);if(s)s.render({});}else{if(at)at.set_text('\\u9519\\u8bef: '+(r.error||'\\u672a\\u77e5\\u9519\\u8bef'));}});"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user