Files
dobetternorge-tools/korrespond.php
T
daveadmin d156f8cf6b feat(tools): persona selector across standalone tools + dashboard chat
Wire the legal-domain persona picker into corpus, deep-research, korrespond and
the dashboard chat. Each endpoint reads the chosen profile, resolves its packages
against client 57, and scopes retrieval via package_ids (falling back to family
when omitted). New dashboard tenants now subscribe to all DBN domain packages so
persona switching survives the subscription intersection.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 23:03:31 +02:00

208 lines
15 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>
<div class="korr-doc-links">
<a href="/korrespond-about.php" target="_blank">About this tool</a>
<span aria-hidden="true">&middot;</span>
<a href="/korrespond-guide.php" target="_blank">User guide</a>
<span aria-hidden="true">&middot;</span>
<a href="/korrespond-tech.php" target="_blank">How it works</a>
</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>
<!-- Engine -->
<div class="control-row" id="korrEngineControl">
<span class="control-label">Engine</span>
<label><input type="radio" name="korrEngine" value="azure_mini" checked> &#x2601;&#xFE0F; Quick <small class="control-hint">(~40-70s)</small></label>
<label><input type="radio" name="korrEngine" value="claude_sonnet"> &#x2601;&#xFE0F; Thorough &#9733;&#9733; <small class="control-hint">(~70-120s)</small></label>
</div>
<p class="upload-hint">Quick uses Claude Haiku 4.5 for drafting — fast and solid for standard correspondence. Thorough uses Claude Sonnet 4.6 — better at multi-statute cases, complex appeal grounds, and ECHR framing.</p>
<!-- Domain persona -->
<div class="control-row corpus-persona is-hidden" id="korrPersonaControl">
<span class="control-label">Domain</span>
<select id="korrPersonaSelect" class="adv-role-select" aria-label="Legal domain persona"></select>
<small class="control-hint">Scopes the hard-RAG law retrieval to a legal domain.</small>
</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>
<div id="docPickerSection" class="doc-picker-section">
<button type="button" id="docPickerBtn" class="doc-picker-btn" aria-haspopup="dialog">
<svg class="doc-picker-btn__icon" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><rect x="2" y="1" width="9" height="12" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M5 5h5M5 8h3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><rect x="7" y="9" width="6" height="5" rx="1" fill="white" stroke="currentColor" stroke-width="1.3"/><path d="M9 11h2M9 12.5h1" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>
<span><?= htmlspecialchars(dbnToolsT('doc_picker_btn', $uiLang)) ?></span>
</button>
<div id="docPickerChips" class="doc-picker-chips" aria-label="Selected documents"></div>
<input type="hidden" id="docPickerIds" name="doc_ids" value="">
</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>
<?php require __DIR__ . '/includes/case_toggle.php'; ?>
<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'; ?>