Fix LoRA folder drops not appearing without weakening engine filters.

Stop empty Comfy blips from freezing the LoRA cache timestamp, and refresh the picker list from Comfy on each poll. Engine tagging (flux/krea) and xaigen platform filtering stay as they were.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-04 15:52:31 -05:00
co-authored by Cursor
parent 10910677d0
commit 06e40793db
4 changed files with 37 additions and 14 deletions
+1 -1
View File
@@ -760,7 +760,7 @@ async function forceResetGpu() {
async function loadLoraKeys() { async function loadLoraKeys() {
loraRowsLoading.value = true loraRowsLoading.value = true
try { try {
const listed = await $fetch<{ image?: string[]; video?: string[] }>('/api/loras').catch(() => ({ image: [], video: [] })) const listed = await $fetch<{ image?: string[]; video?: string[] }>('/api/loras?fresh=1').catch(() => ({ image: [], video: [] }))
const library = await $fetch<{ const library = await $fetch<{
loraAliases?: Record<string, string> loraAliases?: Record<string, string>
loraTriggers?: Record<string, string> loraTriggers?: Record<string, string>
+8 -4
View File
@@ -3915,17 +3915,21 @@ function rememberLoraNames(key: string, names: string[]) {
} }
async function pollLoras() { async function pollLoras() {
const listed = await $fetch<{ image?: string[]; video?: string[] }>('/api/loras').catch(() => ({ image: [], video: [] })) const listed = await $fetch<{ image?: string[]; video?: string[] }>('/api/loras?fresh=1').catch(() => ({ image: [], video: [] }))
const nextVideo = listed.video || [] const nextVideo = listed.video || []
const nextImage = listed.image || [] const nextImage = listed.image || []
const sameNames = (a: string[], b: string[]) => a.length === b.length && a.every((name, i) => name === b[i]) const sameNameSet = (a: string[], b: string[]) => {
if (nextVideo.length && !sameNames(videoLoras.value, nextVideo)) { if (a.length !== b.length) return false
const left = new Set(a)
return b.every(name => left.has(name))
}
if (nextVideo.length && !sameNameSet(videoLoras.value, nextVideo)) {
videoLoras.value = nextVideo videoLoras.value = nextVideo
rememberLoraNames('aigen-lora-names-video', nextVideo) rememberLoraNames('aigen-lora-names-video', nextVideo)
} else if (!videoLoras.value.length) { } else if (!videoLoras.value.length) {
videoLoras.value = readStoredLoraNames('aigen-lora-names-video') videoLoras.value = readStoredLoraNames('aigen-lora-names-video')
} }
if (nextImage.length && !sameNames(imageLoras.value, nextImage)) { if (nextImage.length && !sameNameSet(imageLoras.value, nextImage)) {
imageLoras.value = nextImage imageLoras.value = nextImage
rememberLoraNames('aigen-lora-names-image', nextImage) rememberLoraNames('aigen-lora-names-image', nextImage)
} else if (!imageLoras.value.length) { } else if (!imageLoras.value.length) {
+3 -2
View File
@@ -1,7 +1,8 @@
import { listStudioLoras } from '~/server/utils/loras' import { listStudioLoras } from '~/server/utils/loras'
export default defineEventHandler(async () => { export default defineEventHandler(async (event) => {
const listed = await listStudioLoras() const fresh = String(getQuery(event).fresh || '') === '1'
const listed = await listStudioLoras({ fresh })
return { return {
image: listed.image, image: listed.image,
video: listed.video video: listed.video
+25 -7
View File
@@ -142,10 +142,12 @@ async function discoverFromHost(via: 'video' | 'image') {
return { image, video } return { image, video }
} }
async function loadLoraCache(): Promise<LoraCache> { async function loadLoraCache(options: { fresh?: boolean } = {}): Promise<LoraCache> {
const fresh = options.fresh === true
const now = Date.now() const now = Date.now()
if (cache && now - cache.at < CACHE_MS) return cache if (!fresh && cache && now - cache.at < CACHE_MS) return cache
if (inflight) return inflight if (inflight && (!fresh || inflightFresh)) return inflight
inflightFresh = fresh
inflight = (async () => { inflight = (async () => {
const videoHost = comfyConfigured() ? getComfyHost() : '' const videoHost = comfyConfigured() ? getComfyHost() : ''
const imageHost = getBeastImageHost() const imageHost = getBeastImageHost()
@@ -161,23 +163,39 @@ async function loadLoraCache(): Promise<LoraCache> {
image: uniqueNames([...image.image, ...video.image]), image: uniqueNames([...image.image, ...video.image]),
video: uniqueNames([...video.video, ...image.video]) video: uniqueNames([...video.video, ...image.video])
} }
// Comfy asleep / unreachable returns empty. Keep the last good list so the picker does not vanish. // Comfy asleep / unreachable returns empty. Keep the last good list so the picker
// does not vanish — but do NOT refresh `at`, or a blip freezes new folder drops out.
if (!next.image.length && !next.video.length && cache && (cache.image.length || cache.video.length)) { if (!next.image.length && !next.video.length && cache && (cache.image.length || cache.video.length)) {
cache = { ...cache, at: Date.now() }
return cache return cache
} }
// Prefer the richer list when a partial reply would shrink a known catalog.
if (
cache
&& (next.image.length + next.video.length) < (cache.image.length + cache.video.length)
&& next.image.every(name => cache!.image.includes(name) || cache!.video.includes(name))
&& next.video.every(name => cache!.image.includes(name) || cache!.video.includes(name))
) {
const merged: LoraCache = {
at: Date.now(),
image: uniqueNames([...cache.image, ...next.image]),
video: uniqueNames([...cache.video, ...next.video])
}
cache = merged
return merged
}
cache = next cache = next
return next return next
})().finally(() => { })().finally(() => {
inflight = null inflight = null
inflightFresh = false
}) })
return inflight return inflight
} }
export async function listStudioLoras() { export async function listStudioLoras(options: { fresh?: boolean } = {}) {
const xaigen = isXaigenStudio() const xaigen = isXaigenStudio()
try { try {
const listed = await loadLoraCache() const listed = await loadLoraCache(options)
return { return {
at: listed.at, at: listed.at,
image: filterLorasForStudio(listed.image, xaigen), image: filterLorasForStudio(listed.image, xaigen),