feat: Refactor DpmOverlays to use ShowCard for rendering cards and add ToolStatusToast for operation status notifications

- Moved card rendering logic from DpmOverlays to a new ShowCard component for better reusability.
- Introduced ToolStatusToast to display real-time operation statuses in the top right corner.
- Updated PageHeader to conditionally render credits based on the current path.
- Modified PromptPanel to change tool names and update prompt titles.
- Enhanced ScreenLayout to include ToolStatusToast.
- Updated styles for new components and adjusted existing styles for consistency.
- Implemented statusBus utility for dispatching tool status events.
- Updated useMqttControl to integrate tool status notifications during navigation and card display actions.
This commit is contained in:
Pine
2026-08-19 12:28:35 +08:00
parent 81a888f5ca
commit 107727ea5e
29 changed files with 2076 additions and 247 deletions
+49 -7
View File
@@ -111,23 +111,34 @@ async def get_settings():
@router.get("/api/config")
async def runtime_config():
"""运行配置(供前端启动引导覆盖):MQTT 地址/账号、API 基址
async def runtime_config(request: Request):
"""运行配置(供前端启动引导覆盖):MQTT 地址/账号、API 基址、语音地址
—— 打包部署时展播端从后端拉取,避免构建期写死的局域网 IP 失效
"""
ws = settings.MQTT_WS_URL
# 从 ws://host:port/mqtt 中提取 broker 主机
# 后端可达主机:优先取客户端实际访问本服务的 Host(与 api_base 一致),
# 回退到从 MQTT broker 地址提取。
host = "192.168.1.9"
try:
host = ws.split("://", 1)[1].split(":", 1)[0]
except Exception: # noqa: BLE001
pass
try:
rh = (request.headers.get("host") or "").split(":")[0]
if rh:
host = rh
except Exception: # noqa: BLE001
pass
# s2s 与后端同机启动 → voice_url 默认跟随后端主机;显式 DPM_VOICE_WS 优先
voice_url = settings.S2S_WS_URL
if not settings.VOICE_WS and host:
voice_url = f"ws://{host}:{settings.S2S_PORT}/v1/realtime"
return {
"ok": True,
"mqtt_url": ws,
"mqtt_username": settings.MQTT_USERNAME or "",
"mqtt_password": settings.MQTT_PASSWORD or "",
"voice_url": settings.S2S_WS_URL,
"voice_url": voice_url,
"api_base": f"http://{host}:{settings.PORT}",
"broker_host": host,
}
@@ -418,7 +429,7 @@ AI_GROUPED_QUESTIONS = [
]
AI_OPC_TOOLS = [
"DeepSeek-V3", "通义千问", "ChatGPT", "豆包",
"DeepSeek", "通义千问", "ChatGPT", "豆包",
"Midjourney", "Stable Diffusion", "剪映", "Notion AI",
"WPS AI", "GitHub Copilot",
]
@@ -550,6 +561,28 @@ def vision_frame(body: VisionFrameBody):
return predict_base64(img_b64)
class VisionLlmBody(BaseModel):
image: str = "" # JPEG base64
prompt: str = "" # 可选,自定义识别指令
@router.post("/api/vision/llm")
def vision_llm(body: VisionLlmBody):
"""对话开场画面识别:多模态 LLM(qwen3-vl-flash)识别人数/性别,返回 {ok, people, males, females, desc}。"""
img = (body.image or "").strip()
if not img:
return {"ok": False, "error": "missing image"}
try:
from .vision_llm import analyze_scene
result = analyze_scene(img, body.prompt)
log.info("api: /api/vision/llm -> people=%s males=%s females=%s desc=%s",
result.get("people"), result.get("males"), result.get("females"),
(result.get("desc") or "")[:60])
return result
except Exception as e: # noqa: BLE001
log.warning("vision llm 失败: %s", e)
return {"ok": False, "error": str(e)}
# ==================== 智能体工具调用 + 知识库 ====================
class ToolsExecBody(BaseModel):
name: str = ""
@@ -563,6 +596,9 @@ def tools_exec(body: ToolsExecBody):
return {"ok": False, "error": "missing tool name"}
from .tools import exec_tool
result = exec_tool(name, body.args or {})
log.info("api: /api/tools/exec name=%s args=%s result=%s", name,
json.dumps(body.args or {}, ensure_ascii=False)[:300],
str(result)[:400])
return {"ok": True, "result": result}
@router.get("/api/kb/brief")
@@ -570,7 +606,9 @@ def kb_brief(max_chars: int = 1200):
"""园区知识摘要(前端拼进对话 instructions"""
try:
from .rag import brief
return {"ok": True, "brief": brief(max_chars=max_chars)}
result = brief(max_chars=max_chars)
log.info("api: /api/kb/brief max_chars=%d -> %s", max_chars, str(result)[:300])
return {"ok": True, "brief": result}
except Exception as e: # noqa: BLE001
log.warning("kb brief 失败: %s", e)
return {"ok": False, "error": str(e)}
@@ -583,6 +621,7 @@ def kb_retrieve(q: str = "", top_k: int = 3):
try:
from .rag import retrieve
chunks = retrieve(q, top_k=top_k)
log.info("api: /api/kb/retrieve q=%s top_k=%d -> %d", q[:100], top_k, len(chunks))
return {"ok": True, "chunks": chunks}
except Exception as e: # noqa: BLE001
log.warning("kb retrieve 失败: %s", e)
@@ -593,7 +632,10 @@ def s2s_instructions():
"""语音助手基础提示词(服务端统一组装:配置提示词 + 园区知识库)"""
try:
from .rag import build_instructions
return {"ok": True, "instructions": build_instructions()}
instructions = build_instructions()
log.info("api: /api/s2s/instructions -> 系统提示词(%d 字)=%s",
len(instructions), instructions[:600])
return {"ok": True, "instructions": instructions}
except Exception as e: # noqa: BLE001
log.warning("s2s instructions 构建失败: %s", e)
return {"ok": False, "error": str(e)}