Fixed RPG
This commit is contained in:
+124
-33
@@ -7,7 +7,19 @@ import aiosqlite
|
||||
from database.db import DB_PATH
|
||||
|
||||
|
||||
def parse_card_v2(data: dict) -> dict:
|
||||
def _normalize_alternate_greetings(inner: dict) -> list[str]:
|
||||
raw = inner.get("alternate_greetings") or []
|
||||
if not isinstance(raw, list):
|
||||
return []
|
||||
out = []
|
||||
for item in raw:
|
||||
text = str(item).strip()
|
||||
if text and text not in out:
|
||||
out.append(text)
|
||||
return out
|
||||
|
||||
|
||||
def parse_card_v2(data: dict, card_id: str | None = None) -> dict:
|
||||
inner = data.get("data", data)
|
||||
if isinstance(inner, str):
|
||||
inner = json.loads(inner)
|
||||
@@ -17,12 +29,15 @@ def parse_card_v2(data: dict) -> dict:
|
||||
if isinstance(entries, dict):
|
||||
entries = list(entries.values())
|
||||
|
||||
alternates = _normalize_alternate_greetings(inner)
|
||||
cid = card_id or (
|
||||
inner.get("name", "imported").lower().replace(" ", "_")[:48]
|
||||
+ "_"
|
||||
+ uuid.uuid4().hex[:8]
|
||||
)
|
||||
|
||||
return {
|
||||
"card_id": (
|
||||
inner.get("name", "imported").lower().replace(" ", "_")[:48]
|
||||
+ "_"
|
||||
+ uuid.uuid4().hex[:8]
|
||||
),
|
||||
"card_id": cid,
|
||||
"name": inner.get("name", "Character"),
|
||||
"description": inner.get("description", ""),
|
||||
"personality": inner.get("personality", ""),
|
||||
@@ -31,10 +46,22 @@ def parse_card_v2(data: dict) -> dict:
|
||||
"mes_example": inner.get("mes_example", ""),
|
||||
"appearance_tags": _extract_appearance(inner),
|
||||
"lorebook_json": json.dumps(entries, ensure_ascii=False),
|
||||
"alternate_greetings": alternates,
|
||||
"alternate_greetings_json": json.dumps(alternates, ensure_ascii=False),
|
||||
"raw_json": json.dumps(data if "data" in data else {"data": inner}, ensure_ascii=False),
|
||||
}
|
||||
|
||||
|
||||
def parse_card_bytes(content: bytes, filename: str) -> dict:
|
||||
if filename.lower().endswith(".png"):
|
||||
card = parse_png_card(content)
|
||||
if not card:
|
||||
raise ValueError("PNG does not contain character card metadata")
|
||||
card["_png_bytes"] = content
|
||||
return card
|
||||
return parse_card_v2(json.loads(content.decode("utf-8")))
|
||||
|
||||
|
||||
def _extract_appearance(inner: dict) -> str:
|
||||
"""Extract booru-style appearance tags from character fields."""
|
||||
import re
|
||||
@@ -107,12 +134,18 @@ def build_system_prompt(card: dict) -> str:
|
||||
|
||||
async def save_character(card: dict, lora_name: str = "", lora_weight: float = 0.8) -> dict:
|
||||
card_id = card["card_id"]
|
||||
alt_json = card.get("alternate_greetings_json")
|
||||
if alt_json is None:
|
||||
alts = card.get("alternate_greetings") or []
|
||||
alt_json = json.dumps(alts, ensure_ascii=False) if isinstance(alts, list) else "[]"
|
||||
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
await db.execute(
|
||||
"""INSERT OR REPLACE INTO characters
|
||||
(card_id, name, description, personality, scenario, first_mes,
|
||||
mes_example, raw_json, lora_name, lora_weight, appearance_tags, lorebook_json, avatar_path)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
mes_example, raw_json, lora_name, lora_weight, appearance_tags, lorebook_json,
|
||||
avatar_path, alternate_greetings_json)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
(
|
||||
card_id,
|
||||
card["name"],
|
||||
@@ -127,6 +160,7 @@ async def save_character(card: dict, lora_name: str = "", lora_weight: float = 0
|
||||
card.get("appearance_tags", ""),
|
||||
card["lorebook_json"],
|
||||
card.get("avatar_path", ""),
|
||||
alt_json,
|
||||
),
|
||||
)
|
||||
await db.commit()
|
||||
@@ -140,7 +174,7 @@ async def get_character(card_id: str) -> dict | None:
|
||||
"SELECT * FROM characters WHERE card_id = ?", (card_id,)
|
||||
) as cur:
|
||||
row = await cur.fetchone()
|
||||
return dict(row) if row else None
|
||||
return card_to_api(dict(row)) if row else None
|
||||
|
||||
|
||||
async def list_characters() -> list:
|
||||
@@ -171,9 +205,31 @@ async def update_appearance_tags(card_id: str, appearance_tags: str):
|
||||
await db.commit()
|
||||
|
||||
|
||||
def card_to_api(card: dict) -> dict:
|
||||
alts = card.get("alternate_greetings")
|
||||
if alts is None:
|
||||
try:
|
||||
alts = json.loads(card.get("alternate_greetings_json") or "[]")
|
||||
except Exception:
|
||||
alts = []
|
||||
if not isinstance(alts, list):
|
||||
alts = []
|
||||
return {**card, "alternate_greetings": alts}
|
||||
|
||||
|
||||
async def preview_card_file(content: bytes, filename: str) -> dict:
|
||||
card = parse_card_bytes(content, filename)
|
||||
png_bytes = card.pop("_png_bytes", None)
|
||||
preview = card_to_api(card)
|
||||
preview["is_png"] = bool(png_bytes)
|
||||
preview["alternate_count"] = len(preview.get("alternate_greetings") or [])
|
||||
return preview
|
||||
|
||||
|
||||
async def update_character(card_id: str, fields: dict) -> bool:
|
||||
allowed = {"name", "description", "personality", "scenario", "first_mes",
|
||||
"mes_example", "appearance_tags", "lora_name", "lora_weight", "avatar_path"}
|
||||
"mes_example", "appearance_tags", "lora_name", "lora_weight", "avatar_path",
|
||||
"alternate_greetings_json"}
|
||||
updates = {k: v for k, v in fields.items() if k in allowed}
|
||||
if not updates:
|
||||
return False
|
||||
@@ -187,37 +243,72 @@ async def update_character(card_id: str, fields: dict) -> bool:
|
||||
return cur.rowcount > 0
|
||||
|
||||
|
||||
async def import_card_file(content: bytes, filename: str, lora_name: str = "", lora_weight: float = 0.8) -> dict:
|
||||
if filename.lower().endswith(".png"):
|
||||
card = parse_png_card(content)
|
||||
if not card:
|
||||
raise ValueError("PNG does not contain character card metadata")
|
||||
# Use the PNG itself as avatar
|
||||
avatar_rel = _save_avatar_bytes(content, f"card_{card['card_id']}")
|
||||
async def import_card_file(
|
||||
content: bytes,
|
||||
filename: str,
|
||||
lora_name: str = "",
|
||||
lora_weight: float = 0.8,
|
||||
overrides: dict | None = None,
|
||||
card_id: str | None = None,
|
||||
) -> dict:
|
||||
card = parse_card_bytes(content, filename)
|
||||
png_bytes = card.pop("_png_bytes", None)
|
||||
|
||||
if card_id:
|
||||
card["card_id"] = card_id
|
||||
|
||||
if overrides:
|
||||
for key in (
|
||||
"name", "description", "personality", "scenario", "first_mes",
|
||||
"mes_example", "appearance_tags", "lorebook_json",
|
||||
):
|
||||
if key in overrides and overrides[key] is not None:
|
||||
card[key] = overrides[key]
|
||||
if overrides.get("alternate_greetings_json") is not None:
|
||||
card["alternate_greetings_json"] = overrides["alternate_greetings_json"]
|
||||
elif overrides.get("alternate_greetings") is not None:
|
||||
alts = overrides["alternate_greetings"]
|
||||
if isinstance(alts, str):
|
||||
try:
|
||||
alts = json.loads(alts)
|
||||
except Exception:
|
||||
alts = []
|
||||
card["alternate_greetings"] = alts
|
||||
card["alternate_greetings_json"] = json.dumps(alts, ensure_ascii=False)
|
||||
|
||||
if png_bytes:
|
||||
avatar_rel = _save_avatar_bytes(png_bytes, f"card_{card['card_id']}")
|
||||
card["avatar_path"] = avatar_rel
|
||||
else:
|
||||
card = parse_card_v2(json.loads(content.decode("utf-8")))
|
||||
|
||||
saved = await save_character(card, lora_name=lora_name, lora_weight=lora_weight)
|
||||
|
||||
persona_id = f"card_{saved['card_id']}"
|
||||
from services.personas import create_persona, get_persona
|
||||
from services.personas import create_persona, get_persona, patch_persona
|
||||
|
||||
existing = await get_persona(persona_id)
|
||||
persona_fields = {
|
||||
"name": saved["name"],
|
||||
"emoji": "🎭",
|
||||
"description": (saved["description"] or "")[:80] or "Character card",
|
||||
"prompt": build_system_prompt(saved),
|
||||
"sd_enabled": True,
|
||||
"lora_name": lora_name,
|
||||
"lora_weight": lora_weight,
|
||||
"appearance_tags": saved.get("appearance_tags", ""),
|
||||
"avatar_path": saved.get("avatar_path", ""),
|
||||
"personality": saved.get("personality", ""),
|
||||
"scenario": saved.get("scenario", ""),
|
||||
"first_mes": saved.get("first_mes", ""),
|
||||
"mes_example": saved.get("mes_example", ""),
|
||||
"lorebook_json": saved.get("lorebook_json", "[]"),
|
||||
"alternate_greetings_json": saved.get("alternate_greetings_json", "[]"),
|
||||
}
|
||||
if not existing:
|
||||
await create_persona(
|
||||
persona_id=persona_id,
|
||||
name=saved["name"],
|
||||
emoji="🎭",
|
||||
description=saved["description"][:80] or "Character card",
|
||||
prompt=build_system_prompt(saved),
|
||||
sd_enabled=True,
|
||||
lora_name=lora_name,
|
||||
lora_weight=lora_weight,
|
||||
appearance_tags=saved.get("appearance_tags", ""),
|
||||
avatar_path=saved.get("avatar_path", ""),
|
||||
)
|
||||
return saved
|
||||
await create_persona(persona_id=persona_id, **persona_fields)
|
||||
else:
|
||||
await patch_persona(persona_id, persona_fields)
|
||||
|
||||
return card_to_api(saved)
|
||||
|
||||
|
||||
def _save_avatar_bytes(png_bytes: bytes, prefix: str) -> str:
|
||||
|
||||
Reference in New Issue
Block a user