落地私聊图片询价:识图当口播复用三线文字流程,图上航线三字码保持原样,避免被改写成中文地名。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,7 +12,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import threading
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
from agent.ledger.http_ledger import HttpLedger
|
||||
@@ -55,6 +55,43 @@ class FlowSession:
|
||||
pending_product_quote: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
def _session_payload(sess: FlowSession) -> dict[str, Any]:
|
||||
"""书签转 JSON。tuple 改 list,方便 HTTP/Worker 共用。"""
|
||||
data = asdict(sess)
|
||||
data["allowed"] = list(sess.allowed or ())
|
||||
data["invited_userids"] = list(sess.invited_userids or ())
|
||||
data["invited_product_names"] = list(sess.invited_product_names or ())
|
||||
data["facts"] = {str(k): str(v) for k, v in (sess.facts or {}).items()}
|
||||
return data
|
||||
|
||||
|
||||
def _session_from_payload(raw: dict[str, Any]) -> FlowSession:
|
||||
"""Redis JSON → 书签。缺字段用默认,避免旧键打崩。"""
|
||||
return FlowSession(
|
||||
sender_id=str(raw.get("sender_id") or ""),
|
||||
thread_id=str(raw.get("thread_id") or raw.get("sender_id") or ""),
|
||||
wait_version=int(raw.get("wait_version") or 0),
|
||||
phase=str(raw.get("phase") or "idle"),
|
||||
business_line=str(raw.get("business_line") or ""),
|
||||
facts={str(k): str(v) for k, v in dict(raw.get("facts") or {}).items()},
|
||||
work_order_no=str(raw.get("work_order_no") or ""),
|
||||
first_or_same=str(raw.get("first_or_same") or "first"),
|
||||
history_work_order_no=str(raw.get("history_work_order_no") or ""),
|
||||
quote=dict(raw.get("quote") or {}),
|
||||
status=str(raw.get("status") or ""),
|
||||
allowed=tuple(str(x) for x in (raw.get("allowed") or ())),
|
||||
immutable_text=str(raw.get("immutable_text") or ""),
|
||||
collab_chat_id=str(raw.get("collab_chat_id") or ""),
|
||||
collab_facts={str(k): str(v) for k, v in dict(raw.get("collab_facts") or {}).items()},
|
||||
invited_userids=tuple(str(x) for x in (raw.get("invited_userids") or ())),
|
||||
invited_sales_name=str(raw.get("invited_sales_name") or ""),
|
||||
invited_product_names=tuple(str(x) for x in (raw.get("invited_product_names") or ())),
|
||||
deal_version=int(raw.get("deal_version") or 0),
|
||||
collab_ended=bool(raw.get("collab_ended")),
|
||||
pending_product_quote=dict(raw.get("pending_product_quote") or {}),
|
||||
)
|
||||
|
||||
|
||||
class AirTextInquiryFlow:
|
||||
"""
|
||||
空运文字询价 Owner。海运走 SeaTextInquiryFlow,陆运走 LandTextInquiryFlow。
|
||||
@@ -63,12 +100,15 @@ class AirTextInquiryFlow:
|
||||
价格卡按工单号另存一份当时会话:点哪张卡就生成哪张卡当时的报价单。
|
||||
"""
|
||||
|
||||
def __init__(self, ledger: Any = None, group_client: Any = None) -> None:
|
||||
bookmark_kind = "air"
|
||||
|
||||
def __init__(self, ledger: Any = None, group_client: Any = None, *, bookmark_store: Any = None) -> None:
|
||||
self._ledger = ledger or build_ledger()
|
||||
self._lock = threading.Lock()
|
||||
self._by_sender: dict[str, FlowSession] = {}
|
||||
self._by_ticket: dict[str, FlowSession] = {}
|
||||
self._group_client = group_client
|
||||
self._bookmark_store = bookmark_store
|
||||
|
||||
@property
|
||||
def ledger(self) -> Any:
|
||||
@@ -83,8 +123,16 @@ class AirTextInquiryFlow:
|
||||
return self._group_client
|
||||
|
||||
def session_of(self, sender_id: str) -> Optional[FlowSession]:
|
||||
sid = (sender_id or "").strip()
|
||||
with self._lock:
|
||||
return self._by_sender.get(sender_id)
|
||||
hit = self._by_sender.get(sid)
|
||||
if hit is not None:
|
||||
return hit
|
||||
loaded = self._load_bookmark(sid)
|
||||
if loaded is None:
|
||||
return None
|
||||
self._save(loaded)
|
||||
return loaded
|
||||
|
||||
def session_by_work_order(self, work_order_no: str) -> Optional[FlowSession]:
|
||||
"""用工单号找回书签,不校验 sender。群成交卡校验用。"""
|
||||
@@ -126,17 +174,14 @@ class AirTextInquiryFlow:
|
||||
必须校验 sender_id,禁止用别人的工单号串会话。
|
||||
"""
|
||||
no = copy.work_order_from_card_meta(card_meta, text=text)
|
||||
if not no:
|
||||
return self.session_of(sender_id)
|
||||
with self._lock:
|
||||
if no:
|
||||
hit = self._by_ticket.get(no)
|
||||
if hit and hit.sender_id == sender_id:
|
||||
return hit
|
||||
elif not no:
|
||||
return self._by_sender.get(sender_id)
|
||||
if no:
|
||||
# 卡上有工单号就只动这一单;进程刚重启时从主账收回,禁止落到别人/最新单。
|
||||
return self._load_ticket_session(sender_id, no)
|
||||
return None
|
||||
hit = self._by_ticket.get(no)
|
||||
if hit and hit.sender_id == sender_id:
|
||||
return hit
|
||||
# 卡上有工单号就只动这一单;进程刚重启时从主账收回,禁止落到别人/最新单。
|
||||
return self._load_ticket_session(sender_id, no)
|
||||
|
||||
def _save(self, sess: FlowSession) -> None:
|
||||
with self._lock:
|
||||
@@ -144,6 +189,53 @@ class AirTextInquiryFlow:
|
||||
no = (sess.work_order_no or "").strip()
|
||||
if no:
|
||||
self._by_ticket[no] = sess
|
||||
self._persist_bookmark(sess)
|
||||
|
||||
def drop_current_bookmark(self, sender_id: str) -> Optional[FlowSession]:
|
||||
"""
|
||||
发图新开:丢掉当前书签,已出号会话仍留在 _by_ticket。
|
||||
|
||||
未出号的补问/核对草稿从此不再被文字当「当前单」。
|
||||
HTTP 与 Worker 必须同时丢掉 Redis 里那份,否则补「托盘」会问运输方式。
|
||||
"""
|
||||
sid = (sender_id or "").strip()
|
||||
if not sid:
|
||||
return None
|
||||
with self._lock:
|
||||
old = self._by_sender.pop(sid, None)
|
||||
self._delete_bookmark(sid)
|
||||
return old
|
||||
|
||||
def _persist_bookmark(self, sess: FlowSession) -> None:
|
||||
from agent.redis_coord.flow_bookmark import save_flow_bookmark
|
||||
|
||||
save_flow_bookmark(
|
||||
self.bookmark_kind,
|
||||
sess.sender_id,
|
||||
_session_payload(sess),
|
||||
store=self._bookmark_store,
|
||||
)
|
||||
|
||||
def _load_bookmark(self, sender_id: str) -> Optional[FlowSession]:
|
||||
from agent.redis_coord.flow_bookmark import load_flow_bookmark
|
||||
|
||||
raw = load_flow_bookmark(
|
||||
self.bookmark_kind,
|
||||
sender_id,
|
||||
store=self._bookmark_store,
|
||||
)
|
||||
if not raw:
|
||||
return None
|
||||
return _session_from_payload(raw)
|
||||
|
||||
def _delete_bookmark(self, sender_id: str) -> None:
|
||||
from agent.redis_coord.flow_bookmark import delete_flow_bookmark
|
||||
|
||||
delete_flow_bookmark(
|
||||
self.bookmark_kind,
|
||||
sender_id,
|
||||
store=self._bookmark_store,
|
||||
)
|
||||
|
||||
def _load_ticket_session(self, sender_id: str, work_order_no: str) -> Optional[FlowSession]:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user