79 lines
2.6 KiB
Python
79 lines
2.6 KiB
Python
"""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,
|
||
}
|