update:1.修复一对一模式修改大纲名称没有同步更新章节名称 2.修复一对一模式全新生成大纲,没有关联删除对应章节问题 3.优化根据分析建议重新生成章节内容时引用默认写作风格 5.将写作风格调整至用户级,在一个项目中添加全局可见(需要更新数据库)
This commit is contained in:
@@ -2543,7 +2543,7 @@ async def regenerate_chapter_stream(
|
|||||||
if not analysis:
|
if not analysis:
|
||||||
raise HTTPException(status_code=404, detail="该章节暂无分析结果")
|
raise HTTPException(status_code=404, detail="该章节暂无分析结果")
|
||||||
|
|
||||||
# 预先获取项目上下文数据
|
# 预先获取项目上下文数据和写作风格
|
||||||
async for temp_db in get_db(request):
|
async for temp_db in get_db(request):
|
||||||
try:
|
try:
|
||||||
# 获取项目信息
|
# 获取项目信息
|
||||||
@@ -2566,6 +2566,41 @@ async def regenerate_chapter_stream(
|
|||||||
)
|
)
|
||||||
outline = outline_result.scalar_one_or_none()
|
outline = outline_result.scalar_one_or_none()
|
||||||
|
|
||||||
|
# 获取写作风格
|
||||||
|
style_content = ""
|
||||||
|
style_id = regenerate_request.style_id
|
||||||
|
|
||||||
|
# 如果没有指定风格,尝试使用项目的默认风格
|
||||||
|
if not style_id:
|
||||||
|
from app.models.project_default_style import ProjectDefaultStyle
|
||||||
|
default_style_result = await temp_db.execute(
|
||||||
|
select(ProjectDefaultStyle.style_id)
|
||||||
|
.where(ProjectDefaultStyle.project_id == chapter.project_id)
|
||||||
|
)
|
||||||
|
default_style_id = default_style_result.scalar_one_or_none()
|
||||||
|
if default_style_id:
|
||||||
|
style_id = default_style_id
|
||||||
|
logger.info(f"📝 使用项目默认写作风格: {style_id}")
|
||||||
|
|
||||||
|
# 获取风格内容
|
||||||
|
if style_id:
|
||||||
|
style_result = await temp_db.execute(
|
||||||
|
select(WritingStyle).where(WritingStyle.id == style_id)
|
||||||
|
)
|
||||||
|
style = style_result.scalar_one_or_none()
|
||||||
|
if style:
|
||||||
|
# 验证风格是否可用:全局预设风格(project_id为NULL)或者当前项目的自定义风格
|
||||||
|
if style.project_id is None or style.project_id == chapter.project_id:
|
||||||
|
style_content = style.prompt_content or ""
|
||||||
|
style_type = "全局预设" if style.project_id is None else "项目自定义"
|
||||||
|
logger.info(f"✅ 使用写作风格: {style.name} ({style_type})")
|
||||||
|
else:
|
||||||
|
logger.warning(f"⚠️ 风格 {style_id} 不属于当前项目,跳过")
|
||||||
|
else:
|
||||||
|
logger.warning(f"⚠️ 未找到风格 {style_id}")
|
||||||
|
else:
|
||||||
|
logger.info("ℹ️ 未指定写作风格,使用默认提示词")
|
||||||
|
|
||||||
# 构建项目上下文
|
# 构建项目上下文
|
||||||
project_context = {
|
project_context = {
|
||||||
'project_title': project.title if project else '未知',
|
'project_title': project.title if project else '未知',
|
||||||
@@ -2635,7 +2670,8 @@ async def regenerate_chapter_stream(
|
|||||||
chapter=chapter,
|
chapter=chapter,
|
||||||
analysis=analysis,
|
analysis=analysis,
|
||||||
regenerate_request=regenerate_request,
|
regenerate_request=regenerate_request,
|
||||||
project_context=project_context
|
project_context=project_context,
|
||||||
|
style_content=style_content
|
||||||
):
|
):
|
||||||
# 处理不同类型的事件
|
# 处理不同类型的事件
|
||||||
if event['type'] == 'chunk':
|
if event['type'] == 'chunk':
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ async def update_outline(
|
|||||||
request: Request,
|
request: Request,
|
||||||
db: AsyncSession = Depends(get_db)
|
db: AsyncSession = Depends(get_db)
|
||||||
):
|
):
|
||||||
"""更新大纲信息并同步更新structure字段"""
|
"""更新大纲信息并同步更新structure字段和关联章节"""
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(Outline).where(Outline.id == outline_id)
|
select(Outline).where(Outline.id == outline_id)
|
||||||
)
|
)
|
||||||
@@ -185,7 +185,7 @@ async def update_outline(
|
|||||||
|
|
||||||
# 验证用户权限
|
# 验证用户权限
|
||||||
user_id = getattr(request.state, 'user_id', None)
|
user_id = getattr(request.state, 'user_id', None)
|
||||||
await verify_project_access(outline.project_id, user_id, db)
|
project = await verify_project_access(outline.project_id, user_id, db)
|
||||||
|
|
||||||
# 更新字段
|
# 更新字段
|
||||||
update_data = outline_update.model_dump(exclude_unset=True)
|
update_data = outline_update.model_dump(exclude_unset=True)
|
||||||
@@ -214,6 +214,28 @@ async def update_outline(
|
|||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
logger.warning(f"大纲 {outline_id} 的structure字段格式错误,跳过更新")
|
logger.warning(f"大纲 {outline_id} 的structure字段格式错误,跳过更新")
|
||||||
|
|
||||||
|
# 🔧 传统模式(one-to-one):同步更新关联章节的标题
|
||||||
|
if 'title' in update_data and project.outline_mode == 'one-to-one':
|
||||||
|
try:
|
||||||
|
# 查找对应的章节(通过chapter_number匹配order_index)
|
||||||
|
chapter_result = await db.execute(
|
||||||
|
select(Chapter).where(
|
||||||
|
Chapter.project_id == outline.project_id,
|
||||||
|
Chapter.chapter_number == outline.order_index
|
||||||
|
)
|
||||||
|
)
|
||||||
|
chapter = chapter_result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if chapter:
|
||||||
|
# 同步更新章节标题
|
||||||
|
chapter.title = outline.title
|
||||||
|
logger.info(f"一对一模式:同步更新章节 {chapter.id} 的标题为 '{outline.title}'")
|
||||||
|
else:
|
||||||
|
logger.debug(f"一对一模式:未找到对应的章节(chapter_number={outline.order_index})")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"同步更新章节标题失败: {str(e)}")
|
||||||
|
# 不阻断大纲更新流程,仅记录错误
|
||||||
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
await db.refresh(outline)
|
await db.refresh(outline)
|
||||||
return outline
|
return outline
|
||||||
@@ -485,9 +507,21 @@ async def _generate_new_outline(
|
|||||||
# 解析响应
|
# 解析响应
|
||||||
outline_data = _parse_ai_response(ai_content)
|
outline_data = _parse_ai_response(ai_content)
|
||||||
|
|
||||||
# 全新生成模式:必须删除旧大纲(章节不自动删除,由用户手动管理)
|
# 全新生成模式:删除旧大纲和关联的所有章节
|
||||||
# 注意:这是"new"模式的核心逻辑,应该始终删除旧数据
|
logger.info(f"全新生成:删除项目 {project.id} 的旧大纲和章节")
|
||||||
logger.info(f"删除项目 {project.id} 的旧大纲")
|
|
||||||
|
from sqlalchemy import delete as sql_delete
|
||||||
|
|
||||||
|
# 无论是一对一还是一对多模式,都删除所有项目的章节
|
||||||
|
# 一对一模式:通过 chapter_number 关联
|
||||||
|
# 一对多模式:通过 outline_id 关联
|
||||||
|
delete_result = await db.execute(
|
||||||
|
sql_delete(Chapter).where(Chapter.project_id == project.id)
|
||||||
|
)
|
||||||
|
deleted_chapters_count = delete_result.rowcount
|
||||||
|
logger.info(f"全新生成:删除了 {deleted_chapters_count} 个旧章节")
|
||||||
|
|
||||||
|
# 删除旧大纲
|
||||||
await db.execute(
|
await db.execute(
|
||||||
delete(Outline).where(Outline.project_id == project.id)
|
delete(Outline).where(Outline.project_id == project.id)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,24 +22,12 @@ router = APIRouter(prefix="/writing-styles", tags=["writing-styles"])
|
|||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def verify_project_access(project_id: str, user_id: str, db: AsyncSession) -> Project:
|
def get_current_user_id(request: Request) -> str:
|
||||||
"""验证用户是否有权访问指定项目"""
|
"""获取当前登录用户ID"""
|
||||||
|
user_id = getattr(request.state, 'user_id', None)
|
||||||
if not user_id:
|
if not user_id:
|
||||||
raise HTTPException(status_code=401, detail="未登录")
|
raise HTTPException(status_code=401, detail="未登录")
|
||||||
|
return user_id
|
||||||
result = await db.execute(
|
|
||||||
select(Project).where(
|
|
||||||
Project.id == project_id,
|
|
||||||
Project.user_id == user_id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
project = result.scalar_one_or_none()
|
|
||||||
|
|
||||||
if not project:
|
|
||||||
logger.warning(f"项目访问被拒绝: project_id={project_id}, user_id={user_id}")
|
|
||||||
raise HTTPException(status_code=404, detail="项目不存在或无权访问")
|
|
||||||
|
|
||||||
return project
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/presets/list", response_model=List[dict])
|
@router.get("/presets/list", response_model=List[dict])
|
||||||
@@ -68,14 +56,13 @@ async def create_writing_style(
|
|||||||
db: AsyncSession = Depends(get_db)
|
db: AsyncSession = Depends(get_db)
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
创建新的写作风格
|
创建新的写作风格(用户级别)
|
||||||
|
|
||||||
- **基于预设创建**:提供 preset_id,系统会自动填充预设内容
|
- **基于预设创建**:提供 preset_id,系统会自动填充预设内容
|
||||||
- **完全自定义**:不提供 preset_id,需要手动填写所有字段
|
- **完全自定义**:不提供 preset_id,需要手动填写所有字段
|
||||||
"""
|
"""
|
||||||
# 验证用户权限
|
# 获取当前用户ID
|
||||||
user_id = getattr(request.state, 'user_id', None)
|
user_id = get_current_user_id(request)
|
||||||
await verify_project_access(style_data.project_id, user_id, db)
|
|
||||||
|
|
||||||
# 如果基于预设创建,获取预设内容
|
# 如果基于预设创建,获取预设内容
|
||||||
if style_data.preset_id:
|
if style_data.preset_id:
|
||||||
@@ -98,16 +85,16 @@ async def create_writing_style(
|
|||||||
detail="name 和 prompt_content 是必填字段"
|
detail="name 和 prompt_content 是必填字段"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 获取当前最大 order_index
|
# 获取当前用户的最大 order_index
|
||||||
count_result = await db.execute(
|
count_result = await db.execute(
|
||||||
select(func.count(WritingStyle.id))
|
select(func.count(WritingStyle.id))
|
||||||
.where(WritingStyle.project_id == style_data.project_id)
|
.where(WritingStyle.user_id == user_id)
|
||||||
)
|
)
|
||||||
max_order = count_result.scalar_one()
|
max_order = count_result.scalar_one()
|
||||||
|
|
||||||
# 创建风格记录
|
# 创建风格记录
|
||||||
new_style = WritingStyle(
|
new_style = WritingStyle(
|
||||||
project_id=style_data.project_id,
|
user_id=user_id,
|
||||||
name=style_data.name,
|
name=style_data.name,
|
||||||
style_type=style_data.style_type or ("preset" if style_data.preset_id else "custom"),
|
style_type=style_data.style_type or ("preset" if style_data.preset_id else "custom"),
|
||||||
preset_id=style_data.preset_id,
|
preset_id=style_data.preset_id,
|
||||||
@@ -123,7 +110,7 @@ async def create_writing_style(
|
|||||||
# 返回包含 is_default 字段的字典(新创建的风格默认不是默认风格)
|
# 返回包含 is_default 字段的字典(新创建的风格默认不是默认风格)
|
||||||
return {
|
return {
|
||||||
"id": new_style.id,
|
"id": new_style.id,
|
||||||
"project_id": new_style.project_id,
|
"user_id": new_style.user_id,
|
||||||
"name": new_style.name,
|
"name": new_style.name,
|
||||||
"style_type": new_style.style_type,
|
"style_type": new_style.style_type,
|
||||||
"preset_id": new_style.preset_id,
|
"preset_id": new_style.preset_id,
|
||||||
@@ -136,6 +123,60 @@ async def create_writing_style(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/user", response_model=WritingStyleListResponse)
|
||||||
|
async def get_user_styles(
|
||||||
|
request: Request,
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
获取用户的所有可用写作风格
|
||||||
|
|
||||||
|
返回:全局预设风格 + 该用户的自定义风格
|
||||||
|
按 order_index 排序
|
||||||
|
"""
|
||||||
|
# 获取当前用户ID
|
||||||
|
user_id = get_current_user_id(request)
|
||||||
|
|
||||||
|
# 获取全局预设风格(user_id 为 NULL)
|
||||||
|
result = await db.execute(
|
||||||
|
select(WritingStyle)
|
||||||
|
.where(WritingStyle.user_id.is_(None))
|
||||||
|
.order_by(WritingStyle.order_index)
|
||||||
|
)
|
||||||
|
preset_styles = list(result.scalars().all())
|
||||||
|
|
||||||
|
# 获取用户自定义风格
|
||||||
|
result = await db.execute(
|
||||||
|
select(WritingStyle)
|
||||||
|
.where(WritingStyle.user_id == user_id)
|
||||||
|
.order_by(WritingStyle.order_index)
|
||||||
|
)
|
||||||
|
custom_styles = list(result.scalars().all())
|
||||||
|
|
||||||
|
# 合并:预设风格 + 自定义风格
|
||||||
|
all_styles = preset_styles + custom_styles
|
||||||
|
|
||||||
|
# 转换为响应格式
|
||||||
|
styles_with_default = []
|
||||||
|
for style in all_styles:
|
||||||
|
style_dict = {
|
||||||
|
"id": style.id,
|
||||||
|
"user_id": style.user_id,
|
||||||
|
"name": style.name,
|
||||||
|
"style_type": style.style_type,
|
||||||
|
"preset_id": style.preset_id,
|
||||||
|
"description": style.description,
|
||||||
|
"prompt_content": style.prompt_content,
|
||||||
|
"order_index": style.order_index,
|
||||||
|
"created_at": style.created_at,
|
||||||
|
"updated_at": style.updated_at,
|
||||||
|
"is_default": False # 用户级别不再需要默认风格标记
|
||||||
|
}
|
||||||
|
styles_with_default.append(style_dict)
|
||||||
|
|
||||||
|
return {"styles": styles_with_default, "total": len(styles_with_default)}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/project/{project_id}", response_model=WritingStyleListResponse)
|
@router.get("/project/{project_id}", response_model=WritingStyleListResponse)
|
||||||
async def get_project_styles(
|
async def get_project_styles(
|
||||||
project_id: str,
|
project_id: str,
|
||||||
@@ -143,14 +184,24 @@ async def get_project_styles(
|
|||||||
db: AsyncSession = Depends(get_db)
|
db: AsyncSession = Depends(get_db)
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
获取项目的所有可用写作风格
|
获取项目可用的所有写作风格(保留用于向后兼容)
|
||||||
|
|
||||||
返回:全局预设风格 + 该项目的自定义风格
|
返回:全局预设风格 + 该用户的自定义风格
|
||||||
按 order_index 排序,并标记哪个是当前项目的默认风格
|
按 order_index 排序,并标记哪个是当前项目的默认风格
|
||||||
"""
|
"""
|
||||||
# 验证用户权限
|
# 获取当前用户ID
|
||||||
user_id = getattr(request.state, 'user_id', None)
|
user_id = get_current_user_id(request)
|
||||||
await verify_project_access(project_id, user_id, db)
|
|
||||||
|
# 验证项目访问权限
|
||||||
|
result = await db.execute(
|
||||||
|
select(Project).where(
|
||||||
|
Project.id == project_id,
|
||||||
|
Project.user_id == user_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
project = result.scalar_one_or_none()
|
||||||
|
if not project:
|
||||||
|
raise HTTPException(status_code=404, detail="项目不存在或无权访问")
|
||||||
|
|
||||||
# 获取该项目的默认风格ID
|
# 获取该项目的默认风格ID
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
@@ -159,18 +210,18 @@ async def get_project_styles(
|
|||||||
)
|
)
|
||||||
default_style_id = result.scalar_one_or_none()
|
default_style_id = result.scalar_one_or_none()
|
||||||
|
|
||||||
# 获取全局预设风格(project_id 为 NULL)
|
# 获取全局预设风格(user_id 为 NULL)
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(WritingStyle)
|
select(WritingStyle)
|
||||||
.where(WritingStyle.project_id.is_(None))
|
.where(WritingStyle.user_id.is_(None))
|
||||||
.order_by(WritingStyle.order_index)
|
.order_by(WritingStyle.order_index)
|
||||||
)
|
)
|
||||||
preset_styles = list(result.scalars().all())
|
preset_styles = list(result.scalars().all())
|
||||||
|
|
||||||
# 获取项目自定义风格
|
# 获取用户自定义风格
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(WritingStyle)
|
select(WritingStyle)
|
||||||
.where(WritingStyle.project_id == project_id)
|
.where(WritingStyle.user_id == user_id)
|
||||||
.order_by(WritingStyle.order_index)
|
.order_by(WritingStyle.order_index)
|
||||||
)
|
)
|
||||||
custom_styles = list(result.scalars().all())
|
custom_styles = list(result.scalars().all())
|
||||||
@@ -183,7 +234,7 @@ async def get_project_styles(
|
|||||||
for style in all_styles:
|
for style in all_styles:
|
||||||
style_dict = {
|
style_dict = {
|
||||||
"id": style.id,
|
"id": style.id,
|
||||||
"project_id": style.project_id,
|
"user_id": style.user_id,
|
||||||
"name": style.name,
|
"name": style.name,
|
||||||
"style_type": style.style_type,
|
"style_type": style.style_type,
|
||||||
"preset_id": style.preset_id,
|
"preset_id": style.preset_id,
|
||||||
@@ -221,7 +272,7 @@ async def get_writing_style(
|
|||||||
# 返回包含 is_default 字段的字典
|
# 返回包含 is_default 字段的字典
|
||||||
return {
|
return {
|
||||||
"id": style.id,
|
"id": style.id,
|
||||||
"project_id": style.project_id,
|
"user_id": style.user_id,
|
||||||
"name": style.name,
|
"name": style.name,
|
||||||
"style_type": style.style_type,
|
"style_type": style.style_type,
|
||||||
"preset_id": style.preset_id,
|
"preset_id": style.preset_id,
|
||||||
@@ -247,6 +298,9 @@ async def update_writing_style(
|
|||||||
- 只能修改自定义风格
|
- 只能修改自定义风格
|
||||||
- 不能修改全局预设风格
|
- 不能修改全局预设风格
|
||||||
"""
|
"""
|
||||||
|
# 获取当前用户ID
|
||||||
|
user_id = get_current_user_id(request)
|
||||||
|
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(WritingStyle).where(WritingStyle.id == style_id)
|
select(WritingStyle).where(WritingStyle.id == style_id)
|
||||||
)
|
)
|
||||||
@@ -255,12 +309,12 @@ async def update_writing_style(
|
|||||||
raise HTTPException(status_code=404, detail="写作风格不存在")
|
raise HTTPException(status_code=404, detail="写作风格不存在")
|
||||||
|
|
||||||
# 检查是否为全局预设风格(不允许修改)
|
# 检查是否为全局预设风格(不允许修改)
|
||||||
if style.project_id is None:
|
if style.user_id is None:
|
||||||
raise HTTPException(status_code=403, detail="不能修改全局预设风格,只能修改自定义风格")
|
raise HTTPException(status_code=403, detail="不能修改全局预设风格,只能修改自定义风格")
|
||||||
|
|
||||||
# 验证用户权限
|
# 验证用户权限(只能修改自己的风格)
|
||||||
user_id = getattr(request.state, 'user_id', None)
|
if style.user_id != user_id:
|
||||||
await verify_project_access(style.project_id, user_id, db)
|
raise HTTPException(status_code=403, detail="无权修改其他用户的风格")
|
||||||
|
|
||||||
# 更新字段
|
# 更新字段
|
||||||
update_data = style_data.model_dump(exclude_unset=True)
|
update_data = style_data.model_dump(exclude_unset=True)
|
||||||
@@ -284,7 +338,7 @@ async def update_writing_style(
|
|||||||
# 返回包含 is_default 字段的字典
|
# 返回包含 is_default 字段的字典
|
||||||
return {
|
return {
|
||||||
"id": style.id,
|
"id": style.id,
|
||||||
"project_id": style.project_id,
|
"user_id": style.user_id,
|
||||||
"name": style.name,
|
"name": style.name,
|
||||||
"style_type": style.style_type,
|
"style_type": style.style_type,
|
||||||
"preset_id": style.preset_id,
|
"preset_id": style.preset_id,
|
||||||
@@ -311,6 +365,9 @@ async def delete_writing_style(
|
|||||||
- 不能删除默认风格(必须先设置其他风格为默认)
|
- 不能删除默认风格(必须先设置其他风格为默认)
|
||||||
- 删除后无法恢复
|
- 删除后无法恢复
|
||||||
"""
|
"""
|
||||||
|
# 获取当前用户ID
|
||||||
|
user_id = get_current_user_id(request)
|
||||||
|
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(WritingStyle).where(WritingStyle.id == style_id)
|
select(WritingStyle).where(WritingStyle.id == style_id)
|
||||||
)
|
)
|
||||||
@@ -319,12 +376,12 @@ async def delete_writing_style(
|
|||||||
raise HTTPException(status_code=404, detail="写作风格不存在")
|
raise HTTPException(status_code=404, detail="写作风格不存在")
|
||||||
|
|
||||||
# 检查是否为全局预设风格(不允许删除)
|
# 检查是否为全局预设风格(不允许删除)
|
||||||
if style.project_id is None:
|
if style.user_id is None:
|
||||||
raise HTTPException(status_code=403, detail="不能删除全局预设风格,只能删除自定义风格")
|
raise HTTPException(status_code=403, detail="不能删除全局预设风格,只能删除自定义风格")
|
||||||
|
|
||||||
# 验证用户权限
|
# 验证用户权限(只能删除自己的风格)
|
||||||
user_id = getattr(request.state, 'user_id', None)
|
if style.user_id != user_id:
|
||||||
await verify_project_access(style.project_id, user_id, db)
|
raise HTTPException(status_code=403, detail="无权删除其他用户的风格")
|
||||||
|
|
||||||
# 检查是否有项目将其设置为默认风格
|
# 检查是否有项目将其设置为默认风格
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
@@ -362,9 +419,19 @@ async def set_default_style(
|
|||||||
"""
|
"""
|
||||||
project_id = request_data.project_id
|
project_id = request_data.project_id
|
||||||
|
|
||||||
# 验证用户权限
|
# 获取当前用户ID
|
||||||
user_id = getattr(request.state, 'user_id', None)
|
user_id = get_current_user_id(request)
|
||||||
await verify_project_access(project_id, user_id, db)
|
|
||||||
|
# 验证项目访问权限
|
||||||
|
result = await db.execute(
|
||||||
|
select(Project).where(
|
||||||
|
Project.id == project_id,
|
||||||
|
Project.user_id == user_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
project = result.scalar_one_or_none()
|
||||||
|
if not project:
|
||||||
|
raise HTTPException(status_code=404, detail="项目不存在或无权访问")
|
||||||
|
|
||||||
# 验证风格是否存在
|
# 验证风格是否存在
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
@@ -374,9 +441,9 @@ async def set_default_style(
|
|||||||
if not style:
|
if not style:
|
||||||
raise HTTPException(status_code=404, detail="写作风格不存在")
|
raise HTTPException(status_code=404, detail="写作风格不存在")
|
||||||
|
|
||||||
# 验证风格是否属于该项目(自定义风格)或是全局预设风格
|
# 验证风格是否属于该用户(自定义风格)或是全局预设风格
|
||||||
if style.project_id is not None and style.project_id != project_id:
|
if style.user_id is not None and style.user_id != user_id:
|
||||||
raise HTTPException(status_code=403, detail="无权操作其他项目的风格")
|
raise HTTPException(status_code=403, detail="无权操作其他用户的风格")
|
||||||
|
|
||||||
# 使用 UPSERT 逻辑:先删除该项目的旧默认风格记录,再插入新的
|
# 使用 UPSERT 逻辑:先删除该项目的旧默认风格记录,再插入新的
|
||||||
await db.execute(
|
await db.execute(
|
||||||
@@ -411,9 +478,5 @@ async def initialize_default_styles(
|
|||||||
新架构下,预设风格是全局的,不需要为每个项目单独初始化
|
新架构下,预设风格是全局的,不需要为每个项目单独初始化
|
||||||
该接口保留用于兼容性,直接返回项目可用的所有风格
|
该接口保留用于兼容性,直接返回项目可用的所有风格
|
||||||
"""
|
"""
|
||||||
# 验证用户权限
|
# 直接返回项目可用的所有风格(全局预设 + 用户自定义)
|
||||||
user_id = getattr(request.state, 'user_id', None)
|
|
||||||
await verify_project_access(project_id, user_id, db)
|
|
||||||
|
|
||||||
# 直接返回项目可用的所有风格(全局预设 + 项目自定义)
|
|
||||||
return await get_project_styles(project_id, request, db)
|
return await get_project_styles(project_id, request, db)
|
||||||
@@ -9,7 +9,7 @@ class WritingStyle(Base):
|
|||||||
__tablename__ = "writing_styles"
|
__tablename__ = "writing_styles"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
project_id = Column(String(36), ForeignKey("projects.id", ondelete="CASCADE"), nullable=True, comment="所属项目ID(NULL表示全局预设风格)")
|
user_id = Column(String(255), ForeignKey("users.user_id", ondelete="CASCADE"), nullable=True, comment="所属用户ID(NULL表示全局预设风格)")
|
||||||
name = Column(String(100), nullable=False, comment="风格名称")
|
name = Column(String(100), nullable=False, comment="风格名称")
|
||||||
style_type = Column(String(50), nullable=False, comment="风格类型:preset/custom")
|
style_type = Column(String(50), nullable=False, comment="风格类型:preset/custom")
|
||||||
preset_id = Column(String(50), comment="预设风格ID:natural/classical/modern等")
|
preset_id = Column(String(50), comment="预设风格ID:natural/classical/modern等")
|
||||||
@@ -20,4 +20,4 @@ class WritingStyle(Base):
|
|||||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间")
|
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<WritingStyle(id={self.id}, name={self.name}, project_id={self.project_id})>"
|
return f"<WritingStyle(id={self.id}, name={self.name}, user_id={self.user_id})>"
|
||||||
@@ -13,9 +13,13 @@ class WritingStyleBase(BaseModel):
|
|||||||
prompt_content: str = Field(..., description="风格提示词内容")
|
prompt_content: str = Field(..., description="风格提示词内容")
|
||||||
|
|
||||||
|
|
||||||
class WritingStyleCreate(WritingStyleBase):
|
class WritingStyleCreate(BaseModel):
|
||||||
"""创建写作风格(仅用于创建项目自定义风格)"""
|
"""创建写作风格(仅用于创建用户自定义风格)"""
|
||||||
project_id: str = Field(..., description="所属项目ID")
|
name: str = Field(..., description="风格名称")
|
||||||
|
style_type: Optional[str] = Field(None, description="风格类型:preset/custom")
|
||||||
|
preset_id: Optional[str] = Field(None, description="预设风格ID")
|
||||||
|
description: Optional[str] = Field(None, description="风格描述")
|
||||||
|
prompt_content: str = Field(..., description="风格提示词内容")
|
||||||
|
|
||||||
|
|
||||||
class WritingStyleUpdate(BaseModel):
|
class WritingStyleUpdate(BaseModel):
|
||||||
@@ -33,7 +37,7 @@ class SetDefaultStyleRequest(BaseModel):
|
|||||||
class WritingStyleResponse(BaseModel):
|
class WritingStyleResponse(BaseModel):
|
||||||
"""写作风格响应"""
|
"""写作风格响应"""
|
||||||
id: int
|
id: int
|
||||||
project_id: Optional[str] = None # NULL 表示全局预设风格
|
user_id: Optional[str] = None # NULL 表示全局预设风格
|
||||||
name: str
|
name: str
|
||||||
style_type: str
|
style_type: str
|
||||||
preset_id: Optional[str] = None
|
preset_id: Optional[str] = None
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ class ChapterRegenerator:
|
|||||||
chapter: Chapter,
|
chapter: Chapter,
|
||||||
analysis: Optional[PlotAnalysis],
|
analysis: Optional[PlotAnalysis],
|
||||||
regenerate_request: ChapterRegenerateRequest,
|
regenerate_request: ChapterRegenerateRequest,
|
||||||
project_context: Dict[str, Any]
|
project_context: Dict[str, Any],
|
||||||
|
style_content: str = ""
|
||||||
) -> AsyncGenerator[Dict[str, Any], None]:
|
) -> AsyncGenerator[Dict[str, Any], None]:
|
||||||
"""
|
"""
|
||||||
根据反馈重新生成章节(流式)
|
根据反馈重新生成章节(流式)
|
||||||
@@ -55,7 +56,8 @@ class ChapterRegenerator:
|
|||||||
chapter=chapter,
|
chapter=chapter,
|
||||||
modification_instructions=modification_instructions,
|
modification_instructions=modification_instructions,
|
||||||
project_context=project_context,
|
project_context=project_context,
|
||||||
regenerate_request=regenerate_request
|
regenerate_request=regenerate_request,
|
||||||
|
style_content=style_content
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"🎯 提示词构建完成,开始AI生成")
|
logger.info(f"🎯 提示词构建完成,开始AI生成")
|
||||||
@@ -161,7 +163,8 @@ class ChapterRegenerator:
|
|||||||
chapter: Chapter,
|
chapter: Chapter,
|
||||||
modification_instructions: str,
|
modification_instructions: str,
|
||||||
project_context: Dict[str, Any],
|
project_context: Dict[str, Any],
|
||||||
regenerate_request: ChapterRegenerateRequest
|
regenerate_request: ChapterRegenerateRequest,
|
||||||
|
style_content: str = ""
|
||||||
) -> str:
|
) -> str:
|
||||||
"""构建完整的重新生成提示词"""
|
"""构建完整的重新生成提示词"""
|
||||||
|
|
||||||
@@ -235,6 +238,17 @@ class ChapterRegenerator:
|
|||||||
|
|
||||||
{project_context['previous_context']}
|
{project_context['previous_context']}
|
||||||
|
|
||||||
|
---
|
||||||
|
""")
|
||||||
|
|
||||||
|
# 写作风格要求(如果提供)
|
||||||
|
if style_content:
|
||||||
|
prompt_parts.append(f"""## 🎨 写作风格要求
|
||||||
|
|
||||||
|
{style_content}
|
||||||
|
|
||||||
|
请在重新创作时严格遵循上述写作风格。
|
||||||
|
|
||||||
---
|
---
|
||||||
""")
|
""")
|
||||||
|
|
||||||
@@ -246,6 +260,7 @@ class ChapterRegenerator:
|
|||||||
3. **提升质量**:在节奏、情感、描写等方面明显优于原版
|
3. **提升质量**:在节奏、情感、描写等方面明显优于原版
|
||||||
4. **保留精华**:保持原章节中优秀的部分和关键情节
|
4. **保留精华**:保持原章节中优秀的部分和关键情节
|
||||||
5. **字数控制**:目标字数约{regenerate_request.target_word_count}字(可适当浮动±20%)
|
5. **字数控制**:目标字数约{regenerate_request.target_word_count}字(可适当浮动±20%)
|
||||||
|
{f'6. **风格一致**:严格按照上述写作风格进行创作' if style_content else ''}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ class ImportExportService:
|
|||||||
"chapter_count": project.chapter_count,
|
"chapter_count": project.chapter_count,
|
||||||
"narrative_perspective": project.narrative_perspective,
|
"narrative_perspective": project.narrative_perspective,
|
||||||
"character_count": project.character_count,
|
"character_count": project.character_count,
|
||||||
|
"outline_mode": project.outline_mode,
|
||||||
"user_id": project.user_id,
|
"user_id": project.user_id,
|
||||||
"created_at": project.created_at.isoformat() if project.created_at else None,
|
"created_at": project.created_at.isoformat() if project.created_at else None,
|
||||||
}
|
}
|
||||||
@@ -496,6 +497,7 @@ class ImportExportService:
|
|||||||
chapter_count=project_data.get("chapter_count"),
|
chapter_count=project_data.get("chapter_count"),
|
||||||
narrative_perspective=project_data.get("narrative_perspective"),
|
narrative_perspective=project_data.get("narrative_perspective"),
|
||||||
character_count=project_data.get("character_count"),
|
character_count=project_data.get("character_count"),
|
||||||
|
outline_mode=project_data.get("outline_mode", "one-to-many"), # ✅ 导入大纲模式,默认为一对多
|
||||||
current_words=project_data.get("current_words", 0), # 保留原项目的字数
|
current_words=project_data.get("current_words", 0), # 保留原项目的字数
|
||||||
wizard_step=4, # 导入的项目设置为向导完成状态
|
wizard_step=4, # 导入的项目设置为向导完成状态
|
||||||
wizard_status="completed" # 标记向导已完成
|
wizard_status="completed" # 标记向导已完成
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
-- 迁移写作风格从项目级别到用户级别
|
||||||
|
-- 将 writing_styles 表的 project_id 字段改为 user_id
|
||||||
|
|
||||||
|
-- 步骤1: 添加新的 user_id 字段
|
||||||
|
ALTER TABLE writing_styles ADD COLUMN user_id VARCHAR(255);
|
||||||
|
|
||||||
|
-- 步骤2: 将现有数据从 project_id 映射到 user_id
|
||||||
|
-- 通过 projects 表关联,将项目的用户ID填充到风格的 user_id
|
||||||
|
UPDATE writing_styles ws
|
||||||
|
SET user_id = (
|
||||||
|
SELECT p.user_id
|
||||||
|
FROM projects p
|
||||||
|
WHERE p.id = ws.project_id
|
||||||
|
)
|
||||||
|
WHERE ws.project_id IS NOT NULL;
|
||||||
|
|
||||||
|
-- 步骤3: 添加外键约束
|
||||||
|
ALTER TABLE writing_styles
|
||||||
|
ADD CONSTRAINT fk_writing_styles_user
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
-- 步骤4: 删除旧的 project_id 外键约束
|
||||||
|
ALTER TABLE writing_styles DROP CONSTRAINT IF EXISTS writing_styles_project_id_fkey;
|
||||||
|
|
||||||
|
-- 步骤5: 删除 project_id 列
|
||||||
|
ALTER TABLE writing_styles DROP COLUMN project_id;
|
||||||
|
|
||||||
|
-- 步骤6: 更新注释
|
||||||
|
COMMENT ON COLUMN writing_styles.user_id IS '所属用户ID(NULL表示全局预设风格)';
|
||||||
|
|
||||||
|
-- 验证迁移结果
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_styles,
|
||||||
|
COUNT(user_id) as user_styles,
|
||||||
|
COUNT(*) FILTER (WHERE user_id IS NULL) as preset_styles
|
||||||
|
FROM writing_styles;
|
||||||
@@ -1464,7 +1464,10 @@ export default function Outline() {
|
|||||||
title={
|
title={
|
||||||
<Space size="small" style={{ fontSize: isMobile ? 14 : 16, flexWrap: 'wrap' }}>
|
<Space size="small" style={{ fontSize: isMobile ? 14 : 16, flexWrap: 'wrap' }}>
|
||||||
<span style={{ color: '#1890ff', fontWeight: 'bold' }}>
|
<span style={{ color: '#1890ff', fontWeight: 'bold' }}>
|
||||||
第{item.order_index || '?'}卷
|
{currentProject?.outline_mode === 'one-to-one'
|
||||||
|
? `第${item.order_index || '?'}章`
|
||||||
|
: `第${item.order_index || '?'}卷`
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
<span>{item.title}</span>
|
<span>{item.title}</span>
|
||||||
{/* ✅ 新增:展开状态标识 - 仅在一对多模式显示 */}
|
{/* ✅ 新增:展开状态标识 - 仅在一对多模式显示 */}
|
||||||
|
|||||||
@@ -51,26 +51,29 @@ export default function WritingStyles() {
|
|||||||
xl: 6,
|
xl: 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载项目风格
|
// 加载风格列表 - 如果有项目则加载项目风格(包含默认标记),否则加载用户风格
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentProject?.id) {
|
loadStyles();
|
||||||
loadProjectStyles();
|
|
||||||
}
|
|
||||||
}, [currentProject?.id]);
|
}, [currentProject?.id]);
|
||||||
|
|
||||||
const loadProjectStyles = async () => {
|
const loadStyles = async () => {
|
||||||
if (!currentProject?.id) return;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await writingStyleApi.getProjectStyles(currentProject.id);
|
// 如果有当前项目,使用项目API获取(包含is_default标记)
|
||||||
// 对风格列表进行排序:默认风格优先,然后按原有顺序
|
// 否则使用用户API获取(所有风格的is_default都是false)
|
||||||
|
const response = currentProject?.id
|
||||||
|
? await writingStyleApi.getProjectStyles(currentProject.id)
|
||||||
|
: await writingStyleApi.getUserStyles();
|
||||||
|
|
||||||
|
// 排序:默认风格优先显示
|
||||||
const sortedStyles = (response.styles || []).sort((a, b) => {
|
const sortedStyles = (response.styles || []).sort((a, b) => {
|
||||||
// 默认风格排在前面
|
// 默认风格排在最前面
|
||||||
if (a.is_default && !b.is_default) return -1;
|
if (a.is_default && !b.is_default) return -1;
|
||||||
if (!a.is_default && b.is_default) return 1;
|
if (!a.is_default && b.is_default) return 1;
|
||||||
|
// 其他按原有顺序(order_index)
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
setStyles(sortedStyles);
|
setStyles(sortedStyles);
|
||||||
} catch {
|
} catch {
|
||||||
message.error('加载风格列表失败');
|
message.error('加载风格列表失败');
|
||||||
@@ -80,11 +83,8 @@ export default function WritingStyles() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCreate = async (values: { name: string; description?: string; prompt_content: string }) => {
|
const handleCreate = async (values: { name: string; description?: string; prompt_content: string }) => {
|
||||||
if (!currentProject?.id) return;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const createData: WritingStyleCreate = {
|
const createData: WritingStyleCreate = {
|
||||||
project_id: currentProject.id,
|
|
||||||
name: values.name,
|
name: values.name,
|
||||||
style_type: 'custom',
|
style_type: 'custom',
|
||||||
description: values.description,
|
description: values.description,
|
||||||
@@ -95,7 +95,7 @@ export default function WritingStyles() {
|
|||||||
message.success('创建成功');
|
message.success('创建成功');
|
||||||
setIsCreateModalOpen(false);
|
setIsCreateModalOpen(false);
|
||||||
createForm.resetFields();
|
createForm.resetFields();
|
||||||
await loadProjectStyles();
|
await loadStyles();
|
||||||
} catch {
|
} catch {
|
||||||
message.error('创建失败');
|
message.error('创建失败');
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ export default function WritingStyles() {
|
|||||||
setIsEditModalOpen(false);
|
setIsEditModalOpen(false);
|
||||||
editForm.resetFields();
|
editForm.resetFields();
|
||||||
setEditingStyle(null);
|
setEditingStyle(null);
|
||||||
await loadProjectStyles();
|
await loadStyles();
|
||||||
} catch {
|
} catch {
|
||||||
message.error('更新失败');
|
message.error('更新失败');
|
||||||
}
|
}
|
||||||
@@ -130,19 +130,22 @@ export default function WritingStyles() {
|
|||||||
try {
|
try {
|
||||||
await writingStyleApi.deleteStyle(styleId);
|
await writingStyleApi.deleteStyle(styleId);
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
await loadProjectStyles();
|
await loadStyles();
|
||||||
} catch {
|
} catch {
|
||||||
message.error('删除失败');
|
message.error('删除失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSetDefault = async (styleId: number) => {
|
const handleSetDefault = async (styleId: number) => {
|
||||||
if (!currentProject?.id) return;
|
if (!currentProject?.id) {
|
||||||
|
message.warning('请先选择项目');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await writingStyleApi.setDefaultStyle(styleId, currentProject.id);
|
await writingStyleApi.setDefaultStyle(styleId, currentProject.id);
|
||||||
message.success('设置默认风格成功');
|
message.success('设置默认风格成功');
|
||||||
await loadProjectStyles();
|
await loadStyles();
|
||||||
} catch {
|
} catch {
|
||||||
message.error('设置失败');
|
message.error('设置失败');
|
||||||
}
|
}
|
||||||
@@ -153,8 +156,6 @@ export default function WritingStyles() {
|
|||||||
setIsCreateModalOpen(true);
|
setIsCreateModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!currentProject) return null;
|
|
||||||
|
|
||||||
const getStyleTypeColor = (styleType: string) => {
|
const getStyleTypeColor = (styleType: string) => {
|
||||||
return styleType === 'preset' ? 'blue' : 'purple';
|
return styleType === 'preset' ? 'blue' : 'purple';
|
||||||
};
|
};
|
||||||
@@ -240,13 +241,13 @@ export default function WritingStyles() {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>,
|
</Tooltip>,
|
||||||
<Tooltip key="edit" title={style.project_id === null ? '预设风格不可编辑' : '编辑'}>
|
<Tooltip key="edit" title={style.user_id === null ? '预设风格不可编辑' : '编辑'}>
|
||||||
<EditOutlined
|
<EditOutlined
|
||||||
onClick={() => style.project_id !== null && handleEdit(style)}
|
onClick={() => style.user_id !== null && handleEdit(style)}
|
||||||
style={{
|
style={{
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
cursor: style.project_id === null ? 'not-allowed' : 'pointer',
|
cursor: style.user_id === null ? 'not-allowed' : 'pointer',
|
||||||
color: style.project_id === null ? '#ccc' : undefined
|
color: style.user_id === null ? '#ccc' : undefined
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>,
|
</Tooltip>,
|
||||||
@@ -257,20 +258,18 @@ export default function WritingStyles() {
|
|||||||
onConfirm={() => handleDelete(style.id)}
|
onConfirm={() => handleDelete(style.id)}
|
||||||
okText="确定"
|
okText="确定"
|
||||||
cancelText="取消"
|
cancelText="取消"
|
||||||
disabled={style.project_id === null || styles.length === 1}
|
disabled={style.user_id === null}
|
||||||
>
|
>
|
||||||
<Tooltip title={
|
<Tooltip title={
|
||||||
style.project_id === null
|
style.user_id === null
|
||||||
? '预设风格不可删除'
|
? '预设风格不可删除'
|
||||||
: styles.length === 1
|
|
||||||
? '至少保留一个风格'
|
|
||||||
: '删除'
|
: '删除'
|
||||||
}>
|
}>
|
||||||
<DeleteOutlined
|
<DeleteOutlined
|
||||||
style={{
|
style={{
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
color: (style.project_id === null || styles.length === 1) ? '#ccc' : undefined,
|
color: style.user_id === null ? '#ccc' : undefined,
|
||||||
cursor: (style.project_id === null || styles.length === 1) ? 'not-allowed' : 'pointer'
|
cursor: style.user_id === null ? 'not-allowed' : 'pointer'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -437,7 +437,11 @@ export const writingStyleApi = {
|
|||||||
getPresetStyles: () =>
|
getPresetStyles: () =>
|
||||||
api.get<unknown, PresetStyle[]>('/writing-styles/presets/list'),
|
api.get<unknown, PresetStyle[]>('/writing-styles/presets/list'),
|
||||||
|
|
||||||
// 获取项目的所有风格
|
// 获取用户的所有风格(新接口)
|
||||||
|
getUserStyles: () =>
|
||||||
|
api.get<unknown, WritingStyleListResponse>('/writing-styles/user'),
|
||||||
|
|
||||||
|
// 获取项目的所有风格(保留向后兼容)
|
||||||
getProjectStyles: (projectId: string) =>
|
getProjectStyles: (projectId: string) =>
|
||||||
api.get<unknown, WritingStyleListResponse>(`/writing-styles/project/${projectId}`),
|
api.get<unknown, WritingStyleListResponse>(`/writing-styles/project/${projectId}`),
|
||||||
|
|
||||||
|
|||||||
@@ -406,7 +406,7 @@ export interface ApiResponse<T> {
|
|||||||
// 写作风格类型定义
|
// 写作风格类型定义
|
||||||
export interface WritingStyle {
|
export interface WritingStyle {
|
||||||
id: number;
|
id: number;
|
||||||
project_id: string;
|
user_id: string | null; // NULL 表示全局预设风格
|
||||||
name: string;
|
name: string;
|
||||||
style_type: 'preset' | 'custom';
|
style_type: 'preset' | 'custom';
|
||||||
preset_id?: string;
|
preset_id?: string;
|
||||||
@@ -419,13 +419,11 @@ export interface WritingStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface WritingStyleCreate {
|
export interface WritingStyleCreate {
|
||||||
project_id: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
style_type: 'preset' | 'custom';
|
style_type?: 'preset' | 'custom';
|
||||||
preset_id?: string;
|
preset_id?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
prompt_content: string;
|
prompt_content: string;
|
||||||
is_default?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WritingStyleUpdate {
|
export interface WritingStyleUpdate {
|
||||||
|
|||||||
Reference in New Issue
Block a user