refactor(tags): unify suggestion source — one canonical DB-tag dropdown, drop dead raw/alias machinery (#154)
Every tag suggestion is a canonical DB tag now (tagging-v2 #114: heads + CCIP score EXISTING concept tags). The pre-heads apparatus for model-predicted tags that didn't exist in the DB — creates_new_tag / raw_name / via_alias, the /suggestions/alias endpoint + add_alias_and_accept, AliasPickerDialog, and the store's aliasAccept/removeAlias — was dead and is removed. The type-to-add dropdown was TWO row sources (server autocomplete + the image's ML suggestions) merged with a dedup that dropped the %-bearing suggestion row when the debounced server hit landed — the operator's "confidence % flickers then vanishes". Now it's ONE list of DB-tag matches, each annotated with the model's confidence (join by canonical_tag_id) when the tag was scored for this image. No dedup, no flicker; picking a suggested tag still records acceptance via TagPanel.findPending. Single per-image fetch: score_image now reports above_threshold per row (computed vs the head's own suggest cut, separate from the inclusion floor), so the rail makes ONE min=0 request and derives the panel (above_threshold) and the dropdown (all, text-filtered) client-side — the two /suggestions calls collapse to one. Manual "Create 'X' as <kind>" (novel typed names) is unchanged; the alias table + tag-side alias admin + auto-apply alias matching are untouched. Tests: gate/serializer assertions updated (above_threshold; dropped dead-field + alias-endpoint checks); frontend spec seeds via the single load and covers the byCategory/aboveByCategory split. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CgZP9v2otxVJymiYsnVuMy
This commit is contained in:
@@ -11,10 +11,6 @@
|
||||
{{ suggestion.display_name }}
|
||||
<span v-if="suggestion.rejected" class="fc-suggestion__rejected-tag"
|
||||
title="You rejected this for this image — un-reject to recover">rejected</span>
|
||||
<span v-else-if="suggestion.creates_new_tag" class="fc-suggestion__new"
|
||||
title="No matching tag yet — accepting creates it">+ new</span>
|
||||
<span v-else-if="suggestion.via_alias" class="fc-suggestion__alias"
|
||||
:title="`Mapped from the tagger's “${suggestion.raw_name}” via an alias`">alias</span>
|
||||
</span>
|
||||
<span class="fc-suggestion__score">{{ scorePct }}</span>
|
||||
<!-- Green ✓ / red ✗ pair (operator-asked 2026-06-28) mirrors the eval
|
||||
@@ -46,40 +42,14 @@
|
||||
@click="$emit('dismiss', suggestion)"
|
||||
><v-icon size="16">mdi-close</v-icon></button>
|
||||
</div>
|
||||
<!-- Modal-safe kebab is baked into KebabMenu (this row lives in the
|
||||
teleported image modal — #711). Only rendered when an alias action
|
||||
applies — dismiss now lives on the red ✗, so a centroid hit with no
|
||||
alias option has no menu. -->
|
||||
<KebabMenu
|
||||
v-if="hasMenu"
|
||||
class="fc-suggestion__menu" size="small" variant="outlined"
|
||||
:label="`More actions for ${suggestion.display_name}`"
|
||||
>
|
||||
<!-- Alias is a tagger-prediction remap, so only offer it for tagger
|
||||
suggestions with a raw model key that aren't already aliased.
|
||||
Centroid hits (raw_name null) have nothing to alias. -->
|
||||
<v-list-item
|
||||
v-if="suggestion.raw_name && !suggestion.via_alias"
|
||||
@click="$emit('alias', suggestion)"
|
||||
>
|
||||
<v-list-item-title>Treat as alias for…</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
v-if="suggestion.via_alias"
|
||||
@click="$emit('remove-alias', suggestion)"
|
||||
>
|
||||
<v-list-item-title>Remove alias</v-list-item-title>
|
||||
</v-list-item>
|
||||
</KebabMenu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, inject } from 'vue'
|
||||
import KebabMenu from '../common/KebabMenu.vue'
|
||||
|
||||
const props = defineProps({ suggestion: { type: Object, required: true } })
|
||||
defineEmits(['accept', 'alias', 'remove-alias', 'dismiss', 'undismiss'])
|
||||
defineEmits(['accept', 'dismiss', 'undismiss'])
|
||||
|
||||
// #1206: on hover, tell the image viewer which crop produced this suggestion so
|
||||
// it highlights that region. Provided by ImageViewer/Explore; a no-op elsewhere.
|
||||
@@ -92,12 +62,6 @@ function onLeave () {
|
||||
}
|
||||
|
||||
const scorePct = computed(() => `${Math.round(props.suggestion.score * 100)}%`)
|
||||
// Kebab now only carries alias actions: show it when this suggestion can be
|
||||
// aliased (raw model key, not yet aliased) or is already aliased (so it can be
|
||||
// un-aliased). Centroid hits (no raw_name, no alias) have an empty menu → hide.
|
||||
const hasMenu = computed(() =>
|
||||
Boolean(props.suggestion.raw_name) || Boolean(props.suggestion.via_alias)
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -119,26 +83,6 @@ const hasMenu = computed(() =>
|
||||
color: rgb(var(--v-theme-on-surface));
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
}
|
||||
.fc-suggestion__new {
|
||||
display: inline-block;
|
||||
font-size: 10px; font-weight: 600;
|
||||
color: rgb(var(--v-theme-accent));
|
||||
background: rgba(var(--v-theme-accent), 0.12);
|
||||
border: 1px solid rgb(var(--v-theme-accent), 0.4);
|
||||
padding: 1px 6px; border-radius: 999px;
|
||||
margin-left: 6px;
|
||||
text-transform: uppercase; letter-spacing: 0.04em;
|
||||
}
|
||||
.fc-suggestion__alias {
|
||||
display: inline-block;
|
||||
font-size: 10px; font-weight: 600;
|
||||
color: rgb(var(--v-theme-on-surface-variant));
|
||||
background: rgb(var(--v-theme-surface-light));
|
||||
border: 1px solid rgb(var(--v-theme-surface-light));
|
||||
padding: 1px 6px; border-radius: 999px;
|
||||
margin-left: 6px;
|
||||
text-transform: uppercase; letter-spacing: 0.04em;
|
||||
}
|
||||
.fc-suggestion__score {
|
||||
flex: 0 0 auto; min-width: 38px; text-align: right;
|
||||
font-size: 11px;
|
||||
@@ -166,10 +110,6 @@ const hasMenu = computed(() =>
|
||||
background: transparent; color: rgb(var(--v-theme-on-surface-variant));
|
||||
border: 1px solid rgb(var(--v-theme-on-surface-variant), 0.5);
|
||||
}
|
||||
.fc-suggestion__menu {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/* Rejected state: the row stays put (recovery), dimmed + red-edged so it
|
||||
reads as "handled, negative" without shouting over live suggestions. */
|
||||
.fc-suggestion--rejected {
|
||||
|
||||
Reference in New Issue
Block a user