Files
dobetternorge-tools/korrespond.php
T
daveadmin dfb9692f45 Korrespond: stop mixing UI languages — all chrome follows user UI lang
Drafts still come back in Norwegian + working language (that is intentional),
but every piece of *chrome* now respects the user's UI lang consistently:

- Pass 1 classify LLM now writes missing-fact questions in the user's language
  (not always Norwegian), fixing the case where an English-UI user got "Hva er
  saksnummeret?" in the clarify panel.
- All PHP-emitted progress/status messages go through DbnKorrespondAgent::L()
  with en/no/pl/uk variants instead of hardcoded Norwegian.
- JS introduces an I18N dictionary + t() helper covering status messages,
  button labels, column headers, flag labels, refine panel title/hint,
  jurisdiction radio labels, clarify panel title/hint/buttons, the empty-state
  "Ready" block, and Copy/Copied/Download .txt.
- Static clarify and empty-state chrome use [data-i18n] attributes resolved at
  init and re-applied on every lang-switcher click.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 12:11:16 +02:00

174 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
$toolName = 'korrespond';
$toolTitle = 'Korrespond';
$toolKind = 'Draft & reply to Norwegian authorities';
$toolBadge = 'Hard-RAG · Norsk + EN/PL/UK';
$extraScripts = ['assets/js/korrespond.js'];
require_once __DIR__ . '/includes/layout.php';
?>
<form id="korrForm" class="tool-form deep-research" enctype="multipart/form-data">
<div class="lang-switcher" id="korrLangSwitcher" role="group" aria-label="UI language">
<button type="button" class="lang-btn is-active" data-lang="en">&#127468;&#127463; EN</button>
<button type="button" class="lang-btn" data-lang="no">&#127475;&#127476; NO</button>
<button type="button" class="lang-btn" data-lang="uk">&#127482;&#127462; UK</button>
<button type="button" class="lang-btn" data-lang="pl">&#127477;&#127473; PL</button>
</div>
<!-- Mode toggle -->
<div class="control-row" id="korrModeControl">
<span class="control-label">Mode</span>
<label><input type="radio" name="korrMode" value="reply"> <strong>Reply</strong> <small class="control-hint">to a letter / decision / notice you received</small></label>
<label><input type="radio" name="korrMode" value="initiate" checked> <strong>Initiate</strong> <small class="control-hint">start a new correspondence</small></label>
</div>
<!-- Recipient body -->
<div class="adv-role-row">
<label class="control-label" for="korrBody">Recipient body</label>
<select id="korrBody" class="adv-role-select" required>
<option value="">— Velg mottaker —</option>
<option value="barnehage">Barnehage</option>
<option value="school_1_10">Skole (1.10. trinn)</option>
<option value="sfo">SFO</option>
<option value="nav">NAV</option>
<option value="bufdir">Bufdir (adopsjon, surrogati)</option>
<option value="barnevernet">Barnevernet</option>
<option value="kommune_other">Kommunen (annet)</option>
<option value="statsforvalter">Statsforvalteren</option>
<option value="trygderetten">Trygderetten</option>
<option value="tingrett">Tingretten</option>
<option value="other">Annet</option>
</select>
<p class="upload-hint">Each recipient preset pulls the relevant statute set into the hard-RAG retrieval (fvl, barnevernsloven, NAV-loven, opplæringslova, barnehageloven, EMK).</p>
</div>
<!-- Output type -->
<div class="control-row" id="korrOutputControl">
<span class="control-label">Output type</span>
<label><input type="radio" name="korrOutput" value="email" checked> Email</label>
<label><input type="radio" name="korrOutput" value="formal"> Formal letter</label>
<label><input type="radio" name="korrOutput" value="filing"> Court/tribunal filing</label>
<label><input type="radio" name="korrOutput" value="call_prep"> Phone-call prep</label>
</div>
<!-- Tone -->
<div class="control-row" id="korrToneControl">
<span class="control-label">Tone</span>
<label><input type="radio" name="korrTone" value="cooperative"> Cooperative</label>
<label><input type="radio" name="korrTone" value="neutral" checked> Neutral-professional &#9733;</label>
<label><input type="radio" name="korrTone" value="firm"> Firm</label>
<label><input type="radio" name="korrTone" value="adversarial"> Adversarial</label>
<label><input type="radio" name="korrTone" value="warm"> Conciliatory-warm</label>
</div>
<!-- Case context fields -->
<div class="dr-control-grid">
<div class="dr-control-card">
<label for="korrCaseRef">Case reference (saksnummer)</label>
<input type="text" id="korrCaseRef" maxlength="120" placeholder="e.g. 2026/12345">
</div>
<div class="dr-control-card">
<label for="korrWhere">Where (kommune / fylke)</label>
<input type="text" id="korrWhere" maxlength="200" placeholder="e.g. Trondheim kommune">
</div>
<div class="dr-control-card">
<label for="korrDeadline">Next deadline</label>
<input type="text" id="korrDeadline" maxlength="60" placeholder="YYYY-MM-DD or '3 weeks'">
</div>
</div>
<label class="input-label" for="korrParties">Who is involved? <span class="optional-hint">(names + roles)</span></label>
<textarea id="korrParties" rows="2" maxlength="2000" placeholder="e.g. Me (parent), caseworker Anna Hansen, child Ola (age 5). Use the Redact tool first if you'll share externally."></textarea>
<label class="input-label" for="korrNarrative">What happened / context <span class="required-hint">(required for initiate mode)</span></label>
<textarea id="korrNarrative" rows="6" maxlength="8000" placeholder="Describe the situation: what happened, when, who decided what, what you want."></textarea>
<label class="input-label" for="korrGoal">Goal of this letter <span class="optional-hint">(or pick a chip)</span></label>
<input type="text" id="korrGoal" maxlength="600" placeholder="e.g. request access to all case documents under forvaltningsloven §18">
<div class="korr-goal-chips" id="korrGoalChips" role="group" aria-label="Common goals">
<button type="button" class="korr-chip" data-goal="Request access to case documents (forvaltningsloven §18)">Access to docs (fvl §18)</button>
<button type="button" class="korr-chip" data-goal="Appeal the decision (forvaltningsloven §28)">Appeal (fvl §28)</button>
<button type="button" class="korr-chip" data-goal="Request a meeting">Request meeting</button>
<button type="button" class="korr-chip" data-goal="Request reasoned written decision (forvaltningsloven §24-25)">Reasoned decision (fvl §24-25)</button>
<button type="button" class="korr-chip" data-goal="Invoke right to be heard (forvaltningsloven §17)">Right to be heard (fvl §17)</button>
<button type="button" class="korr-chip" data-goal="Complaint about caseworker conduct">Complaint</button>
<button type="button" class="korr-chip" data-goal="Clarify status / timeline of the case">Clarify timeline</button>
</div>
<!-- File upload (required for Reply mode) -->
<div class="upload-zone" id="korrUploadZone" role="region" aria-label="File upload">
<input type="file" id="korrUploadInput" multiple accept=".pdf,.docx,.txt" aria-label="Choose received letter or attachments">
<div id="korrUploadPrompt" class="upload-prompt">
<span class="upload-icon" aria-hidden="true">&#8679;</span>
<p>Upload the received letter (reply mode) or supporting attachments, or <label for="korrUploadInput" class="upload-browse">browse</label></p>
<p class="upload-hint"><strong>PDF</strong>, <strong>DOCX</strong>, <strong>TXT</strong> — up to 4 files, max 8 MB each — processed in memory.</p>
</div>
<div id="korrUploadFileInfo" class="upload-file is-hidden">
<ul id="korrUploadFileList" class="upload-file-list"></ul>
<button type="button" id="korrUploadClear" class="upload-clear">&times; Clear</button>
</div>
</div>
<div class="form-footer">
<p id="korrStatus" class="form-status" role="status" aria-live="polite"></p>
<button id="korrRunButton" type="submit">Draft</button>
</div>
</form>
<!-- Clarify panel (shown if Pass 1 returns missing_facts) -->
<section id="korrClarifyPanel" class="korr-clarify-panel is-hidden" aria-labelledby="korrClarifyTitle">
<h3 id="korrClarifyTitle" data-i18n="clarify_title">Before we draft, clarify:</h3>
<p class="upload-hint" data-i18n="clarify_hint">Answer what you can, then click Continue draft. Or click Draft anyway to proceed with what we have.</p>
<div id="korrClarifyList" class="korr-clarify-list"></div>
<div class="korr-clarify-actions">
<button type="button" id="korrClarifyContinue" class="primary-button" data-i18n="clarify_continue">Continue draft</button>
<button type="button" id="korrClarifyForce" class="secondary-button" data-i18n="clarify_force">Draft anyway</button>
</div>
</section>
<section id="korrResults" class="results deep-research-results" aria-live="polite">
<div class="empty-state">
<h3 data-i18n="ready_title">Ready</h3>
<p data-i18n="ready_desc">Pick a recipient body, describe the situation, choose an output type and tone, then run. Drafts always come back in Norwegian bokmål + your working language, side-by-side, with verified law citations.</p>
</div>
</section>
<!-- Hidden stubs so tools.js element refs don't crash on this page -->
<div class="is-hidden" id="languageControl" aria-hidden="true">
<input type="radio" name="language" value="en" checked>
<input type="radio" name="language" value="no">
<input type="radio" name="language" value="uk">
<input type="radio" name="language" value="pl">
</div>
<div class="is-hidden" id="redactionControl" aria-hidden="true"></div>
<div class="is-hidden" id="audioZone" aria-hidden="true">
<input type="file" id="audioInput" style="display:none">
<div id="audioPrompt"></div>
<div id="audioFileInfo"><ol id="audioQueueList"></ol><button type="button" id="audioClear"></button></div>
</div>
<div class="is-hidden" id="diarizeControl" aria-hidden="true">
<input type="checkbox" id="diarizeCheck">
<input type="number" id="numSpeakersInput">
</div>
<div class="is-hidden" id="transcribeLangControl" aria-hidden="true"><input type="radio" name="transcribeLang" value="no" checked></div>
<div class="is-hidden" id="vocabControl" aria-hidden="true">
<div id="vocabPresets"></div>
<textarea id="initPromptInput"></textarea>
</div>
<div class="is-hidden" id="aliasSection" aria-hidden="true">
<button type="button" id="addAliasRow"></button>
<div id="aliasRows"></div>
</div>
<div class="is-hidden" id="exemptSection" aria-hidden="true">
<button type="button" id="addExemptRow"></button>
<div id="exemptRows"></div>
</div>
<div class="is-hidden" id="uploadZone" aria-hidden="true">
<input type="file" id="uploadInput">
<div id="uploadPrompt"></div>
<div id="uploadFileInfo"><ul id="uploadFileList"></ul><button type="button" id="uploadClear"></button></div>
</div>
<textarea class="is-hidden" id="toolInput" aria-hidden="true"></textarea>
<?php require_once __DIR__ . '/includes/layout_footer.php'; ?>