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:
@@ -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
@@ -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) {
|
||||||
|
|||||||
@@ -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
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user