把“章节正文 + 图片”从 chapters 单表/JSON 结构,重构为“章节 chapter + 段落 section + 图片 memoir_images 独立表”的新数据模型,同时联动修改接口、PDF 导出、异步任务、迁移脚本、测试,以及修复 Android 端聊天列表显示问题。 (#9)

* refactor: 表结构重构,新增段落section和图片image新表

* fix: fix android app import error

* refactor: 重构文件名

* fix: 优化提示词

* fix: 消息气泡显示位置异常问题

---------

Co-authored-by: yangshilin <2157598560@qq.com>
This commit is contained in:
Sully
2026-03-13 11:12:10 +08:00
committed by GitHub
parent 1cb804fa37
commit 2eb066dbec
19 changed files with 1280 additions and 624 deletions

View File

@@ -47,7 +47,7 @@ import signal
from sqlalchemy import create_engine, select
from sqlalchemy.orm import sessionmaker, Session
from database.models import Base, User, Conversation, Segment, Chapter, Book, MemoirState
from database.models import Base, User, Conversation, Segment, Chapter, ChapterSection, Book, MemoirState
from services.llm_service import LLMService
from agents.state_schema import MemoirStateSchema, SlotData, default_state
from agents.prompts.memory_prompts import (
@@ -57,6 +57,7 @@ from agents.prompts.memory_prompts import (
inject_image_placeholder_template,
STAGE_TO_ORDER,
)
from services.memoir_images.parser import split_narrative_to_sections
logging.basicConfig(
level=logging.INFO,
@@ -350,23 +351,35 @@ def cmd_preview(phone: str, batch_size: int, skip_llm_slots: bool):
nickname = user.nickname
logger.info(f"用户: {nickname} (id={user_id})")
# 读取现有 active 章节
# 读取现有 active 章节(含 sections正文从 sections 拼接)
from sqlalchemy.orm import joinedload
old_chapters = (
db.execute(
select(Chapter)
.where(Chapter.user_id == user_id, Chapter.is_active == True)
.options(joinedload(Chapter.sections))
.order_by(Chapter.order_index)
)
.unique()
.scalars()
.all()
)
old_chapter_data = []
for ch in old_chapters:
content = ""
if getattr(ch, "sections", None):
content = "\n\n".join(
(s.content or "").strip()
for s in sorted(ch.sections, key=lambda x: x.order_index)
if (s.content or "").strip()
)
content_len = len(content)
content_preview = (content[:200] + "") if content_len > 200 else content
old_chapter_data.append({
"category": ch.category,
"title": ch.title,
"content_len": len(ch.content) if ch.content else 0,
"content_preview": (ch.content[:200] + "") if ch.content and len(ch.content) > 200 else (ch.content or ""),
"content_len": content_len,
"content_preview": content_preview,
})
logger.info(f"现有章节: {len(old_chapters)}")
@@ -562,7 +575,7 @@ def cmd_apply(phone: str):
slots={k: {sk: sv.model_dump() for sk, sv in v.items()} for k, v in ds.slots.items()},
))
# 4. 插入新章节
# 4. 插入新章节(无 content/images正文与配图写入 chapter_sections
last_chapter_id = None
for ch_data in new_chapters:
ch_id = str(uuid.uuid4())
@@ -570,15 +583,34 @@ def cmd_apply(phone: str):
id=ch_id,
user_id=user_id,
title=ch_data["title"],
content=ch_data["content"],
order_index=ch_data["order_index"],
status="completed",
category=ch_data["category"],
images=[],
cover_image=None,
is_new=True,
source_segments=ch_data.get("source_segment_ids", []),
)
db.add(chapter)
db.flush()
content = ch_data.get("content") or ""
sections = split_narrative_to_sections(content)
if not sections:
db.add(ChapterSection(
id=str(uuid.uuid4()).replace("-", "")[:32],
chapter_id=ch_id,
order_index=0,
content=content.strip(),
image=None,
))
else:
for order_idx, seg in enumerate(sections):
db.add(ChapterSection(
id=str(uuid.uuid4()).replace("-", "")[:32],
chapter_id=ch_id,
order_index=order_idx,
content=(seg.get("content") or "").strip(),
image=None,
))
last_chapter_id = ch_id
logger.info(f" 新建章节: [{ch_data['category']}] {ch_data['title']}{ch_data['content_len']}")