generated from kgod/ai-review-template
feat: initialize resume agent with OfferPai sync
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
"""Skill-suggestion cards and selection handling for the Builder."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
from typing import Any
|
||||
|
||||
from ..fsm import FSMError, Transition, component
|
||||
from ..models import Stage
|
||||
from ..resume_skill_advisor import recommend_skill_candidates
|
||||
from ..skill_groups import update_skill_groups
|
||||
from .constants import u
|
||||
from .state import _set_stream_phases
|
||||
from .turns import _next_step_turn, recommended_section
|
||||
|
||||
|
||||
def _skill_choice_card(candidates: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
return component(
|
||||
"ChoiceChips",
|
||||
module="builder_skill_select",
|
||||
title=u("确认岗位技能"),
|
||||
description=u("请选择你愿意确认加入简历的技能;未选择的候选不会写入。没有合适的也可以暂时跳过。"),
|
||||
multiple=True,
|
||||
skippable=True,
|
||||
skip_label=u("暂不添加"),
|
||||
options=[
|
||||
{
|
||||
"value": str(candidate["skill"]),
|
||||
"label": f'{candidate["skill"]}{u("(")}{candidate["category"]}{u(")")}',
|
||||
}
|
||||
for candidate in candidates
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def _builder_skill_candidates(
|
||||
profile: dict[str, Any],
|
||||
resume_content: dict[str, Any],
|
||||
skill_suggester: Any | None,
|
||||
) -> list[dict[str, Any]]:
|
||||
if skill_suggester is None:
|
||||
return []
|
||||
existing = [
|
||||
str(skill).strip()
|
||||
for group in resume_content.get("skill_groups") or []
|
||||
if isinstance(group, dict)
|
||||
for skill in group.get("skills") or []
|
||||
if str(skill).strip()
|
||||
]
|
||||
working_profile = deepcopy(profile)
|
||||
working_profile["tags"] = {**dict(working_profile.get("tags") or {}), "skills": existing}
|
||||
facts: list[dict[str, Any]] = []
|
||||
for section in resume_content.get("sections") or []:
|
||||
if not isinstance(section, dict):
|
||||
continue
|
||||
for entry in section.get("items") or []:
|
||||
if isinstance(entry, dict):
|
||||
facts.append(deepcopy(entry))
|
||||
working_profile["experiences"] = facts
|
||||
return recommend_skill_candidates(
|
||||
working_profile,
|
||||
existing,
|
||||
u("根据我选择的目标岗位推荐可确认技能"),
|
||||
skill_suggester,
|
||||
)
|
||||
|
||||
|
||||
def _process_skill_selection(
|
||||
profile: dict[str, Any],
|
||||
state: dict[str, Any],
|
||||
action: str,
|
||||
payload: dict[str, Any],
|
||||
resume_content: dict[str, Any],
|
||||
) -> Transition:
|
||||
if action == "skip":
|
||||
state["pending_skill_candidates"] = []
|
||||
_set_stream_phases(profile, "suggesting_next")
|
||||
return Transition(
|
||||
Stage.BUILDER_CONVERSATION,
|
||||
profile,
|
||||
_next_step_turn(recommended_section(profile, resume_content), prefix=u("好的,先不添加技能。")),
|
||||
lifecycle="dismissed",
|
||||
)
|
||||
if action != "select":
|
||||
raise FSMError("invalid_builder_skill_selection", "Confirm or skip the skill suggestions", status_code=422)
|
||||
selected = payload.get("values")
|
||||
if not isinstance(selected, list):
|
||||
selected = [payload.get("value")]
|
||||
allowed = {
|
||||
str(item.get("skill") or "").strip()
|
||||
for item in state.get("pending_skill_candidates") or []
|
||||
if isinstance(item, dict) and str(item.get("skill") or "").strip()
|
||||
}
|
||||
chosen = [str(value).strip() for value in selected if str(value or "").strip() in allowed]
|
||||
if not chosen:
|
||||
raise FSMError("invalid_builder_skill_selection", "Select at least one suggested skill or skip", status_code=422)
|
||||
existing = [
|
||||
str(skill).strip()
|
||||
for group in resume_content.get("skill_groups") or []
|
||||
if isinstance(group, dict)
|
||||
for skill in group.get("skills") or []
|
||||
if str(skill).strip()
|
||||
]
|
||||
preferred = {
|
||||
str(item.get("skill") or "").strip(): str(item.get("category") or "").strip()
|
||||
for item in state.get("pending_skill_candidates") or []
|
||||
if isinstance(item, dict) and str(item.get("skill") or "").strip() and str(item.get("category") or "").strip()
|
||||
}
|
||||
content = update_skill_groups(resume_content, [*existing, *chosen], preferred_categories=preferred)
|
||||
state["pending_skill_candidates"] = []
|
||||
_set_stream_phases(profile, "saving", "suggesting_next")
|
||||
return Transition(
|
||||
Stage.BUILDER_CONVERSATION,
|
||||
profile,
|
||||
_next_step_turn(
|
||||
recommended_section(profile, content),
|
||||
prefix=u("已添加") + " " + u("、").join(chosen) + u("。"),
|
||||
),
|
||||
lifecycle="confirmed",
|
||||
resume_content=content,
|
||||
)
|
||||
Reference in New Issue
Block a user