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() {
loraRowsLoading.value = true
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<{
loraAliases?: Record<string, string>
loraTriggers?: Record<string, string>
+8 -4
View File
@@ -3915,17 +3915,21 @@ function rememberLoraNames(key: string, names: string[]) {
}
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 nextImage = listed.image || []
const sameNames = (a: string[], b: string[]) => a.length === b.length && a.every((name, i) => name === b[i])
if (nextVideo.length && !sameNames(videoLoras.value, nextVideo)) {
const sameNameSet = (a: string[], b: string[]) => {
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
rememberLoraNames('aigen-lora-names-video', nextVideo)
} else if (!videoLoras.value.length) {
videoLoras.value = readStoredLoraNames('aigen-lora-names-video')
}
if (nextImage.length && !sameNames(imageLoras.value, nextImage)) {
if (nextImage.length && !sameNameSet(imageLoras.value, nextImage)) {
imageLoras.value = nextImage
rememberLoraNames('aigen-lora-names-image', nextImage)
} else if (!imageLoras.value.length) {
+3 -2
View File
@@ -1,7 +1,8 @@
import { listStudioLoras } from '~/server/utils/loras'
export default defineEventHandler(async () => {
const listed = await listStudioLoras()
export default defineEventHandler(async (event) => {
const fresh = String(getQuery(event).fresh || '') === '1'
const listed = await listStudioLoras({ fresh })
return {
image: listed.image,
video: listed.video
+25 -7
View File
@@ -142,10 +142,12 @@ async function discoverFromHost(via: 'video' | 'image') {
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()
if (cache && now - cache.at < CACHE_MS) return cache
if (inflight) return inflight
if (!fresh && cache && now - cache.at < CACHE_MS) return cache
if (inflight && (!fresh || inflightFresh)) return inflight
inflightFresh = fresh
inflight = (async () => {
const videoHost = comfyConfigured() ? getComfyHost() : ''
const imageHost = getBeastImageHost()
@@ -161,23 +163,39 @@ async function loadLoraCache(): Promise<LoraCache> {
image: uniqueNames([...image.image, ...video.image]),
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)) {
cache = { ...cache, at: Date.now() }
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
return next
})().finally(() => {
inflight = null
inflightFresh = false
})
return inflight
}
export async function listStudioLoras() {
export async function listStudioLoras(options: { fresh?: boolean } = {}) {
const xaigen = isXaigenStudio()
try {
const listed = await loadLoraCache()
const listed = await loadLoraCache(options)
return {
at: listed.at,
image: filterLorasForStudio(listed.image, xaigen),