"""cloud_voice_dialog_v1:dialog_result 约定(见 docs/CLOUD_VOICE_FLIGHT_CONFIRM_v1.md)。""" from __future__ import annotations from typing import Any CLOUD_VOICE_DIALOG_V1 = "cloud_voice_dialog_v1" MSG_CONFIRM_TIMEOUT = "未收到确认指令,请重新下发指令。" MSG_CANCELLED = "已取消指令,请重新唤醒后下发指令。" MSG_CONFIRM_EXECUTING = "开始执行飞控指令。" MSG_PROMPT_LISTEN_TIMEOUT = "未检测到语音,请重新唤醒后再说。" def normalize_phrase_text(s: str) -> str: """去首尾空白、合并连续空白。""" return " ".join((s or "").strip().split()) def _strip_tail_punct(s: str) -> str: return s.rstrip("。!!??,, \t") def match_phrase_list(norm: str, phrases: Any) -> bool: """ 命中规则(适配「请回复确认或取消」类长提示 + 只说「确认」「取消」): - 去尾标点后 **全等** 短语;或 - 短语为 **子串** 且整句长度 <= len(短语)+2,避免用户复述整段提示时同时含「确认」「取消」而误触。 """ if not isinstance(phrases, list) or not norm: return False base = _strip_tail_punct(normalize_phrase_text(norm)) if not base: return False for p in phrases: raw = _strip_tail_punct((p or "").strip()) if not raw: continue if base == raw: return True if raw in base and len(base) <= len(raw) + 2: return True return False def parse_confirm_dict(raw: Any) -> dict[str, Any] | None: if not isinstance(raw, dict): return None required = raw.get("required") if not isinstance(required, bool): return None try: timeout_sec = float(raw.get("timeout_sec", 10)) except (TypeError, ValueError): timeout_sec = 10.0 timeout_sec = max(1.0, min(120.0, timeout_sec)) cp = raw.get("confirm_phrases") kp = raw.get("cancel_phrases") if not isinstance(cp, list) or not cp: return None if not isinstance(kp, list) or not kp: return None pending = raw.get("pending_id") if pending is not None and not isinstance(pending, str): pending = str(pending) cplist = [str(x) for x in cp if str(x).strip()] kplist = [str(x) for x in kp if str(x).strip()] if not cplist or not kplist: return None return { "required": required, "timeout_sec": timeout_sec, "confirm_phrases": cplist, "cancel_phrases": kplist, "pending_id": pending, "summary_for_user": raw.get("summary_for_user"), "raw": raw, }