update:1.更新AI生成角色/组织实现自动建立关系 2.新增AI续写大纲智能引入角色功能
This commit is contained in:
@@ -18,6 +18,7 @@ import Settings from './pages/Settings';
|
||||
import MCPPlugins from './pages/MCPPlugins';
|
||||
import UserManagement from './pages/UserManagement';
|
||||
import PromptTemplates from './pages/PromptTemplates';
|
||||
import Sponsor from './pages/Sponsor';
|
||||
// import Polish from './pages/Polish';
|
||||
import Login from './pages/Login';
|
||||
import AuthCallback from './pages/AuthCallback';
|
||||
@@ -37,7 +38,7 @@ function App() {
|
||||
<Routes>
|
||||
<Route path="/login" element={<><Login /><AppFooter /></>} />
|
||||
<Route path="/auth/callback" element={<AuthCallback />} />
|
||||
|
||||
|
||||
<Route path="/" element={<ProtectedRoute><><ProjectList /><AppFooter /></></ProtectedRoute>} />
|
||||
<Route path="/projects" element={<ProtectedRoute><><ProjectList /><AppFooter /></></ProtectedRoute>} />
|
||||
<Route path="/wizard" element={<ProtectedRoute><ProjectWizardNew /></ProtectedRoute>} />
|
||||
@@ -57,6 +58,7 @@ function App() {
|
||||
<Route path="chapters" element={<Chapters />} />
|
||||
<Route path="chapter-analysis" element={<ChapterAnalysis />} />
|
||||
<Route path="writing-styles" element={<WritingStyles />} />
|
||||
<Route path="sponsor" element={<Sponsor />} />
|
||||
{/* <Route path="polish" element={<Polish />} /> */}
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
+575
-240
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@ import {
|
||||
BankOutlined,
|
||||
EditOutlined,
|
||||
FundOutlined,
|
||||
HeartOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useStore } from '../store';
|
||||
import { useCharacterSync, useOutlineSync, useChapterSync } from '../store/hooks';
|
||||
@@ -66,7 +67,7 @@ export default function ProjectDetail() {
|
||||
// 加载项目基本信息
|
||||
const project = await projectApi.getProject(id);
|
||||
setCurrentProject(project);
|
||||
|
||||
|
||||
// 并行加载其他数据
|
||||
await Promise.all([
|
||||
refreshOutlines(id),
|
||||
@@ -138,6 +139,11 @@ export default function ProjectDetail() {
|
||||
// icon: <ToolOutlined />,
|
||||
// label: <Link to={`/project/${projectId}/polish`}>AI去味</Link>,
|
||||
// },
|
||||
{
|
||||
key: 'sponsor',
|
||||
icon: <HeartOutlined />,
|
||||
label: <Link to={`/project/${projectId}/sponsor`}>赞助支持</Link>,
|
||||
},
|
||||
];
|
||||
|
||||
// 根据当前路径动态确定选中的菜单项
|
||||
@@ -151,6 +157,7 @@ export default function ProjectDetail() {
|
||||
if (path.includes('/chapter-analysis')) return 'chapter-analysis';
|
||||
if (path.includes('/chapters')) return 'chapters';
|
||||
if (path.includes('/writing-styles')) return 'writing-styles';
|
||||
if (path.includes('/sponsor')) return 'sponsor';
|
||||
// if (path.includes('/polish')) return 'polish';
|
||||
return 'world-setting'; // 默认选中世界设定
|
||||
}, [location.pathname]);
|
||||
@@ -227,7 +234,7 @@ export default function ProjectDetail() {
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
<h2 style={{
|
||||
margin: 0,
|
||||
color: '#fff',
|
||||
@@ -247,7 +254,7 @@ export default function ProjectDetail() {
|
||||
}}>
|
||||
{currentProject.title}
|
||||
</h2>
|
||||
|
||||
|
||||
{mobile && (
|
||||
<Button
|
||||
type="text"
|
||||
@@ -264,94 +271,94 @@ export default function ProjectDetail() {
|
||||
主页
|
||||
</Button>
|
||||
)}
|
||||
|
||||
|
||||
{!mobile && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', zIndex: 1 }}>
|
||||
<Row gutter={12} style={{ width: '450px', justifyContent: 'flex-end' }}>
|
||||
<Col>
|
||||
<Card
|
||||
size="small"
|
||||
style={{
|
||||
background: 'rgba(255,255,255,0.95)',
|
||||
borderRadius: '6px',
|
||||
border: 'none',
|
||||
minWidth: '80px',
|
||||
textAlign: 'center',
|
||||
padding: '4px 8px'
|
||||
}}
|
||||
styles={{ body: { padding: '8px' } }}
|
||||
>
|
||||
<Statistic
|
||||
title={<span style={{ fontSize: '11px', color: '#666' }}>大纲</span>}
|
||||
value={outlines.length}
|
||||
suffix="条"
|
||||
valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#667eea' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col>
|
||||
<Card
|
||||
size="small"
|
||||
style={{
|
||||
background: 'rgba(255,255,255,0.95)',
|
||||
borderRadius: '6px',
|
||||
border: 'none',
|
||||
minWidth: '80px',
|
||||
textAlign: 'center',
|
||||
padding: '4px 8px'
|
||||
}}
|
||||
styles={{ body: { padding: '8px' } }}
|
||||
>
|
||||
<Statistic
|
||||
title={<span style={{ fontSize: '11px', color: '#666' }}>角色</span>}
|
||||
value={characters.length}
|
||||
suffix="个"
|
||||
valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#52c41a' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col>
|
||||
<Card
|
||||
size="small"
|
||||
style={{
|
||||
background: 'rgba(255,255,255,0.95)',
|
||||
borderRadius: '6px',
|
||||
border: 'none',
|
||||
minWidth: '80px',
|
||||
textAlign: 'center',
|
||||
padding: '4px 8px'
|
||||
}}
|
||||
styles={{ body: { padding: '8px' } }}
|
||||
>
|
||||
<Statistic
|
||||
title={<span style={{ fontSize: '11px', color: '#666' }}>章节</span>}
|
||||
value={chapters.length}
|
||||
suffix="章"
|
||||
valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#1890ff' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col>
|
||||
<Card
|
||||
size="small"
|
||||
style={{
|
||||
background: 'rgba(255,255,255,0.95)',
|
||||
borderRadius: '6px',
|
||||
border: 'none',
|
||||
minWidth: '80px',
|
||||
textAlign: 'center',
|
||||
padding: '4px 8px'
|
||||
}}
|
||||
styles={{ body: { padding: '8px' } }}
|
||||
>
|
||||
<Statistic
|
||||
title={<span style={{ fontSize: '11px', color: '#666' }}>已写</span>}
|
||||
value={currentProject.current_words}
|
||||
suffix="字"
|
||||
valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#fa8c16' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col>
|
||||
<Card
|
||||
size="small"
|
||||
style={{
|
||||
background: 'rgba(255,255,255,0.95)',
|
||||
borderRadius: '6px',
|
||||
border: 'none',
|
||||
minWidth: '80px',
|
||||
textAlign: 'center',
|
||||
padding: '4px 8px'
|
||||
}}
|
||||
styles={{ body: { padding: '8px' } }}
|
||||
>
|
||||
<Statistic
|
||||
title={<span style={{ fontSize: '11px', color: '#666' }}>大纲</span>}
|
||||
value={outlines.length}
|
||||
suffix="条"
|
||||
valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#667eea' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col>
|
||||
<Card
|
||||
size="small"
|
||||
style={{
|
||||
background: 'rgba(255,255,255,0.95)',
|
||||
borderRadius: '6px',
|
||||
border: 'none',
|
||||
minWidth: '80px',
|
||||
textAlign: 'center',
|
||||
padding: '4px 8px'
|
||||
}}
|
||||
styles={{ body: { padding: '8px' } }}
|
||||
>
|
||||
<Statistic
|
||||
title={<span style={{ fontSize: '11px', color: '#666' }}>角色</span>}
|
||||
value={characters.length}
|
||||
suffix="个"
|
||||
valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#52c41a' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col>
|
||||
<Card
|
||||
size="small"
|
||||
style={{
|
||||
background: 'rgba(255,255,255,0.95)',
|
||||
borderRadius: '6px',
|
||||
border: 'none',
|
||||
minWidth: '80px',
|
||||
textAlign: 'center',
|
||||
padding: '4px 8px'
|
||||
}}
|
||||
styles={{ body: { padding: '8px' } }}
|
||||
>
|
||||
<Statistic
|
||||
title={<span style={{ fontSize: '11px', color: '#666' }}>章节</span>}
|
||||
value={chapters.length}
|
||||
suffix="章"
|
||||
valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#1890ff' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col>
|
||||
<Card
|
||||
size="small"
|
||||
style={{
|
||||
background: 'rgba(255,255,255,0.95)',
|
||||
borderRadius: '6px',
|
||||
border: 'none',
|
||||
minWidth: '80px',
|
||||
textAlign: 'center',
|
||||
padding: '4px 8px'
|
||||
}}
|
||||
styles={{ body: { padding: '8px' } }}
|
||||
>
|
||||
<Statistic
|
||||
title={<span style={{ fontSize: '11px', color: '#666' }}>已写</span>}
|
||||
value={currentProject.current_words}
|
||||
suffix="字"
|
||||
valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#fa8c16' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)}
|
||||
@@ -371,23 +378,23 @@ export default function ProjectDetail() {
|
||||
</Drawer>
|
||||
) : (
|
||||
<Sider
|
||||
collapsible
|
||||
collapsed={collapsed}
|
||||
onCollapse={setCollapsed}
|
||||
trigger={null}
|
||||
width={220}
|
||||
collapsedWidth={60}
|
||||
style={{
|
||||
background: '#fff',
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
top: 70,
|
||||
bottom: 0,
|
||||
overflow: 'hidden',
|
||||
boxShadow: '2px 0 12px rgba(0,0,0,0.08)',
|
||||
transition: 'all 0.2s',
|
||||
height: 'calc(100vh - 70px)'
|
||||
}}
|
||||
collapsible
|
||||
collapsed={collapsed}
|
||||
onCollapse={setCollapsed}
|
||||
trigger={null}
|
||||
width={220}
|
||||
collapsedWidth={60}
|
||||
style={{
|
||||
background: '#fff',
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
top: 70,
|
||||
bottom: 0,
|
||||
overflow: 'hidden',
|
||||
boxShadow: '2px 0 12px rgba(0,0,0,0.08)',
|
||||
transition: 'all 0.2s',
|
||||
height: 'calc(100vh - 70px)'
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
height: '100%',
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
import { useState } from 'react';
|
||||
import { Card, Row, Col, Typography, Image, Divider, Modal, Button } from 'antd';
|
||||
import {
|
||||
HeartOutlined,
|
||||
CheckCircleOutlined,
|
||||
FileTextOutlined,
|
||||
RocketOutlined,
|
||||
MessageOutlined,
|
||||
StarOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Title, Paragraph, Text } = Typography;
|
||||
|
||||
interface SponsorOption {
|
||||
amount: number | string;
|
||||
label: string;
|
||||
image: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const sponsorOptions: SponsorOption[] = [
|
||||
{ amount: 5, label: '入门支持', image: '/5.png', description: '¥5' },
|
||||
{ amount: 10, label: '进阶支持', image: '/10.png', description: '¥10' },
|
||||
{ amount: 20, label: '标准支持', image: '/20.png', description: '¥20' },
|
||||
{ amount: 50, label: '高级支持', image: '/50.png', description: '¥50' },
|
||||
{ amount: 'custom', label: '任意金额', image: '/xx.png', description: '自定义' },
|
||||
];
|
||||
|
||||
const benefits = [
|
||||
{
|
||||
icon: <FileTextOutlined style={{ fontSize: '32px', color: '#1890ff' }} />,
|
||||
title: '优先需求响应',
|
||||
description: '您的功能需求和问题反馈将获得优先处理'
|
||||
},
|
||||
{
|
||||
icon: <RocketOutlined style={{ fontSize: '32px', color: '#52c41a' }} />,
|
||||
title: 'Windows一键启动',
|
||||
description: '获取免安装EXE程序,双击即可使用'
|
||||
},
|
||||
{
|
||||
icon: <MessageOutlined style={{ fontSize: '32px', color: '#fa8c16' }} />,
|
||||
title: '专属技术支持',
|
||||
description: '加入赞助者群,获得远程协助和配置指导'
|
||||
}
|
||||
];
|
||||
|
||||
export default function Sponsor() {
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState<SponsorOption | null>(null);
|
||||
|
||||
const handleCardClick = (option: SponsorOption) => {
|
||||
setSelectedOption(option);
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
height: '100%',
|
||||
overflowY: 'auto',
|
||||
padding: '16px'
|
||||
}}>
|
||||
<div style={{
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto'
|
||||
}}>
|
||||
{/* 头部标题区域 */}
|
||||
<div style={{ textAlign: 'center', marginBottom: '24px' }}>
|
||||
<Title level={1} style={{ marginBottom: '8px', fontSize: '32px', fontWeight: 'bold' }}>
|
||||
赞助 MuMuAINovel
|
||||
</Title>
|
||||
<Text type="secondary" style={{ fontSize: '13px', letterSpacing: '2px' }}>
|
||||
SUPPORT AI NOVEL CREATION
|
||||
</Text>
|
||||
|
||||
<div style={{
|
||||
marginTop: '16px',
|
||||
padding: '16px',
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
borderRadius: '12px',
|
||||
color: '#fff'
|
||||
}}>
|
||||
<Title level={4} style={{ color: '#fff', marginBottom: '8px' }}>
|
||||
📚 MuMuAINovel - 基于 AI 的智能小说创作助手
|
||||
</Title>
|
||||
<Paragraph style={{ color: '#fff', fontSize: '14px', margin: 0 }}>
|
||||
支持多AI模型、智能向导、角色管理、章节编辑等强大功能
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 赞助专属权益 */}
|
||||
<div style={{ marginBottom: '32px' }}>
|
||||
<Title level={3} style={{ textAlign: 'center', marginBottom: '20px' }}>
|
||||
<CheckCircleOutlined style={{ color: '#52c41a', marginRight: '8px' }} />
|
||||
赞助专属权益
|
||||
</Title>
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
{benefits.map((benefit, index) => (
|
||||
<Col xs={24} md={8} key={index}>
|
||||
<Card
|
||||
hoverable
|
||||
style={{
|
||||
height: '100%',
|
||||
textAlign: 'center',
|
||||
borderRadius: '10px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.08)'
|
||||
}}
|
||||
styles={{
|
||||
body: { padding: '20px 16px' }
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: '12px' }}>
|
||||
{benefit.icon}
|
||||
</div>
|
||||
<Title level={5} style={{ marginBottom: '8px' }}>{benefit.title}</Title>
|
||||
<Paragraph style={{ color: '#666', marginBottom: 0, fontSize: '13px' }}>
|
||||
{benefit.description}
|
||||
</Paragraph>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
{/* 选择金额 */}
|
||||
<div style={{ marginBottom: '32px' }}>
|
||||
<Title level={3} style={{ textAlign: 'center', marginBottom: '20px' }}>
|
||||
<HeartOutlined style={{ color: '#f5222d', marginRight: '8px' }} />
|
||||
选择金额
|
||||
</Title>
|
||||
|
||||
<Row gutter={[16, 16]} justify="center">
|
||||
{sponsorOptions.map((option, index) => (
|
||||
<Col xs={12} sm={8} md={6} lg={4} key={index}>
|
||||
<Card
|
||||
hoverable
|
||||
onClick={() => handleCardClick(option)}
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
borderRadius: '10px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s',
|
||||
border: '2px solid #f0f0f0'
|
||||
}}
|
||||
styles={{
|
||||
body: { padding: '20px 12px' }
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-8px)';
|
||||
e.currentTarget.style.boxShadow = '0 8px 24px rgba(0,0,0,0.15)';
|
||||
e.currentTarget.style.borderColor = '#1890ff';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.08)';
|
||||
e.currentTarget.style.borderColor = '#f0f0f0';
|
||||
}}
|
||||
>
|
||||
<Title level={3} style={{
|
||||
color: '#1890ff',
|
||||
marginBottom: '4px',
|
||||
fontSize: '28px',
|
||||
fontWeight: 'bold'
|
||||
}}>
|
||||
{option.description}
|
||||
</Title>
|
||||
<Text style={{ fontSize: '14px', color: '#666' }}>
|
||||
{option.label}
|
||||
</Text>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<Divider style={{ margin: '24px 0' }} />
|
||||
|
||||
{/* 感谢文案 */}
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
padding: '24px 20px',
|
||||
background: '#f9f9f9',
|
||||
borderRadius: '10px'
|
||||
}}>
|
||||
<Title level={4} style={{ marginBottom: '12px' }}>
|
||||
💖 感谢您对 MuMuAINovel 项目的支持
|
||||
</Title>
|
||||
<Paragraph style={{ fontSize: '14px', color: '#666', marginBottom: '12px' }}>
|
||||
您的赞助将帮助我们持续改进产品,提供更好的AI小说创作体验
|
||||
</Paragraph>
|
||||
<div style={{ fontSize: '24px' }}>
|
||||
<StarOutlined style={{ color: '#faad14', margin: '0 4px' }} />
|
||||
<StarOutlined style={{ color: '#faad14', margin: '0 4px' }} />
|
||||
<StarOutlined style={{ color: '#faad14', margin: '0 4px' }} />
|
||||
<StarOutlined style={{ color: '#faad14', margin: '0 4px' }} />
|
||||
<StarOutlined style={{ color: '#faad14', margin: '0 4px' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 二维码弹窗 */}
|
||||
<Modal
|
||||
title={
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Title level={3} style={{ marginBottom: '8px' }}>
|
||||
{selectedOption?.description} {selectedOption?.label}
|
||||
</Title>
|
||||
<Text type="secondary">请使用微信扫码支付</Text>
|
||||
</div>
|
||||
}
|
||||
open={modalVisible}
|
||||
onCancel={() => setModalVisible(false)}
|
||||
footer={[
|
||||
<Button key="close" type="primary" onClick={() => setModalVisible(false)}>
|
||||
关闭
|
||||
</Button>
|
||||
]}
|
||||
width={400}
|
||||
centered
|
||||
>
|
||||
<div style={{ textAlign: 'center', padding: '20px 0' }}>
|
||||
<Image
|
||||
src={selectedOption?.image}
|
||||
alt={`${selectedOption?.description}赞助码`}
|
||||
style={{
|
||||
maxWidth: '280px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #f0f0f0'
|
||||
}}
|
||||
preview={false}
|
||||
/>
|
||||
<Paragraph style={{ marginTop: '20px', color: '#666' }}>
|
||||
扫描二维码完成支付
|
||||
</Paragraph>
|
||||
<Paragraph style={{ color: '#999', fontSize: '12px' }}>
|
||||
支付后可添加微信/QQ联系我们获取权益
|
||||
</Paragraph>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+105
-76
@@ -72,11 +72,11 @@ api.interceptors.response.use(
|
||||
},
|
||||
(error) => {
|
||||
let errorMessage = '请求失败';
|
||||
|
||||
|
||||
if (error.response) {
|
||||
const status = error.response.status;
|
||||
const data = error.response.data;
|
||||
|
||||
|
||||
switch (status) {
|
||||
case 400:
|
||||
errorMessage = data?.detail || '请求参数错误';
|
||||
@@ -113,54 +113,54 @@ api.interceptors.response.use(
|
||||
} else {
|
||||
errorMessage = error.message || '请求失败';
|
||||
}
|
||||
|
||||
|
||||
message.error(errorMessage);
|
||||
console.error('API Error:', errorMessage, error);
|
||||
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export const authApi = {
|
||||
getAuthConfig: () => api.get<unknown, { local_auth_enabled: boolean; linuxdo_enabled: boolean }>('/auth/config'),
|
||||
|
||||
|
||||
localLogin: (username: string, password: string) =>
|
||||
api.post<unknown, { success: boolean; message: string; user: User }>('/auth/local/login', { username, password }),
|
||||
|
||||
|
||||
bindAccountLogin: (username: string, password: string) =>
|
||||
api.post<unknown, { success: boolean; message: string; user: User }>('/auth/bind/login', { username, password }),
|
||||
|
||||
|
||||
getLinuxDOAuthUrl: () => api.get<unknown, AuthUrlResponse>('/auth/linuxdo/url'),
|
||||
|
||||
|
||||
getCurrentUser: () => api.get<unknown, User>('/auth/user'),
|
||||
|
||||
|
||||
getPasswordStatus: () => api.get<unknown, {
|
||||
has_password: boolean;
|
||||
has_custom_password: boolean;
|
||||
username: string | null;
|
||||
default_password: string | null;
|
||||
}>('/auth/password/status'),
|
||||
|
||||
|
||||
setPassword: (password: string) =>
|
||||
api.post<unknown, { success: boolean; message: string }>('/auth/password/set', { password }),
|
||||
|
||||
|
||||
refreshSession: () => api.post<unknown, { message: string; expire_at: number; remaining_minutes: number }>('/auth/refresh'),
|
||||
|
||||
|
||||
logout: () => api.post('/auth/logout'),
|
||||
};
|
||||
|
||||
export const userApi = {
|
||||
getCurrentUser: () => api.get<unknown, User>('/users/current'),
|
||||
|
||||
|
||||
listUsers: () => api.get<unknown, User[]>('/users'),
|
||||
|
||||
|
||||
setAdmin: (userId: string, isAdmin: boolean) =>
|
||||
api.post('/users/set-admin', { user_id: userId, is_admin: isAdmin }),
|
||||
|
||||
|
||||
deleteUser: (userId: string) => api.delete(`/users/${userId}`),
|
||||
|
||||
|
||||
getUser: (userId: string) => api.get<unknown, User>(`/users/${userId}`),
|
||||
|
||||
|
||||
resetPassword: (userId: string, newPassword?: string) =>
|
||||
api.post<unknown, {
|
||||
message: string;
|
||||
@@ -172,18 +172,18 @@ export const userApi = {
|
||||
|
||||
export const settingsApi = {
|
||||
getSettings: () => api.get<unknown, Settings>('/settings'),
|
||||
|
||||
|
||||
saveSettings: (data: SettingsUpdate) =>
|
||||
api.post<unknown, Settings>('/settings', data),
|
||||
|
||||
|
||||
updateSettings: (data: SettingsUpdate) =>
|
||||
api.put<unknown, Settings>('/settings', data),
|
||||
|
||||
|
||||
deleteSettings: () => api.delete<unknown, { message: string; user_id: string }>('/settings'),
|
||||
|
||||
|
||||
getAvailableModels: (params: { api_key: string; api_base_url: string; provider: string }) =>
|
||||
api.get<unknown, { provider: string; models: Array<{ value: string; label: string; description: string }>; count?: number }>('/settings/models', { params }),
|
||||
|
||||
|
||||
testApiConnection: (params: { api_key: string; api_base_url: string; provider: string; llm_model: string }) =>
|
||||
api.post<unknown, {
|
||||
success: boolean;
|
||||
@@ -201,20 +201,20 @@ export const settingsApi = {
|
||||
|
||||
export const projectApi = {
|
||||
getProjects: () => api.get<unknown, Project[]>('/projects'),
|
||||
|
||||
|
||||
getProject: (id: string) => api.get<unknown, Project>(`/projects/${id}`),
|
||||
|
||||
|
||||
createProject: (data: ProjectCreate) => api.post<unknown, Project>('/projects', data),
|
||||
|
||||
|
||||
updateProject: (id: string, data: ProjectUpdate) =>
|
||||
api.put<unknown, Project>(`/projects/${id}`, data),
|
||||
|
||||
|
||||
deleteProject: (id: string) => api.delete(`/projects/${id}`),
|
||||
|
||||
|
||||
exportProject: (id: string) => {
|
||||
window.open(`/api/projects/${id}/export`, '_blank');
|
||||
},
|
||||
|
||||
|
||||
// 导出项目数据为JSON
|
||||
exportProjectData: async (id: string, options: { include_generation_history?: boolean; include_writing_styles?: boolean }) => {
|
||||
const response = await axios.post(
|
||||
@@ -227,7 +227,7 @@ export const projectApi = {
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// 从响应头获取文件名
|
||||
const contentDisposition = response.headers['content-disposition'];
|
||||
let filename = 'project_export.json';
|
||||
@@ -237,7 +237,7 @@ export const projectApi = {
|
||||
filename = decodeURIComponent(matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 创建下载链接
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||
const link = document.createElement('a');
|
||||
@@ -248,7 +248,7 @@ export const projectApi = {
|
||||
link.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
},
|
||||
|
||||
|
||||
// 验证导入文件
|
||||
validateImportFile: (file: File) => {
|
||||
const formData = new FormData();
|
||||
@@ -264,7 +264,7 @@ export const projectApi = {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// 导入项目
|
||||
importProject: (file: File) => {
|
||||
const formData = new FormData();
|
||||
@@ -284,22 +284,51 @@ export const projectApi = {
|
||||
export const outlineApi = {
|
||||
getOutlines: (projectId: string) =>
|
||||
api.get<unknown, { total: number; items: Outline[] }>(`/outlines/project/${projectId}`).then(res => res.items),
|
||||
|
||||
|
||||
getOutline: (id: string) => api.get<unknown, Outline>(`/outlines/${id}`),
|
||||
|
||||
|
||||
createOutline: (data: OutlineCreate) => api.post<unknown, Outline>('/outlines', data),
|
||||
|
||||
|
||||
updateOutline: (id: string, data: OutlineUpdate) =>
|
||||
api.put<unknown, Outline>(`/outlines/${id}`, data),
|
||||
|
||||
|
||||
deleteOutline: (id: string) => api.delete(`/outlines/${id}`),
|
||||
|
||||
|
||||
reorderOutlines: (data: OutlineReorderRequest) =>
|
||||
api.post<unknown, { message: string; updated_outlines: number; updated_chapters: number }>('/outlines/reorder', data),
|
||||
|
||||
|
||||
generateOutline: (data: GenerateOutlineRequest) =>
|
||||
api.post<unknown, { total: number; items: Outline[] }>('/outlines/generate', data).then(res => res.items),
|
||||
|
||||
|
||||
// 预测续写所需角色
|
||||
predictCharacters: (data: {
|
||||
project_id: string;
|
||||
start_chapter: number;
|
||||
chapter_count: number;
|
||||
plot_stage: string;
|
||||
story_direction?: string;
|
||||
enable_mcp: boolean;
|
||||
}) =>
|
||||
api.post<unknown, {
|
||||
needs_new_characters: boolean;
|
||||
reason: string;
|
||||
character_count: number;
|
||||
predicted_characters: Array<{
|
||||
name: string | null;
|
||||
role_description: string;
|
||||
suggested_role_type: string;
|
||||
importance: string;
|
||||
appearance_chapter: number;
|
||||
key_abilities: string[];
|
||||
plot_function: string;
|
||||
relationship_suggestions: Array<{
|
||||
target_character_name: string;
|
||||
relationship_type: string;
|
||||
description?: string;
|
||||
}>;
|
||||
}>;
|
||||
}>('/outlines/predict-characters', data),
|
||||
|
||||
// 获取大纲关联的章节
|
||||
getOutlineChapters: (outlineId: string) =>
|
||||
api.get<unknown, {
|
||||
@@ -333,11 +362,11 @@ export const outlineApi = {
|
||||
}> | 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, {
|
||||
@@ -354,7 +383,7 @@ export const outlineApi = {
|
||||
status: string;
|
||||
}>;
|
||||
}>(`/outlines/${outlineId}/create-chapters-from-plans`, { chapter_plans: chapterPlans }),
|
||||
|
||||
|
||||
// 批量展开大纲
|
||||
batchExpandOutlines: (data: BatchOutlineExpansionRequest) =>
|
||||
api.post<unknown, BatchOutlineExpansionResponse>('/outlines/batch-expand', data),
|
||||
@@ -363,9 +392,9 @@ export const outlineApi = {
|
||||
export const characterApi = {
|
||||
getCharacters: (projectId: string) =>
|
||||
api.get<unknown, Character[]>(`/characters/project/${projectId}`),
|
||||
|
||||
|
||||
getCharacter: (id: string) => api.get<unknown, Character>(`/characters/${id}`),
|
||||
|
||||
|
||||
createCharacter: (data: {
|
||||
project_id: string;
|
||||
name: string;
|
||||
@@ -388,12 +417,12 @@ export const characterApi = {
|
||||
color?: string;
|
||||
}) =>
|
||||
api.post<unknown, Character>('/characters', data),
|
||||
|
||||
|
||||
updateCharacter: (id: string, data: CharacterUpdate) =>
|
||||
api.put<unknown, Character>(`/characters/${id}`, data),
|
||||
|
||||
|
||||
deleteCharacter: (id: string) => api.delete(`/characters/${id}`),
|
||||
|
||||
|
||||
generateCharacter: (data: GenerateCharacterRequest) =>
|
||||
api.post<unknown, Character>('/characters/generate', data),
|
||||
};
|
||||
@@ -401,19 +430,19 @@ export const characterApi = {
|
||||
export const chapterApi = {
|
||||
getChapters: (projectId: string) =>
|
||||
api.get<unknown, Chapter[]>(`/chapters/project/${projectId}`),
|
||||
|
||||
|
||||
getChapter: (id: string) => api.get<unknown, Chapter>(`/chapters/${id}`),
|
||||
|
||||
|
||||
createChapter: (data: ChapterCreate) => api.post<unknown, Chapter>('/chapters', data),
|
||||
|
||||
|
||||
updateChapter: (id: string, data: ChapterUpdate) =>
|
||||
api.put<unknown, Chapter>(`/chapters/${id}`, data),
|
||||
|
||||
|
||||
deleteChapter: (id: string) => api.delete(`/chapters/${id}`),
|
||||
|
||||
|
||||
checkCanGenerate: (chapterId: string) =>
|
||||
api.get<unknown, import('../types').ChapterCanGenerateResponse>(`/chapters/${chapterId}/can-generate`),
|
||||
|
||||
|
||||
// 章节重新生成相关
|
||||
getRegenerationTasks: (chapterId: string, limit?: number) =>
|
||||
api.get<unknown, {
|
||||
@@ -436,31 +465,31 @@ export const writingStyleApi = {
|
||||
// 获取预设风格列表
|
||||
getPresetStyles: () =>
|
||||
api.get<unknown, PresetStyle[]>('/writing-styles/presets/list'),
|
||||
|
||||
|
||||
// 获取用户的所有风格(新接口)
|
||||
getUserStyles: () =>
|
||||
api.get<unknown, WritingStyleListResponse>('/writing-styles/user'),
|
||||
|
||||
|
||||
// 获取项目的所有风格(保留向后兼容)
|
||||
getProjectStyles: (projectId: string) =>
|
||||
api.get<unknown, WritingStyleListResponse>(`/writing-styles/project/${projectId}`),
|
||||
|
||||
|
||||
// 创建新风格(基于预设或自定义)
|
||||
createStyle: (data: WritingStyleCreate) =>
|
||||
api.post<unknown, WritingStyle>('/writing-styles', data),
|
||||
|
||||
|
||||
// 更新风格
|
||||
updateStyle: (styleId: number, data: WritingStyleUpdate) =>
|
||||
api.put<unknown, WritingStyle>(`/writing-styles/${styleId}`, data),
|
||||
|
||||
|
||||
// 删除风格
|
||||
deleteStyle: (styleId: number) =>
|
||||
api.delete<unknown, { message: string }>(`/writing-styles/${styleId}`),
|
||||
|
||||
|
||||
// 设置默认风格
|
||||
setDefaultStyle: (styleId: number, projectId: string) =>
|
||||
api.post<unknown, WritingStyle>(`/writing-styles/${styleId}/set-default`, { project_id: projectId }),
|
||||
|
||||
|
||||
// 为项目初始化默认风格(如果没有任何风格)
|
||||
initializeDefaultStyles: (projectId: string) =>
|
||||
api.post<unknown, WritingStyleListResponse>(`/writing-styles/project/${projectId}/initialize`, {}),
|
||||
@@ -469,7 +498,7 @@ export const writingStyleApi = {
|
||||
export const polishApi = {
|
||||
polishText: (data: PolishTextRequest) =>
|
||||
api.post<unknown, { polished_text: string }>('/polish', data),
|
||||
|
||||
|
||||
polishBatch: (texts: string[]) =>
|
||||
api.post<unknown, { polished_texts: string[] }>('/polish/batch', { texts }),
|
||||
};
|
||||
@@ -488,7 +517,7 @@ export const inspirationApi = {
|
||||
options: string[];
|
||||
error?: string;
|
||||
}>('/inspiration/generate-options', data),
|
||||
|
||||
|
||||
// 智能补全缺失信息
|
||||
quickGenerate: (data: {
|
||||
title?: string;
|
||||
@@ -607,39 +636,39 @@ export const mcpPluginApi = {
|
||||
// 获取所有插件
|
||||
getPlugins: () =>
|
||||
api.get<unknown, MCPPlugin[]>('/mcp/plugins'),
|
||||
|
||||
|
||||
// 获取单个插件
|
||||
getPlugin: (id: string) =>
|
||||
api.get<unknown, MCPPlugin>(`/mcp/plugins/${id}`),
|
||||
|
||||
|
||||
// 创建插件
|
||||
createPlugin: (data: MCPPluginCreate) =>
|
||||
api.post<unknown, MCPPlugin>('/mcp/plugins', data),
|
||||
|
||||
|
||||
// 简化创建插件(通过标准MCP配置JSON)
|
||||
createPluginSimple: (data: MCPPluginSimpleCreate) =>
|
||||
api.post<unknown, MCPPlugin>('/mcp/plugins/simple', data),
|
||||
|
||||
|
||||
// 更新插件
|
||||
updatePlugin: (id: string, data: MCPPluginUpdate) =>
|
||||
api.put<unknown, MCPPlugin>(`/mcp/plugins/${id}`, data),
|
||||
|
||||
|
||||
// 删除插件
|
||||
deletePlugin: (id: string) =>
|
||||
api.delete<unknown, { message: string }>(`/mcp/plugins/${id}`),
|
||||
|
||||
|
||||
// 启用/禁用插件
|
||||
togglePlugin: (id: string, enabled: boolean) =>
|
||||
api.post<unknown, MCPPlugin>(`/mcp/plugins/${id}/toggle`, null, { params: { enabled } }),
|
||||
|
||||
|
||||
// 测试插件连接
|
||||
testPlugin: (id: string) =>
|
||||
api.post<unknown, MCPTestResult>(`/mcp/plugins/${id}/test`),
|
||||
|
||||
|
||||
// 获取插件工具列表
|
||||
getPluginTools: (id: string) =>
|
||||
api.get<unknown, { tools: MCPTool[] }>(`/mcp/plugins/${id}/tools`),
|
||||
|
||||
|
||||
// 调用工具
|
||||
callTool: (data: MCPToolCallRequest) =>
|
||||
api.post<unknown, MCPToolCallResponse>('/mcp/call', data),
|
||||
@@ -650,7 +679,7 @@ export const adminApi = {
|
||||
// 获取用户列表
|
||||
getUsers: () =>
|
||||
api.get<unknown, { total: number; users: User[] }>('/admin/users'),
|
||||
|
||||
|
||||
// 添加用户
|
||||
createUser: (data: {
|
||||
username: string;
|
||||
@@ -666,7 +695,7 @@ export const adminApi = {
|
||||
user: User;
|
||||
default_password?: string;
|
||||
}>('/admin/users', data),
|
||||
|
||||
|
||||
// 编辑用户
|
||||
updateUser: (userId: string, data: {
|
||||
display_name?: string;
|
||||
@@ -678,7 +707,7 @@ export const adminApi = {
|
||||
message: string;
|
||||
user: User;
|
||||
}>(`/admin/users/${userId}`, data),
|
||||
|
||||
|
||||
// 切换用户状态(启用/禁用)
|
||||
toggleUserStatus: (userId: string, isActive: boolean) =>
|
||||
api.post<unknown, {
|
||||
@@ -686,7 +715,7 @@ export const adminApi = {
|
||||
message: string;
|
||||
is_active: boolean;
|
||||
}>(`/admin/users/${userId}/toggle-status`, { is_active: isActive }),
|
||||
|
||||
|
||||
// 重置密码
|
||||
resetPassword: (userId: string, newPassword?: string) =>
|
||||
api.post<unknown, {
|
||||
@@ -694,7 +723,7 @@ export const adminApi = {
|
||||
message: string;
|
||||
new_password: string;
|
||||
}>(`/admin/users/${userId}/reset-password`, { new_password: newPassword }),
|
||||
|
||||
|
||||
// 删除用户
|
||||
deleteUser: (userId: string) =>
|
||||
api.delete<unknown, {
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface SSEClientOptions {
|
||||
onError?: (error: string, code?: number) => void;
|
||||
onComplete?: () => void;
|
||||
onConnectionError?: (error: Event) => void;
|
||||
onCharacterConfirmation?: (data: any) => void; // 新增:角色确认回调
|
||||
}
|
||||
|
||||
export class SSEClient {
|
||||
@@ -34,7 +35,7 @@ export class SSEClient {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this.eventSource = new EventSource(this.url);
|
||||
|
||||
|
||||
this.eventSource.onmessage = (event) => {
|
||||
try {
|
||||
const message: SSEMessage = JSON.parse(event.data);
|
||||
@@ -160,6 +161,7 @@ export class SSEPostClient {
|
||||
}
|
||||
|
||||
let buffer = '';
|
||||
let currentEvent = ''; // 跟踪当前事件类型
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
@@ -169,7 +171,7 @@ export class SSEPostClient {
|
||||
}
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
|
||||
|
||||
const lines = buffer.split('\n\n');
|
||||
buffer = lines.pop() || '';
|
||||
|
||||
@@ -179,10 +181,31 @@ export class SSEPostClient {
|
||||
}
|
||||
|
||||
try {
|
||||
// 检查是否有事件类型
|
||||
const eventMatch = line.match(/^event: (.+)$/m);
|
||||
if (eventMatch) {
|
||||
currentEvent = eventMatch[1];
|
||||
}
|
||||
|
||||
// 解析数据
|
||||
const dataMatch = line.match(/^data: (.+)$/m);
|
||||
if (dataMatch) {
|
||||
const message: SSEMessage = JSON.parse(dataMatch[1]);
|
||||
await this.handleMessage(message, resolve, reject);
|
||||
const data = JSON.parse(dataMatch[1]);
|
||||
|
||||
// 根据事件类型处理
|
||||
if (currentEvent === 'character_confirmation_required') {
|
||||
// 处理角色确认事件
|
||||
if (this.options.onCharacterConfirmation) {
|
||||
this.options.onCharacterConfirmation(data);
|
||||
}
|
||||
currentEvent = ''; // 重置事件类型
|
||||
return; // 暂停流程,等待用户确认
|
||||
} else {
|
||||
// 标准消息处理
|
||||
const message: SSEMessage = data;
|
||||
await this.handleMessage(message, resolve, reject);
|
||||
currentEvent = ''; // 重置事件类型
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析SSE消息失败:', error, line);
|
||||
|
||||
Reference in New Issue
Block a user