初始化提交

This commit is contained in:
chenxin committed 2026-08-21 16:00:13 +08:00
commit 887c1f449e
963 files changed
+166869

No files matched your search

+117
View File
@@ -0,0 +1,117 @@
"""角色卡导入 / 导出服务。"""
from __future__ import annotations
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.errors import AppError
from app.db.session import AsyncSessionLocal
from app.models.character import Character
from app.models.lorebook import Lorebook, LorebookEntry
from app.modules.imports.card_parser import parse_character_card
from app.modules.imports.export_mapping import character_to_card
from app.modules.imports.mapping import (
card_to_daoti,
card_to_profile,
character_book_to_entries,
)
from app.repositories import lorebook as lorebook_repo
from app.repositories.json_utils import dumps
from app.schemas.character import ImportCardResponse
async def import_character_card(
session: AsyncSession, raw: bytes, owner_scope: str
) -> Character:
"""解析角色卡字节 → 建 Character + 世谱(character_book)。返回 Character ORM。
``personality`` 写入道体 ``personality_summary`` 作初始值;
``character_book`` 转为该角色的世谱书 + 条目。
"""
card = parse_character_card(raw)
daoti = card_to_daoti(card)
character = Character(
name=card.name or "未命名角色",
display_name=card.nickname or card.name or None,
language="zh-CN",
profile_json=dumps(card_to_profile(card)),
model_policy_json="{}",
personality_summary=daoti["personality_summary"],
privacy_level="normal",
owner_scope=owner_scope,
)
session.add(character)
await session.flush()
entries_data = character_book_to_entries(card.character_book)
if entries_data:
book = card.character_book or {}
lorebook = Lorebook(
character_id=character.id,
name=book.get("name", "") or card.name,
description=book.get("description", ""),
scan_depth=book.get("scan_depth"),
token_budget=book.get("token_budget"),
recursive_scanning=bool(book.get("recursive_scanning", False)),
scope="character",
owner_scope=owner_scope,
)
session.add(lorebook)
await session.flush()
for entry in entries_data:
session.add(
LorebookEntry(
lorebook_id=lorebook.id,
keys_json=dumps(entry["keys"]),
content=entry["content"],
secondary_keys_json=dumps(entry["secondary_keys"]),
constant=entry["constant"],
selective=entry["selective"],
enabled=entry["enabled"],
case_sensitive=entry["case_sensitive"],
use_regex=entry["use_regex"],
position=entry["position"],
insertion_order=entry["insertion_order"],
priority=entry["priority"],
comment=entry["comment"],
)
)
await session.commit()
await session.refresh(character)
return character
async def import_character_card_response(
raw: bytes, owner_scope: str
) -> ImportCardResponse:
"""导入角色卡 → ImportCardResponse(供 API 调用,自管 session)。"""
async with AsyncSessionLocal() as session:
character = await import_character_card(session, raw, owner_scope)
books = await lorebook_repo.list_books(session, character.id, owner_scope)
book_id = books[0].id if books else None
entries = (
await lorebook_repo.list_book_entries(session, book_id) if book_id else []
)
return ImportCardResponse(
character_id=character.id,
name=character.name,
display_name=character.display_name,
lorebook_id=book_id,
entry_count=len(entries),
)
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", "人物不存在。")
books = await lorebook_repo.list_books(session, character_id, owner_scope)
book = books[0] if books else None
entries = (
await lorebook_repo.list_book_entries(session, book.id) if book else []
)
return character_to_card(character, book, entries)