feat(models): add models page and refactor profile form
This commit is contained in:
398
src/pages/models/models.tsx
Normal file
398
src/pages/models/models.tsx
Normal file
@@ -0,0 +1,398 @@
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Card,
|
||||
CardContent,
|
||||
Button,
|
||||
Chip,
|
||||
IconButton,
|
||||
Collapse,
|
||||
Grid,
|
||||
Alert,
|
||||
CircularProgress,
|
||||
Tooltip,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Divider,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
ExpandLess as ExpandLessIcon,
|
||||
Edit as EditIcon,
|
||||
Delete as DeleteIcon,
|
||||
} from '@mui/icons-material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLlmModelSetting } from '@/hooks/setting-hooks';
|
||||
import { useModelDialogs } from '../setting/hooks/useModelDialogs';
|
||||
import type { IFactory, IMyLlmModel, ILlmItem } from '@/interfaces/database/llm';
|
||||
import LLMFactoryCard, { MODEL_TYPE_COLORS } from '../setting/components/LLMFactoryCard';
|
||||
import { ModelDialogs } from '../setting/components/ModelDialogs';
|
||||
import { useDialog } from '@/hooks/useDialog';
|
||||
import logger from '@/utils/logger';
|
||||
import { LLM_FACTORY_LIST, LocalLlmFactories, type LLMFactory } from '@/constants/llm';
|
||||
import { LlmSvgIcon } from '@/components/AppSvgIcon';
|
||||
import { getFactoryIconName } from '@/utils/common';
|
||||
|
||||
interface MyLlmGridItemProps {
|
||||
model: ILlmItem,
|
||||
onDelete: (model: ILlmItem) => void,
|
||||
onEditLlm?: (model: ILlmItem) => void,
|
||||
}
|
||||
|
||||
function MyLlmGridItem({ model, onDelete, onEditLlm }: MyLlmGridItemProps) {
|
||||
return (
|
||||
<Grid size={{ xs: 6, sm: 4, md: 3 }} key={model.name}>
|
||||
<Card variant="outlined" sx={{ p: 2 }}>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="flex-start" mb={1}>
|
||||
<Typography variant="body2" fontWeight="bold">
|
||||
{model.name}
|
||||
</Typography>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
gap: 1,
|
||||
}}>
|
||||
{
|
||||
onEditLlm && (
|
||||
<IconButton
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={() => onEditLlm(model)}
|
||||
>
|
||||
<EditIcon fontSize="small" />
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
<IconButton
|
||||
size="small"
|
||||
color="error"
|
||||
onClick={() => onDelete(model)}
|
||||
>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
<Chip
|
||||
label={model.type}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: MODEL_TYPE_COLORS[model.type.toUpperCase()] || '#757575',
|
||||
color: 'white',
|
||||
mb: 1,
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
// 主页面组件
|
||||
function ModelsPage() {
|
||||
const { t } = useTranslation();
|
||||
const { llmFactory, myLlm, refreshLlmModel } = useLlmModelSetting();
|
||||
const modelDialogs = useModelDialogs(refreshLlmModel);
|
||||
|
||||
// 折叠状态管理 - 使用 Map 来管理每个工厂的折叠状态,默认所有工厂都是折叠的
|
||||
const [collapsedFactories, setCollapsedFactories] = useState<Record<string, boolean>>({});
|
||||
|
||||
// 切换工厂折叠状态
|
||||
const toggleFactoryCollapse = useCallback((factoryName: string) => {
|
||||
setCollapsedFactories(prev => ({
|
||||
...prev,
|
||||
[factoryName]: !prev[factoryName]
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const showLlmFactory = useMemo(() => {
|
||||
const modelFactoryNames = Object.keys(myLlm || {});
|
||||
const filterFactory = llmFactory?.filter(factory => !modelFactoryNames.includes(factory.name));
|
||||
return filterFactory || [];
|
||||
}, [llmFactory, myLlm]);
|
||||
|
||||
const showAddModel = (factoryName: string) => {
|
||||
const configurationFactories: LLMFactory[] = [
|
||||
LLM_FACTORY_LIST.AzureOpenAI,
|
||||
LLM_FACTORY_LIST.Bedrock,
|
||||
LLM_FACTORY_LIST.BaiduYiYan,
|
||||
LLM_FACTORY_LIST.FishAudio,
|
||||
LLM_FACTORY_LIST.GoogleCloud,
|
||||
LLM_FACTORY_LIST.TencentCloud,
|
||||
LLM_FACTORY_LIST.TencentHunYuan,
|
||||
LLM_FACTORY_LIST.XunFeiSpark,
|
||||
LLM_FACTORY_LIST.VolcEngine,
|
||||
]
|
||||
|
||||
const fN = factoryName as LLMFactory;
|
||||
if (!fN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return LocalLlmFactories.includes(fN) ||
|
||||
configurationFactories.includes(fN);
|
||||
}
|
||||
|
||||
// 处理配置模型工厂
|
||||
const handleConfigureFactory = useCallback((factory: IFactory) => {
|
||||
if (factory == null) {
|
||||
return;
|
||||
}
|
||||
// llm 的配置很多,有很多种类型 首先是local llm 然后是配置项不一样的
|
||||
// 然后有很多自定义的配置项,需要单独用 dialog 来配置
|
||||
const factoryName = factory.name as LLMFactory;
|
||||
const configurationFactories: LLMFactory[] = [
|
||||
LLM_FACTORY_LIST.AzureOpenAI,
|
||||
LLM_FACTORY_LIST.Bedrock,
|
||||
LLM_FACTORY_LIST.BaiduYiYan,
|
||||
LLM_FACTORY_LIST.FishAudio,
|
||||
LLM_FACTORY_LIST.GoogleCloud,
|
||||
LLM_FACTORY_LIST.TencentCloud,
|
||||
LLM_FACTORY_LIST.TencentHunYuan,
|
||||
LLM_FACTORY_LIST.XunFeiSpark,
|
||||
LLM_FACTORY_LIST.VolcEngine,
|
||||
]
|
||||
if (LocalLlmFactories.includes(factoryName)) {
|
||||
// local llm
|
||||
modelDialogs.ollamaDialog.openDialog({
|
||||
llm_factory: factory.name,
|
||||
});
|
||||
} else if (configurationFactories.includes(factoryName)) {
|
||||
// custom configuration llm
|
||||
modelDialogs.configurationDialog.openConfigurationDialog(factory.name);
|
||||
} else {
|
||||
// llm set api
|
||||
modelDialogs.apiKeyDialog.openApiKeyDialog(factoryName);
|
||||
}
|
||||
logger.debug('handleConfigureFactory', factory);
|
||||
}, [modelDialogs]);
|
||||
|
||||
const handleEditLlmFactory = useCallback((factoryName: string, llmmodel?: ILlmItem) => {
|
||||
if (factoryName == null) {
|
||||
return;
|
||||
}
|
||||
const factoryN = factoryName as LLMFactory;
|
||||
const configurationFactories: LLMFactory[] = [
|
||||
LLM_FACTORY_LIST.AzureOpenAI,
|
||||
LLM_FACTORY_LIST.Bedrock,
|
||||
LLM_FACTORY_LIST.BaiduYiYan,
|
||||
LLM_FACTORY_LIST.FishAudio,
|
||||
LLM_FACTORY_LIST.GoogleCloud,
|
||||
LLM_FACTORY_LIST.TencentCloud,
|
||||
LLM_FACTORY_LIST.TencentHunYuan,
|
||||
LLM_FACTORY_LIST.XunFeiSpark,
|
||||
LLM_FACTORY_LIST.VolcEngine,
|
||||
]
|
||||
if (LocalLlmFactories.includes(factoryN)) {
|
||||
// local llm
|
||||
modelDialogs.ollamaDialog.openDialog({
|
||||
llm_factory: factoryN,
|
||||
...llmmodel,
|
||||
llm_name: llmmodel?.name || '',
|
||||
model_type: llmmodel?.type || 'chat',
|
||||
}, true);
|
||||
} else if (configurationFactories.includes(factoryN)) {
|
||||
// custom configuration llm
|
||||
modelDialogs.configurationDialog.openConfigurationDialog(factoryN, {
|
||||
...llmmodel,
|
||||
llm_name: llmmodel?.name || '',
|
||||
model_type: llmmodel?.type || 'chat',
|
||||
});
|
||||
} else {
|
||||
// llm set api
|
||||
modelDialogs.apiKeyDialog.openApiKeyDialog(factoryN, {}, true);
|
||||
}
|
||||
logger.debug('handleEditLlmFactory', factoryN);
|
||||
}, []);
|
||||
|
||||
const dialog = useDialog();
|
||||
|
||||
// 处理删除单个模型
|
||||
const handleDeleteModel = useCallback(async (factoryName: string, modelName: string) => {
|
||||
dialog.confirm({
|
||||
title: t('setting.confirmDelete'),
|
||||
content: t('setting.confirmDeleteModel', { modelName }),
|
||||
showCancel: true,
|
||||
onConfirm: async () => {
|
||||
await modelDialogs.deleteOps.deleteLlm(factoryName, modelName);
|
||||
},
|
||||
});
|
||||
}, [dialog, modelDialogs.deleteOps, t]);
|
||||
|
||||
// 处理删除模型工厂
|
||||
const handleDeleteFactory = useCallback(async (factoryName: string) => {
|
||||
dialog.confirm({
|
||||
title: t('setting.confirmDelete'),
|
||||
content: t('setting.confirmDeleteFactory', { factoryName }),
|
||||
showCancel: true,
|
||||
onConfirm: async () => {
|
||||
await modelDialogs.deleteOps.deleteFactory(factoryName);
|
||||
},
|
||||
});
|
||||
}, [dialog, modelDialogs.deleteOps, t]);
|
||||
|
||||
if (!llmFactory || !myLlm) {
|
||||
return (
|
||||
<Box display="flex" justifyContent="center" alignItems="center" minHeight="400px">
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Box mb={4} sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}>
|
||||
<Box>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
{t('setting.modelSettings')}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" gutterBottom>
|
||||
{t('setting.modelSettingsDescription')}
|
||||
</Typography>
|
||||
</Box>
|
||||
{/* 设置默认模型 */}
|
||||
<Button variant="contained" color="primary" onClick={() => modelDialogs.systemDialog.openDialog()}>
|
||||
{t('setting.setDefaultModel')}
|
||||
</Button>
|
||||
</Box>
|
||||
{/* My LLM 部分 */}
|
||||
<Box mb={4} mt={2}>
|
||||
{!myLlm || Object.keys(myLlm).length === 0 ? (
|
||||
<Alert severity="info">
|
||||
{t('setting.noModelsConfigured')}
|
||||
</Alert>
|
||||
) : (
|
||||
<Accordion defaultExpanded>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
{t('setting.myLlmModels')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid container spacing={2}>
|
||||
{Object.entries(myLlm).map(([factoryName, group]) => (
|
||||
<Grid size={12} key={factoryName} >
|
||||
<Card variant="outlined">
|
||||
<CardContent sx={{ padding: 2 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.04)'
|
||||
}
|
||||
}}
|
||||
onClick={() => toggleFactoryCollapse(factoryName)}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{/* 折叠/展开图标 */}
|
||||
<IconButton size="small">
|
||||
{collapsedFactories[factoryName] ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
||||
</IconButton>
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 2, alignItems: 'center' }}>
|
||||
{/* svg icon */}
|
||||
<LlmSvgIcon name={getFactoryIconName(factoryName as LLMFactory)} sx={{ fontSize: 36 }} />
|
||||
{/* 模型工厂名称 */}
|
||||
<Typography variant="h6" gutterBottom>
|
||||
{factoryName}
|
||||
</Typography></Box>
|
||||
{/* 模型标签 */}
|
||||
<Box display="flex" gap={1} mt={2}>
|
||||
{group.tags.split(',').map((tag) => (
|
||||
<Chip
|
||||
key={tag}
|
||||
label={tag.trim()}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: MODEL_TYPE_COLORS[tag.trim()] || '#757575',
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* edit and delete factory button */}
|
||||
<Box sx={{ display: 'flex', gap: 1 }} onClick={(e) => e.stopPropagation()}>
|
||||
<Button
|
||||
variant='contained' color='primary' startIcon={<EditIcon />}
|
||||
onClick={() => handleEditLlmFactory(factoryName)}
|
||||
>
|
||||
{showAddModel(factoryName) ? t('setting.addModel') : t('setting.edit')}
|
||||
</Button>
|
||||
<Button
|
||||
variant='outlined' color='primary' startIcon={<DeleteIcon />}
|
||||
onClick={() => handleDeleteFactory(factoryName)}
|
||||
>
|
||||
{t('setting.delete')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* 模型列表 - 使用 Collapse 组件包装 */}
|
||||
<Collapse in={collapsedFactories[factoryName]} timeout="auto" unmountOnExit>
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Grid container spacing={2}>
|
||||
{group.llm.map((model) => (
|
||||
<MyLlmGridItem
|
||||
key={model.name}
|
||||
model={model}
|
||||
onEditLlm={showAddModel(factoryName) ? (llm) => {
|
||||
handleEditLlmFactory(factoryName, llm);
|
||||
} : undefined}
|
||||
onDelete={() => handleDeleteModel(factoryName, model.name)}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* LLM Factory 部分 */}
|
||||
<Box>
|
||||
<Accordion defaultExpanded>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
{t('setting.llmModelFactories')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid container spacing={2}>
|
||||
{
|
||||
showLlmFactory.map((factory) => (
|
||||
<Grid size={{ xs: 6, sm: 4, md: 3 }} key={factory.name}>
|
||||
<LLMFactoryCard
|
||||
key={factory.name}
|
||||
factory={factory}
|
||||
onConfigure={handleConfigureFactory}
|
||||
/>
|
||||
</Grid>
|
||||
))
|
||||
}
|
||||
</Grid>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Box>
|
||||
|
||||
{/* 模型配置对话框 */}
|
||||
{/* @ts-ignore */}
|
||||
<ModelDialogs {...modelDialogs} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelsPage;
|
||||
Reference in New Issue
Block a user