feat: v0.1.1 迭代——人设外键重构、Chat UI优化、意识节点防幻觉、日志双视图

1. 人设外键重构:persona_template 成为 system_prompt 唯一权威来源,
   agent/系统节点通过 persona_id FK 引用,含数据迁移脚本
2. Chat UI:去掉底部AI提示、加号改为弹出菜单、新建对话乐观跳转
3. 意识节点:无可用worker时禁止编造agent_id,只能自行完成或拒绝
4. 日志页面:双tab布局(系统日志 + 工作流日志列表选择)
5. 其他:SSE流式聊天、对话删除/重命名、standalone模式修复

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 06:18:47 +00:00
parent e3b8686d45
commit 6f1bc27101
39 changed files with 2904 additions and 524 deletions
+119 -1
View File
@@ -12,7 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from fastapi import APIRouter, Depends
import json
import asyncio
import httpx
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from kilostar.utils.ray_hook import ray_actor_hook
from kilostar.utils.access import Accessor, TokenData
@@ -136,3 +140,117 @@ async def send_chat_message(
)
return {"reply": response_msg}
@chat_router.delete("/{chat_id}")
async def delete_chat_session(
chat_id: str,
token_data: TokenData = Depends(Accessor.get_current_user),
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
session = await postgres_database.get_chat_session.remote(chat_id=chat_id)
if not session:
raise HTTPException(status_code=404, detail="Chat session not found")
if session.user_id != token_data.user_id:
raise HTTPException(status_code=403, detail="Forbidden")
await postgres_database.delete_chat_session.remote(chat_id=chat_id)
return {"message": "success"}
@chat_router.post("/{chat_id}/stream")
async def stream_chat_message(
chat_id: str,
request_body: SendMessageRequest,
request: Request,
token_data: TokenData = Depends(Accessor.get_current_user),
):
"""SSE 流式聊天端点:逐 token 推送 AI 回复。"""
postgres_database = ray_actor_hook("postgres_database").postgres_database
# 存用户消息
await postgres_database.add_chat_message.remote(
chat_id=chat_id, message=request_body.message, message_owner="user"
)
# 获取 regulatory_node 的 provider 配置
node_config = await postgres_database.get_system_node_config.remote("regulatory_node")
if not node_config:
raise HTTPException(status_code=500, detail="Regulatory node not configured")
# 获取 provider 详情
from kilostar.core.global_state_machine.gsm_snapshot import fetch_snapshot
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
snapshot = await fetch_snapshot(gsm_actor=global_state_machine)
provider = snapshot.providers.get(node_config.provider_title)
if not provider:
raise HTTPException(status_code=500, detail="Provider not available")
# 加载历史消息作为上下文
history_msgs = await postgres_database.list_chat_messages.remote(chat_id=chat_id)
messages = []
system_prompt = "你是 KiloStar 助手,友善、简洁地回答用户的问题。"
if node_config.persona_id:
tpl = await postgres_database.get_template.remote(node_config.persona_id)
if tpl and tpl.system_prompt:
system_prompt += "\n" + tpl.system_prompt
messages.append({"role": "system", "content": system_prompt})
for msg in history_msgs:
role = "user" if msg.message_owner == "user" else "assistant"
messages.append({"role": role, "content": msg.message})
async def event_generator():
full_response = ""
try:
async with httpx.AsyncClient(timeout=120.0) as client:
url = provider.provider_url.rstrip("/") + "/chat/completions"
payload = {
"model": node_config.model_id,
"messages": messages,
"stream": True,
}
async with client.stream(
"POST",
url,
json=payload,
headers={
"Authorization": f"Bearer {provider.provider_apikey}",
"Content-Type": "application/json",
},
) as resp:
async for line in resp.aiter_lines():
if await request.is_disconnected():
break
if not line.startswith("data: "):
continue
data_str = line[6:]
if data_str.strip() == "[DONE]":
break
try:
chunk = json.loads(data_str)
delta = chunk.get("choices", [{}])[0].get("delta", {})
token = delta.get("content", "")
if token:
full_response += token
yield f"data: {json.dumps({'token': token})}\n\n"
except (json.JSONDecodeError, IndexError, KeyError):
continue
except Exception as e:
from kilostar.utils.logger import get_logger
get_logger("chat_stream").exception(f"Stream error: {e}")
if not full_response:
full_response = "抱歉,生成回复时出错。"
yield f"data: {json.dumps({'token': full_response})}\n\n"
# 流结束,存入数据库
if full_response:
await postgres_database.add_chat_message.remote(
chat_id=chat_id,
message=full_response,
message_owner="regulatory_node",
)
yield f"data: {json.dumps({'done': True, 'full_message': full_response})}\n\n"
return StreamingResponse(event_generator(), media_type="text/event-stream")