Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 4 additions & 11 deletions src/main/fileWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 5 additions & 2 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions src/main/modelManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
})
Expand Down
44 changes: 34 additions & 10 deletions src/renderer/src/store/zimageStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const useZImageStore = defineStore(
// Model Status
const modelStatus = ref<Record<string, { valid: boolean, missingFiles: string[] }>>({})
const isDownloadingModel = ref<Record<string, boolean>>({}) // Track downloading state per model
const downloadProgress = ref<ZImageModelDownloadProgress>({ file: '', progress: 0, currentFileIndex: 0, totalFiles: 0 })
const downloadProgress = ref<ZImageModelDownloadProgress>({ file: '', progress: 0, currentFileIndex: 0, totalFiles: 0, downloadedBytes: 0, totalBytes: 0 })

// Model Zoo (Remote/Preset Models)
const remoteModels = ref([
Expand Down Expand Up @@ -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)
}
})

Expand Down Expand Up @@ -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)
}
}

Expand Down
59 changes: 48 additions & 11 deletions src/renderer/src/views/ZImageGenerate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof setTimeout> | 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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -320,8 +337,8 @@ const gridStyle = computed(() => {
<NImageGroup>
<div v-if="generatedImages.length > 0" class="image-grid" :style="gridStyle">
<div
v-for="(img, index) in generatedImages"
:key="index"
v-for="img in generatedImages"
:key="img.path"
class="image-wrapper"
@contextmenu.prevent="handleContextMenu(img.path)"
>
Expand Down Expand Up @@ -496,12 +513,15 @@ const gridStyle = computed(() => {
aria-modal="true"
>
<div class="download-progress">
<div class="mb-2 flex justify-between text-xs text-gray-500">
<div class="download-info">
<span>{{ t('common.downloadStatus', {
file: downloadProgress.file,
current: downloadProgress.currentFileIndex,
total: downloadProgress.totalFiles,
}) }}</span>
<span v-if="downloadProgress.totalBytes > 0" class="download-size">
{{ formatBytes(downloadProgress.downloadedBytes) }} / {{ formatBytes(downloadProgress.totalBytes) }}
</span>
</div>
<NProgress
type="line"
Expand Down Expand Up @@ -809,6 +829,23 @@ $radius-sm: 12px;
margin-left: 16px;
}
}

.download-progress {
.download-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
font-size: 13px;
color: #666;
}

.download-size {
font-variant-numeric: tabular-nums;
font-weight: 600;
color: #333;
}
}
</style>

<style>
Expand Down
1 change: 1 addition & 0 deletions src/shared/const/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export enum IpcChannelOn {
COMMAND_STDERR = 'ipc:on:command-stderr',
COMMAND_CLOSE = 'ipc:on:command-close-code',
NEW_IMAGE_DETECTED = 'ipc:on:new-image-detected',
BATCH_IMAGES_DETECTED = 'ipc:on:batch-images-detected',
IMAGE_REMOVED = 'ipc:on:image-removed',
MODEL_DOWNLOAD_PROGRESS = 'ipc:on:model-download-progress',
}
2 changes: 2 additions & 0 deletions src/shared/type/zimage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ export interface ZImageModelDownloadProgress {
progress: number
currentFileIndex: number
totalFiles: number
downloadedBytes: number
totalBytes: number
}