|
@@ -4,7 +4,6 @@ import { useState, useCallback, useEffect } from 'react';
|
|
|
import { X, Check } from 'lucide-react';
|
|
import { X, Check } from 'lucide-react';
|
|
|
import { WorldStateFile, WorldLoreFile, WritingConstraint, AntiHomogenizationStrategy, EventNode, ChapterAllocation } from '@/app/lib/novel-types';
|
|
import { WorldStateFile, WorldLoreFile, WritingConstraint, AntiHomogenizationStrategy, EventNode, ChapterAllocation } from '@/app/lib/novel-types';
|
|
|
import Step0ControlCard from './pipeline/Step0ControlCard';
|
|
import Step0ControlCard from './pipeline/Step0ControlCard';
|
|
|
-import Step1PlotSelection from './pipeline/Step1PlotSelection';
|
|
|
|
|
import Step2EmbedHooks from './pipeline/Step2EmbedHooks';
|
|
import Step2EmbedHooks from './pipeline/Step2EmbedHooks';
|
|
|
import Step3ContentEditor from './pipeline/Step4ContentEditor';
|
|
import Step3ContentEditor from './pipeline/Step4ContentEditor';
|
|
|
import Step4ReviewSubmit from './pipeline/Step5ReviewSubmit';
|
|
import Step4ReviewSubmit from './pipeline/Step5ReviewSubmit';
|
|
@@ -35,25 +34,22 @@ interface ChapterDraftPipelineProps {
|
|
|
onCancel: () => void;
|
|
onCancel: () => void;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-type PipelineStep = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
|
|
|
|
|
+type PipelineStep = 0 | 1 | 2 | 3 | 4 | 5;
|
|
|
|
|
|
|
|
interface PipelineState {
|
|
interface PipelineState {
|
|
|
controlCard: ControlCardData | null;
|
|
controlCard: ControlCardData | null;
|
|
|
- selectedPlot: string;
|
|
|
|
|
- customNote: string;
|
|
|
|
|
embedHooks: { id: string; type: string; description: string; embedMethod: string }[];
|
|
embedHooks: { id: string; type: string; description: string; embedMethod: string }[];
|
|
|
manualHooks: string[];
|
|
manualHooks: string[];
|
|
|
chapterContent: string;
|
|
chapterContent: string;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const STEPS = [
|
|
const STEPS = [
|
|
|
- { id: 0, label: '控制卡' },
|
|
|
|
|
- { id: 1, label: '选择剧情' },
|
|
|
|
|
- { id: 2, label: '埋入钩子' },
|
|
|
|
|
- { id: 3, label: '生成内容' },
|
|
|
|
|
- { id: 4, label: '审核提交' },
|
|
|
|
|
- { id: 5, label: '连续性检查' },
|
|
|
|
|
- { id: 6, label: '读者反馈' },
|
|
|
|
|
|
|
+ { id: 0, label: '控制卡+剧情' },
|
|
|
|
|
+ { id: 1, label: '埋入代入感' },
|
|
|
|
|
+ { id: 2, label: '生成内容' },
|
|
|
|
|
+ { id: 3, label: '审核提交' },
|
|
|
|
|
+ { id: 4, label: '连续性检查' },
|
|
|
|
|
+ { id: 5, label: '读者反馈' },
|
|
|
] as const;
|
|
] as const;
|
|
|
|
|
|
|
|
export default function ChapterDraftPipeline({
|
|
export default function ChapterDraftPipeline({
|
|
@@ -80,8 +76,6 @@ export default function ChapterDraftPipeline({
|
|
|
const parsed = JSON.parse(saved);
|
|
const parsed = JSON.parse(saved);
|
|
|
return {
|
|
return {
|
|
|
controlCard: parsed.controlCard || null,
|
|
controlCard: parsed.controlCard || null,
|
|
|
- selectedPlot: parsed.selectedPlot || '',
|
|
|
|
|
- customNote: parsed.customNote || '',
|
|
|
|
|
embedHooks: parsed.embedHooks || [],
|
|
embedHooks: parsed.embedHooks || [],
|
|
|
manualHooks: parsed.manualHooks || [],
|
|
manualHooks: parsed.manualHooks || [],
|
|
|
chapterContent: parsed.chapterContent || '',
|
|
chapterContent: parsed.chapterContent || '',
|
|
@@ -90,8 +84,6 @@ export default function ChapterDraftPipeline({
|
|
|
} catch { /* ignore corrupt data */ }
|
|
} catch { /* ignore corrupt data */ }
|
|
|
return {
|
|
return {
|
|
|
controlCard: null,
|
|
controlCard: null,
|
|
|
- selectedPlot: '',
|
|
|
|
|
- customNote: '',
|
|
|
|
|
embedHooks: [],
|
|
embedHooks: [],
|
|
|
manualHooks: [],
|
|
manualHooks: [],
|
|
|
chapterContent: '',
|
|
chapterContent: '',
|
|
@@ -144,43 +136,45 @@ export default function ChapterDraftPipeline({
|
|
|
}).then(r => r.json()).then(d => { if (d.success) setIronRulesContent(d.content as string); }).catch(() => {});
|
|
}).then(r => r.json()).then(d => { if (d.success) setIronRulesContent(d.content as string); }).catch(() => {});
|
|
|
}, [projectPath, chapterIndex]);
|
|
}, [projectPath, chapterIndex]);
|
|
|
|
|
|
|
|
- // Step 0 完成(控制卡)
|
|
|
|
|
|
|
+ // Compute chapter plan summary from control card for downstream steps
|
|
|
|
|
+ const chapterPlanSummary = pipelineState.controlCard
|
|
|
|
|
+ ? [
|
|
|
|
|
+ `本章改变:${pipelineState.controlCard.whatChanges}`,
|
|
|
|
|
+ `推进线条:${pipelineState.controlCard.lineToAdvance}`,
|
|
|
|
|
+ `情绪债务:${pipelineState.controlCard.debtToReturn}`,
|
|
|
|
|
+ `核心冲突:${pipelineState.controlCard.chapterConflict}`,
|
|
|
|
|
+ `结尾余波:${pipelineState.controlCard.endingResidue}`,
|
|
|
|
|
+ pipelineState.controlCard.workingTitle ? `工作标题:${pipelineState.controlCard.workingTitle}` : '',
|
|
|
|
|
+ ].filter(Boolean).join('\n')
|
|
|
|
|
+ : '';
|
|
|
|
|
+
|
|
|
|
|
+ // Step 0 完成(控制卡+剧情,合并原 Step 0 和 Step 1)
|
|
|
const handleStep0Next = useCallback((data: ControlCardData) => {
|
|
const handleStep0Next = useCallback((data: ControlCardData) => {
|
|
|
updateState((prev) => ({ ...prev, controlCard: data }));
|
|
updateState((prev) => ({ ...prev, controlCard: data }));
|
|
|
setCurrentStep(1);
|
|
setCurrentStep(1);
|
|
|
}, []);
|
|
}, []);
|
|
|
|
|
|
|
|
- // Step 1 完成
|
|
|
|
|
- const handleStep1Next = useCallback((selectedPlot: string, customNote?: string) => {
|
|
|
|
|
- updateState((prev) => ({
|
|
|
|
|
- ...prev,
|
|
|
|
|
- selectedPlot,
|
|
|
|
|
- customNote: customNote || '',
|
|
|
|
|
- }));
|
|
|
|
|
- setCurrentStep(2);
|
|
|
|
|
- }, []);
|
|
|
|
|
-
|
|
|
|
|
- // Step 2 完成(埋入钩子)
|
|
|
|
|
- const handleStep2Next = useCallback(
|
|
|
|
|
|
|
+ // Step 1 完成(埋入代入感)
|
|
|
|
|
+ const handleStep1Next = useCallback(
|
|
|
(embedHooks: { id: string; type: string; description: string; embedMethod: string }[], manualHooks: string[]) => {
|
|
(embedHooks: { id: string; type: string; description: string; embedMethod: string }[], manualHooks: string[]) => {
|
|
|
updateState((prev) => ({
|
|
updateState((prev) => ({
|
|
|
...prev,
|
|
...prev,
|
|
|
embedHooks,
|
|
embedHooks,
|
|
|
manualHooks,
|
|
manualHooks,
|
|
|
}));
|
|
}));
|
|
|
- setCurrentStep(3);
|
|
|
|
|
|
|
+ setCurrentStep(2);
|
|
|
},
|
|
},
|
|
|
[]
|
|
[]
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- // Step 3 完成(内容生成,原 Step 4)
|
|
|
|
|
- const handleStep3Next = useCallback((content: string) => {
|
|
|
|
|
|
|
+ // Step 2 完成(生成内容)
|
|
|
|
|
+ const handleStep2Next = useCallback((content: string) => {
|
|
|
updateState((prev) => ({ ...prev, chapterContent: content }));
|
|
updateState((prev) => ({ ...prev, chapterContent: content }));
|
|
|
- setCurrentStep(4);
|
|
|
|
|
|
|
+ setCurrentStep(3);
|
|
|
}, []);
|
|
}, []);
|
|
|
|
|
|
|
|
- // Step 4 完成 → 进入连续性检查
|
|
|
|
|
- const handleStep4Complete = useCallback(
|
|
|
|
|
|
|
+ // Step 3 完成(审核提交)
|
|
|
|
|
+ const handleStep3Complete = useCallback(
|
|
|
(chapterTitle: string) => {
|
|
(chapterTitle: string) => {
|
|
|
// Save the chapter title into control card for use in final completion
|
|
// Save the chapter title into control card for use in final completion
|
|
|
updateState((prev) => ({
|
|
updateState((prev) => ({
|
|
@@ -189,15 +183,15 @@ export default function ChapterDraftPipeline({
|
|
|
? { ...prev.controlCard, workingTitle: prev.controlCard.workingTitle || chapterTitle }
|
|
? { ...prev.controlCard, workingTitle: prev.controlCard.workingTitle || chapterTitle }
|
|
|
: { whatChanges: '', lineToAdvance: '', debtToReturn: '', chapterConflict: '', endingResidue: '', chapterTitleCandidates: [], workingTitle: chapterTitle, rawAIResponse: '' },
|
|
: { whatChanges: '', lineToAdvance: '', debtToReturn: '', chapterConflict: '', endingResidue: '', chapterTitleCandidates: [], workingTitle: chapterTitle, rawAIResponse: '' },
|
|
|
}));
|
|
}));
|
|
|
- setCurrentStep(5);
|
|
|
|
|
|
|
+ setCurrentStep(4);
|
|
|
},
|
|
},
|
|
|
[]
|
|
[]
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- // Step 5 完成 → 条件性进入 Step 6(每5章一次)或直接完成
|
|
|
|
|
- const handleStep5Complete = useCallback(() => {
|
|
|
|
|
|
|
+ // Step 4 完成(连续性检查)→ 条件性进入 Step 5(每5章一次)或直接完成
|
|
|
|
|
+ const handleStep4Complete = useCallback(() => {
|
|
|
if (chapterIndex % 5 === 0) {
|
|
if (chapterIndex % 5 === 0) {
|
|
|
- setCurrentStep(6);
|
|
|
|
|
|
|
+ setCurrentStep(5);
|
|
|
} else {
|
|
} else {
|
|
|
sessionStorage.removeItem(storageKey);
|
|
sessionStorage.removeItem(storageKey);
|
|
|
const title = pipelineState.controlCard?.workingTitle || `第${chapterIndex}章`;
|
|
const title = pipelineState.controlCard?.workingTitle || `第${chapterIndex}章`;
|
|
@@ -205,8 +199,8 @@ export default function ChapterDraftPipeline({
|
|
|
}
|
|
}
|
|
|
}, [onComplete, pipelineState.chapterContent, pipelineState.controlCard, chapterIndex, storageKey]);
|
|
}, [onComplete, pipelineState.chapterContent, pipelineState.controlCard, chapterIndex, storageKey]);
|
|
|
|
|
|
|
|
- // Step 6 完成 → 最终提交
|
|
|
|
|
- const handleStep6Complete = useCallback(() => {
|
|
|
|
|
|
|
+ // Step 5 完成(读者反馈)→ 最终提交
|
|
|
|
|
+ const handleStep5Complete = useCallback(() => {
|
|
|
sessionStorage.removeItem(storageKey);
|
|
sessionStorage.removeItem(storageKey);
|
|
|
const title = pipelineState.controlCard?.workingTitle || `第${chapterIndex}章`;
|
|
const title = pipelineState.controlCard?.workingTitle || `第${chapterIndex}章`;
|
|
|
onComplete(title, pipelineState.chapterContent);
|
|
onComplete(title, pipelineState.chapterContent);
|
|
@@ -217,11 +211,6 @@ export default function ChapterDraftPipeline({
|
|
|
onCancel();
|
|
onCancel();
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- // 构建传给 Step3 的完整剧情描述(含补充说明)
|
|
|
|
|
- const fullPlotDescription = pipelineState.customNote
|
|
|
|
|
- ? `${pipelineState.selectedPlot}\n\n补充说明:${pipelineState.customNote}`
|
|
|
|
|
- : pipelineState.selectedPlot;
|
|
|
|
|
-
|
|
|
|
|
return (
|
|
return (
|
|
|
<div className="fixed inset-0 z-50 bg-white flex flex-col">
|
|
<div className="fixed inset-0 z-50 bg-white flex flex-col">
|
|
|
{/* 顶部导航栏 */}
|
|
{/* 顶部导航栏 */}
|
|
@@ -305,7 +294,7 @@ export default function ChapterDraftPipeline({
|
|
|
{/* 主内容区域 */}
|
|
{/* 主内容区域 */}
|
|
|
<div className="flex-1 overflow-hidden">
|
|
<div className="flex-1 overflow-hidden">
|
|
|
<div className="max-w-4xl mx-auto px-6 py-6 h-full flex flex-col">
|
|
<div className="max-w-4xl mx-auto px-6 py-6 h-full flex flex-col">
|
|
|
- {/* Step 0: 章节控制卡 */}
|
|
|
|
|
|
|
+ {/* Step 0: 控制卡+剧情(合并) */}
|
|
|
{currentStep === 0 && (
|
|
{currentStep === 0 && (
|
|
|
<Step0ControlCard
|
|
<Step0ControlCard
|
|
|
projectPath={projectPath}
|
|
projectPath={projectPath}
|
|
@@ -317,35 +306,23 @@ export default function ChapterDraftPipeline({
|
|
|
/>
|
|
/>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
- {/* Step 1: 剧情选择 */}
|
|
|
|
|
|
|
+ {/* Step 1: 埋入代入感 */}
|
|
|
{currentStep === 1 && (
|
|
{currentStep === 1 && (
|
|
|
- <Step1PlotSelection
|
|
|
|
|
- projectPath={projectPath}
|
|
|
|
|
- apiSettings={apiSettings}
|
|
|
|
|
- recentChaptersCount={recentChaptersCount}
|
|
|
|
|
- savedSelectedPlot={pipelineState.selectedPlot || null}
|
|
|
|
|
- savedCustomNote={pipelineState.customNote}
|
|
|
|
|
- onNext={handleStep1Next}
|
|
|
|
|
- />
|
|
|
|
|
- )}
|
|
|
|
|
-
|
|
|
|
|
- {/* Step 2: 埋入钩子 */}
|
|
|
|
|
- {currentStep === 2 && (
|
|
|
|
|
<Step2EmbedHooks
|
|
<Step2EmbedHooks
|
|
|
- selectedPlot={pipelineState.selectedPlot}
|
|
|
|
|
|
|
+ chapterPlanSummary={chapterPlanSummary}
|
|
|
savedEmbedHooks={pipelineState.embedHooks}
|
|
savedEmbedHooks={pipelineState.embedHooks}
|
|
|
savedManualHooks={pipelineState.manualHooks}
|
|
savedManualHooks={pipelineState.manualHooks}
|
|
|
apiSettings={apiSettings}
|
|
apiSettings={apiSettings}
|
|
|
- onNext={handleStep2Next}
|
|
|
|
|
- onBack={() => setCurrentStep(1)}
|
|
|
|
|
|
|
+ onNext={handleStep1Next}
|
|
|
|
|
+ onBack={() => setCurrentStep(0)}
|
|
|
/>
|
|
/>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
- {/* Step 3: 内容生成 */}
|
|
|
|
|
- {currentStep === 3 && (
|
|
|
|
|
|
|
+ {/* Step 2: 生成内容 */}
|
|
|
|
|
+ {currentStep === 2 && (
|
|
|
<Step3ContentEditor
|
|
<Step3ContentEditor
|
|
|
projectPath={projectPath}
|
|
projectPath={projectPath}
|
|
|
- selectedPlot={fullPlotDescription}
|
|
|
|
|
|
|
+ selectedPlot={chapterPlanSummary}
|
|
|
embedHooks={pipelineState.embedHooks}
|
|
embedHooks={pipelineState.embedHooks}
|
|
|
manualHooks={pipelineState.manualHooks}
|
|
manualHooks={pipelineState.manualHooks}
|
|
|
savedChapterContent={pipelineState.chapterContent}
|
|
savedChapterContent={pipelineState.chapterContent}
|
|
@@ -356,13 +333,13 @@ export default function ChapterDraftPipeline({
|
|
|
wordCountMax={wordCountMax}
|
|
wordCountMax={wordCountMax}
|
|
|
recentChaptersCount={recentChaptersCount}
|
|
recentChaptersCount={recentChaptersCount}
|
|
|
ironRules={ironRulesContent}
|
|
ironRules={ironRulesContent}
|
|
|
- onNext={handleStep3Next}
|
|
|
|
|
- onBack={() => setCurrentStep(2)}
|
|
|
|
|
|
|
+ onNext={handleStep2Next}
|
|
|
|
|
+ onBack={() => setCurrentStep(1)}
|
|
|
/>
|
|
/>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
- {/* Step 4: 审核提交 */}
|
|
|
|
|
- {currentStep === 4 && (
|
|
|
|
|
|
|
+ {/* Step 3: 审核提交 */}
|
|
|
|
|
+ {currentStep === 3 && (
|
|
|
<Step4ReviewSubmit
|
|
<Step4ReviewSubmit
|
|
|
projectPath={projectPath}
|
|
projectPath={projectPath}
|
|
|
chapterContent={pipelineState.chapterContent}
|
|
chapterContent={pipelineState.chapterContent}
|
|
@@ -372,33 +349,33 @@ export default function ChapterDraftPipeline({
|
|
|
loreFiles={loreFiles}
|
|
loreFiles={loreFiles}
|
|
|
embedHooks={pipelineState.embedHooks}
|
|
embedHooks={pipelineState.embedHooks}
|
|
|
manualHooks={pipelineState.manualHooks}
|
|
manualHooks={pipelineState.manualHooks}
|
|
|
- onComplete={handleStep4Complete}
|
|
|
|
|
- onBack={() => setCurrentStep(3)}
|
|
|
|
|
|
|
+ onComplete={handleStep3Complete}
|
|
|
|
|
+ onBack={() => setCurrentStep(2)}
|
|
|
/>
|
|
/>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
- {/* Step 5: 连续性检查 */}
|
|
|
|
|
- {currentStep === 5 && (
|
|
|
|
|
|
|
+ {/* Step 4: 连续性检查 */}
|
|
|
|
|
+ {currentStep === 4 && (
|
|
|
<Step5ContinuityCheck
|
|
<Step5ContinuityCheck
|
|
|
projectPath={projectPath}
|
|
projectPath={projectPath}
|
|
|
chapterIndex={chapterIndex}
|
|
chapterIndex={chapterIndex}
|
|
|
chapterContent={pipelineState.chapterContent}
|
|
chapterContent={pipelineState.chapterContent}
|
|
|
apiSettings={apiSettings}
|
|
apiSettings={apiSettings}
|
|
|
chapterEvents={chapterEvents}
|
|
chapterEvents={chapterEvents}
|
|
|
- onComplete={handleStep5Complete}
|
|
|
|
|
- onBack={() => setCurrentStep(4)}
|
|
|
|
|
|
|
+ onComplete={handleStep4Complete}
|
|
|
|
|
+ onBack={() => setCurrentStep(3)}
|
|
|
/>
|
|
/>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
- {/* Step 6: 读者反馈 */}
|
|
|
|
|
- {currentStep === 6 && (
|
|
|
|
|
|
|
+ {/* Step 5: 读者反馈 */}
|
|
|
|
|
+ {currentStep === 5 && (
|
|
|
<Step6ReaderFeedback
|
|
<Step6ReaderFeedback
|
|
|
projectPath={projectPath}
|
|
projectPath={projectPath}
|
|
|
chapterIndex={chapterIndex}
|
|
chapterIndex={chapterIndex}
|
|
|
chapterContent={pipelineState.chapterContent}
|
|
chapterContent={pipelineState.chapterContent}
|
|
|
apiSettings={apiSettings}
|
|
apiSettings={apiSettings}
|
|
|
- onComplete={handleStep6Complete}
|
|
|
|
|
- onSkip={handleStep6Complete}
|
|
|
|
|
|
|
+ onComplete={handleStep5Complete}
|
|
|
|
|
+ onSkip={handleStep5Complete}
|
|
|
/>
|
|
/>
|
|
|
)}
|
|
)}
|
|
|
</div>
|
|
</div>
|