feat: initialize resume agent with OfferPai sync

This commit is contained in:
Codex
2026-08-05 20:20:18 +08:00
commit 61ec750031
197 changed files with 33291 additions and 0 deletions
@@ -0,0 +1,95 @@
"""Builder 个人总结再生入口:finish 按钮与对话指令统一走"显式请求即重生成"
agent 侧只在总结缺失或 stale 时才生成(避免自动覆盖用户手工文本);用户的显式
请求必须先把现有总结标记为 stale,让既有闸门放行。对话路径无法直接写简历
add_message 不合并 resume_content),所以走"生成候选 → ChoiceChips 确认 →
组件事件写入"的既有 Builder 模式。
"""
from __future__ import annotations
import re
from typing import Any
from ..fsm import FSMError, Transition, assistant_turn, component
from ..models import ComposerMode, Stage
from ..resume_document import mark_profile_summary_stale, set_generated_profile_summary
from .constants import u
from .state import _set_stream_phases, ensure_builder_state
SUMMARY_APPLY_MODULE = "builder_summary_apply"
_REGEN_VERB = re.compile(r"(重新|再次|再来|重写|更新|刷新|换|再).{0,6}总结")
_ASK_GENERATE = re.compile(r"(?:帮我|请|我要|我想|给我).{0,6}生成.{0,4}总结|^生成.{0,4}总结")
def requests_summary_regen(content: str) -> bool:
""""重新生成个人总结"类指令;提供总结原文或陈述事实的消息不算。"""
normalized = re.sub(r"[\s,,。;;!?]", "", content.casefold())
if "总结" not in normalized or "总结是" in normalized:
return False
return bool(_REGEN_VERB.search(normalized) or _ASK_GENERATE.search(normalized))
def finish_transition(profile: dict[str, Any], resume_content: dict[str, Any]) -> Transition:
""""完成并生成个人总结":已有总结也强制重生成(先标记 stale 放行闸门)。"""
_set_stream_phases(profile, "saving")
return Transition(
Stage.BUILDER_CONVERSATION,
profile,
assistant_turn(
u("好的,已根据当前简历预览信息生成个人总结,内容仍可在右侧预览中编辑。"),
[],
mode=ComposerMode.CHAT,
),
resume_content=mark_profile_summary_stale(resume_content),
generate_profile_summary=True,
)
def summary_regen_turn(agent: Any, profile: dict[str, Any], resume_content: dict[str, Any]) -> Transition:
"""对话"重新生成个人总结":立即生成候选文本,确认后经组件事件写入简历。"""
try:
proposal = agent.profile_summary_generator.generate(resume_content)
except Exception:
return Transition(
Stage.BUILDER_CONVERSATION,
profile,
assistant_turn(u("个人总结生成失败,请稍后重试。"), [], mode=ComposerMode.CHAT),
)
ensure_builder_state(profile)["pending_summary_proposal"] = proposal
card = component(
"ChoiceChips",
module=SUMMARY_APPLY_MODULE,
options=[
{"value": "apply", "label": u("写入简历")},
{"value": "dismiss", "label": u("暂不写入")},
],
)
return Transition(
Stage.BUILDER_CONVERSATION,
profile,
assistant_turn(u("好的,已根据当前简历内容重新生成个人总结:\n") + proposal, [card], mode=ComposerMode.CHAT),
)
def apply_summary_proposal(
profile: dict[str, Any], action: str, payload: dict[str, Any], resume_content: dict[str, Any]
) -> Transition:
"""确认卡事件:apply 写入候选总结(随组件事件合并进简历),否则丢弃。"""
if action != "select":
raise FSMError("invalid_builder_choice", "Choose whether to apply the summary", status_code=422)
proposal = str(ensure_builder_state(profile).pop("pending_summary_proposal", "") or "").strip()
if str(payload.get("value") or "") != "apply" or not proposal:
return Transition(
Stage.BUILDER_CONVERSATION,
profile,
assistant_turn(u("好的,保留当前个人总结。"), [], mode=ComposerMode.CHAT),
)
content = set_generated_profile_summary(mark_profile_summary_stale(resume_content), proposal, replace_stale=True)
return Transition(
Stage.BUILDER_CONVERSATION,
profile,
assistant_turn(u("已写入新的个人总结,仍可在右侧预览中编辑。"), [], mode=ComposerMode.CHAT),
lifecycle="confirmed",
resume_content=content,
)