diff --git a/packages/napcat-framework/napcat.ts b/packages/napcat-framework/napcat.ts index 57444baf..5aac337d 100644 --- a/packages/napcat-framework/napcat.ts +++ b/packages/napcat-framework/napcat.ts @@ -8,6 +8,8 @@ import { QQBasicInfoWrapper } from '@/napcat-core/helper/qq-basic-info'; import { InstanceContext, loadQQWrapper, NapCatCore, NapCatCoreWorkingEnv, NodeIKernelLoginListener, NodeIKernelLoginService, NodeIQQNTWrapperSession, SelfInfo, WrapperNodeApi } from '@/napcat-core'; import { proxiedListenerOf } from '@/napcat-core/helper/proxy-handler'; import { statusHelperSubscription } from '@/napcat-core/helper/status'; +import { applyPendingUpdates } from '@/napcat-webui-backend/src/api/UpdateNapCat'; +import { WebUiDataRuntime } from '@/napcat-webui-backend/src/helper/Data'; // Framework ES入口文件 export async function getWebUiUrl () { @@ -32,6 +34,7 @@ export async function NCoreInitFramework ( }); const pathWrapper = new NapCatPathWrapper(); + await applyPendingUpdates(pathWrapper); const logger = new LogWrapper(pathWrapper.logsPath); const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVersion()); @@ -73,6 +76,7 @@ export async function NCoreInitFramework ( await loaderObject.core.initCore(); // 启动WebUi + WebUiDataRuntime.setWorkingEnv(NapCatCoreWorkingEnv.Framework); InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription).then().catch(e => logger.logError(e)); // 初始化LLNC的Onebot实现 await new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper).InitOneBot(); diff --git a/packages/napcat-pty/unixTerminal.ts b/packages/napcat-pty/unixTerminal.ts index 50290ba9..1d46fb81 100644 --- a/packages/napcat-pty/unixTerminal.ts +++ b/packages/napcat-pty/unixTerminal.ts @@ -12,7 +12,17 @@ import { ArgvOrCommandLine } from '@homebridge/node-pty-prebuilt-multiarch/src/t import { assign } from '@homebridge/node-pty-prebuilt-multiarch/src/utils'; import { pty_loader } from './prebuild-loader'; import { fileURLToPath } from 'url'; -export const pty = pty_loader(); + +// 懒加载pty,避免在模块导入时立即执行pty_loader() +let _pty: any; +export const pty: any = new Proxy({}, { + get (_target, prop) { + if (!_pty) { + _pty = pty_loader(); + } + return _pty[prop]; + } +}); let helperPath: string; helperPath = '../build/Release/spawn-helper'; diff --git a/packages/napcat-shell/base.ts b/packages/napcat-shell/base.ts index 5ebb6ca0..74c79063 100644 --- a/packages/napcat-shell/base.ts +++ b/packages/napcat-shell/base.ts @@ -35,6 +35,7 @@ import { logSubscription, LogWrapper } from '@/napcat-core/helper/log'; import { proxiedListenerOf } from '@/napcat-core/helper/proxy-handler'; import { QQBasicInfoWrapper } from '@/napcat-core/helper/qq-basic-info'; import { statusHelperSubscription } from '@/napcat-core/helper/status'; +import { applyPendingUpdates } from '@/napcat-webui-backend/src/api/UpdateNapCat'; // NapCat Shell App ES 入口文件 async function handleUncaughtExceptions (logger: LogWrapper) { process.on('uncaughtException', (err) => { @@ -318,6 +319,7 @@ export async function NCoreInitShell () { const pathWrapper = new NapCatPathWrapper(); const logger = new LogWrapper(pathWrapper.logsPath); handleUncaughtExceptions(logger); + await applyPendingUpdates(pathWrapper); // 初始化 FFmpeg 服务 await FFmpegService.init(pathWrapper.binaryPath, logger); @@ -338,8 +340,8 @@ export async function NCoreInitShell () { o3Service.addO3MiscListener(new NodeIO3MiscListener()); logger.log('[NapCat] [Core] NapCat.Core Version: ' + napCatVersion); + WebUiDataRuntime.setWorkingEnv(NapCatCoreWorkingEnv.Shell); InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription).then().catch(e => logger.logError(e)); - const engine = wrapper.NodeIQQNTWrapperEngine.get(); const loginService = wrapper.NodeIKernelLoginService.get(); let session: NodeIQQNTWrapperSession; diff --git a/packages/napcat-webui-backend/src/api/UpdateNapCat.ts b/packages/napcat-webui-backend/src/api/UpdateNapCat.ts new file mode 100644 index 00000000..24f4d192 --- /dev/null +++ b/packages/napcat-webui-backend/src/api/UpdateNapCat.ts @@ -0,0 +1,388 @@ +import { RequestHandler } from 'express'; +import { sendSuccess, sendError } from '@/napcat-webui-backend/src/utils/response'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as https from 'https'; +import compressing from 'compressing'; +import { webUiPathWrapper } from '../../index'; +import { NapCatPathWrapper } from '@/napcat-common/src/path'; +import { WebUiDataRuntime } from '@/napcat-webui-backend/src/helper/Data'; +import { NapCatCoreWorkingEnv } from '@/napcat-webui-backend/src/types'; + +interface Release { + tag_name: string; + assets: Array<{ + name: string; + browser_download_url: string; + }>; + body?: string; +} + +// 更新配置文件接口 +interface UpdateConfig { + version: string; + updateTime: string; + files: Array<{ + sourcePath: string; + targetPath: string; + backupPath?: string; + }>; + changelog?: string; +} + +// 需要跳过更新的文件 +const SKIP_UPDATE_FILES = [ + 'NapCatWinBootMain.exe', + 'NapCatWinBootHook.dll' +]; + +/** + * 递归扫描目录中的所有文件 + */ +function scanFilesRecursively (dirPath: string, basePath: string = dirPath): Array<{ + sourcePath: string; + relativePath: string; +}> { + const files: Array<{ + sourcePath: string; + relativePath: string; + }> = []; + + const items = fs.readdirSync(dirPath); + + for (const item of items) { + const fullPath = path.join(dirPath, item); + const relativePath = path.relative(basePath, fullPath); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory()) { + // 递归扫描子目录 + files.push(...scanFilesRecursively(fullPath, basePath)); + } else if (stat.isFile()) { + files.push({ + sourcePath: fullPath, + relativePath: relativePath + }); + } + } + + return files; +} + +// 镜像源列表(参考ffmpeg下载实现) +const mirrorUrls = [ + 'https://j.1win.ggff.net/', + 'https://git.yylx.win/', + 'https://ghfile.geekertao.top/', + 'https://gh-proxy.net/', + 'https://ghm.078465.xyz/', + 'https://gitproxy.127731.xyz/', + 'https://jiashu.1win.eu.org/', + '', // 原始URL +]; + +/** + * 测试URL是否可用 + */ +async function testUrl (url: string): Promise { + return new Promise((resolve) => { + const req = https.get(url, { timeout: 5000 }, (res) => { + const statusCode = res.statusCode || 0; + if (statusCode >= 200 && statusCode < 300) { + req.destroy(); + resolve(true); + } else { + req.destroy(); + resolve(false); + } + }); + + req.on('error', () => resolve(false)); + req.on('timeout', () => { + req.destroy(); + resolve(false); + }); + }); +} + +/** + * 构建镜像URL + */ +function buildMirrorUrl (originalUrl: string, mirror: string): string { + if (!mirror) return originalUrl; + return mirror + originalUrl; +} + +/** + * 查找可用的下载URL + */ +async function findAvailableUrl (originalUrl: string): Promise { + console.log('Testing download URLs...'); + + // 先测试原始URL + if (await testUrl(originalUrl)) { + console.log('Using original URL:', originalUrl); + return originalUrl; + } + + // 测试镜像源 + for (const mirror of mirrorUrls) { + const mirrorUrl = buildMirrorUrl(originalUrl, mirror); + console.log('Testing mirror:', mirrorUrl); + if (await testUrl(mirrorUrl)) { + console.log('Using mirror URL:', mirrorUrl); + return mirrorUrl; + } + } + + throw new Error('所有下载源都不可用'); +} + +/** + * 下载文件(带进度和重试) + */ +async function downloadFile (url: string, dest: string): Promise { + console.log('Starting download from:', url); + const file = fs.createWriteStream(dest); + + return new Promise((resolve, reject) => { + const request = https.get(url, { + headers: { 'User-Agent': 'NapCat-WebUI' } + }, (res) => { + console.log('Response status:', res.statusCode); + console.log('Content-Type:', res.headers['content-type']); + + if (res.statusCode === 302 || res.statusCode === 301) { + console.log('Following redirect to:', res.headers.location); + file.close(); + fs.unlinkSync(dest); + downloadFile(res.headers.location!, dest).then(resolve).catch(reject); + return; + } + + if (res.statusCode !== 200) { + file.close(); + fs.unlinkSync(dest); + reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`)); + return; + } + + res.pipe(file); + file.on('finish', () => { + file.close(); + console.log('Download completed'); + resolve(); + }); + }); + + request.on('error', (err) => { + console.error('Download error:', err); + file.close(); + fs.unlink(dest, () => { }); + reject(err); + }); + }); +} + +export const UpdateNapCatHandler: RequestHandler = async (_req, res) => { + try { + // 获取最新release信息 + const latestRelease = await getLatestRelease() as Release; + const ReleaseName = WebUiDataRuntime.getWorkingEnv() === NapCatCoreWorkingEnv.Framework ? 'NapCat.Framework.zip' : 'NapCat.Shell.zip'; + const shellZipAsset = latestRelease.assets.find(asset => asset.name === ReleaseName); + if (!shellZipAsset) { + throw new Error(`未找到${ReleaseName}文件`); + } + + // 创建临时目录 + const tempDir = path.join(webUiPathWrapper.binaryPath, './temp'); + if (!fs.existsSync(tempDir)) { + fs.mkdirSync(tempDir, { recursive: true }); + } + + // 查找可用的下载URL + const downloadUrl = await findAvailableUrl(shellZipAsset.browser_download_url); + + // 下载zip + const zipPath = path.join(tempDir, 'napcat-latest.zip'); + console.log('[NapCat Update] Saving to:', zipPath); + await downloadFile(downloadUrl, zipPath); + + // 检查文件大小 + const stats = fs.statSync(zipPath); + console.log('[NapCat Update] Downloaded file size:', stats.size, 'bytes'); + + // 解压到临时目录 + const extractPath = path.join(tempDir, 'napcat-extract'); + console.log('[NapCat Update] Extracting to:', extractPath); + await compressing.zip.uncompress(zipPath, extractPath); + + // 获取解压后的实际内容目录(NapCat.Shell.zip直接包含文件,无额外根目录) + const sourcePath = extractPath; + + // 执行更新操作 + try { + // 扫描需要更新的文件 + const allFiles = scanFilesRecursively(sourcePath); + const failedFiles: Array<{ + sourcePath: string; + targetPath: string; + }> = []; + + // 先尝试直接替换文件 + for (const fileInfo of allFiles) { + const targetFilePath = path.join(webUiPathWrapper.binaryPath, fileInfo.relativePath); + + // 跳过指定的文件 + if (SKIP_UPDATE_FILES.includes(path.basename(fileInfo.relativePath))) { + console.log(`[NapCat Update] Skipping update for ${fileInfo.relativePath}`); + continue; + } + + try { + // 确保目标目录存在 + const targetDir = path.dirname(targetFilePath); + if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir, { recursive: true }); + } + + // 尝试直接替换文件 + if (fs.existsSync(targetFilePath)) { + fs.unlinkSync(targetFilePath); // 删除旧文件 + } + fs.copyFileSync(fileInfo.sourcePath, targetFilePath); + } catch (error) { + // 如果替换失败,添加到失败列表 + console.log(`[NapCat Update] Failed to update ${targetFilePath}, will retry on next startup:`, error); + failedFiles.push({ + sourcePath: fileInfo.sourcePath, + targetPath: targetFilePath + }); + } + } + + // 如果有替换失败的文件,创建更新配置文件 + if (failedFiles.length > 0) { + const updateConfig: UpdateConfig = { + version: latestRelease.tag_name, + updateTime: new Date().toISOString(), + files: failedFiles, + changelog: latestRelease.body || '' + }; + + // 保存更新配置文件 + const configPath = path.join(webUiPathWrapper.configPath, 'napcat-update.json'); + fs.writeFileSync(configPath, JSON.stringify(updateConfig, null, 2)); + console.log(`[NapCat Update] Update config saved for ${failedFiles.length} failed files: ${configPath}`); + } + + // 发送成功响应 + const message = failedFiles.length > 0 + ? `更新完成,重启应用以应用剩余${failedFiles.length}个文件的更新` + : '更新完成'; + sendSuccess(res, { + status: 'completed', + message, + newVersion: latestRelease.tag_name, + failedFilesCount: failedFiles.length + }); + + } catch (error) { + console.error('更新失败:', error); + sendError(res, '更新失败: ' + (error instanceof Error ? error.message : '未知错误')); + } + + } catch (error: any) { + console.error('更新失败:', error); + sendError(res, '更新失败: ' + error.message); + } +}; + +async function getLatestRelease (): Promise { + return new Promise((resolve, reject) => { + https.get('https://api.github.com/repos/NapNeko/NapCatQQ/releases/latest', { + headers: { 'User-Agent': 'NapCat-WebUI' } + }, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + try { + const release = JSON.parse(data) as Release; + console.log('Release info:', { + tag_name: release.tag_name, + assets: release.assets?.map(a => ({ name: a.name, url: a.browser_download_url })) + }); + resolve(release); + } catch (e) { + reject(e); + } + }); + }).on('error', reject); + }); +} + +/** + * 应用待处理的更新(在应用启动时调用) + */ +export async function applyPendingUpdates (webUiPathWrapper: NapCatPathWrapper): Promise { + const configPath = path.join(webUiPathWrapper.configPath, 'napcat-update.json'); + + if (!fs.existsSync(configPath)) { + console.log('No pending updates found'); + return; + } + + try { + console.log('[NapCat Update] Applying pending updates...'); + const updateConfig: UpdateConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); + + const remainingFiles: Array<{ + sourcePath: string; + targetPath: string; + }> = []; + + for (const file of updateConfig.files) { + try { + // 检查源文件是否存在 + if (!fs.existsSync(file.sourcePath)) { + console.warn(`[NapCat Update] Source file not found: ${file.sourcePath}`); + continue; + } + + // 确保目标目录存在 + const targetDir = path.dirname(file.targetPath); + if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir, { recursive: true }); + } + + // 尝试替换文件 + if (fs.existsSync(file.targetPath)) { + fs.unlinkSync(file.targetPath); // 删除旧文件 + } + fs.copyFileSync(file.sourcePath, file.targetPath); + console.log(`[NapCat Update] Updated ${path.basename(file.targetPath)} on startup`); + + } catch (error) { + console.error(`[NapCat Update] Failed to update ${file.targetPath} on startup:`, error); + // 如果仍然失败,保留在列表中 + remainingFiles.push(file); + } + } + + // 如果还有失败的文件,更新配置文件 + if (remainingFiles.length > 0) { + const updatedConfig: UpdateConfig = { + ...updateConfig, + files: remainingFiles + }; + fs.writeFileSync(configPath, JSON.stringify(updatedConfig, null, 2)); + console.log(`${remainingFiles.length} files still pending update`); + } else { + // 所有文件都成功更新,删除配置文件 + fs.unlinkSync(configPath); + console.log('[NapCat Update] All pending updates applied successfully'); + } + } catch (error) { + console.error('[NapCat Update] Failed to apply pending updates:', error); + } +} diff --git a/packages/napcat-webui-backend/src/helper/Data.ts b/packages/napcat-webui-backend/src/helper/Data.ts index f5c4b9cd..d452eb33 100644 --- a/packages/napcat-webui-backend/src/helper/Data.ts +++ b/packages/napcat-webui-backend/src/helper/Data.ts @@ -1,8 +1,9 @@ import store from 'napcat-common/src/store'; import { napCatVersion } from 'napcat-common/src/version'; -import type { LoginRuntimeType } from '../types'; +import { NapCatCoreWorkingEnv, type LoginRuntimeType } from '../types'; const LoginRuntime: LoginRuntimeType = { + workingEnv: NapCatCoreWorkingEnv.Unknown, LoginCurrentTime: Date.now(), LoginCurrentRate: 0, QQLoginStatus: false, // 已实现 但太傻了 得去那边注册个回调刷新 @@ -36,6 +37,12 @@ const LoginRuntime: LoginRuntimeType = { }, }; export const WebUiDataRuntime = { + setWorkingEnv (env: NapCatCoreWorkingEnv): void { + LoginRuntime.workingEnv = env; + }, + getWorkingEnv (): NapCatCoreWorkingEnv { + return LoginRuntime.workingEnv; + }, setWebUiTokenChangeCallback (func: (token: string) => Promise): void { LoginRuntime.onWebUiTokenChange = func; }, diff --git a/packages/napcat-webui-backend/src/router/UpdateNapCat.ts b/packages/napcat-webui-backend/src/router/UpdateNapCat.ts new file mode 100644 index 00000000..bb163b21 --- /dev/null +++ b/packages/napcat-webui-backend/src/router/UpdateNapCat.ts @@ -0,0 +1,13 @@ +/** + * @file UpdateNapCat路由 + */ + +import { Router } from 'express'; +import { UpdateNapCatHandler } from '@/napcat-webui-backend/src/api/UpdateNapCat'; + +const router = Router(); + +// POST /api/UpdateNapCat/update - 更新NapCat +router.post('/update', UpdateNapCatHandler); + +export { router as UpdateNapCatRouter }; \ No newline at end of file diff --git a/packages/napcat-webui-backend/src/router/index.ts b/packages/napcat-webui-backend/src/router/index.ts index a83c3c7b..21d094dd 100644 --- a/packages/napcat-webui-backend/src/router/index.ts +++ b/packages/napcat-webui-backend/src/router/index.ts @@ -14,6 +14,7 @@ import { LogRouter } from '@/napcat-webui-backend/src/router/Log'; import { BaseRouter } from '@/napcat-webui-backend/src/router/Base'; import { FileRouter } from './File'; import { WebUIConfigRouter } from './WebUIConfig'; +import { UpdateNapCatRouter } from './UpdateNapCat'; const router = Router(); @@ -38,5 +39,7 @@ router.use('/Log', LogRouter); router.use('/File', FileRouter); // router:WebUI配置相关路由 router.use('/WebUIConfig', WebUIConfigRouter); +// router:更新NapCat相关路由 +router.use('/UpdateNapCat', UpdateNapCatRouter); export { router as ALLRouter }; diff --git a/packages/napcat-webui-backend/src/types/index.ts b/packages/napcat-webui-backend/src/types/index.ts index 79f9cfe5..21c31d44 100644 --- a/packages/napcat-webui-backend/src/types/index.ts +++ b/packages/napcat-webui-backend/src/types/index.ts @@ -30,8 +30,13 @@ export interface WebUiCredentialJson { Data: WebUiCredentialInnerJson; Hmac: string; } - +export enum NapCatCoreWorkingEnv { + Unknown = 0, + Shell = 1, + Framework = 2, +} export interface LoginRuntimeType { + workingEnv: NapCatCoreWorkingEnv; LoginCurrentTime: number; LoginCurrentRate: number; QQLoginStatus: boolean; diff --git a/packages/napcat-webui-frontend/src/components/system_info.tsx b/packages/napcat-webui-frontend/src/components/system_info.tsx index 3699e0d4..29d414ce 100644 --- a/packages/napcat-webui-frontend/src/components/system_info.tsx +++ b/packages/napcat-webui-frontend/src/components/system_info.tsx @@ -1,30 +1,20 @@ -import { Button } from '@heroui/button'; import { Card, CardBody, CardHeader } from '@heroui/card'; -import { Chip } from '@heroui/chip'; import { Spinner } from '@heroui/spinner'; -import { Tooltip } from '@heroui/tooltip'; import { useRequest } from 'ahooks'; -import { useEffect } from 'react'; -import { BsStars } from 'react-icons/bs'; -import { FaCircleInfo, FaInfo, FaQq } from 'react-icons/fa6'; +import { FaCircleInfo, FaQq } from 'react-icons/fa6'; import { IoLogoChrome, IoLogoOctocat } from 'react-icons/io'; import { RiMacFill } from 'react-icons/ri'; -import useDialog from '@/hooks/use-dialog'; -import { request } from '@/utils/request'; -import { compareVersion } from '@/utils/version'; import WebUIManager from '@/controllers/webui_manager'; -import { GithubRelease } from '@/types/github'; -import TailwindMarkdown from './tailwind_markdown'; export interface SystemInfoItemProps { - title: string - icon?: React.ReactNode - value?: React.ReactNode - endContent?: React.ReactNode + title: string; + icon?: React.ReactNode; + value?: React.ReactNode; + endContent?: React.ReactNode; } const SystemInfoItem: React.FC = ({ @@ -44,157 +34,157 @@ const SystemInfoItem: React.FC = ({ }; export interface NewVersionTipProps { - currentVersion?: string + currentVersion?: string; } -const NewVersionTip = (props: NewVersionTipProps) => { - const { currentVersion } = props; - const dialog = useDialog(); - const { data: releaseData, error } = useRequest(() => - request.get( - 'https://api.github.com/repos/NapNeko/NapCatQQ/releases' - ) - ); +// const NewVersionTip = (props: NewVersionTipProps) => { +// const { currentVersion } = props; +// const dialog = useDialog(); +// const { data: releaseData, error } = useRequest(() => +// request.get( +// 'https://api.github.com/repos/NapNeko/NapCatQQ/releases' +// ) +// ); - if (error) { - return ( - - - - ); - } +// if (error) { +// return ( +// +// +// +// ); +// } - const latestVersion = releaseData?.data?.[0]?.tag_name; +// const latestVersion = releaseData?.data?.[0]?.tag_name; - if (!latestVersion || !currentVersion) { - return null; - } +// if (!latestVersion || !currentVersion) { +// return null; +// } - if (compareVersion(latestVersion, currentVersion) <= 0) { - return null; - } +// if (compareVersion(latestVersion, currentVersion) <= 0) { +// return null; +// } - const middleVersions: GithubRelease[] = []; +// 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; - } - } +// 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>( - `https://release.nc.152710.xyz/?version=${version}`, - { - timeout: 30000, - } - ), - { - manual: true, - } - ); +// const AISummaryComponent = () => { +// const { +// data: aiSummaryData, +// loading: aiSummaryLoading, +// error: aiSummaryError, +// run: runAiSummary, +// } = useRequest( +// (version) => +// request.get>( +// `https://release.nc.152710.xyz/?version=${version}`, +// { +// timeout: 30000, +// } +// ), +// { +// manual: true, +// } +// ); - useEffect(() => { - runAiSummary(currentVersion); - }, [currentVersion, runAiSummary]); +// useEffect(() => { +// runAiSummary(currentVersion); +// }, [currentVersion, runAiSummary]); - if (aiSummaryLoading) { - return ( -
- -
- ); - } - if (aiSummaryError) { - return
AI 摘要获取失败
; - } - return {aiSummaryData?.data.data}; - }; +// if (aiSummaryLoading) { +// return ( +//
+// +//
+// ); +// } +// if (aiSummaryError) { +// return
AI 摘要获取失败
; +// } +// return {aiSummaryData?.data.data}; +// }; - return ( - - - - ); -}; +// return ( +// +// +// +// ); +// }; const NapCatVersion = () => { const { @@ -212,7 +202,7 @@ const NapCatVersion = () => { value={ packageError ? ( - `错误:${packageError.message}` + `错误:${packageError.message}` ) : packageLoading ? ( @@ -222,13 +212,12 @@ const NapCatVersion = () => { currentVersion ) } - endContent={} /> ); }; export interface SystemInfoProps { - archInfo?: string + archInfo?: string; } const SystemInfo: React.FC = (props) => { const { archInfo } = props; @@ -252,7 +241,7 @@ const SystemInfo: React.FC = (props) => { value={ qqVersionError ? ( - `错误:${qqVersionError.message}` + `错误:${qqVersionError.message}` ) : qqVersionLoading ? ( diff --git a/packages/napcat-webui-frontend/src/controllers/webui_manager.ts b/packages/napcat-webui-frontend/src/controllers/webui_manager.ts index 76dad172..18812fdc 100644 --- a/packages/napcat-webui-frontend/src/controllers/webui_manager.ts +++ b/packages/napcat-webui-frontend/src/controllers/webui_manager.ts @@ -48,6 +48,15 @@ export default class WebUIManager { return data.data; } + public static async UpdateNapCat () { + const { data } = await serverRequest.post>( + '/UpdateNapCat/update', + {}, + { timeout: 60000 } // 1分钟超时 + ); + return data; + } + public static async getQQVersion () { const { data } = await serverRequest.get>('/base/QQVersion'); diff --git a/packages/napcat-webui-frontend/src/pages/dashboard/about.tsx b/packages/napcat-webui-frontend/src/pages/dashboard/about.tsx index 743f8c32..aaa3c412 100644 --- a/packages/napcat-webui-frontend/src/pages/dashboard/about.tsx +++ b/packages/napcat-webui-frontend/src/pages/dashboard/about.tsx @@ -1,4 +1,5 @@ import { Card, CardBody } from '@heroui/card'; +import { Button } from '@heroui/button'; import { Image } from '@heroui/image'; import { Link } from '@heroui/link'; import { Skeleton } from '@heroui/skeleton'; @@ -7,6 +8,7 @@ import { useRequest } from 'ahooks'; import { useMemo } from 'react'; import { BsTelegram, BsTencentQq } from 'react-icons/bs'; import { IoDocument } from 'react-icons/io5'; +import toast from 'react-hot-toast'; import HoverTiltedCard from '@/components/hover_titled_card'; import NapCatRepoInfo from '@/components/napcat_repo_info'; @@ -20,9 +22,44 @@ import WebUIManager from '@/controllers/webui_manager'; function VersionInfo () { const { data, loading, error } = useRequest(WebUIManager.GetNapCatVersion); + + // 更新NapCat + const { run: updateNapCat, loading: updating } = useRequest( + WebUIManager.UpdateNapCat, + { + manual: true, + onSuccess: (response) => { + console.log('UpdateNapCat onSuccess response:', response); + console.log('response.code:', response.code); + console.log('response.data:', response.data); + console.log('response.message:', response.message); + + if (response.code === 0) { + const message = response.data?.message || '更新完成'; + console.log('显示消息:', message); + toast.success(message, { + duration: 5000, + }); + } else { + console.log('显示错误消息:', response.message || '更新失败'); + toast.error(response.message || '更新失败'); + } + }, + onError: (error) => { + toast.error('更新失败: ' + error.message); + }, + } + ); + + const handleUpdate = () => { + if (!updating) { + updateNapCat(); + } + }; + return ( -
-
+
+
NapCat
{error ? ( @@ -47,6 +84,16 @@ function VersionInfo () { /> )}
+
); }