|
|
@@ -44,6 +44,7 @@ import { BookAnalysisCharacterSkillDialog } from "./book-analysis-character-skil
|
|
|
import { OutlineCreatorDialog } from "./outline-editor"
|
|
|
import { generateSkillsForCharacters } from "@/lib/novel/book-analysis/skill-generator"
|
|
|
import { selectCharacterCandidates } from "@/lib/novel/book-analysis/character-candidate-selection"
|
|
|
+import { loadBookAnalysisResult } from "@/lib/novel/book-analysis/result-loader"
|
|
|
|
|
|
interface StoryFrameworkSelectionData {
|
|
|
book: BookAnalysisLibraryBook
|
|
|
@@ -139,6 +140,7 @@ export function BookAnalysisView() {
|
|
|
const importRevisionBaselineRef = useRef({ projectPath: currentProject?.path ?? null, revision: importRevision })
|
|
|
const importInitializationSequenceRef = useRef(0)
|
|
|
const importInitializationTokenRef = useRef<{ sequence: number; projectPath: string } | null>(null)
|
|
|
+ const notifiedPipelineTaskIdsRef = useRef<Set<string>>(new Set())
|
|
|
|
|
|
// 角色识别 LLM 配置(feature/llm-character-recognizer)
|
|
|
const baseLlmConfig = useWikiStore((s) => s.llmConfig)
|
|
|
@@ -299,6 +301,44 @@ export function BookAnalysisView() {
|
|
|
}
|
|
|
}, [createAwaitingRangeTask, currentProject?.path, importBatches, importTasks])
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
+ if (!currentProject?.path) return
|
|
|
+ for (const task of pipelineTasks) {
|
|
|
+ if (task.status === "completed" && !notifiedPipelineTaskIdsRef.current.has(`${task.id}:completed`)) {
|
|
|
+ notifiedPipelineTaskIdsRef.current.add(`${task.id}:completed`)
|
|
|
+ void reloadLibraryState()
|
|
|
+ if (task.batchId) continue
|
|
|
+ if (task.selectedSkills.includes("characters") && task.selectedSkills.length === 1) {
|
|
|
+ toast.success("角色信息已提取完成,可选择角色生成 Skill。", {
|
|
|
+ persistent: true,
|
|
|
+ action: { label: "打开角色信息", onClick: handleOpenCharacterSkillSelection },
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ toast.success("拆书任务已完成,结果已更新。", {
|
|
|
+ persistent: true,
|
|
|
+ action: {
|
|
|
+ label: "查看结果",
|
|
|
+ onClick: async () => {
|
|
|
+ const loadedResult = await loadBookAnalysisResult(task.projectPath, task.bookId)
|
|
|
+ if (loadedResult) {
|
|
|
+ useBookAnalysisStore.getState().setCurrentResult(loadedResult)
|
|
|
+ }
|
|
|
+ setViewingResultPath(task.projectPath)
|
|
|
+ setShowResultViewer(true)
|
|
|
+ },
|
|
|
+ },
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else if (task.status === "failed" && !notifiedPipelineTaskIdsRef.current.has(`${task.id}:failed`)) {
|
|
|
+ notifiedPipelineTaskIdsRef.current.add(`${task.id}:failed`)
|
|
|
+ void reloadLibraryState()
|
|
|
+ if (!task.batchId) {
|
|
|
+ toast.error(`分析失败:${task.error || "未知错误"}`)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, [currentProject?.path, pipelineTasks, reloadLibraryState, handleOpenCharacterSkillSelection])
|
|
|
+
|
|
|
useEffect(() => {
|
|
|
const projectPath = currentProject?.path ?? null
|
|
|
const baseline = importRevisionBaselineRef.current
|
|
|
@@ -662,11 +702,33 @@ export function BookAnalysisView() {
|
|
|
setPipelineDialog(null)
|
|
|
void startPipelineTask(pipelineDialog.taskId).then(async () => {
|
|
|
await reloadLibraryState()
|
|
|
- if (selectedSkills.includes("characters")) {
|
|
|
- toast.success("角色信息已提取完成,可选择角色生成 Skill。", {
|
|
|
- persistent: true,
|
|
|
- action: { label: "打开角色信息", onClick: handleOpenCharacterSkillSelection },
|
|
|
- })
|
|
|
+ const completedTask = useBookAnalysisPipelineStore.getState().tasks.find(t => t.id === pipelineDialog.taskId)
|
|
|
+ if (completedTask?.status === "completed") {
|
|
|
+ if (selectedSkills.includes("characters")) {
|
|
|
+ toast.success("角色信息已提取完成,可选择角色生成 Skill。", {
|
|
|
+ persistent: true,
|
|
|
+ action: { label: "打开角色信息", onClick: handleOpenCharacterSkillSelection },
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ toast.success("拆书任务已完成,结果已更新。", {
|
|
|
+ persistent: true,
|
|
|
+ action: {
|
|
|
+ label: "查看结果",
|
|
|
+ onClick: async () => {
|
|
|
+ if (completedTask) {
|
|
|
+ const loadedResult = await loadBookAnalysisResult(completedTask.projectPath, completedTask.bookId)
|
|
|
+ if (loadedResult) {
|
|
|
+ useBookAnalysisStore.getState().setCurrentResult(loadedResult)
|
|
|
+ }
|
|
|
+ setViewingResultPath(completedTask.projectPath)
|
|
|
+ }
|
|
|
+ setShowResultViewer(true)
|
|
|
+ },
|
|
|
+ },
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else if (completedTask?.status === "failed") {
|
|
|
+ toast.error(`分析失败:${completedTask.error || "未知错误"}`)
|
|
|
}
|
|
|
}).catch((error) => {
|
|
|
toast.error(`分析失败:${error instanceof Error ? error.message : String(error)}`)
|