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,
}),
+9 -11
View File
@@ -55,7 +55,7 @@
roleCustom: document.getElementById('bvjRoleCustom'),
slices: Array.from(document.querySelectorAll('.adv-slice')),
langButtons: Array.from(document.querySelectorAll('#bvjLangSwitcher .lang-btn')),
engineRadios: Array.from(document.querySelectorAll('input[name="bvjEngine"]')),
tierRadios: Array.from(document.querySelectorAll('input[name="bvjTier"]')),
subQ: document.getElementById('bvjSubQ'),
subQVal: document.getElementById('bvjSubQValue'),
chunkLimit: document.getElementById('bvjChunkLimit'),
@@ -186,9 +186,9 @@
};
}
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';
}
// ── File upload ────────────────────────────────────────────────────────────
@@ -385,13 +385,11 @@
return;
}
const engine = getEngine();
const tier = getTier();
const additionalNotes = (els.notes ? els.notes.value : '').trim();
const expectedDuration = engine === 'azure_full'
? '90180 seconds with Azure gpt-4o'
: (engine === 'gpu' ? '4590 seconds on GPU'
: (engine === 'dbn_legal' ? '60120 seconds with Norwegian specialist'
: '3060 seconds with Azure gpt-4o-mini'));
const expectedDuration = tier === 'pro'
? '90180 seconds with Claude Sonnet'
: '3060 seconds with Claude Haiku';
setStatus(`Analysing document for ${advocateRole}… (${expectedDuration})`, 'busy');
els.runButton.disabled = true;
@@ -403,7 +401,7 @@
const payload = {
advocate_role: advocateRole,
engine,
tier,
language: lang,
slices,
controls: getControls(),
+9 -9
View File
@@ -42,7 +42,7 @@
traceList: document.getElementById('traceList'),
slices: Array.from(document.querySelectorAll('.dr-slice')),
langButtons: Array.from(document.querySelectorAll('#drLangSwitcher .lang-btn')),
engineRadios: Array.from(document.querySelectorAll('input[name="drEngine"]')),
tierRadios: Array.from(document.querySelectorAll('input[name="drTier"]')),
personaControl: document.getElementById('drPersonaControl'),
personaSelect: document.getElementById('drPersonaSelect'),
subQ: document.getElementById('drSubQ'),
@@ -280,9 +280,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() {
@@ -308,10 +308,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'
? '60180 seconds with Claude Sonnet'
: '1545 seconds with Claude Haiku';
setStatus(`Running deep research… (${expectedDuration})`, 'busy');
els.runButton.disabled = true;
@@ -325,7 +325,7 @@
query,
paste_text: '',
slices,
engine,
tier,
language: lang,
controls: getControls(),
};
+4 -6
View File
@@ -49,7 +49,7 @@
results: document.getElementById('dcResults'),
traceList: document.getElementById('traceList'),
langButtons: Array.from(document.querySelectorAll('#dcLangSwitcher .lang-btn')),
engineRadios: Array.from(document.querySelectorAll('input[name="dcEngine"]')),
tierRadios: Array.from(document.querySelectorAll('input[name="dcTier"]')),
slices: Array.from(document.querySelectorAll('.adv-slice')),
// File A
zoneA: document.getElementById('dcZoneA'),
@@ -188,12 +188,10 @@
return;
}
const engine = (els.engineRadios.find((r) => r.checked) || {}).value || 'azure_mini';
const tier = (els.tierRadios.find((r) => r.checked) || {}).value || 'quick';
const slices = getSelectedSlices();
const expectedDuration = engine === 'azure_full' ? '2-3 minutes'
: engine === 'gpu' ? '~90 seconds'
: '60-90 seconds';
const expectedDuration = tier === 'pro' ? '2-3 minutes' : '60-90 seconds';
setStatus(`Comparing documents… (${expectedDuration})`, 'busy');
els.runButton.disabled = true;
@@ -203,7 +201,7 @@
renderTrace(stepState);
const payload = {
engine, language: lang, slices,
tier, language: lang, slices,
use_my_case: (typeof window.dbnGetUseMyCase === 'function') ? window.dbnGetUseMyCase() : false,
};
const form = new FormData();
+1 -1
View File
@@ -280,7 +280,7 @@
clarifications: pendingClarifications,
force_draft: !!forceDraft,
use_my_case: (typeof window.dbnGetUseMyCase === 'function') ? window.dbnGetUseMyCase() : false,
engine: (document.querySelector('[name="korrEngine"]:checked')?.value ?? 'azure_mini'),
tier: (document.querySelector('[name="korrTier"]:checked')?.value ?? 'quick'),
length: (document.querySelector('[name="korrLength"]:checked')?.value ?? 'standard'),
};
if (korrDocIds.length) payload.doc_ids = korrDocIds;
+2 -2
View File
@@ -179,13 +179,13 @@
return;
}
var engine = (document.querySelector('input[name="sumEngine"]:checked') || {}).value || 'azure_mini';
var tier = (document.querySelector('input[name="sumTier"]:checked') || {}).value || 'quick';
var slices = activeSlices();
var payload = {
text: combined,
language: _currentLang,
engine: engine,
tier: tier,
depth: (document.querySelector('input[name="sumDepth"]:checked') || {}).value || 'standard',
slices: slices,
};