Add latest version check and update prompt to UI

Introduces backend and frontend logic to fetch the latest NapCat version tag from multiple sources, exposes a new API endpoint, and adds a UI prompt to notify users of new versions with an update button. Also includes minor code style improvements in dialog context.
This commit is contained in:
手瓜一十雪
2025-11-22 13:52:49 +08:00
parent d525f9b03d
commit 173a165c4b
6 changed files with 182 additions and 11 deletions

View File

@@ -1,13 +1,19 @@
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, FaQq } from 'react-icons/fa6';
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 {
@@ -186,6 +192,75 @@ export interface NewVersionTipProps {
// );
// };
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'>
{currentVersion}
</Chip>
</div>
<div className='text-sm space-x-2'>
<span></span>
<Chip color='primary'>{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,
@@ -212,6 +287,7 @@ const NapCatVersion = () => {
currentVersion
)
}
endContent={<NewVersionTip currentVersion={currentVersion} />}
/>
);
};

View File

@@ -6,30 +6,30 @@ import type { ModalProps } from '@/components/modal';
export interface AlertProps
extends Omit<ModalProps, 'onCancel' | 'showCancel' | 'cancelText'> {
onConfirm?: () => void
onConfirm?: () => void;
}
export interface ConfirmProps extends ModalProps {
onConfirm?: () => void
onCancel?: () => void
onConfirm?: () => void;
onCancel?: () => void;
}
export interface ModalItem extends ModalProps {
id: number
id: number;
}
export interface DialogContextProps {
alert: (config: AlertProps) => void
confirm: (config: ConfirmProps) => void
alert: (config: AlertProps) => void;
confirm: (config: ConfirmProps) => void;
}
export interface DialogProviderProps {
children: React.ReactNode
children: React.ReactNode;
}
export const DialogContext = React.createContext<DialogContextProps>({
alert: () => {},
confirm: () => {},
alert: () => { },
confirm: () => { },
});
const DialogProvider: React.FC<DialogProviderProps> = ({ children }) => {

View File

@@ -48,6 +48,12 @@ export default class WebUIManager {
return data.data;
}
public static async getLatestTag () {
const { data } =
await serverRequest.get<ServerResponse<string>>('/base/getLatestTag');
return data.data;
}
public static async UpdateNapCat () {
const { data } = await serverRequest.post<ServerResponse<any>>(
'/UpdateNapCat/update',