generated from kgod/ai-review-template
161 lines
6.4 KiB
Python
161 lines
6.4 KiB
Python
"""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)
|