Skip to content

Commit 18fc9de

Browse files
committed
Improve AI workflows and chapter export
1 parent 728bc1d commit 18fc9de

24 files changed

Lines changed: 2731 additions & 295 deletions

.dockerignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ build/
1414

1515
# Node
1616
node_modules/
17+
**/node_modules/
1718
npm-debug.log*
1819
yarn-debug.log*
1920
yarn-error.log*
@@ -78,4 +79,4 @@ backend/.instance_id
7879
# Docker相关
7980
Dockerfile
8081
.dockerignore
81-
docker-compose*.yml
82+
docker-compose*.yml

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ env/
3737
*~
3838
.DS_Store
3939
.kilocode/
40+
.playwright-cli/
4041

4142
# Environment variables
4243
.env

backend/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ CORS_ORIGINS=["http://localhost:8000","http://127.0.0.1:8000"]
5353
# HTTP_PROXY=http://your-proxy:port
5454
# HTTPS_PROXY=http://your-proxy:port
5555
# NO_PROXY=localhost,127.0.0.1
56+
# 仅本地 Clash fake-ip DNS 场景需要开启;生产环境建议保持 false
57+
ALLOW_DNS_PROXY_FAKE_IP=false
5658

5759
# ==========================================
5860
# AI 服务配置(至少配置一个)

backend/app/api/chapters.py

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,23 @@ async def get_db_write_lock(user_id: str) -> Lock:
7575
return db_write_locks[user_id]
7676

7777

78+
def _is_gemini_prompt_blocked_error(error: Optional[str]) -> bool:
79+
if not error:
80+
return False
81+
markers = (
82+
"promptFeedback.blockReason=PROHIBITED_CONTENT",
83+
"Gemini 请求被拦截",
84+
)
85+
return any(marker in error for marker in markers)
86+
87+
88+
def _is_same_ai_service(left: AIService, right: AIService) -> bool:
89+
return (
90+
left.api_provider == right.api_provider
91+
and left.default_model == right.default_model
92+
)
93+
94+
7895
@router.post("", response_model=ChapterResponse, summary="创建章节")
7996
async def create_chapter(
8097
chapter: ChapterCreate,
@@ -969,11 +986,56 @@ async def on_retry_callback(attempt: int, max_retries: int, wait_time: int, erro
969986
on_retry=on_retry_callback,
970987
characters_info=characters_info
971988
)
989+
990+
analysis_error = getattr(analyzer, "last_error", None)
991+
992+
if not analysis_result and _is_gemini_prompt_blocked_error(analysis_error):
993+
fallback_ai_service = await get_user_ai_service_from_db_by_usage(
994+
user_id=user_id,
995+
db=db_session,
996+
usage="default"
997+
)
998+
999+
if not _is_same_ai_service(ai_service, fallback_ai_service):
1000+
logger.warning(
1001+
"⚠️ 章节分析专用 Gemini 模型被内容策略拦截,"
1002+
f"改用默认模型兜底: {fallback_ai_service.api_provider}/{fallback_ai_service.default_model}"
1003+
)
1004+
async with write_lock:
1005+
task.status = 'running'
1006+
task.progress = max(task.progress or 0, 40)
1007+
task.error_message = f"专用 Gemini 模型被拦截,正在使用默认模型兜底:{analysis_error[:150]}"
1008+
await db_session.commit()
1009+
1010+
fallback_analyzer = PlotAnalyzer(fallback_ai_service)
1011+
analysis_result = await fallback_analyzer.analyze_chapter(
1012+
chapter_number=chapter.chapter_number,
1013+
title=chapter.title,
1014+
content=chapter.content,
1015+
word_count=chapter.word_count or len(chapter.content),
1016+
user_id=user_id,
1017+
db=db_session,
1018+
max_retries=2,
1019+
existing_foreshadows=existing_foreshadows,
1020+
on_retry=on_retry_callback,
1021+
characters_info=characters_info
1022+
)
1023+
1024+
if not analysis_result:
1025+
fallback_error = getattr(fallback_analyzer, "last_error", None)
1026+
analysis_error = (
1027+
f"{analysis_error};默认模型兜底失败: "
1028+
f"{fallback_error or '未知错误'}"
1029+
)
1030+
else:
1031+
analysis_error = None
1032+
else:
1033+
logger.warning("⚠️ 默认模型与章节分析模型相同,跳过兜底重试")
9721034

9731035
if not analysis_result:
9741036
async with write_lock:
9751037
task.status = 'failed'
976-
task.error_message = 'AI分析失败,请检查日志'
1038+
task.error_message = (analysis_error or 'AI分析失败,请检查日志')[:500]
9771039
task.completed_at = datetime.now()
9781040
await db_session.commit()
9791041
logger.error(f"❌ AI分析失败: {chapter_id}")
@@ -1266,6 +1328,7 @@ async def on_retry_callback(attempt: int, max_retries: int, wait_time: int, erro
12661328
async with write_lock:
12671329
task.progress = 100
12681330
task.status = 'completed'
1331+
task.error_message = None
12691332
task.completed_at = datetime.now()
12701333
await db_session.commit()
12711334
update_success = True
@@ -3281,9 +3344,14 @@ async def execute_batch_generation_in_order(
32813344

32823345
# 直接根据返回值判断
32833346
if not analysis_result:
3284-
last_analysis_error = "分析函数返回失败"
3347+
try:
3348+
await db_session.refresh(analysis_task)
3349+
last_analysis_error = analysis_task.error_message or "分析函数返回失败"
3350+
except Exception as refresh_error:
3351+
logger.warning(f"⚠️ 刷新分析任务错误信息失败: {refresh_error}")
3352+
last_analysis_error = "分析函数返回失败"
32853353
logger.error(f"❌ 章节分析失败: 第{chapter.chapter_number}章")
3286-
raise Exception(f"章节分析失败")
3354+
raise Exception(last_analysis_error)
32873355

32883356
# 分析成功
32893357
analysis_success = True
@@ -4486,4 +4554,3 @@ async def apply_partial_regenerate(
44864554
"old_word_count": old_word_count,
44874555
"message": "局部重写已应用"
44884556
}
4489-

0 commit comments

Comments
 (0)