From 6cf82970a1f01cd5d44292b5ad8d392cec938486 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 15 May 2026 07:56:45 -0400 Subject: [PATCH] feat(fc2b): ml / allowlist / aliases Pinia stores Thin wrappers over the FC-2b API: ml (settings GET/PATCH, backfill + recompute triggers), allowlist (list, threshold patch, remove with optimistic local update), aliases (list, remove with optimistic update). Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/stores/aliases.js | 26 +++++++++++++++++++++ frontend/src/stores/allowlist.js | 30 ++++++++++++++++++++++++ frontend/src/stores/ml.js | 39 ++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 frontend/src/stores/aliases.js create mode 100644 frontend/src/stores/allowlist.js create mode 100644 frontend/src/stores/ml.js diff --git a/frontend/src/stores/aliases.js b/frontend/src/stores/aliases.js new file mode 100644 index 0000000..5872af7 --- /dev/null +++ b/frontend/src/stores/aliases.js @@ -0,0 +1,26 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { useApi } from '../composables/useApi.js' + +export const useAliasesStore = defineStore('aliases', () => { + const api = useApi() + const rows = ref([]) + const loading = ref(false) + + async function load() { + loading.value = true + try { rows.value = await api.get('/api/aliases') } + finally { loading.value = false } + } + + async function remove(aliasString, aliasCategory) { + await api.delete( + `/api/aliases/${encodeURIComponent(aliasString)}/${encodeURIComponent(aliasCategory)}` + ) + rows.value = rows.value.filter( + r => !(r.alias_string === aliasString && r.alias_category === aliasCategory) + ) + } + + return { rows, loading, load, remove } +}) diff --git a/frontend/src/stores/allowlist.js b/frontend/src/stores/allowlist.js new file mode 100644 index 0000000..78db284 --- /dev/null +++ b/frontend/src/stores/allowlist.js @@ -0,0 +1,30 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { useApi } from '../composables/useApi.js' + +export const useAllowlistStore = defineStore('allowlist', () => { + const api = useApi() + const rows = ref([]) + const loading = ref(false) + + async function load() { + loading.value = true + try { rows.value = await api.get('/api/allowlist') } + finally { loading.value = false } + } + + async function updateThreshold(tagId, minConfidence) { + await api.patch(`/api/tags/${tagId}/allowlist`, { + body: { min_confidence: minConfidence } + }) + const r = rows.value.find(x => x.tag_id === tagId) + if (r) r.min_confidence = minConfidence + } + + async function remove(tagId) { + await api.delete(`/api/tags/${tagId}/allowlist`) + rows.value = rows.value.filter(x => x.tag_id !== tagId) + } + + return { rows, loading, load, updateThreshold, remove } +}) diff --git a/frontend/src/stores/ml.js b/frontend/src/stores/ml.js new file mode 100644 index 0000000..1323268 --- /dev/null +++ b/frontend/src/stores/ml.js @@ -0,0 +1,39 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { useApi } from '../composables/useApi.js' + +export const useMLStore = defineStore('ml', () => { + const api = useApi() + const settings = ref(null) + const loading = ref(false) + const error = ref(null) + + async function loadSettings() { + loading.value = true + error.value = null + try { + settings.value = await api.get('/api/ml/settings') + } catch (e) { + error.value = e.message + } finally { + loading.value = false + } + } + + async function patchSettings(patch) { + settings.value = await api.patch('/api/ml/settings', { body: patch }) + } + + async function triggerBackfill() { + await api.post('/api/ml/backfill') + } + + async function triggerRecomputeCentroids() { + await api.post('/api/ml/recompute-centroids') + } + + return { + settings, loading, error, + loadSettings, patchSettings, triggerBackfill, triggerRecomputeCentroids + } +})