feat(redact): tag highlighting, inventory panel, before/after toggle, gpt-4o upgrade
- CSS: colour-coded [TAG] spans by entity type (person=pink, org=blue, place=green, date=amber, id=purple) - Inventory panel: collapsible list showing tag → original text mappings with occurrence counts, sourced from new redaction_map API response key - Before/after toggle: Redacted / Original view-switch buttons wired to lastOriginalText captured at submission time - One-click gpt-4o upgrade button when mini or GPU engine was used - Backend: redaction_map built from applied LLM entities (tag → originals + occurrence count via substr_count on final text) - renderResults now calls setupRedactViewToggle() after DOM is written Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -512,6 +512,7 @@ PROMPT;
|
||||
$finalRedacted = $preRedacted;
|
||||
$pass2Counts = [];
|
||||
$llmDeployment = null;
|
||||
$redactionMap = [];
|
||||
|
||||
$llmResult = $this->llmRedactionPass(
|
||||
$preRedacted, $language, $aliases, $engine,
|
||||
@@ -527,6 +528,7 @@ PROMPT;
|
||||
$entities = $llmResult['entities'] ?? [];
|
||||
$llmDeployment = $llmResult['deployment'] ?? null;
|
||||
$applied = 0;
|
||||
$redactionMap = [];
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
if (!is_array($entity)) {
|
||||
@@ -549,14 +551,32 @@ PROMPT;
|
||||
$finalRedacted = $replaced;
|
||||
$pass2Counts[$type] = ($pass2Counts[$type] ?? 0) + 1;
|
||||
$applied++;
|
||||
if (!isset($redactionMap[$tag])) {
|
||||
$redactionMap[$tag] = ['originals' => [], 'type' => $type];
|
||||
}
|
||||
if (!in_array($original, $redactionMap[$tag]['originals'], true)) {
|
||||
$redactionMap[$tag]['originals'][] = $original;
|
||||
}
|
||||
} elseif (str_contains($finalRedacted, $original)) {
|
||||
// Fallback for names adjacent to punctuation or non-word characters
|
||||
$finalRedacted = str_replace($original, $tag, $finalRedacted);
|
||||
$pass2Counts[$type] = ($pass2Counts[$type] ?? 0) + 1;
|
||||
$applied++;
|
||||
if (!isset($redactionMap[$tag])) {
|
||||
$redactionMap[$tag] = ['originals' => [], 'type' => $type];
|
||||
}
|
||||
if (!in_array($original, $redactionMap[$tag]['originals'], true)) {
|
||||
$redactionMap[$tag]['originals'][] = $original;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add occurrence counts by scanning the final text
|
||||
foreach ($redactionMap as $tag => &$entry) {
|
||||
$entry['occurrences'] = substr_count($finalRedacted, $tag);
|
||||
}
|
||||
unset($entry);
|
||||
|
||||
$pass2Detail = $applied > 0
|
||||
? "{$applied} additional: " . implode(', ', array_map(fn($k, $v) => "{$k}: {$v}", array_keys($pass2Counts), $pass2Counts))
|
||||
: 'no additional entities found';
|
||||
@@ -592,6 +612,7 @@ PROMPT;
|
||||
'redacted_text' => $finalRedacted,
|
||||
'detected_entity_categories' => $categories,
|
||||
'entity_counts' => $allCounts,
|
||||
'redaction_map' => $redactionMap,
|
||||
'evidence_trail' => [['title' => 'Pasted text', 'excerpt' => 'Processed in-memory only; not stored.']],
|
||||
'what_remains_uncertain' => ['Human review is still recommended for contextual identification.'],
|
||||
'next_practical_step' => 'Review the output and rerun in strict mode if the text will be shared broadly.',
|
||||
|
||||
Reference in New Issue
Block a user