fix(aliases): store modal alias under raw model key + make aliases visible/manageable
The headline bug: aliases created from the modal NEVER resolved. Create
sent the normalized display name ('Sword', 'Uchiha Sasuke') while
resolution keys on the raw booru model key ('sword', 'uchiha_sasuke',
case-sensitive) — so the mapping was stored under a key nothing looks up,
and the prediction kept reappearing unaliased. The raw key wasn't even in
the /suggestions response, so the modal couldn't send it.
- Suggestion now carries raw_name (the model key an alias must use) and
via_alias (surfaced via an operator alias); both serialized by the API.
- Modal alias-create sends raw_name, not display_name (the fix). Aliased
suggestions show an 'alias' badge and a 'Remove alias' action; 'Treat as
alias for…' is hidden for centroid hits (no model key) and already-aliased
rows.
- Tag-side management: TagCard ⋮ → 'Aliases…' opens a dialog listing the
model keys that fold into a tag, with remove (GET /api/tags/<id>/aliases +
AliasService.list_for_tag). Creation stays in the modal suggestion flow.
Tests: full API round-trip locking the raw-key contract (raw_name exposed →
alias authored with it → resolves + via_alias on a later image);
list_for_tag (service + API); via_alias/raw_name on the existing service
suggestion tests. No migration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -117,9 +117,15 @@ export const useSuggestionsStore = defineStore('suggestions', () => {
|
||||
async function aliasAccept(suggestion, canonicalTagId) {
|
||||
const imageId = currentImageId
|
||||
if (imageId == null) return
|
||||
// The alias MUST be stored under the raw model key — resolution looks up the
|
||||
// raw prediction key, not the normalized display name. Sending display_name
|
||||
// (the old bug) stored an alias that never resolved, so the prediction kept
|
||||
// reappearing unaliased. raw_name is null only for centroid hits, which
|
||||
// can't be aliased (the UI hides the action for them).
|
||||
const aliasString = suggestion.raw_name ?? suggestion.display_name
|
||||
await api.post(`/api/images/${imageId}/suggestions/alias`, {
|
||||
body: {
|
||||
alias_string: suggestion.display_name,
|
||||
alias_string: aliasString,
|
||||
alias_category: suggestion.category,
|
||||
canonical_tag_id: canonicalTagId
|
||||
}
|
||||
@@ -133,6 +139,22 @@ export const useSuggestionsStore = defineStore('suggestions', () => {
|
||||
})
|
||||
}
|
||||
|
||||
// Remove the alias behind an aliased suggestion (the raw prediction reverts to
|
||||
// its unaliased form on reload). The canonical tag stays applied if it was
|
||||
// accepted — this only undoes the model-key→tag mapping.
|
||||
async function removeAlias(suggestion) {
|
||||
const imageId = currentImageId
|
||||
if (imageId == null || suggestion.raw_name == null) return
|
||||
await api.delete(
|
||||
`/api/aliases/${encodeURIComponent(suggestion.raw_name)}/${encodeURIComponent(suggestion.category)}`
|
||||
)
|
||||
if (currentImageId === imageId) {
|
||||
await load(imageId)
|
||||
await loadAll(imageId)
|
||||
}
|
||||
toast({ text: `Alias removed: ${suggestion.display_name}`, type: 'success' })
|
||||
}
|
||||
|
||||
async function dismiss(suggestion) {
|
||||
const imageId = currentImageId
|
||||
if (imageId == null) return
|
||||
@@ -151,6 +173,6 @@ export const useSuggestionsStore = defineStore('suggestions', () => {
|
||||
|
||||
return {
|
||||
byCategory, allByCategory, loading, error,
|
||||
load, loadAll, accept, aliasAccept, dismiss
|
||||
load, loadAll, accept, aliasAccept, removeAlias, dismiss
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user