219 lines
10 KiB
Python
219 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import TYPE_CHECKING, Optional
|
|
|
|
logger = logging.getLogger("cs_agent")
|
|
|
|
if TYPE_CHECKING:
|
|
from core.pydantic_ai_agent import AgentResponse, ConversationState, CustomerMessage, CustomerServiceAgent
|
|
|
|
|
|
async def handle_find_image_batch_flow(
|
|
agent: "CustomerServiceAgent",
|
|
*,
|
|
message: "CustomerMessage",
|
|
state: "ConversationState",
|
|
customer_text: str,
|
|
shop_type: str,
|
|
) -> Optional["AgentResponse"]:
|
|
"""Handle find-image collecting/quote flow. Return response when handled."""
|
|
from core.pydantic_ai_agent import AgentResponse, TRANSFER_MESSAGE
|
|
|
|
if not (shop_type == "find_image" and agent._is_batch_quote_enabled(message.from_id, message.acc_id)):
|
|
return None
|
|
|
|
incoming_urls = agent._extract_image_urls(customer_text)
|
|
text_without_urls = agent._strip_urls_from_text(customer_text)
|
|
short_intent = agent._classify_short_customer_text(text_without_urls)
|
|
|
|
if incoming_urls:
|
|
is_related_followup = bool(text_without_urls and agent._is_related_image_followup_intent(text_without_urls))
|
|
for u in incoming_urls:
|
|
if u not in state.pending_image_urls:
|
|
state.pending_image_urls.append(u)
|
|
if text_without_urls:
|
|
agent._append_requirement(state, text_without_urls)
|
|
if is_related_followup:
|
|
agent._append_requirement(state, "与上一张相关(截图/局部细节)")
|
|
state.image_count = len(state.pending_image_urls)
|
|
agent._refresh_quote_phase(state, "collecting")
|
|
agent._sync_pending_quote_state(message.from_id, state)
|
|
|
|
if agent._is_batch_finish_intent(
|
|
text=customer_text,
|
|
state=state,
|
|
has_incoming_urls=bool(incoming_urls),
|
|
):
|
|
should_defer = agent._should_defer_batch_quote(state, mark_ready=True)
|
|
agent._sync_pending_quote_state(message.from_id, state)
|
|
if should_defer:
|
|
defer_fallback = "图片和需求我都收齐了,我先整理下,马上给你报总价。"
|
|
defer_reply = await agent._render_collection_reply_with_ai(
|
|
message=message,
|
|
state=state,
|
|
scene="quote_defer_notice",
|
|
intent_hint="确认已收齐图片与需求,先承接,告知稍后马上报价。",
|
|
fallback=defer_fallback,
|
|
)
|
|
state.last_reply_at = datetime.now()
|
|
logger.info("[REPLY->CUSTOMER] %s", defer_reply)
|
|
return AgentResponse(reply=defer_reply, should_reply=True, need_transfer=False)
|
|
quote_res = await agent._quote_pending_images(state, message)
|
|
reply_text = agent._colloquialize_reply(quote_res.get("reply", ""))
|
|
reply_text = await agent._rewrite_reply_with_ai(
|
|
message=message,
|
|
state=state,
|
|
reply=reply_text,
|
|
scene="batch_quote_reply",
|
|
)
|
|
need_transfer = bool(quote_res.get("need_transfer"))
|
|
state.last_reply_at = datetime.now()
|
|
logger.info("[REPLY->CUSTOMER] %s", reply_text)
|
|
return AgentResponse(
|
|
reply=reply_text,
|
|
should_reply=not need_transfer,
|
|
need_transfer=need_transfer,
|
|
transfer_msg=TRANSFER_MESSAGE if need_transfer else "",
|
|
)
|
|
|
|
ack_fallback = "图片收到了,你有补充就继续发,我这边一起看。"
|
|
ack_intent = (
|
|
"告知图片已收到;如果客户继续发图就继续收,发完可统一报价。"
|
|
if not is_related_followup
|
|
else "告知这是和上一张相关的截图/局部图,已按同一需求一起处理。"
|
|
)
|
|
ack = await agent._render_collection_reply_with_ai(
|
|
message=message,
|
|
state=state,
|
|
scene="collect_ack",
|
|
intent_hint=ack_intent,
|
|
fallback=ack_fallback,
|
|
)
|
|
state.last_reply_at = datetime.now()
|
|
logger.info("[REPLY->CUSTOMER] %s", ack)
|
|
return AgentResponse(reply=ack, should_reply=True, need_transfer=False)
|
|
|
|
if not state.pending_image_urls:
|
|
return None
|
|
|
|
if text_without_urls:
|
|
if short_intent == "finish_signal":
|
|
agent._mark_quote_ready(state)
|
|
elif short_intent == "progress_query":
|
|
if state.quote_phase != "ready_to_quote":
|
|
agent._refresh_quote_phase(state, "waiting_result")
|
|
elif short_intent == "ack":
|
|
if state.quote_phase != "ready_to_quote":
|
|
agent._refresh_quote_phase(state, "collecting")
|
|
else:
|
|
agent._append_requirement(state, text_without_urls)
|
|
agent._refresh_quote_phase(state, "collecting")
|
|
agent._sync_pending_quote_state(message.from_id, state)
|
|
if agent._is_find_image_not_edit_conflict(text_without_urls):
|
|
clarify_fallback = "明白你是要找图,不是做图。你说下要找原图、同款还是高清版,我按这个给你找。"
|
|
clarify = await agent._render_collection_reply_with_ai(
|
|
message=message,
|
|
state=state,
|
|
scene="find_not_edit_clarify",
|
|
intent_hint="确认客户要找图不是做图,并追问是找原图/同款/高清版。",
|
|
fallback=clarify_fallback,
|
|
)
|
|
state.last_reply_at = datetime.now()
|
|
logger.info("[REPLY->CUSTOMER] %s", clarify)
|
|
return AgentResponse(reply=clarify, should_reply=True, need_transfer=False)
|
|
|
|
if state.quote_phase == "ready_to_quote" and state.quote_ready_turns <= 0 and short_intent in {"progress_query", "ack", "finish_signal"}:
|
|
quote_res = await agent._quote_pending_images(state, message)
|
|
reply_text = agent._colloquialize_reply(quote_res.get("reply", ""))
|
|
reply_text = await agent._rewrite_reply_with_ai(
|
|
message=message,
|
|
state=state,
|
|
reply=reply_text,
|
|
scene="batch_quote_reply",
|
|
)
|
|
need_transfer = bool(quote_res.get("need_transfer"))
|
|
state.last_reply_at = datetime.now()
|
|
logger.info("[REPLY->CUSTOMER] %s", reply_text)
|
|
return AgentResponse(
|
|
reply=reply_text,
|
|
should_reply=not need_transfer,
|
|
need_transfer=need_transfer,
|
|
transfer_msg=TRANSFER_MESSAGE if need_transfer else "",
|
|
)
|
|
|
|
if short_intent == "progress_query" or agent._is_result_followup_query(text_without_urls):
|
|
progress_fallback = "我这边在跟进了,一有结果马上发你。"
|
|
progress = await agent._render_collection_reply_with_ai(
|
|
message=message,
|
|
state=state,
|
|
scene="collect_progress",
|
|
intent_hint="承接客户的进度/结果追问,简短说明正在跟进,有结果会第一时间回复。",
|
|
fallback=progress_fallback,
|
|
)
|
|
state.last_reply_at = datetime.now()
|
|
logger.info("[REPLY->CUSTOMER] %s", progress)
|
|
return AgentResponse(reply=progress, should_reply=True, need_transfer=False)
|
|
|
|
if agent._needs_clarification_in_collecting(text_without_urls):
|
|
ask_fallback = "你再补一句具体要什么效果,我马上按你的要求来。"
|
|
ask = await agent._render_collection_reply_with_ai(
|
|
message=message,
|
|
state=state,
|
|
scene="collect_clarify",
|
|
intent_hint="客户表达不清,礼貌请对方补充一句关键需求,不要机械,不要生硬。",
|
|
fallback=ask_fallback,
|
|
)
|
|
state.last_reply_at = datetime.now()
|
|
logger.info("[REPLY->CUSTOMER] %s", ask)
|
|
return AgentResponse(reply=ask, should_reply=True, need_transfer=False)
|
|
if agent._is_batch_finish_intent(
|
|
text=customer_text,
|
|
state=state,
|
|
has_incoming_urls=False,
|
|
):
|
|
should_defer = agent._should_defer_batch_quote(state, mark_ready=True)
|
|
agent._sync_pending_quote_state(message.from_id, state)
|
|
if should_defer:
|
|
defer_fallback = "收到,我先把这批图过一遍,马上给你总价。"
|
|
defer_reply = await agent._render_collection_reply_with_ai(
|
|
message=message,
|
|
state=state,
|
|
scene="quote_defer_notice",
|
|
intent_hint="确认已收齐,先承接并告知稍后马上报价。",
|
|
fallback=defer_fallback,
|
|
)
|
|
state.last_reply_at = datetime.now()
|
|
logger.info("[REPLY->CUSTOMER] %s", defer_reply)
|
|
return AgentResponse(reply=defer_reply, should_reply=True, need_transfer=False)
|
|
quote_res = await agent._quote_pending_images(state, message)
|
|
reply_text = agent._colloquialize_reply(quote_res.get("reply", ""))
|
|
reply_text = await agent._rewrite_reply_with_ai(
|
|
message=message,
|
|
state=state,
|
|
reply=reply_text,
|
|
scene="batch_quote_reply",
|
|
)
|
|
need_transfer = bool(quote_res.get("need_transfer"))
|
|
state.last_reply_at = datetime.now()
|
|
logger.info("[REPLY->CUSTOMER] %s", reply_text)
|
|
return AgentResponse(
|
|
reply=reply_text,
|
|
should_reply=not need_transfer,
|
|
need_transfer=need_transfer,
|
|
transfer_msg=TRANSFER_MESSAGE if need_transfer else "",
|
|
)
|
|
|
|
remind_fallback = "需求我记上了,你继续发图,或者让我直接给你报价都行。"
|
|
remind = await agent._render_collection_reply_with_ai(
|
|
message=message,
|
|
state=state,
|
|
scene="collect_remind",
|
|
intent_hint="确认需求已记录,引导客户继续补图或直接让你报价。",
|
|
fallback=remind_fallback,
|
|
)
|
|
state.last_reply_at = datetime.now()
|
|
logger.info("[REPLY->CUSTOMER] %s", remind)
|
|
return AgentResponse(reply=remind, should_reply=True, need_transfer=False)
|