Files
daveadmin a8b1bb87a6 feat(tools): converge two-tier Quick/Pro selector onto .no fork
Port the dobetterlegal-tools two-tier quality stack to dobetternorge.no:
QUALITY_TIERS registry + resolveTier (ToolModels), dbnToolsResolveToolRun
(bootstrap), tier read+charge in the 6 analytical endpoints, Quick/Pro
UI + payload.tier on the 6 tool pages/JS, and the bounded
corpusContextForSummarize RAG fix (per-passage trim + total budget +
reranker_enabled). Back-compat: requests without `tier` keep legacy
engine behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 12:23:46 +02:00

217 lines
16 KiB
PHP
Raw Permalink 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>
<!-- Letter length -->
<div class="control-row" id="korrLengthControl">
<span class="control-label">Letter length</span>
<label><input type="radio" name="korrLength" value="concise"> Concise</label>
<label><input type="radio" name="korrLength" value="standard" checked> Standard &#9733;</label>
<label><input type="radio" name="korrLength" value="detailed"> Detailed</label>
</div>
<p class="upload-hint">Concise: short and to-the-point (2-3 paragraphs). Standard: balanced correspondence. Detailed: full background, all arguments, and complete legal reasoning.</p>
<!-- Quality tier -->
<div class="control-row" id="korrTierControl">
<span class="control-label">Quality</span>
<label><input type="radio" name="korrTier" value="quick" checked> Quick &#9733; <small class="control-hint">(Claude Haiku · fast · 3 credits)</small></label>
<label><input type="radio" name="korrTier" value="pro"> Pro <small class="control-hint">(Claude Sonnet · best · 6 credits)</small></label>
</div>
<p class="upload-hint">Quick uses Claude Haiku 4.5 for drafting — fast and solid for standard correspondence (3 credits). Pro uses Claude Sonnet 4.6 — better at multi-statute cases, complex appeal grounds, and ECHR framing (6 credits).</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'; ?>