mirror of
https://github.com/civitai/civitai.git
synced 2026-09-20 22:08:18 +08:00
fix(training-studio): close the review's labeling-race and balance gaps
Five of Copilot's six findings were real:
- A missing/non-numeric balance from getBuzzAccount now returns null from
the embed's getBuzzBalances instead of coercing to 0 — blue:0 asserted
the whole price was non-Blue with certainty, defeating the fail-safe
"up to" confirmation the unknown-balance path exists for.
- sourceLabel keeps the ARRIVAL text untrimmed (zip .txt and reused
captions), as the switch dialog promises.
- Un-captioned reuse items become labelable only once hydration fills
blobUrl — the drain runs again after hydration, restoring the pre-lazy
behavior (failed hydration still degrades to the manual editor).
- A drain result resolving between abort and delivery is dropped, so a
mode switch can't receive an old-mode label into a freshly reset tile.
- An aborted drain's finally no longer clears the replacement drain's
progress counter — slot and labelRun release only under the identity
guard.
The sixth (getBuzzAccount "returns an array") is wrong — the router's
getUserBuzzAccounts reduces rows into a {clientType: balance} record,
which is exactly how useQueryBuzz consumes it (initialData[type]).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DV3Ku4Eu9qTtzd61zt19dZ
This commit is contained in:
@@ -289,7 +289,8 @@
|
||||
blobId: item.blobId,
|
||||
blobUrl: item.url,
|
||||
...parseLabel(label, labelMode),
|
||||
sourceLabel: label || undefined,
|
||||
// Verbatim — the switch logic promises to restore the ARRIVAL text exactly.
|
||||
sourceLabel: item.caption || undefined,
|
||||
labelTried: label ? true : undefined,
|
||||
};
|
||||
});
|
||||
@@ -321,6 +322,10 @@
|
||||
}
|
||||
patch(t.id, { previewUrl: url, blobUrl: url });
|
||||
});
|
||||
// Un-captioned reuse items only become labelable once `blobUrl` exists — the add-time drain
|
||||
// saw nothing to do, so run it again now (a failed hydration leaves the manual-label editor
|
||||
// as the fallback, same as before).
|
||||
void ensureLabeling();
|
||||
}
|
||||
|
||||
// Video models train on stills too (the on-site trainer has always accepted images for video
|
||||
@@ -387,7 +392,8 @@
|
||||
status: 'uploading' as const,
|
||||
progress: 0,
|
||||
...parseLabel(label, labelMode),
|
||||
sourceLabel: label || undefined,
|
||||
// Verbatim (untrimmed) — the switch logic promises to restore the file's text exactly.
|
||||
sourceLabel: e.caption || undefined,
|
||||
labelTried: label ? true : undefined,
|
||||
};
|
||||
});
|
||||
@@ -428,16 +434,28 @@
|
||||
const items = targets.map((t) => ({ key: String(t.id), mediaUrl: t.blobUrl! }));
|
||||
labelRun = { total: targets.length, done: 0, keys: new Set(items.map((i) => i.key)) };
|
||||
for (const t of targets) patch(t.id, { labeling: true });
|
||||
await runAutoLabel(labelMode, media, items, applyLabel, controller.signal);
|
||||
// A result that resolves between abort and delivery must be dropped — post-switch it
|
||||
// would write an OLD-mode label into a freshly reset tile and mark it done.
|
||||
await runAutoLabel(
|
||||
labelMode,
|
||||
media,
|
||||
items,
|
||||
(r) => {
|
||||
if (!controller.signal.aborted) applyLabel(r);
|
||||
},
|
||||
controller.signal
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
if (!isAbort(err)) for (const i of images) if (i.labeling) patch(i.id, { labeling: false });
|
||||
} finally {
|
||||
// Only release the slot if it's still OURS — an aborted drain's finally runs after the
|
||||
// aborter has already started a replacement, and nulling that one would let a third drain
|
||||
// run concurrently with it.
|
||||
if (labelController === controller) labelController = null;
|
||||
labelRun = null;
|
||||
// Only release the slot/progress if they're still OURS — an aborted drain's finally runs
|
||||
// after the aborter has already started a replacement, and clearing that one's state would
|
||||
// let a third drain start concurrently (or blank the live progress counter).
|
||||
if (labelController === controller) {
|
||||
labelController = null;
|
||||
labelRun = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,12 +145,12 @@ function TrainingStudioEmbed({ orchestratorMode }: { orchestratorMode: 'dev' | '
|
||||
string,
|
||||
number
|
||||
>;
|
||||
const num = (v: unknown) => (typeof v === 'number' ? v : 0);
|
||||
return {
|
||||
yellow: num(accounts.yellow),
|
||||
green: num(accounts.green),
|
||||
blue: num(accounts.blue),
|
||||
};
|
||||
const { yellow, green, blue } = accounts;
|
||||
// A missing/non-numeric balance must stay UNKNOWN (null → the element's fail-safe
|
||||
// "up to" confirmation), not read as a known zero — blue:0 would assert the whole
|
||||
// price is non-Blue with certainty.
|
||||
if ([yellow, green, blue].some((v) => typeof v !== 'number')) return null;
|
||||
return { yellow, green, blue };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user