|
@@ -1,8 +1,8 @@
|
|
|
'use client';
|
|
'use client';
|
|
|
|
|
|
|
|
-import { useState, useCallback } from 'react';
|
|
|
|
|
|
|
+import { useState, useCallback, useEffect } from 'react';
|
|
|
import { X, Check } from 'lucide-react';
|
|
import { X, Check } from 'lucide-react';
|
|
|
-import { WorldStateFile, WorldLoreFile, WritingConstraint, AntiHomogenizationStrategy, NarrativeHook } from '@/app/lib/novel-types';
|
|
|
|
|
|
|
+import { WorldStateFile, WorldLoreFile, WritingConstraint, AntiHomogenizationStrategy, NarrativeHook, EventNode, ChapterAllocation } from '@/app/lib/novel-types';
|
|
|
import { parseHooksFromJson } from '@/app/lib/novel-utils';
|
|
import { parseHooksFromJson } from '@/app/lib/novel-utils';
|
|
|
import Step0ControlCard from './pipeline/Step0ControlCard';
|
|
import Step0ControlCard from './pipeline/Step0ControlCard';
|
|
|
import Step1PlotSelection from './pipeline/Step1PlotSelection';
|
|
import Step1PlotSelection from './pipeline/Step1PlotSelection';
|
|
@@ -11,6 +11,7 @@ import Step3ResolveHooks from './pipeline/Step3ResolveHooks';
|
|
|
import Step4ContentEditor from './pipeline/Step4ContentEditor';
|
|
import Step4ContentEditor from './pipeline/Step4ContentEditor';
|
|
|
import Step5ReviewSubmit from './pipeline/Step5ReviewSubmit';
|
|
import Step5ReviewSubmit from './pipeline/Step5ReviewSubmit';
|
|
|
import Step6ContinuityCheck from './pipeline/Step6ContinuityCheck';
|
|
import Step6ContinuityCheck from './pipeline/Step6ContinuityCheck';
|
|
|
|
|
+import Step7ReaderFeedback from './pipeline/Step7ReaderFeedback';
|
|
|
import type { ControlCardData } from './pipeline/Step0ControlCard';
|
|
import type { ControlCardData } from './pipeline/Step0ControlCard';
|
|
|
|
|
|
|
|
interface ApiSettings {
|
|
interface ApiSettings {
|
|
@@ -30,11 +31,14 @@ interface ChapterDraftPipelineProps {
|
|
|
wordCountMin: number;
|
|
wordCountMin: number;
|
|
|
wordCountMax: number;
|
|
wordCountMax: number;
|
|
|
recentChaptersCount: number;
|
|
recentChaptersCount: number;
|
|
|
|
|
+ chapterEvents?: EventNode[];
|
|
|
|
|
+ ironRules?: string;
|
|
|
|
|
+ styleAnchor?: string;
|
|
|
onComplete: (chapterTitle: string, content: string) => void;
|
|
onComplete: (chapterTitle: string, content: string) => void;
|
|
|
onCancel: () => void;
|
|
onCancel: () => void;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-type PipelineStep = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
|
|
|
|
|
+type PipelineStep = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
|
|
|
|
|
|
|
interface PipelineState {
|
|
interface PipelineState {
|
|
|
controlCard: ControlCardData | null;
|
|
controlCard: ControlCardData | null;
|
|
@@ -54,6 +58,7 @@ const STEPS = [
|
|
|
{ id: 4, label: '生成内容' },
|
|
{ id: 4, label: '生成内容' },
|
|
|
{ id: 5, label: '审核提交' },
|
|
{ id: 5, label: '审核提交' },
|
|
|
{ id: 6, label: '连续性检查' },
|
|
{ id: 6, label: '连续性检查' },
|
|
|
|
|
+ { id: 7, label: '读者反馈' },
|
|
|
] as const;
|
|
] as const;
|
|
|
|
|
|
|
|
export default function ChapterDraftPipeline({
|
|
export default function ChapterDraftPipeline({
|
|
@@ -103,6 +108,9 @@ export default function ChapterDraftPipeline({
|
|
|
const [currentStep, setCurrentStep] = useState<PipelineStep>(0);
|
|
const [currentStep, setCurrentStep] = useState<PipelineStep>(0);
|
|
|
const [showCancelConfirm, setShowCancelConfirm] = useState(false);
|
|
const [showCancelConfirm, setShowCancelConfirm] = useState(false);
|
|
|
const [pipelineState, setPipelineState] = useState<PipelineState>(getInitialState);
|
|
const [pipelineState, setPipelineState] = useState<PipelineState>(getInitialState);
|
|
|
|
|
+ const [chapterEvents, setChapterEvents] = useState<EventNode[]>([]);
|
|
|
|
|
+ const [ironRulesContent, setIronRulesContent] = useState('');
|
|
|
|
|
+ const [styleAnchorContent, setStyleAnchorContent] = useState('');
|
|
|
|
|
|
|
|
// Auto-save state to sessionStorage on every change
|
|
// Auto-save state to sessionStorage on every change
|
|
|
const savePipelineState = (newState: PipelineState) => {
|
|
const savePipelineState = (newState: PipelineState) => {
|
|
@@ -126,6 +134,37 @@ export default function ChapterDraftPipeline({
|
|
|
return parseHooksFromJson(hooksFile.content).filter((h) => h.status === 'open');
|
|
return parseHooksFromJson(hooksFile.content).filter((h) => h.status === 'open');
|
|
|
})();
|
|
})();
|
|
|
|
|
|
|
|
|
|
+ // 加载事件图谱和防漂移文件
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ // Load event graph allocation for this chapter
|
|
|
|
|
+ fetch(`/api/novel/event-graph?projectPath=${encodeURIComponent(projectPath)}`)
|
|
|
|
|
+ .then(r => r.json())
|
|
|
|
|
+ .then(data => {
|
|
|
|
|
+ if (data.success && data.chapterAllocations) {
|
|
|
|
|
+ const alloc = (data.chapterAllocations as ChapterAllocation[]).find(
|
|
|
|
|
+ a => a.chapterIndex === chapterIndex
|
|
|
|
|
+ );
|
|
|
|
|
+ if (alloc) {
|
|
|
|
|
+ setChapterEvents((data.events as EventNode[]).filter(e => alloc.eventIds.includes(e.id)));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch(() => {});
|
|
|
|
|
+
|
|
|
|
|
+ // Load iron-rules and style-anchor
|
|
|
|
|
+ fetch('/api/novel/files', {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
+ body: JSON.stringify({ action: 'read', projectPath, filePath: 'iron-rules.md' }),
|
|
|
|
|
+ }).then(r => r.json()).then(d => { if (d.success) setIronRulesContent(d.content as string); }).catch(() => {});
|
|
|
|
|
+
|
|
|
|
|
+ fetch('/api/novel/files', {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
+ body: JSON.stringify({ action: 'read', projectPath, filePath: 'style-anchor.md' }),
|
|
|
|
|
+ }).then(r => r.json()).then(d => { if (d.success) setStyleAnchorContent(d.content as string); }).catch(() => {});
|
|
|
|
|
+ }, [projectPath, chapterIndex]);
|
|
|
|
|
+
|
|
|
// Step 0 完成(控制卡)
|
|
// Step 0 完成(控制卡)
|
|
|
const handleStep0Next = useCallback((data: ControlCardData) => {
|
|
const handleStep0Next = useCallback((data: ControlCardData) => {
|
|
|
updateState((prev) => ({ ...prev, controlCard: data }));
|
|
updateState((prev) => ({ ...prev, controlCard: data }));
|
|
@@ -188,8 +227,13 @@ export default function ChapterDraftPipeline({
|
|
|
[]
|
|
[]
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- // Step 6 完成 → 最终提交
|
|
|
|
|
|
|
+ // Step 6 完成 → Step 7 读者反馈
|
|
|
const handleStep6Complete = useCallback(() => {
|
|
const handleStep6Complete = useCallback(() => {
|
|
|
|
|
+ setCurrentStep(7);
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ // Step 7 完成 → 最终提交
|
|
|
|
|
+ const handleStep7Complete = 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);
|
|
@@ -295,6 +339,7 @@ export default function ChapterDraftPipeline({
|
|
|
chapterIndex={chapterIndex}
|
|
chapterIndex={chapterIndex}
|
|
|
apiSettings={apiSettings}
|
|
apiSettings={apiSettings}
|
|
|
savedControlCard={pipelineState.controlCard}
|
|
savedControlCard={pipelineState.controlCard}
|
|
|
|
|
+ chapterEvents={chapterEvents}
|
|
|
onNext={handleStep0Next}
|
|
onNext={handleStep0Next}
|
|
|
/>
|
|
/>
|
|
|
)}
|
|
)}
|
|
@@ -348,6 +393,8 @@ export default function ChapterDraftPipeline({
|
|
|
wordCountMin={wordCountMin}
|
|
wordCountMin={wordCountMin}
|
|
|
wordCountMax={wordCountMax}
|
|
wordCountMax={wordCountMax}
|
|
|
recentChaptersCount={recentChaptersCount}
|
|
recentChaptersCount={recentChaptersCount}
|
|
|
|
|
+ ironRules={ironRulesContent}
|
|
|
|
|
+ styleAnchor={styleAnchorContent}
|
|
|
onNext={handleStep4Next}
|
|
onNext={handleStep4Next}
|
|
|
onBack={() => setCurrentStep(3)}
|
|
onBack={() => setCurrentStep(3)}
|
|
|
/>
|
|
/>
|
|
@@ -381,6 +428,18 @@ export default function ChapterDraftPipeline({
|
|
|
onBack={() => setCurrentStep(5)}
|
|
onBack={() => setCurrentStep(5)}
|
|
|
/>
|
|
/>
|
|
|
)}
|
|
)}
|
|
|
|
|
+
|
|
|
|
|
+ {/* Step 7: 读者反馈 */}
|
|
|
|
|
+ {currentStep === 7 && (
|
|
|
|
|
+ <Step7ReaderFeedback
|
|
|
|
|
+ projectPath={projectPath}
|
|
|
|
|
+ chapterIndex={chapterIndex}
|
|
|
|
|
+ chapterContent={pipelineState.chapterContent}
|
|
|
|
|
+ apiSettings={apiSettings}
|
|
|
|
|
+ onComplete={handleStep7Complete}
|
|
|
|
|
+ onSkip={handleStep7Complete}
|
|
|
|
|
+ />
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|