NapCatQQ/packages/napcat-webui-frontend/src/components/system_info.tsx
手瓜一十雪 9be4a08de9 Refactor UI components for consistent styling
Unified card and component styles across the frontend by removing background image logic and related conditional classes. Updated color schemes, shadows, and spacing for a more consistent appearance. Improved error handling and response structure in the backend update handler.
2025-12-22 13:34:59 +08:00

348 lines
10 KiB
TypeScript

import { Card, CardBody, CardHeader } from '@heroui/card';
import { Button } from '@heroui/button';
import { Chip } from '@heroui/chip';
import { Spinner } from '@heroui/spinner';
import { Tooltip } from '@heroui/tooltip';
import { useRequest } from 'ahooks';
import { FaCircleInfo, FaInfo, FaQq } from 'react-icons/fa6';
import { IoLogoChrome, IoLogoOctocat } from 'react-icons/io';
import { RiMacFill } from 'react-icons/ri';
import { useState } from 'react';
import toast from 'react-hot-toast';
import WebUIManager from '@/controllers/webui_manager';
import useDialog from '@/hooks/use-dialog';
export interface SystemInfoItemProps {
title: string;
icon?: React.ReactNode;
value?: React.ReactNode;
endContent?: React.ReactNode;
}
const SystemInfoItem: React.FC<SystemInfoItemProps> = ({
title,
value = '--',
icon,
endContent,
}) => {
return (
<div className='flex text-sm gap-1 p-2 items-center shadow-sm shadow-primary-100 dark:shadow-primary-100 rounded text-primary-400'>
{icon}
<div className='w-24'>{title}</div>
<div className='text-primary-200'>{value}</div>
<div className='ml-auto'>{endContent}</div>
</div>
);
};
export interface NewVersionTipProps {
currentVersion?: string;
}
// const NewVersionTip = (props: NewVersionTipProps) => {
// const { currentVersion } = props;
// const dialog = useDialog();
// const { data: releaseData, error } = useRequest(() =>
// request.get<GithubRelease[]>(
// 'https://api.github.com/repos/NapNeko/NapCatQQ/releases'
// )
// );
// if (error) {
// return (
// <Tooltip content='检查新版本失败'>
// <Button
// isIconOnly
// radius='full'
// color='primary'
// variant='shadow'
// className='!w-5 !h-5 !min-w-0 text-small shadow-md'
// onPress={() => {
// dialog.alert({
// title: '检查新版本失败',
// content: error.message,
// });
// }}
// >
// <FaInfo />
// </Button>
// </Tooltip>
// );
// }
// const latestVersion = releaseData?.data?.[0]?.tag_name;
// if (!latestVersion || !currentVersion) {
// return null;
// }
// if (compareVersion(latestVersion, currentVersion) <= 0) {
// return null;
// }
// const middleVersions: GithubRelease[] = [];
// for (let i = 0; i < releaseData.data.length; i++) {
// const versionInfo = releaseData.data[i];
// if (compareVersion(versionInfo.tag_name, currentVersion) > 0) {
// middleVersions.push(versionInfo);
// } else {
// break;
// }
// }
// const AISummaryComponent = () => {
// const {
// data: aiSummaryData,
// loading: aiSummaryLoading,
// error: aiSummaryError,
// run: runAiSummary,
// } = useRequest(
// (version) =>
// request.get<ServerResponse<string | null>>(
// `https://release.nc.152710.xyz/?version=${version}`,
// {
// timeout: 30000,
// }
// ),
// {
// manual: true,
// }
// );
// useEffect(() => {
// runAiSummary(currentVersion);
// }, [currentVersion, runAiSummary]);
// if (aiSummaryLoading) {
// return (
// <div className='flex justify-center py-1'>
// <Spinner size='sm' />
// </div>
// );
// }
// if (aiSummaryError) {
// return <div className='text-center text-primary-500'>AI 摘要获取失败</div>;
// }
// return <span className='text-default-700'>{aiSummaryData?.data.data}</span>;
// };
// return (
// <Tooltip content='有新版本可用'>
// <Button
// isIconOnly
// radius='full'
// color='primary'
// variant='shadow'
// className='!w-5 !h-5 !min-w-0 text-small shadow-md'
// onPress={() => {
// dialog.confirm({
// title: '有新版本可用',
// content: (
// <div className='space-y-2'>
// <div className='text-sm space-x-2'>
// <span>当前版本</span>
// <Chip color='primary' variant='flat'>
// v{currentVersion}
// </Chip>
// </div>
// <div className='text-sm space-x-2'>
// <span>最新版本</span>
// <Chip color='primary'>{latestVersion}</Chip>
// </div>
// <div className='p-2 rounded-md bg-content2 text-sm'>
// <div className='text-primary-400 font-bold flex items-center gap-1 mb-1'>
// <BsStars />
// <span>AI总结</span>
// </div>
// <AISummaryComponent />
// </div>
// <div className='text-sm space-y-2 !mt-4'>
// {middleVersions.map((versionInfo) => (
// <div
// key={versionInfo.tag_name}
// className='p-4 bg-content1 rounded-md shadow-small'
// >
// <TailwindMarkdown content={versionInfo.body} />
// </div>
// ))}
// </div>
// </div>
// ),
// scrollBehavior: 'inside',
// size: '3xl',
// confirmText: '前往下载',
// onConfirm () {
// window.open(
// 'https://github.com/NapNeko/NapCatQQ/releases',
// '_blank',
// 'noopener'
// );
// },
// });
// }}
// >
// <FaInfo />
// </Button>
// </Tooltip>
// );
// };
const NewVersionTip = (props: NewVersionTipProps) => {
const { currentVersion } = props;
const dialog = useDialog();
const { data: latestVersion, error } = useRequest(WebUIManager.getLatestTag);
const [updating, setUpdating] = useState(false);
if (error || !latestVersion || !currentVersion || latestVersion === currentVersion) {
return null;
}
return (
<Tooltip content='有新版本可用'>
<Button
isIconOnly
radius='full'
color='primary'
variant='shadow'
className='!w-5 !h-5 !min-w-0 text-small shadow-md'
onPress={() => {
dialog.confirm({
title: '有新版本可用',
content: (
<div className='space-y-2'>
<div className='text-sm space-x-2'>
<span></span>
<Chip color='primary' variant='flat'>
v{currentVersion}
</Chip>
</div>
<div className='text-sm space-x-2'>
<span></span>
<Chip color='primary'>v{latestVersion}</Chip>
</div>
{updating && (
<div className='flex justify-center'>
<Spinner size='sm' />
</div>
)}
</div>
),
confirmText: updating ? '更新中...' : '更新',
onConfirm: async () => {
setUpdating(true);
toast('更新中,预计需要几分钟,请耐心等待', {
duration: 3000,
});
try {
await WebUIManager.UpdateNapCat();
toast.success('更新完成,重启生效', {
duration: 5000,
});
} catch (error) {
console.error('Update failed:', error);
toast.success('更新异常', {
duration: 5000,
});
} finally {
setUpdating(false);
}
},
});
}}
>
<FaInfo />
</Button>
</Tooltip>
);
};
const NapCatVersion = () => {
const {
data: packageData,
loading: packageLoading,
error: packageError,
} = useRequest(WebUIManager.GetNapCatVersion);
const currentVersion = packageData?.version;
return (
<SystemInfoItem
title='NapCat 版本'
icon={<IoLogoOctocat className='text-xl' />}
value={
packageError
? (
`错误:${packageError.message}`
)
: packageLoading
? (
<Spinner size='sm' />
)
: (
currentVersion
)
}
endContent={<NewVersionTip currentVersion={currentVersion} />}
/>
);
};
export interface SystemInfoProps {
archInfo?: string;
}
const SystemInfo: React.FC<SystemInfoProps> = (props) => {
const { archInfo } = props;
const {
data: qqVersionData,
loading: qqVersionLoading,
error: qqVersionError,
} = useRequest(WebUIManager.getQQVersion);
return (
<Card className='bg-opacity-60 shadow-sm shadow-primary-100 dark:shadow-primary-100 overflow-visible flex-1'>
<CardHeader className='pb-0 items-center gap-1 text-primary-500 font-extrabold'>
<FaCircleInfo className='text-lg' />
<span></span>
</CardHeader>
<CardBody className='flex-1'>
<div className='flex flex-col justify-between h-full'>
<NapCatVersion />
<SystemInfoItem
title='QQ 版本'
icon={<FaQq className='text-lg' />}
value={
qqVersionError
? (
`错误:${qqVersionError.message}`
)
: qqVersionLoading
? (
<Spinner size='sm' />
)
: (
qqVersionData
)
}
/>
<SystemInfoItem
title='WebUI 版本'
icon={<IoLogoChrome className='text-xl' />}
value='Next'
/>
<SystemInfoItem
title='系统版本'
icon={<RiMacFill className='text-xl' />}
value={archInfo}
/>
</div>
</CardBody>
</Card>
);
};
export default SystemInfo;