feat(fc2b): AliasPickerDialog + TagRenameDialog

AliasPicker: debounced autocomplete scoped to the prediction's category
(artist/character map to tag kinds; copyright searches unscoped),
returns the chosen canonical tag id. RenameDialog: PATCH /api/tags/<id>,
surfaces 409 collisions with the FC-2c merge hint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 07:55:40 -04:00
parent a8cc6a27dc
commit 5ae2154e88
2 changed files with 121 additions and 0 deletions
@@ -0,0 +1,63 @@
<template>
<v-card>
<v-card-title>Treat as alias for</v-card-title>
<v-card-text>
<p class="text-caption mb-2">
Pick the existing tag the model's prediction should map to. All
future predictions of this name will resolve to your tag.
</p>
<v-autocomplete
v-model="selectedId"
:items="results"
:item-title="(t) => t.fandom_name ? `${t.name} — ${t.fandom_name}` : t.name"
:item-value="(t) => t.id"
:loading="loading"
label="Canonical tag"
no-filter clearable density="compact"
@update:search="onSearch"
/>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn @click="$emit('cancel')">Cancel</v-btn>
<v-btn
color="primary" rounded="pill" :disabled="!selectedId"
@click="$emit('confirm', selectedId)"
>Use this tag</v-btn>
</v-card-actions>
</v-card>
</template>
<script setup>
import { ref } from 'vue'
import { useApi } from '../../composables/useApi.js'
const props = defineProps({ category: { type: String, required: true } })
defineEmits(['confirm', 'cancel'])
const api = useApi()
const results = ref([])
const loading = ref(false)
const selectedId = ref(null)
let debounce = null
function onSearch(q) {
if (debounce) clearTimeout(debounce)
debounce = setTimeout(async () => {
const query = (q || '').trim()
if (!query) { results.value = []; return }
loading.value = true
try {
// Scope the autocomplete to the prediction's category where it maps
// to a tag kind. 'copyright' has no tag kind; search unscoped there.
const kind = ['artist', 'character'].includes(props.category)
? props.category : null
const params = { q: query, limit: 20 }
if (kind) params.kind = kind
results.value = await api.get('/api/tags/autocomplete', { params })
} finally {
loading.value = false
}
}, 200)
}
</script>