) => {
+ setPreview(prev => {
+ if (!prev) return prev;
+ const next = [...prev.chapters];
+ next[index] = { ...next[index], ...patch };
+ return { ...prev, chapters: next };
+ });
+ };
+
+ return (
+
+
+
+
+
+ {currentStep === 0 && (
+
+
+ {
+ setFile(f);
+ return false;
+ }}
+ onRemove={() => {
+ setFile(null);
+ }}
+ fileList={
+ file
+ ? [
+ {
+ uid: 'selected-txt',
+ name: file.name,
+ status: 'done',
+ } as UploadFile,
+ ]
+ : []
+ }
+ style={{ padding: '8px 0' }}
+ >
+
+
+
+ 点击或拖拽 TXT 文件到此区域
+ 首版仅支持 .txt,建议不超过 50MB
+
+
+
+ }
+ loading={creatingTask}
+ onClick={startTask}
+ >
+ 开始解析
+
+ {taskId && (
+ 任务ID: {taskId}
+ )}
+
+
+
+ )}
+
+ {currentStep === 1 && (
+
+ {!taskId ? (
+
+ ) : (
+
+
+
+
+ {taskStatus?.status === 'pending' && '等待调度...'}
+ {taskStatus?.status === 'running' && '正在解析TXT文件...'}
+ {taskStatus?.status === 'completed' && '解析完成!正在生成预览...'}
+ {taskStatus?.status === 'failed' && '解析失败'}
+ {taskStatus?.status === 'cancelled' && '已取消'}
+
+ {taskStatus?.message && (
+
+ {taskStatus.message}
+
+ )}
+
+
+ {taskStatus?.error && (
+
+ )}
+
+
+ } onClick={refreshStatus}>刷新状态
+ {taskStatus && ['pending', 'running'].includes(taskStatus.status) && (
+ } onClick={cancelTask}>取消任务
+ )}
+
+
+ )}
+
+ )}
+
+ {currentStep === 2 && (
+ <>
+
+ 确认导入
+
+ }
+ style={{ marginBottom: 16 }}
+ >
+
+ {!preview ? (
+
+ ) : (
+
+
+ {preview.warnings.length > 0 && (
+
+ {preview.warnings.map((w, idx) => (
+ [{w.level}] {w.message}
+ ))}
+
+ }
+ />
+ )}
+
+
+
+
+ 标题
+
+ setPreview(prev => prev ? ({
+ ...prev,
+ project_suggestion: { ...prev.project_suggestion, title: e.target.value },
+ }) : prev)
+ }
+ />
+
+
+ 类型
+
+ setPreview(prev => prev ? ({
+ ...prev,
+ project_suggestion: { ...prev.project_suggestion, genre: e.target.value },
+ }) : prev)
+ }
+ />
+
+
+ 主题
+
+
+
+
+ ({
+ key: String(idx),
+ label: `第 ${ch.chapter_number} 章 · ${ch.title}`,
+ children: (
+
+ updateChapter(idx, { title: e.target.value })}
+ />
+
+ ),
+ }))}
+ />
+
+
+
+
+ )}
+
+
+
+ >
+ )}
+
+ {currentStep === 3 && (
+
+
+
+ {retrying ? '正在重试失败的生成步骤' : (failedSteps.length > 0 && isApplyComplete ? '导入完成,部分步骤需要重试' : '正在为您生成并导入项目内容')}
+
+
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/pages/ProjectList.tsx b/frontend/src/pages/ProjectList.tsx
index be97776..26a2fac 100644
--- a/frontend/src/pages/ProjectList.tsx
+++ b/frontend/src/pages/ProjectList.tsx
@@ -13,6 +13,7 @@ import ChangelogFloatingButton from '../components/ChangelogFloatingButton';
import SettingsPage from './Settings';
import MCPPluginsPage from './MCPPlugins';
import PromptTemplates from './PromptTemplates';
+import BookImport from './BookImport';
const { Title, Text, Paragraph } = Typography;
@@ -39,7 +40,7 @@ const formatWordCount = (count: number): string => {
export default function ProjectList() {
const navigate = useNavigate();
const { projects, loading } = useStore();
- const [activeView, setActiveView] = useState<'projects' | 'settings' | 'mcp' | 'prompts'>('projects');
+ const [activeView, setActiveView] = useState<'projects' | 'settings' | 'mcp' | 'prompts' | 'book-import'>('projects');
const [drawerVisible, setDrawerVisible] = useState(false);
const [modal, contextHolder] = Modal.useModal();
const [showApiTip, setShowApiTip] = useState(true);
@@ -400,27 +401,27 @@ export default function ProjectList() {
创作工具
setActiveView('prompts')}
+ onClick={() => setActiveView('book-import')}
style={{
padding: '10px 16px',
fontSize: 14,
cursor: 'pointer',
borderRadius: 4,
- color: activeView === 'prompts' ? 'var(--color-primary)' : 'rgba(0,0,0,0.85)',
- background: activeView === 'prompts' ? '#e6f7ff' : 'transparent',
+ color: activeView === 'book-import' ? 'var(--color-primary)' : 'rgba(0,0,0,0.85)',
+ background: activeView === 'book-import' ? '#e6f7ff' : 'transparent',
fontWeight: 500,
display: 'flex',
alignItems: 'center',
gap: 10,
transition: 'all 0.3s',
marginBottom: 4,
- borderRight: activeView === 'prompts' ? '3px solid var(--color-primary)' : '3px solid transparent'
+ borderRight: activeView === 'book-import' ? '3px solid var(--color-primary)' : '3px solid transparent'
}}
- onMouseEnter={e => activeView !== 'prompts' && (e.currentTarget.style.background = 'rgba(0,0,0,0.04)')}
- onMouseLeave={e => activeView !== 'prompts' && (e.currentTarget.style.background = 'transparent')}
+ onMouseEnter={e => activeView !== 'book-import' && (e.currentTarget.style.background = 'rgba(0,0,0,0.04)')}
+ onMouseLeave={e => activeView !== 'book-import' && (e.currentTarget.style.background = 'transparent')}
>
-
- 提示词管理
+
+ 拆书导入
setActiveView('mcp')}
@@ -445,6 +446,29 @@ export default function ProjectList() {
MCP 插件
+ setActiveView('prompts')}
+ style={{
+ padding: '10px 16px',
+ fontSize: 14,
+ cursor: 'pointer',
+ borderRadius: 4,
+ color: activeView === 'prompts' ? 'var(--color-primary)' : 'rgba(0,0,0,0.85)',
+ background: activeView === 'prompts' ? '#e6f7ff' : 'transparent',
+ fontWeight: 500,
+ display: 'flex',
+ alignItems: 'center',
+ gap: 10,
+ transition: 'all 0.3s',
+ marginBottom: 4,
+ borderRight: activeView === 'prompts' ? '3px solid var(--color-primary)' : '3px solid transparent'
+ }}
+ onMouseEnter={e => activeView !== 'prompts' && (e.currentTarget.style.background = 'rgba(0,0,0,0.04)')}
+ onMouseLeave={e => activeView !== 'prompts' && (e.currentTarget.style.background = 'transparent')}
+ >
+
+ 提示词管理
+
系统设置
{activeView === 'projects' ? '我的书架' :
activeView === 'prompts' ? '提示词模板' :
+ activeView === 'book-import' ? '拆书导入' :
activeView === 'mcp' ? 'MCP 插件' : 'API 设置'}
@@ -577,7 +602,7 @@ export default function ProjectList() {
selectedKeys={[activeView]}
style={{ borderRight: 0, paddingTop: 8 }}
onClick={({ key }) => {
- setActiveView(key as 'projects' | 'settings' | 'mcp' | 'prompts');
+ setActiveView(key as 'projects' | 'settings' | 'mcp' | 'prompts' | 'book-import');
setDrawerVisible(false);
}}
items={[
@@ -591,15 +616,20 @@ export default function ProjectList() {
label: '创作工具',
children: [
{
- key: 'prompts',
- icon: ,
- label: '提示词管理',
+ key: 'book-import',
+ icon: ,
+ label: '拆书导入',
},
{
key: 'mcp',
icon: ,
label: 'MCP 插件',
},
+ {
+ key: 'prompts',
+ icon: ,
+ label: '提示词管理',
+ },
],
},
{
@@ -676,6 +706,7 @@ export default function ProjectList() {
}}>
{activeView === 'projects' ? '我的书架' :
activeView === 'prompts' ? '提示词模板' :
+ activeView === 'book-import' ? '拆书导入' :
activeView === 'mcp' ? 'MCP 插件' : 'API 设置'}
@@ -743,7 +774,9 @@ export default function ProjectList() {
style={{
flex: 1,
overflowY: 'auto',
- padding: activeView === 'projects' ? `${isMobile ? 16 : 24}px ${isMobile ? 16 : 32}px` : 0,
+ padding: (activeView === 'projects' || activeView === 'book-import')
+ ? `${isMobile ? 16 : 24}px ${isMobile ? 16 : 32}px`
+ : 0,
background: 'var(--color-bg-base)',
}}
>
@@ -751,6 +784,12 @@ export default function ProjectList() {
{activeView === 'mcp' && }
{activeView === 'prompts' && }
+ {activeView === 'book-import' && (
+
+
+
+ )}
+
{activeView === 'projects' && (