update:新增用户API配置提示 优化大纲全新/续写的分批生成
This commit is contained in:
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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}>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user