fix:初始化

This commit is contained in:
Your Name
2026-04-16 21:32:21 +08:00
parent 0b4772cac2
commit 235c98517d
51 changed files with 29453 additions and 0 deletions

468
src/pages/ModelSettings.tsx Normal file
View File

@@ -0,0 +1,468 @@
import React, { useState, useEffect } from 'react';
import { Card, Form, Input, InputNumber, Button, Space, Divider, Select, Spin, Tag, App } from 'antd';
import { ApiOutlined, SaveOutlined, SyncOutlined, CheckCircleOutlined } from '@ant-design/icons';
import { OllamaService } from '../utils/ollama';
import { useOllama } from '../contexts/OllamaContext';
const { Option } = Select;
interface ModelInfo {
name: string;
size?: number;
modified?: string;
}
const ModelSettings: React.FC = () => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [testing, setTesting] = useState(false);
const [detectingModels, setDetectingModels] = useState(false);
const [availableModels, setAvailableModels] = useState<ModelInfo[]>([]);
const [connectionStatus, setConnectionStatus] = useState<'unknown' | 'success' | 'error'>('unknown');
const [currentModel, setCurrentModel] = useState<string>('');
const { status, refreshModels } = useOllama();
const { message: messageApi } = App.useApp();
useEffect(() => {
const loadConfig = async () => {
// 从 localStorage 加载配置
const configData = localStorage.getItem('ai_system_config');
const config = configData ? JSON.parse(configData) : {
ollamaUrl: 'http://localhost:11434',
model: '',
temperature: 0.7,
topP: 0.9,
maxTokens: 2000
};
form.setFieldsValue(config);
setCurrentModel(config.model || '');
// 如果已连接且有模型,自动显示当前状态
if (status.isConnected && status.availableModels.length > 0) {
setAvailableModels(status.availableModels);
setConnectionStatus('success');
}
};
loadConfig();
}, [form, status]);
const handleDetectModels = async () => {
setDetectingModels(true);
setConnectionStatus('unknown');
try {
const values = await form.validateFields(['ollamaUrl']);
const ollamaService = new OllamaService(values);
// 测试连接
const isConnected = await ollamaService.testConnection();
if (!isConnected) {
setConnectionStatus('error');
messageApi.open({
type: 'error',
content: '无法连接到 Ollama 服务,请检查服务地址和状态',
});
setAvailableModels([]);
return;
}
// 获取模型列表
const models = await ollamaService.getAvailableModelsWithInfo();
setAvailableModels(models);
setConnectionStatus('success');
if (models.length === 0) {
messageApi.open({
type: 'warning',
content: '未检测到已安装的模型,请先使用 ollama pull 命令安装模型',
});
} else {
messageApi.open({
type: 'success',
content: `成功检测到 ${models.length} 个已安装模型`,
});
// 如果当前模型不在列表中,清空选择
if (currentModel && !models.find(m => m.name === currentModel)) {
form.setFieldValue('model', undefined);
setCurrentModel('');
messageApi.open({
type: 'warning',
content: '当前选择的模型未在本地安装,请重新选择',
});
}
}
// 刷新全局状态
await refreshModels();
} catch (error) {
setConnectionStatus('error');
messageApi.open({
type: 'error',
content: '模型检测失败,请检查 Ollama 服务状态',
});
setAvailableModels([]);
} finally {
setDetectingModels(false);
}
};
const handleTestConnection = async () => {
setTesting(true);
setConnectionStatus('unknown');
try {
const values = await form.validateFields();
const ollamaService = new OllamaService(values);
const isConnected = await ollamaService.testConnection();
if (isConnected) {
setConnectionStatus('success');
messageApi.open({
type: 'success',
content: 'Ollama 服务连接成功!',
});
await refreshModels();
} else {
setConnectionStatus('error');
messageApi.open({
type: 'error',
content: 'Ollama 服务连接失败,请检查服务地址',
});
}
} catch (error) {
setConnectionStatus('error');
messageApi.open({
type: 'error',
content: '连接测试失败,请检查配置',
});
} finally {
setTesting(false);
}
};
const handleModelChange = (value: string) => {
setCurrentModel(value);
};
const handleSave = async () => {
setLoading(true);
try {
const values = await form.validateFields();
// 验证选择的模型是否可用
if (currentModel && availableModels.length > 0) {
const modelExists = availableModels.find(m => m.name === currentModel);
if (!modelExists) {
messageApi.open({
type: 'error',
content: '请选择本地已安装的模型,或点击"检测模型"刷新列表',
});
setLoading(false);
return;
}
}
// 确保包含当前选择的模型
const configToSave = {
...values,
model: currentModel || values.model
};
// 保存到 localStorage
localStorage.setItem('ai_system_config', JSON.stringify(configToSave));
// 显示保存成功提示
messageApi.open({
type: 'success',
content: '配置保存成功!',
});
// 更新当前模型显示
setCurrentModel(configToSave.model);
// 刷新全局状态
await refreshModels();
} catch (error) {
messageApi.open({
type: 'error',
content: '配置保存失败,请检查输入',
});
} finally {
setLoading(false);
}
};
const formatModelSize = (bytes?: number) => {
if (!bytes) return '未知';
const gb = bytes / (1024 * 1024 * 1024);
return `${gb.toFixed(2)} GB`;
};
return (
<div>
<div style={{ marginBottom: '24px' }}>
<h2 style={{ margin: 0, fontSize: '20px', fontWeight: 600, color: '#262626' }}></h2>
<p style={{ margin: '8px 0 0 0', color: '#8c8c8c', fontSize: '14px' }}> Ollama AI </p>
</div>
<div style={{
background: 'white',
borderRadius: '8px',
border: '1px solid #f0f0f0',
marginBottom: '16px',
overflow: 'hidden'
}}>
<div style={{
padding: '16px 24px',
background: '#fafafa',
borderBottom: '1px solid #f0f0f0',
display: 'flex',
alignItems: 'center',
gap: '12px'
}}>
<div style={{
width: '8px',
height: '8px',
borderRadius: '50%',
background: connectionStatus === 'success' ? '#52c41a' : connectionStatus === 'error' ? '#ff4d4f' : '#faad14'
}} />
<div>
<div style={{ fontWeight: 500, color: '#262626' }}>
{connectionStatus === 'success' ? '服务正常' : connectionStatus === 'error' ? '服务异常' : '未检测'}
</div>
<div style={{ fontSize: '12px', color: '#8c8c8c' }}>
{availableModels.length > 0
? `已安装 ${availableModels.length} 个模型`
: connectionStatus === 'success'
? '未安装模型'
: '请先连接服务'}
</div>
</div>
</div>
</div>
<Card bordered={false} style={{ boxShadow: 'none' }}>
<Form
form={form}
layout="vertical"
initialValues={{
ollamaUrl: 'http://localhost:11434',
model: '',
temperature: 0.7,
topP: 0.9,
maxTokens: 2000
}}
>
<div style={{ marginBottom: '24px' }}>
<div style={{
fontSize: '14px',
fontWeight: 500,
color: '#262626',
marginBottom: '16px'
}}>
</div>
<Form.Item
label={<span style={{ color: '#595959' }}>Ollama </span>}
name="ollamaUrl"
rules={[{ required: true, message: '请输入Ollama服务地址' }]}
>
<Input
placeholder="http://localhost:11434"
style={{ borderRadius: '6px' }}
/>
</Form.Item>
<Space style={{ marginBottom: '16px' }}>
<Button
icon={<ApiOutlined />}
onClick={handleTestConnection}
loading={testing}
style={{ borderRadius: '6px' }}
>
</Button>
<Button
type="primary"
icon={<SyncOutlined />}
onClick={handleDetectModels}
loading={detectingModels}
style={{ borderRadius: '6px' }}
>
</Button>
</Space>
</div>
<div style={{ marginBottom: '24px' }}>
<div style={{
fontSize: '14px',
fontWeight: 500,
color: '#262626',
marginBottom: '16px'
}}>
</div>
<Form.Item
label={
<Space>
<span style={{ color: '#595959' }}>AI </span>
{availableModels.length > 0 && (
<Tag color="success">{availableModels.length} </Tag>
)}
</Space>
}
name="model"
rules={[{
required: true,
message: '请选择AI模型'
}]}
tooltip="只能选择本地已安装的模型"
>
<Select
placeholder={availableModels.length === 0 ? "请先点击检测模型" : "选择AI模型"}
showSearch
allowClear
onChange={handleModelChange}
notFoundContent={detectingModels ? <Spin size="small" /> : "未检测到可用模型"}
disabled={availableModels.length === 0}
style={{ borderRadius: '6px' }}
>
{availableModels.map(model => (
<Option key={model.name} value={model.name}>
<Space>
<span>{model.name}</span>
{model.size && (
<span style={{ color: '#8c8c8c', fontSize: '12px' }}>
({formatModelSize(model.size)})
</span>
)}
</Space>
</Option>
))}
</Select>
</Form.Item>
{currentModel && (
<div style={{
padding: '12px',
background: '#f6ffed',
border: '1px solid #b7eb8f',
borderRadius: '6px',
marginBottom: '16px'
}}>
<Space>
<CheckCircleOutlined style={{ color: '#52c41a' }} />
<span style={{ color: '#52c41a', fontSize: '14px' }}>
使: {currentModel}
</span>
</Space>
</div>
)}
</div>
<div style={{ marginBottom: '24px' }}>
<div style={{
fontSize: '14px',
fontWeight: 500,
color: '#262626',
marginBottom: '16px'
}}>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px' }}>
<Form.Item
label={<span style={{ color: '#595959' }}></span>}
name="temperature"
rules={[{ required: true, message: '请输入温度值' }]}
tooltip="控制生成文本的随机性"
>
<InputNumber
min={0}
max={2}
step={0.1}
precision={1}
style={{ width: '100%', borderRadius: '6px' }}
/>
</Form.Item>
<Form.Item
label={<span style={{ color: '#595959' }}>Top P</span>}
name="topP"
rules={[{ required: true, message: '请输入Top P值' }]}
tooltip="控制生成文本的多样性"
>
<InputNumber
min={0}
max={1}
step={0.1}
precision={1}
style={{ width: '100%', borderRadius: '6px' }}
/>
</Form.Item>
<Form.Item
label={<span style={{ color: '#595959' }}> Tokens</span>}
name="maxTokens"
rules={[{ required: true, message: '请输入最大生成长度' }]}
>
<InputNumber
min={100}
max={8000}
step={100}
style={{ width: '100%', borderRadius: '6px' }}
/>
</Form.Item>
</div>
</div>
<Form.Item style={{ marginBottom: 0 }}>
<Button
type="primary"
icon={<SaveOutlined />}
onClick={handleSave}
loading={loading}
size="large"
style={{ borderRadius: '6px', minWidth: '120px' }}
>
</Button>
</Form.Item>
</Form>
<Divider style={{ margin: '32px 0' }} />
<div>
<div style={{
fontSize: '14px',
fontWeight: 500,
color: '#262626',
marginBottom: '16px'
}}>
使
</div>
<ul style={{
margin: 0,
paddingLeft: '20px',
color: '#595959',
fontSize: '14px',
lineHeight: '1.8'
}}>
<li> Ollama 11434</li>
<li>"测试连接" Ollama </li>
<li>"检测模型"</li>
<li>使</li>
<li>使ollama pull </li>
<li>qwen3:8b起步</li>
<li> 0.7</li>
</ul>
</div>
</Card>
</div>
);
};
export default ModelSettings;