update:新增用户API配置提示 优化大纲全新/续写的分批生成

This commit is contained in:
xiamuceer
2025-10-30 22:01:10 +08:00
parent c4f8cd78f0
commit 12ded06b36
7 changed files with 830 additions and 91 deletions
+85 -16
View File
@@ -1,9 +1,10 @@
import { useState, useEffect } from 'react';
import { Button, List, Modal, Form, Input, message, Empty, Space, Popconfirm, Card, Select, Radio, Tag } from 'antd';
import { Button, List, Modal, Form, Input, message, Empty, Space, Popconfirm, Card, Select, Radio, Tag, Progress } from 'antd';
import { EditOutlined, DeleteOutlined, ThunderboltOutlined, ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
import { useStore } from '../store';
import { useOutlineSync } from '../store/hooks';
import { cardStyles } from '../components/CardStyles';
import { SSEPostClient } from '../utils/sseClient';
const { TextArea } = Input;
@@ -13,6 +14,11 @@ export default function Outline() {
const [editForm] = Form.useForm();
const [generateForm] = Form.useForm();
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
// SSE进度状态
const [sseProgress, setSSEProgress] = useState(0);
const [sseMessage, setSSEMessage] = useState('');
const [sseModalVisible, setSSEModalVisible] = useState(false);
useEffect(() => {
const handleResize = () => {
@@ -23,13 +29,12 @@ export default function Outline() {
return () => window.removeEventListener('resize', handleResize);
}, []);
// 使用同步 hooks(移除createOutline
// 使用同步 hooks
const {
refreshOutlines,
updateOutline,
deleteOutline,
reorderOutlines,
generateOutlines
reorderOutlines
} = useOutlineSync();
// 初始加载大纲列表
@@ -159,9 +164,17 @@ export default function Outline() {
const handleGenerate = async (values: GenerateFormValues) => {
try {
setIsGenerating(true);
// 如果是全新生成模式,keep_existing应该为false
const isNewMode = values.mode === 'new';
const result = await generateOutlines({
// 关闭生成表单Modal
Modal.destroyAll();
// 显示进度Modal
setSSEProgress(0);
setSSEMessage('正在连接AI服务...');
setSSEModalVisible(true);
// 准备请求数据
const requestData = {
project_id: currentProject.id,
genre: currentProject.genre || '通用',
theme: values.theme || currentProject.theme || '',
@@ -169,20 +182,44 @@ export default function Outline() {
narrative_perspective: values.narrative_perspective || currentProject.narrative_perspective || '第三人称',
target_words: currentProject.target_words || 100000,
requirements: values.requirements,
// 续写参数
mode: values.mode || 'auto',
story_direction: values.story_direction,
plot_stage: values.plot_stage || 'development',
keep_existing: !isNewMode, // 全新生成模式下不保留旧大纲
provider: values.provider,
model: values.model
};
// 使用SSE客户端
const apiUrl = `/api/outlines/generate-stream`;
const client = new SSEPostClient(apiUrl, requestData, {
onProgress: (msg: string, progress: number) => {
setSSEMessage(msg);
setSSEProgress(progress);
},
onResult: (data: any) => {
console.log('生成完成,结果:', data);
},
onError: (error: string) => {
message.error(`生成失败: ${error}`);
setSSEModalVisible(false);
setIsGenerating(false);
},
onComplete: () => {
message.success('大纲生成完成!');
setSSEModalVisible(false);
setIsGenerating(false);
// 刷新大纲列表
refreshOutlines();
}
});
message.success(`成功生成 ${result.length} 条大纲`);
Modal.destroyAll();
// 刷新大纲列表,确保显示最新数据
await refreshOutlines();
// 开始连接
client.connect();
} catch (error) {
console.error('AI生成失败:', error);
message.error('AI生成失败');
} finally {
setSSEModalVisible(false);
setIsGenerating(false);
}
};
@@ -335,7 +372,38 @@ export default function Outline() {
};
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<>
{/* SSE进度Modal */}
<Modal
title="生成大纲中"
open={sseModalVisible}
footer={null}
closable={false}
centered
width={500}
>
<div style={{ padding: '20px 0' }}>
<Progress
percent={sseProgress}
status={sseProgress === 100 ? 'success' : 'active'}
strokeColor={{
'0%': '#108ee9',
'100%': '#87d068',
}}
/>
<div style={{
marginTop: 16,
color: '#666',
fontSize: 14,
minHeight: 40,
lineHeight: '20px'
}}>
{sseMessage}
</div>
</div>
</Modal>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
{/* 固定头部 */}
<div style={{
position: 'sticky',
@@ -475,6 +543,7 @@ export default function Outline() {
</Card>
)}
</div>
</div>
</div>
</>
);
}
+57 -3
View File
@@ -1,7 +1,7 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Card, Button, Empty, Modal, message, Spin, Row, Col, Statistic, Space, Tag, Progress, Typography, Tooltip, Badge } from 'antd';
import { EditOutlined, DeleteOutlined, BookOutlined, RocketOutlined, CalendarOutlined, FileTextOutlined, TrophyOutlined, FireOutlined, SettingOutlined } from '@ant-design/icons';
import { Card, Button, Empty, Modal, message, Spin, Row, Col, Statistic, Space, Tag, Progress, Typography, Tooltip, Badge, Alert } from 'antd';
import { EditOutlined, DeleteOutlined, BookOutlined, RocketOutlined, CalendarOutlined, FileTextOutlined, TrophyOutlined, FireOutlined, SettingOutlined, InfoCircleOutlined, CloseOutlined } from '@ant-design/icons';
import { useStore } from '../store';
import { useProjectSync } from '../store/hooks';
import type { ReactNode } from 'react';
@@ -13,6 +13,7 @@ const { Title, Text, Paragraph } = Typography;
export default function ProjectList() {
const navigate = useNavigate();
const { projects, loading } = useStore();
const [showApiTip, setShowApiTip] = useState(true);
const { refreshProjects, deleteProject } = useProjectSync();
@@ -195,6 +196,59 @@ export default function ProjectList() {
</Col>
</Row>
{showApiTip && projects.length === 0 && (
<Alert
message={
<Space align="center" style={{ width: '100%' }}>
<InfoCircleOutlined style={{ fontSize: 16, color: '#1890ff' }} />
<Text strong style={{ fontSize: window.innerWidth <= 768 ? 13 : 14 }}>
使
</Text>
</Space>
}
description={
<Space direction="vertical" size={8} style={{ width: '100%' }}>
<Text style={{ fontSize: window.innerWidth <= 768 ? 12 : 13 }}>
AI接口OpenAI和Anthropic两种接口
</Text>
<Space size={8}>
<Button
type="primary"
size="small"
icon={<SettingOutlined />}
onClick={() => navigate('/settings')}
style={{
borderRadius: 6,
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
border: 'none'
}}
>
</Button>
<Button
size="small"
onClick={() => setShowApiTip(false)}
style={{ borderRadius: 6 }}
>
</Button>
</Space>
</Space>
}
type="info"
showIcon={false}
closable
closeIcon={<CloseOutlined style={{ fontSize: 12 }} />}
onClose={() => setShowApiTip(false)}
style={{
marginTop: window.innerWidth <= 768 ? 16 : 24,
borderRadius: 12,
background: 'linear-gradient(135deg, #e6f7ff 0%, #f0f5ff 100%)',
border: '1px solid #91d5ff'
}}
/>
)}
{projects.length > 0 && (
<Row gutter={[16, 16]} style={{ marginTop: window.innerWidth <= 768 ? 16 : 24 }}>
<Col xs={24} sm={8}>
+2 -2
View File
@@ -116,9 +116,9 @@ export default function SettingsPage() {
const apiProviders = [
{ value: 'openai', label: 'OpenAI', defaultUrl: 'https://api.openai.com/v1' },
{ value: 'azure', label: 'Azure OpenAI', defaultUrl: 'https://YOUR-RESOURCE.openai.azure.com' },
// { value: 'azure', label: 'Azure OpenAI', defaultUrl: 'https://YOUR-RESOURCE.openai.azure.com' },
{ value: 'anthropic', label: 'Anthropic', defaultUrl: 'https://api.anthropic.com' },
{ value: 'custom', label: '自定义', defaultUrl: '' },
// { value: 'custom', label: '自定义', defaultUrl: '' },
];
const handleProviderChange = (value: string) => {