From c41387a27c6095db7730d3b960a538784b442ade Mon Sep 17 00:00:00 2001 From: whc Date: Mon, 16 Feb 2026 19:32:33 +0800 Subject: [PATCH] Turn on GPU acceleration, update the download progress bar --- src/main/fileWatcher.ts | 15 ++---- src/main/index.ts | 7 ++- src/main/modelManager.ts | 4 ++ src/renderer/src/store/zimageStore.ts | 44 +++++++++++++---- src/renderer/src/views/ZImageGenerate.vue | 59 ++++++++++++++++++----- src/shared/const/ipc.ts | 1 + src/shared/type/zimage.ts | 2 + 7 files changed, 98 insertions(+), 34 deletions(-) diff --git a/src/main/fileWatcher.ts b/src/main/fileWatcher.ts index a7ff1a0..a3e4e87 100644 --- a/src/main/fileWatcher.ts +++ b/src/main/fileWatcher.ts @@ -55,17 +55,10 @@ export function startWatchingDirectory( // Add to detected set fileStats.forEach(item => detectedFiles.add(item.path)) - // Send existing images to frontend - if (fileStats.length > 0) { - console.log(`Sending ${fileStats.length} existing images to frontend`) - fileStats.forEach((item) => { - if (!_event.sender.isDestroyed()) { - _event.sender.send(IpcChannelOn.NEW_IMAGE_DETECTED, { - path: item.path, - mtime: item.mtime, - }) - } - }) + // Send existing images to frontend in a single batch + if (fileStats.length > 0 && !_event.sender.isDestroyed()) { + console.log(`Sending ${fileStats.length} existing images to frontend (batch)`) + _event.sender.send(IpcChannelOn.BATCH_IMAGES_DETECTED, fileStats) } } catch (error) { diff --git a/src/main/index.ts b/src/main/index.ts index 81b4e91..23cccff 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -129,8 +129,11 @@ function setTray(): void { tray.setContextMenu(contextMenu) } -// disable hardware acceleration for Compatibility for windows -app.disableHardwareAcceleration() +// Only disable hardware acceleration on Windows for compatibility +// macOS and Linux benefit significantly from GPU-accelerated rendering +if (process.platform === 'win32') { + app.disableHardwareAcceleration() +} // This method will be called when Electron has finished // initialization and is ready to create browser windows. diff --git a/src/main/modelManager.ts b/src/main/modelManager.ts index e80b6e2..e0cdf37 100644 --- a/src/main/modelManager.ts +++ b/src/main/modelManager.ts @@ -193,6 +193,8 @@ export async function downloadModels( progress: 0, currentFileIndex: index + 1, totalFiles: filesToDownload.length, + downloadedBytes: 0, + totalBytes: 0, } _event.sender.send(IpcChannelOn.MODEL_DOWNLOAD_PROGRESS, progressData) @@ -204,6 +206,8 @@ export async function downloadModels( progress: percentage, currentFileIndex: index + 1, totalFiles: filesToDownload.length, + downloadedBytes: downloaded, + totalBytes: total, } _event.sender.send(IpcChannelOn.MODEL_DOWNLOAD_PROGRESS, progressData) }) diff --git a/src/renderer/src/store/zimageStore.ts b/src/renderer/src/store/zimageStore.ts index 013f5fa..d0f326c 100644 --- a/src/renderer/src/store/zimageStore.ts +++ b/src/renderer/src/store/zimageStore.ts @@ -29,7 +29,7 @@ export const useZImageStore = defineStore( // Model Status const modelStatus = ref>({}) const isDownloadingModel = ref>({}) // Track downloading state per model - const downloadProgress = ref({ file: '', progress: 0, currentFileIndex: 0, totalFiles: 0 }) + const downloadProgress = ref({ file: '', progress: 0, currentFileIndex: 0, totalFiles: 0, downloadedBytes: 0, totalBytes: 0 }) // Model Zoo (Remote/Preset Models) const remoteModels = ref([ @@ -192,17 +192,42 @@ export const useZImageStore = defineStore( return { success: true } } - // Listen for new images from file watcher + // Binary search insert into descending-sorted array (by mtime) + function insertImageSorted(image: { path: string, mtime: number }): void { + const arr = generatedImages.value + let lo = 0 + let hi = arr.length + while (lo < hi) { + const mid = (lo + hi) >>> 1 + if (arr[mid].mtime > image.mtime) { + lo = mid + 1 + } + else { + hi = mid + } + } + arr.splice(lo, 0, image) + } + + // Listen for batch images (initial load) + ipcRenderer.on(IpcChannelOn.BATCH_IMAGES_DETECTED, (_event: any, images: Array<{ path: string, mtime: number }>) => { + console.log('[Store] Batch images received:', images.length) + const existingPaths = new Set(generatedImages.value.map(img => img.path)) + const newImages = images.filter(img => !existingPaths.has(img.path)) + if (newImages.length > 0) { + generatedImages.value.push(...newImages) + generatedImages.value.sort((a, b) => b.mtime - a.mtime) + console.log('[Store] Batch loaded. Total count:', generatedImages.value.length) + } + }) + + // Listen for single new image from file watcher ipcRenderer.on(IpcChannelOn.NEW_IMAGE_DETECTED, (_event: any, image: { path: string, mtime: number }) => { console.log('[Store] New image detected:', image.path) - - // Check if already exists by path const exists = generatedImages.value.some(img => img.path === image.path) if (!exists) { - generatedImages.value.push(image) - // Sort by mtime descending (Newest first) - generatedImages.value.sort((a, b) => b.mtime - a.mtime) - console.log('[Store] Image added and sorted. Count:', generatedImages.value.length) + insertImageSorted(image) + console.log('[Store] Image inserted. Count:', generatedImages.value.length) } }) @@ -273,8 +298,7 @@ export const useZImageStore = defineStore( const addGeneratedImage = (image: { path: string, mtime: number }): void => { const exists = generatedImages.value.some(img => img.path === image.path) if (!exists) { - generatedImages.value.push(image) - generatedImages.value.sort((a, b) => b.mtime - a.mtime) + insertImageSorted(image) } } diff --git a/src/renderer/src/views/ZImageGenerate.vue b/src/renderer/src/views/ZImageGenerate.vue index 5c7f52a..9d60608 100644 --- a/src/renderer/src/views/ZImageGenerate.vue +++ b/src/renderer/src/views/ZImageGenerate.vue @@ -99,15 +99,21 @@ onUnmounted(() => { ipcRenderer.removeAllListeners(IpcChannelOn.IMAGE_REMOVED) }) -// Log Scrolling -watch(logs, async () => { - await nextTick() - if (logRef.value?.$el) { - const scrollContainer = logRef.value.$el.querySelector('.n-log-loader') - if (scrollContainer) { - scrollContainer.scrollTop = scrollContainer.scrollHeight +// Log Scrolling (throttled to avoid excessive DOM operations during generation) +let logScrollTimer: ReturnType | null = null +watch(logs, () => { + if (logScrollTimer) + return + logScrollTimer = setTimeout(async () => { + logScrollTimer = null + await nextTick() + if (logRef.value?.$el) { + const scrollContainer = logRef.value.$el.querySelector('.n-log-loader') + if (scrollContainer) { + scrollContainer.scrollTop = scrollContainer.scrollHeight + } } - } + }, 150) }) // Actions @@ -218,6 +224,17 @@ const gpuIdStr = computed({ }, }) +// Format bytes to human-readable string +function formatBytes(bytes: number): string { + if (bytes === 0) + return '0 B' + const units = ['B', 'KB', 'MB', 'GB'] + const k = 1024 + const i = Math.floor(Math.log(bytes) / Math.log(k)) + const value = bytes / k ** i + return `${value.toFixed(i >= 2 ? 2 : 0)} ${units[i]}` +} + // Logic: Stepped Hard-Coded Grid // <= 4 images: 2 columns (1/2 width) // 5 - 15 images: 5 columns (1/5 width) @@ -320,8 +337,8 @@ const gridStyle = computed(() => {
@@ -496,12 +513,15 @@ const gridStyle = computed(() => { aria-modal="true" >
-
+
{{ t('common.downloadStatus', { file: downloadProgress.file, current: downloadProgress.currentFileIndex, total: downloadProgress.totalFiles, }) }} + + {{ formatBytes(downloadProgress.downloadedBytes) }} / {{ formatBytes(downloadProgress.totalBytes) }} +