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:
+49
-7
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user