update:1.更新大纲细化功能
This commit is contained in:
+263
-90
@@ -1,11 +1,10 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { List, Button, Modal, Form, Input, Select, message, Empty, Space, Badge, Tag, Card, Tooltip, InputNumber, Progress, Alert, Radio } from 'antd';
|
||||
import { EditOutlined, FileTextOutlined, ThunderboltOutlined, LockOutlined, DownloadOutlined, SettingOutlined, FundOutlined, SyncOutlined, CheckCircleOutlined, CloseCircleOutlined, RocketOutlined, StopOutlined } from '@ant-design/icons';
|
||||
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { List, Button, Modal, Form, Input, Select, message, Empty, Space, Badge, Tag, Card, Tooltip, InputNumber, Progress, Alert, Radio, Descriptions, Collapse } from 'antd';
|
||||
import { EditOutlined, FileTextOutlined, ThunderboltOutlined, LockOutlined, DownloadOutlined, SettingOutlined, FundOutlined, SyncOutlined, CheckCircleOutlined, CloseCircleOutlined, RocketOutlined, StopOutlined, InfoCircleOutlined, CaretRightOutlined } from '@ant-design/icons';
|
||||
import { useStore } from '../store';
|
||||
import { useChapterSync } from '../store/hooks';
|
||||
import { projectApi, writingStyleApi } from '../services/api';
|
||||
import type { Chapter, ChapterUpdate, ApiError, WritingStyle, AnalysisTask } from '../types';
|
||||
import { cardStyles } from '../components/CardStyles';
|
||||
import type { Chapter, ChapterUpdate, ApiError, WritingStyle, AnalysisTask, ExpansionPlanData } from '../types';
|
||||
import ChapterAnalysis from '../components/ChapterAnalysis';
|
||||
import { SSELoadingOverlay } from '../components/SSELoadingOverlay';
|
||||
|
||||
@@ -479,6 +478,34 @@ export default function Chapters() {
|
||||
|
||||
const sortedChapters = [...chapters].sort((a, b) => a.chapter_number - b.chapter_number);
|
||||
|
||||
// 按大纲分组章节
|
||||
const groupedChapters = useMemo(() => {
|
||||
const groups: Record<string, {
|
||||
outlineId: string | null;
|
||||
outlineTitle: string;
|
||||
outlineOrder: number;
|
||||
chapters: Chapter[];
|
||||
}> = {};
|
||||
|
||||
sortedChapters.forEach(chapter => {
|
||||
const key = chapter.outline_id || 'uncategorized';
|
||||
|
||||
if (!groups[key]) {
|
||||
groups[key] = {
|
||||
outlineId: chapter.outline_id || null,
|
||||
outlineTitle: chapter.outline_title || '未分类章节',
|
||||
outlineOrder: chapter.outline_order ?? 999,
|
||||
chapters: []
|
||||
};
|
||||
}
|
||||
|
||||
groups[key].chapters.push(chapter);
|
||||
});
|
||||
|
||||
// 转换为数组并按大纲顺序排序
|
||||
return Object.values(groups).sort((a, b) => a.outlineOrder - b.outlineOrder);
|
||||
}, [sortedChapters]);
|
||||
|
||||
const handleExport = () => {
|
||||
if (chapters.length === 0) {
|
||||
message.warning('当前项目没有章节,无法导出');
|
||||
@@ -709,6 +736,97 @@ export default function Chapters() {
|
||||
}
|
||||
};
|
||||
|
||||
// 显示展开规划详情
|
||||
const showExpansionPlanModal = (chapter: Chapter) => {
|
||||
if (!chapter.expansion_plan) return;
|
||||
|
||||
try {
|
||||
const planData: ExpansionPlanData = JSON.parse(chapter.expansion_plan);
|
||||
|
||||
Modal.info({
|
||||
title: (
|
||||
<Space>
|
||||
<InfoCircleOutlined style={{ color: '#1890ff' }} />
|
||||
<span>第{chapter.chapter_number}章展开规划</span>
|
||||
</Space>
|
||||
),
|
||||
width: 800,
|
||||
content: (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Descriptions column={1} size="small" bordered>
|
||||
<Descriptions.Item label="章节标题">
|
||||
<strong>{chapter.title}</strong>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="情感基调">
|
||||
<Tag color="blue">{planData.emotional_tone}</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="冲突类型">
|
||||
<Tag color="orange">{planData.conflict_type}</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="预估字数">
|
||||
<Tag color="green">{planData.estimated_words}字</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="叙事目标">
|
||||
{planData.narrative_goal}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="关键事件">
|
||||
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
||||
{planData.key_events.map((event, idx) => (
|
||||
<div key={idx} style={{ padding: '4px 0' }}>
|
||||
<Tag color="purple">{idx + 1}</Tag> {event}
|
||||
</div>
|
||||
))}
|
||||
</Space>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="涉及角色">
|
||||
<Space wrap>
|
||||
{planData.character_focus.map((char, idx) => (
|
||||
<Tag key={idx} color="cyan">{char}</Tag>
|
||||
))}
|
||||
</Space>
|
||||
</Descriptions.Item>
|
||||
{planData.scenes && planData.scenes.length > 0 && (
|
||||
<Descriptions.Item label="场景规划">
|
||||
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
||||
{planData.scenes.map((scene, idx) => (
|
||||
<Card key={idx} size="small" style={{ backgroundColor: '#fafafa' }}>
|
||||
<div style={{ marginBottom: 4 }}>
|
||||
<strong>📍 地点:</strong>{scene.location}
|
||||
</div>
|
||||
<div style={{ marginBottom: 4 }}>
|
||||
<strong>👥 角色:</strong>
|
||||
<Space size="small" wrap style={{ marginLeft: 8 }}>
|
||||
{scene.characters.map((char, charIdx) => (
|
||||
<Tag key={charIdx}>{char}</Tag>
|
||||
))}
|
||||
</Space>
|
||||
</div>
|
||||
<div>
|
||||
<strong>🎯 目的:</strong>{scene.purpose}
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
</Descriptions>
|
||||
<Alert
|
||||
message="提示"
|
||||
description="这些是AI在大纲展开时生成的规划信息,可以作为创作章节内容时的参考。"
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginTop: 16 }}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
okText: '关闭',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('解析展开规划失败:', error);
|
||||
message.error('展开规划数据格式错误');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<div style={{
|
||||
@@ -754,21 +872,54 @@ export default function Chapters() {
|
||||
|
||||
<div style={{ flex: 1, overflowY: 'auto' }}>
|
||||
{chapters.length === 0 ? (
|
||||
<Empty description="还没有章节,开始创作吧!" />
|
||||
) : (
|
||||
<Card style={cardStyles.base}>
|
||||
<List
|
||||
dataSource={sortedChapters}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
<Empty description="还没有章节,开始创作吧!" />
|
||||
) : (
|
||||
<Collapse
|
||||
bordered={false}
|
||||
defaultActiveKey={groupedChapters.map((_, idx) => idx.toString())}
|
||||
expandIcon={({ isActive }) => <CaretRightOutlined rotate={isActive ? 90 : 0} />}
|
||||
style={{ background: 'transparent' }}
|
||||
>
|
||||
{groupedChapters.map((group, groupIndex) => (
|
||||
<Collapse.Panel
|
||||
key={groupIndex.toString()}
|
||||
header={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<Tag color={group.outlineId ? 'blue' : 'default'} style={{ margin: 0 }}>
|
||||
{group.outlineId ? `📖 大纲 ${group.outlineOrder}` : '📝 未分类'}
|
||||
</Tag>
|
||||
<span style={{ fontWeight: 600, fontSize: 16 }}>
|
||||
{group.outlineTitle}
|
||||
</span>
|
||||
<Badge
|
||||
count={`${group.chapters.length} 章`}
|
||||
style={{ backgroundColor: '#52c41a' }}
|
||||
/>
|
||||
<Badge
|
||||
count={`${group.chapters.reduce((sum, ch) => sum + (ch.word_count || 0), 0)} 字`}
|
||||
style={{ backgroundColor: '#1890ff' }}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
style={{
|
||||
padding: '16px 0',
|
||||
marginBottom: 16,
|
||||
background: '#fff',
|
||||
borderRadius: 8,
|
||||
transition: 'background 0.3s ease',
|
||||
flexDirection: isMobile ? 'column' : 'row',
|
||||
alignItems: isMobile ? 'flex-start' : 'center'
|
||||
border: '1px solid #f0f0f0',
|
||||
}}
|
||||
actions={isMobile ? undefined : [
|
||||
>
|
||||
<List
|
||||
dataSource={group.chapters}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
style={{
|
||||
padding: '16px 0',
|
||||
borderRadius: 8,
|
||||
transition: 'background 0.3s ease',
|
||||
flexDirection: isMobile ? 'column' : 'row',
|
||||
alignItems: isMobile ? 'flex-start' : 'center',
|
||||
}}
|
||||
actions={isMobile ? undefined : [
|
||||
<Button
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleOpenEditor(item.id)}
|
||||
@@ -806,85 +957,107 @@ export default function Chapters() {
|
||||
>
|
||||
修改信息
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<div style={{ width: '100%' }}>
|
||||
<List.Item.Meta
|
||||
avatar={!isMobile && <FileTextOutlined style={{ fontSize: 32, color: '#1890ff' }} />}
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: isMobile ? 4 : 8, flexWrap: 'wrap', fontSize: isMobile ? 14 : 16 }}>
|
||||
<span>第{item.chapter_number}章:{item.title}</span>
|
||||
<Tag color={getStatusColor(item.status)}>{getStatusText(item.status)}</Tag>
|
||||
<Badge count={`${item.word_count || 0}字`} style={{ backgroundColor: '#52c41a' }} />
|
||||
{renderAnalysisStatus(item.id)}
|
||||
{!canGenerateChapter(item) && (
|
||||
<Tooltip title={getGenerateDisabledReason(item)}>
|
||||
<Tag icon={<LockOutlined />} color="warning">
|
||||
需前置章节
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
description={
|
||||
item.content ? (
|
||||
<div style={{ marginTop: 8, color: 'rgba(0,0,0,0.65)', lineHeight: 1.6, fontSize: isMobile ? 12 : 14 }}>
|
||||
{item.content.substring(0, isMobile ? 80 : 150)}
|
||||
{item.content.length > (isMobile ? 80 : 150) && '...'}
|
||||
</div>
|
||||
) : (
|
||||
<span style={{ color: 'rgba(0,0,0,0.45)', fontSize: isMobile ? 12 : 14 }}>暂无内容</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{isMobile && (
|
||||
<Space style={{ marginTop: 12, width: '100%', justifyContent: 'flex-end' }} wrap>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleOpenEditor(item.id)}
|
||||
size="small"
|
||||
title="编辑内容"
|
||||
/>
|
||||
{(() => {
|
||||
const task = analysisTasksMap[item.id];
|
||||
const isAnalyzing = task && (task.status === 'pending' || task.status === 'running');
|
||||
const hasContent = item.content && item.content.trim() !== '';
|
||||
]}
|
||||
>
|
||||
<div style={{ width: '100%' }}>
|
||||
<List.Item.Meta
|
||||
avatar={!isMobile && <FileTextOutlined style={{ fontSize: 32, color: '#1890ff' }} />}
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: isMobile ? 4 : 8, flexWrap: 'wrap', fontSize: isMobile ? 14 : 16 }}>
|
||||
<span>
|
||||
第{item.chapter_number}章:{item.title}
|
||||
</span>
|
||||
<Tag color={getStatusColor(item.status)}>{getStatusText(item.status)}</Tag>
|
||||
<Badge count={`${item.word_count || 0}字`} style={{ backgroundColor: '#52c41a' }} />
|
||||
{renderAnalysisStatus(item.id)}
|
||||
{item.expansion_plan && (
|
||||
<Tooltip title="已有展开规划,点击信息图标查看详情">
|
||||
<Tag icon={<CheckCircleOutlined />} color="blue">
|
||||
已展开
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!canGenerateChapter(item) && (
|
||||
<Tooltip title={getGenerateDisabledReason(item)}>
|
||||
<Tag icon={<LockOutlined />} color="warning">
|
||||
需前置章节
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
)}
|
||||
{item.expansion_plan && (
|
||||
<Tooltip title="查看展开规划详情">
|
||||
<InfoCircleOutlined
|
||||
style={{ color: '#1890ff', cursor: 'pointer', fontSize: 16 }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
showExpansionPlanModal(item);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
description={
|
||||
item.content ? (
|
||||
<div style={{ marginTop: 8, color: 'rgba(0,0,0,0.65)', lineHeight: 1.6, fontSize: isMobile ? 12 : 14 }}>
|
||||
{item.content.substring(0, isMobile ? 80 : 150)}
|
||||
{item.content.length > (isMobile ? 80 : 150) && '...'}
|
||||
</div>
|
||||
) : (
|
||||
<span style={{ color: 'rgba(0,0,0,0.45)', fontSize: isMobile ? 12 : 14 }}>暂无内容</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
!hasContent ? '请先生成章节内容' :
|
||||
isAnalyzing ? '分析中' :
|
||||
'查看分析'
|
||||
}
|
||||
>
|
||||
{isMobile && (
|
||||
<Space style={{ marginTop: 12, width: '100%', justifyContent: 'flex-end' }} wrap>
|
||||
<Button
|
||||
type="text"
|
||||
icon={isAnalyzing ? <SyncOutlined spin /> : <FundOutlined />}
|
||||
onClick={() => handleShowAnalysis(item.id)}
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleOpenEditor(item.id)}
|
||||
size="small"
|
||||
disabled={!hasContent || isAnalyzing}
|
||||
loading={isAnalyzing}
|
||||
title="编辑内容"
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
})()}
|
||||
<Button
|
||||
type="text"
|
||||
icon={<SettingOutlined />}
|
||||
onClick={() => handleOpenModal(item.id)}
|
||||
size="small"
|
||||
title="修改信息"
|
||||
/>
|
||||
</Space>
|
||||
{(() => {
|
||||
const task = analysisTasksMap[item.id];
|
||||
const isAnalyzing = task && (task.status === 'pending' || task.status === 'running');
|
||||
const hasContent = item.content && item.content.trim() !== '';
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
!hasContent ? '请先生成章节内容' :
|
||||
isAnalyzing ? '分析中' :
|
||||
'查看分析'
|
||||
}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
icon={isAnalyzing ? <SyncOutlined spin /> : <FundOutlined />}
|
||||
onClick={() => handleShowAnalysis(item.id)}
|
||||
size="small"
|
||||
disabled={!hasContent || isAnalyzing}
|
||||
loading={isAnalyzing}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
})()}
|
||||
<Button
|
||||
type="text"
|
||||
icon={<SettingOutlined />}
|
||||
onClick={() => handleOpenModal(item.id)}
|
||||
size="small"
|
||||
title="修改信息"
|
||||
/>
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Card>
|
||||
/>
|
||||
</Collapse.Panel>
|
||||
))}
|
||||
</Collapse>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
+874
-86
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,10 @@ import type {
|
||||
OutlineCreate,
|
||||
OutlineUpdate,
|
||||
OutlineReorderRequest,
|
||||
OutlineExpansionRequest,
|
||||
OutlineExpansionResponse,
|
||||
BatchOutlineExpansionRequest,
|
||||
BatchOutlineExpansionResponse,
|
||||
Character,
|
||||
CharacterUpdate,
|
||||
Chapter,
|
||||
@@ -295,6 +299,65 @@ export const outlineApi = {
|
||||
|
||||
generateOutline: (data: GenerateOutlineRequest) =>
|
||||
api.post<unknown, { total: number; items: Outline[] }>('/outlines/generate', data).then(res => res.items),
|
||||
|
||||
// 获取大纲关联的章节
|
||||
getOutlineChapters: (outlineId: string) =>
|
||||
api.get<unknown, {
|
||||
has_chapters: boolean;
|
||||
outline_id: string;
|
||||
outline_title: string;
|
||||
chapter_count: number;
|
||||
chapters: Array<{
|
||||
id: string;
|
||||
chapter_number: number;
|
||||
title: string;
|
||||
summary: string;
|
||||
sub_index: number;
|
||||
status: string;
|
||||
word_count: number;
|
||||
}>;
|
||||
expansion_plans: Array<{
|
||||
sub_index: number;
|
||||
title: string;
|
||||
plot_summary: string;
|
||||
key_events: string[];
|
||||
character_focus: string[];
|
||||
emotional_tone: string;
|
||||
narrative_goal: string;
|
||||
conflict_type: string;
|
||||
estimated_words: number;
|
||||
scenes?: Array<{
|
||||
location: string;
|
||||
characters: string[];
|
||||
purpose: string;
|
||||
}> | null;
|
||||
}> | null;
|
||||
}>(`/outlines/${outlineId}/chapters`),
|
||||
|
||||
// 单个大纲展开为多章
|
||||
expandOutline: (outlineId: string, data: OutlineExpansionRequest) =>
|
||||
api.post<unknown, OutlineExpansionResponse>(`/outlines/${outlineId}/expand`, data),
|
||||
|
||||
// 根据已有规划创建章节(避免重复AI调用)
|
||||
createChaptersFromPlans: (outlineId: string, chapterPlans: any[]) =>
|
||||
api.post<unknown, {
|
||||
outline_id: string;
|
||||
outline_title: string;
|
||||
chapters_created: number;
|
||||
created_chapters: Array<{
|
||||
id: string;
|
||||
chapter_number: number;
|
||||
title: string;
|
||||
summary: string;
|
||||
outline_id: string;
|
||||
sub_index: number;
|
||||
status: string;
|
||||
}>;
|
||||
}>(`/outlines/${outlineId}/create-chapters-from-plans`, { chapter_plans: chapterPlans }),
|
||||
|
||||
// 批量展开大纲
|
||||
batchExpandOutlines: (data: BatchOutlineExpansionRequest) =>
|
||||
api.post<unknown, BatchOutlineExpansionResponse>('/outlines/batch-expand', data),
|
||||
};
|
||||
|
||||
export const characterApi = {
|
||||
|
||||
@@ -200,23 +200,6 @@ export function useOutlineSync() {
|
||||
}
|
||||
}, [removeOutline]);
|
||||
|
||||
// 重排序大纲(带同步)
|
||||
const reorderOutlines = useCallback(async (orders: Array<{ id: string; order_index: number }>, projectId?: string) => {
|
||||
try {
|
||||
await outlineApi.reorderOutlines({ orders });
|
||||
// 重新获取完整列表以确保顺序正确
|
||||
const id = projectId || currentProject?.id;
|
||||
if (id) {
|
||||
const data = await outlineApi.getOutlines(id);
|
||||
const outlines = Array.isArray(data) ? data : (data as PaginationResponse<Outline>).items || [];
|
||||
setOutlines(outlines);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('重排序大纲失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}, [currentProject?.id, setOutlines]); // 添加 currentProject?.id 到依赖数组
|
||||
|
||||
// AI生成大纲(带同步)
|
||||
const generateOutlines = useCallback(async (data: GenerateOutlineRequest) => {
|
||||
try {
|
||||
@@ -235,7 +218,6 @@ export function useOutlineSync() {
|
||||
createOutline,
|
||||
updateOutline: updateOutlineSync,
|
||||
deleteOutline,
|
||||
reorderOutlines,
|
||||
generateOutlines,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -202,6 +202,21 @@ export interface CharacterUpdate {
|
||||
color?: string;
|
||||
}
|
||||
|
||||
// 展开规划数据结构
|
||||
export interface ExpansionPlanData {
|
||||
key_events: string[];
|
||||
character_focus: string[];
|
||||
emotional_tone: string;
|
||||
narrative_goal: string;
|
||||
conflict_type: string;
|
||||
estimated_words: number;
|
||||
scenes?: Array<{
|
||||
location: string;
|
||||
characters: string[];
|
||||
purpose: string;
|
||||
}> | null;
|
||||
}
|
||||
|
||||
// 章节类型定义
|
||||
export interface Chapter {
|
||||
id: string;
|
||||
@@ -212,6 +227,11 @@ export interface Chapter {
|
||||
chapter_number: number;
|
||||
word_count: number;
|
||||
status: 'draft' | 'writing' | 'completed';
|
||||
expansion_plan?: string; // JSON字符串,解析后为ExpansionPlanData
|
||||
outline_id?: string; // 关联的大纲ID
|
||||
sub_index?: number; // 大纲下的子章节序号
|
||||
outline_title?: string; // 大纲标题(从后端联表查询获得)
|
||||
outline_order?: number; // 大纲排序序号(从后端联表查询获得)
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
@@ -284,6 +304,72 @@ export interface OutlineReorderRequest {
|
||||
orders: OutlineReorderItem[];
|
||||
}
|
||||
|
||||
// 大纲展开相关类型定义
|
||||
export interface ChapterPlanItem {
|
||||
sub_index: number;
|
||||
title: string;
|
||||
plot_summary: string;
|
||||
key_events: string[];
|
||||
character_focus: string[];
|
||||
emotional_tone: string;
|
||||
narrative_goal: string;
|
||||
conflict_type: string;
|
||||
estimated_words: number;
|
||||
scenes?: Array<{
|
||||
location: string;
|
||||
characters: string[];
|
||||
purpose: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface OutlineExpansionRequest {
|
||||
target_chapter_count: number;
|
||||
expansion_strategy?: 'balanced' | 'climax' | 'detail';
|
||||
auto_create_chapters?: boolean;
|
||||
provider?: string;
|
||||
model?: string;
|
||||
}
|
||||
|
||||
export interface OutlineExpansionResponse {
|
||||
outline_id: string;
|
||||
outline_title: string;
|
||||
target_chapter_count: number;
|
||||
actual_chapter_count: number;
|
||||
expansion_strategy: string;
|
||||
chapter_plans: ChapterPlanItem[];
|
||||
created_chapters?: Array<{
|
||||
id: string;
|
||||
chapter_number: number;
|
||||
title: string;
|
||||
summary: string;
|
||||
outline_id: string;
|
||||
sub_index: number;
|
||||
status: string;
|
||||
}> | null;
|
||||
}
|
||||
|
||||
export interface BatchOutlineExpansionRequest {
|
||||
project_id: string;
|
||||
outline_ids?: string[];
|
||||
chapters_per_outline: number;
|
||||
expansion_strategy?: 'balanced' | 'climax' | 'detail';
|
||||
auto_create_chapters?: boolean;
|
||||
provider?: string;
|
||||
model?: string;
|
||||
}
|
||||
|
||||
export interface BatchOutlineExpansionResponse {
|
||||
project_id: string;
|
||||
total_outlines_expanded: number;
|
||||
total_chapters_created: number;
|
||||
expansion_results: OutlineExpansionResponse[];
|
||||
skipped_outlines?: Array<{
|
||||
outline_id: string;
|
||||
outline_title: string;
|
||||
reason: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface GenerateCharacterRequest {
|
||||
project_id: string;
|
||||
name?: string;
|
||||
|
||||
Reference in New Issue
Block a user