diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index e8ffb56..ace04fa 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -27,10 +27,14 @@ const kbWritePathEnabled = ref(true); // code embeddings sit on a much higher similarity floor than prose, so 0.55 let // unrelated code through (#2223). Shares top-k, not the threshold. const kbWritePathThreshold = ref("0.68"); -// Near-duplicate report floor. Deliberately looser than the 0.90 write-time -// gate: that one BLOCKS a create and must be unforgiving of noise, this one only -// suggests a merge the operator reviews (services/dedup.py). -const kbDuplicateThreshold = ref("0.82"); +// Near-duplicate report floors, one per record kind (services/dedup.py). +// Snippets are single-chunk, so their floor sits below the 0.90 write-time +// gate and catches what it lets through. Notes/tasks are scored at chunk +// grain (#280) — related families clear 0.90 easily — so their floor sits +// above the gate to keep the report pointed at genuinely-alike records. +const kbDupThresholdSnippet = ref("0.82"); +const kbDupThresholdNote = ref("0.93"); +const kbDupThresholdTask = ref("0.93"); const savingKbInject = ref(false); const kbInjectSaved = ref(false); @@ -76,16 +80,20 @@ async function saveRetention() { async function saveKbInject() { const t = Math.min(1, Math.max(0, Number(kbInjectThreshold.value) || 0)); const k = Math.min(10, Math.max(1, Math.floor(Number(kbInjectTopK.value) || 1))); - // `|| 0.82` not `|| 0`: an unparseable value here should fall back to the - // default, not to 0 — a 0 floor would report every snippet as a duplicate of - // every other one. - const dupT = Math.min(1, Math.max(0, Number(kbDuplicateThreshold.value) || 0.82)); - // Same `|| default` reasoning as dupT: falling back to 0 would surface every + // `|| default` not `|| 0`: an unparseable value here should fall back to the + // per-kind default, not to 0 — a 0 floor would report every record as a + // duplicate of every other one. + const dupSnip = Math.min(1, Math.max(0, Number(kbDupThresholdSnippet.value) || 0.82)); + const dupNote = Math.min(1, Math.max(0, Number(kbDupThresholdNote.value) || 0.93)); + const dupTask = Math.min(1, Math.max(0, Number(kbDupThresholdTask.value) || 0.93)); + // Same `|| default` reasoning: falling back to 0 would surface every // snippet in the corpus on every edit, which is the failure this knob fixes. const wpT = Math.min(1, Math.max(0, Number(kbWritePathThreshold.value) || 0.68)); kbInjectThreshold.value = String(t); kbInjectTopK.value = String(k); - kbDuplicateThreshold.value = String(dupT); + kbDupThresholdSnippet.value = String(dupSnip); + kbDupThresholdNote.value = String(dupNote); + kbDupThresholdTask.value = String(dupTask); kbWritePathThreshold.value = String(wpT); savingKbInject.value = true; kbInjectSaved.value = false; @@ -99,7 +107,9 @@ async function saveKbInject() { // measurements that split them. kb_writepath_enabled: kbWritePathEnabled.value ? 'true' : 'false', kb_writepath_threshold: String(wpT), - kb_duplicate_threshold: String(dupT), + kb_duplicate_threshold_snippet: String(dupSnip), + kb_duplicate_threshold_note: String(dupNote), + kb_duplicate_threshold_task: String(dupTask), }); kbInjectSaved.value = true; setTimeout(() => (kbInjectSaved.value = false), 2000); @@ -487,8 +497,14 @@ onMounted(async () => { if (allSettings.kb_writepath_threshold !== undefined) { kbWritePathThreshold.value = allSettings.kb_writepath_threshold; } - if (allSettings.kb_duplicate_threshold !== undefined) { - kbDuplicateThreshold.value = allSettings.kb_duplicate_threshold; + if (allSettings.kb_duplicate_threshold_snippet !== undefined) { + kbDupThresholdSnippet.value = allSettings.kb_duplicate_threshold_snippet; + } + if (allSettings.kb_duplicate_threshold_note !== undefined) { + kbDupThresholdNote.value = allSettings.kb_duplicate_threshold_note; + } + if (allSettings.kb_duplicate_threshold_task !== undefined) { + kbDupThresholdTask.value = allSettings.kb_duplicate_threshold_task; } if (allSettings.notify_task_reminders !== undefined) { notifyTaskReminders.value = allSettings.notify_task_reminders !== "false"; @@ -1267,10 +1283,10 @@ function formatUserDate(iso: string): string { is for (#274). -->
- +
+ +
+ + +

+ The floor for the Knowledge page's note report. Notes are compared + section by section, so related records — a run of dev-logs, notes on + one topic — score high without being duplicates. Stricter than the + snippet floor on purpose; lower it to browse related families rather + than hunt true duplicates. +

+
+ +
+ + +

+ Same as the note floor, for the task report. Step tasks from different + milestones ("Verify on CI") legitimately resemble each other, so this + stays strict to keep the report pointed at work opened twice. +

+