Add agent workflow engine foundation and theme-style page switcher.

Introduce WorkflowEngine with state machine, tool registry, and builtin chat template; migrate stream chat to engine callbacks. Move page mode switching to TopBar actions cluster as a ThemeToggle-style dropdown (聊天/工作室/爽文/房间).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-31 02:30:43 +08:00
parent d6745b45a5
commit bc130d98f4
24 changed files with 1164 additions and 614 deletions

View File

@@ -221,119 +221,47 @@ async def _handle_stream_chat(
workflow_service
):
"""
处理流式聊天请求
Args:
websocket: WebSocket 连接
role_name: 角色名
chat_name: 聊天名
request_data: 请求数据
workflow_service: 工作流服务实例
处理流式聊天请求 engine callbacks emit worldbook_active / tasks_created / chunk.
"""
try:
print(f"[StreamChat] 🚀 开始流式处理")
# ✅ 第1步加载角色卡
current_role = request_data.get("currentRole")
character_data = request_data.get("characterData")
if character_data:
try:
from backend.models.internal import CharacterCard
except ImportError:
from models.internal import CharacterCard
character = CharacterCard(**character_data)
else:
from backend.services.character_service import CharacterService
character_service = CharacterService()
character = character_service.get_character_by_name(current_role)
if not character:
print(f"[StreamChat] ❌ 错误: 无法加载角色 '{current_role}'")
await websocket.send_json({
"type": "error",
"message": f"角色 '{current_role}' 不存在"
})
return
print(f"[StreamChat] ✅ 已加载角色卡: {character.name}")
# ✅ 第2步激活世界书条目在LLM调用之前
print(f"[StreamChat] 📚 正在激活世界书条目...")
active_entries = await workflow_service._collect_and_activate_worldbooks(
request_data,
character
)
# ✅ 发送激活的世界书条目信息在LLM调用前
if active_entries:
print(f"[StreamChat] 📤 发送世界书激活信息: {len(active_entries)} 个条目")
# 将 Pydantic 模型转换为字典
entries_dict = [entry.model_dump() for entry in active_entries]
await websocket.send_json({
"type": "worldbook_active",
"entries": entries_dict
})
# ✅ TODO: RAG检索暂时为空待实现
rag_results = []
if rag_results:
print(f"[StreamChat] 🔍 发送 RAG 检索结果: {len(rag_results)}")
await websocket.send_json({
"type": "rag_results",
"results": rag_results
})
# ✅ 第2步启动并行任务在LLM调用前创建任务ID
options = request_data.get("options", {})
task_ids = {
"imageWorkflow": None,
"dynamicTable": None
}
if options.get("imageWorkflow", False):
import uuid
chat_id = f"{role_name}/{chat_name}"
task_ids["imageWorkflow"] = f"img_{uuid.uuid4().hex[:8]}"
from backend.services.task_queue_manager import task_queue_manager, TaskType
await task_queue_manager.add_task(task_ids["imageWorkflow"], TaskType.IMAGE_WORKFLOW, chat_id)
if options.get("dynamicTable", False):
import uuid
chat_id = f"{role_name}/{chat_name}"
task_ids["dynamicTable"] = f"tbl_{uuid.uuid4().hex[:8]}"
from backend.services.task_queue_manager import task_queue_manager, TaskType
await task_queue_manager.add_task(task_ids["dynamicTable"], TaskType.DYNAMIC_TABLE, chat_id)
# ✅ 发送任务ID信息在LLM调用前
if task_ids.get("imageWorkflow") or task_ids.get("dynamicTable"):
print(f"[StreamChat] 📤 发送任务ID信息: {task_ids}")
await websocket.send_json({
"type": "tasks_created",
"tasks": task_ids
})
# ✅ 第3步调用LLM流式生成
chunk_count = [0] # 使用列表以便在闭包中修改
chunk_count = [0]
async def on_worldbook_active(entries):
if entries:
print(f"[StreamChat] 📤 发送世界书激活信息: {len(entries)} 个条目")
await websocket.send_json({
"type": "worldbook_active",
"entries": entries,
})
async def on_tasks_created(task_ids):
if task_ids.get("imageWorkflow") or task_ids.get("dynamicTable"):
print(f"[StreamChat] 📤 发送任务ID信息: {task_ids}")
await websocket.send_json({
"type": "tasks_created",
"tasks": task_ids,
})
async def on_chunk(chunk):
chunk_count[0] += 1
if chunk_count[0] % 10 == 0:
print(f"[StreamChat] 📤 已发送 {chunk_count[0]} 个 chunks")
await websocket.send_json({"type": "chunk", "content": chunk})
result = await workflow_service.process_chat_request_stream(
request_data,
on_chunk=lambda chunk: asyncio.create_task(
_send_chunk_with_log(websocket, chunk, chunk_count)
)
on_chunk=on_chunk,
on_worldbook_active=on_worldbook_active,
on_tasks_created=on_tasks_created,
)
if result["success"]:
content = result["content"]
print(f"\n[StreamChat] ✨ 流式生成成功,总长度: {len(content)}")
# 发送完成信号
print(f"[StreamChat] ✅ 发送完成信号")
await websocket.send_json({
"type": "complete"
})
# 保存消息
await websocket.send_json({"type": "complete"})
print(f"[StreamChat] 💾 保存消息到文件...")
await _save_messages(role_name, chat_name, request_data, content)
print(f"[StreamChat] ✅ 消息保存完成\n")
@@ -342,35 +270,19 @@ async def _handle_stream_chat(
print(f"[StreamChat] ❌ 流式处理失败: {error_msg}")
await websocket.send_json({
"type": "error",
"message": error_msg
"message": error_msg,
})
except Exception as e:
print(f"\n[StreamChat] ⚠️ 错误: {str(e)}")
import traceback
traceback.print_exc()
await websocket.send_json({
"type": "error",
"message": f"流式处理失败: {str(e)}"
"message": f"流式处理失败: {str(e)}",
})
async def _send_chunk_with_log(websocket: WebSocket, chunk: str, chunk_count: list):
"""
发送 chunk 并记录日志
Args:
websocket: WebSocket 连接
chunk: 文本片段
chunk_count: 计数器(使用列表以便在闭包中修改)
"""
chunk_count[0] += 1
if chunk_count[0] % 10 == 0: # 每10个chunk记录一次
print(f"[StreamChat] 📤 已发送 {chunk_count[0]} 个 chunks")
await websocket.send_json({"type": "chunk", "content": chunk})
async def _save_messages(
role_name: str,
chat_name: str,