style: 移动端响应式优化:MCPPlugins页面完整重构、多处Modal弹窗适配、项目卡片单列显示

This commit is contained in:
xiamuceer-j
2026-01-14 19:48:18 +08:00
parent fb16cc4072
commit dc3dbaaf2c
8 changed files with 188 additions and 99 deletions
+11 -3
View File
@@ -564,6 +564,11 @@ async def export_project_data(
Args: Args:
project_id: 项目ID project_id: 项目ID
options: 导出选项 options: 导出选项
- include_generation_history: 是否包含生成历史
- include_writing_styles: 是否包含写作风格
- include_careers: 是否包含职业系统
- include_memories: 是否包含故事记忆
- include_plot_analysis: 是否包含剧情分析
Returns: Returns:
JSON文件下载 JSON文件下载
@@ -575,7 +580,7 @@ async def export_project_data(
logger.warning("未登录用户尝试导出项目数据") logger.warning("未登录用户尝试导出项目数据")
raise HTTPException(status_code=401, detail="未登录") raise HTTPException(status_code=401, detail="未登录")
logger.info(f"开始导出项目数据: project_id={project_id}, user_id={user_id}") logger.info(f"开始导出项目数据: project_id={project_id}, user_id={user_id}, options={options.model_dump()}")
# 只查询当前用户的项目 # 只查询当前用户的项目
result = await db.execute( result = await db.execute(
@@ -590,12 +595,15 @@ async def export_project_data(
logger.warning(f"项目不存在或无权访问: project_id={project_id}, user_id={user_id}") logger.warning(f"项目不存在或无权访问: project_id={project_id}, user_id={user_id}")
raise HTTPException(status_code=404, detail="项目不存在") raise HTTPException(status_code=404, detail="项目不存在")
# 导出数据 # 导出数据(使用所有选项)
export_data = await ImportExportService.export_project( export_data = await ImportExportService.export_project(
project_id=project_id, project_id=project_id,
db=db, db=db,
include_generation_history=options.include_generation_history, include_generation_history=options.include_generation_history,
include_writing_styles=options.include_writing_styles include_writing_styles=options.include_writing_styles,
include_careers=options.include_careers,
include_memories=options.include_memories,
include_plot_analysis=options.include_plot_analysis
) )
# 转换为JSON # 转换为JSON
+7 -9
View File
@@ -635,21 +635,19 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
title="章节分析" title="章节分析"
open={visible} open={visible}
onCancel={onClose} onCancel={onClose}
width={isMobile ? '100%' : '90%'} width={isMobile ? 'calc(100vw - 32px)' : '90%'}
centered={!isMobile} centered
style={{ style={{
maxWidth: isMobile ? '100%' : '1400px', maxWidth: isMobile ? 'calc(100vw - 32px)' : '1400px',
paddingBottom: 0, margin: isMobile ? '0 auto' : undefined,
top: isMobile ? 0 : undefined, padding: isMobile ? '0 16px' : undefined
margin: isMobile ? 0 : undefined,
maxHeight: isMobile ? '100vh' : undefined
}} }}
styles={{ styles={{
body: { body: {
padding: isMobile ? '12px' : '24px', padding: isMobile ? '12px' : '24px',
paddingBottom: 0, paddingBottom: 0,
maxHeight: isMobile ? 'calc(100vh - 110px)' : undefined, maxHeight: isMobile ? 'calc(100vh - 200px)' : 'calc(90vh - 150px)',
overflowY: isMobile ? 'auto' : undefined overflowY: 'auto'
} }
}} }}
footer={[ footer={[
+28 -24
View File
@@ -1230,16 +1230,16 @@ export default function Chapters() {
<span style={{ wordBreak: 'break-word' }}>{chapter.chapter_number}</span> <span style={{ wordBreak: 'break-word' }}>{chapter.chapter_number}</span>
</Space> </Space>
), ),
width: isMobile ? '95%' : 800, width: isMobile ? 'calc(100vw - 32px)' : 800,
centered: true, centered: true,
style: isMobile ? { style: isMobile ? {
top: 20, maxWidth: 'calc(100vw - 32px)',
maxWidth: 'calc(100vw - 16px)', margin: '0 auto',
margin: '0 8px' padding: '0 16px'
} : undefined, } : undefined,
styles: { styles: {
body: { body: {
maxHeight: isMobile ? 'calc(100vh - 150px)' : 'calc(80vh - 110px)', maxHeight: isMobile ? 'calc(100vh - 200px)' : 'calc(80vh - 110px)',
overflowY: 'auto' overflowY: 'auto'
} }
}, },
@@ -2006,17 +2006,16 @@ export default function Chapters() {
open={isModalOpen} open={isModalOpen}
onCancel={() => setIsModalOpen(false)} onCancel={() => setIsModalOpen(false)}
footer={null} footer={null}
centered={!isMobile} centered
width={isMobile ? 'calc(100% - 32px)' : 520} width={isMobile ? 'calc(100vw - 32px)' : 520}
style={isMobile ? { style={isMobile ? {
top: 20,
paddingBottom: 0,
maxWidth: 'calc(100vw - 32px)', maxWidth: 'calc(100vw - 32px)',
margin: '0 16px' margin: '0 auto',
padding: '0 16px'
} : undefined} } : undefined}
styles={{ styles={{
body: { body: {
maxHeight: isMobile ? 'calc(100vh - 150px)' : 'calc(80vh - 110px)', maxHeight: isMobile ? 'calc(100vh - 200px)' : 'calc(80vh - 110px)',
overflowY: 'auto' overflowY: 'auto'
} }
}} }}
@@ -2082,17 +2081,16 @@ export default function Chapters() {
closable={!isGenerating} closable={!isGenerating}
maskClosable={!isGenerating} maskClosable={!isGenerating}
keyboard={!isGenerating} keyboard={!isGenerating}
width={isMobile ? 'calc(100% - 32px)' : '85%'} width={isMobile ? 'calc(100vw - 32px)' : '85%'}
centered={!isMobile} centered
style={isMobile ? { style={isMobile ? {
top: 20,
paddingBottom: 0,
maxWidth: 'calc(100vw - 32px)', maxWidth: 'calc(100vw - 32px)',
margin: '0 16px' margin: '0 auto',
padding: '0 16px'
} : undefined} } : undefined}
styles={{ styles={{
body: { body: {
maxHeight: isMobile ? 'calc(100vh - 150px)' : 'calc(100vh - 110px)', maxHeight: isMobile ? 'calc(100vh - 200px)' : 'calc(100vh - 110px)',
overflowY: 'auto', overflowY: 'auto',
padding: isMobile ? '16px 12px' : '8px' padding: isMobile ? '16px 12px' : '8px'
} }
@@ -2366,6 +2364,7 @@ export default function Chapters() {
content: '批量生成正在进行中,确定要取消吗?', content: '批量生成正在进行中,确定要取消吗?',
okText: '确定取消', okText: '确定取消',
cancelText: '继续生成', cancelText: '继续生成',
centered: true,
onOk: () => { onOk: () => {
handleCancelBatchGenerate(); handleCancelBatchGenerate();
setBatchGenerateVisible(false); setBatchGenerateVisible(false);
@@ -2376,7 +2375,7 @@ export default function Chapters() {
} }
}} }}
footer={!batchGenerating ? ( footer={!batchGenerating ? (
<Space style={{ width: '100%', justifyContent: 'flex-end' }}> <Space style={{ width: '100%', justifyContent: 'flex-end', flexWrap: 'wrap' }}>
<Button onClick={() => setBatchGenerateVisible(false)}> <Button onClick={() => setBatchGenerateVisible(false)}>
</Button> </Button>
@@ -2385,13 +2384,18 @@ export default function Chapters() {
</Button> </Button>
</Space> </Space>
) : null} ) : null}
width={700} width={isMobile ? 'calc(100vw - 32px)' : 700}
centered centered
closable={!batchGenerating} closable={!batchGenerating}
maskClosable={!batchGenerating} maskClosable={!batchGenerating}
style={isMobile ? {
maxWidth: 'calc(100vw - 32px)',
margin: '0 auto',
padding: '0 16px'
} : undefined}
styles={{ styles={{
body: { body: {
maxHeight: 'calc(100vh - 260px)', maxHeight: isMobile ? 'calc(100vh - 200px)' : 'calc(100vh - 260px)',
overflowY: 'auto', overflowY: 'auto',
overflowX: 'hidden' overflowX: 'hidden'
} }
@@ -2419,7 +2423,7 @@ export default function Chapters() {
/> />
{/* 第一行:起始章节 + 生成数量 */} {/* 第一行:起始章节 + 生成数量 */}
<div style={{ display: 'flex', gap: 16 }}> <div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: isMobile ? 0 : 16 }}>
<Form.Item <Form.Item
label="起始章节" label="起始章节"
name="startChapterNumber" name="startChapterNumber"
@@ -2444,7 +2448,7 @@ export default function Chapters() {
rules={[{ required: true, message: '请选择' }]} rules={[{ required: true, message: '请选择' }]}
style={{ marginBottom: 12 }} style={{ marginBottom: 12 }}
> >
<Radio.Group buttonStyle="solid"> <Radio.Group buttonStyle="solid" size={isMobile ? 'small' : 'middle'}>
<Radio.Button value={5}>5</Radio.Button> <Radio.Button value={5}>5</Radio.Button>
<Radio.Button value={10}>10</Radio.Button> <Radio.Button value={10}>10</Radio.Button>
<Radio.Button value={15}>15</Radio.Button> <Radio.Button value={15}>15</Radio.Button>
@@ -2454,7 +2458,7 @@ export default function Chapters() {
</div> </div>
{/* 第二行:写作风格 + 目标字数 */} {/* 第二行:写作风格 + 目标字数 */}
<div style={{ display: 'flex', gap: 16 }}> <div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: isMobile ? 0 : 16 }}>
<Form.Item <Form.Item
label="写作风格" label="写作风格"
name="styleId" name="styleId"
@@ -2494,7 +2498,7 @@ export default function Chapters() {
</div> </div>
{/* 第三行:AI模型 + 同步分析 */} {/* 第三行:AI模型 + 同步分析 */}
<div style={{ display: 'flex', gap: 16 }}> <div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: isMobile ? 0 : 16 }}>
<Form.Item <Form.Item
label="AI模型" label="AI模型"
tooltip="不选则使用默认模型" tooltip="不选则使用默认模型"
+132 -57
View File
@@ -37,8 +37,17 @@ const { Paragraph, Text, Title } = Typography;
const { TextArea } = Input; const { TextArea } = Input;
export default function MCPPluginsPage() { export default function MCPPluginsPage() {
const isMobile = window.innerWidth <= 768; const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
const [form] = Form.useForm(); const [form] = Form.useForm();
// 响应式监听窗口大小变化
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth <= 768);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
const [modal, contextHolder] = Modal.useModal(); const [modal, contextHolder] = Modal.useModal();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [plugins, setPlugins] = useState<MCPPlugin[]>([]); const [plugins, setPlugins] = useState<MCPPlugin[]>([]);
@@ -591,10 +600,9 @@ export default function MCPPluginsPage() {
<div style={{ <div style={{
minHeight: '90vh', minHeight: '90vh',
background: 'linear-gradient(180deg, var(--color-bg-base) 0%, #EEF2F3 100%)', background: 'linear-gradient(180deg, var(--color-bg-base) 0%, #EEF2F3 100%)',
padding: isMobile ? '20px 16px' : '24px 24px', padding: isMobile ? '20px 16px 70px' : '24px 24px 70px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
marginBottom: '55px',
}}> }}>
<div style={{ <div style={{
maxWidth: 1400, maxWidth: 1400,
@@ -657,7 +665,7 @@ export default function MCPPluginsPage() {
</Col> </Col>
</Row> </Row>
<div style={{ marginTop: isMobile ? 16 : 24, display: 'flex', gap: 16, flexDirection: isMobile ? 'column' : 'row' }}> <div style={{ marginTop: isMobile ? 16 : 24, display: 'flex', gap: isMobile ? 12 : 16, flexDirection: isMobile ? 'column' : 'row' }}>
<Card <Card
variant="borderless" variant="borderless"
style={{ style={{
@@ -668,27 +676,36 @@ export default function MCPPluginsPage() {
backdropFilter: 'blur(10px)', backdropFilter: 'blur(10px)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.03)' boxShadow: '0 4px 12px rgba(0, 0, 0, 0.03)'
}} }}
styles={{ body: { padding: 20 } }} styles={{ body: { padding: isMobile ? 14 : 20 } }}
> >
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <div style={{
<Space align="start"> display: 'flex',
flexDirection: isMobile ? 'column' : 'row',
justifyContent: 'space-between',
alignItems: isMobile ? 'stretch' : 'center',
gap: isMobile ? 12 : 0
}}>
<Space align="start" style={{ flex: 1 }}>
<div style={{ <div style={{
width: 40, height: 40, borderRadius: '50%', width: isMobile ? 36 : 40,
height: isMobile ? 36 : 40,
borderRadius: '50%',
background: modelSupportStatus === 'supported' ? 'var(--color-success-bg)' : modelSupportStatus === 'unsupported' ? 'var(--color-error-bg)' : 'var(--color-info-bg)', background: modelSupportStatus === 'supported' ? 'var(--color-success-bg)' : modelSupportStatus === 'unsupported' ? 'var(--color-error-bg)' : 'var(--color-info-bg)',
display: 'flex', alignItems: 'center', justifyContent: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center',
border: `1px solid ${modelSupportStatus === 'supported' ? 'var(--color-success-border)' : modelSupportStatus === 'unsupported' ? 'var(--color-error-border)' : 'var(--color-info-border)'}` border: `1px solid ${modelSupportStatus === 'supported' ? 'var(--color-success-border)' : modelSupportStatus === 'unsupported' ? 'var(--color-error-border)' : 'var(--color-info-border)'}`,
flexShrink: 0
}}> }}>
{modelSupportStatus === 'supported' ? ( {modelSupportStatus === 'supported' ? (
<CheckCircleOutlined style={{ fontSize: 20, color: 'var(--color-success)' }} /> <CheckCircleOutlined style={{ fontSize: isMobile ? 18 : 20, color: 'var(--color-success)' }} />
) : modelSupportStatus === 'unsupported' ? ( ) : modelSupportStatus === 'unsupported' ? (
<CloseCircleOutlined style={{ fontSize: 20, color: 'var(--color-error)' }} /> <CloseCircleOutlined style={{ fontSize: isMobile ? 18 : 20, color: 'var(--color-error)' }} />
) : ( ) : (
<QuestionCircleOutlined style={{ fontSize: 20, color: 'var(--color-info)' }} /> <QuestionCircleOutlined style={{ fontSize: isMobile ? 18 : 20, color: 'var(--color-info)' }} />
)} )}
</div> </div>
<div> <div style={{ flex: 1, minWidth: 0 }}>
<Text strong style={{ fontSize: 16, display: 'block', color: 'var(--color-text-primary)' }}></Text> <Text strong style={{ fontSize: isMobile ? 14 : 16, display: 'block', color: 'var(--color-text-primary)' }}></Text>
<Text type="secondary" style={{ fontSize: 13 }}> <Text type="secondary" style={{ fontSize: isMobile ? 12 : 13, display: 'block', lineHeight: 1.5 }}>
{modelSupportStatus === 'supported' {modelSupportStatus === 'supported'
? '当前模型支持 Function Calling,可正常使用 MCP 插件' ? '当前模型支持 Function Calling,可正常使用 MCP 插件'
: modelSupportStatus === 'unsupported' : modelSupportStatus === 'unsupported'
@@ -702,7 +719,8 @@ export default function MCPPluginsPage() {
icon={<ApiOutlined />} icon={<ApiOutlined />}
onClick={handleCheckFunctionCalling} onClick={handleCheckFunctionCalling}
loading={checkingFunctionCalling} loading={checkingFunctionCalling}
style={{ borderRadius: 8 }} style={{ borderRadius: 8, width: isMobile ? '100%' : 'auto' }}
size={isMobile ? 'middle' : 'middle'}
> >
{modelSupportStatus === 'unknown' ? '开始检测' : '重新检测'} {modelSupportStatus === 'unknown' ? '开始检测' : '重新检测'}
</Button> </Button>
@@ -719,13 +737,13 @@ export default function MCPPluginsPage() {
backdropFilter: 'blur(10px)', backdropFilter: 'blur(10px)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.03)' boxShadow: '0 4px 12px rgba(0, 0, 0, 0.03)'
}} }}
styles={{ body: { padding: 20 } }} styles={{ body: { padding: isMobile ? 14 : 20 } }}
> >
<Space align="start"> <Space align="start">
<InfoCircleOutlined style={{ fontSize: 20, color: 'var(--color-primary)', marginTop: 4 }} /> <InfoCircleOutlined style={{ fontSize: isMobile ? 18 : 20, color: 'var(--color-primary)', marginTop: 2, flexShrink: 0 }} />
<div> <div style={{ flex: 1, minWidth: 0 }}>
<Text strong style={{ fontSize: 16, display: 'block', color: 'var(--color-text-primary)', marginBottom: 4 }}> MCP </Text> <Text strong style={{ fontSize: isMobile ? 14 : 16, display: 'block', color: 'var(--color-text-primary)', marginBottom: 4 }}> MCP </Text>
<Text style={{ fontSize: 13, display: 'block', color: 'var(--color-text-secondary)', lineHeight: 1.6 }}> <Text style={{ fontSize: isMobile ? 12 : 13, display: 'block', color: 'var(--color-text-secondary)', lineHeight: 1.6 }}>
MCP (Model Context Protocol) AI AI 访API MCP (Model Context Protocol) AI AI 访API
</Text> </Text>
</div> </div>
@@ -769,7 +787,7 @@ export default function MCPPluginsPage() {
</Button> </Button>
</Empty> </Empty>
) : ( ) : (
<Space direction="vertical" size="middle" style={{ width: '100%' }}> <Space direction="vertical" size={isMobile ? 'small' : 'middle'} style={{ width: '100%' }}>
{plugins.map((plugin) => ( {plugins.map((plugin) => (
<Card <Card
key={plugin.id} key={plugin.id}
@@ -778,30 +796,62 @@ export default function MCPPluginsPage() {
borderRadius: 8, borderRadius: 8,
border: '1px solid #f0f0f0', border: '1px solid #f0f0f0',
}} }}
styles={{ body: { padding: isMobile ? 12 : 16 } }}
> >
<div <div
style={{ style={{
display: 'flex', display: 'flex',
justifyContent: 'space-between', flexDirection: 'column',
alignItems: 'flex-start', gap: isMobile ? 12 : 16,
gap: '16px',
flexWrap: isMobile ? 'wrap' : 'nowrap',
}} }}
> >
{/* 插件信息区域 */}
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<Space direction="vertical" size="small" style={{ width: '100%' }}> <Space direction="vertical" size="small" style={{ width: '100%' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}> {/* 标题和状态标签 */}
<Text strong style={{ fontSize: isMobile ? '14px' : '16px' }}> <div style={{
{plugin.display_name || plugin.plugin_name} display: 'flex',
</Text> alignItems: 'center',
{getStatusTag(plugin)} gap: '6px',
<Tag color={plugin.plugin_type === 'http' || plugin.plugin_type === 'streamable_http' || plugin.plugin_type === 'sse' ? 'blue' : 'cyan'}> flexWrap: 'wrap',
justifyContent: 'space-between'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'wrap', flex: 1 }}>
<Text strong style={{ fontSize: isMobile ? '14px' : '16px' }}>
{plugin.display_name || plugin.plugin_name}
</Text>
{getStatusTag(plugin)}
</div>
{/* 移动端:开关放在标题行右侧 */}
{isMobile && (
<Switch
title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : (plugin.enabled ? '禁用插件' : '启用插件')}
checked={plugin.enabled}
onChange={(checked) => handleToggle(plugin, checked)}
disabled={modelSupportStatus !== 'supported'}
size="small"
checkedChildren="开"
unCheckedChildren="关"
style={{
flexShrink: 0,
height: 16,
minHeight: 16,
lineHeight: '16px'
}}
/>
)}
</div>
{/* 类型和分类标签 */}
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
<Tag color={plugin.plugin_type === 'http' || plugin.plugin_type === 'streamable_http' || plugin.plugin_type === 'sse' ? 'blue' : 'cyan'} style={{ fontSize: isMobile ? 11 : 12 }}>
{plugin.plugin_type?.toUpperCase() || 'UNKNOWN'} {plugin.plugin_type?.toUpperCase() || 'UNKNOWN'}
</Tag> </Tag>
{plugin.category && plugin.category !== 'general' && ( {plugin.category && plugin.category !== 'general' && (
<Tag color="purple">{plugin.category}</Tag> <Tag color="purple" style={{ fontSize: isMobile ? 11 : 12 }}>{plugin.category}</Tag>
)} )}
</div> </div>
{plugin.description && ( {plugin.description && (
<Paragraph <Paragraph
type="secondary" type="secondary"
@@ -817,8 +867,13 @@ export default function MCPPluginsPage() {
{/* 只显示有值的URL或命令,脱敏处理敏感信息 */} {/* 只显示有值的URL或命令,脱敏处理敏感信息 */}
{(plugin.plugin_type === 'http' || plugin.plugin_type === 'streamable_http' || plugin.plugin_type === 'sse') && plugin.server_url && ( {(plugin.plugin_type === 'http' || plugin.plugin_type === 'streamable_http' || plugin.plugin_type === 'sse') && plugin.server_url && (
<div style={{ fontSize: isMobile ? '11px' : '12px' }}> <div style={{
<Text type="secondary" code> fontSize: isMobile ? '11px' : '12px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}>
<Text type="secondary" code style={{ fontSize: 'inherit' }}>
{(() => { {(() => {
// 脱敏处理:隐藏URL中的API Key // 脱敏处理:隐藏URL中的API Key
const url = plugin.server_url; const url = plugin.server_url;
@@ -849,8 +904,13 @@ export default function MCPPluginsPage() {
)} )}
{plugin.plugin_type === 'stdio' && plugin.command && ( {plugin.plugin_type === 'stdio' && plugin.command && (
<div style={{ fontSize: isMobile ? '11px' : '12px' }}> <div style={{
<Text type="secondary" code> fontSize: isMobile ? '11px' : '12px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}>
<Text type="secondary" code style={{ fontSize: 'inherit' }}>
{plugin.command} {plugin.args?.join(' ')} {plugin.command} {plugin.args?.join(' ')}
</Text> </Text>
</div> </div>
@@ -865,20 +925,27 @@ export default function MCPPluginsPage() {
</Space> </Space>
</div> </div>
<Space size="small" wrap> {/* 操作按钮区域 */}
<Switch <div style={{
title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : (plugin.enabled ? '禁用插件' : '启用插件')} display: 'flex',
checked={plugin.enabled} justifyContent: isMobile ? 'flex-end' : 'flex-start',
onChange={(checked) => handleToggle(plugin, checked)} alignItems: 'center',
disabled={modelSupportStatus !== 'supported'} gap: isMobile ? 8 : 8,
size={isMobile ? 'small' : 'default'} flexWrap: 'wrap',
style={{ borderTop: isMobile ? '1px solid #f0f0f0' : 'none',
flexShrink: 0, paddingTop: isMobile ? 12 : 0
height: isMobile ? 16 : 22, }}>
minHeight: isMobile ? 16 : 22, {/* 桌面端显示开关 */}
lineHeight: isMobile ? '16px' : '22px' {!isMobile && (
}} <Switch
/> title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : (plugin.enabled ? '禁用插件' : '启用插件')}
checked={plugin.enabled}
onChange={(checked) => handleToggle(plugin, checked)}
disabled={modelSupportStatus !== 'supported'}
checkedChildren="开"
unCheckedChildren="关"
/>
)}
<Button <Button
title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : '测试连接'} title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : '测试连接'}
icon={<ThunderboltOutlined />} icon={<ThunderboltOutlined />}
@@ -886,21 +953,27 @@ export default function MCPPluginsPage() {
loading={testingPluginId === plugin.id} loading={testingPluginId === plugin.id}
disabled={modelSupportStatus !== 'supported'} disabled={modelSupportStatus !== 'supported'}
size={isMobile ? 'small' : 'middle'} size={isMobile ? 'small' : 'middle'}
/> >
{!isMobile && '测试'}
</Button>
<Button <Button
title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : '查看工具'} title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : '查看工具'}
icon={<ToolOutlined />} icon={<ToolOutlined />}
onClick={() => handleViewTools(plugin.id)} onClick={() => handleViewTools(plugin.id)}
disabled={modelSupportStatus !== 'supported' || !plugin.enabled || plugin.status !== 'active'} disabled={modelSupportStatus !== 'supported' || !plugin.enabled || plugin.status !== 'active'}
size={isMobile ? 'small' : 'middle'} size={isMobile ? 'small' : 'middle'}
/> >
{!isMobile && '工具'}
</Button>
<Button <Button
title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : '编辑'} title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : '编辑'}
icon={<EditOutlined />} icon={<EditOutlined />}
onClick={() => handleEdit(plugin)} onClick={() => handleEdit(plugin)}
disabled={modelSupportStatus !== 'supported'} disabled={modelSupportStatus !== 'supported'}
size={isMobile ? 'small' : 'middle'} size={isMobile ? 'small' : 'middle'}
/> >
{!isMobile && '编辑'}
</Button>
<Button <Button
title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : '删除'} title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : '删除'}
danger danger
@@ -908,8 +981,10 @@ export default function MCPPluginsPage() {
onClick={() => handleDelete(plugin)} onClick={() => handleDelete(plugin)}
disabled={modelSupportStatus !== 'supported'} disabled={modelSupportStatus !== 'supported'}
size={isMobile ? 'small' : 'middle'} size={isMobile ? 'small' : 'middle'}
/> >
</Space> {!isMobile && '删除'}
</Button>
</div>
</div> </div>
</Card> </Card>
))} ))}
@@ -942,7 +1017,7 @@ export default function MCPPluginsPage() {
extra="粘贴标准MCP配置,系统自动提取插件名称。支持HTTP和Stdio类型" extra="粘贴标准MCP配置,系统自动提取插件名称。支持HTTP和Stdio类型"
> >
<TextArea <TextArea
rows={16} rows={isMobile ? 12 : 16}
placeholder={`示例: placeholder={`示例:
{ {
"mcpServers": { "mcpServers": {
+1 -2
View File
@@ -253,10 +253,9 @@ export default function PromptTemplates() {
<div style={{ <div style={{
minHeight: '90vh', minHeight: '90vh',
background: 'linear-gradient(180deg, var(--color-bg-base) 0%, #EEF2F3 100%)', background: 'linear-gradient(180deg, var(--color-bg-base) 0%, #EEF2F3 100%)',
padding: isMobile ? '20px 16px' : '24px 24px', padding: isMobile ? '20px 16px 70px' : '24px 24px 70px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
marginBottom: '55px',
}}> }}>
<div style={{ <div style={{
maxWidth: 1400, maxWidth: 1400,
+1 -2
View File
@@ -794,10 +794,9 @@ export default function SettingsPage() {
<div style={{ <div style={{
minHeight: '90vh', minHeight: '90vh',
background: 'linear-gradient(180deg, var(--color-bg-base) 0%, #EEF2F3 100%)', background: 'linear-gradient(180deg, var(--color-bg-base) 0%, #EEF2F3 100%)',
padding: isMobile ? '20px 16px' : '24px 24px', padding: isMobile ? '20px 16px 70px' : '24px 24px 70px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
marginBottom: '55px',
}}> }}>
<div style={{ <div style={{
maxWidth: 1400, maxWidth: 1400,
+1 -1
View File
@@ -21,7 +21,7 @@ interface SponsorOption {
const sponsorOptions: SponsorOption[] = [ const sponsorOptions: SponsorOption[] = [
{ amount: 5, label: '🌶️ 一包辣条', image: '/5.png', description: '¥5' }, { amount: 5, label: '🌶️ 一包辣条', image: '/5.png', description: '¥5' },
{ amount: 10, label: '🍱 一顿拼好饭', image: '/10.png', description: '¥10' }, { amount: 10, label: '🍱 一顿拼好饭', image: '/10.png', description: '¥10' },
{ amount: 20, label: '🧋 一杯咖啡', image: '/20.png', description: '¥20' }, { amount: 20, label: ' 一杯咖啡', image: '/20.png', description: '¥20' },
{ amount: 50, label: '🍖 一次烧烤', image: '/50.png', description: '¥50' }, { amount: 50, label: '🍖 一次烧烤', image: '/50.png', description: '¥50' },
{ amount: 99, label: '🍲 一顿海底捞', image: '/99.png', description: '¥99' }, { amount: 99, label: '🍲 一顿海底捞', image: '/99.png', description: '¥99' },
]; ];
+7 -1
View File
@@ -289,7 +289,13 @@ export const projectApi = {
}, },
// 导出项目数据为JSON // 导出项目数据为JSON
exportProjectData: async (id: string, options: { include_generation_history?: boolean; include_writing_styles?: boolean }) => { exportProjectData: async (id: string, options: {
include_generation_history?: boolean;
include_writing_styles?: boolean;
include_careers?: boolean;
include_memories?: boolean;
include_plot_analysis?: boolean;
}) => {
const response = await axios.post( const response = await axios.post(
`/api/projects/${id}/export-data`, `/api/projects/${id}/export-data`,
options, options,