feat(modal): green ✓ / red ✗ verdict pair on suggestion rows
Replace the single "Accept" pill in the modal Suggestions rail with the eval card's green ✓ / red ✗ language: ✓ accepts the tag (positive), ✗ dismisses it for this image — which already persists a TagSuggestionRejection (hard negative the heads train on). The pair occupies ~the footprint of the old pill, so per-image rejection becomes a one-click peer of accepting instead of being buried in the kebab. Dismiss moves off the 3-dot menu, so the kebab now only carries alias actions and is hidden when none apply (centroid hits with no alias option). Toward #1134 (native per-image negatives in the rail). The bigger piece — heads as a suggestion source feeding this panel — is still ahead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ttrj5P7upUTueSfoJcxEqa
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- Chip-card row: visible border + hover/focus state unifies the
|
<!-- Chip-card row: visible border + hover/focus state unifies the
|
||||||
name, score, and action buttons as one "object" (operator-asked
|
name, score, and action buttons as one "object" (operator-asked
|
||||||
2026-06-01). The row itself is informational; the explicit
|
2026-06-01). The row itself is informational; the green ✓ / red ✗
|
||||||
Accept button + 3-dot menu are the action affordances. -->
|
verdict pair + 3-dot alias menu are the action affordances. -->
|
||||||
<div class="fc-suggestion">
|
<div class="fc-suggestion">
|
||||||
<span class="fc-suggestion__name">
|
<span class="fc-suggestion__name">
|
||||||
{{ suggestion.display_name }}
|
{{ suggestion.display_name }}
|
||||||
@@ -12,18 +12,32 @@
|
|||||||
:title="`Mapped from the tagger's “${suggestion.raw_name}” via an alias`">alias</span>
|
:title="`Mapped from the tagger's “${suggestion.raw_name}” via an alias`">alias</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="fc-suggestion__score">{{ scorePct }}</span>
|
<span class="fc-suggestion__score">{{ scorePct }}</span>
|
||||||
<v-btn
|
<!-- Green ✓ / red ✗ pair (operator-asked 2026-06-28) mirrors the eval
|
||||||
class="fc-suggestion__accept"
|
card's verdict buttons: ✓ accepts the tag (positive), ✗ dismisses it
|
||||||
size="small" variant="tonal" color="accent"
|
for this image (records a TagSuggestionRejection — a hard negative the
|
||||||
density="compact" rounded="pill"
|
heads train on). Together they occupy ~the footprint of the old single
|
||||||
:aria-label="`Accept ${suggestion.display_name}`"
|
Accept pill, so rejecting is now a one-click peer of accepting rather
|
||||||
@click="$emit('accept', suggestion)"
|
than buried in the kebab. -->
|
||||||
>
|
<div class="fc-suggestion__acts">
|
||||||
Accept
|
<button
|
||||||
</v-btn>
|
class="fc-act fc-act--yes" type="button"
|
||||||
|
:aria-label="`Accept ${suggestion.display_name}`"
|
||||||
|
:title="`Yes — tag ${suggestion.display_name}`"
|
||||||
|
@click="$emit('accept', suggestion)"
|
||||||
|
><v-icon size="16">mdi-check</v-icon></button>
|
||||||
|
<button
|
||||||
|
class="fc-act fc-act--no" type="button"
|
||||||
|
:aria-label="`Reject ${suggestion.display_name}`"
|
||||||
|
:title="`No — not ${suggestion.display_name}`"
|
||||||
|
@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
|
<!-- Modal-safe kebab is baked into KebabMenu (this row lives in the
|
||||||
teleported image modal — #711). -->
|
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
|
<KebabMenu
|
||||||
|
v-if="hasMenu"
|
||||||
class="fc-suggestion__menu" size="small" variant="outlined"
|
class="fc-suggestion__menu" size="small" variant="outlined"
|
||||||
:label="`More actions for ${suggestion.display_name}`"
|
:label="`More actions for ${suggestion.display_name}`"
|
||||||
>
|
>
|
||||||
@@ -42,9 +56,6 @@
|
|||||||
>
|
>
|
||||||
<v-list-item-title>Remove alias</v-list-item-title>
|
<v-list-item-title>Remove alias</v-list-item-title>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item @click="$emit('dismiss', suggestion)">
|
|
||||||
<v-list-item-title>Dismiss for this image</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
</KebabMenu>
|
</KebabMenu>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -57,6 +68,12 @@ const props = defineProps({ suggestion: { type: Object, required: true } })
|
|||||||
defineEmits(['accept', 'alias', 'remove-alias', 'dismiss'])
|
defineEmits(['accept', 'alias', 'remove-alias', 'dismiss'])
|
||||||
|
|
||||||
const scorePct = computed(() => `${Math.round(props.suggestion.score * 100)}%`)
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -104,11 +121,22 @@ const scorePct = computed(() => `${Math.round(props.suggestion.score * 100)}%`)
|
|||||||
color: rgb(var(--v-theme-on-surface-variant, var(--v-theme-on-surface)));
|
color: rgb(var(--v-theme-on-surface-variant, var(--v-theme-on-surface)));
|
||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: 'JetBrains Mono', monospace;
|
||||||
}
|
}
|
||||||
/* Vuetify's compact density doesn't shrink the tonal button enough
|
/* Green ✓ / red ✗ verdict pair — same circular language as the eval card
|
||||||
for a tight row; clamp the min-width so Accept stays compact. */
|
(TagEvalCard .fc-act) so accept/reject read identically across surfaces. */
|
||||||
.fc-suggestion__accept :deep(.v-btn__content) {
|
.fc-suggestion__acts {
|
||||||
font-size: 12px; letter-spacing: 0.02em;
|
flex: 0 0 auto; display: flex; gap: 4px;
|
||||||
}
|
}
|
||||||
|
.fc-act {
|
||||||
|
width: 26px; height: 26px; border-radius: 50%; border: none; cursor: pointer;
|
||||||
|
display: flex; align-items: center; justify-content: center; color: #fff;
|
||||||
|
opacity: 0.9; transition: transform 0.1s, opacity 0.1s;
|
||||||
|
}
|
||||||
|
.fc-act:hover { opacity: 1; transform: scale(1.1); }
|
||||||
|
.fc-act:focus-visible {
|
||||||
|
outline: 2px solid rgb(var(--v-theme-accent)); outline-offset: 1px;
|
||||||
|
}
|
||||||
|
.fc-act--yes { background: rgb(var(--v-theme-success)); }
|
||||||
|
.fc-act--no { background: rgb(var(--v-theme-error)); }
|
||||||
.fc-suggestion__menu {
|
.fc-suggestion__menu {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user