"""Deterministic, trilingual replies the channel can send without a model. Every one of these is a reply the employee may receive when the model is not consulted at all — gate closed, sender unknown, unsupported message shape, or a backend fault. They must therefore be complete answers with a next step, never a bare error: an employee who messages IT and gets "failed" has been given a dead end, which is exactly the experience this product exists to remove. """ from __future__ import annotations from app.core.locale import DEFAULT_LANG, Lang _PORTAL = "https://helix.pingcap.net" _UNBOUND: dict[Lang, str] = { "zh": ( "你好,我是 Helix 助手。我还没能确认你的身份 —— 请先用飞书登录一次 " f"Helix({_PORTAL}),完成绑定后回来再问我一次就可以了。" ), "en": ( "Hi, this is the Helix assistant. I could not confirm who you are yet — " f"please sign in to Helix once with Feishu ({_PORTAL}), then message me " "again." ), "ja": ( "こんにちは、Helix アシスタントです。まだご本人を確認できていません。" f"一度 Feishu で Helix({_PORTAL})にログインしてから、もう一度お声がけ" "ください。" ), } _DEPARTED: dict[Lang, str] = { "zh": "你的 Helix 账号已停用,助手无法提供查询。如有疑问请联系 IT。", "en": ( "Your Helix account is no longer active, so the assistant cannot answer " "queries. Please contact IT if you think this is wrong." ), "ja": ( "Helix アカウントが無効のため、アシスタントはご利用いただけません。" "お心当たりがない場合は IT までご連絡ください。" ), } _UNSUPPORTED: dict[Lang, str] = { "zh": "我目前只看得懂文字消息,请把问题用文字发给我。", "en": "I can only read text messages for now — please send your question as text.", "ja": "現在テキストメッセージのみに対応しています。ご質問は文章でお送りください。", } _KERNEL_UNAVAILABLE: dict[Lang, str] = { "zh": f"AI 助手暂时不可用。你可以到 {_PORTAL} 查询自己的申请与设备,或提交 IT 求助。", "en": ( "The AI assistant is unavailable right now. You can check your requests " f"and devices at {_PORTAL}, or raise an IT request there." ), "ja": ( f"AI アシスタントは現在ご利用いただけません。{_PORTAL} から申請・端末の" "確認や IT への依頼が可能です。" ), } _WORKING: dict[Lang, str] = { "zh": "收到,我正在查你的数据,稍等一下。", "en": "Got it — I'm looking through your data, one moment.", "ja": "承知しました。データを確認していますので少々お待ちください。", } #: 「回答完马上看这条」 was a promise nothing kept. There is no queue: the #: message is dropped, and its event id is already inside a 6-hour dedup key, so #: Feishu's own redelivery is discarded as a duplicate too. The second message is #: usually the one carrying the detail the assistant just asked for — an email #: suffix, a date — so the employee waits for an answer to something we never #: read. Say what actually happens and what to do about it. _BUSY: dict[Lang, str] = { "zh": "我还在处理你上一条消息。这条我没接住 —— 等我回答完再发一次就行。", "en": ( "I'm still working on your previous message, so this one did not reach " "me — send it again once I've answered and I'll pick it up." ), "ja": ( "前のメッセージを処理中のため、こちらは受け取れませんでした。" "回答後にもう一度お送りください。" ), } _RATE_LIMITED: dict[Lang, str] = { "zh": f"你这一小时问得有点多,我先歇一下。过一会儿再来找我,急的话直接到 {_PORTAL} 提 IT 求助。", "en": ( "That's a lot of questions this hour, so I'm pausing for a bit. Try me " f"again shortly — or raise an IT request at {_PORTAL} if it's urgent." ), "ja": ( "この 1 時間のご質問が多いため、少しお休みします。しばらくしてから" f"またお声がけください。お急ぎの場合は {_PORTAL} から IT へご依頼ください。" ), } _MISMATCH: dict[Lang, str] = { "zh": "我没能确认这条消息是谁发的,为安全起见不作回答。请联系 IT。", "en": ( "I could not confirm who sent this message, so I'm not answering it. " "Please contact IT." ), "ja": ( "このメッセージの送信者を確認できなかったため、回答を控えます。" "IT までご連絡ください。" ), } _OFFER_FAILED: dict[Lang, str] = { "zh": f"确认卡片没发出去,所以还没提交任何东西。你可以再说一次让我重试,或到 {_PORTAL} 自己提交。", "en": ( "The confirmation card didn't go through, so nothing has been " f"submitted. Ask me again to retry, or submit it at {_PORTAL}." ), "ja": ( "確認カードを送信できなかったため、まだ何も申請されていません。" f"もう一度お申し付けいただくか、{_PORTAL} からお手続きください。" ), } _RESET: dict[Lang, str] = { "zh": "好的,我们从头开始。有什么可以帮你?", "en": "Done — starting a fresh thread. What can I help with?", "ja": "了解しました。新しい話題を始めます。ご用件をどうぞ。", } #: 使用说明。2026-08-31 扩写 + 接收从卡片页脚挪来的那句「回复卡片就能接着问」。 #: #: 原来只说了「可以自然语言问我」加两条指令,没有说**具体**能问到什么,读的人 #: 还是不知道边界在哪。现在按「能查 / 能办 / 接着聊 / 办不了怎么办」四段写, #: 每段给真例子。 #: #: ⚠️ 这些文案以 ``msg_type: "text"`` 发出(见 ``channel.send_text`` → #: ``feishu_client.send_text_to_user``)——**纯文本,不渲染 markdown**。 #: 所以这里不能用 ``**加粗**``,那会原样显示成星号。层级用【】与「•」表达。 _HELP: dict[Lang, str] = { "zh": ( "我是 Helix IT 助手,直接用大白话问我就行,不用记指令。\n\n" "【能查】设备、软件与账号权限、网络与 VPN、IT 制度与流程、" "你提过的申请和工单进度。\n" " 例:「我名下有哪些设备」「我的 Figma 权限还在吗」" "「上周那个申请到哪了」\n\n" "【能办】我可以帮你发起平台上已有的申请。会先把要提交的内容做成" "一张卡片给你确认,你点了才真的提交。\n" " 例:「我要改 GitHub ID」「申请一个软件坐席」「电脑坏了要报修」\n\n" "【接着聊】IT 推给你的任何一张卡片,直接回复它就能问 —— " "我知道你说的是哪一张,不用再复述一遍。\n\n" "【办不了会说清楚】查不到、你没权限、或者这件事必须人来做," "我会说明原因并帮你转人工,不会编一个答案。\n\n" "常用指令:\n" "• /new 或 /reset:清空这次会话,从新话题开始\n" "• /help:再次查看这份使用说明\n\n" f"也可以到 {_PORTAL} 查看自己的设备、申请和工单。" ), "en": ( "I'm the Helix IT assistant. Just say it in plain language — " "no commands to memorise.\n\n" "[What I can look up] Devices, software and account access, network " "and VPN, IT policies and procedures, and the status of requests and " "tickets you filed.\n" " e.g. \u201cwhat devices are assigned to me\u201d, " "\u201cdo I still have Figma access\u201d, " "\u201cwhat happened to last week's request\u201d\n\n" "[What I can file] I can start any request the platform already " "supports. You get a card showing exactly what will be submitted, " "and nothing is sent until you confirm it.\n" " e.g. \u201cchange my GitHub ID\u201d, " "\u201crequest a software seat\u201d, " "\u201cmy laptop is broken\u201d\n\n" "[Keeping the thread] Reply directly to any card IT pushes you — " "I know which one you mean, so you don't have to describe it again." "\n\n" "[When I can't] If I cannot find it, you lack access, or it needs a " "person, I say why and hand it to IT. I will not invent an answer." "\n\n" "Commands:\n" "• /new or /reset: clear this conversation\n" "• /help: show this guide again\n\n" f"You can also view your devices, requests, and tickets at {_PORTAL}." ), "ja": ( "Helix IT アシスタントです。コマンドを覚える必要はありません。" "普段の言葉でお尋ねください。\n\n" "【調べられること】端末、ソフトウェアとアカウント権限、" "ネットワークと VPN、IT 制度と手順、申請やチケットの進捗。\n" " 例:「私に割り当てられている端末は」" "「Figma の権限はまだありますか」「先週の申請はどうなりましたか」\n\n" "【手続きできること】プラットフォームにある申請を代わりに起票します。" "送信内容をカードでお見せし、確認いただくまで送信しません。\n" " 例:「GitHub ID を変更したい」「ソフトウェアのシートを申請」" "「PC が壊れた」\n\n" "【続けて聞く】IT から届いたカードにそのまま返信してください。" "どのカードのことか分かるので、説明し直す必要はありません。\n\n" "【できないとき】見つからない、権限がない、人の対応が必要 —— " "理由をお伝えし、担当者へ引き継ぎます。憶測ではお答えしません。\n\n" "コマンド:\n" "• /new または /reset:この会話をリセット\n" "• /help:この案内を再表示\n\n" f"端末・申請・チケットは {_PORTAL} でも確認できます。" ), } #: 「转人工」但还没说是什么事。**下一条消息会被直接做成工单**,所以这句话必须 #: 把那件事说明白 —— 员工在这一刻最怕的是「我说了要找人,它还在跟我绕」。 #: #: 为什么不干脆直接开一张工单:从空对话里草拟出来的工单标题是「AI 求助升级」、 #: 正文只有一行说明,IT 拿到手还得回头问一遍「你遇到什么事了」—— 那正是这条 #: 链路存在的理由。一句说明换一张能直接派工的工单,这笔交换对双方都划算。 _HANDOFF_NEEDS_DETAIL: dict[Lang, str] = { "zh": ( "好,我帮你转人工。先用一句话说说遇到的是什么事 —— " "你的下一条消息我会直接整理成工单交给 IT,不再经过我。" ), "en": ( "Of course — I'll hand this to a person. First tell me in one line " "what is going on: your next message goes straight to IT as a ticket, " "without passing through me." ), "ja": ( "承知しました、担当者におつなぎします。まず一言で状況をお書きください。" "次のメッセージはそのままチケットとして IT にお渡しします。" ), } #: 同一件事,但这一次**没能记下**「下一条是那句说明」(Redis 不可用)。措辞里 #: 因此不能出现那句承诺:员工的下一句会照常交给模型,而承诺一件做不到的事, #: 比不承诺更伤。人工仍然拿得到 —— 说完之后再说一次「转人工」就是了。 _HANDOFF_DESCRIBE_IT: dict[Lang, str] = { "zh": ( "好,我帮你转人工。请先用一句话说说遇到的是什么事," "我把它整理成工单交给 IT。" ), "en": ( "Of course — I'll hand this to a person. Tell me in one line what is " "going on and I'll turn it into a ticket for IT." ), "ja": ( "承知しました、担当者におつなぎします。まず一言で状況をお書きください。" "チケットとして IT にお渡しします。" ), } _FAILED: dict[Lang, str] = { "zh": f"我这边出了点问题,没能处理你的消息。请稍后再试,或到 {_PORTAL} 提交 IT 求助。", "en": ( "Something went wrong on my side and I could not handle your message. " f"Please try again shortly, or raise an IT request at {_PORTAL}." ), "ja": ( "処理中に問題が発生しました。しばらくしてからもう一度お試しいただくか、" f"{_PORTAL} から IT へご依頼ください。" ), } def _pick(table: dict[Lang, str], lang: Lang) -> str: return table.get(lang) or table[DEFAULT_LANG] def unbound(lang: Lang = DEFAULT_LANG) -> str: return _pick(_UNBOUND, lang) def departed(lang: Lang = DEFAULT_LANG) -> str: return _pick(_DEPARTED, lang) def unsupported_message(lang: Lang = DEFAULT_LANG) -> str: return _pick(_UNSUPPORTED, lang) def failed(lang: Lang = DEFAULT_LANG) -> str: return _pick(_FAILED, lang) def kernel_unavailable(lang: Lang = DEFAULT_LANG) -> str: return _pick(_KERNEL_UNAVAILABLE, lang) def working(lang: Lang = DEFAULT_LANG) -> str: return _pick(_WORKING, lang) def reset_done(lang: Lang = DEFAULT_LANG) -> str: return _pick(_RESET, lang) def help_guide(lang: Lang = DEFAULT_LANG) -> str: return _pick(_HELP, lang) def handoff_needs_detail(lang: Lang = DEFAULT_LANG) -> str: """转人工,但对话还是空的 —— 下一条消息会直接变成工单。""" return _pick(_HANDOFF_NEEDS_DETAIL, lang) def handoff_describe_it(lang: Lang = DEFAULT_LANG) -> str: """同上,但没能记住「下一条是那句说明」,所以不作那个承诺。""" return _pick(_HANDOFF_DESCRIBE_IT, lang) def busy(lang: Lang = DEFAULT_LANG) -> str: return _pick(_BUSY, lang) def rate_limited(lang: Lang = DEFAULT_LANG) -> str: return _pick(_RATE_LIMITED, lang) def sender_mismatch(lang: Lang = DEFAULT_LANG) -> str: return _pick(_MISMATCH, lang) #: What the transcript and the model's memory record when the reply *was* a #: confirmation card. Storing "" instead — the first shape — left a hole: #: ``load_history`` drops empty turns, so the next message showed the model an #: unanswered request and it offered the same card a second time. #: The life span is stated on purpose. Working memory outlives the offer — the #: session keeps 120 minutes of history and a proposal expires after 60 — so #: without it the model spends the second hour telling the employee to tap a #: card that is already dead. _CARD_OFFERED: dict[Lang, str] = { "zh": "(我已经把确认卡片发给你了,等你在卡片上确认。卡片 60 分钟内有效;" "如果过期了,你再说一声,我重新发一张。)", "en": "(I sent you a confirmation card — waiting for you to tap it. It is " "valid for 60 minutes; if it has expired, just say so and I will " "send a fresh one.)", "ja": "(確認カードを送信しました。カード上での確認をお待ちしています。" "有効期限は 60 分です。期限切れの場合はお知らせください、" "新しいカードをお送りします。)", } def card_offered(lang: Lang = DEFAULT_LANG) -> str: """Stand-in text for a turn whose real reply was a card.""" return _pick(_CARD_OFFERED, lang) #: The same fact on the web page, where the offer is not a message that was #: "sent" anywhere — it is rendered directly beneath this sentence. Saying #: 「已经把卡片发给你了」 there points the employee at a chat they are not in. _OFFER_SHOWN: dict[Lang, str] = { "zh": "(确认内容在下面,你确认之后我才会提交。60 分钟内有效;" "过期了再说一声,我重新准备一份。)", "en": "(The confirmation is below — nothing is submitted until you confirm " "it. It is valid for 60 minutes; if it lapses, just say so and I will " "prepare a fresh one.)", "ja": "(確認内容は下にあります。確認いただくまで送信されません。" "有効期限は 60 分です。期限切れの場合はお知らせください、" "新しく用意します。)", } def offer_shown(lang: Lang = DEFAULT_LANG) -> str: """Stand-in text for a web turn whose real reply is the offer below it.""" return _pick(_OFFER_SHOWN, lang) #: The web wording for "an offer you were told about does not exist". The chat #: version says 「确认卡片没发出去」 — a card, and a send, neither of which #: happened on a page where the offer is drawn inline. What both versions must #: say, and the only part that matters, is that NOTHING was submitted. _NOTHING_SUBMITTED: dict[Lang, str] = { "zh": "刚才那一步没成功,所以没有提交任何东西。你可以再说一次让我重试," "或者到 " + _PORTAL + " 自己提交。", "en": "That step did not go through, so nothing was submitted. Say it again " "and I will retry, or submit it yourself at " + _PORTAL + ".", "ja": "その手順が完了しなかったため、何も送信されていません。" "もう一度お申し付けいただければ再試行します。" "ご自身で送信する場合は " + _PORTAL + " へ。", } def nothing_submitted(lang: Lang = DEFAULT_LANG) -> str: """Say that nothing was submitted, without naming a card that never existed.""" return _pick(_NOTHING_SUBMITTED, lang) #: 「转人工」 typed into the WEB composer. The chat channel answers this by #: drafting a ticket card, because in a chat there is no button to point at. On #: the page there is one, right under the composer, and it already carries the #: transcript — so this points at it rather than inventing a second way to file #: the same ticket. Deterministic either way: a request for a human must never #: depend on the model being available to recognise it. _HANDOFF_HINT: dict[Lang, str] = { "zh": "好的,下面的「转人工 / 开工单」就是找人 —— 点它会带上我们这段对话," "你不用重打一遍。", "en": "Of course — the 转人工 / 开工单 button just below reaches a person, " "and it carries this conversation with it so you do not have to " "retype anything.", "ja": "承知しました。下の「転人工 / チケット作成」から担当者につながります。" "これまでのやり取りも一緒に送られるので、入力し直す必要はありません。", } def handoff_hint(lang: Lang = DEFAULT_LANG) -> str: """Point at the human escape that is already on the page.""" return _pick(_HANDOFF_HINT, lang) def offer_failed(lang: Lang = DEFAULT_LANG) -> str: return _pick(_OFFER_FAILED, lang) #: Why a tap was turned away, in the reader's own language, keyed on the #: ``ProposalError`` code. #: #: ``ProposalError.message`` is written for an API envelope and is English only. #: The card path rendered it straight onto the refusal card, so a colleague whose #: Helix language is Chinese or Japanese read "This request has already been #: handled." — every other sentence this channel sends is localised, and the one #: an employee meets when something goes WRONG was not. #: #: Keyed on the code rather than translated at each raise site: the raise sites #: are deep in the proposal machinery and have no reader to be in the language #: of, while the code is a stable contract the tests already assert on. _REFUSALS: dict[str, dict[Lang, str]] = { "ASSISTANT_PROPOSAL_ALREADY_HANDLED": { "zh": "这张卡片已经处理过了,不会重复提交。", "en": "This request has already been handled — it will not be submitted twice.", "ja": "この依頼はすでに処理済みです。二重に送信されることはありません。", }, "ASSISTANT_PROPOSAL_EXPIRED": { "zh": "这张卡片已经过期(有效期 60 分钟)。再跟我说一次,我重新发一张。", "en": ( "This offer expired (it is valid for 60 minutes). Just ask me again " "and I will send a fresh card." ), "ja": ( "この確認は期限切れです(有効期限 60 分)。もう一度お声がけいただければ" "新しいカードをお送りします。" ), }, "ASSISTANT_PROPOSAL_FORBIDDEN": { "zh": "这张卡片是别人的申请,只有本人能确认。你可以让我帮你单独提一份。", "en": ( "This card belongs to someone else's request, and only they can " "confirm it. I can raise the same thing for you instead — just ask." ), "ja": ( "このカードは他の方の申請です。ご本人のみ確認できます。" "同じ内容をあなたの名義で作成することもできますので、お知らせください。" ), }, "ASSISTANT_PROPOSAL_NOT_FOUND": { "zh": "这张卡片对应的申请已经不存在了。再跟我说一次,我重新发一张。", "en": ( "The request behind this card no longer exists. Ask me again and I " "will send a fresh one." ), "ja": ( "このカードの申請は存在しません。もう一度お声がけください、" "新しいカードをお送りします。" ), }, "ASSISTANT_PROPOSAL_CHANGED": { "zh": "这张卡片的内容在你打开之后变过了,所以没有提交。再跟我说一次即可。", "en": ( "What this card contains changed after you opened it, so nothing was " "submitted. Ask me again and I will send a fresh one." ), "ja": ( "開いた後に内容が変わったため送信していません。もう一度お声がけください。" ), }, "ASSISTANT_PROPOSAL_TOKEN_INVALID": { "zh": "这个确认链接已经失效了。再跟我说一次,我重新发一张卡片。", "en": ( "This confirmation link is no longer valid. Ask me again and I will " "send a fresh card." ), "ja": ( "この確認リンクは無効です。もう一度お声がけください、" "新しいカードをお送りします。" ), }, "ASSISTANT_ACTION_UNKNOWN": { "zh": "这个流程已经下线了,没法再从这里提交。可以让我帮你转 IT 处理。", "en": ( "This flow is no longer available, so it cannot be submitted from " "here. I can hand it to IT for you instead." ), "ja": ( "この手続きは提供を終了しました。IT へお繋ぎすることもできます。" ), }, "ASSISTANT_ACTION_FAILED": { "zh": "提交没成功。卡片还能再点一次;如果还是不行,跟我说一声我转给 IT。", "en": ( "Submitting it did not work. The card can be tapped again; if it " "keeps failing, tell me and I will pass it to IT." ), "ja": ( "送信に失敗しました。カードは再度タップできます。" "続けて失敗する場合はお知らせください、IT へ引き継ぎます。" ), }, } #: Read access is wider than submit access, so this is the one thing the #: assistant has to be able to say clearly rather than let a card fail after the #: employee has already approved it. _CANNOT_SUBMIT_YET: dict[Lang, str] = { "zh": ( "我还不能以你的名义提交申请 —— 账号要到入职当天才完全激活(如果你已确认离职," "提交权限也会收起)。你可以直接找 IT 帮忙,我这边查东西不受影响。" ), "en": ( "I can't submit requests in your name yet — an account activates fully on " "your first day (and a confirmed leaver's submit rights are withdrawn). " "IT can help directly in the meantime, and I can still look things up for " "you." ), "ja": ( "まだあなたの名義で申請を送信できません(アカウントは入社初日に有効化され、" "退職確定後は送信権限が外れます)。その間は IT が直接対応できます。" "調べものは引き続きお手伝いします。" ), } #: 「新话题」 that did not take. The one thing the employee needs to know is that #: the thread is STILL THERE — the generic failure line said "try again later or #: raise an IT request", which is both silent about that and a non-answer to a #: reset command. _RESET_FAILED: dict[Lang, str] = { "zh": "这次没清掉,之前的上下文还在。再发一次「新话题」就好。", "en": ( "That didn't take — the earlier context is still there. Send \"reset\" " "once more and it should stick." ), "ja": ( "リセットできませんでした。これまでの文脈は残っています。" "もう一度「リセット」とお送りください。" ), } def reset_failed(lang: Lang = DEFAULT_LANG) -> str: """Say that the thread was NOT forgotten, which is the fact that matters.""" return _pick(_RESET_FAILED, lang) def cannot_submit_yet(lang: Lang = DEFAULT_LANG) -> str: """Why nothing can be filed for this person, in their own language.""" return _pick(_CANNOT_SUBMIT_YET, lang) def proposal_refused(code: str, lang: Lang = DEFAULT_LANG) -> str: """Localised reason for a refused tap. An unmapped code falls back to the generic failure sentence rather than to the English original: a code nobody translated is a gap in this table, and leaking an API message onto a card hides that gap behind something that looks deliberate. """ table = _REFUSALS.get(code) if table is None: return failed(lang) return _pick(table, lang) # ── 群里的确定性回复 ──────────────────────────────────────────────── # # 群里的每一句确定性回复都被房间里所有人看见,所以它们比单聊那一批更短、更 # 中性:一句「你还没登录过 Helix」在私聊里是指引,在群里念给五个人听就是通报。 # 能私下说的就私下说,群里只留一句「已私信」。 _GROUP_ANY_DISABLED: dict[Lang, str] = { "zh": ( "我目前只在 IT 求助群里应答。直接私聊我(点我的头像)就可以问同样的问题," f"或者到 {_PORTAL} 自助。" ), "en": ( "I only answer inside IT help groups for now. Message me directly (tap my " f"avatar) with the same question, or use {_PORTAL}." ), "ja": ( "現在は IT 相談グループ内でのみ応答しています。同じ質問は直接 DM(アイコン" f"をタップ)でお受けできます。または {_PORTAL} をご利用ください。" ), } _GROUP_DISABLED: dict[Lang, str] = { "zh": "群内应答被管理员关闭了。直接私聊我可以问同样的问题,工单也照常在处理。", "en": ( "In-group answering is switched off by an administrator. Message me " "directly for the same question — the ticket is unaffected." ), "ja": ( "グループ内応答は管理者により無効化されています。同じ質問は DM でお受け" "できます。チケットの処理は継続します。" ), } _GROUP_DETAIL_SENT_PRIVATELY: dict[Lang, str] = { "zh": "涉及你个人记录的部分我私聊发给你了。", "en": "I have sent the part about your own records to you privately.", "ja": "ご本人の記録に関わる部分は DM でお送りしました。", } _GROUP_ALREADY_HUMAN: dict[Lang, str] = { "zh": ( "这个群就是「转人工」的结果 —— IT 负责人已经在群里了,直接把问题说出来" "就行,不需要再转一次。" ), "en": ( "This room IS the handoff — the IT owner for this area is already here. " "Just describe the problem; there is nothing more to escalate to." ), "ja": ( "このグループが「担当者へのエスカレーション」の結果です。担当者はすでに" "参加していますので、そのままご相談ください。" ), } _GROUP_WELCOME: dict[Lang, str] = { "zh": ( "我是 Helix IT 助手。在这个群里 @我 就可以问 IT 的问题 —— 设备、账号、" "权限、软件、流程都可以。\n" "涉及个人记录的答复我会私聊发给你;需要确认的操作也会以私聊卡片发出。" ), "en": ( "I'm the Helix IT assistant. @-mention me in this group to ask anything " "about devices, accounts, access, software or processes.\n" "Answers about someone's own records go to them privately, and anything " "that needs confirming arrives as a private card." ), "ja": ( "Helix IT アシスタントです。このグループで @メンション いただければ、端末・" "アカウント・権限・ソフト・手続きについてお答えします。\n" "個人の記録に関する回答は DM で送信し、確認が必要な操作も DM のカードで" "お送りします。" ), } def group_any_disabled(lang: Lang = DEFAULT_LANG) -> str: """任意群里被 @ 了,但那个开关没开。""" return _pick(_GROUP_ANY_DISABLED, lang) def group_disabled(lang: Lang = DEFAULT_LANG) -> str: """工单群里被 @ 了,但群内应答被关掉了。""" return _pick(_GROUP_DISABLED, lang) def group_detail_sent_privately(lang: Lang = DEFAULT_LANG) -> str: """群里那一句「细节私聊给你了」。""" return _pick(_GROUP_DETAIL_SENT_PRIVATELY, lang) def group_already_human(lang: Lang = DEFAULT_LANG) -> str: """在工单群里打「转人工」—— 他们已经到了。""" return _pick(_GROUP_ALREADY_HUMAN, lang) def group_welcome(lang: Lang = DEFAULT_LANG) -> str: """机器人刚被拉进一个群时的自我介绍。""" return _pick(_GROUP_WELCOME, lang)