chore(align): sync Git with running container as authoritative source
Phase 0 SSE 流式聊天 landed in the running container and captured in Git so
the three sources (container := image := Git) agree.
Phase 0 — SSE 流式聊天(Web 端,IM bot 路径保持不变、共用引擎):
- app/api/chat.py: 新增独立 SSE 端点
POST /app/api/chat/conversations/{conv_id}/messages/stream
基于 asyncio.Queue + create_task 将 _send_message_core 桥接为
text/event-stream 生成器,吐出 delta / reasoning / done / error 事件
- app/api/chat_service.py: _send_message_core 增加 stream + on_event 参数;
普通(无工具)聊天 round-0 主调用走 achat_complete(stream=True) 流式;
done 事件携带 message_id / content / process_steps / used_skills /
used_mcp / generating
- app/core/llm_client.py: 修复 _create_kwargs 硬编码 stream=False 的根因 bug
(流式调用传 stream=True 导致 TypeError: multiple values for keyword),
现 _create_kwargs(request_body, stream=True) 字段随参数变化
- frontend/src/pages/front/ChatPage.tsx + ChatPage.css + types/index.ts:
发消息改为 fetch SSE 消费,实时增量渲染 AI 回复文本与思考链(reasoning),
done 事件用 message_id 定稿并渲染过程步骤;网络/流式失败自动回退非流式
Alignment performed 2026-07-24 18:25:11. No deletions; secrets/venv/node_modules/
caches were not touched (tracked-source-only copy).
This commit is contained in:
1 parent
3788a93425
commit
256d9f33e6
3 files changed
+2182
-2031
No files matched your search
@@ -1999,6 +1999,15 @@ async def _send_message_core(
|
||||
|
||||
process_steps = [] # 过程步骤(思考/技能/MCP执行),用于前端收拢式展示
|
||||
|
||||
async def _push_step(step: dict):
|
||||
"""追加过程步骤,并在流式场景实时推送给前端(step 事件)。"""
|
||||
await _push_step(step)
|
||||
if on_event is not None:
|
||||
try:
|
||||
await on_event({"type": "step", "step": step, "index": len(process_steps) - 1})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -4489,7 +4498,7 @@ async def _send_message_core(
|
||||
|
||||
})
|
||||
|
||||
process_steps.append({
|
||||
await _push_step({
|
||||
|
||||
"type": "skill",
|
||||
|
||||
@@ -4791,10 +4800,11 @@ async def _send_message_core(
|
||||
|
||||
try:
|
||||
|
||||
if stream and on_event is not None and round_num == 0 and not (effective_mcp_ids or effective_skill_ids or effective_agent_ids):
|
||||
if stream and on_event is not None:
|
||||
|
||||
_sc = ""
|
||||
_sr = ""
|
||||
_final_msg = None
|
||||
async for _kind, _text in await achat_complete(
|
||||
api_url, api_key, request_body,
|
||||
proxy=_proxy_for_url(api_url),
|
||||
@@ -4807,7 +4817,15 @@ async def _send_message_core(
|
||||
elif _kind == "reasoning":
|
||||
_sr += _text
|
||||
await on_event({"type": "reasoning", "content": _text})
|
||||
message = {"content": _sc, "tool_calls": [], "reasoning": _sr}
|
||||
elif _kind == "message":
|
||||
_final_msg = _text
|
||||
if _final_msg is not None:
|
||||
_final_msg = dict(_final_msg)
|
||||
if "reasoning_content" in _final_msg:
|
||||
_final_msg["reasoning"] = _final_msg.pop("reasoning_content")
|
||||
message = _final_msg
|
||||
else:
|
||||
message = {"content": _sc, "tool_calls": [], "reasoning": _sr}
|
||||
choice = {"message": message}
|
||||
data = {"choices": [choice], "usage": {}}
|
||||
else:
|
||||
@@ -4983,7 +5001,7 @@ async def _send_message_core(
|
||||
|
||||
if _think and tool_calls:
|
||||
|
||||
process_steps.append({
|
||||
await _push_step({
|
||||
|
||||
"type": "thinking",
|
||||
|
||||
@@ -5469,7 +5487,7 @@ async def _send_message_core(
|
||||
|
||||
called_mcp_names.append(_srv.get("name", ""))
|
||||
|
||||
process_steps.append({
|
||||
await _push_step({
|
||||
|
||||
"type": "mcp",
|
||||
|
||||
@@ -5545,7 +5563,7 @@ async def _send_message_core(
|
||||
|
||||
called_skill_names.append(_sk.get("name", ""))
|
||||
|
||||
process_steps.append({
|
||||
await _push_step({
|
||||
|
||||
"type": "skill",
|
||||
|
||||
|
||||
Reference in new issue
Block a user