update:1.新增AI生成组织功能,扩展优化组织字段(所在地 代表颜色 格言/口号)
2.适配移动端项目管理-剧情分析UI页面
This commit is contained in:
+82
-23
@@ -553,36 +553,59 @@ async def analyze_chapter_background(
|
||||
)
|
||||
logger.info(f"✅ 添加{added_count}条记忆到向量库")
|
||||
|
||||
# 最终更新任务状态(写操作,需要锁)
|
||||
async with write_lock:
|
||||
task.progress = 100
|
||||
task.status = 'completed'
|
||||
task.completed_at = datetime.now()
|
||||
await db_session.commit()
|
||||
# 最终更新任务状态(写操作,需要锁)- 增加重试机制
|
||||
update_success = False
|
||||
for retry in range(3):
|
||||
try:
|
||||
async with write_lock:
|
||||
task.progress = 100
|
||||
task.status = 'completed'
|
||||
task.completed_at = datetime.now()
|
||||
await db_session.commit()
|
||||
update_success = True
|
||||
logger.info(f"✅ 章节分析完成: {chapter_id}, 提取{len(memories)}条记忆")
|
||||
break
|
||||
except Exception as commit_error:
|
||||
logger.error(f"❌ 提交任务完成状态失败(重试{retry+1}/3): {str(commit_error)}")
|
||||
if retry < 2:
|
||||
await asyncio.sleep(0.1)
|
||||
else:
|
||||
logger.error(f"❌ 无法更新任务为completed状态: {task_id}")
|
||||
# 即使失败也不抛出异常,因为分析本身已经完成
|
||||
|
||||
logger.info(f"✅ 章节分析完成: {chapter_id}, 提取{len(memories)}条记忆")
|
||||
if not update_success:
|
||||
logger.warning(f"⚠️ 章节分析完成但状态更新失败: {chapter_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 后台分析异常: {str(e)}", exc_info=True)
|
||||
# 确保任务状态被更新为failed(写操作,需要锁)
|
||||
if db_session:
|
||||
try:
|
||||
async with write_lock:
|
||||
task_result = await db_session.execute(
|
||||
select(AnalysisTask).where(AnalysisTask.id == task_id)
|
||||
)
|
||||
task = task_result.scalar_one_or_none()
|
||||
if task:
|
||||
task.status = 'failed'
|
||||
task.error_message = str(e)[:500]
|
||||
task.completed_at = datetime.now()
|
||||
task.progress = 0
|
||||
await db_session.commit()
|
||||
logger.info(f"✅ 任务状态已更新为failed: {task_id}")
|
||||
# 多次重试更新任务状态
|
||||
for retry in range(3):
|
||||
try:
|
||||
async with write_lock:
|
||||
# 重新获取任务(可能是旧会话导致的问题)
|
||||
task_result = await db_session.execute(
|
||||
select(AnalysisTask).where(AnalysisTask.id == task_id)
|
||||
)
|
||||
task = task_result.scalar_one_or_none()
|
||||
if task:
|
||||
task.status = 'failed'
|
||||
task.error_message = str(e)[:500]
|
||||
task.completed_at = datetime.now()
|
||||
task.progress = 0
|
||||
await db_session.commit()
|
||||
logger.info(f"✅ 任务状态已更新为failed: {task_id} (重试{retry+1}次)")
|
||||
break
|
||||
else:
|
||||
logger.error(f"❌ 无法找到任务进行状态更新: {task_id}")
|
||||
break
|
||||
except Exception as update_error:
|
||||
logger.error(f"❌ 更新任务状态失败(重试{retry+1}/3): {str(update_error)}")
|
||||
if retry < 2:
|
||||
await asyncio.sleep(0.1) # 短暂等待后重试
|
||||
else:
|
||||
logger.error(f"❌ 无法找到任务进行状态更新: {task_id}")
|
||||
except Exception as update_error:
|
||||
logger.error(f"❌ 更新任务状态失败: {str(update_error)}")
|
||||
logger.error(f"❌ 任务状态更新失败,已达到最大重试次数: {task_id}")
|
||||
finally:
|
||||
if db_session:
|
||||
await db_session.close()
|
||||
@@ -956,14 +979,21 @@ async def get_analysis_task_status(
|
||||
"""
|
||||
查询指定章节的最新分析任务状态
|
||||
|
||||
自动恢复机制:
|
||||
- 如果任务状态为running且超过1分钟未更新,自动标记为failed
|
||||
- 如果任务状态为pending且超过2分钟未启动,自动标记为failed
|
||||
|
||||
返回:
|
||||
- task_id: 任务ID
|
||||
- status: pending/running/completed/failed
|
||||
- progress: 0-100
|
||||
- error_message: 错误信息(如果失败)
|
||||
- auto_recovered: 是否被自动恢复
|
||||
- created_at: 创建时间
|
||||
- completed_at: 完成时间
|
||||
"""
|
||||
from datetime import timedelta
|
||||
|
||||
# 获取该章节最新的分析任务
|
||||
result = await db.execute(
|
||||
select(AnalysisTask)
|
||||
@@ -976,12 +1006,41 @@ async def get_analysis_task_status(
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="未找到分析任务")
|
||||
|
||||
auto_recovered = False
|
||||
current_time = datetime.now()
|
||||
|
||||
# 自动恢复卡住的任务
|
||||
if task.status == 'running':
|
||||
# 如果任务在running状态超过1分钟,标记为失败
|
||||
if task.started_at and (current_time - task.started_at) > timedelta(minutes=1):
|
||||
task.status = 'failed'
|
||||
task.error_message = '任务超时(超过1分钟未完成,已自动恢复)'
|
||||
task.completed_at = current_time
|
||||
task.progress = 0
|
||||
auto_recovered = True
|
||||
await db.commit()
|
||||
await db.refresh(task)
|
||||
logger.warning(f"🔄 自动恢复卡住的任务: {task.id}, 章节: {chapter_id}")
|
||||
|
||||
elif task.status == 'pending':
|
||||
# 如果任务在pending状态超过2分钟仍未开始,标记为失败
|
||||
if task.created_at and (current_time - task.created_at) > timedelta(minutes=2):
|
||||
task.status = 'failed'
|
||||
task.error_message = '任务启动超时(超过2分钟未启动,已自动恢复)'
|
||||
task.completed_at = current_time
|
||||
task.progress = 0
|
||||
auto_recovered = True
|
||||
await db.commit()
|
||||
await db.refresh(task)
|
||||
logger.warning(f"🔄 自动恢复未启动的任务: {task.id}, 章节: {chapter_id}")
|
||||
|
||||
return {
|
||||
"task_id": task.id,
|
||||
"chapter_id": task.chapter_id,
|
||||
"status": task.status,
|
||||
"progress": task.progress,
|
||||
"error_message": task.error_message,
|
||||
"auto_recovered": auto_recovered,
|
||||
"created_at": task.created_at.isoformat() if task.created_at else None,
|
||||
"started_at": task.started_at.isoformat() if task.started_at else None,
|
||||
"completed_at": task.completed_at.isoformat() if task.completed_at else None
|
||||
|
||||
@@ -44,7 +44,50 @@ async def get_characters(
|
||||
)
|
||||
characters = result.scalars().all()
|
||||
|
||||
return CharacterListResponse(total=total, items=characters)
|
||||
# 为组织类型的角色填充Organization表的额外字段
|
||||
enriched_characters = []
|
||||
for char in characters:
|
||||
char_dict = {
|
||||
"id": char.id,
|
||||
"project_id": char.project_id,
|
||||
"name": char.name,
|
||||
"age": char.age,
|
||||
"gender": char.gender,
|
||||
"is_organization": char.is_organization,
|
||||
"role_type": char.role_type,
|
||||
"personality": char.personality,
|
||||
"background": char.background,
|
||||
"appearance": char.appearance,
|
||||
"relationships": char.relationships,
|
||||
"organization_type": char.organization_type,
|
||||
"organization_purpose": char.organization_purpose,
|
||||
"organization_members": char.organization_members,
|
||||
"traits": char.traits,
|
||||
"avatar_url": char.avatar_url,
|
||||
"created_at": char.created_at,
|
||||
"updated_at": char.updated_at,
|
||||
"power_level": None,
|
||||
"location": None,
|
||||
"motto": None,
|
||||
"color": None
|
||||
}
|
||||
|
||||
if char.is_organization:
|
||||
org_result = await db.execute(
|
||||
select(Organization).where(Organization.character_id == char.id)
|
||||
)
|
||||
org = org_result.scalar_one_or_none()
|
||||
if org:
|
||||
char_dict.update({
|
||||
"power_level": org.power_level,
|
||||
"location": org.location,
|
||||
"motto": org.motto,
|
||||
"color": org.color
|
||||
})
|
||||
|
||||
enriched_characters.append(char_dict)
|
||||
|
||||
return CharacterListResponse(total=total, items=enriched_characters)
|
||||
|
||||
|
||||
@router.get("/project/{project_id}", response_model=CharacterListResponse, summary="获取项目的所有角色")
|
||||
@@ -67,7 +110,50 @@ async def get_project_characters(
|
||||
)
|
||||
characters = result.scalars().all()
|
||||
|
||||
return CharacterListResponse(total=total, items=characters)
|
||||
# 为组织类型的角色填充Organization表的额外字段
|
||||
enriched_characters = []
|
||||
for char in characters:
|
||||
char_dict = {
|
||||
"id": char.id,
|
||||
"project_id": char.project_id,
|
||||
"name": char.name,
|
||||
"age": char.age,
|
||||
"gender": char.gender,
|
||||
"is_organization": char.is_organization,
|
||||
"role_type": char.role_type,
|
||||
"personality": char.personality,
|
||||
"background": char.background,
|
||||
"appearance": char.appearance,
|
||||
"relationships": char.relationships,
|
||||
"organization_type": char.organization_type,
|
||||
"organization_purpose": char.organization_purpose,
|
||||
"organization_members": char.organization_members,
|
||||
"traits": char.traits,
|
||||
"avatar_url": char.avatar_url,
|
||||
"created_at": char.created_at,
|
||||
"updated_at": char.updated_at,
|
||||
"power_level": None,
|
||||
"location": None,
|
||||
"motto": None,
|
||||
"color": None
|
||||
}
|
||||
|
||||
if char.is_organization:
|
||||
org_result = await db.execute(
|
||||
select(Organization).where(Organization.character_id == char.id)
|
||||
)
|
||||
org = org_result.scalar_one_or_none()
|
||||
if org:
|
||||
char_dict.update({
|
||||
"power_level": org.power_level,
|
||||
"location": org.location,
|
||||
"motto": org.motto,
|
||||
"color": org.color
|
||||
})
|
||||
|
||||
enriched_characters.append(char_dict)
|
||||
|
||||
return CharacterListResponse(total=total, items=enriched_characters)
|
||||
|
||||
|
||||
@router.get("/{character_id}", response_model=CharacterResponse, summary="获取角色详情")
|
||||
@@ -213,16 +299,12 @@ async def generate_character(
|
||||
logger.info(f" - 角色名:{request.name or 'AI生成'}")
|
||||
logger.info(f" - 角色定位:{request.role_type}")
|
||||
logger.info(f" - 背景设定:{request.background or '无'}")
|
||||
logger.info(f" - AI提供商:{request.provider or 'default'}")
|
||||
logger.info(f" - AI模型:{request.model or 'default'}")
|
||||
logger.info(f" - AI提供商:{user_ai_service.api_provider}")
|
||||
logger.info(f" - AI模型:{user_ai_service.default_model}")
|
||||
logger.info(f" - Prompt长度:{len(prompt)} 字符")
|
||||
|
||||
try:
|
||||
ai_response = await user_ai_service.generate_text(
|
||||
prompt=prompt,
|
||||
provider=request.provider,
|
||||
model=request.model
|
||||
)
|
||||
ai_response = await user_ai_service.generate_text(prompt=prompt)
|
||||
logger.info(f"✅ AI响应接收完成,长度:{len(ai_response) if ai_response else 0} 字符")
|
||||
except Exception as ai_error:
|
||||
logger.error(f"❌ AI服务调用异常:{str(ai_error)}")
|
||||
@@ -317,7 +399,8 @@ async def generate_character(
|
||||
member_count=0,
|
||||
power_level=character_data.get("power_level", 50),
|
||||
location=character_data.get("location"),
|
||||
motto=character_data.get("motto")
|
||||
motto=character_data.get("motto"),
|
||||
color=character_data.get("color")
|
||||
)
|
||||
db.add(organization)
|
||||
await db.flush()
|
||||
@@ -477,7 +560,7 @@ async def generate_character(
|
||||
project_id=request.project_id,
|
||||
prompt=prompt,
|
||||
generated_content=ai_response,
|
||||
model=request.model or "default"
|
||||
model=user_ai_service.default_model
|
||||
)
|
||||
db.add(history)
|
||||
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, and_
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
import json
|
||||
|
||||
from app.database import get_db
|
||||
from app.models.relationship import Organization, OrganizationMember
|
||||
from app.models.character import Character
|
||||
from app.models.project import Project
|
||||
from app.models.generation_history import GenerationHistory
|
||||
from app.schemas.relationship import (
|
||||
OrganizationCreate,
|
||||
OrganizationUpdate,
|
||||
@@ -17,12 +21,25 @@ from app.schemas.relationship import (
|
||||
OrganizationMemberResponse,
|
||||
OrganizationMemberDetailResponse
|
||||
)
|
||||
from app.schemas.character import CharacterResponse
|
||||
from app.services.ai_service import AIService
|
||||
from app.services.prompt_service import prompt_service
|
||||
from app.logger import get_logger
|
||||
from app.api.settings import get_user_ai_service
|
||||
|
||||
router = APIRouter(prefix="/organizations", tags=["组织管理"])
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class OrganizationGenerateRequest(BaseModel):
|
||||
"""AI生成组织的请求模型"""
|
||||
project_id: str = Field(..., description="项目ID")
|
||||
name: Optional[str] = Field(None, description="组织名称")
|
||||
organization_type: Optional[str] = Field(None, description="组织类型")
|
||||
background: Optional[str] = Field(None, description="组织背景")
|
||||
requirements: Optional[str] = Field(None, description="特殊要求")
|
||||
|
||||
|
||||
@router.get("/project/{project_id}", response_model=List[OrganizationDetailResponse], summary="获取项目的所有组织")
|
||||
async def get_project_organizations(
|
||||
project_id: str,
|
||||
@@ -338,4 +355,193 @@ async def remove_organization_member(
|
||||
await db.commit()
|
||||
|
||||
logger.info(f"移除成员成功:{member_id}")
|
||||
return {"message": "成员移除成功", "id": member_id}
|
||||
return {"message": "成员移除成功", "id": member_id}
|
||||
|
||||
@router.post("/generate", response_model=CharacterResponse, summary="AI生成组织")
|
||||
async def generate_organization(
|
||||
request: OrganizationGenerateRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user_ai_service: AIService = Depends(get_user_ai_service)
|
||||
):
|
||||
"""
|
||||
使用AI生成组织设定
|
||||
|
||||
根据用户输入的信息,结合项目的世界观、主题等背景,
|
||||
AI会生成一个完整、详细的组织设定。
|
||||
|
||||
生成内容包括:组织名称、类型、特性、背景、目的、势力等级等
|
||||
"""
|
||||
# 验证项目是否存在并获取项目信息
|
||||
result = await db.execute(
|
||||
select(Project).where(Project.id == request.project_id)
|
||||
)
|
||||
project = result.scalar_one_or_none()
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="项目不存在")
|
||||
|
||||
try:
|
||||
# 获取已存在的角色和组织列表
|
||||
existing_chars_result = await db.execute(
|
||||
select(Character)
|
||||
.where(Character.project_id == request.project_id)
|
||||
.order_by(Character.created_at.desc())
|
||||
)
|
||||
existing_characters = existing_chars_result.scalars().all()
|
||||
|
||||
# 构建现有角色和组织信息摘要
|
||||
existing_info = ""
|
||||
character_list = []
|
||||
organization_list = []
|
||||
|
||||
if existing_characters:
|
||||
for c in existing_characters[:10]: # 最多显示10个
|
||||
if c.is_organization:
|
||||
organization_list.append(f"- {c.name} [{c.organization_type or '组织'}]")
|
||||
else:
|
||||
character_list.append(f"- {c.name}({c.role_type or '未知'})")
|
||||
|
||||
if character_list:
|
||||
existing_info += "\n已有角色:\n" + "\n".join(character_list)
|
||||
if organization_list:
|
||||
existing_info += "\n\n已有组织:\n" + "\n".join(organization_list)
|
||||
|
||||
# 构建项目上下文信息
|
||||
project_context = f"""
|
||||
项目信息:
|
||||
- 书名:{project.title}
|
||||
- 主题:{project.theme or '未设定'}
|
||||
- 类型:{project.genre or '未设定'}
|
||||
- 时间背景:{project.world_time_period or '未设定'}
|
||||
- 地理位置:{project.world_location or '未设定'}
|
||||
- 氛围基调:{project.world_atmosphere or '未设定'}
|
||||
- 世界规则:{project.world_rules or '未设定'}
|
||||
{existing_info}
|
||||
"""
|
||||
|
||||
# 构建用户输入信息
|
||||
user_input = f"""
|
||||
用户要求:
|
||||
- 组织名称:{request.name or '请AI生成'}
|
||||
- 组织类型:{request.organization_type or '请AI根据世界观决定'}
|
||||
- 背景设定:{request.background or '无特殊要求'}
|
||||
- 其他要求:{request.requirements or '无'}
|
||||
"""
|
||||
|
||||
# 使用统一的提示词服务
|
||||
prompt = prompt_service.get_single_organization_prompt(
|
||||
project_context=project_context,
|
||||
user_input=user_input
|
||||
)
|
||||
|
||||
# 调用AI生成组织
|
||||
logger.info(f"🎯 开始为项目 {request.project_id} 生成组织")
|
||||
logger.info(f" - 组织名:{request.name or 'AI生成'}")
|
||||
logger.info(f" - 组织类型:{request.organization_type or 'AI决定'}")
|
||||
logger.info(f" - 背景设定:{request.background or '无'}")
|
||||
logger.info(f" - AI提供商:{user_ai_service.api_provider}")
|
||||
logger.info(f" - AI模型:{user_ai_service.default_model}")
|
||||
logger.info(f" - Prompt长度:{len(prompt)} 字符")
|
||||
|
||||
try:
|
||||
ai_response = await user_ai_service.generate_text(prompt=prompt)
|
||||
logger.info(f"✅ AI响应接收完成,长度:{len(ai_response) if ai_response else 0} 字符")
|
||||
except Exception as ai_error:
|
||||
logger.error(f"❌ AI服务调用异常:{str(ai_error)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"AI服务调用失败:{str(ai_error)}"
|
||||
)
|
||||
|
||||
# 检查AI响应
|
||||
if not ai_response or not ai_response.strip():
|
||||
logger.error("❌ AI返回了空响应")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="AI服务返回空响应。请检查AI配置和网络连接。"
|
||||
)
|
||||
|
||||
logger.info(f"📝 开始清理AI响应")
|
||||
# 清理AI响应
|
||||
cleaned_response = ai_response.strip()
|
||||
if cleaned_response.startswith("```json"):
|
||||
cleaned_response = cleaned_response[7:]
|
||||
if cleaned_response.startswith("```"):
|
||||
cleaned_response = cleaned_response[3:]
|
||||
if cleaned_response.endswith("```"):
|
||||
cleaned_response = cleaned_response[:-3]
|
||||
cleaned_response = cleaned_response.strip()
|
||||
|
||||
logger.info(f" - 清理后长度:{len(cleaned_response)}")
|
||||
|
||||
# 解析AI响应
|
||||
logger.info(f"🔍 开始解析JSON")
|
||||
try:
|
||||
organization_data = json.loads(cleaned_response)
|
||||
logger.info(f"✅ JSON解析成功")
|
||||
logger.info(f" - 解析后的字段:{list(organization_data.keys())}")
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"❌ JSON解析失败:{str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"AI返回的内容无法解析为JSON。错误:{str(e)}"
|
||||
)
|
||||
|
||||
# 创建角色记录(组织也是角色的一种)
|
||||
character = Character(
|
||||
project_id=request.project_id,
|
||||
name=organization_data.get("name", request.name or "未命名组织"),
|
||||
is_organization=True,
|
||||
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"),
|
||||
organization_members=json.dumps(
|
||||
organization_data.get("organization_members", []),
|
||||
ensure_ascii=False
|
||||
),
|
||||
traits=json.dumps(
|
||||
organization_data.get("traits", []),
|
||||
ensure_ascii=False
|
||||
)
|
||||
)
|
||||
db.add(character)
|
||||
await db.flush()
|
||||
|
||||
logger.info(f"✅ 组织角色创建成功:{character.name} (ID: {character.id})")
|
||||
|
||||
# 自动创建Organization详情记录
|
||||
organization = Organization(
|
||||
character_id=character.id,
|
||||
project_id=request.project_id,
|
||||
member_count=0,
|
||||
power_level=organization_data.get("power_level", 50),
|
||||
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} (Org ID: {organization.id})")
|
||||
|
||||
# 记录生成历史
|
||||
history = GenerationHistory(
|
||||
project_id=request.project_id,
|
||||
prompt=prompt,
|
||||
generated_content=ai_response,
|
||||
model=user_ai_service.default_model
|
||||
)
|
||||
db.add(history)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(character)
|
||||
|
||||
logger.info(f"🎉 成功为项目 {request.project_id} 生成组织: {character.name}")
|
||||
|
||||
return character
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"生成组织失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"生成组织失败: {str(e)}")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""项目管理API"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Request
|
||||
from fastapi.responses import Response
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func, delete
|
||||
@@ -13,6 +13,7 @@ from app.models.outline import Outline
|
||||
from app.models.chapter import Chapter
|
||||
from app.models.generation_history import GenerationHistory
|
||||
from app.models.relationship import CharacterRelationship, Organization, OrganizationMember
|
||||
from app.models.memory import StoryMemory, PlotAnalysis
|
||||
from app.schemas.project import (
|
||||
ProjectCreate,
|
||||
ProjectUpdate,
|
||||
@@ -25,6 +26,7 @@ from app.schemas.import_export import (
|
||||
ImportResult
|
||||
)
|
||||
from app.services.import_export_service import ImportExportService
|
||||
from app.services.memory_service import memory_service
|
||||
from app.logger import get_logger
|
||||
from app.utils.data_consistency import (
|
||||
run_full_data_consistency_check,
|
||||
@@ -143,6 +145,7 @@ async def update_project(
|
||||
@router.delete("/{project_id}", summary="删除项目")
|
||||
async def delete_project(
|
||||
project_id: str,
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
try:
|
||||
@@ -158,6 +161,19 @@ async def delete_project(
|
||||
|
||||
project_title = project.title
|
||||
|
||||
# 从认证中间件获取用户ID
|
||||
user_id = getattr(request.state, 'user_id', None)
|
||||
|
||||
# 删除向量数据库中的记忆
|
||||
if user_id:
|
||||
try:
|
||||
await memory_service.delete_project_memories(user_id, project_id)
|
||||
logger.info(f"✅ 向量数据库清理成功")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ 向量数据库清理失败(继续删除其他数据): {str(e)}")
|
||||
else:
|
||||
logger.warning(f"⚠️ 未找到用户ID,跳过向量数据库清理")
|
||||
|
||||
relationships_result = await db.execute(
|
||||
delete(CharacterRelationship).where(CharacterRelationship.project_id == project_id)
|
||||
)
|
||||
@@ -200,11 +216,14 @@ async def delete_project(
|
||||
)
|
||||
logger.debug(f"删除角色数: {characters_result.rowcount}")
|
||||
|
||||
# 注意:StoryMemory和PlotAnalysis会通过数据库级联删除自动清理
|
||||
# 但向量数据库已在上面手动清理
|
||||
|
||||
await db.delete(project)
|
||||
await db.commit()
|
||||
|
||||
logger.info(f"项目删除成功: {project_title}")
|
||||
return {"message": "项目及所有关联数据删除成功"}
|
||||
return {"message": "项目及所有关联数据(包括向量数据库)删除成功"}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
|
||||
@@ -452,21 +452,24 @@ async def characters_generator(
|
||||
elif isinstance(char_data.get("relationships"), str):
|
||||
relationships_text = char_data.get("relationships")
|
||||
|
||||
# 判断是否为组织
|
||||
is_organization = char_data.get("is_organization", False)
|
||||
|
||||
character = Character(
|
||||
project_id=project_id,
|
||||
name=char_data.get("name", "未命名角色"),
|
||||
age=char_data.get("age"),
|
||||
gender=char_data.get("gender"),
|
||||
is_organization=char_data.get("is_organization", False),
|
||||
age=str(char_data.get("age", "")) if not is_organization else None,
|
||||
gender=char_data.get("gender") if not is_organization else None,
|
||||
is_organization=is_organization,
|
||||
role_type=char_data.get("role_type", "supporting"),
|
||||
personality=char_data.get("personality", ""),
|
||||
background=char_data.get("background", ""),
|
||||
appearance=char_data.get("appearance", ""),
|
||||
relationships=relationships_text,
|
||||
organization_type=char_data.get("organization_type"),
|
||||
organization_purpose=char_data.get("organization_purpose"),
|
||||
organization_members=json.dumps(char_data.get("organization_members", []), ensure_ascii=False),
|
||||
traits=json.dumps(char_data.get("traits", []), ensure_ascii=False)
|
||||
organization_type=char_data.get("organization_type") if is_organization else None,
|
||||
organization_purpose=char_data.get("organization_purpose") if is_organization else None,
|
||||
organization_members=json.dumps(char_data.get("organization_members", []), ensure_ascii=False) if is_organization else None,
|
||||
traits=json.dumps(char_data.get("traits", []), ensure_ascii=False) if char_data.get("traits") else None
|
||||
)
|
||||
db.add(character)
|
||||
created_characters.append((character, char_data))
|
||||
@@ -497,9 +500,10 @@ async def characters_generator(
|
||||
character_id=character.id,
|
||||
project_id=project_id,
|
||||
member_count=0, # 初始为0,后续添加成员时会更新
|
||||
power_level=char_data.get("power_level", 5),
|
||||
power_level=char_data.get("power_level", 50),
|
||||
location=char_data.get("location"),
|
||||
motto=char_data.get("motto")
|
||||
motto=char_data.get("motto"),
|
||||
color=char_data.get("color")
|
||||
)
|
||||
db.add(org)
|
||||
logger.info(f"向导创建组织记录:{character.name}")
|
||||
|
||||
Reference in New Issue
Block a user