76 lines
2.8 KiB
Python
76 lines
2.8 KiB
Python
"""角色卡导入 / 导出。
|
||
|
||
rewrite/1.0 M2b:导入不再直接建人物——ST V1/V2/V3 卡解析后产出**可审阅灵胚**
|
||
(产品 PRD §5.2「导入默认生成可审阅灵胚」);``character_book`` 暂存于灵胚道体草稿,
|
||
启镜时物化为新人物的世谱书与条目(见 jingsheng.service.activate_embryo)。
|
||
导出方向保持:人物 → ST V3 卡静态快照。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from app.core.errors import AppError
|
||
from app.db.session import AsyncSessionLocal
|
||
from app.models.character import Character
|
||
from app.modules.imports.card_parser import parse_character_card
|
||
from app.modules.imports.export_mapping import character_to_card
|
||
|
||
|
||
def card_to_embryo_payload(raw: bytes) -> dict:
|
||
"""解析角色卡字节 → 灵胚创建载荷(name/description/identity_baseline)。
|
||
|
||
identity_baseline 保存卡的全部有效字段与原始 character_book,
|
||
启镜前可经照心编辑(PATCH /embryo-drafts/{id})审阅修改。
|
||
"""
|
||
card = parse_character_card(raw)
|
||
description = "\n\n".join(
|
||
part
|
||
for part in (
|
||
card.description.strip(),
|
||
f"【性格】{card.personality.strip()}" if card.personality.strip() else "",
|
||
f"【场景】{card.scenario.strip()}" if card.scenario.strip() else "",
|
||
)
|
||
if part
|
||
)
|
||
baseline = {
|
||
"import_card": {
|
||
"spec_version": card.spec_version,
|
||
"description": card.description,
|
||
"personality": card.personality,
|
||
"scenario": card.scenario,
|
||
"first_mes": card.first_mes,
|
||
"mes_example": card.mes_example,
|
||
"system_prompt": card.system_prompt,
|
||
"post_history_instructions": card.post_history_instructions,
|
||
"alternate_greetings": card.alternate_greetings,
|
||
"tags": card.tags,
|
||
"creator": card.creator,
|
||
"character_version": card.character_version,
|
||
"character_book": card.character_book,
|
||
}
|
||
}
|
||
name = card.name.strip() or "未命名灵胚"
|
||
return {
|
||
"name": name[:120],
|
||
"description": description or None,
|
||
"identity_baseline": baseline,
|
||
"book_entry_count": _count_book_entries(card.character_book),
|
||
}
|
||
|
||
|
||
def _count_book_entries(book: dict | None) -> int:
|
||
if not book:
|
||
return 0
|
||
entries = book.get("entries")
|
||
if isinstance(entries, (dict, list)):
|
||
return len(entries)
|
||
return 0
|
||
|
||
|
||
async def export_character_card(character_id: str, owner_scope: str) -> dict:
|
||
"""导出人物为 ST V3 卡静态快照(丢弃演化维度)。"""
|
||
async with AsyncSessionLocal() as session:
|
||
character = await session.get(Character, character_id)
|
||
if character is None or character.owner_scope != owner_scope:
|
||
raise AppError("not_found", "人物不存在。")
|
||
return character_to_card(character)
|