mirror of
https://github.com/chaitin/MonkeyCode.git
synced 2026-02-02 14:53:55 +08:00
feat: 模型未配置,添加禁用功能, 修复对话记录滚动问题
This commit is contained in:
@@ -6,6 +6,7 @@ import { Modal } from '@c-x/ui';
|
||||
import { useMemo, useState } from 'react';
|
||||
import Qrcode from '@/assets/images/qrcode.png';
|
||||
import Version from './version';
|
||||
import { useCommonContext } from '@/hooks/context';
|
||||
|
||||
const ADMIN_MENUS = [
|
||||
{
|
||||
@@ -14,6 +15,7 @@ const ADMIN_MENUS = [
|
||||
pathname: 'dashboard',
|
||||
icon: 'icon-yibiaopan',
|
||||
show: true,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: '对话记录',
|
||||
@@ -21,6 +23,7 @@ const ADMIN_MENUS = [
|
||||
pathname: 'chat',
|
||||
icon: 'icon-duihuajilu1',
|
||||
show: true,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: '补全记录',
|
||||
@@ -28,6 +31,7 @@ const ADMIN_MENUS = [
|
||||
pathname: 'completion',
|
||||
icon: 'icon-buquanjilu',
|
||||
show: true,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: '代码安全',
|
||||
@@ -35,6 +39,7 @@ const ADMIN_MENUS = [
|
||||
pathname: 'code-security',
|
||||
icon: 'icon-daimaanquan1',
|
||||
show: true,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: '模型管理',
|
||||
@@ -42,6 +47,7 @@ const ADMIN_MENUS = [
|
||||
pathname: 'model',
|
||||
icon: 'icon-moxingguanli',
|
||||
show: true,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: '成员管理',
|
||||
@@ -49,6 +55,7 @@ const ADMIN_MENUS = [
|
||||
pathname: 'user-management',
|
||||
icon: 'icon-yonghuguanli1',
|
||||
show: true,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: '管理员',
|
||||
@@ -56,6 +63,7 @@ const ADMIN_MENUS = [
|
||||
pathname: 'admin',
|
||||
icon: 'icon-guanliyuan1',
|
||||
show: true,
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -66,6 +74,7 @@ const USER_MENUS = [
|
||||
pathname: '/user/dashboard',
|
||||
icon: 'icon-yibiaopan',
|
||||
show: true,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: '对话记录',
|
||||
@@ -73,6 +82,7 @@ const USER_MENUS = [
|
||||
pathname: '/user/chat',
|
||||
icon: 'icon-duihuajilu1',
|
||||
show: true,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: '补全记录',
|
||||
@@ -80,6 +90,7 @@ const USER_MENUS = [
|
||||
pathname: '/user/completion',
|
||||
icon: 'icon-buquanjilu',
|
||||
show: true,
|
||||
disabled: false,
|
||||
},
|
||||
// {
|
||||
// label: '设置',
|
||||
@@ -111,12 +122,15 @@ const Sidebar = () => {
|
||||
const { pathname } = useLocation();
|
||||
const theme = useTheme();
|
||||
const [showQrcode, setShowQrcode] = useState(false);
|
||||
const { isConfigModel } = useCommonContext();
|
||||
const menus = useMemo(() => {
|
||||
if (pathname.startsWith('/user/')) {
|
||||
return USER_MENUS;
|
||||
}
|
||||
return ADMIN_MENUS;
|
||||
}, [pathname]);
|
||||
return isConfigModel
|
||||
? ADMIN_MENUS.map((item) => ({ ...item, disabled: false }))
|
||||
: ADMIN_MENUS.map((item) => ({ ...item, disabled: true }));
|
||||
}, [pathname, isConfigModel]);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@@ -178,9 +192,15 @@ const Sidebar = () => {
|
||||
zIndex: isActive ? 2 : 1,
|
||||
color: isActive ? '#FFFFFF' : 'text.primary',
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (it.disabled) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant={isActive ? 'contained' : 'text'}
|
||||
disabled={it.pathname === 'model' ? false : it.disabled}
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: 50,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createContext } from 'react';
|
||||
import { DomainUser, DomainAdminUser } from '@/api/types';
|
||||
import { DomainUser, DomainAdminUser, DomainModel } from '@/api/types';
|
||||
|
||||
export const AuthContext = createContext<
|
||||
[
|
||||
@@ -20,6 +20,13 @@ export const AuthContext = createContext<
|
||||
]);
|
||||
|
||||
export const CommonContext = createContext<{
|
||||
contactModalOpen: boolean;
|
||||
setContactModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}>({ contactModalOpen: false, setContactModalOpen: () => {} });
|
||||
coderModel: DomainModel[];
|
||||
llmModel: DomainModel[];
|
||||
isConfigModel: boolean;
|
||||
refreshModel: () => void;
|
||||
}>({
|
||||
coderModel: [],
|
||||
llmModel: [],
|
||||
isConfigModel: false,
|
||||
refreshModel: () => {},
|
||||
});
|
||||
|
||||
@@ -9,15 +9,15 @@ import '@/assets/styles/markdown.css';
|
||||
import { ThemeProvider } from '@c-x/ui';
|
||||
import { getUserProfile } from '@/api/UserManage';
|
||||
import { getAdminProfile } from '@/api/Admin';
|
||||
import { getMyModelList } from '@/api/Model';
|
||||
import dayjs from 'dayjs';
|
||||
import duration from 'dayjs/plugin/duration';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { AuthContext } from './context';
|
||||
import { DomainUser, DomainAdminUser } from './api/types';
|
||||
import { AuthContext, CommonContext } from './context';
|
||||
import { DomainUser, DomainAdminUser, DomainModel } from './api/types';
|
||||
import { lightTheme } from './theme';
|
||||
import router from './router';
|
||||
import { getRedirectUrl } from './utils';
|
||||
import { Loading } from '@c-x/ui';
|
||||
|
||||
dayjs.locale('zh-cn');
|
||||
dayjs.extend(duration);
|
||||
@@ -26,12 +26,56 @@ dayjs.extend(relativeTime);
|
||||
const App = () => {
|
||||
const [user, setUser] = useState<DomainUser | DomainAdminUser | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [coderModel, setCoderModel] = useState<DomainModel[]>([]);
|
||||
const [llmModel, setLlmModel] = useState<DomainModel[]>([]);
|
||||
const [isConfigModel, setIsConfigModel] = useState(false);
|
||||
|
||||
const onGotoRedirect = (source: 'user' | 'admin') => {
|
||||
const redirectUrl = getRedirectUrl(source);
|
||||
window.location.href = redirectUrl.href;
|
||||
};
|
||||
|
||||
const getModelList = () => {
|
||||
return Promise.all([
|
||||
getMyModelList({
|
||||
model_type: 'coder',
|
||||
}),
|
||||
getMyModelList({
|
||||
model_type: 'llm',
|
||||
}),
|
||||
])
|
||||
.then((res) => {
|
||||
setCoderModel(res[0] || []);
|
||||
setLlmModel(res[1] || []);
|
||||
return res;
|
||||
})
|
||||
.then(handleModelConfig);
|
||||
};
|
||||
|
||||
const handleModelConfig = (res: [DomainModel[], DomainModel[]]) => {
|
||||
if ((res[0] || [])?.length == 0 || (res[1] || [])?.length == 0) {
|
||||
if (location.pathname !== '/model') {
|
||||
window.location.href = '/model';
|
||||
}
|
||||
setIsConfigModel(false);
|
||||
return false;
|
||||
} else {
|
||||
const isActive =
|
||||
res[0].every((item) => item.is_active) &&
|
||||
res[1].every((item) => item.is_active);
|
||||
if (isActive) {
|
||||
setIsConfigModel(true);
|
||||
return true;
|
||||
} else {
|
||||
if (location.pathname !== '/model') {
|
||||
window.location.href = '/model';
|
||||
}
|
||||
setIsConfigModel(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getUser = () => {
|
||||
setLoading(true);
|
||||
if (location.pathname.startsWith('/user')) {
|
||||
@@ -49,9 +93,13 @@ const App = () => {
|
||||
return getAdminProfile()
|
||||
.then((res) => {
|
||||
setUser(res);
|
||||
if (location.pathname.startsWith('/login')) {
|
||||
onGotoRedirect('admin');
|
||||
}
|
||||
getModelList().then((res) => {
|
||||
if (res) {
|
||||
if (location.pathname.startsWith('/login')) {
|
||||
onGotoRedirect('admin');
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
@@ -71,18 +119,27 @@ const App = () => {
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<AuthContext.Provider
|
||||
value={[
|
||||
user,
|
||||
{
|
||||
loading,
|
||||
setUser,
|
||||
refreshUser: getUser,
|
||||
},
|
||||
]}
|
||||
<CommonContext.Provider
|
||||
value={{
|
||||
coderModel,
|
||||
llmModel,
|
||||
isConfigModel,
|
||||
refreshModel: getModelList,
|
||||
}}
|
||||
>
|
||||
<RouterProvider router={router} />
|
||||
</AuthContext.Provider>
|
||||
<AuthContext.Provider
|
||||
value={[
|
||||
user,
|
||||
{
|
||||
loading,
|
||||
setUser,
|
||||
refreshUser: getUser,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<RouterProvider router={router} />
|
||||
</AuthContext.Provider>
|
||||
</CommonContext.Provider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,16 @@ import { getListChatRecord } from '@/api/Billing';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import Card from '@/components/card';
|
||||
import { Autocomplete, Box, FormControl, InputLabel, MenuItem, Select, Stack, TextField } from '@mui/material';
|
||||
import {
|
||||
Autocomplete,
|
||||
Box,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
Stack,
|
||||
TextField,
|
||||
} from '@mui/material';
|
||||
import StyledLabel from '@/components/label';
|
||||
|
||||
import ChatDetailModal from './chatDetailModal';
|
||||
@@ -180,7 +189,14 @@ const Chat = () => {
|
||||
label='工作模式'
|
||||
value={filterMode}
|
||||
onChange={(e) =>
|
||||
setfilterMode(e.target.value as 'code' | 'ask' | 'architect' | 'debug' | 'orchestrator')
|
||||
setfilterMode(
|
||||
e.target.value as
|
||||
| 'code'
|
||||
| 'ask'
|
||||
| 'architect'
|
||||
| 'debug'
|
||||
| 'orchestrator'
|
||||
)
|
||||
}
|
||||
>
|
||||
<MenuItem value=''>全部</MenuItem>
|
||||
@@ -193,7 +209,7 @@ const Chat = () => {
|
||||
</FormControl>
|
||||
</Stack>
|
||||
<Table
|
||||
height='100%'
|
||||
height='calc(100% - 52px)'
|
||||
sx={{ mx: -2 }}
|
||||
PaginationProps={{
|
||||
sx: {
|
||||
|
||||
@@ -87,6 +87,7 @@ const Dashboard = () => {
|
||||
size='small'
|
||||
onChange={(e) => setTimeRange(e.target.value as TimeRange)}
|
||||
sx={{ fontSize: 14 }}
|
||||
disabled
|
||||
>
|
||||
<MenuItem value='24h'>最近 24 小时</MenuItem>
|
||||
<MenuItem value='90d'>最近 90 天</MenuItem>
|
||||
|
||||
@@ -48,7 +48,7 @@ const ModelItem = ({
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const onRemoveModel = () => {
|
||||
Modal.confirm({
|
||||
title: '删除模型',
|
||||
@@ -114,18 +114,6 @@ const ModelItem = ({
|
||||
boxShadow:
|
||||
'rgba(54, 59, 76, 0.3) 0px 10px 30px 0px, rgba(54, 59, 76, 0.03) 0px 0px 1px 1px',
|
||||
},
|
||||
// '&::before': {
|
||||
// content: '""',
|
||||
// position: 'absolute',
|
||||
// top: 0,
|
||||
// left: 0,
|
||||
// right: 0,
|
||||
// height: 3,
|
||||
// background: data.is_active
|
||||
// ? 'linear-gradient(90deg, #4CAF50, #66BB6A)'
|
||||
// : 'linear-gradient(90deg, #E0E0E0, #BDBDBD)',
|
||||
// transition: 'all 0.3s ease',
|
||||
// },
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
@@ -141,11 +129,30 @@ const ModelItem = ({
|
||||
}
|
||||
sx={{ fontSize: 24 }}
|
||||
/>
|
||||
<Stack direction='row' alignItems='center' gap={1} sx={{ fontSize: 14, minWidth: 0 }}>
|
||||
<Box sx={{ fontWeight: 700, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
<Stack
|
||||
direction='row'
|
||||
alignItems='center'
|
||||
gap={1}
|
||||
sx={{ fontSize: 14, minWidth: 0 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
fontWeight: 700,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{data.show_name || '未命名'}
|
||||
</Box>
|
||||
<Box sx={{ color: 'text.tertiary', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
<Box
|
||||
sx={{
|
||||
color: 'text.tertiary',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
/ {data.model_name}
|
||||
</Box>
|
||||
</Stack>
|
||||
@@ -187,13 +194,11 @@ const ModelItem = ({
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
<Stack direction='row' alignItems='center'>
|
||||
{data.is_active && <StyledLabel color='success'>正在使用</StyledLabel>}
|
||||
{data.is_active && (
|
||||
<StyledLabel color='success'>正在使用</StyledLabel>
|
||||
)}
|
||||
</Stack>
|
||||
<Stack
|
||||
direction='row'
|
||||
sx={{ button: { minWidth: 0 } }}
|
||||
gap={2}
|
||||
>
|
||||
<Stack direction='row' sx={{ button: { minWidth: 0 } }} gap={2}>
|
||||
{!data.is_active && (
|
||||
<ButtonBase
|
||||
disableRipple
|
||||
@@ -206,15 +211,17 @@ const ModelItem = ({
|
||||
</ButtonBase>
|
||||
)}
|
||||
|
||||
{!data.is_internal && <ButtonBase
|
||||
disableRipple
|
||||
sx={{
|
||||
color: 'info.main',
|
||||
}}
|
||||
onClick={() => onEdit(data)}
|
||||
>
|
||||
编辑
|
||||
</ButtonBase>}
|
||||
{!data.is_internal && (
|
||||
<ButtonBase
|
||||
disableRipple
|
||||
sx={{
|
||||
color: 'info.main',
|
||||
}}
|
||||
onClick={() => onEdit(data)}
|
||||
>
|
||||
编辑
|
||||
</ButtonBase>
|
||||
)}
|
||||
|
||||
{data.is_active && (
|
||||
<ButtonBase
|
||||
@@ -248,9 +255,16 @@ const ModelItem = ({
|
||||
interface IModelCardProps {
|
||||
title: string;
|
||||
modelType: ConstsModelType;
|
||||
data: DomainModel[];
|
||||
refreshModel: () => void;
|
||||
}
|
||||
|
||||
const ModelCard: React.FC<IModelCardProps> = ({ title, modelType }) => {
|
||||
const ModelCard: React.FC<IModelCardProps> = ({
|
||||
title,
|
||||
modelType,
|
||||
data,
|
||||
refreshModel,
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [editData, setEditData] = useState<DomainModel | null>(null);
|
||||
|
||||
@@ -259,12 +273,6 @@ const ModelCard: React.FC<IModelCardProps> = ({ title, modelType }) => {
|
||||
setEditData(data);
|
||||
};
|
||||
|
||||
const { data: modelList = [], refresh } = useRequest(() =>
|
||||
getMyModelList({
|
||||
model_type: modelType,
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Stack direction='row' justifyContent='space-between' alignItems='center'>
|
||||
@@ -277,21 +285,18 @@ const ModelCard: React.FC<IModelCardProps> = ({ title, modelType }) => {
|
||||
添加模型
|
||||
</Button>
|
||||
</Stack>
|
||||
{modelList?.length > 0 ? (
|
||||
{data?.length > 0 ? (
|
||||
<Grid container spacing={2} sx={{ mt: 2 }}>
|
||||
{modelList.map((item) => (
|
||||
<Grid
|
||||
size={{ xs: 12, sm: 12, md: 12, lg: 6, xl: 4 }}
|
||||
key={item.id}
|
||||
>
|
||||
<ModelItem data={item} onEdit={onEdit} refresh={refresh} />
|
||||
{data.map((item) => (
|
||||
<Grid size={{ xs: 12, sm: 12, md: 12, lg: 6, xl: 4 }} key={item.id}>
|
||||
<ModelItem data={item} onEdit={onEdit} refresh={refreshModel} />
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
) : (
|
||||
<Stack alignItems={'center'} sx={{ mt: 2 }}>
|
||||
<Stack alignItems={'center'} sx={{ my: 2 }}>
|
||||
<img src={NoData} width={150} alt='empty' />
|
||||
<Box sx={{ color: 'text.tertiary', fontSize: 12 }}>
|
||||
<Box sx={{ color: 'error.main', fontSize: 12 }}>
|
||||
暂无模型,请先添加模型
|
||||
</Box>
|
||||
</Stack>
|
||||
@@ -303,7 +308,7 @@ const ModelCard: React.FC<IModelCardProps> = ({ title, modelType }) => {
|
||||
setOpen(false);
|
||||
setEditData(null);
|
||||
}}
|
||||
refresh={refresh}
|
||||
refresh={refreshModel}
|
||||
data={editData}
|
||||
type={modelType}
|
||||
/>
|
||||
|
||||
@@ -1,16 +1,54 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import TokenUsage from './components/tokenUsage';
|
||||
import ModelCard from './components/modelCard';
|
||||
import { Stack } from '@mui/material';
|
||||
import { ConstsModelType } from '@/api/types';
|
||||
import { useCommonContext } from '@/hooks/context';
|
||||
import { Modal } from '@c-x/ui';
|
||||
|
||||
const Model = () => {
|
||||
const { coderModel, llmModel, refreshModel, isConfigModel } =
|
||||
useCommonContext();
|
||||
const isFirst = useRef(true);
|
||||
useEffect(() => {
|
||||
if (isFirst.current && !isConfigModel) {
|
||||
isFirst.current = false;
|
||||
let text = '';
|
||||
if (
|
||||
(coderModel || []).length === 0 ||
|
||||
coderModel.every((it) => !it.is_active)
|
||||
) {
|
||||
text = '代码补全模型尚未配置激活,平台功能暂不可用!';
|
||||
} else if (
|
||||
(llmModel || []).length === 0 ||
|
||||
llmModel.every((it) => !it.is_active)
|
||||
) {
|
||||
text = '对话模型尚未配置激活,平台功能暂不可用!';
|
||||
} else {
|
||||
text = '模型尚未配置激活,平台功能暂不可用!';
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: text,
|
||||
showCancel: false,
|
||||
okText: '去配置',
|
||||
});
|
||||
}
|
||||
}, [isConfigModel, coderModel, llmModel]);
|
||||
|
||||
return (
|
||||
<Stack gap={2}>
|
||||
<TokenUsage />
|
||||
<ModelCard title='对话模型' modelType={ConstsModelType.ModelTypeLLM} />
|
||||
<ModelCard
|
||||
title='对话模型'
|
||||
data={llmModel}
|
||||
refreshModel={refreshModel}
|
||||
modelType={ConstsModelType.ModelTypeLLM}
|
||||
/>
|
||||
<ModelCard
|
||||
title='代码补全模型'
|
||||
data={coderModel}
|
||||
refreshModel={refreshModel}
|
||||
modelType={ConstsModelType.ModelTypeCoder}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -115,8 +115,9 @@ const User = () => {
|
||||
color='info'
|
||||
sx={{ gap: 2 }}
|
||||
onClick={() => setThirdPartyLoginSettingModalOpen(true)}
|
||||
disabled
|
||||
>
|
||||
配置
|
||||
配置 (敬请期待)
|
||||
</Button>
|
||||
</StyledCard>
|
||||
<LoginHistory />
|
||||
|
||||
@@ -20,6 +20,7 @@ const Dashboard = () => {
|
||||
size='small'
|
||||
onChange={(e) => setTimeRange(e.target.value as TimeRange)}
|
||||
sx={{ fontSize: 14 }}
|
||||
disabled
|
||||
>
|
||||
<MenuItem value='24h'>最近 24 小时</MenuItem>
|
||||
<MenuItem value='90d'>最近 90 天</MenuItem>
|
||||
|
||||
Reference in New Issue
Block a user