update:1.小说项目创建支持双模式生成,大纲-章节(一对一&一对多) 2.新增章节管理-编辑章节规划功能 3.修复灵感模式可重复点击选项问题,刷新对话内容丢失问题
This commit is contained in:
+198
-9
@@ -28,7 +28,8 @@ from app.schemas.chapter import (
|
||||
ChapterGenerateRequest,
|
||||
BatchGenerateRequest,
|
||||
BatchGenerateResponse,
|
||||
BatchGenerateStatusResponse
|
||||
BatchGenerateStatusResponse,
|
||||
ExpansionPlanUpdate
|
||||
)
|
||||
from app.schemas.regeneration import (
|
||||
ChapterRegenerateRequest,
|
||||
@@ -1008,6 +1009,10 @@ async def generate_chapter_content_stream(
|
||||
yield f"data: {json.dumps({'type': 'error', 'error': '项目不存在'}, ensure_ascii=False)}\n\n"
|
||||
return
|
||||
|
||||
# 获取项目的大纲模式
|
||||
outline_mode = project.outline_mode if project else 'one-to-many'
|
||||
logger.info(f"📋 项目大纲模式: {outline_mode}")
|
||||
|
||||
# 获取对应的大纲
|
||||
outline_result = await db_session.execute(
|
||||
select(Outline)
|
||||
@@ -1188,6 +1193,48 @@ async def generate_chapter_content_stream(
|
||||
logger.warning(f"⚠️ MCP工具调用失败,降级为基础模式: {str(e)}")
|
||||
yield f"data: {json.dumps({'type': 'progress', 'message': '⚠️ MCP工具暂时不可用,使用基础模式', 'progress': 32}, ensure_ascii=False)}\n\n"
|
||||
|
||||
# 📋 根据大纲模式构建差异化的章节大纲上下文
|
||||
chapter_outline_content = ""
|
||||
if outline_mode == 'one-to-one':
|
||||
# 一对一模式:使用大纲的 content
|
||||
chapter_outline_content = outline.content if outline else current_chapter.summary or '暂无大纲'
|
||||
logger.info(f"✏️ 一对一模式:使用大纲内容作为章节指导")
|
||||
else:
|
||||
# 一对多模式:优先使用 expansion_plan 的详细规划
|
||||
if current_chapter.expansion_plan:
|
||||
try:
|
||||
plan = json.loads(current_chapter.expansion_plan)
|
||||
chapter_outline_content = f"""【本章详细规划】
|
||||
剧情摘要:{plan.get('plot_summary', '无')}
|
||||
|
||||
关键事件:
|
||||
{chr(10).join(f'- {event}' for event in plan.get('key_events', []))}
|
||||
|
||||
角色焦点:{', '.join(plan.get('character_focus', []))}
|
||||
|
||||
情感基调:{plan.get('emotional_tone', '未设定')}
|
||||
|
||||
叙事目标:{plan.get('narrative_goal', '未设定')}
|
||||
|
||||
冲突类型:{plan.get('conflict_type', '未设定')}"""
|
||||
|
||||
# 可选:附加章节 summary
|
||||
if current_chapter.summary and current_chapter.summary.strip():
|
||||
chapter_outline_content += f"\n\n【章节补充说明】\n{current_chapter.summary}"
|
||||
|
||||
# 可选:附加大纲的背景信息
|
||||
if outline:
|
||||
chapter_outline_content += f"\n\n【大纲节点背景】\n{outline.content}"
|
||||
|
||||
logger.info(f"✏️ 一对多模式:使用expansion_plan详细规划({len(chapter_outline_content)}字符)")
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning(f"⚠️ expansion_plan解析失败: {e},回退到大纲内容")
|
||||
chapter_outline_content = outline.content if outline else current_chapter.summary or '暂无大纲'
|
||||
else:
|
||||
# 没有expansion_plan,使用大纲内容
|
||||
chapter_outline_content = outline.content if outline else current_chapter.summary or '暂无大纲'
|
||||
logger.warning(f"⚠️ 一对多模式但无expansion_plan,使用大纲内容")
|
||||
|
||||
# 根据是否有前置内容选择不同的提示词,并应用写作风格、记忆增强和MCP参考资料
|
||||
if previous_content:
|
||||
prompt = prompt_service.get_chapter_generation_with_context_prompt(
|
||||
@@ -1204,11 +1251,12 @@ async def generate_chapter_content_stream(
|
||||
previous_content=previous_content,
|
||||
chapter_number=current_chapter.chapter_number,
|
||||
chapter_title=current_chapter.title,
|
||||
chapter_outline=outline.content if outline else current_chapter.summary or '暂无大纲',
|
||||
chapter_outline=chapter_outline_content,
|
||||
style_content=style_content,
|
||||
target_word_count=target_word_count,
|
||||
memory_context=memory_context,
|
||||
mcp_references=mcp_reference_materials
|
||||
mcp_references=mcp_reference_materials,
|
||||
outline_mode=outline_mode
|
||||
)
|
||||
else:
|
||||
prompt = prompt_service.get_chapter_generation_prompt(
|
||||
@@ -1224,11 +1272,12 @@ async def generate_chapter_content_stream(
|
||||
outlines_context=outlines_context,
|
||||
chapter_number=current_chapter.chapter_number,
|
||||
chapter_title=current_chapter.title,
|
||||
chapter_outline=outline.content if outline else current_chapter.summary or '暂无大纲',
|
||||
chapter_outline=chapter_outline_content,
|
||||
style_content=style_content,
|
||||
target_word_count=target_word_count,
|
||||
memory_context=memory_context,
|
||||
mcp_references=mcp_reference_materials
|
||||
mcp_references=mcp_reference_materials,
|
||||
outline_mode=outline_mode
|
||||
)
|
||||
|
||||
if mcp_reference_materials:
|
||||
@@ -1238,11 +1287,39 @@ async def generate_chapter_content_stream(
|
||||
|
||||
# 流式生成内容
|
||||
full_content = ""
|
||||
chunk_count = 0
|
||||
last_progress = 0
|
||||
|
||||
async for chunk in user_ai_service.generate_text_stream(prompt=prompt):
|
||||
full_content += chunk
|
||||
chunk_count += 1
|
||||
|
||||
# 发送内容块
|
||||
yield f"data: {json.dumps({'type': 'content', 'content': chunk}, ensure_ascii=False)}\n\n"
|
||||
|
||||
# 每50个chunk发送一次进度更新(估算)
|
||||
if chunk_count % 50 == 0:
|
||||
current_word_count = len(full_content)
|
||||
# 根据目标字数估算进度(35%起步,最高95%,为后续保存留5%)
|
||||
estimated_progress = min(95, 35 + int((current_word_count / target_word_count) * 60))
|
||||
|
||||
# 只在进度变化时发送
|
||||
if estimated_progress > last_progress:
|
||||
progress_data = {
|
||||
'type': 'progress',
|
||||
'progress': estimated_progress,
|
||||
'message': f'正在创作中... 已生成 {current_word_count} 字',
|
||||
'word_count': current_word_count,
|
||||
'status': 'processing'
|
||||
}
|
||||
yield f"data: {json.dumps(progress_data, ensure_ascii=False)}\n\n"
|
||||
last_progress = estimated_progress
|
||||
|
||||
await asyncio.sleep(0) # 让出控制权
|
||||
|
||||
# 发送保存进度
|
||||
yield f"data: {json.dumps({'type': 'progress', 'progress': 98, 'message': '正在保存章节...', 'status': 'processing'}, ensure_ascii=False)}\n\n"
|
||||
|
||||
# 更新章节内容到数据库
|
||||
old_word_count = current_chapter.word_count or 0
|
||||
current_chapter.content = full_content
|
||||
@@ -1297,6 +1374,9 @@ async def generate_chapter_content_stream(
|
||||
ai_service=user_ai_service
|
||||
)
|
||||
|
||||
# 发送最终进度100%
|
||||
yield f"data: {json.dumps({'type': 'progress', 'progress': 100, 'message': '创作完成!', 'word_count': new_word_count, 'status': 'success'}, ensure_ascii=False)}\n\n"
|
||||
|
||||
# 发送完成事件(包含分析任务ID)
|
||||
completion_data = {
|
||||
'type': 'done',
|
||||
@@ -2216,6 +2296,10 @@ async def generate_single_chapter_for_batch(
|
||||
if not project:
|
||||
raise Exception("项目不存在")
|
||||
|
||||
# 获取项目的大纲模式
|
||||
outline_mode = project.outline_mode if project else 'one-to-many'
|
||||
logger.info(f"📋 批量生成 - 项目大纲模式: {outline_mode}")
|
||||
|
||||
# 获取对应的大纲
|
||||
outline_result = await db_session.execute(
|
||||
select(Outline)
|
||||
@@ -2285,6 +2369,48 @@ async def generate_single_chapter_for_batch(
|
||||
character_names=[c.name for c in characters] if characters else None
|
||||
)
|
||||
|
||||
# 📋 根据大纲模式构建差异化的章节大纲上下文
|
||||
chapter_outline_content = ""
|
||||
if outline_mode == 'one-to-one':
|
||||
# 一对一模式:使用大纲的 content
|
||||
chapter_outline_content = outline.content if outline else chapter.summary or '暂无大纲'
|
||||
logger.info(f"✏️ 批量生成 - 一对一模式:使用大纲内容")
|
||||
else:
|
||||
# 一对多模式:优先使用 expansion_plan 的详细规划
|
||||
if chapter.expansion_plan:
|
||||
try:
|
||||
plan = json.loads(chapter.expansion_plan)
|
||||
chapter_outline_content = f"""【本章详细规划】
|
||||
剧情摘要:{plan.get('plot_summary', '无')}
|
||||
|
||||
关键事件:
|
||||
{chr(10).join(f'- {event}' for event in plan.get('key_events', []))}
|
||||
|
||||
角色焦点:{', '.join(plan.get('character_focus', []))}
|
||||
|
||||
情感基调:{plan.get('emotional_tone', '未设定')}
|
||||
|
||||
叙事目标:{plan.get('narrative_goal', '未设定')}
|
||||
|
||||
冲突类型:{plan.get('conflict_type', '未设定')}"""
|
||||
|
||||
# 可选:附加章节 summary
|
||||
if chapter.summary and chapter.summary.strip():
|
||||
chapter_outline_content += f"\n\n【章节补充说明】\n{chapter.summary}"
|
||||
|
||||
# 可选:附加大纲的背景信息
|
||||
if outline:
|
||||
chapter_outline_content += f"\n\n【大纲节点背景】\n{outline.content}"
|
||||
|
||||
logger.info(f"✏️ 批量生成 - 一对多模式:使用expansion_plan详细规划")
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning(f"⚠️ expansion_plan解析失败: {e},回退到大纲内容")
|
||||
chapter_outline_content = outline.content if outline else chapter.summary or '暂无大纲'
|
||||
else:
|
||||
# 没有expansion_plan,使用大纲内容
|
||||
chapter_outline_content = outline.content if outline else chapter.summary or '暂无大纲'
|
||||
logger.warning(f"⚠️ 批量生成 - 一对多模式但无expansion_plan,使用大纲内容")
|
||||
|
||||
# 生成提示词
|
||||
if previous_content:
|
||||
prompt = prompt_service.get_chapter_generation_with_context_prompt(
|
||||
@@ -2301,10 +2427,11 @@ async def generate_single_chapter_for_batch(
|
||||
previous_content=previous_content,
|
||||
chapter_number=chapter.chapter_number,
|
||||
chapter_title=chapter.title,
|
||||
chapter_outline=outline.content if outline else chapter.summary or '暂无大纲',
|
||||
chapter_outline=chapter_outline_content,
|
||||
style_content=style_content,
|
||||
target_word_count=target_word_count,
|
||||
memory_context=memory_context
|
||||
memory_context=memory_context,
|
||||
outline_mode=outline_mode
|
||||
)
|
||||
else:
|
||||
prompt = prompt_service.get_chapter_generation_prompt(
|
||||
@@ -2320,10 +2447,11 @@ async def generate_single_chapter_for_batch(
|
||||
outlines_context=outlines_context,
|
||||
chapter_number=chapter.chapter_number,
|
||||
chapter_title=chapter.title,
|
||||
chapter_outline=outline.content if outline else chapter.summary or '暂无大纲',
|
||||
chapter_outline=chapter_outline_content,
|
||||
style_content=style_content,
|
||||
target_word_count=target_word_count,
|
||||
memory_context=memory_context
|
||||
memory_context=memory_context,
|
||||
outline_mode=outline_mode
|
||||
)
|
||||
|
||||
# 非流式生成内容
|
||||
@@ -2643,3 +2771,64 @@ async def get_regeneration_tasks(
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{chapter_id}/expansion-plan", response_model=dict, summary="更新章节规划信息")
|
||||
async def update_chapter_expansion_plan(
|
||||
chapter_id: str,
|
||||
expansion_plan: ExpansionPlanUpdate,
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
更新章节的展开规划信息
|
||||
|
||||
Args:
|
||||
chapter_id: 章节ID
|
||||
expansion_plan: 规划信息更新数据
|
||||
|
||||
Returns:
|
||||
更新后的章节规划信息
|
||||
"""
|
||||
# 获取章节
|
||||
result = await db.execute(
|
||||
select(Chapter).where(Chapter.id == chapter_id)
|
||||
)
|
||||
chapter = result.scalar_one_or_none()
|
||||
|
||||
if not chapter:
|
||||
raise HTTPException(status_code=404, detail="章节不存在")
|
||||
|
||||
# 验证用户权限
|
||||
user_id = getattr(request.state, 'user_id', None)
|
||||
await verify_project_access(chapter.project_id, user_id, db)
|
||||
|
||||
# 准备更新数据(排除None值)
|
||||
plan_data = expansion_plan.model_dump(exclude_unset=True, exclude_none=True)
|
||||
|
||||
# 如果已有规划,合并更新;否则创建新规划
|
||||
if chapter.expansion_plan:
|
||||
try:
|
||||
existing_plan = json.loads(chapter.expansion_plan)
|
||||
# 合并更新
|
||||
existing_plan.update(plan_data)
|
||||
chapter.expansion_plan = json.dumps(existing_plan, ensure_ascii=False)
|
||||
except json.JSONDecodeError:
|
||||
logger.warning(f"章节 {chapter_id} 的expansion_plan格式错误,将覆盖")
|
||||
chapter.expansion_plan = json.dumps(plan_data, ensure_ascii=False)
|
||||
else:
|
||||
chapter.expansion_plan = json.dumps(plan_data, ensure_ascii=False)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(chapter)
|
||||
|
||||
logger.info(f"章节规划更新成功: {chapter_id}")
|
||||
|
||||
# 返回更新后的规划数据
|
||||
updated_plan = json.loads(chapter.expansion_plan) if chapter.expansion_plan else None
|
||||
|
||||
return {
|
||||
"id": chapter.id,
|
||||
"expansion_plan": updated_plan,
|
||||
"message": "规划信息更新成功"
|
||||
}
|
||||
|
||||
|
||||
+174
-11
@@ -236,18 +236,29 @@ async def delete_outline(
|
||||
|
||||
# 验证用户权限
|
||||
user_id = getattr(request.state, 'user_id', None)
|
||||
await verify_project_access(outline.project_id, user_id, db)
|
||||
project = await verify_project_access(outline.project_id, user_id, db)
|
||||
|
||||
project_id = outline.project_id
|
||||
deleted_order = outline.order_index
|
||||
|
||||
# 删除该大纲对应的所有章节(通过outline_id关联)
|
||||
delete_result = await db.execute(
|
||||
delete(Chapter).where(Chapter.outline_id == outline_id)
|
||||
)
|
||||
deleted_chapters_count = delete_result.rowcount
|
||||
|
||||
logger.info(f"删除大纲 {outline_id},同时删除了 {deleted_chapters_count} 个关联章节")
|
||||
# 根据项目模式删除对应的章节
|
||||
if project.outline_mode == 'one-to-one':
|
||||
# one-to-one模式:通过chapter_number删除对应章节
|
||||
delete_result = await db.execute(
|
||||
delete(Chapter).where(
|
||||
Chapter.project_id == project_id,
|
||||
Chapter.chapter_number == outline.order_index
|
||||
)
|
||||
)
|
||||
deleted_chapters_count = delete_result.rowcount
|
||||
logger.info(f"一对一模式:删除大纲 {outline_id}(序号{outline.order_index}),同时删除了第{outline.order_index}章({deleted_chapters_count}个章节)")
|
||||
else:
|
||||
# one-to-many模式:通过outline_id删除关联章节
|
||||
delete_result = await db.execute(
|
||||
delete(Chapter).where(Chapter.outline_id == outline_id)
|
||||
)
|
||||
deleted_chapters_count = delete_result.rowcount
|
||||
logger.info(f"一对多模式:删除大纲 {outline_id},同时删除了 {deleted_chapters_count} 个关联章节")
|
||||
|
||||
# 删除大纲
|
||||
await db.delete(outline)
|
||||
@@ -264,6 +275,21 @@ async def delete_outline(
|
||||
for o in subsequent_outlines:
|
||||
o.order_index -= 1
|
||||
|
||||
# 如果是one-to-one模式,还需要重新排序后续章节的chapter_number
|
||||
if project.outline_mode == 'one-to-one':
|
||||
chapters_result = await db.execute(
|
||||
select(Chapter).where(
|
||||
Chapter.project_id == project_id,
|
||||
Chapter.chapter_number > deleted_order
|
||||
).order_by(Chapter.chapter_number)
|
||||
)
|
||||
subsequent_chapters = chapters_result.scalars().all()
|
||||
|
||||
for ch in subsequent_chapters:
|
||||
ch.chapter_number -= 1
|
||||
|
||||
logger.info(f"一对一模式:重新排序了 {len(subsequent_chapters)} 个后续章节")
|
||||
|
||||
await db.commit()
|
||||
|
||||
return {
|
||||
@@ -852,7 +878,17 @@ async def _save_outlines(
|
||||
db: AsyncSession,
|
||||
start_index: int = 1
|
||||
) -> List[Outline]:
|
||||
"""保存大纲到数据库(不自动创建章节)"""
|
||||
"""
|
||||
保存大纲到数据库
|
||||
|
||||
如果项目为one-to-one模式,同时自动创建对应的章节
|
||||
"""
|
||||
# 获取项目信息以确定outline_mode
|
||||
project_result = await db.execute(
|
||||
select(Project).where(Project.id == project_id)
|
||||
)
|
||||
project = project_result.scalar_one_or_none()
|
||||
|
||||
outlines = []
|
||||
|
||||
for idx, chapter_data in enumerate(outline_data):
|
||||
@@ -879,6 +915,28 @@ async def _save_outlines(
|
||||
db.add(outline)
|
||||
outlines.append(outline)
|
||||
|
||||
# 如果是one-to-one模式,自动创建章节
|
||||
if project and project.outline_mode == 'one-to-one':
|
||||
await db.flush() # 确保大纲有ID
|
||||
|
||||
for outline in outlines:
|
||||
await db.refresh(outline)
|
||||
|
||||
# 为每个大纲创建对应的章节
|
||||
chapter = Chapter(
|
||||
project_id=project_id,
|
||||
title=outline.title,
|
||||
summary=outline.content,
|
||||
chapter_number=outline.order_index,
|
||||
sub_index=1,
|
||||
outline_id=None, # one-to-one模式不关联outline_id
|
||||
status='pending',
|
||||
content=""
|
||||
)
|
||||
db.add(chapter)
|
||||
|
||||
logger.info(f"一对一模式:为{len(outlines)}个大纲自动创建了对应的章节")
|
||||
|
||||
return outlines
|
||||
|
||||
|
||||
@@ -1646,6 +1704,104 @@ async def expand_outline_generator(
|
||||
yield await SSEResponse.send_error(f"展开失败: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/{outline_id}/create-single-chapter", summary="一对一创建章节(传统模式)")
|
||||
async def create_single_chapter_from_outline(
|
||||
outline_id: str,
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
传统模式:一个大纲对应创建一个章节
|
||||
|
||||
适用场景:
|
||||
- 项目的outline_mode为'one-to-one'
|
||||
- 直接将大纲内容作为章节摘要
|
||||
- 不调用AI,不展开
|
||||
|
||||
流程:
|
||||
1. 验证项目模式为one-to-one
|
||||
2. 检查该大纲是否已创建章节
|
||||
3. 创建章节记录(outline_id=NULL,chapter_number=outline.order_index)
|
||||
|
||||
返回:创建的章节信息
|
||||
"""
|
||||
# 验证用户权限
|
||||
user_id = getattr(request.state, 'user_id', None)
|
||||
|
||||
# 获取大纲
|
||||
result = await db.execute(
|
||||
select(Outline).where(Outline.id == outline_id)
|
||||
)
|
||||
outline = result.scalar_one_or_none()
|
||||
|
||||
if not outline:
|
||||
raise HTTPException(status_code=404, detail="大纲不存在")
|
||||
|
||||
# 验证项目权限并获取项目信息
|
||||
project = await verify_project_access(outline.project_id, user_id, db)
|
||||
|
||||
# 验证项目模式
|
||||
if project.outline_mode != 'one-to-one':
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"当前项目为{project.outline_mode}模式,不支持一对一创建。请使用展开功能。"
|
||||
)
|
||||
|
||||
# 检查该大纲对应的章节是否已存在
|
||||
existing_chapter_result = await db.execute(
|
||||
select(Chapter).where(
|
||||
Chapter.project_id == outline.project_id,
|
||||
Chapter.chapter_number == outline.order_index,
|
||||
Chapter.sub_index == 1
|
||||
)
|
||||
)
|
||||
existing_chapter = existing_chapter_result.scalar_one_or_none()
|
||||
|
||||
if existing_chapter:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"第{outline.order_index}章已存在,不能重复创建"
|
||||
)
|
||||
|
||||
try:
|
||||
# 创建章节(outline_id=NULL表示一对一模式)
|
||||
new_chapter = Chapter(
|
||||
project_id=outline.project_id,
|
||||
title=outline.title,
|
||||
summary=outline.content, # 使用大纲内容作为摘要
|
||||
chapter_number=outline.order_index,
|
||||
sub_index=1, # 一对一模式固定为1
|
||||
outline_id=None, # 传统模式不关联outline_id
|
||||
status='pending'
|
||||
)
|
||||
|
||||
db.add(new_chapter)
|
||||
await db.commit()
|
||||
await db.refresh(new_chapter)
|
||||
|
||||
logger.info(f"一对一模式:为大纲 {outline.title} 创建章节 {new_chapter.chapter_number}")
|
||||
|
||||
return {
|
||||
"message": "章节创建成功",
|
||||
"chapter": {
|
||||
"id": new_chapter.id,
|
||||
"project_id": new_chapter.project_id,
|
||||
"title": new_chapter.title,
|
||||
"summary": new_chapter.summary,
|
||||
"chapter_number": new_chapter.chapter_number,
|
||||
"sub_index": new_chapter.sub_index,
|
||||
"outline_id": new_chapter.outline_id,
|
||||
"status": new_chapter.status,
|
||||
"created_at": new_chapter.created_at.isoformat() if new_chapter.created_at else None
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"一对一创建章节失败: {str(e)}", exc_info=True)
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=500, detail=f"创建章节失败: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/{outline_id}/expand", response_model=OutlineExpansionResponse, summary="展开单个大纲为多章")
|
||||
async def expand_outline_to_chapters(
|
||||
outline_id: str,
|
||||
@@ -1681,8 +1837,15 @@ async def expand_outline_to_chapters(
|
||||
if not outline:
|
||||
raise HTTPException(status_code=404, detail="大纲不存在")
|
||||
|
||||
# 验证项目权限
|
||||
await verify_project_access(outline.project_id, user_id, db)
|
||||
# 验证项目权限并获取项目信息
|
||||
project = await verify_project_access(outline.project_id, user_id, db)
|
||||
|
||||
# 验证项目模式
|
||||
if project.outline_mode != 'one-to-many':
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"当前项目为{project.outline_mode}模式,不支持展开功能。请使用一对一创建。"
|
||||
)
|
||||
|
||||
try:
|
||||
# 创建展开服务实例
|
||||
|
||||
@@ -354,9 +354,8 @@ async def export_project_chapters(
|
||||
txt_content.append("\n" + "=" * 80 + "\n\n")
|
||||
|
||||
for chapter in chapters:
|
||||
# 处理子章节序号显示
|
||||
chapter_display = f"{chapter.chapter_number}-{chapter.sub_index}" if chapter.sub_index and chapter.sub_index > 1 else str(chapter.chapter_number)
|
||||
txt_content.append(f"第 {chapter_display} 章 {chapter.title}")
|
||||
# 只显示主章节号,不显示子索引
|
||||
txt_content.append(f"第 {chapter.chapter_number} 章 {chapter.title}")
|
||||
txt_content.append("-" * 80)
|
||||
txt_content.append("") # 空行
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ async def world_building_generator(
|
||||
target_words = data.get("target_words")
|
||||
chapter_count = data.get("chapter_count")
|
||||
character_count = data.get("character_count")
|
||||
outline_mode = data.get("outline_mode", "one-to-many") # 大纲模式,默认一对多
|
||||
provider = data.get("provider")
|
||||
model = data.get("model")
|
||||
enable_mcp = data.get("enable_mcp", True) # 默认启用MCP
|
||||
@@ -215,6 +216,7 @@ async def world_building_generator(
|
||||
target_words=target_words,
|
||||
chapter_count=chapter_count,
|
||||
character_count=character_count,
|
||||
outline_mode=outline_mode, # 设置大纲模式
|
||||
wizard_status="incomplete",
|
||||
wizard_step=1,
|
||||
status="planning"
|
||||
@@ -1017,39 +1019,82 @@ async def outline_generator(
|
||||
|
||||
logger.info(f"✅ 成功创建{len(created_outlines)}个大纲节点")
|
||||
|
||||
# 向导流程中不展开大纲,避免等待时间过长
|
||||
# 用户可以在大纲页面手动展开需要的大纲节点
|
||||
yield await SSEResponse.send_progress("跳过大纲展开,加快创建速度...", 85)
|
||||
# 根据项目的大纲模式决定是否自动创建章节
|
||||
created_chapters = []
|
||||
if project.outline_mode == 'one-to-one':
|
||||
# 一对一模式:自动为每个大纲创建对应的章节
|
||||
yield await SSEResponse.send_progress("一对一模式:自动创建章节...", 50)
|
||||
|
||||
for outline in created_outlines:
|
||||
chapter = Chapter(
|
||||
project_id=project_id,
|
||||
title=outline.title,
|
||||
content="", # 空内容,等待用户生成
|
||||
outline_id=None, # 一对一模式下不关联outline_id
|
||||
chapter_number=outline.order_index, # 使用chapter_number而不是order_index
|
||||
status="pending"
|
||||
)
|
||||
db.add(chapter)
|
||||
created_chapters.append(chapter)
|
||||
|
||||
await db.flush()
|
||||
for chapter in created_chapters:
|
||||
await db.refresh(chapter)
|
||||
|
||||
logger.info(f"✅ 一对一模式:自动创建了{len(created_chapters)}个章节")
|
||||
yield await SSEResponse.send_progress(f"已自动创建{len(created_chapters)}个章节", 85)
|
||||
else:
|
||||
# 一对多模式:跳过自动创建,用户可手动展开
|
||||
yield await SSEResponse.send_progress("细化模式:跳过自动创建章节", 85)
|
||||
logger.info(f"📝 细化模式:跳过章节创建,用户可在大纲页面手动展开")
|
||||
|
||||
# 更新项目信息
|
||||
project.chapter_count = 0 # 向导阶段不创建章节
|
||||
project.chapter_count = len(created_chapters) # 记录实际创建的章节数
|
||||
project.narrative_perspective = narrative_perspective
|
||||
project.target_words = target_words
|
||||
project.status = "writing"
|
||||
project.wizard_status = "completed"
|
||||
project.wizard_step = 3
|
||||
project.wizard_step = 3
|
||||
|
||||
await db.commit()
|
||||
db_committed = True
|
||||
|
||||
logger.info(f"📊 向导大纲生成完成:")
|
||||
logger.info(f" - 创建大纲节点:{len(created_outlines)} 个")
|
||||
logger.info(f" - 提示:可在大纲页面手动展开为章节")
|
||||
logger.info(f" - 创建章节:{len(created_chapters)} 个")
|
||||
logger.info(f" - 大纲模式:{project.outline_mode}")
|
||||
|
||||
# 构建结果消息
|
||||
if project.outline_mode == 'one-to-one':
|
||||
result_message = f"成功生成{len(created_outlines)}个大纲节点并自动创建{len(created_chapters)}个章节(传统模式)"
|
||||
result_note = "已自动创建章节,可直接生成内容"
|
||||
else:
|
||||
result_message = f"成功生成{len(created_outlines)}个大纲节点(细化模式,可在大纲页面手动展开)"
|
||||
result_note = "可在大纲页面展开为多个章节"
|
||||
|
||||
# 发送结果
|
||||
yield await SSEResponse.send_result({
|
||||
"message": f"成功生成{len(created_outlines)}个大纲节点(未展开章节,可在大纲页面手动展开)",
|
||||
"message": result_message,
|
||||
"outline_count": len(created_outlines),
|
||||
"chapter_count": 0,
|
||||
"chapter_count": len(created_chapters),
|
||||
"outline_mode": project.outline_mode,
|
||||
"outlines": [
|
||||
{
|
||||
"id": outline.id,
|
||||
"order_index": outline.order_index,
|
||||
"title": outline.title,
|
||||
"content": outline.content[:100] + "..." if len(outline.content) > 100 else outline.content,
|
||||
"note": "可在大纲页面展开为章节"
|
||||
"note": result_note
|
||||
} for outline in created_outlines
|
||||
]
|
||||
],
|
||||
"chapters": [
|
||||
{
|
||||
"id": chapter.id,
|
||||
"chapter_number": chapter.chapter_number,
|
||||
"title": chapter.title,
|
||||
"status": chapter.status
|
||||
} for chapter in created_chapters
|
||||
] if created_chapters else []
|
||||
})
|
||||
|
||||
yield await SSEResponse.send_progress("完成!", 100, "success")
|
||||
|
||||
Reference in New Issue
Block a user