generated from kgod/ai-review-template
152 lines
5.5 KiB
Python
152 lines
5.5 KiB
Python
"""基本信息阶段的卡片与校验:个人信息(PERSONAL_INFO)与意向职位(TARGET_POSITION)。
|
|
|
|
独立成模块以控制 fsm.py 行数;fsm.py 通过函数内 import 调用,避免循环依赖。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from .fsm import FSMError, Transition, assistant_turn, component
|
|
from .models import ComposerMode, Stage
|
|
from .validators import valid_email, valid_url
|
|
|
|
PERSONAL_INFO_FIELDS: list[dict[str, Any]] = [
|
|
{"key": "name", "label": "姓名", "kind": "text", "required": True},
|
|
{"key": "email", "label": "邮箱", "kind": "text", "required": True},
|
|
{"key": "city", "label": "所在地", "kind": "text", "required": False},
|
|
{"key": "portfolio_url", "label": "作品集链接", "kind": "text", "required": False},
|
|
]
|
|
|
|
TARGET_POSITION_FIELDS: list[dict[str, Any]] = [
|
|
{"key": "target_position", "label": "意向职位", "kind": "text", "required": True},
|
|
]
|
|
TARGET_POSITION_MAJOR_FIELDS: list[dict[str, Any]] = [
|
|
{"key": "major", "label": "专业", "kind": "text", "required": True},
|
|
{"key": "interests", "label": "感兴趣的方向", "kind": "text", "required": False},
|
|
]
|
|
|
|
|
|
def personal_info_transition(profile: dict[str, Any]) -> Transition:
|
|
return Transition(
|
|
Stage.PERSONAL_INFO,
|
|
profile,
|
|
assistant_turn(
|
|
"先填写你的个人信息,姓名和邮箱将用于简历抬头。",
|
|
[
|
|
component(
|
|
"RecordFields",
|
|
title="填写个人信息",
|
|
fields=PERSONAL_INFO_FIELDS,
|
|
show_description=False,
|
|
skippable=False,
|
|
)
|
|
],
|
|
mode=ComposerMode.UI_ONLY,
|
|
),
|
|
)
|
|
|
|
|
|
def validate_personal_info(payload: dict[str, Any]) -> dict[str, Any]:
|
|
"""姓名和邮箱必填;所在地和作品集链接选填。"""
|
|
name = str(payload.get("name") or "").strip()
|
|
email = str(payload.get("email") or "").strip()
|
|
city = str(payload.get("city") or "").strip()
|
|
portfolio_url = str(payload.get("portfolio_url") or "").strip()
|
|
missing: list[str] = []
|
|
if not name or len(name) > 64:
|
|
missing.append("name")
|
|
if not valid_email(email):
|
|
missing.append("email")
|
|
if portfolio_url and not valid_url(portfolio_url):
|
|
missing.append("portfolio_url")
|
|
if missing:
|
|
raise FSMError(
|
|
"invalid_personal_info",
|
|
"请填写姓名、有效邮箱,并确认作品集链接以 http(s):// 开头",
|
|
status_code=422,
|
|
missing_fields=missing,
|
|
)
|
|
return {
|
|
"name": name,
|
|
"email": email,
|
|
"city": city or None,
|
|
"portfolio_url": portfolio_url or None,
|
|
}
|
|
|
|
|
|
def target_position_transition(profile: dict[str, Any]) -> Transition:
|
|
return Transition(
|
|
Stage.TARGET_POSITION,
|
|
profile,
|
|
assistant_turn(
|
|
"先确认你是否已有明确的意向职位。",
|
|
[
|
|
component(
|
|
"ChoiceChips",
|
|
title="意向职位",
|
|
description="已有方向可直接填写;暂不确定时,先根据专业获得职位建议。",
|
|
options=[
|
|
{"value": "known", "label": "我有明确意向职位"},
|
|
{"value": "explore", "label": "暂不确定,想先获取建议"},
|
|
],
|
|
skippable=False,
|
|
)
|
|
],
|
|
mode=ComposerMode.UI_ONLY,
|
|
),
|
|
)
|
|
|
|
|
|
def target_position_input_transition(profile: dict[str, Any]) -> Transition:
|
|
return Transition(
|
|
Stage.TARGET_POSITION,
|
|
profile,
|
|
assistant_turn(
|
|
"填写一个意向职位,后续的优化和技能推荐会参考它。",
|
|
[component("RecordFields", title="填写意向职位", fields=TARGET_POSITION_FIELDS, show_description=False)],
|
|
mode=ComposerMode.UI_ONLY,
|
|
),
|
|
)
|
|
|
|
|
|
def target_position_major_transition(profile: dict[str, Any]) -> Transition:
|
|
return Transition(
|
|
Stage.TARGET_POSITION_MAJOR,
|
|
profile,
|
|
assistant_turn(
|
|
"填写专业和可选兴趣方向,我会给出可选择的职位建议。",
|
|
[component("RecordFields", title="专业与方向", fields=TARGET_POSITION_MAJOR_FIELDS, show_description=False)],
|
|
mode=ComposerMode.UI_ONLY,
|
|
),
|
|
)
|
|
|
|
|
|
def target_position_recommendation_transition(profile: dict[str, Any]) -> Transition:
|
|
suggestions = profile.get("target_position_suggestions") or []
|
|
options = [
|
|
{"value": item["title"], "label": item["title"], "description": item.get("reason")}
|
|
for item in suggestions if isinstance(item, dict) and item.get("title")
|
|
]
|
|
options.append({"value": "manual", "label": "手动填写职位"})
|
|
return Transition(
|
|
Stage.TARGET_POSITION_RECOMMENDATION,
|
|
profile,
|
|
assistant_turn(
|
|
"以下职位仅供探索参考,选择后仍可在简历预览中修改。",
|
|
[component("ChoiceChips", title="职位建议", options=options, skippable=False)],
|
|
mode=ComposerMode.UI_ONLY,
|
|
),
|
|
)
|
|
|
|
|
|
def validate_target_position(payload: dict[str, Any]) -> str:
|
|
value = str(payload.get("target_position") or "").strip()
|
|
if not value or len(value) > 32:
|
|
raise FSMError(
|
|
"invalid_target_position",
|
|
"请填写意向职位(32 字以内),或点击「暂时跳过」。",
|
|
status_code=422,
|
|
missing_fields=["target_position"],
|
|
)
|
|
return value |