update: 更新设置页面测试功能,新增temperature和max_tokens参数

This commit is contained in:
xiamuceer-j
2026-01-14 14:30:06 +08:00
parent 46debab624
commit e412e809eb
3 changed files with 143 additions and 114 deletions
+16 -7
View File
@@ -349,6 +349,8 @@ class ApiTestRequest(BaseModel):
api_base_url: str api_base_url: str
provider: str provider: str
llm_model: str llm_model: str
temperature: Optional[float] = None
max_tokens: Optional[int] = None
@router.post("/check-function-calling") @router.post("/check-function-calling")
@@ -578,7 +580,7 @@ async def test_api_connection(data: ApiTestRequest):
测试 API 连接和配置是否正确 测试 API 连接和配置是否正确
Args: Args:
data: 包含 API 配置的请求数据 data: 包含 API 配置的请求数据(包括 temperature 和 max_tokens
Returns: Returns:
测试结果包含状态、响应时间和详细信息 测试结果包含状态、响应时间和详细信息
@@ -587,19 +589,22 @@ async def test_api_connection(data: ApiTestRequest):
api_base_url = data.api_base_url api_base_url = data.api_base_url
provider = data.provider provider = data.provider
llm_model = data.llm_model llm_model = data.llm_model
# 使用前端传递的参数,如果未传递则使用默认值
temperature = data.temperature if data.temperature is not None else 0.7
max_tokens = data.max_tokens if data.max_tokens is not None else 2000
import time import time
try: try:
start_time = time.time() start_time = time.time()
# 创建临时 AI 服务实例 # 创建临时 AI 服务实例,使用前端传递的参数
test_service = AIService( test_service = AIService(
api_provider=provider, api_provider=provider,
api_key=api_key, api_key=api_key,
api_base_url=api_base_url, api_base_url=api_base_url,
default_model=llm_model, default_model=llm_model,
default_temperature=0.7, default_temperature=temperature,
default_max_tokens=100 default_max_tokens=max_tokens
) )
# 发送简单的测试请求 # 发送简单的测试请求
@@ -609,13 +614,15 @@ async def test_api_connection(data: ApiTestRequest):
logger.info(f" - 提供商: {provider}") logger.info(f" - 提供商: {provider}")
logger.info(f" - 模型: {llm_model}") logger.info(f" - 模型: {llm_model}")
logger.info(f" - Base URL: {api_base_url}") logger.info(f" - Base URL: {api_base_url}")
logger.info(f" - Temperature: {temperature}")
logger.info(f" - Max Tokens: {max_tokens}")
response = await test_service.generate_text( response = await test_service.generate_text(
prompt=test_prompt, prompt=test_prompt,
provider=provider, provider=provider,
model=llm_model, model=llm_model,
temperature=0.7, temperature=temperature,
max_tokens=8000, max_tokens=max_tokens,
auto_mcp=False # 测试时不加载MCP工具 auto_mcp=False # 测试时不加载MCP工具
) )
@@ -639,7 +646,9 @@ async def test_api_connection(data: ApiTestRequest):
"details": { "details": {
"api_available": True, "api_available": True,
"model_accessible": True, "model_accessible": True,
"response_valid": bool(response) "response_valid": bool(response),
"temperature": temperature,
"max_tokens": max_tokens
} }
} }
+1 -1
View File
@@ -184,7 +184,7 @@ else:
@app.get("/") @app.get("/")
async def root(): async def root():
return { return {
"message": "欢迎使用AI Story Creator", "message": "欢迎使用MuMuAINovel",
"version": config_settings.app_version, "version": config_settings.app_version,
"docs": "/docs", "docs": "/docs",
"notice": "请先构建前端: cd frontend && npm run build" "notice": "请先构建前端: cd frontend && npm run build"
+126 -106
View File
@@ -1,9 +1,9 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Card, Form, Input, Button, Select, Slider, InputNumber, message, Space, Typography, Spin, Modal, Alert, Grid, Tabs, List, Tag, Popconfirm, Empty, Row, Col } from 'antd'; import { Card, Form, Input, Button, Select, Slider, InputNumber, message, Space, Typography, Spin, Modal, Alert, Grid, Tabs, List, Tag, Popconfirm, Empty, Row, Col } from 'antd';
import { SettingOutlined, SaveOutlined, DeleteOutlined, ReloadOutlined, ArrowLeftOutlined, InfoCircleOutlined, CheckCircleOutlined, CloseCircleOutlined, ThunderboltOutlined, PlusOutlined, EditOutlined, CopyOutlined, WarningOutlined } from '@ant-design/icons'; import { SaveOutlined, DeleteOutlined, ReloadOutlined, InfoCircleOutlined, CheckCircleOutlined, CloseCircleOutlined, ThunderboltOutlined, PlusOutlined, EditOutlined, CopyOutlined, WarningOutlined } from '@ant-design/icons';
import { settingsApi, mcpPluginApi } from '../services/api'; import { settingsApi, mcpPluginApi } from '../services/api';
import type { SettingsUpdate, APIKeyPreset, PresetCreateRequest, APIKeyPresetConfig } from '../types'; import type { SettingsUpdate, APIKeyPreset, PresetCreateRequest, APIKeyPresetConfig } from '../types';
import { eventBus, EventNames } from '../store/eventBus';
const { Title, Text } = Typography; const { Title, Text } = Typography;
const { Option } = Select; const { Option } = Select;
@@ -11,7 +11,6 @@ const { useBreakpoint } = Grid;
const { TextArea } = Input; const { TextArea } = Input;
export default function SettingsPage() { export default function SettingsPage() {
const navigate = useNavigate();
const screens = useBreakpoint(); const screens = useBreakpoint();
const isMobile = !screens.md; // md断点是768px const isMobile = !screens.md; // md断点是768px
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -50,6 +49,7 @@ export default function SettingsPage() {
if (activeTab === 'presets') { if (activeTab === 'presets') {
loadPresets(); loadPresets();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -72,6 +72,7 @@ export default function SettingsPage() {
setIsDefaultSettings(false); setIsDefaultSettings(false);
setHasSettings(true); setHasSettings(true);
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) { } catch (error: any) {
// 如果404表示还没有设置,使用默认值 // 如果404表示还没有设置,使用默认值
if (error?.response?.status === 404) { if (error?.response?.status === 404) {
@@ -167,7 +168,7 @@ export default function SettingsPage() {
okText: '前往 MCP 页面', okText: '前往 MCP 页面',
cancelText: '稍后处理', cancelText: '稍后处理',
onOk: () => { onOk: () => {
navigate('/mcp-plugins'); eventBus.emit(EventNames.SWITCH_TO_MCP_VIEW);
}, },
}); });
} }
@@ -175,7 +176,7 @@ export default function SettingsPage() {
console.error('Failed to disable MCP plugins:', err); console.error('Failed to disable MCP plugins:', err);
} }
} }
} catch (error) { } catch {
message.error('保存设置失败'); message.error('保存设置失败');
} finally { } finally {
setLoading(false); setLoading(false);
@@ -218,7 +219,7 @@ export default function SettingsPage() {
message.success('设置已删除'); message.success('设置已删除');
setHasSettings(false); setHasSettings(false);
form.resetFields(); form.resetFields();
} catch (error) { } catch {
message.error('删除设置失败'); message.error('删除设置失败');
} finally { } finally {
setLoading(false); setLoading(false);
@@ -268,6 +269,7 @@ export default function SettingsPage() {
if (!silent) { if (!silent) {
message.success(`成功获取 ${response.count || response.models.length} 个可用模型`); message.success(`成功获取 ${response.count || response.models.length} 个可用模型`);
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) { } catch (error: any) {
const errorMsg = error?.response?.data?.detail || '获取模型列表失败'; const errorMsg = error?.response?.data?.detail || '获取模型列表失败';
if (!silent) { if (!silent) {
@@ -292,6 +294,8 @@ export default function SettingsPage() {
const apiBaseUrl = form.getFieldValue('api_base_url'); const apiBaseUrl = form.getFieldValue('api_base_url');
const provider = form.getFieldValue('api_provider'); const provider = form.getFieldValue('api_provider');
const modelName = form.getFieldValue('llm_model'); const modelName = form.getFieldValue('llm_model');
const temperature = form.getFieldValue('temperature');
const maxTokens = form.getFieldValue('max_tokens');
if (!apiKey || !apiBaseUrl || !provider || !modelName) { if (!apiKey || !apiBaseUrl || !provider || !modelName) {
message.warning('请先填写完整的配置信息'); message.warning('请先填写完整的配置信息');
@@ -306,7 +310,9 @@ export default function SettingsPage() {
api_key: apiKey, api_key: apiKey,
api_base_url: apiBaseUrl, api_base_url: apiBaseUrl,
provider: provider, provider: provider,
llm_model: modelName llm_model: modelName,
temperature: temperature,
max_tokens: maxTokens
}); });
setTestResult(result); setTestResult(result);
@@ -317,6 +323,7 @@ export default function SettingsPage() {
} else { } else {
message.error('API 测试失败,请查看详细信息'); message.error('API 测试失败,请查看详细信息');
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) { } catch (error: any) {
const errorMsg = error?.response?.data?.detail || '测试请求失败'; const errorMsg = error?.response?.data?.detail || '测试请求失败';
message.error(errorMsg); message.error(errorMsg);
@@ -416,6 +423,7 @@ export default function SettingsPage() {
await settingsApi.deletePreset(presetId); await settingsApi.deletePreset(presetId);
message.success('预设已删除'); message.success('预设已删除');
loadPresets(); loadPresets();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) { } catch (error: any) {
message.error(error.response?.data?.detail || '删除失败'); message.error(error.response?.data?.detail || '删除失败');
console.error(error); console.error(error);
@@ -503,7 +511,7 @@ export default function SettingsPage() {
okText: '前往 MCP 页面', okText: '前往 MCP 页面',
cancelText: '稍后处理', cancelText: '稍后处理',
onOk: () => { onOk: () => {
navigate('/mcp-plugins'); eventBus.emit(EventNames.SWITCH_TO_MCP_VIEW);
}, },
}); });
} }
@@ -784,11 +792,12 @@ export default function SettingsPage() {
<> <>
{contextHolder} {contextHolder}
<div style={{ <div style={{
minHeight: '100vh', 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' : '40px 24px', padding: isMobile ? '20px 16px' : '24px 24px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
marginBottom: '55px',
}}> }}>
<div style={{ <div style={{
maxWidth: 1400, maxWidth: 1400,
@@ -820,7 +829,6 @@ export default function SettingsPage() {
<Col xs={24} sm={12}> <Col xs={24} sm={12}>
<Space direction="vertical" size={4}> <Space direction="vertical" size={4}>
<Title level={isMobile ? 3 : 2} style={{ margin: 0, color: '#fff', textShadow: '0 2px 4px rgba(0,0,0,0.1)' }}> <Title level={isMobile ? 3 : 2} style={{ margin: 0, color: '#fff', textShadow: '0 2px 4px rgba(0,0,0,0.1)' }}>
<SettingOutlined style={{ color: 'rgba(255,255,255,0.9)', marginRight: 8 }} />
AI API AI API
</Title> </Title>
<Text style={{ fontSize: isMobile ? 12 : 14, color: 'rgba(255,255,255,0.85)', marginLeft: isMobile ? 40 : 48 }}> <Text style={{ fontSize: isMobile ? 12 : 14, color: 'rgba(255,255,255,0.85)', marginLeft: isMobile ? 40 : 48 }}>
@@ -829,31 +837,7 @@ export default function SettingsPage() {
</Space> </Space>
</Col> </Col>
<Col xs={24} sm={12}> <Col xs={24} sm={12}>
<Space size={12} style={{ display: 'flex', justifyContent: isMobile ? 'flex-start' : 'flex-end', width: '100%' }}> {/* 按钮区域预留 */}
<Button
icon={<ArrowLeftOutlined />}
onClick={() => navigate('/')}
style={{
borderRadius: 12,
background: 'rgba(255, 255, 255, 0.15)',
border: '1px solid rgba(255, 255, 255, 0.3)',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
color: '#fff',
backdropFilter: 'blur(10px)',
transition: 'all 0.3s ease'
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.25)';
e.currentTarget.style.transform = 'translateY(-1px)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.15)';
e.currentTarget.style.transform = 'none';
}}
>
</Button>
</Space>
</Col> </Col>
</Row> </Row>
</Card> </Card>
@@ -1399,99 +1383,135 @@ export default function SettingsPage() {
open={isPresetModalVisible} open={isPresetModalVisible}
onOk={handlePresetSave} onOk={handlePresetSave}
onCancel={handlePresetCancel} onCancel={handlePresetCancel}
width={isMobile ? '90%' : 600} width={isMobile ? '95%' : 640}
centered centered
okText="保存" okText="保存"
cancelText="取消" cancelText="取消"
styles={{
body: {
padding: isMobile ? '16px' : '20px 24px'
}
}}
> >
<Form <Form
form={presetForm} form={presetForm}
layout="vertical" layout="vertical"
size={isMobile ? 'middle' : 'large'}
> >
<Form.Item {/* 基本信息 */}
name="name" <Row gutter={16}>
label="预设名称" <Col xs={24} sm={16}>
rules={[ <Form.Item
{ required: true, message: '请输入预设名称' }, name="name"
{ max: 50, message: '名称不能超过50个字符' }, label="预设名称"
]} rules={[
> { required: true, message: '请输入预设名称' },
<Input placeholder="例如:工作账号-GPT4" /> { max: 50, message: '名称不能超过50个字符' },
</Form.Item> ]}
style={{ marginBottom: 16 }}
>
<Input placeholder="例如:工作账号-GPT4" />
</Form.Item>
</Col>
<Col xs={24} sm={8}>
<Form.Item
name="api_provider"
label="API 提供商"
rules={[{ required: true, message: '请选择' }]}
style={{ marginBottom: 16 }}
>
<Select placeholder="选择提供商">
<Select.Option value="openai">OpenAI</Select.Option>
<Select.Option value="gemini">Google Gemini</Select.Option>
</Select>
</Form.Item>
</Col>
</Row>
<Form.Item <Form.Item
name="description" name="description"
label="预设描述" label="预设描述"
rules={[{ max: 200, message: '描述不能超过200个字符' }]} rules={[{ max: 200, message: '描述不能超过200个字符' }]}
style={{ marginBottom: 16 }}
> >
<TextArea rows={2} placeholder="例如:用于日常写作任务(可选)" /> <Input placeholder="例如:用于日常写作任务(可选)" />
</Form.Item> </Form.Item>
<Form.Item {/* API 配置 */}
name="api_provider" <Row gutter={16}>
label="API 提供商" <Col xs={24} sm={12}>
rules={[{ required: true, message: '请选择API提供商' }]} <Form.Item
> name="api_key"
<Select> label="API Key"
<Select.Option value="openai">OpenAI</Select.Option> rules={[{ required: true, message: '请输入API Key' }]}
{/* <Select.Option value="anthropic">Anthropic (Claude)</Select.Option> */} style={{ marginBottom: 16 }}
<Select.Option value="gemini">Google Gemini</Select.Option> >
</Select> <Input.Password placeholder="sk-..." />
</Form.Item> </Form.Item>
</Col>
<Col xs={24} sm={12}>
<Form.Item
name="api_base_url"
label="API Base URL"
style={{ marginBottom: 16 }}
>
<Input placeholder="https://api.openai.com/v1" />
</Form.Item>
</Col>
</Row>
<Form.Item {/* 模型配置 */}
name="api_key" <Row gutter={16}>
label="API Key" <Col xs={24} sm={12}>
rules={[{ required: true, message: '请输入API Key' }]} <Form.Item
> name="llm_model"
<Input.Password placeholder="sk-..." /> label="模型名称"
</Form.Item> rules={[{ required: true, message: '请输入模型名称' }]}
style={{ marginBottom: 16 }}
<Form.Item name="api_base_url" label="API Base URL"> >
<Input placeholder="https://api.openai.com/v1(可选)" /> <Input placeholder="例如:gpt-4" />
</Form.Item> </Form.Item>
</Col>
<Form.Item <Col xs={12} sm={6}>
name="llm_model" <Form.Item
label="模型名称" name="temperature"
rules={[{ required: true, message: '请输入模型名称' }]} label="温度"
> rules={[{ required: true, message: '必填' }]}
<Input placeholder="例如:gpt-4, claude-3-opus-20240229" /> style={{ marginBottom: 16 }}
</Form.Item> >
<InputNumber
<Form.Item min={0}
name="temperature" max={2}
label="温度参数" step={0.1}
rules={[{ required: true, message: '请输入温度参数' }]} style={{ width: '100%' }}
> placeholder="0.7"
<InputNumber />
min={0} </Form.Item>
max={2} </Col>
step={0.1} <Col xs={12} sm={6}>
style={{ width: '100%' }} <Form.Item
placeholder="0.7" name="max_tokens"
/> label="最大Tokens"
</Form.Item> rules={[{ required: true, message: '必填' }]}
style={{ marginBottom: 16 }}
<Form.Item >
name="max_tokens" <InputNumber
label="最大 Tokens" min={1}
rules={[{ required: true, message: '请输入最大tokens' }]} max={100000}
> style={{ width: '100%' }}
<InputNumber placeholder="2000"
min={1} />
max={100000} </Form.Item>
style={{ width: '100%' }} </Col>
placeholder="2000" </Row>
/>
</Form.Item>
<Form.Item <Form.Item
name="system_prompt" name="system_prompt"
label="系统提示词" label="系统提示词"
style={{ marginBottom: 0 }}
> >
<TextArea <TextArea
rows={3} rows={isMobile ? 2 : 3}
placeholder="例如:你是一个专业的小说创作助手...(可选)" placeholder="例如:你是一个专业的小说创作助手...(可选)"
maxLength={10000} maxLength={10000}
showCount showCount