diff --git a/app.py b/app.py index 94bd0c5..8ccff7a 100644 --- a/app.py +++ b/app.py @@ -96,7 +96,7 @@ except Exception as e: # 🚩 系統版本定義 (備份與顯示用) # 🚩 2026-05-01 V10.76: Move monthly analysis report onto V2 shell -SYSTEM_VERSION = "V10.76" +SYSTEM_VERSION = "V10.77" # ========================================== # 🔒 SQL Injection 防護函數 diff --git a/routes/openclaw_bot_routes.py b/routes/openclaw_bot_routes.py index 59d5c1a..3e0c746 100644 --- a/routes/openclaw_bot_routes.py +++ b/routes/openclaw_bot_routes.py @@ -4166,6 +4166,63 @@ _HELP_KEYWORDS = ( '使用說明', '操作說明', 'help', '幫助', ) +_WAKEUP_KEYWORDS = ( + '小o', + '小o小龍蝦', + '小o_小龍蝦', + '小龍蝦', + 'openclaw', +) + +_BUSINESS_KEYWORDS = ( + '業績', '營收', '銷售', '銷量', '熱銷', '商品', '廠商', '目標', + '報表', '趨勢', '分析', '簡報', '比價', '競品', '補貨', '促銷', + '異常', '健康', '分類', '策略', '天氣', '新聞', '匯率', '節慶', + '價格', '今日', '昨日', '今天', '本週', '上週', '本月', '上月', '今年', +) + +_GREETING_KEYWORDS = ( + '你好', '嗨', '哈囉', '早安', '午安', '晚安', '在嗎', '你有空', '在不在', + 'hello', 'hi', 'hey', +) + + +def _normalize_nl_query(q: str) -> str: + """V-Fix:去除 NL 句尾符號與空白,降低誤判率。""" + return re.sub(r'[\s_\-,:;,.,。!?!?"“”\(\)\[\]<>]+', '', (q or '').lower()) + + +def _contains_business_signal(q: str) -> bool: + """V-Fix:是否包含可直接進 AI 查詢邏輯的業務關鍵字。""" + ql = (q or '').lower() + return any(kw in ql for kw in _BUSINESS_KEYWORDS) + + +def _looks_like_wakeup_prompt(q: str) -> bool: + """V-Fix:防止只叫名字/打招呼時走自由生成。""" + ql = (q or '').lower().strip() + if not ql: + return False + + compact = _normalize_nl_query(ql) + if not compact: + return False + + if compact in _WAKEUP_KEYWORDS: + return True + + if ( + any(name in compact for name in _WAKEUP_KEYWORDS) + and not _contains_business_signal(ql) + and len(compact) <= 18 + ): + return True + + if compact in _GREETING_KEYWORDS and len(compact) <= 10: + return True + + return False + def _is_help_question(q: str) -> bool: ql = q.lower() return any(kw in ql for kw in _HELP_KEYWORDS) @@ -4392,6 +4449,17 @@ def openclaw_answer(question: str, chat_id: int = None): today_str = now.strftime("%Y/%m/%d") history_ctx = openclaw_session.history_as_prompt(chat_id) if chat_id else "" + # ── 只叫名 / 問候:先導回主選單,避免題外市場回覆 ─────────────── + if _looks_like_wakeup_prompt(question): + wakeup_text = ( + "👋 *OpenClaw(小O)* 在!\n\n" + "你可以直接點下面按鈕,或直接問我:\n" + " 「今天業績如何?」\n" + " 「怎麼查看熱銷商品?」\n" + " 「有什麼市場情報?」" + ) + return wakeup_text, quick_menu_keyboard() + # ── 功能說明直接導 help ─────────────────────────────────── if _is_help_question(question): help_text = ( diff --git a/tests/test_openclaw_bot_routes_webhook.py b/tests/test_openclaw_bot_routes_webhook.py index 8a6935d..3b980bf 100644 --- a/tests/test_openclaw_bot_routes_webhook.py +++ b/tests/test_openclaw_bot_routes_webhook.py @@ -38,6 +38,24 @@ def test_webhook_menu_command_handles_bot_suffix(monkeypatch): assert calls == [("menu", "", -200, 55)] +def test_openclaw_answer_wakeup_query_returns_menu(): + from routes import openclaw_bot_routes as bot + + text, kb = bot.openclaw_answer("小龍蝦") + + assert "OpenClaw(小O)" in text + assert kb == bot.quick_menu_keyboard() + + +def test_openclaw_answer_variants_are_menu_wakeup(): + from routes import openclaw_bot_routes as bot + + assert bot._looks_like_wakeup_prompt("小O_小龍蝦") is True + assert bot._looks_like_wakeup_prompt("Hello, 小O") is True + assert bot._looks_like_wakeup_prompt("今天業績") is False + assert bot._looks_like_wakeup_prompt("你好") is True + + def test_private_menu_command_is_allowed_when_no_whitelist_and_fallback_enabled(monkeypatch): from routes import openclaw_bot_routes as bot