- renderTimeline(): group consecutive same-date+actor events into one card
with a bullet list; single events keep their current layout
- Date format: YYYY-MM-DD → "1 Jun 2023" (3-letter month, international)
- Time shown in header when available
- Remove date_type badge; confidence badge replaced by amber ⚠ flag on
low-confidence events only (high/medium border colour still shows)
- LegalTools.php: resolve azure_full/azure_mini to Bedrock Sonnet/Haiku
when DbnBedrockGateway is active; claude_sonnet/claude_haiku also handled
- timeline.php + api/timeline.php: engine labels updated (Claude Haiku/Sonnet);
claude_haiku + claude_sonnet added to valid engine list
- i18n engine labels updated in all 4 languages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove GPU/cuttlefish engine from timeline.php, api/timeline.php, LegalTools.php, tools.js (all 4 langs)
- Add engine-aware credit cost: gpt-4o-mini=1 credit, gpt-4o=2 credits (matches redact pattern)
- Remove multiple attribute from file input (single document only)
- New api/timeline-stream.php: SSE endpoint emitting status events + final result
- New api/timeline-download.php: DOCX export of timeline events
- LegalTools::timeline() gains ?callable $onProgress for live status updates
- tools.js: spinner on run, SSE streaming fetch, Export to Word button
- Save to My Docs was already wired (showSaveResultButton at line 1136)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove js-save-corpus button from redact output (was failing with 'no_workspace'
for users without a linked CaveauAI workspace)
- Single save path now goes through showSaveResultButton() → api/case/save-result.php,
which works for all paid (Plus/Pro) users without workspace dependency
- Relabel 'Save result' → 'Save to My Docs' and update success message
- Fix DOCX: contentTypesXml() had wrong ContentType for docProps/core.xml
(application/package/... → application/vnd.openxmlformats-package.core-properties+xml);
Word validates this strictly and was rejecting the file
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
Add doc_picker_btn/audio_picker_btn to i18n.php (en/no/uk/pl), add
docPickerBtn to REDACT_I18N + TIMELINE_I18N and audioPickerBtn to
TRANSCRIBE_I18N in tools.js. PHP-render picker labels in legal-analysis,
translate, summarize, tool_form, and layout_footer using dbnToolsT().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The tool now respects the chosen UI language end-to-end — even if the
source document is Norwegian, a user on EN/UK/PL gets the analysis in
their language. Norwegian statute references (barnevernsloven § 4-25,
EMK Art. 8) and case names (Strand Lobben mot Norge 37283/13) are kept
verbatim because they are proper nouns.
LLM (LegalAnalysisAgent.php):
- extractIssues: prompt asks for question + brief_context in user's
language; statute refs preserved
- answerIssue: Norwegian core system prompt (keeps fine-tune precision)
+ language-coercion line for non-NO; localised context/source labels
- synthesise: overall_assessment, next_steps, disclaimer in user's
language; explicit per-language disclaimer text
- runFullAnalysis empty-case fallback also localised
- what_to_check translated per language
UI:
- 40 new la_* translation keys in i18n.php × 4 languages (NO/EN/UK/PL)
- legal-analysis.php: 4-way lang switcher, dbnToolsT() for every label,
emits window.DBN_LA_I18N for runtime JS strings
- legal-analysis.js: t() helper reads from window.DBN_LA_I18N
- layout_footer.php: emits window.DBN_CURRENT_LANG +
window.DBN_ADDON_I18N so the legal-analysis add-on button works in
the page's language no matter which tool it's invoked from
- tools.js add-on: reads from DBN_ADDON_I18N, passes DBN_CURRENT_LANG
to /api/legal-analysis.php so server responds in same language
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same defensive guard as legal-analysis + summarize, now applied to the
upload-zone and audio-zone handlers in tools.js. Affects redact,
timeline, and transcribe (which all share these zones via tools.js's
setupUpload / setupAudioUpload). Stops native label-for clicks and the
input's own click from bubbling into the zone handler that would
otherwise programmatically re-open the picker.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Restores the dbn-legal-agent-v3 fine-tune on ocelot (was silently aliased
to plain qwen2.5:14b in LiteLLM since the viper retirement) and ships a
new tool that uses it via a two-pass flow:
Pass 1 (Azure 4o-mini) → extract up to 5 distinct legal issues
Pass 2 (ocelot v3 only) → answer each issue, ≤350 tokens, with corpus
Pass 3 (Azure 4o-mini) → synthesise overall assessment + next steps
The 12GB-VRAM constraint motivates the split: dbn-legal-agent-v3 stays
hot in VRAM through the 5 sequential per-issue calls because issue
extraction and synthesis run on Azure, not on ocelot.
New surface:
- includes/LegalAnalysisAgent.php
- api/legal-analysis.php (NDJSON streaming endpoint)
- legal-analysis.php (dedicated tool page)
- assets/js/legal-analysis.js (streamed UI with per-issue cards)
- Save-result + case-result.php rendering for legal-analysis output
- Nav registration in all four UI languages
Add-on integration: a "⚖️🇳🇴 Run deep legal analysis on this text"
button now appears on Summarize, Ask, and Redact result pages and
streams the same pipeline inline below the existing result.
Existing tools relabelled: the misleading "🇳🇴 Norwegian specialist v3 ⭐"
option on advocate/deep-research/discrepancy/barnevernet is now honestly
"DBN Legal Agent" — now that the real fine-tune is actually deployed,
the label finally matches reality. The advocate.php v2 option was
removed since the v2 GGUF is retired.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All tool results can now be saved to My Case manually. Users click
'Save result', type a description, and confirm. This replaces the
previous silent auto-save on barnevernet/timeline/etc., giving users
control over what stays and what it's called (supports multiple runs
of the same tool with different titles).
- CaseResults: extend ELIGIBLE_TOOLS to include summarize, ask, redact,
transcribe; add toolLabel/toolIcon entries; support explicit title
via meta['title'] in save()
- api/case/save-result.php: new client-initiated save endpoint;
accepts tool + title + input_payload + output_payload + meta
- Remove CaseResults::save() auto-save from barnevernet, deep-research,
discrepancy, korrespond, timeline API endpoints
- tools.js: add showSaveResultButton() (exposed as window.dbnShowSaveResultButton);
wire for ask, redact, timeline, transcribe (both file-upload and
stored-audio paths)
- barnevernet.js: wire save button after final result render
- summarize.js: wire save button after renderFinal(); passes sumResults
container so widget appears in the correct #sumResults div
- case-result.php: rich tool-specific rendering for summarize, ask,
redact, transcribe, timeline; update re-run link map to include all
new tools
- tools.css: styles for .save-result-widget and its states (idle,
prompt, done, error)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add "Select from My Docs" button to all text tool forms; free-tier
users see an upgrade modal, paid (CaveauAI) users get a searchable
multi-select modal backed by /api/dashboard/documents.php
- Add "Select from My Audio" picker on Transcribe with single-select
and a "Save to My Audio" button for persisting uploaded clips
- New PHP helpers in bootstrap.php: dbnToolsFetchDocChunks,
dbnToolsClientIdFromSession, dbnToolsInjectDocContent
- timeline, ask, redact APIs prepend selected document content
(fetched from client_chunks SQL) before the textarea text
- api/dashboard/audio-upload.php stores audio files on server and
creates a client_documents row with source_type='audio'
- api/transcribe.php falls back to stored audio via audio_doc_id POST
field when no file is uploaded
- api/dashboard/documents.php supports ?source_type= filter
- tools.js: doc_ids added to JSON payload; stored-audio transcribe path
- New assets/css/doc-picker.css, assets/js/doc-picker.js
- SQL migration: scripts/sql/audio_docs_column.sql
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- POST /api/save-to-corpus.php — saves tool output text to user's default CaveauAI corpus via ClientRagPipeline
- api/case/upload.php — dual-writes uploaded PDFs to CaveauAI client_documents (best-effort)
- assets/js/corpus-save.js — shared <dialog> handler for .js-save-corpus buttons on all tool pages
- includes/layout_footer.php — injects corpus-save.js + shared save dialog markup
- korrespond/deep-research/barnevernet/discrepancy JS — save-to-corpus buttons on output sections
- api/search.php + LegalTools::search() — corpus_scope param ('shared'|'private'|'both'), merges personal CaveauAI corpus with shared legal library when 'both'
- includes/tool_form.php + assets/js/tools.js — corpus scope radio toggle shown on search tab
- api/user-docs.php — add POST upload method for non-SSO authenticated users
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds an optional textarea below the main text input where users can provide
clarifications to guide the LLM — e.g. year anchors, actor aliases, or focus
instructions. Notes are injected into the prompt as a clearly delimited block
and translated across all four UI languages (en/no/uk/pl).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Live search/filter bar: filters events by keyword across event, actor, source_excerpt, date
- Actor filter chips: click to filter by actor, multi-select, teal active state
- Year/month group headers when sorted chronologically (── 2023 ──, Mar 2024 ──)
- Per-event copy button (hover-revealed 📋): copies "date · actor · event" to clipboard
- "Hide/show sources" toggle: collapses all source excerpts without re-rendering
- Count badge: "23 events · 3 actors · 2022–2025" above the list
- applyTimelineFilters() unifies sort + actor + text filters in one re-render pass
- CSV export now includes end_date column
- Reset all filter state on each new run
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- Vocab textarea now shows live 0/500 char counter (turns amber at 450+)
- Animated progress bar during transcription; determinate for multi-clip, indeterminate for single
- Results card shows inline stats row (duration, language, speakers) and AI cleanup badge
- Copy button + Download TXT moved above transcript box; SRT/VTT remain below
- Speaker role legend repeats inside Segments panel for easy cross-reference
- Batch errors no longer halt the queue; remaining clips continue, failed files named in status bar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds optional post-transcription cleanup via GPT-4o/GPT-4o-mini to fix
mishearing errors, punctuation, and domain terms. Speaker role labelling
now accepts a deployment param. Adds i18n strings for advanced options
panel (task, VAD filter, Whisper model, AI cleanup) in all four languages.
Updates BvjAnalyzerAgent and DeepResearchAgent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes user-facing engine/model/key/beam controls. The server now picks
the best available engine automatically:
1. Microsoft Azure Speech — short clips (≤1MB, no diarization, audio/*)
2. Google Cloud Speech v2 — long audio, diarization, all languages
3. OpenAI Whisper GPU — local fallback
Results display which provider was used (e.g. "Transcribed with Google
Cloud Speech") via transcript-engine-badge and traceMeta.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prompt now instructs the model to extract time of day (HH:MM) when
present in Norwegian formats: kl. 14:30, kl 09.00, 14:30, 14.30.
renderTimeline shows time as a muted inline annotation next to the date.
CSV export gains a Time column after Date.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Public landing page at / for unauthenticated users (EN/NO/UK/PL)
- Authenticated / shows Case Workbench dashboard with manifesto strip,
stats, and launched-tool grid (Transcribe, Timeline, BVJ, Advocate,
Deep Research, Corpus)
- Added includes/i18n.php with full 4-language translation layer
- Extended layout.php to Case Workbench shell with tool rail, lang switcher
- AI output language normalization extended to en/no/uk/pl in PHP agents
- SSO token validation in bootstrap.php / index.php (dobetternorge.no bridge)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds Nordic-pack regex patterns for:
- DD.MM.YYYY / DD/MM/YYYY / YYYY-MM-DD
- Year ranges (2011/2012, 2018-2019)
- Month + year (Norwegian + English, with optional day)
- Year preceded by temporal preposition (i 2015, fra 2019, rundt 2018)
Also renames the entity toggle from "Dates of birth" to "Dates" (broader
scope) in all four languages, and expands the LLM prompt so soft date
references in free text are caught even when regex misses them.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New api/feedback.php stores rating + correction text to tool_feedback
table in bnl_admin. renderFeedbackWidget() appended to all tool results
(timeline, redact, transcribe, ask, summarize, search). Thumbs reveal
a textarea for missed/wrong items on click; submit POSTs asynchronously.
Engine from last run is stored alongside the rating.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 4-language switcher (EN/NO/UK/PL), engine choice (Azure mini/full,
GPU/cuttlefish), and expandable Advanced panel (Focus, Confidence filter,
Date types) to timeline.php. Wire new params through api/timeline.php and
LegalTools::timeline() with engine routing, focus-aware prompt injection,
and confidence/date-type post-filters. Add TIMELINE_I18N to tools.js with
improved renderTimeline() confidence colour-coding and new CSS classes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Wrap Mode/Region/Entities/Officials/Output/Exempt/Aliases in a
<details> toggle so the form opens clean with only engine + input visible
- After redaction: Copy, Download .txt, Download .docx buttons appear
below the redacted output (all four languages translated)
- New api/redact-download.php: returns plain text or a minimal valid
DOCX built from scratch with ZipArchive (no external dependencies)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Custom inline form (EN/NO/UK/PL lang switcher) replacing generic stub
- Engine selector: Azure gpt-4o-mini (default), gpt-4o, GPU cuttlefish, regex-only
- Entity type toggles: names, organisations, places, dates of birth
- Output formats: contextual role tags, generic [PERSON], Norwegian pseudonyms
- Keep officials mode: judges/experts kept as [JUDGE: Andersen] format
- Exempt names list: specific names excluded from redaction
- Hint paragraphs explaining each option in all four languages
- Backend: engine routing, callGpuLlm(), applyGenericTags(), applyPseudonymization()
- AzureOpenAiGateway: withDeployment() clone pattern for per-call model override
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- api/transcribe.php falls back to DBN_AZURE_SPEECH_KEY/REGION env vars so BYOK not required
- JS hides Azure key input when DBN_AZURE_SPEECH_CONFIGURED is true
- Remove Translate to English task option from Advanced settings
- Add explanatory hint text for Beam size and VAD filter in all 4 languages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Default UI language to English; lang switcher (EN/NO/UK/PL) persisted in localStorage
- Rename 'rettssak/tingrett' preset to 'Mediation / legal meeting' — court recording is illegal
- Add Ukrainian (uk) and Polish (pl) as selectable audio transcription languages
- TRANSCRIBE_I18N translation object drives all status messages, labels, and trace text
- Apache ProxyTimeout raised to 1800s on server (was 300s — caused 504 on large files)
- set_time_limit(0) + ignore_user_abort(true) in api/transcribe.php
- applyTranscribeI18n() patches data-i18n / data-i18n-placeholder / data-i18n-aria attrs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Default language → nb (Bokmål); auto-detect demoted with warning note
- Default model → large-v3; VAD filter on by default
- Vocabulary prompt promoted to main form with 4 preset buttons
(Barnerett/CPS, Rettssak/tingrett, Generell norsk, Egendefinert)
- Multi-file upload queue: drop/select multiple clips, numbered list UI
- Sequential queue processing with cumulative time_offset per clip
- Backend shifts segment timestamps so SRT/VTT covers full court day
- Merged transcript + segments across all clips for single download
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Split monolithic index.php into per-tool pages (ask, search, summarize,
timeline, redact, transcribe), each with its own URL and bookmarkable state
- Shared shell: includes/layout.php + layout_footer.php; shared form:
includes/tool_form.php used by all text-tool pages
- index.php now redirects authenticated users to ask.php; unauthenticated
users see the login gate only
- transcribe.php: engine selector (GPU/OpenAI/Azure), model size (small/
medium/large-v3), diarize, language, expert settings (beam, VAD, task,
initial prompt)
- api/transcribe.php: engine routing — GPU (cuttlefish), OpenAI BYOK,
Azure AI Speech; passes model/beam/task/vad/prompt to Whisper server
- tools.js: data-active-tool body attr drives setTool() on load; <a> nav
tabs skip click listeners; null guards on form/passcodeForm; engine radio
toggle shows/hides BYOK key inputs and model selector; RTF shown in status
- tools.css: styles for BYOK inputs, expert settings panel, prompt textarea
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Guard against INPUT clicks bubbling up to zone handler,
which caused the file picker to open twice.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The hidden textarea still had required=true, so browser-native form
validation silently blocked submit when no audio was the only input.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New sixth tool in the hub. Accepts MP3/WAV/OGG/M4A/FLAC/WEBM up to 200 MB,
proxies to Whisper on cuttlefish GPU. Optional speaker separation with LLM
role labelling (dommer, advokat, forelder, sakkyndig, etc. via GPT-4o-mini).
Client-side TXT / SRT / VTT download from segment data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract limit raised from 32K to 128K chars per file (long legal docs now fit)
- Redact API body/text limits raised (400KB / 128K chars) to match
- Upload zone accepts multiple files (up to 5); extracted text concatenated with
doc separator and combined before redaction; shows per-file char counts
- LLM redact pass now infers contextual person roles (FATHER, MOTHER, CHILD,
ATTORNEY, JUDGE, etc.) instead of generic [PERSON] for all names; same
individual gets consistent tag throughout the document
- Tag validation widened to allow any [A-Za-z0-9_- ] pattern (not just the
five hardcoded tags), supporting contextual and alias tags
- Alias UI added to Redact mode: user maps real names to bracketed aliases
(e.g. "David Jr" -> [Junior]); aliases injected into LLM system prompt as
override instructions; max 20 aliases, 100 chars each
- max_tokens raised from 2000 to 4000; timeout from 60s to 90s for larger docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
api/extract.php — new endpoint accepting .pdf/.docx/.txt up to 4 MB;
pdftotext for PDFs, ZipArchive+DOMXPath for DOCX, mb_convert_encoding
for TXT; truncates to 32 000 chars to stay within redact limit.
index.php — drop/browse upload zone above the textarea, visible only
in Redact mode.
tools.js — setupUpload(), handleFileUpload(), resetUpload(); drag-and-drop
and file picker both call the extract endpoint then populate the textarea.
tools.css — upload zone, drag-over, file-info, clear button styles.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- index.php: public showcase landing page (hero, how-it-works, capabilities,
evidence mock, login form) visible to unauthenticated visitors; full OG/SEO
meta; app shell hidden behind auth as before
- tools.css: showcase section styles (gradient hero, step cards, capability
grid, CTA button, evidence mock, footer)
- LegalTools.php: sourceFromChunk() batch-fetches doc_summaries from RAG DB
for non-private chunks; excerpt shows doc summary when available, falls back
to raw chunk text; chunk_text field always carries the raw excerpt
- tools.js: renderEvidenceItem() shows doc summary as card body; adds a
collapsible "View chunk" toggle when summary differs from raw chunk text
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pass 1: deterministic regex with Nordic/European/ECHR/Global packs
covering fødselsnummer, Swedish personnummer, Danish/Finnish CPR,
UK NI, French INSEE, IBAN, EU phones, ECHR application numbers, DOB,
and national ID label patterns.
Pass 2: LLM semantic scan (Azure OpenAI) finds names, orgs, places
and identifying descriptions missed by regex. Runs on pre-redacted
text so no raw PII reaches the LLM.
Adds region selector (Nordic/European/ECHR/Global) to the Redact UI.
Falls back gracefully when Azure is not yet configured.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>