diff --git a/.gitignore b/.gitignore index 699af84..ef04244 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ out /.idea /coverage/ /json/ +*.vpy +*.lwi diff --git a/package.json b/package.json index 1fa38b9..bb5362d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vset", "productName": "VSET", - "version": "4.3.6", + "version": "4.4.1", "description": "VSET", "author": "NangInShell", "main": "./out/main/index.js", diff --git a/resources/icon.png b/resources/icon.png index fd3803a..d6ed2b2 100644 Binary files a/resources/icon.png and b/resources/icon.png differ diff --git a/src/main/childProcessManager.ts b/src/main/childProcessManager.ts index a53f9dd..1edbf76 100644 --- a/src/main/childProcessManager.ts +++ b/src/main/childProcessManager.ts @@ -29,10 +29,6 @@ function safeUnpipe(): void { vspipe.proc.stdout.unpipe(ffmpeg.proc.stdin) } catch {} - try { - ffmpeg.proc.stdin.end() - } - catch {} } } diff --git a/src/main/getCorePath.ts b/src/main/getCorePath.ts index e18555b..92d98f7 100644 --- a/src/main/getCorePath.ts +++ b/src/main/getCorePath.ts @@ -1,4 +1,5 @@ import type { TaskConfig } from '@shared/type/taskConfig' +import { existsSync, mkdirSync } from 'node:fs' import path from 'node:path' import { app } from 'electron' @@ -19,13 +20,14 @@ export function getCorePath(): string { /** * 获取 VSET-core 中的可执行文件路径 - * @returns {object} 包含 vspipe、ffmpeg 和 ffprobe 的路径 + * @returns {object} 包含 vspipe、ffmpeg、ffprobe 和 mediainfo 的路径 */ -export function getExecPath(): { vspipe: string, ffmpeg: string, ffprobe: string } { +export function getExecPath(): { vspipe: string, ffmpeg: string, ffprobe: string, mediainfo: string } { return { vspipe: path.join(getCorePath(), 'VSPipe.exe'), ffmpeg: path.join(getCorePath(), 'ffmpeg.exe'), ffprobe: path.join(getCorePath(), 'ffprobe.exe'), + mediainfo: path.join(getCorePath(), 'MediaInfo.exe'), } } @@ -36,6 +38,18 @@ export function getExtraSRModelPath(): string { return path.join(getCorePath(), 'vs-coreplugins', 'models', 'VSET_ExtraSrModel') } +function ensureVpyDir(): string { + const dir = process.env.NODE_ENV === 'development' + ? path.join(app.getAppPath(), 'resources', 'vpyFile') + : path.join(app.getAppPath(), '..', 'vpyFile') + + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }) + } + + return dir +} + /** * 获取 VSET 生成的设置文件路径 * 暂时存放在 outputfolder 目录下 @@ -46,8 +60,8 @@ export function getGenSettingsPath(taskConfig: TaskConfig): string { /** * 获取 VSET 生成的 vpy 文件路径 - * 暂时存放在 outputfolder 目录下 + * 存放在软件 vpyFile 目录下 */ -export function getGenVpyPath(taskConfig: TaskConfig, baseName: string): string { - return path.join(taskConfig.outputFolder, `${baseName}.vpy`) +export function getGenVpyPath(_taskConfig: TaskConfig, baseName: string): string { + return path.join(ensureVpyDir(), `${baseName}.vpy`) } diff --git a/src/main/getSystemInfo.ts b/src/main/getSystemInfo.ts index ebbb896..9314ce3 100644 --- a/src/main/getSystemInfo.ts +++ b/src/main/getSystemInfo.ts @@ -1,4 +1,6 @@ +import { readdir } from 'node:fs/promises' import si from 'systeminformation' +import { getExtraSRModelPath } from './getCorePath' export async function getGpuInfo(): Promise> { const deviceList: Array = [] @@ -11,6 +13,26 @@ export async function getGpuInfo(): Promise> { export async function getCpuInfo(): Promise { const cpu = await si.cpu() - return cpu.brand } + +export async function getMemoryInfo(): Promise { + const mem = await si.mem() + const totalGB = (mem.total / (1024 * 1024 * 1024)).toFixed(2) + return `${totalGB} GB` +} + +export async function getExtraSRModelList(): Promise> { + const modelDir = getExtraSRModelPath() + try { + const files = await readdir(modelDir) + // 过滤出 .onnx 文件并去掉扩展名 + return files + .filter(file => file.endsWith('.onnx')) + .map(file => file.replace('.onnx', '')) + } + catch (error) { + console.error('Error reading extra SR model directory:', error) + return [] + } +} diff --git a/src/main/index.ts b/src/main/index.ts index f1a39d9..8ebe7a7 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -4,7 +4,7 @@ import { IpcChannelInvoke, IpcChannelSend } from '@shared/constant/ipc' import { app, BrowserWindow, ipcMain, nativeImage, shell } from 'electron' import appIcon from '../../resources/icon.png?asset' import { killAllProcesses } from './childProcessManager' -import { getCpuInfo, getGpuInfo } from './getSystemInfo' +import { getCpuInfo, getExtraSRModelList, getGpuInfo, getMemoryInfo } from './getSystemInfo' import { openDirectory } from './openDirectory' import { preview, previewFrame } from './previewOutput' import { PauseCommand, runCommand } from './runCommand' @@ -12,14 +12,15 @@ import { writeSettingsJson } from './writeFile' function createWindow(): BrowserWindow { const mainWindow = new BrowserWindow({ - width: 900, - height: 700, - minWidth: 900, - minHeight: 670, + width: 1000, + height: 875, + minWidth: 1000, + minHeight: 875, show: false, autoHideMenuBar: true, + frame: false, + resizable: true, icon: nativeImage.createFromPath(appIcon), - title: 'VSET 4.3.6', webPreferences: { preload: path.join(__dirname, '../preload/index.js'), sandbox: false, @@ -27,6 +28,23 @@ function createWindow(): BrowserWindow { }) // ipcMain + + ipcMain.on('window:minimize', () => { + mainWindow?.minimize() + }) + + ipcMain.on('window:maximize', () => { + if (mainWindow?.isMaximized()) { + mainWindow.unmaximize() + } + else { + mainWindow?.maximize() + } + }) + + ipcMain.on('window:close', () => { + mainWindow?.close() + }) ipcMain.on(IpcChannelSend.EXECUTE_COMMAND, runCommand) ipcMain.on(IpcChannelSend.PAUSE, PauseCommand) @@ -45,6 +63,10 @@ function createWindow(): BrowserWindow { ipcMain.handle(IpcChannelInvoke.GET_CPU_INFO, getCpuInfo) + ipcMain.handle(IpcChannelInvoke.GET_MEMORY_INFO, getMemoryInfo) + + ipcMain.handle(IpcChannelInvoke.GET_EXTRA_SR_MODEL_LIST, getExtraSRModelList) + // mainWindow mainWindow.on('ready-to-show', () => { mainWindow?.show() diff --git a/src/main/runCommand.ts b/src/main/runCommand.ts index 0ff5a0d..2daa07d 100644 --- a/src/main/runCommand.ts +++ b/src/main/runCommand.ts @@ -13,6 +13,38 @@ import { writeVpyFile } from './writeFile' const exec = promisify(execCallback) +// 定义 ffprobe 输出类型接口 +interface FfprobeStream { + codec_type?: 'video' | 'audio' | 'subtitle' + nb_frames?: string + avg_frame_rate?: string + width?: number + height?: number + [key: string]: any +} + +interface FfprobeMetadata { + streams?: FfprobeStream[] + [key: string]: any +} + +// 定义 MediaInfo 输出类型接口 +interface MediaInfoTrack { + '@type'?: 'Video' | 'Audio' | 'General' | string + 'FrameRate_Mode'?: string + [key: string]: any +} + +interface MediaInfoMedia { + track?: MediaInfoTrack[] + [key: string]: any +} + +interface MediaInfoData { + media?: MediaInfoMedia + [key: string]: any +} + function splitArgs(str: string): string[] { const matches = str.match(/"[^"]+"|\S+/g) return matches ? matches.map(s => s.replace(/^"|"$/g, '')) : [] @@ -42,13 +74,136 @@ function generate_cmd(taskConfig: TaskConfig, hasAudio: boolean, hasSubtitle: bo return cmd } +// 新增:获取输入视频信息的独立函数 +async function getInputVideoInfo(video: string): Promise<{ + hasAudio: boolean + hasSubtitle: boolean + videoStream: FfprobeStream | undefined + frameRateMode: string + frameCount: string + frameRate: string + resolution: string + audioText: string + subtitleText: string +}> { + const ffprobePath = getExecPath().ffprobe + const mediainfoPath = getExecPath().mediainfo + + const ffprobeCommand = `"${ffprobePath}" -v error -show_streams -of json "${video}"` + const { stdout: probeOut } = await exec(ffprobeCommand) + const metadata: FfprobeMetadata = JSON.parse(probeOut) + + const allStreams: FfprobeStream[] = metadata.streams || [] + const videoStream = allStreams.find((s: FfprobeStream) => s.codec_type === 'video') + const hasAudio = allStreams.some((s: FfprobeStream) => s.codec_type === 'audio') + const hasSubtitle = allStreams.some((s: FfprobeStream) => s.codec_type === 'subtitle') + + // 调用 MediaInfo.exe 获取 FrameRate_Mode + let frameRateMode = '未知' + try { + const mediainfoCommand = `"${mediainfoPath}" --Output=JSON "${video}"` + const { stdout: mediainfoOut } = await exec(mediainfoCommand) + const mediainfoData: MediaInfoData = JSON.parse(mediainfoOut) + + // MediaInfo JSON 格式:media.track[0].FrameRate_Mode(视频轨道通常是第一个) + if (mediainfoData.media && mediainfoData.media.track && mediainfoData.media.track.length > 0) { + const videoTrack = mediainfoData.media.track.find((track: MediaInfoTrack) => track['@type'] === 'Video') + if (videoTrack && videoTrack.FrameRate_Mode) { + frameRateMode = videoTrack.FrameRate_Mode + } + } + } + catch (error) { + console.error('MediaInfo 执行出错:', error) + } + + const frameCount = videoStream ? (videoStream.nb_frames || '未知') : '未知' + const frameRate = videoStream ? (videoStream.avg_frame_rate || '未知') : '未知' + const resolution = videoStream ? `${videoStream.width}x${videoStream.height}` : '未知' + const audioText = hasAudio ? '是' : '否' + const subtitleText = hasSubtitle ? '是' : '否' + + return { + hasAudio, + hasSubtitle, + videoStream, + frameRateMode, + frameCount, + frameRate, + resolution, + audioText, + subtitleText, + } +} + +// 新增:获取输出视频信息的独立函数 +async function getOutputVideoInfo(event: IpcMainEvent, vpyPath: string): Promise<{ + width: string + height: string + frames: string + fps: string +}> { + const vspipePath = getExecPath().vspipe + + const info: { + width: string + height: string + frames: string + fps: string + } = { + width: '未知', + height: '未知', + frames: '0', + fps: '0', + } + + await new Promise((resolve, reject) => { + const vspipeInfoProcess = spawn(vspipePath, ['--info', vpyPath]) + addProcess('vspipe', vspipeInfoProcess) + + let vspipeOut = '' // 用于保存 stdout 内容 + // eslint-disable-next-line unused-imports/no-unused-vars + let stderrOut = '' // 用于保存 stderr 内容 + + vspipeInfoProcess.stdout.on('data', (data: Buffer) => { + const str = iconv.decode(data, 'gbk') + vspipeOut += str + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `${str}`) + }) + + vspipeInfoProcess.stderr.on('data', (data: Buffer) => { + const str = iconv.decode(data, 'gbk') + stderrOut += str + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `${str}`) + }) + + vspipeInfoProcess.on('close', (code) => { + removeProcess(vspipeInfoProcess) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `vspipe info 执行完毕,退出码: ${code}\n`) + + info.width = vspipeOut.match(/Width:\s*(\d+)/)?.[1] || '未知' + info.height = vspipeOut.match(/Height:\s*(\d+)/)?.[1] || '未知' + info.frames = vspipeOut.match(/Frames:\s*(\d+)/)?.[1] || '0' + info.fps = vspipeOut.match(/FPS:\s*([\d/]+)\s*\(([\d.]+) fps\)/)?.[2] || '0' + + resolve() + }) + + vspipeInfoProcess.on('error', (err) => { + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `vspipe 执行出错: ${err.message}\n`) + reject(err) + }) + }) + + return info +} + export async function runCommand(event: IpcMainEvent, taskConfig: TaskConfig): Promise { const vpyContent = taskConfig.vpyContent const ffmpegCMD = taskConfig.ffmpegCMD const vspipePath = getExecPath().vspipe const ffmpegPath = getExecPath().ffmpeg - const ffprobePath = getExecPath().ffprobe const videos = Array.isArray(taskConfig.fileList) ? taskConfig.fileList : [] @@ -62,99 +217,44 @@ export async function runCommand(event: IpcMainEvent, taskConfig: TaskConfig): P } try { // ========== 1. 获取输入视频信息 ========== - const ffprobeCommand = `"${ffprobePath}" -v error -show_streams -of json "${video}"` - const { stdout: probeOut } = await exec(ffprobeCommand) - const metadata = JSON.parse(probeOut) - - const allStreams = metadata.streams || [] - const videoStream = allStreams.find((s: any) => s.codec_type === 'video') - const hasAudio = allStreams.some((s: any) => s.codec_type === 'audio') - const hasSubtitle = allStreams.some((s: any) => s.codec_type === 'subtitle') - - if (videoStream) { - const frameCount = videoStream.nb_frames || '未知' - const frameRate = videoStream.avg_frame_rate || '未知' - const resolution = `${videoStream.width}x${videoStream.height}` - const audioText = hasAudio ? '是' : '否' - const subtitleText = hasSubtitle ? '是' : '否' // 字幕信息 - - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `正在处理输入视频 ${video} 的信息:\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧数(输入): ${frameCount}\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧率(输入): ${frameRate}\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `分辨率(输入): ${resolution}\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `是否含有音频: ${audioText}\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `是否含有字幕: ${subtitleText}\n`) + const { hasAudio, hasSubtitle, frameCount, frameRate, resolution, audioText, subtitleText, frameRateMode } = await getInputVideoInfo(video) + + const frameRateModeText = frameRateMode === 'VFR' ? '是' : frameRateMode === 'CFR' ? '否' : '未知' + + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `正在处理输入视频 ${video} 的信息:\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧数(输入): ${frameCount}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧率(输入): ${frameRate}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `分辨率(输入): ${resolution}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `是否含有音频: ${audioText}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `是否含有字幕: ${subtitleText}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `视频流是否为可变帧率: ${frameRateModeText}\n`) + + // 如果是可变帧率,跳过处理该视频 + if (frameRateModeText === '是') { + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `跳过可变帧率视频: ${video}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `⚠️可变帧率视频会导致渲染结果出现音画不同步的问题,请在导入VSET之前处理为固定帧率视频。\n`) + continue } // ========== 2. 生成 vpy 文件 ========== - // 生成唯一 vpy 路径 const baseName = path.basename(video, path.extname(video)) const vpyPath = getGenVpyPath(taskConfig, baseName) await writeVpyFile(null, vpyPath, vpyContent, video) // ========== 3. 获取输出视频信息 ========== - let info: { - width: string - height: string - frames: string - fps: string - } = { - width: '未知', - height: '未知', - frames: '0', - fps: '0', - } - await new Promise((resolve, reject) => { - const vspipeInfoProcess = spawn(vspipePath, ['--info', vpyPath]) - addProcess('vspipe', vspipeInfoProcess) - - let vspipeOut = '' // 用于保存 stdout 内容 - // eslint-disable-next-line unused-imports/no-unused-vars - let stderrOut = '' // 用于保存 stderr 内容 - - vspipeInfoProcess.stdout.on('data', (data: Buffer) => { - const str = iconv.decode(data, 'gbk') - vspipeOut += str - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `${str}`) - }) - - vspipeInfoProcess.stderr.on('data', (data: Buffer) => { - const str = iconv.decode(data, 'gbk') - stderrOut += str - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `${str}`) - }) - - vspipeInfoProcess.on('close', (code) => { - removeProcess(vspipeInfoProcess) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `vspipe info 执行完毕,退出码: ${code}\n`)/////// - - info = { - width: vspipeOut.match(/Width:\s*(\d+)/)?.[1] || '未知', - height: vspipeOut.match(/Height:\s*(\d+)/)?.[1] || '未知', - frames: vspipeOut.match(/Frames:\s*(\d+)/)?.[1] || '0', - fps: vspipeOut.match(/FPS:\s*([\d/]+)\s*\(([\d.]+) fps\)/)?.[2] || '0', - } + const info = await getOutputVideoInfo(event, vpyPath) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `======= 输出视频信息 =======\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `宽: ${info.width}\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `高: ${info.height}\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧数: ${info.frames}\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧率: ${info.fps}\n`) - resolve() - }) - - vspipeInfoProcess.on('error', (err) => { - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `vspipe 执行出错: ${err.message}\n`) - reject(err) - }) - }) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `======= 输出视频信息 =======\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `宽(输出): ${info.width}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `高(输出): ${info.height}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧数(输出): ${info.frames}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧率(输出): ${info.fps}\n`) // ========== 4. 构建渲染命令 ========== const vspipeArgs = ffmpegCMD[0].replace(MagicStr.VPY_PATH, vpyPath) const ffmpegMajorArgs = ffmpegCMD[1] const ffmpegMinorArgs = ffmpegCMD[2] const ffmpeg_audio_sub_Args = generate_cmd(taskConfig, hasAudio, hasSubtitle) - const ffmpegArgs = ffmpegMajorArgs.replace(MagicStr.VIDEO_PATH, video) + ffmpeg_audio_sub_Args + ffmpegMinorArgs.replace(MagicStr.VIDEO_NAME, path.join(taskConfig.outputFolder, `${baseName}_enhance`) + taskConfig.videoContainer) const full_cmd = `${`"${vspipePath}" ${vspipeArgs}`} | "${ffmpegPath}" ${ffmpegArgs}` diff --git a/src/preload/index.ts b/src/preload/index.ts index 8a02264..a9b19a9 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1,9 +1,13 @@ import { electronAPI } from '@electron-toolkit/preload' -import { contextBridge } from 'electron' +import { contextBridge, ipcRenderer } from 'electron' // Custom APIs for renderer const api = {} - +contextBridge.exposeInMainWorld('electronWindow', { + minimize: () => ipcRenderer.send('window:minimize'), + maximize: () => ipcRenderer.send('window:maximize'), + close: () => ipcRenderer.send('window:close'), +}) // Use `contextBridge` APIs to expose Electron APIs to // renderer only if context isolation is enabled, otherwise // just add to the DOM global. diff --git a/src/renderer/index.html b/src/renderer/index.html index d697473..784c0f3 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -2,7 +2,7 @@ - VSET 4.3.6 + VSET 4.4.1 VNode { return () => h(NIcon, null, { default: () => h(icon) }) } const menuOptions: MenuOption[] = [ - { - whateverLabel: '主页', - whateverKey: 'home', - icon: renderIcon(homeIcon), - path: '/', - }, - - { - whateverLabel: '输入', - whateverKey: 'input', - icon: renderIcon(inputIcon), - path: '/input', - }, - - { - whateverLabel: '增强', - whateverKey: 'enhance', - icon: renderIcon(enhanceIcon), - path: '/enhance', - }, - - { - whateverLabel: '滤镜', - whateverKey: 'filter', - icon: renderIcon(filterIcon), - path: '/filter', - }, - - { - whateverLabel: '输出', - whateverKey: 'output', - icon: renderIcon(outputIcon), - path: '/output', - }, - - { - whateverLabel: '渲染', - whateverKey: 'render', - icon: renderIcon(renderingIcon), - path: '/render', - }, - { - whateverLabel: '预览', - whateverKey: 'preview', - icon: renderIcon(PreviewIcon), - path: '/preview', - }, - + { whateverLabel: '主页', whateverKey: 'home', icon: renderIcon(homeIcon), path: '/' }, + { whateverLabel: '输入', whateverKey: 'input', icon: renderIcon(inputIcon), path: '/input' }, + { whateverLabel: '增强', whateverKey: 'enhance', icon: renderIcon(enhanceIcon), path: '/enhance' }, + { whateverLabel: '滤镜', whateverKey: 'filter', icon: renderIcon(filterIcon), path: '/filter' }, + { whateverLabel: '输出', whateverKey: 'output', icon: renderIcon(outputIcon), path: '/output' }, + { whateverLabel: '渲染', whateverKey: 'render', icon: renderIcon(renderingIcon), path: '/render' }, + { whateverLabel: '预览', whateverKey: 'preview', icon: renderIcon(PreviewIcon), path: '/preview' }, ] export default defineComponent({ setup() { const router = useRouter() + const collapsed = ref(true) + const systemInfoStore = useSystemInfoStore() + + // 在应用启动时初始化系统信息 + onMounted(() => { + systemInfoStore.initSystemInfo() + // 清空并重新加载模型列表,确保每次启动都获取最新模型 + systemInfoStore.extraSrModelList = [] + }) + const onMenuChange = (value: string): void => { router.push(value) } + // 添加折叠状态切换处理函数 + const handleCollapseChange = (isCollapsed: boolean): void => { + collapsed.value = isCollapsed + } + + const handleMinimize = (): void => window.electronWindow?.minimize() + const handleMaximize = (): void => window.electronWindow?.maximize() + const handleClose = (): void => window.electronWindow?.close() + router.push('/home') return { - collapsed: ref(true), + appIcon, + collapsed, menuOptions, onMenuChange, + handleCollapseChange, // 导出折叠处理函数 + handleMinimize, + handleMaximize, + handleClose, + Remove, + Square, + Close, } }, }) @@ -90,17 +81,41 @@ export default defineComponent({ diff --git a/src/renderer/src/components/EnhancePage.vue b/src/renderer/src/components/EnhancePage.vue index 6555366..fffcab0 100644 --- a/src/renderer/src/components/EnhancePage.vue +++ b/src/renderer/src/components/EnhancePage.vue @@ -1,14 +1,14 @@ diff --git a/src/renderer/src/components/FilterPage.vue b/src/renderer/src/components/FilterPage.vue index 7153d88..d81a8d1 100644 --- a/src/renderer/src/components/FilterPage.vue +++ b/src/renderer/src/components/FilterPage.vue @@ -1,4 +1,5 @@ diff --git a/src/renderer/src/components/HomePage.vue b/src/renderer/src/components/HomePage.vue index e43326e..163e354 100644 --- a/src/renderer/src/components/HomePage.vue +++ b/src/renderer/src/components/HomePage.vue @@ -1,24 +1,40 @@