feat(series): Suggestions tab + matcher controls — frontend (FC-6.3)
Completes FC-6.3 with the UI. - SeriesView gains tabs: Browse (the existing grid) + Suggestions. - Suggestions tab: pending matches as rows (post → series, per-signal strength chips, score), Add (→ chapter) / Skip (→ dismiss); a "Matching on" toggle and a threshold field (both DB-backed via /settings/import), and a Rescan button that enqueues the background matcher. - seriesSuggestions store wires load / accept / dismiss / rescan / settings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { useApi } from '../composables/useApi.js'
|
||||
import { useAsyncAction } from '../composables/useAsyncAction.js'
|
||||
import { toast } from '../utils/toast.js'
|
||||
|
||||
// Backs the Series → Suggestions tab (FC-6.3). The matcher is confirm-only:
|
||||
// accept turns a suggestion into a chapter; skip dismisses it. enabled +
|
||||
// threshold are the operator-tunable knobs (DB-backed via /settings/import).
|
||||
export const useSeriesSuggestionsStore = defineStore('seriesSuggestions', () => {
|
||||
const api = useApi()
|
||||
const suggestions = ref([])
|
||||
const enabled = ref(true)
|
||||
const threshold = ref(0.5)
|
||||
const { loading, error, run } = useAsyncAction({ errorAs: 'message' })
|
||||
|
||||
async function load() {
|
||||
await run(async () => {
|
||||
const body = await api.get('/api/series/suggestions')
|
||||
suggestions.value = body.suggestions || []
|
||||
})
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
const s = await api.get('/api/settings/import')
|
||||
enabled.value = s.series_suggest_enabled
|
||||
threshold.value = s.series_suggest_threshold
|
||||
}
|
||||
|
||||
async function saveSettings(patch) {
|
||||
await api.patch('/api/settings/import', { body: patch })
|
||||
}
|
||||
|
||||
async function setEnabled(v) {
|
||||
enabled.value = v
|
||||
await saveSettings({ series_suggest_enabled: v })
|
||||
}
|
||||
|
||||
async function setThreshold(v) {
|
||||
threshold.value = v
|
||||
await saveSettings({ series_suggest_threshold: v })
|
||||
}
|
||||
|
||||
async function accept(id) {
|
||||
try {
|
||||
await api.post(`/api/series/suggestions/${id}/accept`, {})
|
||||
suggestions.value = suggestions.value.filter(s => s.id !== id)
|
||||
toast({ text: 'Added to series', type: 'success' })
|
||||
} catch (e) {
|
||||
toast({ text: `Accept failed: ${e.message}`, type: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
async function dismiss(id) {
|
||||
try {
|
||||
await api.post(`/api/series/suggestions/${id}/dismiss`, {})
|
||||
suggestions.value = suggestions.value.filter(s => s.id !== id)
|
||||
} catch (e) {
|
||||
toast({ text: `Skip failed: ${e.message}`, type: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
async function rescan() {
|
||||
await api.post('/api/series/suggestions/rescan', {})
|
||||
toast({
|
||||
text: 'Rescan queued — runs in the background; reload to see new matches.',
|
||||
type: 'info'
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
suggestions, enabled, threshold, loading, error,
|
||||
load, loadSettings, setEnabled, setThreshold, accept, dismiss, rescan
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user