Always live-scan Studio 2 LoRAs on load and Refresh like Settings.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -394,7 +394,7 @@ let timer:ReturnType<typeof setTimeout>,clock:ReturnType<typeof setInterval>,dis
|
|||||||
onMounted(async()=>{
|
onMounted(async()=>{
|
||||||
let storage;try{storage=localStorage}catch{}
|
let storage;try{storage=localStorage}catch{}
|
||||||
loraMemory=createLoraMemory(xaigen,storage);loraMemory.sync(form);form.settings.seed=randomSeed()
|
loraMemory=createLoraMemory(xaigen,storage);loraMemory.sync(form);form.settings.seed=randomSeed()
|
||||||
clock=setInterval(()=>{now.value=Date.now()},1000);void loadLoras()
|
clock=setInterval(()=>{now.value=Date.now()},1000);void loadLoras(true)
|
||||||
library.value=await $fetch('/api/studio-2/library');form.folderId=library.value.folders[0]?.id||'';loadRevealed(form.folderId)
|
library.value=await $fetch('/api/studio-2/library');form.folderId=library.value.folders[0]?.id||'';loadRevealed(form.folderId)
|
||||||
const id=String(route.query.still||route.query.clip||route.query.track||'')
|
const id=String(route.query.still||route.query.clip||route.query.track||'')
|
||||||
const asset=[...library.value.stills,...library.value.clips,...(library.value.tracks||[])].find((a:any)=>a.id===id)
|
const asset=[...library.value.stills,...library.value.clips,...(library.value.tracks||[])].find((a:any)=>a.id===id)
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
import { readFileSync,writeFileSync,renameSync } from 'node:fs';
|
import { readFileSync,writeFileSync,renameSync } from 'node:fs'
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path'
|
||||||
import { cachedLoras } from '~/shared/studio2/lora-cache.mjs';
|
import { cachedLoras } from '~/shared/studio2/lora-cache.mjs'
|
||||||
import { listStudioLoras } from '../../utils/loras';
|
import { listStudioLoras } from '../../utils/loras'
|
||||||
import { studio2Root } from '../../utils/studio2/store';
|
import { studio2Root } from '../../utils/studio2/store'
|
||||||
import { studio2LoraCatalog } from '../../utils/studio2/lora-catalog';
|
import { studio2LoraCatalog } from '../../utils/studio2/lora-catalog'
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
const {owner}=assertLibraryOwner(event),path=join(studio2Root(),`loras-${owner}.cache`);
|
const { owner } = assertLibraryOwner(event)
|
||||||
|
const path = join(studio2Root(), `loras-${owner}.cache`)
|
||||||
|
// Same live Comfy scan as Settings (/api/loras?fresh=1). Always fresh so CACHE_MS cannot freeze adds.
|
||||||
const listed = await cachedLoras({
|
const listed = await cachedLoras({
|
||||||
read:()=>{try{return JSON.parse(readFileSync(path,'utf8'));}catch{return null;}},
|
read: () => { try { return JSON.parse(readFileSync(path, 'utf8')) } catch { return null } },
|
||||||
write:value=>{writeFileSync(path+'.tmp',JSON.stringify(value));renameSync(path+'.tmp',path);},
|
write: value => { writeFileSync(path + '.tmp', JSON.stringify(value)); renameSync(path + '.tmp', path) },
|
||||||
live:()=>listStudioLoras({fresh:true}),fresh:getQuery(event).fresh==='1'
|
live: () => listStudioLoras({ fresh: true }),
|
||||||
});
|
fresh: true
|
||||||
return {...listed,...studio2LoraCatalog(owner)};
|
})
|
||||||
});
|
return { ...listed, ...studio2LoraCatalog(owner) }
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
export async function cachedLoras({read,write,live,fresh=false}) {
|
export async function cachedLoras({read,write,live,fresh: _fresh=false}) {
|
||||||
const cached=read() || {image:[],video:[]};
|
const cached=read() || {image:[],video:[]};
|
||||||
if (!fresh && (cached.image?.length || cached.video?.length)) return cached;
|
|
||||||
try {
|
try {
|
||||||
|
// Always live-scan. Disk is asleep/empty fallback only — never a stale snapshot that skips Comfy.
|
||||||
const listed=await live();
|
const listed=await live();
|
||||||
const image=Array.isArray(listed?.image)?listed.image:[];
|
const image=Array.isArray(listed?.image)?listed.image:[];
|
||||||
const video=Array.isArray(listed?.video)?listed.video:[];
|
const video=Array.isArray(listed?.video)?listed.video:[];
|
||||||
// Comfy asleep / empty: keep disk. Otherwise replace — never merge stale names that freeze adds.
|
|
||||||
if (!image.length && !video.length) return cached;
|
if (!image.length && !video.length) return cached;
|
||||||
const next={image,video};
|
const next={image,video};
|
||||||
write(next);
|
write(next);
|
||||||
|
|||||||
@@ -34,26 +34,32 @@ test('hidden xAIGen canvas and family render no image or video elements',async()
|
|||||||
test('cached LoRAs survive sleeping GPU and failed explicit refresh',async()=>{
|
test('cached LoRAs survive sleeping GPU and failed explicit refresh',async()=>{
|
||||||
const cache={image:['Klein/portrait.safetensors'],video:['motion.safetensors']};let queried=0
|
const cache={image:['Klein/portrait.safetensors'],video:['motion.safetensors']};let queried=0
|
||||||
const opts={read:()=>cache,write:()=>assert.fail('Do not erase cache'),live:async()=>{queried++;throw Error('Comfy asleep')}}
|
const opts={read:()=>cache,write:()=>assert.fail('Do not erase cache'),live:async()=>{queried++;throw Error('Comfy asleep')}}
|
||||||
assert.deepEqual(await cachedLoras(opts),cache);assert.equal(queried,0);assert.deepEqual(await cachedLoras({...opts,fresh:true}),cache);assert.equal(queried,1)
|
assert.deepEqual(await cachedLoras(opts),cache);assert.equal(queried,1)
|
||||||
|
assert.deepEqual(await cachedLoras({...opts,fresh:true}),cache);assert.equal(queried,2)
|
||||||
})
|
})
|
||||||
test('fresh LoRA refresh replaces disk cache so new Comfy names appear',async()=>{
|
test('bench load and Refresh always live-scan and replace disk so new Comfy names appear',async()=>{
|
||||||
let store={image:['old.safetensors'],video:['old-vid.safetensors']}
|
let store={image:['old.safetensors'],video:['old-vid.safetensors']}
|
||||||
const next=await cachedLoras({
|
const next=await cachedLoras({
|
||||||
read:()=>store,
|
read:()=>store,
|
||||||
write:value=>{store=value},
|
write:value=>{store=value},
|
||||||
live:async()=>({image:['old.safetensors','new-drop.safetensors'],video:['old-vid.safetensors']}),
|
live:async()=>({image:['old.safetensors','klein-pokemon_anime_style_klein9b.safetensors'],video:['old-vid.safetensors']}),
|
||||||
fresh:true
|
fresh:false
|
||||||
})
|
})
|
||||||
assert.deepEqual(next.image,['old.safetensors','new-drop.safetensors'])
|
assert.ok(next.image.includes('klein-pokemon_anime_style_klein9b.safetensors'))
|
||||||
assert.deepEqual(store.image,['old.safetensors','new-drop.safetensors'])
|
assert.deepEqual(store.image,next.image)
|
||||||
const removed=await cachedLoras({
|
const removed=await cachedLoras({
|
||||||
read:()=>store,
|
read:()=>store,
|
||||||
write:value=>{store=value},
|
write:value=>{store=value},
|
||||||
live:async()=>({image:['new-drop.safetensors'],video:[]}),
|
live:async()=>({image:['klein-pokemon_anime_style_klein9b.safetensors'],video:[]}),
|
||||||
fresh:true
|
fresh:true
|
||||||
})
|
})
|
||||||
assert.deepEqual(removed,{image:['new-drop.safetensors'],video:[]})
|
assert.deepEqual(removed,{image:['klein-pokemon_anime_style_klein9b.safetensors'],video:[]})
|
||||||
assert.deepEqual(store,{image:['new-drop.safetensors'],video:[]})
|
})
|
||||||
|
test('bench mounts and Refresh LoRAs request a live scan',()=>{
|
||||||
|
const src=readFileSync(new URL('../components/studio2/Bench.vue',import.meta.url),'utf8')
|
||||||
|
assert.match(src,/void loadLoras\(true\)/)
|
||||||
|
assert.match(src,/loadLoras\(true\)/)
|
||||||
|
assert.match(src,/\/api\/studio-2\/loras'\+\(fresh\?'\?fresh=1':''\)/)
|
||||||
})
|
})
|
||||||
test('source/hero actions are independent and full input restores structured settings',()=>{
|
test('source/hero actions are independent and full input restores structured settings',()=>{
|
||||||
const form={identityStillId:'hero',imageAId:'source',promptSections:{action:'old'},settings:{}}
|
const form={identityStillId:'hero',imageAId:'source',promptSections:{action:'old'},settings:{}}
|
||||||
|
|||||||
Reference in New Issue
Block a user