feature:新增大纲续写-智能生成组织功能,自动添加组织成员
This commit is contained in:
+756
-83
@@ -25,7 +25,10 @@ from app.schemas.outline import (
|
||||
CreateChaptersFromPlansResponse,
|
||||
CharacterPredictionRequest,
|
||||
PredictedCharacter,
|
||||
CharacterPredictionResponse
|
||||
CharacterPredictionResponse,
|
||||
OrganizationPredictionRequest,
|
||||
PredictedOrganization,
|
||||
OrganizationPredictionResponse
|
||||
)
|
||||
from app.services.ai_service import AIService
|
||||
from app.services.prompt_service import prompt_service, PromptService
|
||||
@@ -464,6 +467,136 @@ async def predict_characters(
|
||||
raise HTTPException(status_code=500, detail=f"角色预测失败: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/predict-organizations", summary="预测续写所需组织")
|
||||
async def predict_organizations(
|
||||
request_data: OrganizationPredictionRequest,
|
||||
http_request: Request,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user_ai_service: AIService = Depends(get_user_ai_service)
|
||||
):
|
||||
"""
|
||||
预测续写大纲时可能需要的新组织
|
||||
|
||||
用于组织确认机制的第一步:在生成大纲前预测组织需求
|
||||
"""
|
||||
from app.schemas.outline import OrganizationPredictionResponse, PredictedOrganization
|
||||
from app.models.relationship import Organization
|
||||
|
||||
# 验证用户权限
|
||||
user_id = getattr(http_request.state, 'user_id', None)
|
||||
project = await verify_project_access(request_data.project_id, user_id, db)
|
||||
|
||||
try:
|
||||
# 获取现有大纲
|
||||
existing_result = await db.execute(
|
||||
select(Outline)
|
||||
.where(Outline.project_id == request_data.project_id)
|
||||
.order_by(Outline.order_index)
|
||||
)
|
||||
existing_outlines = existing_result.scalars().all()
|
||||
|
||||
if not existing_outlines:
|
||||
return OrganizationPredictionResponse(
|
||||
needs_new_organizations=False,
|
||||
reason="项目尚无大纲,无法预测组织需求",
|
||||
organization_count=0,
|
||||
predicted_organizations=[]
|
||||
)
|
||||
|
||||
# 获取现有角色
|
||||
characters_result = await db.execute(
|
||||
select(Character).where(Character.project_id == request_data.project_id)
|
||||
)
|
||||
characters = characters_result.scalars().all()
|
||||
|
||||
# 获取现有组织
|
||||
organizations_result = await db.execute(
|
||||
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 = ""
|
||||
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
|
||||
|
||||
auto_org_service = get_auto_organization_service(user_ai_service)
|
||||
|
||||
# 使用预测模式(不创建组织,仅分析)
|
||||
last_chapter_number = existing_outlines[-1].order_index
|
||||
auto_result = await auto_org_service.analyze_and_create_organizations(
|
||||
project_id=request_data.project_id,
|
||||
outline_content="", # 预测模式不需要大纲内容
|
||||
existing_characters=list(characters),
|
||||
existing_organizations=existing_organizations,
|
||||
db=db,
|
||||
user_id=user_id,
|
||||
enable_mcp=request_data.enable_mcp,
|
||||
all_chapters_brief=all_chapters_brief,
|
||||
start_chapter=last_chapter_number + 1,
|
||||
chapter_count=request_data.chapter_count,
|
||||
plot_stage=request_data.plot_stage,
|
||||
story_direction=request_data.story_direction,
|
||||
preview_only=True # 仅预测不创建
|
||||
)
|
||||
|
||||
# 构建预测响应
|
||||
predicted_organizations = []
|
||||
for org_data in auto_result.get("predicted_organizations", []):
|
||||
predicted_organizations.append(PredictedOrganization(
|
||||
name=org_data.get("name"),
|
||||
organization_description=org_data.get("organization_description", ""),
|
||||
organization_type=org_data.get("organization_type", "未知"),
|
||||
importance=org_data.get("importance", "medium"),
|
||||
appearance_chapter=org_data.get("appearance_chapter", last_chapter_number + 1),
|
||||
power_level=org_data.get("power_level", 50),
|
||||
plot_function=org_data.get("plot_function", ""),
|
||||
location=org_data.get("location"),
|
||||
motto=org_data.get("motto"),
|
||||
initial_members=org_data.get("initial_members", []),
|
||||
relationship_suggestions=org_data.get("relationship_suggestions", [])
|
||||
))
|
||||
|
||||
return OrganizationPredictionResponse(
|
||||
needs_new_organizations=auto_result.get("needs_new_organizations", False),
|
||||
reason=auto_result.get("reason", ""),
|
||||
organization_count=len(predicted_organizations),
|
||||
predicted_organizations=predicted_organizations
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"组织预测失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"组织预测失败: {str(e)}")
|
||||
|
||||
|
||||
|
||||
async def _generate_new_outline(
|
||||
request: OutlineGenerateRequest,
|
||||
@@ -839,14 +972,10 @@ async def _continue_outline(
|
||||
except Exception as e:
|
||||
logger.error(f"⚠️ 【确认模式】创建确认角色失败: {e}", exc_info=True)
|
||||
else:
|
||||
# 🔮 预测模式:仅预测角色,不自动创建,需要用户确认
|
||||
# 抛出特殊异常,在非SSE接口中会被捕获并返回449状态码
|
||||
# 在SSE接口中会被特殊处理
|
||||
# 根据 require_character_confirmation 决定处理方式
|
||||
try:
|
||||
from app.services.auto_character_service import get_auto_character_service
|
||||
|
||||
logger.info(f"🔮 【预测模式】在生成大纲前预测是否需要新角色")
|
||||
|
||||
# 构建已有章节概览
|
||||
all_chapters_brief_for_analysis = ""
|
||||
if len(existing_outlines) > 20:
|
||||
@@ -861,43 +990,83 @@ async def _continue_outline(
|
||||
for o in existing_outlines
|
||||
])
|
||||
|
||||
# 调用自动角色服务(✅ 设置 preview_only=True,仅预测不创建)
|
||||
auto_char_service = get_auto_character_service(user_ai_service)
|
||||
auto_result = await auto_char_service.analyze_and_create_characters(
|
||||
project_id=project.id,
|
||||
outline_content="", # 预测模式不需要大纲内容
|
||||
existing_characters=list(characters),
|
||||
db=db,
|
||||
user_id=user_id,
|
||||
enable_mcp=request.enable_mcp,
|
||||
all_chapters_brief=all_chapters_brief_for_analysis,
|
||||
start_chapter=last_chapter_number + 1,
|
||||
chapter_count=total_chapters_to_generate,
|
||||
plot_stage=request.plot_stage,
|
||||
story_direction=request.story_direction or "自然延续",
|
||||
preview_only=True # ✅ 关键修复:设置为True,仅预测不创建
|
||||
)
|
||||
|
||||
# 检查是否需要新角色
|
||||
if auto_result.get("needs_new_characters") and auto_result.get("predicted_characters"):
|
||||
predicted_count = len(auto_result["predicted_characters"])
|
||||
logger.warning(
|
||||
f"⚠️ 【预测模式】AI预测需要 {predicted_count} 个新角色,需要用户确认!"
|
||||
if request.require_character_confirmation:
|
||||
# 🔮 预测模式:仅预测角色,不自动创建,需要用户确认
|
||||
logger.info(f"🔮 【预测模式】在生成大纲前预测是否需要新角色(需用户确认)")
|
||||
|
||||
auto_result = await auto_char_service.analyze_and_create_characters(
|
||||
project_id=project.id,
|
||||
outline_content="", # 预测模式不需要大纲内容
|
||||
existing_characters=list(characters),
|
||||
db=db,
|
||||
user_id=user_id,
|
||||
enable_mcp=request.enable_mcp,
|
||||
all_chapters_brief=all_chapters_brief_for_analysis,
|
||||
start_chapter=last_chapter_number + 1,
|
||||
chapter_count=total_chapters_to_generate,
|
||||
plot_stage=request.plot_stage,
|
||||
story_direction=request.story_direction or "自然延续",
|
||||
preview_only=True # ✅ 仅预测不创建
|
||||
)
|
||||
|
||||
# 🚨 抛出特殊异常,包含预测的角色信息
|
||||
raise HTTPException(
|
||||
status_code=449, # 449 Retry With
|
||||
detail={
|
||||
"code": "CHARACTER_CONFIRMATION_REQUIRED",
|
||||
"message": "续写需要引入新角色,请先确认角色信息",
|
||||
"predicted_characters": auto_result["predicted_characters"],
|
||||
"reason": auto_result.get("reason", "剧情发展需要新角色"),
|
||||
"chapter_range": f"第{last_chapter_number + 1}-{last_chapter_number + total_chapters_to_generate}章"
|
||||
}
|
||||
)
|
||||
# 检查是否需要新角色
|
||||
if auto_result.get("needs_new_characters") and auto_result.get("predicted_characters"):
|
||||
predicted_count = len(auto_result["predicted_characters"])
|
||||
logger.warning(
|
||||
f"⚠️ 【预测模式】AI预测需要 {predicted_count} 个新角色,需要用户确认!"
|
||||
)
|
||||
|
||||
# 🚨 抛出特殊异常,包含预测的角色信息
|
||||
raise HTTPException(
|
||||
status_code=449, # 449 Retry With
|
||||
detail={
|
||||
"code": "CHARACTER_CONFIRMATION_REQUIRED",
|
||||
"message": "续写需要引入新角色,请先确认角色信息",
|
||||
"predicted_characters": auto_result["predicted_characters"],
|
||||
"reason": auto_result.get("reason", "剧情发展需要新角色"),
|
||||
"chapter_range": f"第{last_chapter_number + 1}-{last_chapter_number + total_chapters_to_generate}章"
|
||||
}
|
||||
)
|
||||
else:
|
||||
logger.info(f"✅ 【预测模式】AI判断无需引入新角色,继续生成大纲")
|
||||
else:
|
||||
logger.info(f"✅ 【预测模式】AI判断无需引入新角色,继续生成大纲")
|
||||
# 🚀 直接创建模式:预测后自动创建,无需用户确认
|
||||
logger.info(f"🚀 【直接创建模式】在生成大纲前预测并直接创建新角色(无需确认)")
|
||||
|
||||
auto_result = await auto_char_service.analyze_and_create_characters(
|
||||
project_id=project.id,
|
||||
outline_content="",
|
||||
existing_characters=list(characters),
|
||||
db=db,
|
||||
user_id=user_id,
|
||||
enable_mcp=request.enable_mcp,
|
||||
all_chapters_brief=all_chapters_brief_for_analysis,
|
||||
start_chapter=last_chapter_number + 1,
|
||||
chapter_count=total_chapters_to_generate,
|
||||
plot_stage=request.plot_stage,
|
||||
story_direction=request.story_direction or "自然延续",
|
||||
preview_only=False # ✅ 直接创建角色
|
||||
)
|
||||
|
||||
# 如果创建了新角色,更新角色列表
|
||||
if auto_result.get("new_characters"):
|
||||
new_count = len(auto_result["new_characters"])
|
||||
logger.info(f"✅ 【直接创建模式】自动创建了 {new_count} 个新角色")
|
||||
|
||||
# 提交角色到数据库
|
||||
await db.commit()
|
||||
|
||||
# 更新角色信息(供后续大纲生成使用)
|
||||
characters.extend(auto_result["new_characters"])
|
||||
characters_info = "\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
|
||||
])
|
||||
else:
|
||||
logger.info(f"✅ 【直接创建模式】AI判断无需引入新角色,继续生成大纲")
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
@@ -905,6 +1074,212 @@ async def _continue_outline(
|
||||
logger.error(f"⚠️ 【方案A】预测性角色引入失败: {e}", exc_info=True)
|
||||
# 不阻断大纲生成流程
|
||||
|
||||
# 🏛️ 【组织引入】在生成大纲前预测并创建组织
|
||||
if request.enable_auto_organizations:
|
||||
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()
|
||||
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:
|
||||
# 直接使用用户确认的组织列表创建组织
|
||||
try:
|
||||
from app.services.auto_organization_service import get_auto_organization_service
|
||||
|
||||
logger.info(f"🏛️ 【确认模式】用户提供了 {len(request.confirmed_organizations)} 个确认的组织,直接创建")
|
||||
|
||||
auto_org_service = get_auto_organization_service(user_ai_service)
|
||||
|
||||
for org_data in request.confirmed_organizations:
|
||||
try:
|
||||
# 生成组织详细信息
|
||||
organization_data = await auto_org_service._generate_organization_details(
|
||||
spec=org_data,
|
||||
project=project,
|
||||
existing_characters=list(characters),
|
||||
existing_organizations=existing_organizations,
|
||||
db=db,
|
||||
user_id=user_id,
|
||||
enable_mcp=request.enable_mcp
|
||||
)
|
||||
|
||||
# 创建组织记录
|
||||
org_character, organization = await auto_org_service._create_organization_record(
|
||||
project_id=project.id,
|
||||
organization_data=organization_data,
|
||||
db=db
|
||||
)
|
||||
|
||||
# 建立成员关系
|
||||
members_data = organization_data.get("initial_members", [])
|
||||
if members_data:
|
||||
await auto_org_service._create_member_relationships(
|
||||
organization=organization,
|
||||
member_specs=members_data,
|
||||
existing_characters=list(characters),
|
||||
project_id=project.id,
|
||||
db=db
|
||||
)
|
||||
|
||||
# 更新角色列表(组织也是Character)
|
||||
characters.append(org_character)
|
||||
existing_organizations.append({
|
||||
"id": organization.id,
|
||||
"name": org_character.name,
|
||||
"organization_type": org_character.organization_type,
|
||||
"organization_purpose": org_character.organization_purpose,
|
||||
"power_level": organization.power_level,
|
||||
"location": organization.location,
|
||||
"motto": organization.motto
|
||||
})
|
||||
logger.info(f"✅ 创建确认的组织: {org_character.name}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"创建确认的组织失败: {e}", exc_info=True)
|
||||
continue
|
||||
|
||||
# 提交组织到数据库
|
||||
await db.commit()
|
||||
|
||||
# 更新角色信息(供后续大纲生成使用)
|
||||
characters_info = "\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
|
||||
])
|
||||
|
||||
logger.info(f"✅ 【确认模式】成功创建 {len(request.confirmed_organizations)} 个用户确认的组织")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"⚠️ 【确认模式】创建确认组织失败: {e}", exc_info=True)
|
||||
else:
|
||||
# 根据 require_organization_confirmation 决定处理方式
|
||||
try:
|
||||
from app.services.auto_organization_service import get_auto_organization_service
|
||||
|
||||
# 构建已有章节概览
|
||||
all_chapters_brief_for_org_analysis = ""
|
||||
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)
|
||||
|
||||
if request.require_organization_confirmation:
|
||||
# 🔮 预测模式:仅预测组织,不自动创建,需要用户确认
|
||||
logger.info(f"🔮 【预测模式】在生成大纲前预测是否需要新组织(需用户确认)")
|
||||
|
||||
auto_result = await 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=request.enable_mcp,
|
||||
all_chapters_brief=all_chapters_brief_for_org_analysis,
|
||||
start_chapter=last_chapter_number + 1,
|
||||
chapter_count=total_chapters_to_generate,
|
||||
plot_stage=request.plot_stage,
|
||||
story_direction=request.story_direction or "自然延续",
|
||||
preview_only=True # ✅ 仅预测不创建
|
||||
)
|
||||
|
||||
# 检查是否需要新组织
|
||||
if auto_result.get("needs_new_organizations") and auto_result.get("predicted_organizations"):
|
||||
predicted_count = len(auto_result["predicted_organizations"])
|
||||
logger.warning(
|
||||
f"⚠️ 【预测模式】AI预测需要 {predicted_count} 个新组织,需要用户确认!"
|
||||
)
|
||||
|
||||
# 🚨 抛出特殊异常,包含预测的组织信息
|
||||
raise HTTPException(
|
||||
status_code=449, # 449 Retry With
|
||||
detail={
|
||||
"code": "ORGANIZATION_CONFIRMATION_REQUIRED",
|
||||
"message": "续写需要引入新组织,请先确认组织信息",
|
||||
"predicted_organizations": auto_result["predicted_organizations"],
|
||||
"reason": auto_result.get("reason", "剧情发展需要新组织"),
|
||||
"chapter_range": f"第{last_chapter_number + 1}-{last_chapter_number + total_chapters_to_generate}章"
|
||||
}
|
||||
)
|
||||
else:
|
||||
logger.info(f"✅ 【预测模式】AI判断无需引入新组织,继续生成大纲")
|
||||
else:
|
||||
# 🚀 直接创建模式:预测后自动创建,无需用户确认
|
||||
logger.info(f"🚀 【直接创建模式】在生成大纲前预测并直接创建新组织(无需确认)")
|
||||
|
||||
auto_result = await 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=request.enable_mcp,
|
||||
all_chapters_brief=all_chapters_brief_for_org_analysis,
|
||||
start_chapter=last_chapter_number + 1,
|
||||
chapter_count=total_chapters_to_generate,
|
||||
plot_stage=request.plot_stage,
|
||||
story_direction=request.story_direction or "自然延续",
|
||||
preview_only=False # ✅ 直接创建组织
|
||||
)
|
||||
|
||||
# 如果创建了新组织,更新角色列表
|
||||
if auto_result.get("new_organizations"):
|
||||
new_count = len(auto_result["new_organizations"])
|
||||
logger.info(f"✅ 【直接创建模式】自动创建了 {new_count} 个新组织")
|
||||
|
||||
# 提交组织到数据库
|
||||
await db.commit()
|
||||
|
||||
# 更新角色信息(供后续大纲生成使用)
|
||||
for org_item in auto_result["new_organizations"]:
|
||||
org_char = org_item.get("character")
|
||||
if org_char:
|
||||
characters.append(org_char)
|
||||
characters_info = "\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
|
||||
])
|
||||
else:
|
||||
logger.info(f"✅ 【直接创建模式】AI判断无需引入新组织,继续生成大纲")
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"⚠️ 【组织引入】预测性组织引入失败: {e}", exc_info=True)
|
||||
# 不阻断大纲生成流程
|
||||
|
||||
# 批量生成
|
||||
all_new_outlines = []
|
||||
current_start_chapter = last_chapter_number + 1
|
||||
@@ -1755,17 +2130,12 @@ async def continue_outline_generator(
|
||||
28
|
||||
)
|
||||
else:
|
||||
# 🔮 预测模式:仅预测角色,不自动创建,需要用户确认
|
||||
# 根据 require_character_confirmation 决定处理方式
|
||||
require_confirmation = data.get("require_character_confirmation", True)
|
||||
|
||||
try:
|
||||
yield await SSEResponse.send_progress(
|
||||
"🔮 【预测模式】检测是否需要新角色(需用户确认)...",
|
||||
27
|
||||
)
|
||||
|
||||
from app.services.auto_character_service import get_auto_character_service
|
||||
|
||||
logger.info(f"🔮 【预测模式】在生成大纲前预测是否需要新角色")
|
||||
|
||||
# 构建已有章节概览
|
||||
all_chapters_brief_for_analysis = ""
|
||||
if len(existing_outlines) > 20:
|
||||
@@ -1780,47 +2150,106 @@ async def continue_outline_generator(
|
||||
for o in existing_outlines
|
||||
])
|
||||
|
||||
# 调用自动角色服务(✅ 设置 preview_only=True)
|
||||
auto_char_service = get_auto_character_service(user_ai_service)
|
||||
auto_result = await auto_char_service.analyze_and_create_characters(
|
||||
project_id=project_id,
|
||||
outline_content="", # 预测模式不需要大纲内容
|
||||
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=True # ✅ 关键修复:仅预测不创建
|
||||
)
|
||||
|
||||
# 检查是否需要新角色
|
||||
if auto_result.get("needs_new_characters") and auto_result.get("predicted_characters"):
|
||||
predicted_count = len(auto_result["predicted_characters"])
|
||||
logger.warning(
|
||||
f"⚠️ 【预测模式】AI预测需要 {predicted_count} 个新角色,需要用户确认!"
|
||||
)
|
||||
|
||||
# 🚨 使用专用事件类型通知前端需要角色确认
|
||||
yield await SSEResponse.send_event(
|
||||
event="character_confirmation_required",
|
||||
data={
|
||||
"message": "续写需要引入新角色,请先确认角色信息",
|
||||
"predicted_characters": auto_result["predicted_characters"],
|
||||
"reason": auto_result.get("reason", "剧情发展需要新角色"),
|
||||
"chapter_range": f"第{last_chapter_number + 1}-{last_chapter_number + total_chapters_to_generate}章"
|
||||
}
|
||||
)
|
||||
return
|
||||
else:
|
||||
if require_confirmation:
|
||||
# 🔮 预测模式:仅预测角色,不自动创建,需要用户确认
|
||||
yield await SSEResponse.send_progress(
|
||||
"✅ 【预测模式】无需引入新角色,继续生成大纲",
|
||||
28
|
||||
"🔮 【预测模式】检测是否需要新角色(需用户确认)...",
|
||||
27
|
||||
)
|
||||
logger.info(f"✅ 【预测模式】AI判断无需引入新角色")
|
||||
|
||||
logger.info(f"🔮 【预测模式】在生成大纲前预测是否需要新角色")
|
||||
|
||||
auto_result = await auto_char_service.analyze_and_create_characters(
|
||||
project_id=project_id,
|
||||
outline_content="", # 预测模式不需要大纲内容
|
||||
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=True # ✅ 仅预测不创建
|
||||
)
|
||||
|
||||
# 检查是否需要新角色
|
||||
if auto_result.get("needs_new_characters") and auto_result.get("predicted_characters"):
|
||||
predicted_count = len(auto_result["predicted_characters"])
|
||||
logger.warning(
|
||||
f"⚠️ 【预测模式】AI预测需要 {predicted_count} 个新角色,需要用户确认!"
|
||||
)
|
||||
|
||||
# 🚨 使用专用事件类型通知前端需要角色确认
|
||||
yield await SSEResponse.send_event(
|
||||
event="character_confirmation_required",
|
||||
data={
|
||||
"message": "续写需要引入新角色,请先确认角色信息",
|
||||
"predicted_characters": auto_result["predicted_characters"],
|
||||
"reason": auto_result.get("reason", "剧情发展需要新角色"),
|
||||
"chapter_range": f"第{last_chapter_number + 1}-{last_chapter_number + total_chapters_to_generate}章"
|
||||
}
|
||||
)
|
||||
return
|
||||
else:
|
||||
yield await SSEResponse.send_progress(
|
||||
"✅ 【预测模式】无需引入新角色,继续生成大纲",
|
||||
28
|
||||
)
|
||||
logger.info(f"✅ 【预测模式】AI判断无需引入新角色")
|
||||
else:
|
||||
# 🚀 直接创建模式:预测后自动创建,无需用户确认
|
||||
yield await SSEResponse.send_progress(
|
||||
"🚀 【直接创建模式】检测并自动创建新角色(无需确认)...",
|
||||
27
|
||||
)
|
||||
|
||||
logger.info(f"🚀 【直接创建模式】在生成大纲前预测并直接创建新角色")
|
||||
|
||||
auto_result = await auto_char_service.analyze_and_create_characters(
|
||||
project_id=project_id,
|
||||
outline_content="",
|
||||
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 # ✅ 直接创建角色
|
||||
)
|
||||
|
||||
# 如果创建了新角色,更新角色列表
|
||||
if auto_result.get("new_characters"):
|
||||
new_count = len(auto_result["new_characters"])
|
||||
logger.info(f"✅ 【直接创建模式】自动创建了 {new_count} 个新角色")
|
||||
|
||||
yield await SSEResponse.send_progress(
|
||||
f"✅ 【直接创建模式】自动创建了 {new_count} 个新角色",
|
||||
28
|
||||
)
|
||||
|
||||
# 提交角色到数据库
|
||||
await db.commit()
|
||||
|
||||
# 更新角色信息(供后续大纲生成使用)
|
||||
characters.extend(auto_result["new_characters"])
|
||||
characters_info = "\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
|
||||
])
|
||||
else:
|
||||
yield await SSEResponse.send_progress(
|
||||
"✅ 【直接创建模式】无需引入新角色,继续生成大纲",
|
||||
28
|
||||
)
|
||||
logger.info(f"✅ 【直接创建模式】AI判断无需引入新角色")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"⚠️ 【方案A】预测性角色引入失败: {e}", exc_info=True)
|
||||
@@ -1830,6 +2259,250 @@ async def continue_outline_generator(
|
||||
)
|
||||
# 不阻断大纲生成流程
|
||||
|
||||
# 🏛️ 【组织引入】在生成大纲前预测并创建组织
|
||||
enable_auto_organizations = data.get("enable_auto_organizations", True)
|
||||
confirmed_organizations = data.get("confirmed_organizations")
|
||||
|
||||
if enable_auto_organizations:
|
||||
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()
|
||||
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:
|
||||
# 直接使用用户确认的组织列表创建组织
|
||||
try:
|
||||
yield await SSEResponse.send_progress(
|
||||
f"🏛️ 【确认模式】创建 {len(confirmed_organizations)} 个用户确认的组织...",
|
||||
29
|
||||
)
|
||||
|
||||
from app.services.auto_organization_service import get_auto_organization_service
|
||||
|
||||
logger.info(f"🏛️ 【确认模式】用户提供了 {len(confirmed_organizations)} 个确认的组织,直接创建")
|
||||
|
||||
auto_org_service = get_auto_organization_service(user_ai_service)
|
||||
|
||||
created_org_count = 0
|
||||
for org_data in confirmed_organizations:
|
||||
try:
|
||||
# 生成组织详细信息
|
||||
organization_data = await auto_org_service._generate_organization_details(
|
||||
spec=org_data,
|
||||
project=project,
|
||||
existing_characters=list(characters),
|
||||
existing_organizations=existing_organizations,
|
||||
db=db,
|
||||
user_id=user_id,
|
||||
enable_mcp=data.get("enable_mcp", True)
|
||||
)
|
||||
|
||||
# 创建组织记录
|
||||
org_character, organization = await auto_org_service._create_organization_record(
|
||||
project_id=project_id,
|
||||
organization_data=organization_data,
|
||||
db=db
|
||||
)
|
||||
|
||||
# 建立成员关系
|
||||
members_data = organization_data.get("initial_members", [])
|
||||
if members_data:
|
||||
await auto_org_service._create_member_relationships(
|
||||
organization=organization,
|
||||
member_specs=members_data,
|
||||
existing_characters=list(characters),
|
||||
project_id=project_id,
|
||||
db=db
|
||||
)
|
||||
|
||||
# 更新角色列表(组织也是Character)
|
||||
characters.append(org_character)
|
||||
existing_organizations.append({
|
||||
"id": organization.id,
|
||||
"name": org_character.name,
|
||||
"organization_type": org_character.organization_type,
|
||||
"organization_purpose": org_character.organization_purpose,
|
||||
"power_level": organization.power_level,
|
||||
"location": organization.location,
|
||||
"motto": organization.motto
|
||||
})
|
||||
created_org_count += 1
|
||||
logger.info(f"✅ 创建确认的组织: {org_character.name}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"创建确认的组织失败: {e}", exc_info=True)
|
||||
continue
|
||||
|
||||
# 提交组织到数据库
|
||||
await db.commit()
|
||||
|
||||
yield await SSEResponse.send_progress(
|
||||
f"✅ 【确认模式】成功创建 {created_org_count} 个组织",
|
||||
30
|
||||
)
|
||||
logger.info(f"✅ 【确认模式】成功创建 {created_org_count} 个用户确认的组织")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"⚠️ 【确认模式】创建确认组织失败: {e}", exc_info=True)
|
||||
yield await SSEResponse.send_progress(
|
||||
f"⚠️ 组织创建失败,继续生成大纲",
|
||||
30
|
||||
)
|
||||
else:
|
||||
# 根据 require_organization_confirmation 决定处理方式
|
||||
require_org_confirmation = data.get("require_organization_confirmation", True)
|
||||
|
||||
try:
|
||||
from app.services.auto_organization_service import get_auto_organization_service
|
||||
|
||||
# 构建已有章节概览
|
||||
all_chapters_brief_for_org_analysis = ""
|
||||
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)
|
||||
|
||||
if require_org_confirmation:
|
||||
# 🔮 预测模式:仅预测组织,不自动创建,需要用户确认
|
||||
yield await SSEResponse.send_progress(
|
||||
"🔮 【预测模式】检测是否需要新组织(需用户确认)...",
|
||||
29
|
||||
)
|
||||
|
||||
logger.info(f"🔮 【预测模式】在生成大纲前预测是否需要新组织")
|
||||
|
||||
auto_result = await 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=True # ✅ 仅预测不创建
|
||||
)
|
||||
|
||||
# 检查是否需要新组织
|
||||
if auto_result.get("needs_new_organizations") and auto_result.get("predicted_organizations"):
|
||||
predicted_count = len(auto_result["predicted_organizations"])
|
||||
logger.warning(
|
||||
f"⚠️ 【预测模式】AI预测需要 {predicted_count} 个新组织,需要用户确认!"
|
||||
)
|
||||
|
||||
# 🚨 使用专用事件类型通知前端需要组织确认
|
||||
yield await SSEResponse.send_event(
|
||||
event="organization_confirmation_required",
|
||||
data={
|
||||
"message": "续写需要引入新组织,请先确认组织信息",
|
||||
"predicted_organizations": auto_result["predicted_organizations"],
|
||||
"reason": auto_result.get("reason", "剧情发展需要新组织"),
|
||||
"chapter_range": f"第{last_chapter_number + 1}-{last_chapter_number + total_chapters_to_generate}章"
|
||||
}
|
||||
)
|
||||
return
|
||||
else:
|
||||
yield await SSEResponse.send_progress(
|
||||
"✅ 【预测模式】无需引入新组织,继续生成大纲",
|
||||
30
|
||||
)
|
||||
logger.info(f"✅ 【预测模式】AI判断无需引入新组织")
|
||||
else:
|
||||
# 🚀 直接创建模式:预测后自动创建,无需用户确认
|
||||
yield await SSEResponse.send_progress(
|
||||
"🚀 【直接创建模式】检测并自动创建新组织(无需确认)...",
|
||||
29
|
||||
)
|
||||
|
||||
logger.info(f"🚀 【直接创建模式】在生成大纲前预测并直接创建新组织")
|
||||
|
||||
auto_result = await 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 # ✅ 直接创建组织
|
||||
)
|
||||
|
||||
# 如果创建了新组织,更新角色列表
|
||||
if auto_result.get("new_organizations"):
|
||||
new_count = len(auto_result["new_organizations"])
|
||||
logger.info(f"✅ 【直接创建模式】自动创建了 {new_count} 个新组织")
|
||||
|
||||
yield await SSEResponse.send_progress(
|
||||
f"✅ 【直接创建模式】自动创建了 {new_count} 个新组织",
|
||||
30
|
||||
)
|
||||
|
||||
# 提交组织到数据库
|
||||
await db.commit()
|
||||
|
||||
# 更新角色信息(供后续大纲生成使用)
|
||||
for org_item in auto_result["new_organizations"]:
|
||||
org_char = org_item.get("character")
|
||||
if org_char:
|
||||
characters.append(org_char)
|
||||
characters_info = "\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
|
||||
])
|
||||
else:
|
||||
yield await SSEResponse.send_progress(
|
||||
"✅ 【直接创建模式】无需引入新组织,继续生成大纲",
|
||||
30
|
||||
)
|
||||
logger.info(f"✅ 【直接创建模式】AI判断无需引入新组织")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"⚠️ 【组织引入】预测性组织引入失败: {e}", exc_info=True)
|
||||
yield await SSEResponse.send_progress(
|
||||
f"⚠️ 组织预测失败,继续生成大纲",
|
||||
30
|
||||
)
|
||||
# 不阻断大纲生成流程
|
||||
|
||||
# 批量生成
|
||||
all_new_outlines = []
|
||||
current_start_chapter = last_chapter_number + 1
|
||||
|
||||
@@ -35,6 +35,40 @@ class CharacterPredictionResponse(BaseModel):
|
||||
predicted_characters: List[PredictedCharacter]
|
||||
|
||||
|
||||
# 组织预测相关Schema
|
||||
class OrganizationPredictionRequest(BaseModel):
|
||||
"""组织预测请求"""
|
||||
project_id: str
|
||||
start_chapter: int
|
||||
chapter_count: int = 3
|
||||
plot_stage: str = "development"
|
||||
story_direction: Optional[str] = "自然延续"
|
||||
enable_mcp: bool = True
|
||||
|
||||
|
||||
class PredictedOrganization(BaseModel):
|
||||
"""预测的组织信息"""
|
||||
name: Optional[str] = None
|
||||
organization_description: str
|
||||
organization_type: str
|
||||
importance: str
|
||||
appearance_chapter: int
|
||||
power_level: int = 50
|
||||
plot_function: str
|
||||
location: Optional[str] = None
|
||||
motto: Optional[str] = None
|
||||
initial_members: List[Dict[str, Any]] = []
|
||||
relationship_suggestions: List[Dict[str, str]] = []
|
||||
|
||||
|
||||
class OrganizationPredictionResponse(BaseModel):
|
||||
"""组织预测响应"""
|
||||
needs_new_organizations: bool
|
||||
reason: str
|
||||
organization_count: int
|
||||
predicted_organizations: List[PredictedOrganization]
|
||||
|
||||
|
||||
class OutlineBase(BaseModel):
|
||||
"""大纲基础模型"""
|
||||
title: str = Field(..., description="章节标题")
|
||||
@@ -93,9 +127,17 @@ class OutlineGenerateRequest(BaseModel):
|
||||
plot_stage: str = Field("development", description="情节阶段: development(发展), climax(高潮), ending(结局)")
|
||||
keep_existing: bool = Field(False, description="是否保留现有大纲(续写时)")
|
||||
enable_mcp: bool = Field(True, description="是否启用MCP工具增强(搜索情节设计参考)")
|
||||
|
||||
# 自动角色引入相关参数
|
||||
enable_auto_characters: bool = Field(True, description="是否启用自动角色引入(根据剧情推进自动创建新角色)")
|
||||
require_character_confirmation: bool = Field(True, description="是否需要用户确认新角色(False则AI预测的角色直接创建)")
|
||||
confirmed_characters: Optional[List[Dict[str, Any]]] = Field(None, description="用户确认的角色列表(跳过预测直接创建)")
|
||||
|
||||
# 自动组织引入相关参数
|
||||
enable_auto_organizations: bool = Field(True, description="是否启用自动组织引入(根据剧情推进自动创建新组织)")
|
||||
require_organization_confirmation: bool = Field(True, description="是否需要用户确认新组织(False则AI预测的组织直接创建)")
|
||||
confirmed_organizations: Optional[List[Dict[str, Any]]] = Field(None, description="用户确认的组织列表(跳过预测直接创建)")
|
||||
|
||||
|
||||
class ChapterOutlineGenerateRequest(BaseModel):
|
||||
"""为单个章节生成大纲的请求模型"""
|
||||
|
||||
@@ -0,0 +1,504 @@
|
||||
"""自动组织引入服务 - 在续写大纲时根据剧情推进自动引入新组织"""
|
||||
from typing import List, Dict, Any, Optional
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
import json
|
||||
|
||||
from app.models.character import Character
|
||||
from app.models.relationship import Organization, OrganizationMember
|
||||
from app.models.project import Project
|
||||
from app.services.ai_service import AIService
|
||||
from app.services.prompt_service import PromptService
|
||||
from app.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class AutoOrganizationService:
|
||||
"""自动组织引入服务"""
|
||||
|
||||
def __init__(self, ai_service: AIService):
|
||||
self.ai_service = ai_service
|
||||
|
||||
async def analyze_and_create_organizations(
|
||||
self,
|
||||
project_id: str,
|
||||
outline_content: str,
|
||||
existing_characters: List[Character],
|
||||
existing_organizations: List[Dict[str, Any]],
|
||||
db: AsyncSession,
|
||||
user_id: str = None,
|
||||
enable_mcp: bool = True,
|
||||
all_chapters_brief: str = "",
|
||||
start_chapter: int = 1,
|
||||
chapter_count: int = 3,
|
||||
plot_stage: str = "发展",
|
||||
story_direction: str = "继续推进主线剧情",
|
||||
preview_only: bool = False
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
预测性分析并创建需要的新组织
|
||||
|
||||
Args:
|
||||
project_id: 项目ID
|
||||
outline_content: 当前批次大纲内容(用于向后兼容,实际不使用)
|
||||
existing_characters: 现有角色列表
|
||||
existing_organizations: 现有组织列表
|
||||
db: 数据库会话
|
||||
user_id: 用户ID(用于MCP和自定义提示词)
|
||||
enable_mcp: 是否启用MCP增强
|
||||
all_chapters_brief: 已有章节概览
|
||||
start_chapter: 起始章节号
|
||||
chapter_count: 续写章节数
|
||||
plot_stage: 剧情阶段
|
||||
story_direction: 故事发展方向
|
||||
preview_only: 仅预测不创建(用于组织确认机制)
|
||||
|
||||
Returns:
|
||||
{
|
||||
"new_organizations": [组织对象列表], # preview_only=True时为空
|
||||
"members_created": [成员关系列表], # preview_only=True时为空
|
||||
"organization_count": 新增组织数量,
|
||||
"analysis_result": AI分析结果,
|
||||
"predicted_organizations": [预测的组织数据] # 仅preview_only=True时返回
|
||||
"needs_new_organizations": bool,
|
||||
"reason": str
|
||||
}
|
||||
"""
|
||||
logger.info(f"🏛️ 【组织引入】预测性分析:检测是否需要引入新组织...")
|
||||
logger.info(f" - 项目ID: {project_id}")
|
||||
logger.info(f" - 续写计划: 第{start_chapter}章起,共{chapter_count}章")
|
||||
logger.info(f" - 剧情阶段: {plot_stage}")
|
||||
logger.info(f" - 发展方向: {story_direction}")
|
||||
logger.info(f" - 现有角色数: {len(existing_characters)}")
|
||||
logger.info(f" - 现有组织数: {len(existing_organizations)}")
|
||||
|
||||
# 1. 获取项目信息
|
||||
project_result = await db.execute(
|
||||
select(Project).where(Project.id == project_id)
|
||||
)
|
||||
project = project_result.scalar_one_or_none()
|
||||
if not project:
|
||||
raise ValueError("项目不存在")
|
||||
|
||||
# 2. 构建现有组织信息摘要
|
||||
existing_orgs_summary = self._build_organization_summary(existing_organizations)
|
||||
existing_chars_summary = self._build_character_summary(existing_characters)
|
||||
|
||||
# 3. AI预测性分析是否需要新组织
|
||||
analysis_result = await self._analyze_organization_needs(
|
||||
project=project,
|
||||
outline_content=outline_content,
|
||||
existing_orgs_summary=existing_orgs_summary,
|
||||
existing_chars_summary=existing_chars_summary,
|
||||
db=db,
|
||||
user_id=user_id,
|
||||
enable_mcp=enable_mcp,
|
||||
all_chapters_brief=all_chapters_brief,
|
||||
start_chapter=start_chapter,
|
||||
chapter_count=chapter_count,
|
||||
plot_stage=plot_stage,
|
||||
story_direction=story_direction
|
||||
)
|
||||
|
||||
# 4. 判断是否需要创建组织
|
||||
if not analysis_result or not analysis_result.get("needs_new_organizations"):
|
||||
logger.info("✅ AI判断:当前剧情不需要引入新组织")
|
||||
return {
|
||||
"new_organizations": [],
|
||||
"members_created": [],
|
||||
"organization_count": 0,
|
||||
"analysis_result": analysis_result,
|
||||
"predicted_organizations": [],
|
||||
"needs_new_organizations": False,
|
||||
"reason": analysis_result.get("reason", "当前剧情不需要新组织")
|
||||
}
|
||||
|
||||
# 5. 如果是预览模式,仅返回预测结果,不创建组织
|
||||
if preview_only:
|
||||
organization_specs = analysis_result.get("organization_specifications", [])
|
||||
logger.info(f"🔮 预览模式:预测到 {len(organization_specs)} 个组织,不创建数据库记录")
|
||||
return {
|
||||
"new_organizations": [],
|
||||
"members_created": [],
|
||||
"organization_count": 0,
|
||||
"analysis_result": analysis_result,
|
||||
"predicted_organizations": organization_specs,
|
||||
"needs_new_organizations": True,
|
||||
"reason": analysis_result.get("reason", "预测需要新组织")
|
||||
}
|
||||
|
||||
# 6. 批量生成新组织(非预览模式)
|
||||
new_organizations = []
|
||||
members_created = []
|
||||
|
||||
organization_specs = analysis_result.get("organization_specifications", [])
|
||||
logger.info(f"🎯 AI建议引入 {len(organization_specs)} 个新组织")
|
||||
|
||||
for idx, spec in enumerate(organization_specs):
|
||||
try:
|
||||
spec_name = spec.get('name', spec.get('organization_description', '未命名'))
|
||||
logger.info(f" [{idx+1}/{len(organization_specs)}] 生成组织规格: {spec_name}")
|
||||
logger.debug(f" 组织规格内容: {json.dumps(spec, ensure_ascii=False)}")
|
||||
|
||||
# 生成组织详细信息
|
||||
organization_data = await self._generate_organization_details(
|
||||
spec=spec,
|
||||
project=project,
|
||||
existing_characters=existing_characters,
|
||||
existing_organizations=existing_organizations,
|
||||
db=db,
|
||||
user_id=user_id,
|
||||
enable_mcp=enable_mcp
|
||||
)
|
||||
|
||||
logger.debug(f" AI生成的组织数据: {json.dumps(organization_data, ensure_ascii=False)[:200]}")
|
||||
|
||||
# 创建组织记录(先创建Character记录,再创建Organization记录)
|
||||
character, organization = await self._create_organization_record(
|
||||
project_id=project_id,
|
||||
organization_data=organization_data,
|
||||
db=db
|
||||
)
|
||||
|
||||
new_organizations.append({
|
||||
"character": character,
|
||||
"organization": organization
|
||||
})
|
||||
logger.info(f" ✅ 创建新组织: {character.name}, ID: {organization.id}")
|
||||
|
||||
# 建立成员关系
|
||||
members_data = organization_data.get("initial_members", [])
|
||||
if members_data:
|
||||
logger.info(f" 🔗 开始创建 {len(members_data)} 个成员关系...")
|
||||
members = await self._create_member_relationships(
|
||||
organization=organization,
|
||||
member_specs=members_data,
|
||||
existing_characters=existing_characters,
|
||||
project_id=project_id,
|
||||
db=db
|
||||
)
|
||||
members_created.extend(members)
|
||||
logger.info(f" ✅ 实际创建了 {len(members)} 个成员关系记录")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f" ❌ 创建组织失败: {e}", exc_info=True)
|
||||
continue
|
||||
|
||||
# 7. 提交事务(注意:这里只flush,让调用方commit)
|
||||
await db.flush()
|
||||
|
||||
logger.info(f"🎉 自动组织引入完成: 新增{len(new_organizations)}个组织, {len(members_created)}个成员关系")
|
||||
|
||||
return {
|
||||
"new_organizations": new_organizations,
|
||||
"members_created": members_created,
|
||||
"organization_count": len(new_organizations),
|
||||
"analysis_result": analysis_result,
|
||||
"predicted_organizations": [],
|
||||
"needs_new_organizations": True,
|
||||
"reason": analysis_result.get("reason", "")
|
||||
}
|
||||
|
||||
def _build_organization_summary(self, organizations: List[Dict[str, Any]]) -> str:
|
||||
"""构建现有组织摘要"""
|
||||
if not organizations:
|
||||
return "暂无组织"
|
||||
|
||||
summary = []
|
||||
for org in organizations:
|
||||
org_name = org.get("name", "未知")
|
||||
org_type = org.get("organization_type", "未知类型")
|
||||
power_level = org.get("power_level", 50)
|
||||
purpose = (org.get("organization_purpose") or "")[:50]
|
||||
summary.append(f"- {org_name} ({org_type}, 势力等级:{power_level}): {purpose}")
|
||||
|
||||
return "\n".join(summary[:15]) # 最多显示15个
|
||||
|
||||
def _build_character_summary(self, characters: List[Character]) -> str:
|
||||
"""构建现有角色摘要"""
|
||||
if not characters:
|
||||
return "暂无角色"
|
||||
|
||||
summary = []
|
||||
for char in characters:
|
||||
if not char.is_organization: # 只统计非组织角色
|
||||
char_role = char.role_type or "未知"
|
||||
personality = (char.personality or "")[:30]
|
||||
summary.append(f"- {char.name} ({char_role}): {personality}")
|
||||
|
||||
return "\n".join(summary[:20]) # 最多显示20个
|
||||
|
||||
async def _analyze_organization_needs(
|
||||
self,
|
||||
project: Project,
|
||||
outline_content: str,
|
||||
existing_orgs_summary: str,
|
||||
existing_chars_summary: str,
|
||||
db: AsyncSession,
|
||||
user_id: str,
|
||||
enable_mcp: bool,
|
||||
all_chapters_brief: str = "",
|
||||
start_chapter: int = 1,
|
||||
chapter_count: int = 3,
|
||||
plot_stage: str = "发展",
|
||||
story_direction: str = "继续推进主线剧情"
|
||||
) -> Dict[str, Any]:
|
||||
"""AI预测性分析是否需要新组织"""
|
||||
|
||||
# 构建分析提示词
|
||||
template = await PromptService.get_template(
|
||||
"AUTO_ORGANIZATION_ANALYSIS",
|
||||
user_id,
|
||||
db
|
||||
)
|
||||
|
||||
# 使用新的预测性分析参数
|
||||
prompt = PromptService.format_prompt(
|
||||
template,
|
||||
title=project.title,
|
||||
theme=project.theme or "未设定",
|
||||
genre=project.genre or "未设定",
|
||||
time_period=project.world_time_period or "未设定",
|
||||
location=project.world_location or "未设定",
|
||||
atmosphere=project.world_atmosphere or "未设定",
|
||||
existing_organizations=existing_orgs_summary,
|
||||
existing_characters=existing_chars_summary,
|
||||
all_chapters_brief=all_chapters_brief,
|
||||
start_chapter=start_chapter,
|
||||
chapter_count=chapter_count,
|
||||
plot_stage=plot_stage,
|
||||
story_direction=story_direction
|
||||
)
|
||||
|
||||
try:
|
||||
# 调用AI分析(使用统一的JSON调用方法)
|
||||
if enable_mcp and user_id:
|
||||
result = await self.ai_service.generate_text_with_mcp(
|
||||
prompt=prompt,
|
||||
user_id=user_id,
|
||||
db_session=db,
|
||||
enable_mcp=True,
|
||||
max_tool_rounds=2
|
||||
)
|
||||
content = result.get("content", "")
|
||||
# 使用统一的JSON清洗方法
|
||||
cleaned = self.ai_service._clean_json_response(content)
|
||||
analysis = json.loads(cleaned)
|
||||
else:
|
||||
# 非MCP调用:使用带自动重试的JSON调用
|
||||
analysis = await self.ai_service.call_with_json_retry(
|
||||
prompt=prompt,
|
||||
max_retries=3
|
||||
)
|
||||
|
||||
logger.info(f" ✅ AI分析完成: needs_new_organizations={analysis.get('needs_new_organizations')}")
|
||||
return analysis
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f" ❌ 组织需求分析JSON解析失败: {e}")
|
||||
return {"needs_new_organizations": False}
|
||||
except Exception as e:
|
||||
logger.error(f" ❌ 组织需求分析失败: {e}")
|
||||
return {"needs_new_organizations": False}
|
||||
|
||||
async def _generate_organization_details(
|
||||
self,
|
||||
spec: Dict[str, Any],
|
||||
project: Project,
|
||||
existing_characters: List[Character],
|
||||
existing_organizations: List[Dict[str, Any]],
|
||||
db: AsyncSession,
|
||||
user_id: str,
|
||||
enable_mcp: bool
|
||||
) -> Dict[str, Any]:
|
||||
"""生成组织详细信息"""
|
||||
|
||||
# 构建组织生成提示词
|
||||
template = await PromptService.get_template(
|
||||
"AUTO_ORGANIZATION_GENERATION",
|
||||
user_id,
|
||||
db
|
||||
)
|
||||
|
||||
existing_orgs_summary = self._build_organization_summary(existing_organizations)
|
||||
existing_chars_summary = self._build_character_summary(existing_characters)
|
||||
|
||||
prompt = PromptService.format_prompt(
|
||||
template,
|
||||
title=project.title,
|
||||
genre=project.genre or "未设定",
|
||||
theme=project.theme or "未设定",
|
||||
time_period=project.world_time_period or "未设定",
|
||||
location=project.world_location or "未设定",
|
||||
atmosphere=project.world_atmosphere or "未设定",
|
||||
rules=project.world_rules or "未设定",
|
||||
existing_organizations=existing_orgs_summary,
|
||||
existing_characters=existing_chars_summary,
|
||||
plot_context="根据剧情需要引入的新组织",
|
||||
organization_specification=json.dumps(spec, ensure_ascii=False, indent=2),
|
||||
mcp_references="" # 暂时不使用MCP增强
|
||||
)
|
||||
|
||||
# 调用AI生成(使用统一的JSON调用方法)
|
||||
try:
|
||||
if enable_mcp and user_id:
|
||||
result = await self.ai_service.generate_text_with_mcp(
|
||||
prompt=prompt,
|
||||
user_id=user_id,
|
||||
db_session=db,
|
||||
enable_mcp=True,
|
||||
max_tool_rounds=2
|
||||
)
|
||||
content = result.get("content", "")
|
||||
# 使用统一的JSON清洗方法
|
||||
cleaned = self.ai_service._clean_json_response(content)
|
||||
organization_data = json.loads(cleaned)
|
||||
else:
|
||||
# 非MCP调用:使用带自动重试的JSON调用
|
||||
organization_data = await self.ai_service.call_with_json_retry(
|
||||
prompt=prompt,
|
||||
max_retries=3
|
||||
)
|
||||
|
||||
org_name = organization_data.get('name', '未知')
|
||||
logger.info(f" ✅ 组织详情生成成功: {org_name}")
|
||||
logger.debug(f" 组织数据字段: {list(organization_data.keys())}")
|
||||
|
||||
# 确保关键字段存在
|
||||
if 'name' not in organization_data or not organization_data['name']:
|
||||
logger.warning(f" ⚠️ AI返回的组织数据缺少name字段,使用规格中的信息")
|
||||
organization_data['name'] = spec.get('name', f"新组织{spec.get('organization_description', '')[:10]}")
|
||||
|
||||
return organization_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f" ❌ 生成组织详情失败: {e}")
|
||||
raise
|
||||
|
||||
async def _create_organization_record(
|
||||
self,
|
||||
project_id: str,
|
||||
organization_data: Dict[str, Any],
|
||||
db: AsyncSession
|
||||
) -> tuple:
|
||||
"""创建组织数据库记录(包括Character和Organization)"""
|
||||
|
||||
# 首先创建Character记录(is_organization=True)
|
||||
character = Character(
|
||||
project_id=project_id,
|
||||
name=organization_data.get("name", "未命名组织"),
|
||||
is_organization=True,
|
||||
role_type=organization_data.get("role_type", "supporting"),
|
||||
personality=organization_data.get("personality", ""), # 组织特性
|
||||
background=organization_data.get("background", ""), # 组织背景
|
||||
appearance=organization_data.get("appearance", ""), # 外在表现
|
||||
organization_type=organization_data.get("organization_type"),
|
||||
organization_purpose=organization_data.get("organization_purpose"),
|
||||
traits=json.dumps(organization_data.get("traits", []), ensure_ascii=False) if organization_data.get("traits") else None
|
||||
)
|
||||
|
||||
db.add(character)
|
||||
await db.flush()
|
||||
|
||||
# 然后创建Organization记录
|
||||
organization = Organization(
|
||||
character_id=character.id,
|
||||
project_id=project_id,
|
||||
power_level=organization_data.get("power_level", 50),
|
||||
member_count=0,
|
||||
location=organization_data.get("location"),
|
||||
motto=organization_data.get("motto"),
|
||||
color=organization_data.get("color")
|
||||
)
|
||||
|
||||
db.add(organization)
|
||||
await db.flush()
|
||||
|
||||
logger.info(f" ✅ 创建组织记录: {character.name}, Organization ID: {organization.id}")
|
||||
|
||||
return character, organization
|
||||
|
||||
async def _create_member_relationships(
|
||||
self,
|
||||
organization: Organization,
|
||||
member_specs: List[Dict[str, Any]],
|
||||
existing_characters: List[Character],
|
||||
project_id: str,
|
||||
db: AsyncSession
|
||||
) -> List[OrganizationMember]:
|
||||
"""创建组织成员关系"""
|
||||
|
||||
if not member_specs:
|
||||
return []
|
||||
|
||||
members = []
|
||||
|
||||
for member_spec in member_specs:
|
||||
try:
|
||||
character_name = member_spec.get("character_name")
|
||||
if not character_name:
|
||||
continue
|
||||
|
||||
# 查找目标角色
|
||||
target_char = next(
|
||||
(c for c in existing_characters if c.name == character_name and not c.is_organization),
|
||||
None
|
||||
)
|
||||
|
||||
if not target_char:
|
||||
logger.warning(f" ⚠️ 目标角色不存在: {character_name}")
|
||||
continue
|
||||
|
||||
# 检查成员关系是否已存在
|
||||
existing_member = await db.execute(
|
||||
select(OrganizationMember).where(
|
||||
OrganizationMember.organization_id == organization.id,
|
||||
OrganizationMember.character_id == target_char.id
|
||||
)
|
||||
)
|
||||
if existing_member.scalar_one_or_none():
|
||||
logger.debug(f" ℹ️ 成员关系已存在: {character_name} -> {organization.id}")
|
||||
continue
|
||||
|
||||
# 创建成员关系
|
||||
member = OrganizationMember(
|
||||
organization_id=organization.id,
|
||||
character_id=target_char.id,
|
||||
position=member_spec.get("position", "成员"),
|
||||
rank=member_spec.get("rank", 0),
|
||||
loyalty=member_spec.get("loyalty", 50),
|
||||
status=member_spec.get("status", "active"),
|
||||
joined_at=member_spec.get("joined_at"),
|
||||
source="auto" # 标记为自动生成
|
||||
)
|
||||
|
||||
db.add(member)
|
||||
members.append(member)
|
||||
|
||||
logger.info(
|
||||
f" ✅ 创建成员关系: {character_name} -> {organization.id} "
|
||||
f"({member_spec.get('position', '成员')})"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f" ❌ 创建成员关系失败: {e}")
|
||||
continue
|
||||
|
||||
# 更新组织成员数量
|
||||
if members:
|
||||
organization.member_count = (organization.member_count or 0) + len(members)
|
||||
|
||||
return members
|
||||
|
||||
|
||||
# 全局实例缓存
|
||||
_auto_organization_service_instance: Optional[AutoOrganizationService] = None
|
||||
|
||||
|
||||
def get_auto_organization_service(ai_service: AIService) -> AutoOrganizationService:
|
||||
"""获取自动组织服务实例(单例模式)"""
|
||||
global _auto_organization_service_instance
|
||||
if _auto_organization_service_instance is None:
|
||||
_auto_organization_service_instance = AutoOrganizationService(ai_service)
|
||||
return _auto_organization_service_instance
|
||||
@@ -1679,6 +1679,248 @@ class PromptService:
|
||||
❌ 在描述中使用特殊符号
|
||||
❌ 引用不存在的角色或组织
|
||||
❌ 使用职业ID而非职业名称
|
||||
</constraints>"""
|
||||
|
||||
# 自动组织引入 - 预测性分析提示词(RTCO框架)
|
||||
AUTO_ORGANIZATION_ANALYSIS = """<system>
|
||||
你是专业的小说世界构建顾问,擅长预测剧情发展对组织/势力的需求。
|
||||
</system>
|
||||
|
||||
<task>
|
||||
【分析任务】
|
||||
预测在接下来的{chapter_count}章续写中,根据剧情发展方向和阶段,是否需要引入新的组织或势力。
|
||||
|
||||
【重要说明】
|
||||
这是预测性分析,而非基于已生成内容的事后分析。
|
||||
组织包括:帮派、门派、公司、政府机构、神秘组织、家族等。
|
||||
</task>
|
||||
|
||||
<project priority="P1">
|
||||
【项目信息】
|
||||
书名:{title}
|
||||
类型:{genre}
|
||||
主题:{theme}
|
||||
|
||||
【世界观】
|
||||
时间背景:{time_period}
|
||||
地理位置:{location}
|
||||
氛围基调:{atmosphere}
|
||||
</project>
|
||||
|
||||
<context priority="P0">
|
||||
【已有组织】
|
||||
{existing_organizations}
|
||||
|
||||
【已有角色】
|
||||
{existing_characters}
|
||||
|
||||
【已有章节概览】
|
||||
{all_chapters_brief}
|
||||
|
||||
【续写计划】
|
||||
- 起始章节:第{start_chapter}章
|
||||
- 续写数量:{chapter_count}章
|
||||
- 剧情阶段:{plot_stage}
|
||||
- 发展方向:{story_direction}
|
||||
</context>
|
||||
|
||||
<analysis_framework priority="P0">
|
||||
【预测分析维度】
|
||||
|
||||
**1. 世界观扩展需求**
|
||||
根据发展方向,是否需要新的势力或组织来丰富世界观?
|
||||
|
||||
**2. 冲突升级需求**
|
||||
剧情是否需要新的对立势力、竞争组织或神秘集团?
|
||||
|
||||
**3. 角色归属需求**
|
||||
现有角色是否需要加入或对抗某个新组织?
|
||||
|
||||
**4. 剧情推动需求**
|
||||
新组织能否成为推动剧情的关键力量?
|
||||
|
||||
**5. 引入时机**
|
||||
新组织应该在哪个章节出现最合适?
|
||||
|
||||
【预测依据】
|
||||
- 剧情阶段的典型组织需求(如:高潮阶段可能需要强大的敌对势力)
|
||||
- 故事发展方向的逻辑需要(如:进入新地点需要当地势力)
|
||||
- 世界观完整性需要(如:权力格局需要多方势力)
|
||||
- 角色成长需要(如:主角需要加入或创建组织)
|
||||
</analysis_framework>
|
||||
|
||||
<output priority="P0">
|
||||
【输出格式】
|
||||
返回纯JSON对象(两种情况之一):
|
||||
|
||||
**情况A:需要新组织**
|
||||
{{
|
||||
"needs_new_organizations": true,
|
||||
"reason": "预测分析原因(150-200字),说明为什么即将的剧情需要新组织",
|
||||
"organization_count": 1,
|
||||
"organization_specifications": [
|
||||
{{
|
||||
"name": "建议的组织名字(可选)",
|
||||
"organization_description": "组织在剧情中的定位和作用(100-150字)",
|
||||
"organization_type": "帮派/门派/公司/政府/家族/神秘组织等",
|
||||
"importance": "high/medium/low",
|
||||
"appearance_chapter": {start_chapter},
|
||||
"power_level": 70,
|
||||
"plot_function": "在剧情中的具体功能",
|
||||
"location": "组织所在地或活动区域",
|
||||
"motto": "组织口号或宗旨(可选)",
|
||||
"initial_members": [
|
||||
{{
|
||||
"character_name": "现有角色名(如需加入)",
|
||||
"position": "职位",
|
||||
"reason": "为什么加入"
|
||||
}}
|
||||
],
|
||||
"relationship_suggestions": [
|
||||
{{
|
||||
"target_organization": "已有组织名",
|
||||
"relationship_type": "建议的关系类型(盟友/敌对/竞争/合作等)",
|
||||
"reason": "为什么建立这种关系"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
]
|
||||
}}
|
||||
|
||||
**情况B:不需要新组织**
|
||||
{{
|
||||
"needs_new_organizations": false,
|
||||
"reason": "现有组织足以支撑即将的剧情发展,说明理由"
|
||||
}}
|
||||
</output>
|
||||
|
||||
<constraints>
|
||||
【必须遵守】
|
||||
✅ 这是预测性分析,面向未来剧情
|
||||
✅ 考虑世界观的丰富性和完整性
|
||||
✅ 确保引入必要性,不为引入而引入
|
||||
✅ 优先考虑组织的长期作用
|
||||
✅ 组织应该是推动剧情的关键力量
|
||||
|
||||
【禁止事项】
|
||||
❌ 输出markdown标记
|
||||
❌ 基于已生成内容做事后分析
|
||||
❌ 为了引入组织而强行引入
|
||||
❌ 设计一次性功能组织
|
||||
❌ 创建与现有组织功能重复的组织
|
||||
</constraints>"""
|
||||
|
||||
# 自动组织引入 - 生成提示词(RTCO框架)
|
||||
AUTO_ORGANIZATION_GENERATION = """<system>
|
||||
你是专业的世界构建师,擅长根据剧情需求创建完整的组织/势力设定。
|
||||
</system>
|
||||
|
||||
<task>
|
||||
【生成任务】
|
||||
为小说生成新组织的完整设定,包括基本信息、组织特性、背景历史和成员结构。
|
||||
</task>
|
||||
|
||||
<project priority="P1">
|
||||
【项目信息】
|
||||
书名:{title}
|
||||
类型:{genre}
|
||||
主题:{theme}
|
||||
|
||||
【世界观】
|
||||
时间背景:{time_period}
|
||||
地理位置:{location}
|
||||
氛围基调:{atmosphere}
|
||||
世界规则:{rules}
|
||||
</project>
|
||||
|
||||
<context priority="P0">
|
||||
【已有组织】
|
||||
{existing_organizations}
|
||||
|
||||
【已有角色】
|
||||
{existing_characters}
|
||||
|
||||
【剧情上下文】
|
||||
{plot_context}
|
||||
|
||||
【组织规格要求】
|
||||
{organization_specification}
|
||||
</context>
|
||||
|
||||
<mcp_context priority="P2">
|
||||
【MCP工具参考】
|
||||
{mcp_references}
|
||||
</mcp_context>
|
||||
|
||||
<requirements priority="P0">
|
||||
【核心要求】
|
||||
1. 组织必须符合剧情需求和世界观设定
|
||||
2. 组织要有明确的目的、结构和特色
|
||||
3. 组织特性、背景要有深度和独特性
|
||||
4. 外在表现要具体生动
|
||||
5. 考虑与已有组织的关系和互动
|
||||
6. 如果需要,可以建议将现有角色加入组织
|
||||
</requirements>
|
||||
|
||||
<output priority="P0">
|
||||
【输出格式】
|
||||
返回纯JSON对象:
|
||||
|
||||
{{
|
||||
"name": "组织名称",
|
||||
"is_organization": true,
|
||||
"role_type": "supporting",
|
||||
"organization_type": "组织类型(帮派/门派/公司/政府/家族/神秘组织等)",
|
||||
"personality": "组织特性的详细描述(150-200字):运作方式、核心理念、行事风格、文化价值观",
|
||||
"background": "组织背景故事(200-300字):建立历史、发展历程、重要事件、当前地位",
|
||||
"appearance": "外在表现(100-150字):总部位置、标志性建筑、组织标志、成员着装",
|
||||
"organization_purpose": "组织目的和宗旨:明确目标、长期愿景、行动准则",
|
||||
"power_level": 75,
|
||||
"location": "所在地点:主要活动区域、势力范围",
|
||||
"motto": "组织格言或口号",
|
||||
"color": "组织代表颜色",
|
||||
"traits": ["特征1", "特征2", "特征3"],
|
||||
|
||||
"initial_members": [
|
||||
{{
|
||||
"character_name": "已存在的角色名称",
|
||||
"position": "职位名称",
|
||||
"rank": 8,
|
||||
"loyalty": 80,
|
||||
"joined_at": "加入时间(可选)",
|
||||
"status": "active"
|
||||
}}
|
||||
],
|
||||
|
||||
"organization_relationships": [
|
||||
{{
|
||||
"target_organization_name": "已存在的组织名称",
|
||||
"relationship_type": "盟友/敌对/竞争/合作/从属等",
|
||||
"description": "关系的具体描述"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
|
||||
【数值范围】
|
||||
- power_level:0-100的整数,表示在世界中的影响力
|
||||
- rank:0到10(职位等级)
|
||||
- loyalty:0到100(成员忠诚度)
|
||||
</output>
|
||||
|
||||
<constraints>
|
||||
【必须遵守】
|
||||
✅ 符合剧情需求和世界观设定
|
||||
✅ 组织要有独特的定位和价值
|
||||
✅ character_name必须精确匹配【已有角色】
|
||||
✅ target_organization_name必须精确匹配【已有组织】
|
||||
✅ 组织能够推动剧情发展
|
||||
|
||||
【禁止事项】
|
||||
❌ 输出markdown标记
|
||||
❌ 在描述中使用特殊符号
|
||||
❌ 引用不存在的角色或组织
|
||||
❌ 创建功能与现有组织重复的组织
|
||||
❌ 创建对剧情没有实际作用的组织
|
||||
</constraints>"""
|
||||
|
||||
# 职业体系生成提示词 V2(RTCO框架)
|
||||
@@ -2166,6 +2408,20 @@ class PromptService:
|
||||
"parameters": ["title", "genre", "theme", "time_period", "location", "atmosphere", "rules",
|
||||
"existing_characters", "plot_context", "character_specification", "mcp_references"]
|
||||
},
|
||||
"AUTO_ORGANIZATION_ANALYSIS": {
|
||||
"name": "自动组织分析",
|
||||
"category": "自动组织引入",
|
||||
"description": "分析新生成的大纲,判断是否需要引入新组织",
|
||||
"parameters": ["title", "genre", "theme", "time_period", "location", "atmosphere",
|
||||
"existing_organizations", "existing_characters", "all_chapters_brief", "start_chapter", "chapter_count", "plot_stage", "story_direction"]
|
||||
},
|
||||
"AUTO_ORGANIZATION_GENERATION": {
|
||||
"name": "自动组织生成",
|
||||
"category": "自动组织引入",
|
||||
"description": "根据剧情需求自动生成新组织的完整设定",
|
||||
"parameters": ["title", "genre", "theme", "time_period", "location", "atmosphere", "rules",
|
||||
"existing_organizations", "existing_characters", "plot_context", "organization_specification", "mcp_references"]
|
||||
},
|
||||
"CAREER_SYSTEM_GENERATION": {
|
||||
"name": "职业体系生成",
|
||||
"category": "世界构建",
|
||||
|
||||
@@ -33,6 +33,37 @@ interface CharacterConfirmationData {
|
||||
chapter_range: string;
|
||||
}
|
||||
|
||||
// 组织预测数据类型
|
||||
interface PredictedOrganization {
|
||||
name?: string;
|
||||
organization_description: string;
|
||||
organization_type: string;
|
||||
importance: string;
|
||||
appearance_chapter: number;
|
||||
power_level: number;
|
||||
plot_function: string;
|
||||
location?: string;
|
||||
motto?: string;
|
||||
initial_members: Array<{
|
||||
character_name: string;
|
||||
position: string;
|
||||
reason?: string;
|
||||
}>;
|
||||
relationship_suggestions: Array<{
|
||||
target_organization: string;
|
||||
relationship_type: string;
|
||||
reason?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface OrganizationConfirmationData {
|
||||
code: string;
|
||||
message: string;
|
||||
predicted_organizations: PredictedOrganization[];
|
||||
reason: string;
|
||||
chapter_range: string;
|
||||
}
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
export default function Outline() {
|
||||
@@ -56,6 +87,11 @@ export default function Outline() {
|
||||
const [pendingGenerateData, setPendingGenerateData] = useState<any>(null);
|
||||
const [selectedCharacterIndices, setSelectedCharacterIndices] = useState<number[]>([]);
|
||||
|
||||
// 组织确认相关状态
|
||||
const [organizationConfirmData, setOrganizationConfirmData] = useState<OrganizationConfirmationData | null>(null);
|
||||
const [organizationConfirmVisible, setOrganizationConfirmVisible] = useState(false);
|
||||
const [selectedOrganizationIndices, setSelectedOrganizationIndices] = useState<number[]>([]);
|
||||
|
||||
// 缓存批量展开的规划数据,避免重复AI调用
|
||||
const [cachedBatchExpansionResponse, setCachedBatchExpansionResponse] = useState<BatchOutlineExpansionResponse | null>(null);
|
||||
|
||||
@@ -124,6 +160,15 @@ export default function Outline() {
|
||||
}
|
||||
}, [characterConfirmData]);
|
||||
|
||||
// 当组织确认数据变化时,初始化选中状态(默认全选)
|
||||
useEffect(() => {
|
||||
if (organizationConfirmData) {
|
||||
setSelectedOrganizationIndices(
|
||||
organizationConfirmData.predicted_organizations.map((_, idx) => idx)
|
||||
);
|
||||
}
|
||||
}, [organizationConfirmData]);
|
||||
|
||||
// 移除事件监听,避免无限循环
|
||||
// Hook 内部已经更新了 store,不需要再次刷新
|
||||
|
||||
@@ -205,6 +250,9 @@ export default function Outline() {
|
||||
plot_stage?: 'development' | 'climax' | 'ending';
|
||||
keep_existing?: boolean;
|
||||
enable_auto_characters?: boolean;
|
||||
require_character_confirmation?: boolean;
|
||||
enable_auto_organizations?: boolean;
|
||||
require_organization_confirmation?: boolean;
|
||||
}
|
||||
|
||||
const handleGenerate = async (values: GenerateFormValues) => {
|
||||
@@ -237,7 +285,10 @@ export default function Outline() {
|
||||
mode: values.mode || 'auto',
|
||||
story_direction: values.story_direction,
|
||||
plot_stage: values.plot_stage || 'development',
|
||||
enable_auto_characters: values.enable_auto_characters !== undefined ? values.enable_auto_characters : true
|
||||
enable_auto_characters: values.enable_auto_characters !== undefined ? values.enable_auto_characters : true,
|
||||
require_character_confirmation: values.require_character_confirmation !== undefined ? values.require_character_confirmation : true,
|
||||
enable_auto_organizations: values.enable_auto_organizations !== undefined ? values.enable_auto_organizations : true,
|
||||
require_organization_confirmation: values.require_organization_confirmation !== undefined ? values.require_organization_confirmation : true
|
||||
};
|
||||
|
||||
// 只有在用户选择了模型时才添加model参数
|
||||
@@ -281,6 +332,20 @@ export default function Outline() {
|
||||
setCharacterConfirmData(data);
|
||||
setCharacterConfirmVisible(true);
|
||||
},
|
||||
onOrganizationConfirmation: (data: any) => {
|
||||
// ✨ 新增:处理组织确认事件
|
||||
console.log('收到组织确认请求:', data);
|
||||
// 关闭SSE进度Modal
|
||||
setSSEModalVisible(false);
|
||||
setIsGenerating(false);
|
||||
|
||||
// 保存待处理的生成数据
|
||||
setPendingGenerateData(requestData);
|
||||
|
||||
// 显示组织确认对话框
|
||||
setOrganizationConfirmData(data);
|
||||
setOrganizationConfirmVisible(true);
|
||||
},
|
||||
onError: (error: string) => {
|
||||
// 现在只处理真正的错误
|
||||
message.error(`生成失败: ${error}`);
|
||||
@@ -359,6 +424,9 @@ export default function Outline() {
|
||||
theme: currentProject.theme || '',
|
||||
model: defaultModel, // 添加默认模型
|
||||
enable_auto_characters: false, // 默认禁用自动角色引入
|
||||
require_character_confirmation: true, // 默认需要用户确认
|
||||
enable_auto_organizations: false, // 默认禁用自动组织引入
|
||||
require_organization_confirmation: true, // 默认需要用户确认
|
||||
}}
|
||||
>
|
||||
{hasOutlines && (
|
||||
@@ -467,19 +535,94 @@ export default function Outline() {
|
||||
<TextArea rows={2} placeholder="其他特殊要求(可选)" />
|
||||
</Form.Item>
|
||||
|
||||
{/* 自动角色引入开关 - 仅在续写模式显示 */}
|
||||
{isContinue && (
|
||||
{/* 自动角色和组织引入开关 - 仅在续写模式显示 */}
|
||||
{isContinue && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
{/* 角色引入部分 */}
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 24, alignItems: 'flex-start' }}>
|
||||
<Form.Item
|
||||
label="智能角色引入"
|
||||
name="enable_auto_characters"
|
||||
tooltip="AI会根据剧情发展自动判断是否需要引入新角色,并自动创建角色卡片和建立关系"
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Radio.Group buttonStyle="solid">
|
||||
<Radio.Button value={true}>启用</Radio.Button>
|
||||
<Radio.Button value={false}>禁用</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{/* 角色确认选项 */}
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.enable_auto_characters !== currentValues.enable_auto_characters
|
||||
}
|
||||
>
|
||||
{({ getFieldValue }) => {
|
||||
const enableAutoChars = getFieldValue('enable_auto_characters');
|
||||
if (!enableAutoChars) return null;
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
label="新角色确认"
|
||||
name="require_character_confirmation"
|
||||
tooltip="启用后,AI预测到需要新角色时会先让您确认;禁用后,AI预测的角色将直接创建"
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Radio.Group buttonStyle="solid">
|
||||
<Radio.Button value={true}>需要确认</Radio.Button>
|
||||
<Radio.Button value={false}>直接创建</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
{/* 组织引入部分 */}
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 24, alignItems: 'flex-start' }}>
|
||||
<Form.Item
|
||||
label="智能组织引入"
|
||||
name="enable_auto_organizations"
|
||||
tooltip="AI会根据剧情发展自动判断是否需要引入新组织/势力,并自动创建设定和建立关系"
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Radio.Group buttonStyle="solid">
|
||||
<Radio.Button value={true}>启用</Radio.Button>
|
||||
<Radio.Button value={false}>禁用</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
{/* 组织确认选项 */}
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.enable_auto_organizations !== currentValues.enable_auto_organizations
|
||||
}
|
||||
>
|
||||
{({ getFieldValue }) => {
|
||||
const enableAutoOrgs = getFieldValue('enable_auto_organizations');
|
||||
if (!enableAutoOrgs) return null;
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
label="新组织确认"
|
||||
name="require_organization_confirmation"
|
||||
tooltip="启用后,AI预测到需要新组织时会先让您确认;禁用后,AI预测的组织将直接创建"
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Radio.Group buttonStyle="solid">
|
||||
<Radio.Button value={true}>需要确认</Radio.Button>
|
||||
<Radio.Button value={false}>直接创建</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
@@ -1640,6 +1783,15 @@ export default function Outline() {
|
||||
setCharacterConfirmData(null);
|
||||
// 刷新大纲列表
|
||||
refreshOutlines();
|
||||
},
|
||||
onOrganizationConfirmation: (data: any) => {
|
||||
// 处理可能的后续组织确认
|
||||
console.log('收到组织确认请求:', data);
|
||||
setSSEModalVisible(false);
|
||||
setIsGenerating(false);
|
||||
setPendingGenerateData(requestData);
|
||||
setOrganizationConfirmData(data);
|
||||
setOrganizationConfirmVisible(true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1666,7 +1818,7 @@ export default function Outline() {
|
||||
|
||||
// 显示进度Modal
|
||||
setSSEProgress(0);
|
||||
setSSEMessage('跳过角色创建,开始续写大纲...');
|
||||
setSSEMessage('跳过角色创建,继续生成...');
|
||||
setSSEModalVisible(true);
|
||||
|
||||
// 准备请求数据,禁用自动角色引入
|
||||
@@ -1687,6 +1839,15 @@ export default function Outline() {
|
||||
onResult: (data: any) => {
|
||||
console.log('生成完成,结果:', data);
|
||||
},
|
||||
onOrganizationConfirmation: (data: any) => {
|
||||
// 处理可能的后续组织确认
|
||||
console.log('收到组织确认请求:', data);
|
||||
setSSEModalVisible(false);
|
||||
setIsGenerating(false);
|
||||
setPendingGenerateData(requestData);
|
||||
setOrganizationConfirmData(data);
|
||||
setOrganizationConfirmVisible(true);
|
||||
},
|
||||
onError: (error: string) => {
|
||||
message.error(`生成失败: ${error}`);
|
||||
setSSEModalVisible(false);
|
||||
@@ -1714,6 +1875,128 @@ export default function Outline() {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理组织确认 - 用户同意创建组织
|
||||
const handleConfirmOrganizations = async (selectedOrganizations: PredictedOrganization[]) => {
|
||||
if (!pendingGenerateData) {
|
||||
message.error('生成数据丢失,请重新操作');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setOrganizationConfirmVisible(false);
|
||||
setIsGenerating(true);
|
||||
|
||||
// 显示进度Modal
|
||||
setSSEProgress(0);
|
||||
setSSEMessage('正在创建确认的组织...');
|
||||
setSSEModalVisible(true);
|
||||
|
||||
// 准备请求数据,添加确认的组织
|
||||
const requestData = {
|
||||
...pendingGenerateData,
|
||||
confirmed_organizations: selectedOrganizations
|
||||
};
|
||||
|
||||
console.log('携带确认组织重新请求:', requestData);
|
||||
|
||||
// 重新发起SSE请求
|
||||
const apiUrl = `/api/outlines/generate-stream`;
|
||||
const client = new SSEPostClient(apiUrl, requestData, {
|
||||
onProgress: (msg: string, progress: number) => {
|
||||
setSSEMessage(msg);
|
||||
setSSEProgress(progress);
|
||||
},
|
||||
onResult: (data: any) => {
|
||||
console.log('生成完成,结果:', data);
|
||||
},
|
||||
onError: (error: string) => {
|
||||
message.error(`生成失败: ${error}`);
|
||||
setSSEModalVisible(false);
|
||||
setIsGenerating(false);
|
||||
},
|
||||
onComplete: () => {
|
||||
message.success('大纲生成完成!');
|
||||
setSSEModalVisible(false);
|
||||
setIsGenerating(false);
|
||||
// 清理状态
|
||||
setPendingGenerateData(null);
|
||||
setOrganizationConfirmData(null);
|
||||
// 刷新大纲列表
|
||||
refreshOutlines();
|
||||
}
|
||||
});
|
||||
|
||||
client.connect();
|
||||
|
||||
} catch (error) {
|
||||
console.error('确认组织失败:', error);
|
||||
message.error('操作失败');
|
||||
setSSEModalVisible(false);
|
||||
setIsGenerating(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理组织确认 - 用户拒绝创建组织
|
||||
const handleRejectOrganizations = async () => {
|
||||
if (!pendingGenerateData) {
|
||||
message.error('生成数据丢失,请重新操作');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setOrganizationConfirmVisible(false);
|
||||
setIsGenerating(true);
|
||||
|
||||
// 显示进度Modal
|
||||
setSSEProgress(0);
|
||||
setSSEMessage('跳过组织创建,继续生成...');
|
||||
setSSEModalVisible(true);
|
||||
|
||||
// 准备请求数据,禁用自动组织引入
|
||||
const requestData = {
|
||||
...pendingGenerateData,
|
||||
enable_auto_organizations: false // 禁用自动组织引入
|
||||
};
|
||||
|
||||
console.log('跳过组织创建,重新请求:', requestData);
|
||||
|
||||
// 重新发起SSE请求
|
||||
const apiUrl = `/api/outlines/generate-stream`;
|
||||
const client = new SSEPostClient(apiUrl, requestData, {
|
||||
onProgress: (msg: string, progress: number) => {
|
||||
setSSEMessage(msg);
|
||||
setSSEProgress(progress);
|
||||
},
|
||||
onResult: (data: any) => {
|
||||
console.log('生成完成,结果:', data);
|
||||
},
|
||||
onError: (error: string) => {
|
||||
message.error(`生成失败: ${error}`);
|
||||
setSSEModalVisible(false);
|
||||
setIsGenerating(false);
|
||||
},
|
||||
onComplete: () => {
|
||||
message.success('大纲生成完成!');
|
||||
setSSEModalVisible(false);
|
||||
setIsGenerating(false);
|
||||
// 清理状态
|
||||
setPendingGenerateData(null);
|
||||
setOrganizationConfirmData(null);
|
||||
// 刷新大纲列表
|
||||
refreshOutlines();
|
||||
}
|
||||
});
|
||||
|
||||
client.connect();
|
||||
|
||||
} catch (error) {
|
||||
console.error('跳过组织创建失败:', error);
|
||||
message.error('操作失败');
|
||||
setSSEModalVisible(false);
|
||||
setIsGenerating(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染角色确认对话框
|
||||
const renderCharacterConfirmModal = () => {
|
||||
if (!characterConfirmData) return null;
|
||||
@@ -1853,10 +2136,146 @@ export default function Outline() {
|
||||
);
|
||||
};
|
||||
|
||||
// 渲染组织确认对话框
|
||||
const renderOrganizationConfirmModal = () => {
|
||||
if (!organizationConfirmData) return null;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
<Space>
|
||||
<ExclamationCircleOutlined style={{ color: 'var(--color-warning)' }} />
|
||||
<span>确认引入新组织</span>
|
||||
</Space>
|
||||
}
|
||||
open={organizationConfirmVisible}
|
||||
onOk={() => {
|
||||
const selectedOrganizations = organizationConfirmData.predicted_organizations.filter(
|
||||
(_, idx) => selectedOrganizationIndices.includes(idx)
|
||||
);
|
||||
handleConfirmOrganizations(selectedOrganizations);
|
||||
}}
|
||||
onCancel={() => {
|
||||
modalApi.confirm({
|
||||
title: '确认操作',
|
||||
content: '是否跳过组织创建,直接续写大纲?',
|
||||
okText: '跳过组织,继续续写',
|
||||
cancelText: '返回选择',
|
||||
onOk: handleRejectOrganizations
|
||||
});
|
||||
}}
|
||||
width={800}
|
||||
centered
|
||||
okText={`确认创建选中的 ${selectedOrganizationIndices.length} 个组织`}
|
||||
cancelText="跳过组织创建"
|
||||
>
|
||||
<div>
|
||||
<div style={{ marginBottom: 16, padding: 12, background: 'var(--color-warning-bg)', borderRadius: 4, border: '1px solid var(--color-warning-border)' }}>
|
||||
<div style={{ fontWeight: 500, marginBottom: 8, color: '#d48806' }}>
|
||||
AI 分析结果
|
||||
</div>
|
||||
<div style={{ color: '#666', marginBottom: 8 }}>
|
||||
{organizationConfirmData.reason}
|
||||
</div>
|
||||
<Tag color="blue">{organizationConfirmData.chapter_range}</Tag>
|
||||
<Tag color="green">{organizationConfirmData.predicted_organizations.length} 个预测组织</Tag>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Space>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => setSelectedOrganizationIndices(
|
||||
organizationConfirmData.predicted_organizations.map((_, idx) => idx)
|
||||
)}
|
||||
>
|
||||
全选
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => setSelectedOrganizationIndices([])}
|
||||
>
|
||||
全不选
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<List
|
||||
dataSource={organizationConfirmData.predicted_organizations}
|
||||
renderItem={(org, index) => (
|
||||
<List.Item
|
||||
key={index}
|
||||
style={{
|
||||
background: selectedOrganizationIndices.includes(index) ? '#f0f5ff' : 'transparent',
|
||||
padding: 12,
|
||||
borderRadius: 4,
|
||||
marginBottom: 8,
|
||||
border: selectedOrganizationIndices.includes(index) ? '1px solid var(--color-primary)' : '1px solid var(--color-border-secondary)',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={() => {
|
||||
if (selectedOrganizationIndices.includes(index)) {
|
||||
setSelectedOrganizationIndices(selectedOrganizationIndices.filter(i => i !== index));
|
||||
} else {
|
||||
setSelectedOrganizationIndices([...selectedOrganizationIndices, index]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div style={{ width: '100%' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
|
||||
<Space>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOrganizationIndices.includes(index)}
|
||||
onChange={() => { }}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
<span style={{ fontWeight: 500, fontSize: 16 }}>
|
||||
{org.name || org.organization_description}
|
||||
</span>
|
||||
<Tag color="blue">{org.organization_type}</Tag>
|
||||
<Tag color="orange">势力等级: {org.power_level}</Tag>
|
||||
</Space>
|
||||
<Tag>第{org.appearance_chapter}章登场</Tag>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 8, color: '#666' }}>
|
||||
<strong>剧情作用:</strong>{org.plot_function}
|
||||
</div>
|
||||
|
||||
{org.location && (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<strong>地点:</strong>{org.location}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{org.initial_members && org.initial_members.length > 0 && (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<strong>初始成员:</strong>
|
||||
<Space wrap style={{ marginLeft: 8 }}>
|
||||
{org.initial_members.map((member, idx) => (
|
||||
<Tag key={idx} color="purple">
|
||||
{member.character_name} - {member.position}
|
||||
</Tag>
|
||||
))}
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 角色确认对话框 */}
|
||||
{renderCharacterConfirmModal()}
|
||||
{/* 组织确认对话框 */}
|
||||
{renderOrganizationConfirmModal()}
|
||||
|
||||
{/* 批量展开预览 Modal */}
|
||||
<Modal
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface SSEClientOptions {
|
||||
onComplete?: () => void;
|
||||
onConnectionError?: (error: Event) => void;
|
||||
onCharacterConfirmation?: (data: any) => void; // 新增:角色确认回调
|
||||
onOrganizationConfirmation?: (data: any) => void; // 新增:组织确认回调
|
||||
}
|
||||
|
||||
export class SSEClient {
|
||||
@@ -200,6 +201,13 @@ export class SSEPostClient {
|
||||
}
|
||||
currentEvent = ''; // 重置事件类型
|
||||
return; // 暂停流程,等待用户确认
|
||||
} else if (currentEvent === 'organization_confirmation_required') {
|
||||
// 处理组织确认事件
|
||||
if (this.options.onOrganizationConfirmation) {
|
||||
this.options.onOrganizationConfirmation(data);
|
||||
}
|
||||
currentEvent = ''; // 重置事件类型
|
||||
return; // 暂停流程,等待用户确认
|
||||
} else {
|
||||
// 标准消息处理
|
||||
const message: SSEMessage = data;
|
||||
|
||||
Reference in New Issue
Block a user