style: 移动端响应式优化:MCPPlugins页面完整重构、多处Modal弹窗适配、项目卡片单列显示
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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={[
|
||||||
|
|||||||
@@ -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="不选则使用默认模型"
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user