update:1.更新根据分析建议重新生成章节内容

This commit is contained in:
xiamuceer
2025-11-11 19:50:12 +08:00
parent 5b46d657f3
commit 913edd0cce
30 changed files with 3896 additions and 1928 deletions
+140 -2
View File
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Spin, Result, Button } from 'antd';
import { Spin, Result, Button, Modal, Input, message } from 'antd';
import { authApi } from '../services/api';
import AnnouncementModal from '../components/AnnouncementModal';
@@ -9,6 +9,11 @@ export default function AuthCallback() {
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
const [errorMessage, setErrorMessage] = useState('');
const [showAnnouncement, setShowAnnouncement] = useState(false);
const [showPasswordModal, setShowPasswordModal] = useState(false);
const [passwordStatus, setPasswordStatus] = useState<any>(null);
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [settingPassword, setSettingPassword] = useState(false);
useEffect(() => {
const handleCallback = async () => {
@@ -17,8 +22,21 @@ export default function AuthCallback() {
// 这里只需要验证登录状态
await authApi.getCurrentUser();
// 检查密码状态
const pwdStatus = await authApi.getPasswordStatus();
setPasswordStatus(pwdStatus);
setStatus('success');
// 只有在用户完全没有密码时才显示密码设置提示
// 如果已经有密码(无论是默认密码还是自定义密码),都不再提示
if (!pwdStatus.has_password) {
setTimeout(() => {
setShowPasswordModal(true);
}, 1000);
return;
}
// 从 sessionStorage 获取重定向地址
const redirect = sessionStorage.getItem('login_redirect') || '/';
sessionStorage.removeItem('login_redirect');
@@ -105,6 +123,70 @@ export default function AuthCallback() {
localStorage.setItem('announcement_do_not_show_until', tomorrow.getTime().toString());
};
const handleSetPassword = async () => {
if (!newPassword) {
message.error('请输入新密码');
return;
}
if (newPassword.length < 6) {
message.error('密码长度至少为6个字符');
return;
}
if (newPassword !== confirmPassword) {
message.error('两次输入的密码不一致');
return;
}
setSettingPassword(true);
try {
await authApi.setPassword(newPassword);
message.success('密码设置成功');
setShowPasswordModal(false);
// 继续后续流程
const redirect = sessionStorage.getItem('login_redirect') || '/';
sessionStorage.removeItem('login_redirect');
const doNotShowUntil = localStorage.getItem('announcement_do_not_show_until');
const now = new Date().getTime();
if (!doNotShowUntil || now > parseInt(doNotShowUntil)) {
setTimeout(() => {
setShowAnnouncement(true);
}, 500);
} else {
setTimeout(() => {
navigate(redirect);
}, 500);
}
} catch (error) {
message.error('密码设置失败,请重试');
} finally {
setSettingPassword(false);
}
};
const handleSkipPasswordSetting = () => {
setShowPasswordModal(false);
// 继续后续流程
const redirect = sessionStorage.getItem('login_redirect') || '/';
sessionStorage.removeItem('login_redirect');
const doNotShowUntil = localStorage.getItem('announcement_do_not_show_until');
const now = new Date().getTime();
if (!doNotShowUntil || now > parseInt(doNotShowUntil)) {
setTimeout(() => {
setShowAnnouncement(true);
}, 500);
} else {
setTimeout(() => {
navigate(redirect);
}, 500);
}
};
return (
<>
<AnnouncementModal
@@ -112,6 +194,62 @@ export default function AuthCallback() {
onClose={handleAnnouncementClose}
onDoNotShowToday={handleDoNotShowToday}
/>
<Modal
title="设置账号密码"
open={showPasswordModal}
centered
onOk={handleSetPassword}
onCancel={handleSkipPasswordSetting}
confirmLoading={settingPassword}
okText="设置密码"
cancelText="暂不设置"
width={500}
>
<div style={{ marginBottom: 20 }}>
<p> Linux DO </p>
<p>使</p>
{passwordStatus?.default_password && (
<div style={{
background: '#f0f2f5',
padding: 12,
borderRadius: 4,
marginTop: 12
}}>
<strong></strong>{passwordStatus.username}<br/>
<strong></strong><code style={{
background: '#fff',
padding: '2px 8px',
borderRadius: 3,
color: '#1890ff',
fontSize: 14
}}>{passwordStatus.default_password}</code>
</div>
)}
</div>
<div style={{ marginTop: 20 }}>
<div style={{ marginBottom: 12 }}>
<label>6</label>
<Input.Password
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
placeholder="请输入新密码"
style={{ marginTop: 4 }}
/>
</div>
<div>
<label></label>
<Input.Password
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="请再次输入密码"
style={{ marginTop: 4 }}
/>
</div>
</div>
</Modal>
<div style={{
display: 'flex',
justifyContent: 'center',
@@ -122,7 +260,7 @@ export default function AuthCallback() {
<Result
status="success"
title="登录成功"
subTitle={showAnnouncement ? "欢迎使用..." : "正在跳转..."}
subTitle={showPasswordModal ? "请设置账号密码..." : (showAnnouncement ? "欢迎使用..." : "正在跳转...")}
style={{ background: 'white', padding: 40, borderRadius: 8 }}
/>
</div>
+1
View File
@@ -600,6 +600,7 @@ export default function MCPPluginsPage() {
<Modal
title={editingPlugin ? '编辑插件' : '添加插件'}
open={modalVisible}
centered
onCancel={() => {
setModalVisible(false);
form.resetFields();
+45 -80
View File
@@ -73,23 +73,8 @@ export default function ProjectList() {
};
const handleEnterProject = (id: string) => {
const project = projects.find(p => p.id === id);
if (project) {
console.log('项目信息:', {
id: project.id,
title: project.title,
wizard_status: project.wizard_status,
wizard_step: project.wizard_step
});
if (project.wizard_status === 'incomplete' || !project.wizard_status) {
console.log('向导未完成,跳转到向导页面');
navigate(`/wizard?projectId=${id}&step=${project.wizard_step || 0}`);
} else {
console.log('向导已完成,进入项目管理界面');
navigate(`/project/${id}`);
}
}
// 简化后直接进入项目,不再检查向导状态
navigate(`/project/${id}`);
};
const getStatusTag = (status: string) => {
@@ -207,8 +192,8 @@ export default function ProjectList() {
setSelectedProjectIds([]);
};
// 获取可导出的项目(过滤掉向导未完成的项目)
const exportableProjects = projects.filter(p => p.wizard_status === 'completed');
// 获取所有可导出的项目
const exportableProjects = projects;
// 关闭导出对话框
const handleCloseExportModal = () => {
@@ -631,12 +616,11 @@ export default function ProjectList() {
<Row gutter={[16, 16]}>
{projects.map((project) => {
const progress = getProgress(project.current_words, project.target_words || 0);
const isWizardComplete = project.wizard_status === 'completed';
return (
<Col {...gridConfig} key={project.id}>
<Badge.Ribbon
text={isWizardComplete ? getStatusTag(project.status) : <Tag color="orange" icon={<RocketOutlined />}></Tag>}
text={getStatusTag(project.status)}
color="transparent"
style={{ top: 12, right: 12 }}
>
@@ -680,69 +664,50 @@ export default function ProjectList() {
{project.description || '暂无描述'}
</Paragraph>
{isWizardComplete ? (
<>
{project.target_words && project.target_words > 0 && (
<div style={{ marginBottom: 16 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
<Text type="secondary" style={{ fontSize: 12 }}></Text>
<Text strong style={{ fontSize: 12 }}>{progress}%</Text>
</div>
<Progress
percent={progress}
strokeColor={getProgressColor(progress)}
showInfo={false}
size={{ height: 8 }}
/>
</div>
)}
<Row gutter={12}>
<Col span={12}>
<div style={{
textAlign: 'center',
padding: '12px 0',
background: '#f5f5f5',
borderRadius: 8
}}>
<div style={{ fontSize: 20, fontWeight: 'bold', color: '#1890ff' }}>
{(project.current_words / 1000).toFixed(1)}K
</div>
<Text type="secondary" style={{ fontSize: 12 }}></Text>
</div>
</Col>
<Col span={12}>
<div style={{
textAlign: 'center',
padding: '12px 0',
background: '#f5f5f5',
borderRadius: 8
}}>
<div style={{ fontSize: 20, fontWeight: 'bold', color: '#52c41a' }}>
{project.target_words ? (project.target_words / 1000).toFixed(0) + 'K' : '--'}
</div>
<Text type="secondary" style={{ fontSize: 12 }}></Text>
</div>
</Col>
</Row>
</>
) : (
<div style={{
textAlign: 'center',
padding: '24px 0',
background: '#f5f5f5',
borderRadius: 8
}}>
<RocketOutlined style={{ fontSize: 32, color: '#faad14', marginBottom: 12 }} />
<div style={{ color: '#faad14', fontWeight: 'bold', marginBottom: 4 }}>
{project.target_words && project.target_words > 0 && (
<div style={{ marginBottom: 16 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
<Text type="secondary" style={{ fontSize: 12 }}></Text>
<Text strong style={{ fontSize: 12 }}>{progress}%</Text>
</div>
<Text type="secondary" style={{ fontSize: 12 }}>
</Text>
<Progress
percent={progress}
strokeColor={getProgressColor(progress)}
showInfo={false}
size={{ height: 8 }}
/>
</div>
)}
<Row gutter={12}>
<Col span={12}>
<div style={{
textAlign: 'center',
padding: '12px 0',
background: '#f5f5f5',
borderRadius: 8
}}>
<div style={{ fontSize: 20, fontWeight: 'bold', color: '#1890ff' }}>
{(project.current_words / 1000).toFixed(1)}K
</div>
<Text type="secondary" style={{ fontSize: 12 }}></Text>
</div>
</Col>
<Col span={12}>
<div style={{
textAlign: 'center',
padding: '12px 0',
background: '#f5f5f5',
borderRadius: 8
}}>
<div style={{ fontSize: 20, fontWeight: 'bold', color: '#52c41a' }}>
{project.target_words ? (project.target_words / 1000).toFixed(0) + 'K' : '--'}
</div>
<Text type="secondary" style={{ fontSize: 12 }}></Text>
</div>
</Col>
</Row>
<div style={{
marginTop: 16,
paddingTop: 16,
File diff suppressed because it is too large Load Diff
+126 -6
View File
@@ -1,12 +1,18 @@
import { Card, Descriptions, Empty, Typography } from 'antd';
import { GlobalOutlined } from '@ant-design/icons';
import { Card, Descriptions, Empty, Typography, Button, Modal, Form, Input, message } from 'antd';
import { GlobalOutlined, EditOutlined } from '@ant-design/icons';
import { useState } from 'react';
import { useStore } from '../store';
import { cardStyles } from '../components/CardStyles';
import { projectApi } from '../services/api';
const { Title, Paragraph } = Typography;
const { TextArea } = Input;
export default function WorldSetting() {
const { currentProject } = useStore();
const { currentProject, setCurrentProject } = useStore();
const [isEditModalVisible, setIsEditModalVisible] = useState(false);
const [editForm] = Form.useForm();
const [isSaving, setIsSaving] = useState(false);
if (!currentProject) return null;
@@ -62,10 +68,28 @@ export default function WorldSetting() {
marginBottom: 24,
borderBottom: '1px solid #f0f0f0',
display: 'flex',
alignItems: 'center'
alignItems: 'center',
justifyContent: 'space-between'
}}>
<GlobalOutlined style={{ fontSize: 24, marginRight: 12, color: '#1890ff' }} />
<h2 style={{ margin: 0 }}></h2>
<div style={{ display: 'flex', alignItems: 'center' }}>
<GlobalOutlined style={{ fontSize: 24, marginRight: 12, color: '#1890ff' }} />
<h2 style={{ margin: 0 }}></h2>
</div>
<Button
type="primary"
icon={<EditOutlined />}
onClick={() => {
editForm.setFieldsValue({
world_time_period: currentProject.world_time_period || '',
world_location: currentProject.world_location || '',
world_atmosphere: currentProject.world_atmosphere || '',
world_rules: currentProject.world_rules || '',
});
setIsEditModalVisible(true);
}}
>
</Button>
</div>
{/* 可滚动内容区域 */}
@@ -182,6 +206,102 @@ export default function WorldSetting() {
</div>
</Card>
</div>
{/* 编辑世界观模态框 */}
<Modal
title="编辑世界观"
open={isEditModalVisible}
centered
onCancel={() => {
setIsEditModalVisible(false);
editForm.resetFields();
}}
onOk={async () => {
try {
const values = await editForm.validateFields();
setIsSaving(true);
const updatedProject = await projectApi.updateProject(currentProject.id, {
world_time_period: values.world_time_period,
world_location: values.world_location,
world_atmosphere: values.world_atmosphere,
world_rules: values.world_rules,
});
setCurrentProject(updatedProject);
message.success('世界观更新成功');
setIsEditModalVisible(false);
editForm.resetFields();
} catch (error) {
console.error('更新世界观失败:', error);
message.error('更新失败,请重试');
} finally {
setIsSaving(false);
}
}}
confirmLoading={isSaving}
width={800}
okText="保存"
cancelText="取消"
>
<Form
form={editForm}
layout="vertical"
style={{ marginTop: 16 }}
>
<Form.Item
label="时间设定"
name="world_time_period"
rules={[{ required: true, message: '请输入时间设定' }]}
>
<TextArea
rows={4}
placeholder="描述故事发生的时代背景..."
showCount
maxLength={1000}
/>
</Form.Item>
<Form.Item
label="地点设定"
name="world_location"
rules={[{ required: true, message: '请输入地点设定' }]}
>
<TextArea
rows={4}
placeholder="描述故事发生的地理位置和环境..."
showCount
maxLength={1000}
/>
</Form.Item>
<Form.Item
label="氛围设定"
name="world_atmosphere"
rules={[{ required: true, message: '请输入氛围设定' }]}
>
<TextArea
rows={4}
placeholder="描述故事的整体氛围和基调..."
showCount
maxLength={1000}
/>
</Form.Item>
<Form.Item
label="规则设定"
name="world_rules"
rules={[{ required: true, message: '请输入规则设定' }]}
>
<TextArea
rows={4}
placeholder="描述这个世界的特殊规则和设定..."
showCount
maxLength={1000}
/>
</Form.Item>
</Form>
</Modal>
</div>
);
}