Fix Regional LoRA picker state

This commit is contained in:
BlackVortexAI
2026-08-23 18:00:28 +02:00
parent 9e13d9fb87
commit dab986ef4b
5 changed files with 31 additions and 22 deletions
+18 -18
View File
File diff suppressed because one or more lines are too long
+9
View File
@@ -8,6 +8,7 @@ import {emptyLoraV3Config,parseLoraV3Config,serializeLoraV3Config} from "../ui/s
const collectors=[{id:"collector",label:"Styles",resources:[{id:"skin",label:"Skin"}]}];
const editorSource=readFileSync(new URL("../ui/src/regional/LoraV3EditorWindow.tsx",import.meta.url),"utf8");
const styles=readFileSync(new URL("../ui/src/index.css",import.meta.url),"utf8");
test("the production picker renders Collector / Resource from persisted ids",()=>{
const config={version:1,collector_id:"collector",entries:[{id:"entry",source:{kind:"external",resource_id:"skin"},targets:[{scope:"global"}]}]};
const html=renderToStaticMarkup(React.createElement(LoraV3ResourcePickerPanel,{collectors,config,resolved:true,targetOptions:[{value:"global",label:"Global",target:{scope:"global"}}],onCollector(){},onResource(){},onAddExternal(){}}));
@@ -40,6 +41,14 @@ test("Regional Prompt easy mode renders every stack assigned to its scope",()=>{
assert.match(html,/LoRA enabled/);assert.match(html,/LoRA Stack 1/);assert.match(html,/LoRA Stack 2/);assert.match(html,/Add LoRA stack/);
assert.match(html,/aria-label="Remove LoRA Stack 1"/);assert.doesNotMatch(html,/>Remove</);
});
test("Regional Prompt keeps the LoRA toggle above the stack card without overlap",()=>{
assert.match(styles,/\.bv-lora-v3-enable-row\{[^}]*grid-template-columns:minmax\(0,1fr\)/);
assert.doesNotMatch(styles,/\.bv-lora-v3-enable-row>\.bv-control-field\{[^}]*padding-top/);
});
test("adding an advanced LoRA stack commits through the graph-linking helper",()=>{
assert.match(editorSource,/commitLoraV3Config\(node,next\)/);
assert.doesNotMatch(editorSource,/writeNodeLoraV3Config\(node,next\)/);
});
test("the LoRA editor exposes ordered repeatable scope operations and participates in window visibility",()=>{
assert.match(editorSource,/Add step/);assert.match(editorSource,/<SortableList/);assert.match(editorSource,/onReorder=/);
assert.match(editorSource,/label="Target"/);assert.match(editorSource,/label="Operation"/);assert.match(editorSource,/crypto\.randomUUID\(\)/);
+1 -1
View File
@@ -586,5 +586,5 @@
.lg-node:has([node-type="BV M0 Fake Resource Collector"]) [node-type="BV M0 Fake Resource Collector"]:has(input[aria-label="collector_id"]),.lg-node:has([node-type="BV M0 Fake Resource Collector"]) [node-type="BV M0 Fake Resource Collector"]:has(input[aria-label="resource_a_id"]),.lg-node:has([node-type="BV M0 Fake Resource Collector"]) [node-type="BV M0 Fake Resource Collector"]:has(input[aria-label="resource_b_id"]),.lg-node:has([node-type="BV M0 Fake Resource Consumer"]) [node-type="BV M0 Fake Resource Consumer"]:has(input[aria-label="collector_id"]),.lg-node:has([node-type="BV M0 Fake Resource Consumer"]) [node-type="BV M0 Fake Resource Consumer"]:has(input[aria-label="resource_id"]){display:none}
.lg-slot:has(circle[fill*="BV_RUNTIME_RESOURCE_PROVIDER_M0"],[style*="BV_RUNTIME_RESOURCE_PROVIDER_M0"]){display:none!important}
.lg-slot:has(circle[fill*="BV_RUNTIME_RESOURCE_PROVIDER"],[style*="BV_RUNTIME_RESOURCE_PROVIDER"]){display:none!important}
.bv-lora-v3-picker-host{position:relative;z-index:1;padding:6px}.bv-lora-v3-picker-panel{display:grid;gap:10px}.bv-lora-v3-target{display:grid;gap:8px;padding:9px;border:1px solid var(--bv-ui-border);border-radius:4px}.bv-lora-v3-target>h3{margin:0}.bv-optional-lora-scope{display:grid;gap:7px}.bv-lora-v3-enable-row{display:grid;grid-template-columns:max-content minmax(0,1fr);align-items:start;gap:12px}.bv-lora-v3-enable-row>.bv-control-field>.bv-control-label{display:none}.bv-lora-v3-enable-row>.bv-control-field{padding-top:20px}.bv-lora-v3-enable-row>.bv-control-field .bv-toggle{white-space:nowrap}.bv-lora-v3-scope-picker{display:grid;gap:8px}.bv-lora-v3-stack-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 8px;align-items:end;padding:7px;border:1px solid var(--bv-ui-border);border-radius:4px}.bv-lora-v3-stack-row>strong,.bv-lora-v3-stack-row>.bv-resource-picker{grid-column:1}.bv-lora-v3-stack-row>.bv-button{grid-column:2;grid-row:2}.bv-trash-icon{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.bv-lora-v3-window{display:grid;gap:10px;padding:10px;overflow:auto}
.bv-lora-v3-picker-host{position:relative;z-index:1;padding:6px}.bv-lora-v3-picker-panel{display:grid;gap:10px}.bv-lora-v3-target{display:grid;gap:8px;padding:9px;border:1px solid var(--bv-ui-border);border-radius:4px}.bv-lora-v3-target>h3{margin:0}.bv-optional-lora-scope{display:grid;gap:7px}.bv-lora-v3-enable-row{display:grid;grid-template-columns:minmax(0,1fr);align-items:start;gap:8px}.bv-lora-v3-enable-row>.bv-control-field>.bv-control-label{display:none}.bv-lora-v3-enable-row>.bv-control-field .bv-toggle{white-space:nowrap}.bv-lora-v3-scope-picker{display:grid;gap:8px}.bv-lora-v3-stack-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 8px;align-items:end;padding:7px;border:1px solid var(--bv-ui-border);border-radius:4px}.bv-lora-v3-stack-row>strong,.bv-lora-v3-stack-row>.bv-resource-picker{grid-column:1}.bv-lora-v3-stack-row>.bv-button{grid-column:2;grid-row:2}.bv-trash-icon{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.bv-lora-v3-window{display:grid;gap:10px;padding:10px;overflow:auto}
.lg-node:has([node-type="BV M0 Fake Multi Resource Consumer"]) [node-type="BV M0 Fake Multi Resource Consumer"]:has(input[aria-label="resource_bindings"]){display:none}.bv-m0-multi-picker{max-height:520px;overflow:auto}.bv-m0-binding{display:grid;gap:5px;padding:6px;border:1px solid var(--bv-ui-border);border-radius:4px}.bv-m0-binding>header{display:flex;align-items:center;justify-content:space-between}.bv-m0-binding>header button,.bv-m0-add-binding{color:var(--bv-ui-text);background:var(--bv-ui-input);border:1px solid var(--bv-ui-border);border-radius:4px}.bv-m0-binding>header button{width:24px;height:24px}.bv-m0-add-binding{min-height:28px}
+2 -2
View File
@@ -3,7 +3,7 @@ import { Button, ResourcePicker, SelectField, SortableList, type SortableItem }
import { BvManagedWindow } from "../ui/window";
import { setWindowMenuVisible, useWindowMenuVisibility } from "../ui/windowRegistry";
import { type LoraV3Config, type LoraV3Operation, type LoraV3Step, loraV3TargetValue } from "./LoraV3ResourcePickerPanel";
import { loraV3Catalog, loraV3Resolved, loraV3TargetOptions, readNodeLoraV3Config, setLoraV3Collector, setLoraV3EntryResource, writeNodeLoraV3Config } from "./loraV3Ui";
import { commitLoraV3Config, loraV3Catalog, loraV3Resolved, loraV3TargetOptions, readNodeLoraV3Config, setLoraV3Collector, setLoraV3EntryResource } from "./loraV3Ui";
const widget=(node:any,name:string)=>node?.widgets?.find((item:any)=>item.name===name);
const operations=["replace","merge","subtract","clear"].map(value=>({value,label:value[0].toUpperCase()+value.slice(1)}));
@@ -15,7 +15,7 @@ export default function LoraV3EditorWindow({open,node,onClose}:{open:boolean;nod
const collectors=loraV3Catalog(node).filter(item=>item.resources.length>0),targets=loraV3TargetOptions(node),resolved=loraV3Resolved(node,config);
const legacyOperation=String(widget(node,"operation")?.value??"replace") as LoraV3Operation;
const steps:LoraV3Step[]=config.version===2?(config.steps??[]):config.entries.flatMap(entry=>entry.targets.map(target=>({id:crypto.randomUUID(),operation:legacyOperation,target:target as any,entries:[{...entry,targets:[target]}]})));
const commit=(nextSteps:LoraV3Step[],collectorId=config.collector_id)=>{const next:LoraV3Config={version:2,collector_id:collectorId,entries:[],steps:nextSteps};writeNodeLoraV3Config(node,next);setConfig(next);};
const commit=(nextSteps:LoraV3Step[],collectorId=config.collector_id)=>{const next:LoraV3Config={version:2,collector_id:collectorId,entries:[],steps:nextSteps};setConfig(commitLoraV3Config(node,next));};
const items:SortableItem[]=steps.map((step,index)=>{
const targetValue=loraV3TargetValue(step.target),targetLabel=targets.find(item=>item.value===targetValue)?.label??"Missing region";
return {id:step.id,title:`Step ${index+1}: ${targetLabel}`,description:step.operation,content:<div className="bv-ui-stack">
+1 -1
View File
@@ -39,7 +39,7 @@ export function loraV3TargetOptions(node:any):LoraV3TargetOption[]{
return [{value:"global",label:"Global",target:{scope:"global"}},...(document.regions??[]).map((region:any)=>({value:`region:${document.document_id}:${region.id}`,label:String(region.name||region.id),target:{scope:"region" as const,document_id:String(document.document_id),region_id:String(region.id)}}))];
}
export function loraV3Resolved(node:any,config:LoraV3Config){const linked=linkedLocalLoraCollector(node);return Boolean(linked)&&String(widget(linked,"collector_id")?.value??"")===String(config.collector_id??"");}
function commitLoraV3Config(node:any,next:LoraV3Config){
export function commitLoraV3Config(node:any,next:LoraV3Config){
const choice=loraV3Catalog(node).find(item=>item.id===next.collector_id);connectLoraConsumerTree(node,choice?.node??null);writeNodeLoraV3Config(node,next);return next;
}
export function setLoraV3Collector(node:any,config:LoraV3Config,collectorId:string){const choice=loraV3Catalog(node).find(item=>item.id===collectorId),resourceId=choice?.resources[0]?.id??"",map=(entry:any)=>entry.source.kind==="external"?{...entry,source:{...entry.source,resource_id:resourceId}}:entry;return commitLoraV3Config(node,{...config,collector_id:collectorId||null,entries:config.entries.map(map),steps:config.steps?.map(step=>({...step,entries:step.entries.map(map)}))});}