"""Continuation and revision turns for already-confirmed Builder entries.""" from __future__ import annotations from types import SimpleNamespace from typing import Any from ..fsm import FIELD_LABELS, Transition, assistant_turn, component from ..models import ComposerMode, Stage from .candidate import _candidate_rewrite from .constants import SECTION_HEADINGS, u from .expander_provider import shared_expander from .predicates import _entry_by_id from .state import ( _highlights, _merge_fact_text, _public_entry, _reset_gap_state, _set_stream_phases, ensure_builder_state, ) def reconcile_last_confirmed_entry(profile: dict[str, Any], resume_content: dict[str, Any]) -> None: """Keep Builder's continuation pointer aligned after document ID reconciliation.""" state = ensure_builder_state(profile) reference = state.get("last_confirmed_entry") if not isinstance(reference, dict): return entry_id = str(reference.get("entry_id") or "") if entry_id and _entry_by_id(resume_content, entry_id) is not None: return section_kind = str(reference.get("section") or "") section = next( (item for item in resume_content.get("sections") or [] if item.get("kind") == section_kind), None, ) entries = section.get("items") if isinstance(section, dict) else None if isinstance(entries, list) and entries and isinstance(entries[-1], dict): reference["entry_id"] = str(entries[-1].get("id") or "") return state["last_confirmed_entry"] = None def _continue_recent_entry( agent: Any, profile: dict[str, Any], content: str, resume_content: dict[str, Any] ) -> Transition | None: state = ensure_builder_state(profile) reference = state.get("last_confirmed_entry") if not isinstance(reference, dict): return None entry_id = str(reference.get("entry_id") or "").strip() target = _entry_by_id(resume_content, entry_id) if not entry_id or target is None: state["last_confirmed_entry"] = None return None section_data, saved_entry = target section = str(section_data.get("kind") or reference.get("section") or "") if section not in SECTION_HEADINGS: return None entry = _public_entry(saved_entry) original_facts = str(reference.get("fact_description") or entry.get("description") or "").strip() entry["description"] = _merge_fact_text(original_facts, content.strip()) entry["highlights"] = _highlights(str(entry["description"])) entry["_proposal"] = _candidate_rewrite(agent, profile, entry, section) state["active_section"] = section state["identity_draft"] = _public_entry(saved_entry) state["editing_base_entry"] = _public_entry(saved_entry) state["editing_entry_id"] = entry_id state["pending_entry"] = entry state["selection_candidates"] = [] _reset_gap_state(state) _set_stream_phases(profile, "structuring", "rewriting") return Transition( Stage.BUILDER_CONVERSATION, profile, assistant_turn( "好的,已收到这条补充信息。我已基于原内容重新整理候选改写,尚未写入简历。请确认后再保存。", [ component( "ExperienceConfirmCard", title="确认更新这段经历", value=entry, labels=FIELD_LABELS, ai_proposal=entry["_proposal"], ) ], mode=ComposerMode.CHAT, ), ) def _regenerate_entry_candidate( agent: Any, profile: dict[str, Any], section_data: dict[str, Any], saved_entry: dict[str, Any], instruction: str, ) -> Transition: """Re-run the light rewrite for a confirmed entry (e.g. "帮我重新优化这段").""" state = ensure_builder_state(profile) section = str(section_data.get("kind") or "") entry = _public_entry(saved_entry) entry["_proposal"] = _candidate_rewrite(agent, profile, entry, section, instruction=instruction) state["active_section"] = section state["identity_draft"] = _public_entry(saved_entry) state["editing_base_entry"] = _public_entry(saved_entry) state["editing_entry_id"] = saved_entry.get("id") state["pending_entry"] = entry state["selection_candidates"] = [] _reset_gap_state(state) _set_stream_phases(profile, "structuring", "rewriting") return Transition( Stage.BUILDER_CONVERSATION, profile, assistant_turn( "好的,已按你的要求重新生成候选改写,尚未写入简历。请在原始内容与候选稿之间选择。", [ component( "ExperienceConfirmCard", title="确认更新这段经历", value=entry, labels=FIELD_LABELS, ai_proposal=entry["_proposal"], ) ], mode=ComposerMode.CHAT, ), ) def _redisplay_revision_candidate(agent: Any, profile: dict[str, Any], instruction: str) -> Transition: state = ensure_builder_state(profile) section = str(state.get("active_section") or "education") entry = _public_entry(dict(state.get("identity_draft") or {})) entry["_proposal"] = _candidate_rewrite(agent, profile, entry, section, instruction=instruction, ensure_facts=True) state["pending_entry"] = entry state["revision_mode"] = False _set_stream_phases(profile, "structuring", "rewriting") return Transition( Stage.BUILDER_CONVERSATION, profile, assistant_turn( u("好的,已理解你的调整说明。我会保留这段已确认的事实,并重新展示候选改写供你确认。"), [component("ExperienceConfirmCard", title=u("确认更新这段经历"), value=entry, labels=FIELD_LABELS, ai_proposal=entry["_proposal"])], mode=ComposerMode.CHAT, ), ) def _revise_pending_candidate(profile: dict[str, Any], pending: dict[str, Any], instruction: str) -> Transition: """Regenerate the pending proposal with guidance (confirm card's revise action). Card events carry no agent reference, so the shared expander is used. """ state = ensure_builder_state(profile) state["identity_draft"] = _public_entry(pending) agent = SimpleNamespace(expander=shared_expander()) return _redisplay_revision_candidate(agent, profile, instruction)