update:更新智能引入角色/组织sse推送逻辑,优化进度展示
This commit is contained in:
+165
-259
@@ -75,6 +75,48 @@ async def verify_project_access(project_id: str, user_id: str, db: AsyncSession)
|
|||||||
return project
|
return project
|
||||||
|
|
||||||
|
|
||||||
|
def _build_chapters_brief(outlines: List[Outline], max_recent: int = 20) -> str:
|
||||||
|
"""构建章节概览字符串"""
|
||||||
|
target = outlines[-max_recent:] if len(outlines) > max_recent else outlines
|
||||||
|
return "\n".join([f"第{o.order_index}章《{o.title}》" for o in target])
|
||||||
|
|
||||||
|
|
||||||
|
def _build_characters_info(characters: List[Character]) -> str:
|
||||||
|
"""构建角色信息字符串"""
|
||||||
|
return "\n".join([
|
||||||
|
f"- {char.name} ({'组织' if char.is_organization else '角色'}, {char.role_type}): "
|
||||||
|
f"{char.personality[:100] if char.personality else '暂无描述'}"
|
||||||
|
for char in characters
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
async def _get_existing_organizations(project_id: str, db: AsyncSession) -> List[dict]:
|
||||||
|
"""获取项目现有组织列表"""
|
||||||
|
from app.models.relationship import Organization
|
||||||
|
|
||||||
|
organizations_result = await db.execute(
|
||||||
|
select(Character, Organization)
|
||||||
|
.join(Organization, Character.id == Organization.character_id)
|
||||||
|
.where(
|
||||||
|
Character.project_id == project_id,
|
||||||
|
Character.is_organization == True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
organizations_raw = organizations_result.all()
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"id": org.id,
|
||||||
|
"name": char.name,
|
||||||
|
"organization_type": char.organization_type,
|
||||||
|
"organization_purpose": char.organization_purpose,
|
||||||
|
"power_level": org.power_level,
|
||||||
|
"location": org.location,
|
||||||
|
"motto": org.motto
|
||||||
|
}
|
||||||
|
for char, org in organizations_raw
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@router.post("", response_model=OutlineResponse, summary="创建大纲")
|
@router.post("", response_model=OutlineResponse, summary="创建大纲")
|
||||||
async def create_outline(
|
async def create_outline(
|
||||||
outline: OutlineCreate,
|
outline: OutlineCreate,
|
||||||
@@ -145,26 +187,8 @@ async def get_project_outlines(
|
|||||||
request: Request,
|
request: Request,
|
||||||
db: AsyncSession = Depends(get_db)
|
db: AsyncSession = Depends(get_db)
|
||||||
):
|
):
|
||||||
"""获取指定项目的所有大纲(路径参数版本)"""
|
"""获取指定项目的所有大纲(路径参数版本,兼容旧API)"""
|
||||||
# 验证用户权限
|
return await get_outlines(project_id, request, db)
|
||||||
user_id = getattr(request.state, 'user_id', None)
|
|
||||||
await verify_project_access(project_id, user_id, db)
|
|
||||||
|
|
||||||
# 获取总数
|
|
||||||
count_result = await db.execute(
|
|
||||||
select(func.count(Outline.id)).where(Outline.project_id == project_id)
|
|
||||||
)
|
|
||||||
total = count_result.scalar_one()
|
|
||||||
|
|
||||||
# 获取大纲列表
|
|
||||||
result = await db.execute(
|
|
||||||
select(Outline)
|
|
||||||
.where(Outline.project_id == project_id)
|
|
||||||
.order_by(Outline.order_index)
|
|
||||||
)
|
|
||||||
outlines = result.scalars().all()
|
|
||||||
|
|
||||||
return OutlineListResponse(total=total, items=outlines)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{outline_id}", response_model=OutlineResponse, summary="获取大纲详情")
|
@router.get("/{outline_id}", response_model=OutlineResponse, summary="获取大纲详情")
|
||||||
@@ -406,18 +430,7 @@ async def predict_characters(
|
|||||||
characters = characters_result.scalars().all()
|
characters = characters_result.scalars().all()
|
||||||
|
|
||||||
# 构建已有章节概览
|
# 构建已有章节概览
|
||||||
all_chapters_brief = ""
|
all_chapters_brief = _build_chapters_brief(existing_outlines)
|
||||||
if len(existing_outlines) > 20:
|
|
||||||
recent_20 = existing_outlines[-20:]
|
|
||||||
all_chapters_brief = "\n".join([
|
|
||||||
f"第{o.order_index}章《{o.title}》"
|
|
||||||
for o in recent_20
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
all_chapters_brief = "\n".join([
|
|
||||||
f"第{o.order_index}章《{o.title}》"
|
|
||||||
for o in existing_outlines
|
|
||||||
])
|
|
||||||
|
|
||||||
# 调用自动角色服务进行预测
|
# 调用自动角色服务进行预测
|
||||||
from app.services.auto_character_service import get_auto_character_service
|
from app.services.auto_character_service import get_auto_character_service
|
||||||
@@ -479,7 +492,6 @@ async def predict_organizations(
|
|||||||
|
|
||||||
用于组织确认机制的第一步:在生成大纲前预测组织需求
|
用于组织确认机制的第一步:在生成大纲前预测组织需求
|
||||||
"""
|
"""
|
||||||
from app.schemas.outline import OrganizationPredictionResponse, PredictedOrganization
|
|
||||||
from app.models.relationship import Organization
|
from app.models.relationship import Organization
|
||||||
|
|
||||||
# 验证用户权限
|
# 验证用户权限
|
||||||
@@ -510,40 +522,10 @@ async def predict_organizations(
|
|||||||
characters = characters_result.scalars().all()
|
characters = characters_result.scalars().all()
|
||||||
|
|
||||||
# 获取现有组织
|
# 获取现有组织
|
||||||
organizations_result = await db.execute(
|
existing_organizations = await _get_existing_organizations(request_data.project_id, db)
|
||||||
select(Character, Organization)
|
|
||||||
.join(Organization, Character.id == Organization.character_id)
|
|
||||||
.where(
|
|
||||||
Character.project_id == request_data.project_id,
|
|
||||||
Character.is_organization == True
|
|
||||||
)
|
|
||||||
)
|
|
||||||
organizations_raw = organizations_result.all()
|
|
||||||
existing_organizations = []
|
|
||||||
for char, org in organizations_raw:
|
|
||||||
existing_organizations.append({
|
|
||||||
"id": org.id,
|
|
||||||
"name": char.name,
|
|
||||||
"organization_type": char.organization_type,
|
|
||||||
"organization_purpose": char.organization_purpose,
|
|
||||||
"power_level": org.power_level,
|
|
||||||
"location": org.location,
|
|
||||||
"motto": org.motto
|
|
||||||
})
|
|
||||||
|
|
||||||
# 构建已有章节概览
|
# 构建已有章节概览
|
||||||
all_chapters_brief = ""
|
all_chapters_brief = _build_chapters_brief(existing_outlines)
|
||||||
if len(existing_outlines) > 20:
|
|
||||||
recent_20 = existing_outlines[-20:]
|
|
||||||
all_chapters_brief = "\n".join([
|
|
||||||
f"第{o.order_index}章《{o.title}》"
|
|
||||||
for o in recent_20
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
all_chapters_brief = "\n".join([
|
|
||||||
f"第{o.order_index}章《{o.title}》"
|
|
||||||
for o in existing_outlines
|
|
||||||
])
|
|
||||||
|
|
||||||
# 调用自动组织服务进行预测
|
# 调用自动组织服务进行预测
|
||||||
from app.services.auto_organization_service import get_auto_organization_service
|
from app.services.auto_organization_service import get_auto_organization_service
|
||||||
@@ -613,11 +595,7 @@ async def _generate_new_outline(
|
|||||||
select(Character).where(Character.project_id == project.id)
|
select(Character).where(Character.project_id == project.id)
|
||||||
)
|
)
|
||||||
characters = characters_result.scalars().all()
|
characters = characters_result.scalars().all()
|
||||||
characters_info = "\n".join([
|
characters_info = _build_characters_info(characters)
|
||||||
f"- {char.name} ({'组织' if char.is_organization else '角色'}, {char.role_type}): "
|
|
||||||
f"{char.personality[:100] if char.personality else '暂无描述'}"
|
|
||||||
for char in characters
|
|
||||||
])
|
|
||||||
|
|
||||||
# 🔍 MCP工具增强:收集情节设计参考资料(优化版)
|
# 🔍 MCP工具增强:收集情节设计参考资料(优化版)
|
||||||
mcp_reference_materials = ""
|
mcp_reference_materials = ""
|
||||||
@@ -894,11 +872,7 @@ async def _continue_outline(
|
|||||||
select(Character).where(Character.project_id == project.id)
|
select(Character).where(Character.project_id == project.id)
|
||||||
)
|
)
|
||||||
characters = characters_result.scalars().all()
|
characters = characters_result.scalars().all()
|
||||||
characters_info = "\n".join([
|
characters_info = _build_characters_info(characters)
|
||||||
f"- {char.name} ({'组织' if char.is_organization else '角色'}, {char.role_type}): "
|
|
||||||
f"{char.personality[:100] if char.personality else '暂无描述'}"
|
|
||||||
for char in characters
|
|
||||||
])
|
|
||||||
|
|
||||||
# 情节阶段指导
|
# 情节阶段指导
|
||||||
stage_instructions = {
|
stage_instructions = {
|
||||||
@@ -978,11 +952,7 @@ async def _continue_outline(
|
|||||||
logger.info(f"ℹ️ 【确认模式】所有角色均已存在,无需创建")
|
logger.info(f"ℹ️ 【确认模式】所有角色均已存在,无需创建")
|
||||||
|
|
||||||
# 更新角色信息(供后续大纲生成使用)
|
# 更新角色信息(供后续大纲生成使用)
|
||||||
characters_info = "\n".join([
|
characters_info = _build_characters_info(characters)
|
||||||
f"- {char.name} ({'组织' if char.is_organization else '角色'}, {char.role_type}): "
|
|
||||||
f"{char.personality[:100] if char.personality else '暂无描述'}"
|
|
||||||
for char in characters
|
|
||||||
])
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"⚠️ 【确认模式】创建确认角色失败: {e}", exc_info=True)
|
logger.error(f"⚠️ 【确认模式】创建确认角色失败: {e}", exc_info=True)
|
||||||
@@ -992,18 +962,7 @@ async def _continue_outline(
|
|||||||
from app.services.auto_character_service import get_auto_character_service
|
from app.services.auto_character_service import get_auto_character_service
|
||||||
|
|
||||||
# 构建已有章节概览
|
# 构建已有章节概览
|
||||||
all_chapters_brief_for_analysis = ""
|
all_chapters_brief_for_analysis = _build_chapters_brief(existing_outlines)
|
||||||
if len(existing_outlines) > 20:
|
|
||||||
recent_20 = existing_outlines[-20:]
|
|
||||||
all_chapters_brief_for_analysis = "\n".join([
|
|
||||||
f"第{o.order_index}章《{o.title}》"
|
|
||||||
for o in recent_20
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
all_chapters_brief_for_analysis = "\n".join([
|
|
||||||
f"第{o.order_index}章《{o.title}》"
|
|
||||||
for o in existing_outlines
|
|
||||||
])
|
|
||||||
|
|
||||||
auto_char_service = get_auto_character_service(user_ai_service)
|
auto_char_service = get_auto_character_service(user_ai_service)
|
||||||
|
|
||||||
@@ -1075,11 +1034,7 @@ async def _continue_outline(
|
|||||||
|
|
||||||
# 更新角色信息(供后续大纲生成使用)
|
# 更新角色信息(供后续大纲生成使用)
|
||||||
characters.extend(auto_result["new_characters"])
|
characters.extend(auto_result["new_characters"])
|
||||||
characters_info = "\n".join([
|
characters_info = _build_characters_info(characters)
|
||||||
f"- {char.name} ({'组织' if char.is_organization else '角色'}, {char.role_type}): "
|
|
||||||
f"{char.personality[:100] if char.personality else '暂无描述'}"
|
|
||||||
for char in characters
|
|
||||||
])
|
|
||||||
else:
|
else:
|
||||||
logger.info(f"✅ 【直接创建模式】AI判断无需引入新角色,继续生成大纲")
|
logger.info(f"✅ 【直接创建模式】AI判断无需引入新角色,继续生成大纲")
|
||||||
|
|
||||||
@@ -1091,29 +1046,8 @@ async def _continue_outline(
|
|||||||
|
|
||||||
# 🏛️ 【组织引入】在生成大纲前预测并创建组织
|
# 🏛️ 【组织引入】在生成大纲前预测并创建组织
|
||||||
if request.enable_auto_organizations:
|
if request.enable_auto_organizations:
|
||||||
from app.models.relationship import Organization
|
|
||||||
|
|
||||||
# 获取现有组织
|
# 获取现有组织
|
||||||
organizations_result = await db.execute(
|
existing_organizations = await _get_existing_organizations(project.id, db)
|
||||||
select(Character, Organization)
|
|
||||||
.join(Organization, Character.id == Organization.character_id)
|
|
||||||
.where(
|
|
||||||
Character.project_id == project.id,
|
|
||||||
Character.is_organization == True
|
|
||||||
)
|
|
||||||
)
|
|
||||||
organizations_raw = organizations_result.all()
|
|
||||||
existing_organizations = []
|
|
||||||
for char, org in organizations_raw:
|
|
||||||
existing_organizations.append({
|
|
||||||
"id": org.id,
|
|
||||||
"name": char.name,
|
|
||||||
"organization_type": char.organization_type,
|
|
||||||
"organization_purpose": char.organization_purpose,
|
|
||||||
"power_level": org.power_level,
|
|
||||||
"location": org.location,
|
|
||||||
"motto": org.motto
|
|
||||||
})
|
|
||||||
|
|
||||||
# 检查是否有用户确认的组织列表
|
# 检查是否有用户确认的组织列表
|
||||||
if request.confirmed_organizations:
|
if request.confirmed_organizations:
|
||||||
@@ -1177,11 +1111,7 @@ async def _continue_outline(
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
# 更新角色信息(供后续大纲生成使用)
|
# 更新角色信息(供后续大纲生成使用)
|
||||||
characters_info = "\n".join([
|
characters_info = _build_characters_info(characters)
|
||||||
f"- {char.name} ({'组织' if char.is_organization else '角色'}, {char.role_type}): "
|
|
||||||
f"{char.personality[:100] if char.personality else '暂无描述'}"
|
|
||||||
for char in characters
|
|
||||||
])
|
|
||||||
|
|
||||||
logger.info(f"✅ 【确认模式】成功创建 {len(request.confirmed_organizations)} 个用户确认的组织")
|
logger.info(f"✅ 【确认模式】成功创建 {len(request.confirmed_organizations)} 个用户确认的组织")
|
||||||
|
|
||||||
@@ -1193,18 +1123,7 @@ async def _continue_outline(
|
|||||||
from app.services.auto_organization_service import get_auto_organization_service
|
from app.services.auto_organization_service import get_auto_organization_service
|
||||||
|
|
||||||
# 构建已有章节概览
|
# 构建已有章节概览
|
||||||
all_chapters_brief_for_org_analysis = ""
|
all_chapters_brief_for_org_analysis = _build_chapters_brief(existing_outlines)
|
||||||
if len(existing_outlines) > 20:
|
|
||||||
recent_20 = existing_outlines[-20:]
|
|
||||||
all_chapters_brief_for_org_analysis = "\n".join([
|
|
||||||
f"第{o.order_index}章《{o.title}》"
|
|
||||||
for o in recent_20
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
all_chapters_brief_for_org_analysis = "\n".join([
|
|
||||||
f"第{o.order_index}章《{o.title}》"
|
|
||||||
for o in existing_outlines
|
|
||||||
])
|
|
||||||
|
|
||||||
auto_org_service = get_auto_organization_service(user_ai_service)
|
auto_org_service = get_auto_organization_service(user_ai_service)
|
||||||
|
|
||||||
@@ -1281,11 +1200,7 @@ async def _continue_outline(
|
|||||||
org_char = org_item.get("character")
|
org_char = org_item.get("character")
|
||||||
if org_char:
|
if org_char:
|
||||||
characters.append(org_char)
|
characters.append(org_char)
|
||||||
characters_info = "\n".join([
|
characters_info = _build_characters_info(characters)
|
||||||
f"- {char.name} ({'组织' if char.is_organization else '角色'}, {char.role_type}): "
|
|
||||||
f"{char.personality[:100] if char.personality else '暂无描述'}"
|
|
||||||
for char in characters
|
|
||||||
])
|
|
||||||
else:
|
else:
|
||||||
logger.info(f"✅ 【直接创建模式】AI判断无需引入新组织,继续生成大纲")
|
logger.info(f"✅ 【直接创建模式】AI判断无需引入新组织,继续生成大纲")
|
||||||
|
|
||||||
@@ -1711,11 +1626,7 @@ async def new_outline_generator(
|
|||||||
select(Character).where(Character.project_id == project_id)
|
select(Character).where(Character.project_id == project_id)
|
||||||
)
|
)
|
||||||
characters = characters_result.scalars().all()
|
characters = characters_result.scalars().all()
|
||||||
characters_info = "\n".join([
|
characters_info = _build_characters_info(characters)
|
||||||
f"- {char.name} ({'组织' if char.is_organization else '角色'}, {char.role_type}): "
|
|
||||||
f"{char.personality[:100] if char.personality else '暂无描述'}"
|
|
||||||
for char in characters
|
|
||||||
])
|
|
||||||
|
|
||||||
# 🔍 MCP工具增强:收集情节设计参考资料(优化版)
|
# 🔍 MCP工具增强:收集情节设计参考资料(优化版)
|
||||||
mcp_reference_materials = ""
|
mcp_reference_materials = ""
|
||||||
@@ -2049,11 +1960,7 @@ async def continue_outline_generator(
|
|||||||
select(Character).where(Character.project_id == project_id)
|
select(Character).where(Character.project_id == project_id)
|
||||||
)
|
)
|
||||||
characters = characters_result.scalars().all()
|
characters = characters_result.scalars().all()
|
||||||
characters_info = "\n".join([
|
characters_info = _build_characters_info(characters)
|
||||||
f"- {char.name} ({'组织' if char.is_organization else '角色'}, {char.role_type}): "
|
|
||||||
f"{char.personality[:100] if char.personality else '暂无描述'}"
|
|
||||||
for char in characters
|
|
||||||
])
|
|
||||||
|
|
||||||
# 分批配置
|
# 分批配置
|
||||||
batch_size = 5
|
batch_size = 5
|
||||||
@@ -2195,35 +2102,19 @@ async def continue_outline_generator(
|
|||||||
from app.services.auto_character_service import get_auto_character_service
|
from app.services.auto_character_service import get_auto_character_service
|
||||||
|
|
||||||
# 构建已有章节概览
|
# 构建已有章节概览
|
||||||
all_chapters_brief_for_analysis = ""
|
all_chapters_brief_for_analysis = _build_chapters_brief(existing_outlines)
|
||||||
if len(existing_outlines) > 20:
|
|
||||||
recent_20 = existing_outlines[-20:]
|
|
||||||
all_chapters_brief_for_analysis = "\n".join([
|
|
||||||
f"第{o.order_index}章《{o.title}》"
|
|
||||||
for o in recent_20
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
all_chapters_brief_for_analysis = "\n".join([
|
|
||||||
f"第{o.order_index}章《{o.title}》"
|
|
||||||
for o in existing_outlines
|
|
||||||
])
|
|
||||||
|
|
||||||
auto_char_service = get_auto_character_service(user_ai_service)
|
auto_char_service = get_auto_character_service(user_ai_service)
|
||||||
|
|
||||||
if require_confirmation:
|
if require_confirmation:
|
||||||
# 🔮 预测模式:仅预测角色,不自动创建,需要用户确认
|
# 🔮 预测模式:仅预测角色,不自动创建,需要用户确认
|
||||||
yield await SSEResponse.send_progress(
|
yield await SSEResponse.send_progress(
|
||||||
"🔮 【预测模式】AI分析角色需求...",
|
"🔮 【预测模式】开始分析角色需求...",
|
||||||
11
|
12
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"🔮 【预测模式】在生成大纲前预测是否需要新角色")
|
logger.info(f"🔮 【预测模式】在生成大纲前预测是否需要新角色")
|
||||||
|
|
||||||
yield await SSEResponse.send_progress(
|
# 进度消息不使用回调,因为在async generator中无法嵌套yield
|
||||||
"🤖 【预测模式】AI智能预测新角色...",
|
|
||||||
15
|
|
||||||
)
|
|
||||||
|
|
||||||
auto_result = await auto_char_service.analyze_and_create_characters(
|
auto_result = await auto_char_service.analyze_and_create_characters(
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
outline_content="", # 预测模式不需要大纲内容
|
outline_content="", # 预测模式不需要大纲内容
|
||||||
@@ -2239,6 +2130,11 @@ async def continue_outline_generator(
|
|||||||
preview_only=True # ✅ 仅预测不创建
|
preview_only=True # ✅ 仅预测不创建
|
||||||
)
|
)
|
||||||
|
|
||||||
|
yield await SSEResponse.send_progress(
|
||||||
|
"✅ 【预测模式】角色需求分析完成",
|
||||||
|
18
|
||||||
|
)
|
||||||
|
|
||||||
# 检查是否需要新角色
|
# 检查是否需要新角色
|
||||||
if auto_result.get("needs_new_characters") and auto_result.get("predicted_characters"):
|
if auto_result.get("needs_new_characters") and auto_result.get("predicted_characters"):
|
||||||
predicted_count = len(auto_result["predicted_characters"])
|
predicted_count = len(auto_result["predicted_characters"])
|
||||||
@@ -2266,25 +2162,53 @@ async def continue_outline_generator(
|
|||||||
else:
|
else:
|
||||||
# 🚀 直接创建模式:预测后自动创建,无需用户确认
|
# 🚀 直接创建模式:预测后自动创建,无需用户确认
|
||||||
yield await SSEResponse.send_progress(
|
yield await SSEResponse.send_progress(
|
||||||
"🚀 【直接创建模式】检测并自动创建新角色(无需确认)...",
|
"🚀 【直接创建模式】开始分析并创建角色...",
|
||||||
13
|
14
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"🚀 【直接创建模式】在生成大纲前预测并直接创建新角色")
|
logger.info(f"🚀 【直接创建模式】在生成大纲前预测并直接创建新角色")
|
||||||
|
|
||||||
auto_result = await auto_char_service.analyze_and_create_characters(
|
# 使用队列桥接回调和generator
|
||||||
project_id=project_id,
|
import asyncio
|
||||||
outline_content="",
|
progress_queue = asyncio.Queue()
|
||||||
existing_characters=list(characters),
|
|
||||||
db=db,
|
async def char_progress_callback(message):
|
||||||
user_id=user_id,
|
await progress_queue.put(message)
|
||||||
enable_mcp=data.get("enable_mcp", True),
|
|
||||||
all_chapters_brief=all_chapters_brief_for_analysis,
|
# 启动服务任务
|
||||||
start_chapter=last_chapter_number + 1,
|
char_task = asyncio.create_task(
|
||||||
chapter_count=total_chapters_to_generate,
|
auto_char_service.analyze_and_create_characters(
|
||||||
plot_stage=data.get("plot_stage", "development"),
|
project_id=project_id,
|
||||||
story_direction=data.get("story_direction", "自然延续"),
|
outline_content="",
|
||||||
preview_only=False # ✅ 直接创建角色
|
existing_characters=list(characters),
|
||||||
|
db=db,
|
||||||
|
user_id=user_id,
|
||||||
|
enable_mcp=data.get("enable_mcp", True),
|
||||||
|
all_chapters_brief=all_chapters_brief_for_analysis,
|
||||||
|
start_chapter=last_chapter_number + 1,
|
||||||
|
chapter_count=total_chapters_to_generate,
|
||||||
|
plot_stage=data.get("plot_stage", "development"),
|
||||||
|
story_direction=data.get("story_direction", "自然延续"),
|
||||||
|
preview_only=False,
|
||||||
|
progress_callback=char_progress_callback
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 在等待任务完成的同时,消费队列中的进度消息
|
||||||
|
char_progress_base = 14
|
||||||
|
while not char_task.done():
|
||||||
|
try:
|
||||||
|
message = await asyncio.wait_for(progress_queue.get(), timeout=0.1)
|
||||||
|
char_progress_base = min(char_progress_base + 1, 17)
|
||||||
|
yield await SSEResponse.send_progress(message, char_progress_base)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 获取结果
|
||||||
|
auto_result = await char_task
|
||||||
|
|
||||||
|
yield await SSEResponse.send_progress(
|
||||||
|
"✅ 【直接创建模式】角色分析和创建完成",
|
||||||
|
18
|
||||||
)
|
)
|
||||||
|
|
||||||
# 如果创建了新角色,更新角色列表
|
# 如果创建了新角色,更新角色列表
|
||||||
@@ -2302,11 +2226,7 @@ async def continue_outline_generator(
|
|||||||
|
|
||||||
# 更新角色信息(供后续大纲生成使用)
|
# 更新角色信息(供后续大纲生成使用)
|
||||||
characters.extend(auto_result["new_characters"])
|
characters.extend(auto_result["new_characters"])
|
||||||
characters_info = "\n".join([
|
characters_info = _build_characters_info(characters)
|
||||||
f"- {char.name} ({'组织' if char.is_organization else '角色'}, {char.role_type}): "
|
|
||||||
f"{char.personality[:100] if char.personality else '暂无描述'}"
|
|
||||||
for char in characters
|
|
||||||
])
|
|
||||||
else:
|
else:
|
||||||
yield await SSEResponse.send_progress(
|
yield await SSEResponse.send_progress(
|
||||||
"✅ 【直接创建模式】无需引入新角色,继续生成大纲",
|
"✅ 【直接创建模式】无需引入新角色,继续生成大纲",
|
||||||
@@ -2329,29 +2249,8 @@ async def continue_outline_generator(
|
|||||||
# confirmed_organizations = data.get("confirmed_organizations")
|
# confirmed_organizations = data.get("confirmed_organizations")
|
||||||
|
|
||||||
if enable_auto_organizations:
|
if enable_auto_organizations:
|
||||||
from app.models.relationship import Organization
|
|
||||||
|
|
||||||
# 获取现有组织
|
# 获取现有组织
|
||||||
organizations_result = await db.execute(
|
existing_organizations = await _get_existing_organizations(project_id, db)
|
||||||
select(Character, Organization)
|
|
||||||
.join(Organization, Character.id == Organization.character_id)
|
|
||||||
.where(
|
|
||||||
Character.project_id == project_id,
|
|
||||||
Character.is_organization == True
|
|
||||||
)
|
|
||||||
)
|
|
||||||
organizations_raw = organizations_result.all()
|
|
||||||
existing_organizations = []
|
|
||||||
for char, org in organizations_raw:
|
|
||||||
existing_organizations.append({
|
|
||||||
"id": org.id,
|
|
||||||
"name": char.name,
|
|
||||||
"organization_type": char.organization_type,
|
|
||||||
"organization_purpose": char.organization_purpose,
|
|
||||||
"power_level": org.power_level,
|
|
||||||
"location": org.location,
|
|
||||||
"motto": org.motto
|
|
||||||
})
|
|
||||||
|
|
||||||
# 检查是否有用户确认的组织列表
|
# 检查是否有用户确认的组织列表
|
||||||
if confirmed_organizations:
|
if confirmed_organizations:
|
||||||
@@ -2465,34 +2364,17 @@ async def continue_outline_generator(
|
|||||||
from app.services.auto_organization_service import get_auto_organization_service
|
from app.services.auto_organization_service import get_auto_organization_service
|
||||||
|
|
||||||
# 构建已有章节概览
|
# 构建已有章节概览
|
||||||
all_chapters_brief_for_org_analysis = ""
|
all_chapters_brief_for_org_analysis = _build_chapters_brief(existing_outlines)
|
||||||
if len(existing_outlines) > 20:
|
|
||||||
recent_20 = existing_outlines[-20:]
|
|
||||||
all_chapters_brief_for_org_analysis = "\n".join([
|
|
||||||
f"第{o.order_index}章《{o.title}》"
|
|
||||||
for o in recent_20
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
all_chapters_brief_for_org_analysis = "\n".join([
|
|
||||||
f"第{o.order_index}章《{o.title}》"
|
|
||||||
for o in existing_outlines
|
|
||||||
])
|
|
||||||
|
|
||||||
auto_org_service = get_auto_organization_service(user_ai_service)
|
auto_org_service = get_auto_organization_service(user_ai_service)
|
||||||
|
|
||||||
if require_org_confirmation:
|
if require_org_confirmation:
|
||||||
# 🔮 预测模式:仅预测组织,不自动创建,需要用户确认
|
# 🔮 预测模式:仅预测组织,不自动创建,需要用户确认
|
||||||
yield await SSEResponse.send_progress(
|
yield await SSEResponse.send_progress(
|
||||||
"🔮 【预测模式】AI分析组织需求...",
|
"🔮 【预测模式】开始分析组织需求...",
|
||||||
21
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"🔮 【预测模式】在生成大纲前预测是否需要新组织")
|
|
||||||
|
|
||||||
yield await SSEResponse.send_progress(
|
|
||||||
"🤖 【预测模式】AI智能预测新组织...",
|
|
||||||
22
|
22
|
||||||
)
|
)
|
||||||
|
logger.info(f"🔮 【预测模式】在生成大纲前预测是否需要新组织")
|
||||||
|
|
||||||
auto_result = await auto_org_service.analyze_and_create_organizations(
|
auto_result = await auto_org_service.analyze_and_create_organizations(
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
@@ -2510,6 +2392,11 @@ async def continue_outline_generator(
|
|||||||
preview_only=True # ✅ 仅预测不创建
|
preview_only=True # ✅ 仅预测不创建
|
||||||
)
|
)
|
||||||
|
|
||||||
|
yield await SSEResponse.send_progress(
|
||||||
|
"✅ 【预测模式】组织需求分析完成",
|
||||||
|
28
|
||||||
|
)
|
||||||
|
|
||||||
# 检查是否需要新组织
|
# 检查是否需要新组织
|
||||||
if auto_result.get("needs_new_organizations") and auto_result.get("predicted_organizations"):
|
if auto_result.get("needs_new_organizations") and auto_result.get("predicted_organizations"):
|
||||||
predicted_count = len(auto_result["predicted_organizations"])
|
predicted_count = len(auto_result["predicted_organizations"])
|
||||||
@@ -2537,31 +2424,54 @@ async def continue_outline_generator(
|
|||||||
else:
|
else:
|
||||||
# 🚀 直接创建模式:预测后自动创建,无需用户确认
|
# 🚀 直接创建模式:预测后自动创建,无需用户确认
|
||||||
yield await SSEResponse.send_progress(
|
yield await SSEResponse.send_progress(
|
||||||
"🚀 【直接创建模式】AI分析组织需求...",
|
"🚀 【直接创建模式】开始分析并创建组织...",
|
||||||
23
|
24
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"🚀 【直接创建模式】在生成大纲前预测并直接创建新组织")
|
logger.info(f"🚀 【直接创建模式】在生成大纲前预测并直接创建新组织")
|
||||||
|
|
||||||
yield await SSEResponse.send_progress(
|
# 使用队列桥接回调和generator
|
||||||
"🤖 【直接创建模式】AI预测并生成组织详情...",
|
import asyncio
|
||||||
25
|
org_progress_queue = asyncio.Queue()
|
||||||
|
|
||||||
|
async def org_progress_callback(message):
|
||||||
|
await org_progress_queue.put(message)
|
||||||
|
|
||||||
|
# 启动服务任务
|
||||||
|
org_task = asyncio.create_task(
|
||||||
|
auto_org_service.analyze_and_create_organizations(
|
||||||
|
project_id=project_id,
|
||||||
|
outline_content="",
|
||||||
|
existing_characters=list(characters),
|
||||||
|
existing_organizations=existing_organizations,
|
||||||
|
db=db,
|
||||||
|
user_id=user_id,
|
||||||
|
enable_mcp=data.get("enable_mcp", True),
|
||||||
|
all_chapters_brief=all_chapters_brief_for_org_analysis,
|
||||||
|
start_chapter=last_chapter_number + 1,
|
||||||
|
chapter_count=total_chapters_to_generate,
|
||||||
|
plot_stage=data.get("plot_stage", "development"),
|
||||||
|
story_direction=data.get("story_direction", "自然延续"),
|
||||||
|
preview_only=False,
|
||||||
|
progress_callback=org_progress_callback
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
auto_result = await auto_org_service.analyze_and_create_organizations(
|
# 在等待任务完成的同时,消费队列中的进度消息
|
||||||
project_id=project_id,
|
org_progress_base = 24
|
||||||
outline_content="",
|
while not org_task.done():
|
||||||
existing_characters=list(characters),
|
try:
|
||||||
existing_organizations=existing_organizations,
|
message = await asyncio.wait_for(org_progress_queue.get(), timeout=0.1)
|
||||||
db=db,
|
org_progress_base = min(org_progress_base + 1, 27)
|
||||||
user_id=user_id,
|
yield await SSEResponse.send_progress(message, org_progress_base)
|
||||||
enable_mcp=data.get("enable_mcp", True),
|
except asyncio.TimeoutError:
|
||||||
all_chapters_brief=all_chapters_brief_for_org_analysis,
|
pass
|
||||||
start_chapter=last_chapter_number + 1,
|
|
||||||
chapter_count=total_chapters_to_generate,
|
# 获取结果
|
||||||
plot_stage=data.get("plot_stage", "development"),
|
auto_result = await org_task
|
||||||
story_direction=data.get("story_direction", "自然延续"),
|
|
||||||
preview_only=False # ✅ 直接创建组织
|
yield await SSEResponse.send_progress(
|
||||||
|
"✅ 【直接创建模式】组织分析和创建完成",
|
||||||
|
28
|
||||||
)
|
)
|
||||||
|
|
||||||
# 如果创建了新组织,更新角色列表
|
# 如果创建了新组织,更新角色列表
|
||||||
@@ -2587,11 +2497,7 @@ async def continue_outline_generator(
|
|||||||
org_char = org_item.get("character")
|
org_char = org_item.get("character")
|
||||||
if org_char:
|
if org_char:
|
||||||
characters.append(org_char)
|
characters.append(org_char)
|
||||||
characters_info = "\n".join([
|
characters_info = _build_characters_info(characters)
|
||||||
f"- {char.name} ({'组织' if char.is_organization else '角色'}, {char.role_type}): "
|
|
||||||
f"{char.personality[:100] if char.personality else '暂无描述'}"
|
|
||||||
for char in characters
|
|
||||||
])
|
|
||||||
else:
|
else:
|
||||||
yield await SSEResponse.send_progress(
|
yield await SSEResponse.send_progress(
|
||||||
"✅ 【直接创建模式】无需引入新组织,继续生成大纲",
|
"✅ 【直接创建模式】无需引入新组织,继续生成大纲",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""自动角色引入服务 - 在续写大纲时根据剧情推进自动引入新角色"""
|
"""自动角色引入服务 - 在续写大纲时根据剧情推进自动引入新角色"""
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional, Callable, Awaitable
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
import json
|
import json
|
||||||
@@ -33,7 +33,8 @@ class AutoCharacterService:
|
|||||||
chapter_count: int = 3,
|
chapter_count: int = 3,
|
||||||
plot_stage: str = "发展",
|
plot_stage: str = "发展",
|
||||||
story_direction: str = "继续推进主线剧情",
|
story_direction: str = "继续推进主线剧情",
|
||||||
preview_only: bool = False
|
preview_only: bool = False,
|
||||||
|
progress_callback: Optional[Callable[[str], Awaitable[None]]] = None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
预测性分析并创建需要的新角色(方案A:先角色后大纲)
|
预测性分析并创建需要的新角色(方案A:先角色后大纲)
|
||||||
@@ -136,6 +137,9 @@ class AutoCharacterService:
|
|||||||
logger.info(f" [{idx+1}/{len(character_specs)}] 生成角色规格: {spec_name}")
|
logger.info(f" [{idx+1}/{len(character_specs)}] 生成角色规格: {spec_name}")
|
||||||
logger.debug(f" 角色规格内容: {json.dumps(spec, ensure_ascii=False)}")
|
logger.debug(f" 角色规格内容: {json.dumps(spec, ensure_ascii=False)}")
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(f"🎨 [{idx+1}/{len(character_specs)}] 生成角色详情: {spec_name}")
|
||||||
|
|
||||||
# 生成角色详细信息
|
# 生成角色详细信息
|
||||||
character_data = await self._generate_character_details(
|
character_data = await self._generate_character_details(
|
||||||
spec=spec,
|
spec=spec,
|
||||||
@@ -148,6 +152,9 @@ class AutoCharacterService:
|
|||||||
|
|
||||||
logger.debug(f" AI生成的角色数据: {json.dumps(character_data, ensure_ascii=False)[:200]}")
|
logger.debug(f" AI生成的角色数据: {json.dumps(character_data, ensure_ascii=False)[:200]}")
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(f"💾 [{idx+1}/{len(character_specs)}] 保存角色: {character_data.get('name', spec_name)}")
|
||||||
|
|
||||||
# 创建角色记录
|
# 创建角色记录
|
||||||
character = await self._create_character_record(
|
character = await self._create_character_record(
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
@@ -158,6 +165,9 @@ class AutoCharacterService:
|
|||||||
new_characters.append(character)
|
new_characters.append(character)
|
||||||
logger.info(f" ✅ 创建新角色: {character.name} ({character.role_type}), ID: {character.id}")
|
logger.info(f" ✅ 创建新角色: {character.name} ({character.role_type}), ID: {character.id}")
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(f"✅ [{idx+1}/{len(character_specs)}] 角色创建成功: {character.name}")
|
||||||
|
|
||||||
# 建立关系(兼容两种字段名)
|
# 建立关系(兼容两种字段名)
|
||||||
relationships_data = character_data.get("relationships") or character_data.get("relationships_array", [])
|
relationships_data = character_data.get("relationships") or character_data.get("relationships_array", [])
|
||||||
logger.info(f" 🔍 检查关系数据:")
|
logger.info(f" 🔍 检查关系数据:")
|
||||||
@@ -170,6 +180,9 @@ class AutoCharacterService:
|
|||||||
logger.info(f" 🔗 开始创建 {len(relationships_data)} 条关系...")
|
logger.info(f" 🔗 开始创建 {len(relationships_data)} 条关系...")
|
||||||
for idx, rel in enumerate(relationships_data):
|
for idx, rel in enumerate(relationships_data):
|
||||||
logger.info(f" [{idx+1}] {rel.get('target_character_name')} - {rel.get('relationship_type')}")
|
logger.info(f" [{idx+1}] {rel.get('target_character_name')} - {rel.get('relationship_type')}")
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(f"🔗 [{idx+1}/{len(character_specs)}] 建立 {len(relationships_data)} 个关系")
|
||||||
else:
|
else:
|
||||||
logger.warning(f" ⚠️ AI返回的角色数据中没有关系信息!")
|
logger.warning(f" ⚠️ AI返回的角色数据中没有关系信息!")
|
||||||
logger.warning(f" 完整的character_data keys: {list(character_data.keys())}")
|
logger.warning(f" 完整的character_data keys: {list(character_data.keys())}")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""自动组织引入服务 - 在续写大纲时根据剧情推进自动引入新组织"""
|
"""自动组织引入服务 - 在续写大纲时根据剧情推进自动引入新组织"""
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional, Callable, Awaitable
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
import json
|
import json
|
||||||
@@ -34,7 +34,8 @@ class AutoOrganizationService:
|
|||||||
chapter_count: int = 3,
|
chapter_count: int = 3,
|
||||||
plot_stage: str = "发展",
|
plot_stage: str = "发展",
|
||||||
story_direction: str = "继续推进主线剧情",
|
story_direction: str = "继续推进主线剧情",
|
||||||
preview_only: bool = False
|
preview_only: bool = False,
|
||||||
|
progress_callback: Optional[Callable[[str], Awaitable[None]]] = None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
预测性分析并创建需要的新组织
|
预测性分析并创建需要的新组织
|
||||||
@@ -86,6 +87,9 @@ class AutoOrganizationService:
|
|||||||
existing_chars_summary = self._build_character_summary(existing_characters)
|
existing_chars_summary = self._build_character_summary(existing_characters)
|
||||||
|
|
||||||
# 3. AI预测性分析是否需要新组织
|
# 3. AI预测性分析是否需要新组织
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback("🤖 AI分析组织需求...")
|
||||||
|
|
||||||
analysis_result = await self._analyze_organization_needs(
|
analysis_result = await self._analyze_organization_needs(
|
||||||
project=project,
|
project=project,
|
||||||
outline_content=outline_content,
|
outline_content=outline_content,
|
||||||
@@ -101,6 +105,9 @@ class AutoOrganizationService:
|
|||||||
story_direction=story_direction
|
story_direction=story_direction
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback("✅ 组织需求分析完成")
|
||||||
|
|
||||||
# 4. 判断是否需要创建组织
|
# 4. 判断是否需要创建组织
|
||||||
if not analysis_result or not analysis_result.get("needs_new_organizations"):
|
if not analysis_result or not analysis_result.get("needs_new_organizations"):
|
||||||
logger.info("✅ AI判断:当前剧情不需要引入新组织")
|
logger.info("✅ AI判断:当前剧情不需要引入新组织")
|
||||||
@@ -141,6 +148,9 @@ class AutoOrganizationService:
|
|||||||
logger.info(f" [{idx+1}/{len(organization_specs)}] 生成组织规格: {spec_name}")
|
logger.info(f" [{idx+1}/{len(organization_specs)}] 生成组织规格: {spec_name}")
|
||||||
logger.debug(f" 组织规格内容: {json.dumps(spec, ensure_ascii=False)}")
|
logger.debug(f" 组织规格内容: {json.dumps(spec, ensure_ascii=False)}")
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(f"🏛️ [{idx+1}/{len(organization_specs)}] 生成组织详情: {spec_name}")
|
||||||
|
|
||||||
# 生成组织详细信息
|
# 生成组织详细信息
|
||||||
organization_data = await self._generate_organization_details(
|
organization_data = await self._generate_organization_details(
|
||||||
spec=spec,
|
spec=spec,
|
||||||
@@ -154,6 +164,9 @@ class AutoOrganizationService:
|
|||||||
|
|
||||||
logger.debug(f" AI生成的组织数据: {json.dumps(organization_data, ensure_ascii=False)[:200]}")
|
logger.debug(f" AI生成的组织数据: {json.dumps(organization_data, ensure_ascii=False)[:200]}")
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(f"💾 [{idx+1}/{len(organization_specs)}] 保存组织: {organization_data.get('name', spec_name)}")
|
||||||
|
|
||||||
# 创建组织记录(先创建Character记录,再创建Organization记录)
|
# 创建组织记录(先创建Character记录,再创建Organization记录)
|
||||||
character, organization = await self._create_organization_record(
|
character, organization = await self._create_organization_record(
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
@@ -167,10 +180,17 @@ class AutoOrganizationService:
|
|||||||
})
|
})
|
||||||
logger.info(f" ✅ 创建新组织: {character.name}, ID: {organization.id}")
|
logger.info(f" ✅ 创建新组织: {character.name}, ID: {organization.id}")
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(f"✅ [{idx+1}/{len(organization_specs)}] 组织创建成功: {character.name}")
|
||||||
|
|
||||||
# 建立成员关系
|
# 建立成员关系
|
||||||
members_data = organization_data.get("initial_members", [])
|
members_data = organization_data.get("initial_members", [])
|
||||||
if members_data:
|
if members_data:
|
||||||
logger.info(f" 🔗 开始创建 {len(members_data)} 个成员关系...")
|
logger.info(f" 🔗 开始创建 {len(members_data)} 个成员关系...")
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(f"🔗 [{idx+1}/{len(organization_specs)}] 建立 {len(members_data)} 个成员关系")
|
||||||
|
|
||||||
members = await self._create_member_relationships(
|
members = await self._create_member_relationships(
|
||||||
organization=organization,
|
organization=organization,
|
||||||
member_specs=members_data,
|
member_specs=members_data,
|
||||||
|
|||||||
Reference in New Issue
Block a user