redact: UX overhaul — engine simplification, credits, spinner, save-to-docs, badges

- Remove GPU/regex engine options; keep only azure_mini (1 credit) and azure_full (2 credits)
- Variable credit cost: engine-aware pre-check and charge in api/redact.php; PricingCatalog base = 1
- Fix ATTORNEY not preserved when keepOfficials=true: add to LLM prompt, generic-tag, pseudonym regexes
- Replace Azure credits hint with per-engine credit cost text (all 4 languages)
- Single-file upload only (was: up to 5); simplify status messages
- Clear previous redaction output and show pulsing spinner when a new run starts
- Add "Save to My Docs" button in redact output panel (corpus-save.js path)
- corpus-save.js: capture source_doc_ids from button dataset, pass in POST payload
- api/save-to-corpus.php: accept source_doc_ids, store first as source_url=corpus-doc:{id}
- doc-picker.js: show "✂ Redacted" badge for documents saved from the redact tool
- CSS: .redact-working spinner, doc-item__badge--redact pill styles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 08:18:51 +02:00
parent a821d39dcd
commit 56cd87dd7b
10 changed files with 117 additions and 50 deletions
+16 -10
View File
@@ -41,6 +41,7 @@
}
function bodyFor(kind, payload) {
const sourceDocIds = (payload.sourceDocIds || '').split(',').map(s => s.trim()).filter(Boolean);
if (window.DBN_DASHBOARD) {
return JSON.stringify({
title: payload.title,
@@ -48,6 +49,7 @@
source_tool: payload.tool || 'dashboard-save',
tags: payload.tags,
kind,
...(sourceDocIds.length ? { source_doc_ids: sourceDocIds } : {}),
});
}
return JSON.stringify({
@@ -55,6 +57,7 @@
content: payload.content,
source_tool: payload.tool || '',
tags: payload.tags,
...(sourceDocIds.length ? { source_doc_ids: sourceDocIds } : {}),
});
}
@@ -74,9 +77,10 @@
}
_pendingBtn = btn;
dlg.dataset.pendingContent = content;
dlg.dataset.pendingTool = btn.dataset.tool || '';
dlg.dataset.pendingKind = 'tool_output';
dlg.dataset.pendingContent = content;
dlg.dataset.pendingTool = btn.dataset.tool || '';
dlg.dataset.pendingKind = 'tool_output';
dlg.dataset.pendingSourceDocIds = btn.dataset.sourceDocIds || '';
titleIn.value = btn.dataset.suggestedTitle || '';
tagsIn.value = '';
@@ -90,12 +94,13 @@
e.preventDefault();
dlg.close();
const btn = _pendingBtn;
const content = dlg.dataset.pendingContent || '';
const tool = dlg.dataset.pendingTool || '';
const kind = dlg.dataset.pendingKind || 'tool_output';
const title = titleIn.value.trim();
const tags = tagsIn.value.trim();
const btn = _pendingBtn;
const content = dlg.dataset.pendingContent || '';
const tool = dlg.dataset.pendingTool || '';
const kind = dlg.dataset.pendingKind || 'tool_output';
const sourceDocIds = dlg.dataset.pendingSourceDocIds || '';
const title = titleIn.value.trim();
const tags = tagsIn.value.trim();
if (!title || !content) return;
@@ -109,7 +114,7 @@
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: bodyFor(kind, { title, content, tool, tags }),
body: bodyFor(kind, { title, content, tool, tags, sourceDocIds }),
});
const data = await resp.json().catch(() => ({}));
@@ -146,6 +151,7 @@
delete dlg.dataset.pendingContent;
delete dlg.dataset.pendingTool;
delete dlg.dataset.pendingKind;
delete dlg.dataset.pendingSourceDocIds;
});
function showToast(msg, isError) {