from __future__ import annotations import logging from enum import Enum from yuxi.channel.extensions.helpscout.auth import HelpScoutAuth from yuxi.channel.extensions.helpscout.client import HelpScoutClient from yuxi.channel.extensions.helpscout.config import resolve_account from yuxi.channel.extensions.helpscout.types import OutboundResult logger = logging.getLogger(__name__) class ApprovalStatus(Enum): PENDING = "pending" APPROVED = "approved" REJECTED = "rejected" class HelpScoutApproval: def __init__(self): self._pending: dict[str, dict] = {} async def submit_for_approval( self, conversation_id: int, draft_text: str, draft_html: str, account_id: str | None = None, ) -> OutboundResult: account = resolve_account(account_id or "default") client = self._build_client(account) try: result = await client.reply_conversation( conversation_id=conversation_id, text=draft_text, html=draft_html, draft=True, ) draft_id = str(result.get("id", "")) note_text = ( "AI Draft requires review before sending:\n\n" f"{draft_text[:500]}{'...' if len(draft_text) > 500 else ''}\n\n" "Review the draft in Help Scout and approve or reject." ) await client.create_note(conversation_id=conversation_id, text=note_text) key = f"conv:{conversation_id}" self._pending[key] = { "conversation_id": conversation_id, "draft_id": draft_id, "status": ApprovalStatus.PENDING, "draft_text": draft_text, } logger.info( "Help Scout draft submitted for approval: conv=%d draft_id=%s", conversation_id, draft_id, ) return OutboundResult(success=True, message_id=draft_id) except Exception as e: logger.exception("Help Scout approval submit failed: conv=%d", conversation_id) return OutboundResult(success=False, error="exception", detail=str(e)) finally: await client.close() async def approve_draft( self, conversation_id: int, account_id: str | None = None, ) -> OutboundResult: key = f"conv:{conversation_id}" pending = self._pending.get(key) if not pending: return OutboundResult(success=False, error="no_pending_draft") pending["status"] = ApprovalStatus.APPROVED account = resolve_account(account_id or "default") client = self._build_client(account) try: result = await client.reply_conversation( conversation_id=conversation_id, text=pending["draft_text"], draft=False, ) self._pending.pop(key, None) logger.info("Help Scout draft approved and sent: conv=%d", conversation_id) return OutboundResult(success=True, message_id=str(result.get("id", ""))) except Exception as e: logger.exception("Help Scout approve and send failed: conv=%d", conversation_id) return OutboundResult(success=False, error="exception", detail=str(e)) finally: await client.close() async def reject_draft( self, conversation_id: int, reason: str = "", account_id: str | None = None, ) -> OutboundResult: key = f"conv:{conversation_id}" pending = self._pending.get(key) if not pending: return OutboundResult(success=False, error="no_pending_draft") pending["status"] = ApprovalStatus.REJECTED account = resolve_account(account_id or "default") client = self._build_client(account) try: note_text = "AI Draft rejected. " if reason: note_text += f"Reason: {reason}" await client.create_note(conversation_id=conversation_id, text=note_text) self._pending.pop(key, None) logger.info( "Help Scout draft rejected: conv=%d reason=%s", conversation_id, reason, ) return OutboundResult(success=True, message_id="") except Exception as e: logger.exception("Help Scout reject failed: conv=%d", conversation_id) return OutboundResult(success=False, error="exception", detail=str(e)) finally: await client.close() def get_pending_drafts(self) -> list[dict]: return [ { "conversation_id": v["conversation_id"], "draft_id": v["draft_id"], "status": v["status"].value, "preview": v["draft_text"][:200], } for v in self._pending.values() ] @staticmethod def _build_client(account: dict) -> HelpScoutClient: auth = HelpScoutAuth(account["app_id"], account["app_secret"]) return HelpScoutClient(auth)