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>
This commit is contained in:
2026-06-15 12:23:46 +02:00
parent b217f18118
commit a8b1bb87a6
21 changed files with 339 additions and 103 deletions
+12 -12
View File
@@ -64,7 +64,7 @@
roleCustom: document.getElementById('advRoleCustom'),
slices: Array.from(document.querySelectorAll('.adv-slice')),
langButtons: Array.from(document.querySelectorAll('#advLangSwitcher .lang-btn')),
engineRadios: Array.from(document.querySelectorAll('input[name="advEngine"]')),
tierRadios: Array.from(document.querySelectorAll('input[name="advTier"]')),
subQ: document.getElementById('advSubQ'),
subQVal: document.getElementById('advSubQValue'),
chunkLimit: document.getElementById('advChunkLimit'),
@@ -337,9 +337,9 @@
return out;
}
function getEngine() {
const checked = els.engineRadios.find((r) => r.checked);
return checked ? checked.value : 'azure_mini';
function getTier() {
const checked = els.tierRadios.find((r) => r.checked);
return checked ? checked.value : 'quick';
}
function getControls() {
@@ -371,10 +371,10 @@
return;
}
const engine = getEngine();
const expectedDuration = engine === 'azure_full'
? '60180 seconds with Azure gpt-4o'
: (engine === 'gpu' ? '3090 seconds on GPU' : '1545 seconds with Azure gpt-4o-mini');
const tier = getTier();
const expectedDuration = tier === 'pro'
? '35 minutes with Claude Sonnet'
: '24 minutes with Claude Haiku';
setStatus(`Building advocate brief for ${advocateRole}… (${expectedDuration})`, 'busy');
els.runButton.disabled = true;
@@ -387,7 +387,7 @@
query,
paste_text: '',
slices,
engine,
tier,
language: lang,
controls: getControls(),
advocate_role: advocateRole,
@@ -505,7 +505,7 @@
els.runButton.disabled = false;
renderTrace(finalResult.trace || []);
renderResults(finalResult);
saveToCache(finalResult, { query, role: advocateRole, engine, slices, lang });
saveToCache(finalResult, { query, role: advocateRole, tier, slices, lang });
function handleStreamEvent(evt) {
if (!evt || !evt.event) return;
@@ -957,7 +957,7 @@
els.input.value = formState.query || '';
updateCharCount();
if (formState.role) els.roleSelect.value = formState.role;
const radio = els.engineRadios.find((r) => r.value === formState.engine);
const radio = els.tierRadios.find((r) => r.value === formState.tier);
if (radio) radio.checked = true;
if (formState.slices) {
els.slices.forEach((btn) => {
@@ -1074,7 +1074,7 @@
body: JSON.stringify({
query,
language: lang,
engine: getEngine(),
engine: getTier() === 'pro' ? 'claude_sonnet' : 'claude_haiku',
controls: getControls(),
advocate_role: advocateRole,
}),