fix: 修复多处TypeScript类型错误(Inspiration、ExpansionPlanEditor、sseClient等)
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
# 应用配置
|
# 应用配置
|
||||||
# ==========================================
|
# ==========================================
|
||||||
APP_NAME=MuMuAINovel
|
APP_NAME=MuMuAINovel
|
||||||
APP_VERSION=1.2.7
|
APP_VERSION=1.2.8
|
||||||
APP_HOST=0.0.0.0
|
APP_HOST=0.0.0.0
|
||||||
APP_PORT=8000
|
APP_PORT=8000
|
||||||
DEBUG=false
|
DEBUG=false
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.2.7",
|
"version": "1.2.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 188 KiB |
@@ -40,8 +40,8 @@ function App() {
|
|||||||
<Route path="/login" element={<><Login /><AppFooter /></>} />
|
<Route path="/login" element={<><Login /><AppFooter /></>} />
|
||||||
<Route path="/auth/callback" element={<AuthCallback />} />
|
<Route path="/auth/callback" element={<AuthCallback />} />
|
||||||
|
|
||||||
<Route path="/" element={<ProtectedRoute><><ProjectList /><AppFooter /></></ProtectedRoute>} />
|
<Route path="/" element={<ProtectedRoute><><ProjectList /><AppFooter sidebarWidth={220} /></></ProtectedRoute>} />
|
||||||
<Route path="/projects" element={<ProtectedRoute><><ProjectList /><AppFooter /></></ProtectedRoute>} />
|
<Route path="/projects" element={<ProtectedRoute><><ProjectList /><AppFooter sidebarWidth={220} /></></ProtectedRoute>} />
|
||||||
<Route path="/wizard" element={<ProtectedRoute><ProjectWizardNew /></ProtectedRoute>} />
|
<Route path="/wizard" element={<ProtectedRoute><ProjectWizardNew /></ProtectedRoute>} />
|
||||||
<Route path="/inspiration" element={<ProtectedRoute><Inspiration /></ProtectedRoute>} />
|
<Route path="/inspiration" element={<ProtectedRoute><Inspiration /></ProtectedRoute>} />
|
||||||
<Route path="/settings" element={<ProtectedRoute><Settings /></ProtectedRoute>} />
|
<Route path="/settings" element={<ProtectedRoute><Settings /></ProtectedRoute>} />
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export interface MemoryAnnotation {
|
|||||||
strength?: number;
|
strength?: number;
|
||||||
foreshadowType?: 'planted' | 'resolved';
|
foreshadowType?: 'planted' | 'resolved';
|
||||||
relatedCharacters?: string[];
|
relatedCharacters?: string[];
|
||||||
[key: string]: any;
|
[key: string]: unknown;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,17 +73,17 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
width={800}
|
width={700}
|
||||||
centered
|
centered
|
||||||
styles={{
|
styles={{
|
||||||
body: {
|
body: {
|
||||||
padding: '24px',
|
padding: '20px',
|
||||||
background: 'var(--color-bg-container)',
|
background: 'var(--color-bg-container)',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
background: 'linear-gradient(135deg, rgba(77, 128, 136, 0.08) 0%, rgba(248, 246, 241, 0.95) 100%)',
|
background: 'linear-gradient(135deg, rgba(77, 128, 136, 0.08) 0%, rgba(248, 246, 241, 0.95) 100%)',
|
||||||
borderBottom: '1px solid var(--color-border-secondary)',
|
borderBottom: '1px solid var(--color-border-secondary)',
|
||||||
padding: '20px 24px',
|
padding: '16px 24px',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
background: 'var(--color-bg-container)',
|
background: 'var(--color-bg-container)',
|
||||||
@@ -94,25 +94,24 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
>
|
>
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<div style={{
|
<div style={{
|
||||||
marginBottom: '16px',
|
marginBottom: '12px',
|
||||||
fontSize: '16px',
|
fontSize: '15px',
|
||||||
color: 'var(--color-text-secondary)',
|
color: 'var(--color-text-secondary)',
|
||||||
lineHeight: '1.6',
|
lineHeight: '1.5',
|
||||||
}}>
|
}}>
|
||||||
<p>👋 欢迎加入我们的交流群!</p>
|
<p style={{ marginBottom: '8px' }}>👋 欢迎加入我们的交流群!在这里你可以:</p>
|
||||||
<p>在这里你可以:</p>
|
|
||||||
<ul style={{
|
<ul style={{
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
marginLeft: '40px',
|
marginLeft: '40px',
|
||||||
marginTop: '12px',
|
marginTop: '0',
|
||||||
marginBottom: '20px',
|
marginBottom: '12px',
|
||||||
}}>
|
}}>
|
||||||
<li>💬 与其他创作者交流心得</li>
|
<li>💬 与其他创作者交流心得</li>
|
||||||
<li>💡 获取最新功能更新和使用技巧</li>
|
<li>💡 获取最新功能更新和使用技巧</li>
|
||||||
<li>🐛 反馈问题和建议</li>
|
<li>🐛 反馈问题和建议</li>
|
||||||
<li>📚 分享创作经验和灵感</li>
|
<li>📚 分享创作经验和灵感</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p style={{ fontWeight: 600, color: 'var(--color-text-primary)', marginBottom: '16px' }}>
|
<p style={{ fontWeight: 600, color: 'var(--color-text-primary)', marginBottom: '12px' }}>
|
||||||
扫描下方二维码加入交流群:
|
扫描下方二维码加入交流群:
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,7 +121,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
gap: '24px',
|
gap: '24px',
|
||||||
padding: '20px',
|
padding: '16px',
|
||||||
background: 'var(--color-bg-layout)',
|
background: 'var(--color-bg-layout)',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
@@ -132,9 +131,9 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
minWidth: '280px',
|
minWidth: '200px',
|
||||||
}}>
|
}}>
|
||||||
<p style={{ fontWeight: 600, color: 'var(--color-text-primary)', marginBottom: '12px', fontSize: '15px' }}>
|
<p style={{ fontWeight: 600, color: 'var(--color-text-primary)', marginBottom: '8px', fontSize: '14px' }}>
|
||||||
QQ交流群
|
QQ交流群
|
||||||
</p>
|
</p>
|
||||||
{!qqImageError ? (
|
{!qqImageError ? (
|
||||||
@@ -144,15 +143,15 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
background: 'var(--color-bg-container)',
|
background: 'var(--color-bg-container)',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
padding: '8px',
|
padding: '6px',
|
||||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||||
}}>
|
}}>
|
||||||
<img
|
<img
|
||||||
src="/qq.jpg"
|
src="/qq.jpg"
|
||||||
alt="QQ交流群二维码"
|
alt="QQ交流群二维码"
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '280px',
|
maxWidth: '180px',
|
||||||
maxHeight: '280px',
|
maxHeight: '180px',
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
display: 'block',
|
display: 'block',
|
||||||
@@ -163,8 +162,8 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{
|
<div style={{
|
||||||
width: '280px',
|
width: '180px',
|
||||||
height: '280px',
|
height: '180px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -182,9 +181,9 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
minWidth: '280px',
|
minWidth: '200px',
|
||||||
}}>
|
}}>
|
||||||
<p style={{ fontWeight: 600, color: 'var(--color-text-primary)', marginBottom: '12px', fontSize: '15px' }}>
|
<p style={{ fontWeight: 600, color: 'var(--color-text-primary)', marginBottom: '8px', fontSize: '14px' }}>
|
||||||
微信交流群
|
微信交流群
|
||||||
</p>
|
</p>
|
||||||
{!wxImageError ? (
|
{!wxImageError ? (
|
||||||
@@ -194,15 +193,15 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
background: 'var(--color-bg-container)',
|
background: 'var(--color-bg-container)',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
padding: '8px',
|
padding: '6px',
|
||||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||||
}}>
|
}}>
|
||||||
<img
|
<img
|
||||||
src="/WX.png"
|
src="/WX.png"
|
||||||
alt="微信交流群二维码"
|
alt="微信交流群二维码"
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '280px',
|
maxWidth: '180px',
|
||||||
maxHeight: '280px',
|
maxHeight: '180px',
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
display: 'block',
|
display: 'block',
|
||||||
@@ -213,8 +212,8 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{
|
<div style={{
|
||||||
width: '280px',
|
width: '180px',
|
||||||
height: '280px',
|
height: '180px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -229,12 +228,12 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{
|
<div style={{
|
||||||
marginTop: '20px',
|
marginTop: '16px',
|
||||||
padding: '12px',
|
padding: '10px',
|
||||||
background: 'var(--color-warning-bg)',
|
background: 'var(--color-warning-bg)',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
border: '1px solid var(--color-warning-border)',
|
border: '1px solid var(--color-warning-border)',
|
||||||
fontSize: '14px',
|
fontSize: '13px',
|
||||||
color: 'var(--color-warning)',
|
color: 'var(--color-warning)',
|
||||||
}}>
|
}}>
|
||||||
💡 提示:选择"今日内不再展示"当天不再显示,选择"永不再展示"将永久隐藏此公告
|
💡 提示:选择"今日内不再展示"当天不再显示,选择"永不再展示"将永久隐藏此公告
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Typography, Space, Divider, Badge, Button } from 'antd';
|
import { Typography, Space, Divider, Badge, Button, Grid } from 'antd';
|
||||||
import { GithubOutlined, CopyrightOutlined, HeartFilled, ClockCircleOutlined, GiftOutlined } from '@ant-design/icons';
|
import { GithubOutlined, CopyrightOutlined, HeartFilled, ClockCircleOutlined, GiftOutlined } from '@ant-design/icons';
|
||||||
import { VERSION_INFO, getVersionString } from '../config/version';
|
import { VERSION_INFO, getVersionString } from '../config/version';
|
||||||
import { checkLatestVersion } from '../services/versionService';
|
import { checkLatestVersion } from '../services/versionService';
|
||||||
|
|
||||||
const { Text, Link } = Typography;
|
const { Text, Link } = Typography;
|
||||||
|
const { useBreakpoint } = Grid;
|
||||||
|
|
||||||
export default function AppFooter() {
|
interface AppFooterProps {
|
||||||
const isMobile = window.innerWidth <= 768;
|
sidebarWidth?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) {
|
||||||
|
const screens = useBreakpoint();
|
||||||
|
const isMobile = !screens.md;
|
||||||
const [hasUpdate, setHasUpdate] = useState(false);
|
const [hasUpdate, setHasUpdate] = useState(false);
|
||||||
const [latestVersion, setLatestVersion] = useState('');
|
const [latestVersion, setLatestVersion] = useState('');
|
||||||
const [releaseUrl, setReleaseUrl] = useState('');
|
const [releaseUrl, setReleaseUrl] = useState('');
|
||||||
@@ -20,7 +26,7 @@ export default function AppFooter() {
|
|||||||
setHasUpdate(result.hasUpdate);
|
setHasUpdate(result.hasUpdate);
|
||||||
setLatestVersion(result.latestVersion);
|
setLatestVersion(result.latestVersion);
|
||||||
setReleaseUrl(result.releaseUrl);
|
setReleaseUrl(result.releaseUrl);
|
||||||
} catch (error) {
|
} catch {
|
||||||
// 静默失败
|
// 静默失败
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -37,12 +43,15 @@ export default function AppFooter() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 计算左边距:桌面端有侧边栏时需要偏移
|
||||||
|
const leftOffset = isMobile ? 0 : sidebarWidth;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: leftOffset,
|
||||||
right: 0,
|
right: 0,
|
||||||
backdropFilter: 'blur(20px) saturate(180%)',
|
backdropFilter: 'blur(20px) saturate(180%)',
|
||||||
WebkitBackdropFilter: 'blur(20px) saturate(180%)',
|
WebkitBackdropFilter: 'blur(20px) saturate(180%)',
|
||||||
@@ -51,6 +60,7 @@ export default function AppFooter() {
|
|||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
boxShadow: 'var(--shadow-card)',
|
boxShadow: 'var(--shadow-card)',
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.8)', // 半透明背景以支持 backdrop-filter
|
backgroundColor: 'rgba(255, 255, 255, 0.8)', // 半透明背景以支持 backdrop-filter
|
||||||
|
transition: 'left 0.3s ease', // 平滑过渡
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const cardStyles = {
|
|||||||
// 悬浮效果
|
// 悬浮效果
|
||||||
hoverable: {
|
hoverable: {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
position: 'relative' as const,
|
||||||
} as CSSProperties,
|
} as CSSProperties,
|
||||||
|
|
||||||
// 角色卡片样式
|
// 角色卡片样式
|
||||||
@@ -32,15 +33,52 @@ export const cardStyles = {
|
|||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
} as CSSProperties,
|
} as CSSProperties,
|
||||||
|
|
||||||
// 项目卡片样式 - 现代化设计
|
// 项目卡片样式 - 书籍风格 (Book Style)
|
||||||
project: {
|
project: {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
borderRadius: 20,
|
borderRadius: '6px 16px 16px 6px', // 左侧稍直(书脊),右侧圆润
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
background: 'var(--color-bg-container)',
|
background: '#fff',
|
||||||
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.04)',
|
// 基础阴影 + 书籍厚度阴影
|
||||||
transition: 'all 0.35s cubic-bezier(0.4, 0, 0.2, 1)',
|
boxShadow: `
|
||||||
border: '1px solid rgba(0, 0, 0, 0.04)',
|
0 2px 8px rgba(0, 0, 0, 0.04),
|
||||||
|
4px 0 8px rgba(0, 0, 0, 0.02)
|
||||||
|
`,
|
||||||
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
border: '1px solid rgba(0,0,0,0.02)',
|
||||||
|
borderLeft: '6px solid var(--color-primary)', // 书脊效果
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
position: 'relative',
|
||||||
|
} as CSSProperties,
|
||||||
|
|
||||||
|
// 新建项目卡片样式 - 统一书籍风格
|
||||||
|
newProjectBook: {
|
||||||
|
height: '100%',
|
||||||
|
borderRadius: '6px 16px 16px 6px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
background: '#fff',
|
||||||
|
// 基础阴影 + 书籍厚度阴影 (与普通项目一致)
|
||||||
|
boxShadow: `
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.04),
|
||||||
|
4px 0 8px rgba(0, 0, 0, 0.02)
|
||||||
|
`,
|
||||||
|
border: '1px solid rgba(0,0,0,0.02)',
|
||||||
|
borderLeft: '6px solid var(--color-primary)', // 与普通项目一致的书脊颜色
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
position: 'relative',
|
||||||
|
} as CSSProperties,
|
||||||
|
|
||||||
|
// 书架风格容器
|
||||||
|
bookshelf: {
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
|
||||||
|
gap: '24px',
|
||||||
|
padding: '24px 0',
|
||||||
} as CSSProperties,
|
} as CSSProperties,
|
||||||
|
|
||||||
// 卡片内容区域样式
|
// 卡片内容区域样式
|
||||||
@@ -74,17 +112,28 @@ export const cardStyles = {
|
|||||||
} as CSSProperties),
|
} as CSSProperties),
|
||||||
};
|
};
|
||||||
|
|
||||||
// 卡片悬浮动画 - 增强版
|
// 卡片悬浮动画 - 增强版 (Subtle Lift)
|
||||||
export const cardHoverHandlers = {
|
export const cardHoverHandlers = {
|
||||||
onMouseEnter: (e: React.MouseEvent<HTMLDivElement>) => {
|
onMouseEnter: (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
const target = e.currentTarget;
|
const target = e.currentTarget;
|
||||||
target.style.transform = 'translateY(-10px) scale(1.01)';
|
target.style.transform = 'translateY(-6px) rotateY(-2deg)'; // 悬浮时轻微翻起
|
||||||
target.style.boxShadow = '0 20px 40px rgba(77, 128, 136, 0.2), 0 8px 16px rgba(0, 0, 0, 0.08)';
|
|
||||||
|
// 统一书本悬浮态
|
||||||
|
target.style.boxShadow = `
|
||||||
|
-2px 0 4px rgba(0, 0, 0, 0.1), // 书脊阴影加深
|
||||||
|
8px 12px 24px rgba(0, 0, 0, 0.12)
|
||||||
|
`;
|
||||||
|
|
||||||
},
|
},
|
||||||
onMouseLeave: (e: React.MouseEvent<HTMLDivElement>) => {
|
onMouseLeave: (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
const target = e.currentTarget;
|
const target = e.currentTarget;
|
||||||
target.style.transform = 'translateY(0) scale(1)';
|
target.style.transform = 'translateY(0) rotateY(0)';
|
||||||
target.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.04)';
|
|
||||||
|
// 统一恢复基础阴影
|
||||||
|
target.style.boxShadow = `
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.04),
|
||||||
|
4px 0 8px rgba(0, 0, 0, 0.02)
|
||||||
|
`;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,30 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { FloatButton } from 'antd';
|
import { FloatButton, Grid } from 'antd';
|
||||||
import { FileTextOutlined } from '@ant-design/icons';
|
import { FileTextOutlined } from '@ant-design/icons';
|
||||||
import ChangelogModal from './ChangelogModal';
|
import ChangelogModal from './ChangelogModal';
|
||||||
|
|
||||||
|
const { useBreakpoint } = Grid;
|
||||||
|
|
||||||
export default function ChangelogFloatingButton() {
|
export default function ChangelogFloatingButton() {
|
||||||
const [showChangelog, setShowChangelog] = useState(false);
|
const [showChangelog, setShowChangelog] = useState(false);
|
||||||
|
const screens = useBreakpoint();
|
||||||
|
const isMobile = !screens.md;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: 'fixed', zIndex: 9999 }}>
|
<>
|
||||||
<FloatButton
|
<FloatButton
|
||||||
icon={<FileTextOutlined />}
|
icon={<FileTextOutlined />}
|
||||||
type="primary"
|
type="primary"
|
||||||
tooltip="查看更新日志"
|
tooltip="查看更新日志"
|
||||||
style={{
|
style={{
|
||||||
|
// 桌面端时,确保按钮在主内容区域内(侧边栏右侧)
|
||||||
right: 24,
|
right: 24,
|
||||||
bottom: 100,
|
bottom: 100,
|
||||||
|
// 移动端无侧边栏,不需要额外处理
|
||||||
|
...(isMobile ? {} : {
|
||||||
|
// 确保 zIndex 低于侧边栏但高于内容
|
||||||
|
zIndex: 999,
|
||||||
|
}),
|
||||||
}}
|
}}
|
||||||
onClick={() => setShowChangelog(true)}
|
onClick={() => setShowChangelog(true)}
|
||||||
/>
|
/>
|
||||||
@@ -23,6 +33,6 @@ export default function ChangelogFloatingButton() {
|
|||||||
visible={showChangelog}
|
visible={showChangelog}
|
||||||
onClose={() => setShowChangelog(false)}
|
onClose={() => setShowChangelog(false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -55,6 +55,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
|
|||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
// 清除可能存在的轮询
|
// 清除可能存在的轮询
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [visible, chapterId]);
|
}, [visible, chapterId]);
|
||||||
|
|
||||||
// 🔧 新增:独立的章节信息加载函数
|
// 🔧 新增:独立的章节信息加载函数
|
||||||
|
|||||||
@@ -78,8 +78,9 @@ const ChapterContentComparison: React.FC<ChapterContentComparisonProps> = ({
|
|||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
message.error(error.message || '应用失败');
|
const err = error as Error;
|
||||||
|
message.error(err.message || '应用失败');
|
||||||
} finally {
|
} finally {
|
||||||
setApplying(false);
|
setApplying(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,7 +119,22 @@ const ChapterRegenerationModal: React.FC<ChapterRegenerationModalProps> = ({
|
|||||||
setWordCount(0);
|
setWordCount(0);
|
||||||
|
|
||||||
// 构建请求数据
|
// 构建请求数据
|
||||||
const requestData: any = {
|
interface RegenerationRequest {
|
||||||
|
modification_source: string;
|
||||||
|
custom_instructions?: string;
|
||||||
|
selected_suggestion_indices: number[];
|
||||||
|
preserve_elements: {
|
||||||
|
preserve_structure: boolean;
|
||||||
|
preserve_dialogues: string[];
|
||||||
|
preserve_plot_points: string[];
|
||||||
|
preserve_character_traits: boolean;
|
||||||
|
};
|
||||||
|
style_id?: string;
|
||||||
|
target_word_count: number;
|
||||||
|
focus_areas: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestData: RegenerationRequest = {
|
||||||
modification_source: values.modification_source,
|
modification_source: values.modification_source,
|
||||||
custom_instructions: values.custom_instructions,
|
custom_instructions: values.custom_instructions,
|
||||||
selected_suggestion_indices: selectedSuggestions,
|
selected_suggestion_indices: selectedSuggestions,
|
||||||
@@ -158,7 +173,7 @@ const ChapterRegenerationModal: React.FC<ChapterRegenerationModalProps> = ({
|
|||||||
currentWordCount = accumulatedContent.length;
|
currentWordCount = accumulatedContent.length;
|
||||||
// 不再自己计算进度,完全依赖后端发送的progress消息
|
// 不再自己计算进度,完全依赖后端发送的progress消息
|
||||||
},
|
},
|
||||||
onResult: (data: any) => {
|
onResult: (data: { word_count?: number }) => {
|
||||||
// 生成完成,确保使用最新的累积内容
|
// 生成完成,确保使用最新的累积内容
|
||||||
setProgress(100);
|
setProgress(100);
|
||||||
setStatus('success');
|
setStatus('success');
|
||||||
@@ -183,11 +198,12 @@ const ChapterRegenerationModal: React.FC<ChapterRegenerationModalProps> = ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('提交失败:', error);
|
console.error('提交失败:', error);
|
||||||
setStatus('error');
|
setStatus('error');
|
||||||
setErrorMessage(error.message || '提交失败');
|
const err = error as Error;
|
||||||
message.error('操作失败: ' + (error.message || '未知错误'));
|
setErrorMessage(err.message || '提交失败');
|
||||||
|
message.error('操作失败: ' + (err.message || '未知错误'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { Card, Button, Modal, Form, Select, InputNumber, Input, message, Progress, Tag, Space, Divider, Typography } from 'antd';
|
import { Card, Button, Modal, Form, Select, InputNumber, Input, message, Progress, Tag, Space, Divider, Typography } from 'antd';
|
||||||
import { EditOutlined, PlusOutlined, DeleteOutlined, TrophyOutlined } from '@ant-design/icons';
|
import { EditOutlined, PlusOutlined, DeleteOutlined, TrophyOutlined } from '@ant-design/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
@@ -59,14 +59,7 @@ export const CharacterCareerCard: React.FC<Props> = ({
|
|||||||
const [progressForm] = Form.useForm();
|
const [progressForm] = Form.useForm();
|
||||||
const [modal, contextHolder] = Modal.useModal();
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchCharacterCareers = useCallback(async () => {
|
||||||
fetchCharacterCareers();
|
|
||||||
if (editable) {
|
|
||||||
fetchAllCareers();
|
|
||||||
}
|
|
||||||
}, [characterId]);
|
|
||||||
|
|
||||||
const fetchCharacterCareers = async () => {
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
@@ -75,14 +68,15 @@ export const CharacterCareerCard: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
setMainCareer(response.data.main_career || null);
|
setMainCareer(response.data.main_career || null);
|
||||||
setSubCareers(response.data.sub_careers || []);
|
setSubCareers(response.data.sub_careers || []);
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
message.error(error.response?.data?.detail || '获取职业信息失败');
|
const axiosError = error as { response?: { data?: { detail?: string } } };
|
||||||
|
message.error(axiosError.response?.data?.detail || '获取职业信息失败');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}, [characterId]);
|
||||||
|
|
||||||
const fetchAllCareers = async () => {
|
const fetchAllCareers = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${API_BASE_URL}/api/careers`, {
|
const response = await axios.get(`${API_BASE_URL}/api/careers`, {
|
||||||
params: { project_id: projectId },
|
params: { project_id: projectId },
|
||||||
@@ -91,12 +85,19 @@ export const CharacterCareerCard: React.FC<Props> = ({
|
|||||||
const main = response.data.main_careers || [];
|
const main = response.data.main_careers || [];
|
||||||
const sub = response.data.sub_careers || [];
|
const sub = response.data.sub_careers || [];
|
||||||
setAllCareers([...main, ...sub]);
|
setAllCareers([...main, ...sub]);
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('获取职业列表失败:', error);
|
console.error('获取职业列表失败:', error);
|
||||||
}
|
}
|
||||||
};
|
}, [projectId]);
|
||||||
|
|
||||||
const handleSetMainCareer = async (values: any) => {
|
useEffect(() => {
|
||||||
|
fetchCharacterCareers();
|
||||||
|
if (editable) {
|
||||||
|
fetchAllCareers();
|
||||||
|
}
|
||||||
|
}, [characterId, editable, fetchCharacterCareers, fetchAllCareers]);
|
||||||
|
|
||||||
|
const handleSetMainCareer = async (values: { career_id: string; current_stage?: number; started_at?: string }) => {
|
||||||
try {
|
try {
|
||||||
await axios.post(
|
await axios.post(
|
||||||
`${API_BASE_URL}/api/careers/character/${characterId}/careers/main`,
|
`${API_BASE_URL}/api/careers/character/${characterId}/careers/main`,
|
||||||
@@ -108,12 +109,13 @@ export const CharacterCareerCard: React.FC<Props> = ({
|
|||||||
mainForm.resetFields();
|
mainForm.resetFields();
|
||||||
fetchCharacterCareers();
|
fetchCharacterCareers();
|
||||||
onUpdate?.();
|
onUpdate?.();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
message.error(error.response?.data?.detail || '设置主职业失败');
|
const axiosError = error as { response?: { data?: { detail?: string } } };
|
||||||
|
message.error(axiosError.response?.data?.detail || '设置主职业失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddSubCareer = async (values: any) => {
|
const handleAddSubCareer = async (values: { career_id: string; current_stage?: number; started_at?: string }) => {
|
||||||
try {
|
try {
|
||||||
await axios.post(
|
await axios.post(
|
||||||
`${API_BASE_URL}/api/careers/character/${characterId}/careers/sub`,
|
`${API_BASE_URL}/api/careers/character/${characterId}/careers/sub`,
|
||||||
@@ -125,12 +127,13 @@ export const CharacterCareerCard: React.FC<Props> = ({
|
|||||||
subForm.resetFields();
|
subForm.resetFields();
|
||||||
fetchCharacterCareers();
|
fetchCharacterCareers();
|
||||||
onUpdate?.();
|
onUpdate?.();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
message.error(error.response?.data?.detail || '添加副职业失败');
|
const axiosError = error as { response?: { data?: { detail?: string } } };
|
||||||
|
message.error(axiosError.response?.data?.detail || '添加副职业失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateProgress = async (values: any) => {
|
const handleUpdateProgress = async (values: { current_stage: number; stage_progress: number; reached_current_stage_at?: string; notes?: string }) => {
|
||||||
if (!selectedCareer) return;
|
if (!selectedCareer) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -144,8 +147,9 @@ export const CharacterCareerCard: React.FC<Props> = ({
|
|||||||
progressForm.resetFields();
|
progressForm.resetFields();
|
||||||
fetchCharacterCareers();
|
fetchCharacterCareers();
|
||||||
onUpdate?.();
|
onUpdate?.();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
message.error(error.response?.data?.detail || '更新职业阶段失败');
|
const axiosError = error as { response?: { data?: { detail?: string } } };
|
||||||
|
message.error(axiosError.response?.data?.detail || '更新职业阶段失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -163,8 +167,9 @@ export const CharacterCareerCard: React.FC<Props> = ({
|
|||||||
message.success('副职业删除成功');
|
message.success('副职业删除成功');
|
||||||
fetchCharacterCareers();
|
fetchCharacterCareers();
|
||||||
onUpdate?.();
|
onUpdate?.();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
message.error(error.response?.data?.detail || '删除副职业失败');
|
const axiosError = error as { response?: { data?: { detail?: string } } };
|
||||||
|
message.error(axiosError.response?.data?.detail || '删除副职业失败');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Modal, Form, Input, InputNumber, Select, Tag, Space, Button, message, Divider } from 'antd';
|
import { Modal, Form, Input, InputNumber, Select, Tag, Space, Button, message, Divider } from 'antd';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import type { ExpansionPlanData, Character } from '../types';
|
import type { ExpansionPlanData, Character } from '../types';
|
||||||
import { characterApi } from '../services/api';
|
import { characterApi } from '../services/api';
|
||||||
|
|
||||||
@@ -36,13 +36,7 @@ export default function ExpansionPlanEditor({
|
|||||||
const [loadingCharacters, setLoadingCharacters] = useState(false);
|
const [loadingCharacters, setLoadingCharacters] = useState(false);
|
||||||
|
|
||||||
// 加载项目角色列表
|
// 加载项目角色列表
|
||||||
useEffect(() => {
|
const loadCharacters = useCallback(async () => {
|
||||||
if (visible && projectId) {
|
|
||||||
loadCharacters();
|
|
||||||
}
|
|
||||||
}, [visible, projectId]);
|
|
||||||
|
|
||||||
const loadCharacters = async () => {
|
|
||||||
try {
|
try {
|
||||||
setLoadingCharacters(true);
|
setLoadingCharacters(true);
|
||||||
setAvailableCharacters([]); // 重置为空数组
|
setAvailableCharacters([]); // 重置为空数组
|
||||||
@@ -53,8 +47,11 @@ export default function ExpansionPlanEditor({
|
|||||||
let chars: Character[] = [];
|
let chars: Character[] = [];
|
||||||
if (Array.isArray(response)) {
|
if (Array.isArray(response)) {
|
||||||
chars = response;
|
chars = response;
|
||||||
} else if (response && typeof response === 'object' && 'items' in response && Array.isArray((response as any).items)) {
|
} else if (response && typeof response === 'object' && 'items' in response) {
|
||||||
chars = (response as any).items;
|
const responseObj = response as { items?: Character[] };
|
||||||
|
if (Array.isArray(responseObj.items)) {
|
||||||
|
chars = responseObj.items;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('角色API返回格式异常:', response);
|
console.error('角色API返回格式异常:', response);
|
||||||
message.warning('角色数据格式异常');
|
message.warning('角色数据格式异常');
|
||||||
@@ -62,14 +59,21 @@ export default function ExpansionPlanEditor({
|
|||||||
|
|
||||||
setAvailableCharacters(chars);
|
setAvailableCharacters(chars);
|
||||||
console.log('设置的角色列表:', chars);
|
console.log('设置的角色列表:', chars);
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('加载角色列表失败:', error);
|
console.error('加载角色列表失败:', error);
|
||||||
setAvailableCharacters([]);
|
setAvailableCharacters([]);
|
||||||
message.error('加载角色列表失败: ' + (error?.message || '未知错误'));
|
const err = error as Error;
|
||||||
|
message.error('加载角色列表失败: ' + (err?.message || '未知错误'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingCharacters(false);
|
setLoadingCharacters(false);
|
||||||
}
|
}
|
||||||
};
|
}, [projectId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible && projectId) {
|
||||||
|
loadCharacters();
|
||||||
|
}
|
||||||
|
}, [visible, projectId, loadCharacters]);
|
||||||
|
|
||||||
// 当planData或chapterSummary变化时更新状态
|
// 当planData或chapterSummary变化时更新状态
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -325,7 +329,7 @@ export default function ExpansionPlanEditor({
|
|||||||
step={100}
|
step={100}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
formatter={(value) => `${value} 字`}
|
formatter={(value) => `${value} 字`}
|
||||||
parser={(value) => value?.replace(' 字', '') as any}
|
parser={(value) => Number(value?.replace(' 字', '')) as 500 | 10000}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,12 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
export default function UserMenu() {
|
interface UserMenuProps {
|
||||||
|
/** 是否总是显示完整信息(用于移动端侧边栏) */
|
||||||
|
showFullInfo?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UserMenu({ showFullInfo = false }: UserMenuProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||||
const [showChangePassword, setShowChangePassword] = useState(false);
|
const [showChangePassword, setShowChangePassword] = useState(false);
|
||||||
@@ -54,9 +59,10 @@ export default function UserMenu() {
|
|||||||
message.success('密码修改成功');
|
message.success('密码修改成功');
|
||||||
setShowChangePassword(false);
|
setShowChangePassword(false);
|
||||||
changePasswordForm.resetFields();
|
changePasswordForm.resetFields();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('修改密码失败:', error);
|
console.error('修改密码失败:', error);
|
||||||
message.error(error.response?.data?.detail || '修改密码失败');
|
const err = error as { response?: { data?: { detail?: string } } };
|
||||||
|
message.error(err.response?.data?.detail || '修改密码失败');
|
||||||
} finally {
|
} finally {
|
||||||
setChangingPassword(false);
|
setChangingPassword(false);
|
||||||
}
|
}
|
||||||
@@ -171,7 +177,7 @@ export default function UserMenu() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Space direction="vertical" size={0} style={{ display: window.innerWidth <= 768 ? 'none' : 'flex' }}>
|
<Space direction="vertical" size={0} style={{ display: (window.innerWidth <= 768 && !showFullInfo) ? 'none' : 'flex' }}>
|
||||||
<Text strong style={{
|
<Text strong style={{
|
||||||
color: 'var(--color-text-primary)',
|
color: 'var(--color-text-primary)',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|||||||
@@ -10,7 +10,13 @@ export default function AuthCallback() {
|
|||||||
const [errorMessage, setErrorMessage] = useState('');
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
const [showAnnouncement, setShowAnnouncement] = useState(false);
|
const [showAnnouncement, setShowAnnouncement] = useState(false);
|
||||||
const [showPasswordModal, setShowPasswordModal] = useState(false);
|
const [showPasswordModal, setShowPasswordModal] = useState(false);
|
||||||
const [passwordStatus, setPasswordStatus] = useState<any>(null);
|
interface PasswordStatus {
|
||||||
|
has_password: boolean;
|
||||||
|
has_custom_password: boolean;
|
||||||
|
username: string;
|
||||||
|
default_password: string;
|
||||||
|
}
|
||||||
|
const [passwordStatus, setPasswordStatus] = useState<PasswordStatus | null>(null);
|
||||||
const [newPassword, setNewPassword] = useState('');
|
const [newPassword, setNewPassword] = useState('');
|
||||||
const [confirmPassword, setConfirmPassword] = useState('');
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
const [settingPassword, setSettingPassword] = useState(false);
|
const [settingPassword, setSettingPassword] = useState(false);
|
||||||
@@ -187,7 +193,7 @@ export default function AuthCallback() {
|
|||||||
setShowAnnouncement(true);
|
setShowAnnouncement(true);
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
message.error('密码设置失败,请重试');
|
message.error('密码设置失败,请重试');
|
||||||
} finally {
|
} finally {
|
||||||
setSettingPassword(false);
|
setSettingPassword(false);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { Button, Modal, Form, Input, Select, message, Row, Col, Empty, Tabs, Card, Tag, Space, Divider, Typography, InputNumber } from 'antd';
|
import { Button, Modal, Form, Input, Select, message, Row, Col, Empty, Tabs, Card, Tag, Space, Divider, Typography, InputNumber } from 'antd';
|
||||||
import { ThunderboltOutlined, PlusOutlined, EditOutlined, DeleteOutlined, TrophyOutlined } from '@ant-design/icons';
|
import { ThunderboltOutlined, PlusOutlined, EditOutlined, DeleteOutlined, TrophyOutlined } from '@ant-design/icons';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
@@ -46,26 +46,26 @@ export default function Careers() {
|
|||||||
const [aiProgress, setAiProgress] = useState(0);
|
const [aiProgress, setAiProgress] = useState(0);
|
||||||
const [aiMessage, setAiMessage] = useState('');
|
const [aiMessage, setAiMessage] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchCareers = useCallback(async () => {
|
||||||
if (projectId) {
|
|
||||||
fetchCareers();
|
|
||||||
}
|
|
||||||
}, [projectId]);
|
|
||||||
|
|
||||||
const fetchCareers = async () => {
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response: any = await api.get('/careers', {
|
const response = await api.get('/careers', {
|
||||||
params: { project_id: projectId }
|
params: { project_id: projectId }
|
||||||
});
|
}) as { main_careers?: Career[]; sub_careers?: Career[] };
|
||||||
setMainCareers(response.main_careers || []);
|
setMainCareers(response.main_careers || []);
|
||||||
setSubCareers(response.sub_careers || []);
|
setSubCareers(response.sub_careers || []);
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('获取职业列表失败:', error);
|
console.error('获取职业列表失败:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}, [projectId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (projectId) {
|
||||||
|
fetchCareers();
|
||||||
|
}
|
||||||
|
}, [projectId, fetchCareers]);
|
||||||
|
|
||||||
const handleOpenModal = (career?: Career) => {
|
const handleOpenModal = (career?: Career) => {
|
||||||
if (career) {
|
if (career) {
|
||||||
@@ -81,7 +81,18 @@ export default function Careers() {
|
|||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async (values: any) => {
|
interface CareerFormValues {
|
||||||
|
name: string;
|
||||||
|
type: 'main' | 'sub';
|
||||||
|
description?: string;
|
||||||
|
category?: string;
|
||||||
|
stages?: string;
|
||||||
|
requirements?: string;
|
||||||
|
special_abilities?: string;
|
||||||
|
worldview_rules?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async (values: CareerFormValues) => {
|
||||||
try {
|
try {
|
||||||
// 解析阶段数据
|
// 解析阶段数据
|
||||||
const stagesText = values.stages || '';
|
const stagesText = values.stages || '';
|
||||||
@@ -124,8 +135,9 @@ export default function Careers() {
|
|||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
fetchCareers();
|
fetchCareers();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
message.error(error.response?.data?.detail || '操作失败');
|
const axiosError = error as { response?: { data?: { detail?: string } } };
|
||||||
|
message.error(axiosError.response?.data?.detail || '操作失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -139,14 +151,15 @@ export default function Careers() {
|
|||||||
await api.delete(`/careers/${id}`);
|
await api.delete(`/careers/${id}`);
|
||||||
message.success('职业删除成功');
|
message.success('职业删除成功');
|
||||||
fetchCareers();
|
fetchCareers();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
message.error(error.response?.data?.detail || '删除失败');
|
const axiosError = error as { response?: { data?: { detail?: string } } };
|
||||||
|
message.error(axiosError.response?.data?.detail || '删除失败');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAIGenerate = async (values: any) => {
|
const handleAIGenerate = async (values: { main_career_count: number; sub_career_count: number }) => {
|
||||||
setIsAIModalOpen(false);
|
setIsAIModalOpen(false);
|
||||||
setAiGenerating(true);
|
setAiGenerating(true);
|
||||||
setAiProgress(0);
|
setAiProgress(0);
|
||||||
@@ -193,9 +206,10 @@ export default function Careers() {
|
|||||||
setAiGenerating(false);
|
setAiGenerating(false);
|
||||||
message.error('连接中断,生成失败');
|
message.error('连接中断,生成失败');
|
||||||
};
|
};
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
setAiGenerating(false);
|
setAiGenerating(false);
|
||||||
message.error(err.message || '启动生成失败');
|
const error = err as Error;
|
||||||
|
message.error(error.message || '启动生成失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { Card, Spin, Alert, Button, Space, Switch, Drawer, message, Progress } from 'antd';
|
import { Card, Spin, Alert, Button, Space, Switch, Drawer, message, Progress } from 'antd';
|
||||||
import {
|
import {
|
||||||
@@ -75,13 +75,7 @@ const ChapterReader: React.FC = () => {
|
|||||||
const [analysisProgress, setAnalysisProgress] = useState(0);
|
const [analysisProgress, setAnalysisProgress] = useState(0);
|
||||||
const [navigation, setNavigation] = useState<NavigationData | null>(null);
|
const [navigation, setNavigation] = useState<NavigationData | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
const loadChapterData = useCallback(async () => {
|
||||||
if (chapterId) {
|
|
||||||
loadChapterData();
|
|
||||||
}
|
|
||||||
}, [chapterId]);
|
|
||||||
|
|
||||||
const loadChapterData = async () => {
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -130,13 +124,20 @@ const ChapterReader: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
setAnnotationsData(null);
|
setAnnotationsData(null);
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
console.error('加载章节数据失败:', err);
|
console.error('加载章节数据失败:', err);
|
||||||
setError(err.response?.data?.detail || err.message || '加载失败');
|
const error = err as { response?: { data?: { detail?: string } }; message?: string };
|
||||||
|
setError(error.response?.data?.detail || error.message || '加载失败');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}, [chapterId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (chapterId) {
|
||||||
|
loadChapterData();
|
||||||
|
}
|
||||||
|
}, [chapterId, loadChapterData]);
|
||||||
|
|
||||||
const handleAnnotationClick = (annotation: MemoryAnnotation) => {
|
const handleAnnotationClick = (annotation: MemoryAnnotation) => {
|
||||||
setActiveAnnotationId(annotation.id);
|
setActiveAnnotationId(annotation.id);
|
||||||
@@ -211,10 +212,11 @@ const ChapterReader: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
setAnalyzing(false);
|
setAnalyzing(false);
|
||||||
|
const error = err as { response?: { data?: { detail?: string } } };
|
||||||
message.error({
|
message.error({
|
||||||
content: err.response?.data?.detail || '触发分析失败',
|
content: error.response?.data?.detail || '触发分析失败',
|
||||||
key: 'analyze'
|
key: 'analyze'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Card, Input, Button, Space, Typography, message, Spin, Modal } from 'antd';
|
import { Card, Input, Button, Space, Typography, message, Spin, Modal } from 'antd';
|
||||||
import { SendOutlined, ArrowLeftOutlined, ReloadOutlined } from '@ant-design/icons';
|
import { SendOutlined, ArrowLeftOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||||
@@ -102,8 +102,18 @@ const Inspiration: React.FC = () => {
|
|||||||
|
|
||||||
// ==================== 缓存管理函数 ====================
|
// ==================== 缓存管理函数 ====================
|
||||||
|
|
||||||
|
// 清除缓存
|
||||||
|
const clearCache = useCallback(() => {
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(CACHE_KEY);
|
||||||
|
console.log('🗑️ 缓存已清除');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('清除缓存失败:', error);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 保存到缓存
|
// 保存到缓存
|
||||||
const saveToCache = () => {
|
const saveToCache = useCallback(() => {
|
||||||
try {
|
try {
|
||||||
// 只在对话阶段保存,生成阶段不保存
|
// 只在对话阶段保存,生成阶段不保存
|
||||||
if (currentStep === 'generating' || currentStep === 'complete') {
|
if (currentStep === 'generating' || currentStep === 'complete') {
|
||||||
@@ -130,10 +140,10 @@ const Inspiration: React.FC = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存缓存失败:', error);
|
console.error('保存缓存失败:', error);
|
||||||
}
|
}
|
||||||
};
|
}, [currentStep, messages, wizardData, initialIdea, selectedOptions, lastFailedRequest]);
|
||||||
|
|
||||||
// 从缓存恢复
|
// 从缓存恢复
|
||||||
const restoreFromCache = (): boolean => {
|
const restoreFromCache = useCallback((): boolean => {
|
||||||
try {
|
try {
|
||||||
const cached = localStorage.getItem(CACHE_KEY);
|
const cached = localStorage.getItem(CACHE_KEY);
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
@@ -174,17 +184,7 @@ const Inspiration: React.FC = () => {
|
|||||||
clearCache();
|
clearCache();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
}, [clearCache]);
|
||||||
|
|
||||||
// 清除缓存
|
|
||||||
const clearCache = () => {
|
|
||||||
try {
|
|
||||||
localStorage.removeItem(CACHE_KEY);
|
|
||||||
console.log('🗑️ 缓存已清除');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('清除缓存失败:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ==================== 组件挂载时恢复缓存 ====================
|
// ==================== 组件挂载时恢复缓存 ====================
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ const Inspiration: React.FC = () => {
|
|||||||
restoreFromCache();
|
restoreFromCache();
|
||||||
setCacheLoaded(true);
|
setCacheLoaded(true);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [cacheLoaded, restoreFromCache]);
|
||||||
|
|
||||||
// ==================== 自动保存:状态变化时保存 ====================
|
// ==================== 自动保存:状态变化时保存 ====================
|
||||||
|
|
||||||
@@ -206,7 +206,7 @@ const Inspiration: React.FC = () => {
|
|||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [messages, currentStep, wizardData, initialIdea, selectedOptions, lastFailedRequest, cacheLoaded]);
|
}, [messages, currentStep, wizardData, initialIdea, selectedOptions, lastFailedRequest, cacheLoaded, saveToCache]);
|
||||||
|
|
||||||
// 自动滚动到底部
|
// 自动滚动到底部
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
@@ -259,7 +259,7 @@ const Inspiration: React.FC = () => {
|
|||||||
};
|
};
|
||||||
setMessages(prev => [...prev, aiMessage]);
|
setMessages(prev => [...prev, aiMessage]);
|
||||||
setLastFailedRequest(null);
|
setLastFailedRequest(null);
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('重试失败:', error);
|
console.error('重试失败:', error);
|
||||||
message.error('重试失败,请稍后再试');
|
message.error('重试失败,请稍后再试');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -307,7 +307,7 @@ const Inspiration: React.FC = () => {
|
|||||||
const step = targetMessage.step as 'title' | 'description' | 'theme' | 'genre';
|
const step = targetMessage.step as 'title' | 'description' | 'theme' | 'genre';
|
||||||
|
|
||||||
// 构建上下文
|
// 构建上下文
|
||||||
const context: any = {
|
const context: Partial<WizardData> & { initial_idea?: string } = {
|
||||||
initial_idea: initialIdea,
|
initial_idea: initialIdea,
|
||||||
title: wizardData.title,
|
title: wizardData.title,
|
||||||
description: wizardData.description,
|
description: wizardData.description,
|
||||||
@@ -339,9 +339,11 @@ const Inspiration: React.FC = () => {
|
|||||||
setMessages(prev => [...prev, aiMessage]);
|
setMessages(prev => [...prev, aiMessage]);
|
||||||
|
|
||||||
message.success('已根据您的反馈重新生成选项');
|
message.success('已根据您的反馈重新生成选项');
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('优化选项失败:', error);
|
console.error('优化选项失败:', error);
|
||||||
message.error(error.response?.data?.detail || '优化失败,请重试');
|
const errMsg = error instanceof Error ? error.message : '优化失败,请重试';
|
||||||
|
const axiosError = error as { response?: { data?: { detail?: string } } };
|
||||||
|
message.error(axiosError.response?.data?.detail || errMsg);
|
||||||
} finally {
|
} finally {
|
||||||
setRefining(false);
|
setRefining(false);
|
||||||
}
|
}
|
||||||
@@ -406,9 +408,11 @@ const Inspiration: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
await handleCustomInput(userInput);
|
await handleCustomInput(userInput);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('发送消息失败:', error);
|
console.error('发送消息失败:', error);
|
||||||
message.error(error.response?.data?.detail || '生成失败,请重试');
|
const errMsg = error instanceof Error ? error.message : '生成失败,请重试';
|
||||||
|
const axiosError = error as { response?: { data?: { detail?: string } } };
|
||||||
|
message.error(axiosError.response?.data?.detail || errMsg);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -575,9 +579,11 @@ const Inspiration: React.FC = () => {
|
|||||||
setWizardData(updatedData);
|
setWizardData(updatedData);
|
||||||
|
|
||||||
await generateNextStep(updatedData);
|
await generateNextStep(updatedData);
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('选择选项失败:', error);
|
console.error('选择选项失败:', error);
|
||||||
message.error(error.response?.data?.detail || '生成失败,请重试');
|
const errMsg = error instanceof Error ? error.message : '生成失败,请重试';
|
||||||
|
const axiosError = error as { response?: { data?: { detail?: string } } };
|
||||||
|
message.error(axiosError.response?.data?.detail || errMsg);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -625,9 +631,11 @@ const Inspiration: React.FC = () => {
|
|||||||
|
|
||||||
setWizardData(updatedData);
|
setWizardData(updatedData);
|
||||||
await generateNextStep(updatedData);
|
await generateNextStep(updatedData);
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('处理自定义输入失败:', error);
|
console.error('处理自定义输入失败:', error);
|
||||||
message.error(error.response?.data?.detail || '处理失败,请重试');
|
const errMsg = error instanceof Error ? error.message : '处理失败,请重试';
|
||||||
|
const axiosError = error as { response?: { data?: { detail?: string } } };
|
||||||
|
message.error(axiosError.response?.data?.detail || errMsg);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -1050,7 +1058,7 @@ const Inspiration: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
minHeight: '100vh',
|
minHeight: '100dvh',
|
||||||
background: 'var(--color-bg-base)',
|
background: 'var(--color-bg-base)',
|
||||||
}}>
|
}}>
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
@@ -1118,7 +1126,7 @@ const Inspiration: React.FC = () => {
|
|||||||
color: '#fff',
|
color: '#fff',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isMobile ? '返回' : '返回项目列表'}
|
{isMobile ? '返回' : '返回首页'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
@@ -1133,12 +1141,6 @@ const Inspiration: React.FC = () => {
|
|||||||
>
|
>
|
||||||
✨ 灵感模式
|
✨ 灵感模式
|
||||||
</Title>
|
</Title>
|
||||||
<Text style={{
|
|
||||||
color: 'rgba(255,255,255,0.85)',
|
|
||||||
fontSize: isMobile ? 12 : 14,
|
|
||||||
}}>
|
|
||||||
通过对话快速创建你的小说项目
|
|
||||||
</Text>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 重新开始按钮 - 只在对话进行中显示 */}
|
{/* 重新开始按钮 - 只在对话进行中显示 */}
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ export default function Organizations() {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [projectId]);
|
}, [projectId]);
|
||||||
|
|
||||||
const loadCharacters = useCallback(async () => {
|
const loadCharacters = useCallback(async () => {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export default function ProjectWizardNew() {
|
|||||||
setResumeProjectId(projectId);
|
setResumeProjectId(projectId);
|
||||||
handleResumeGeneration(projectId);
|
handleResumeGeneration(projectId);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
// 恢复未完成项目的生成
|
// 恢复未完成项目的生成
|
||||||
@@ -320,7 +321,7 @@ export default function ProjectWizardNew() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
minHeight: '100vh',
|
minHeight: '100dvh',
|
||||||
background: 'var(--color-bg-base)',
|
background: 'var(--color-bg-base)',
|
||||||
}}>
|
}}>
|
||||||
{/* 顶部标题栏 - 固定不滚动 */}
|
{/* 顶部标题栏 - 固定不滚动 */}
|
||||||
@@ -358,6 +359,7 @@ export default function ProjectWizardNew() {
|
|||||||
color: '#fff',
|
color: '#fff',
|
||||||
textShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
textShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||||
}}>
|
}}>
|
||||||
|
<RocketOutlined style={{ marginRight: 8 }} />
|
||||||
项目创建向导
|
项目创建向导
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export default function Relationships() {
|
|||||||
if (projectId) {
|
if (projectId) {
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [projectId]);
|
}, [projectId]);
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
|
|||||||
@@ -90,7 +90,16 @@ export default function UserManagement() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 添加用户
|
// 添加用户
|
||||||
const handleCreate = async (values: any) => {
|
interface CreateUserValues {
|
||||||
|
username: string;
|
||||||
|
display_name: string;
|
||||||
|
password?: string;
|
||||||
|
avatar_url?: string;
|
||||||
|
trust_level?: number;
|
||||||
|
is_admin?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCreate = async (values: CreateUserValues) => {
|
||||||
try {
|
try {
|
||||||
const res = await adminApi.createUser(values);
|
const res = await adminApi.createUser(values);
|
||||||
message.success('用户创建成功');
|
message.success('用户创建成功');
|
||||||
@@ -134,7 +143,14 @@ export default function UserManagement() {
|
|||||||
setEditModalVisible(true);
|
setEditModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdate = async (values: any) => {
|
interface UpdateUserValues {
|
||||||
|
display_name: string;
|
||||||
|
avatar_url?: string;
|
||||||
|
trust_level?: number;
|
||||||
|
is_admin?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpdate = async (values: UpdateUserValues) => {
|
||||||
if (!currentUser) return;
|
if (!currentUser) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -290,7 +306,7 @@ export default function UserManagement() {
|
|||||||
key: 'action',
|
key: 'action',
|
||||||
width: isMobile ? 80 : 300,
|
width: isMobile ? 80 : 300,
|
||||||
fixed: 'right' as const,
|
fixed: 'right' as const,
|
||||||
render: (_: any, record: UserWithStatus) => {
|
render: (_: unknown, record: UserWithStatus) => {
|
||||||
const isActive = record.is_active !== false;
|
const isActive = record.is_active !== false;
|
||||||
|
|
||||||
// 移动端:使用下拉菜单
|
// 移动端:使用下拉菜单
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default function WorldSetting() {
|
|||||||
// 可以在这里显示生成的内容片段(可选)
|
// 可以在这里显示生成的内容片段(可选)
|
||||||
console.log('生成片段:', chunk);
|
console.log('生成片段:', chunk);
|
||||||
},
|
},
|
||||||
onResult: (result: any) => {
|
onResult: (result: { time_period: string; location: string; atmosphere: string; rules: string }) => {
|
||||||
// 保存新生成的数据
|
// 保存新生成的数据
|
||||||
const newData = {
|
const newData = {
|
||||||
time_period: result.time_period,
|
time_period: result.time_period,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export default function WritingStyles() {
|
|||||||
// 加载风格列表 - 如果有项目则加载项目风格(包含默认标记),否则加载用户风格
|
// 加载风格列表 - 如果有项目则加载项目风格(包含默认标记),否则加载用户风格
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadStyles();
|
loadStyles();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentProject?.id]);
|
}, [currentProject?.id]);
|
||||||
|
|
||||||
const loadStyles = async () => {
|
const loadStyles = async () => {
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ export const settingsApi = {
|
|||||||
getAvailableModels: (params: { api_key: string; api_base_url: string; provider: string }) =>
|
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 }),
|
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 }) =>
|
testApiConnection: (params: { api_key: string; api_base_url: string; provider: string; llm_model: string; temperature?: number; max_tokens?: number }) =>
|
||||||
api.post<unknown, {
|
api.post<unknown, {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
@@ -200,7 +200,7 @@ export const settingsApi = {
|
|||||||
provider?: string;
|
provider?: string;
|
||||||
model?: string;
|
model?: string;
|
||||||
response_preview?: string;
|
response_preview?: string;
|
||||||
details?: Record<string, boolean>;
|
details?: Record<string, boolean | number>;
|
||||||
error?: string;
|
error?: string;
|
||||||
error_type?: string;
|
error_type?: string;
|
||||||
suggestions?: string[];
|
suggestions?: string[];
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export async function checkLatestVersion(): Promise<VersionCheckResult> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('无法从 Badge API 解析版本信息');
|
throw new Error('无法从 Badge API 解析版本信息');
|
||||||
} catch (error) {
|
} catch {
|
||||||
// 失败时返回无更新
|
// 失败时返回无更新
|
||||||
return {
|
return {
|
||||||
hasUpdate: false,
|
hasUpdate: false,
|
||||||
|
|||||||
@@ -112,4 +112,7 @@ export const EventNames = {
|
|||||||
CHAPTER_UPDATED: 'chapter:updated',
|
CHAPTER_UPDATED: 'chapter:updated',
|
||||||
CHAPTER_DELETED: 'chapter:deleted',
|
CHAPTER_DELETED: 'chapter:deleted',
|
||||||
CHAPTER_NEEDS_REFRESH: 'chapter:needsRefresh',
|
CHAPTER_NEEDS_REFRESH: 'chapter:needsRefresh',
|
||||||
|
|
||||||
|
// 视图切换事件
|
||||||
|
SWITCH_TO_MCP_VIEW: 'view:switchToMcp',
|
||||||
} as const;
|
} as const;
|
||||||
@@ -113,7 +113,7 @@ class SessionManager {
|
|||||||
await this.refreshSession();
|
await this.refreshSession();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
// 静默处理错误
|
// 静默处理错误
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +231,7 @@ class SessionManager {
|
|||||||
try {
|
try {
|
||||||
await this.refreshSession();
|
await this.refreshSession();
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
export interface SSEMessage {
|
export interface SSEMessage {
|
||||||
type: 'progress' | 'chunk' | 'result' | 'error' | 'done';
|
type: 'progress' | 'chunk' | 'result' | 'error' | 'done';
|
||||||
message?: string;
|
message?: string;
|
||||||
@@ -61,7 +62,7 @@ export class SSEClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleMessage(message: SSEMessage, resolve: Function, reject: Function) {
|
private handleMessage(message: SSEMessage, resolve: (value: any) => void, reject: (reason?: any) => void) {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case 'progress':
|
case 'progress':
|
||||||
if (this.options.onProgress && message.progress !== undefined) {
|
if (this.options.onProgress && message.progress !== undefined) {
|
||||||
@@ -129,6 +130,7 @@ export class SSEPostClient {
|
|||||||
private options: SSEClientOptions;
|
private options: SSEClientOptions;
|
||||||
private abortController: AbortController | null = null;
|
private abortController: AbortController | null = null;
|
||||||
private accumulatedContent: string = '';
|
private accumulatedContent: string = '';
|
||||||
|
private resultData: any = null;
|
||||||
|
|
||||||
constructor(url: string, data: any, options: SSEClientOptions = {}) {
|
constructor(url: string, data: any, options: SSEClientOptions = {}) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
@@ -137,7 +139,12 @@ export class SSEPostClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async connect(): Promise<any> {
|
async connect(): Promise<any> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
this.connectInternal(resolve, reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async connectInternal(resolve: (value: any) => void, reject: (reason?: any) => void) {
|
||||||
try {
|
try {
|
||||||
this.abortController = new AbortController();
|
this.abortController = new AbortController();
|
||||||
|
|
||||||
@@ -232,10 +239,9 @@ export class SSEPostClient {
|
|||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleMessage(message: SSEMessage, resolve: Function, reject: Function) {
|
private async handleMessage(message: SSEMessage, resolve: (value: any) => void, reject: (reason?: any) => void) {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case 'progress':
|
case 'progress':
|
||||||
if (this.options.onProgress && message.progress !== undefined) {
|
if (this.options.onProgress && message.progress !== undefined) {
|
||||||
@@ -261,7 +267,7 @@ export class SSEPostClient {
|
|||||||
if (this.options.onResult && message.data) {
|
if (this.options.onResult && message.data) {
|
||||||
this.options.onResult(message.data);
|
this.options.onResult(message.data);
|
||||||
}
|
}
|
||||||
(this as any).resultData = message.data;
|
this.resultData = message.data;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'error':
|
case 'error':
|
||||||
@@ -275,8 +281,8 @@ export class SSEPostClient {
|
|||||||
if (this.options.onComplete) {
|
if (this.options.onComplete) {
|
||||||
this.options.onComplete();
|
this.options.onComplete();
|
||||||
}
|
}
|
||||||
if ((this as any).resultData) {
|
if (this.resultData) {
|
||||||
resolve((this as any).resultData);
|
resolve(this.resultData);
|
||||||
} else if (this.accumulatedContent) {
|
} else if (this.accumulatedContent) {
|
||||||
resolve({ content: this.accumulatedContent });
|
resolve({ content: this.accumulatedContent });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user