feat: Legal Tools v1 — multilingual landing, dashboard, SSO bridge
- Public landing page at / for unauthenticated users (EN/NO/UK/PL) - Authenticated / shows Case Workbench dashboard with manifesto strip, stats, and launched-tool grid (Transcribe, Timeline, BVJ, Advocate, Deep Research, Corpus) - Added includes/i18n.php with full 4-language translation layer - Extended layout.php to Case Workbench shell with tool rail, lang switcher - AI output language normalization extended to en/no/uk/pl in PHP agents - SSO token validation in bootstrap.php / index.php (dobetternorge.no bridge) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -62,7 +62,7 @@ final class DbnBvjAnalyzerAgent
|
||||
): array {
|
||||
$engine = in_array($engine, ['azure_mini', 'azure_full', 'gpu'], true)
|
||||
? $engine : 'azure_mini';
|
||||
$language = in_array($language, ['en', 'no'], true) ? $language : 'en';
|
||||
$language = dbnToolsNormalizeUiLanguage($language);
|
||||
$controls = $this->normalizeControls($controls);
|
||||
|
||||
if (empty($uploadedFiles)) {
|
||||
@@ -440,7 +440,7 @@ final class DbnBvjAnalyzerAgent
|
||||
|
||||
private function classifyDocument(string $docText, string $language): array
|
||||
{
|
||||
$locale = $language === 'no' ? 'Norwegian' : 'English';
|
||||
$locale = dbnToolsLanguageName($language);
|
||||
$excerpt = mb_substr($docText, 0, 6000, 'UTF-8');
|
||||
|
||||
$prompt = <<<PROMPT
|
||||
@@ -492,7 +492,7 @@ PROMPT;
|
||||
|
||||
private function extractParties(string $docText, string $language): array
|
||||
{
|
||||
$locale = $language === 'no' ? 'Norwegian' : 'English';
|
||||
$locale = dbnToolsLanguageName($language);
|
||||
$excerpt = mb_substr($docText, 0, 12000, 'UTF-8');
|
||||
|
||||
$prompt = <<<PROMPT
|
||||
@@ -540,7 +540,7 @@ PROMPT;
|
||||
|
||||
private function extractTimeline(string $docText, string $language): array
|
||||
{
|
||||
$locale = $language === 'no' ? 'Norwegian' : 'English';
|
||||
$locale = dbnToolsLanguageName($language);
|
||||
$excerpt = mb_substr($docText, 0, 12000, 'UTF-8');
|
||||
|
||||
$prompt = <<<PROMPT
|
||||
@@ -600,7 +600,7 @@ PROMPT;
|
||||
int $count,
|
||||
string $language
|
||||
): array {
|
||||
$locale = $language === 'no' ? 'Norwegian' : 'English';
|
||||
$locale = dbnToolsLanguageName($language);
|
||||
$docType = $docMeta['doc_type'] ?? 'BVJ document';
|
||||
$roleStr = $advocateRole !== '' ? $advocateRole : 'the affected party';
|
||||
|
||||
@@ -698,7 +698,7 @@ PROMPT;
|
||||
string $additionalNotes,
|
||||
?callable $emit = null
|
||||
): array {
|
||||
$locale = $language === 'no' ? 'Norwegian' : 'English';
|
||||
$locale = dbnToolsLanguageName($language);
|
||||
$roleStr = $advocateRole !== '' ? $advocateRole : 'the affected party';
|
||||
$docType = $docMeta['doc_type'] ?? 'BVJ Document';
|
||||
$docDate = $docMeta['doc_date'] ?? 'unknown date';
|
||||
@@ -708,9 +708,12 @@ PROMPT;
|
||||
$sourceCount = count($numberedSources);
|
||||
|
||||
if (empty($numberedSources)) {
|
||||
$emptyBrief = $language === 'no'
|
||||
? 'Ingen kildetreff ble funnet i korpuset for de valgte skivene og spørsmålene.'
|
||||
: 'No corpus sources were retrieved for the selected slices and sub-questions.';
|
||||
$emptyBrief = match (dbnToolsNormalizeUiLanguage($language)) {
|
||||
'no' => 'Ingen kildetreff ble funnet i korpuset for de valgte skivene og spørsmålene.',
|
||||
'uk' => 'Для вибраних розділів і підпитань не знайдено джерел у корпусі.',
|
||||
'pl' => 'Nie znaleziono źródeł w korpusie dla wybranych sekcji i pytań pomocniczych.',
|
||||
default => 'No corpus sources were retrieved for the selected slices and sub-questions.',
|
||||
};
|
||||
return [
|
||||
'json' => [
|
||||
'advocacy_brief' => $emptyBrief,
|
||||
|
||||
@@ -38,7 +38,7 @@ final class DbnDeepResearchAgent
|
||||
$seedQuery = trim($seedQuery);
|
||||
$pastedText = trim($pastedText);
|
||||
$engine = in_array($engine, ['azure_mini', 'azure_full', 'gpu'], true) ? $engine : 'azure_mini';
|
||||
$language = in_array($language, ['en', 'no'], true) ? $language : 'en';
|
||||
$language = dbnToolsNormalizeUiLanguage($language);
|
||||
|
||||
$controls = $this->normalizeControls($controls);
|
||||
|
||||
@@ -444,7 +444,7 @@ final class DbnDeepResearchAgent
|
||||
|
||||
private function interpretSeed(string $seedDescription, string $language, string $advocateRole = '', ?array $priorContext = null, string $branchNotes = ''): array
|
||||
{
|
||||
$locale = $language === 'no' ? 'Norwegian' : 'English';
|
||||
$locale = dbnToolsLanguageName($language);
|
||||
$rolePrefix = $advocateRole !== ''
|
||||
? "You are preparing a case-research brief for: {$advocateRole}. Frame your interpretation to identify the strongest legal angles for this party.\n\n"
|
||||
: '';
|
||||
@@ -511,7 +511,7 @@ PROMPT;
|
||||
|
||||
private function expandQueries(string $seedDescription, string $brief, int $targetCount, string $language, string $advocateRole = ''): array
|
||||
{
|
||||
$locale = $language === 'no' ? 'Norwegian' : 'English';
|
||||
$locale = dbnToolsLanguageName($language);
|
||||
|
||||
if ($advocateRole !== '') {
|
||||
$prompt = <<<PROMPT
|
||||
@@ -942,14 +942,17 @@ PROMPT;
|
||||
?array $priorContext = null,
|
||||
string $branchNotes = ''
|
||||
): array {
|
||||
$locale = $language === 'no' ? 'Norwegian' : 'English';
|
||||
$locale = dbnToolsLanguageName($language);
|
||||
|
||||
if (empty($numberedSources)) {
|
||||
return [
|
||||
'json' => [
|
||||
'brief_markdown' => $language === 'no'
|
||||
? 'Jeg fant ikke tilstrekkelig kildestøtte i korpuset til å gi et grunnlagsbasert svar.'
|
||||
: 'I did not find enough source support in the corpus to give a grounded answer.',
|
||||
'brief_markdown' => match (dbnToolsNormalizeUiLanguage($language)) {
|
||||
'no' => 'Jeg fant ikke tilstrekkelig kildestøtte i korpuset til å gi et grunnlagsbasert svar.',
|
||||
'uk' => 'Я не знайшов достатньої підтримки джерел у корпусі, щоб дати обґрунтовану відповідь.',
|
||||
'pl' => 'Nie znalazłem wystarczającego wsparcia źródłowego w korpusie, aby udzielić ugruntowanej odpowiedzi.',
|
||||
default => 'I did not find enough source support in the corpus to give a grounded answer.',
|
||||
},
|
||||
'what_we_found' => 'No retrieved sources passed the similarity threshold.',
|
||||
'what_remains_uncertain' => ['No corpus evidence retrieved for the given query and slice selection.'],
|
||||
'next_practical_step' => 'Try widening slice selection or rephrasing with more specific statutory or party terms.',
|
||||
|
||||
+10
-7
@@ -139,9 +139,12 @@ final class DbnLegalToolsService
|
||||
return [
|
||||
'tool' => 'ask',
|
||||
'language' => $language,
|
||||
'answer' => $language === 'no'
|
||||
? 'Jeg fant ikke nok kildestøtte i familie-rettskorpuset til å svare sikkert.'
|
||||
: 'I did not find enough source support in the family-law corpus to answer safely.',
|
||||
'answer' => match (dbnToolsNormalizeUiLanguage($language)) {
|
||||
'no' => 'Jeg fant ikke nok kildestøtte i familierettskorpuset til å svare sikkert.',
|
||||
'uk' => 'Я не знайшов достатньої підтримки в корпусі сімейного права, щоб відповісти безпечно.',
|
||||
'pl' => 'Nie znalazłem wystarczającego wsparcia źródłowego w korpusie prawa rodzinnego, aby odpowiedzieć bezpiecznie.',
|
||||
default => 'I did not find enough source support in the family-law corpus to answer safely.',
|
||||
},
|
||||
'what_we_found' => $search['what_we_found'],
|
||||
'evidence_trail' => [],
|
||||
'what_remains_uncertain' => $search['what_remains_uncertain'],
|
||||
@@ -160,7 +163,7 @@ final class DbnLegalToolsService
|
||||
$this->azure->requireChat();
|
||||
|
||||
$context = $this->buildEvidenceContext($hits);
|
||||
$locale = $language === 'no' ? 'Norwegian' : 'English';
|
||||
$locale = dbnToolsLanguageName($language);
|
||||
$prompt = <<<PROMPT
|
||||
Question:
|
||||
{$question}
|
||||
@@ -229,7 +232,7 @@ PROMPT;
|
||||
$text = $this->requirePasteText($text);
|
||||
$this->azure->requireChat();
|
||||
|
||||
$locale = $language === 'no' ? 'Norwegian' : 'English';
|
||||
$locale = dbnToolsLanguageName($language);
|
||||
$prompt = <<<PROMPT
|
||||
Summarize this pasted case-preparation text in {$locale}. Do not invent missing facts.
|
||||
|
||||
@@ -296,7 +299,7 @@ PROMPT;
|
||||
$this->azure->requireChat();
|
||||
}
|
||||
|
||||
$locale = $language === 'no' ? 'Norwegian' : 'English';
|
||||
$locale = dbnToolsLanguageName($language);
|
||||
|
||||
$focusInstruction = match ($focus) {
|
||||
'deadlines' => "\nFocus specifically on: legal deadlines, filing dates, response windows, appeal periods, and statutory time limits. Deprioritise narrative events with no legal deadline significance.",
|
||||
@@ -599,7 +602,7 @@ PROMPT;
|
||||
|
||||
private function legalJsonSystemPrompt(string $language): string
|
||||
{
|
||||
$locale = $language === 'no' ? 'Norwegian' : 'English';
|
||||
$locale = dbnToolsLanguageName($language);
|
||||
return <<<PROMPT
|
||||
You are Do Better Norge Legal Tools in a source-grounded legal preparation workflow.
|
||||
Use the DBN legal guardrails:
|
||||
|
||||
@@ -107,6 +107,7 @@ function dbnToolsStartSession(): void
|
||||
}
|
||||
|
||||
dbnToolsStartSession();
|
||||
require_once __DIR__ . '/i18n.php';
|
||||
|
||||
function dbnToolsIsAuthenticated(): bool
|
||||
{
|
||||
@@ -224,8 +225,7 @@ function dbnToolsJsonInput(int $maxBytes = 50000): array
|
||||
|
||||
function dbnToolsNormalizeLanguage(mixed $value): string
|
||||
{
|
||||
$language = strtolower(trim((string)$value));
|
||||
return in_array($language, ['no', 'en'], true) ? $language : 'en';
|
||||
return dbnToolsNormalizeUiLanguage($value);
|
||||
}
|
||||
|
||||
function dbnToolsNormalizeRegion(mixed $value): string
|
||||
@@ -472,10 +472,13 @@ function dbnToolsHasActiveSubscription(int $clientId, int $packageId, ?PDO $db =
|
||||
|
||||
function dbnToolsDisclaimer(string $language): string
|
||||
{
|
||||
if ($language === 'no') {
|
||||
return 'Juridisk informasjon og forberedelsesstøtte, ikke endelig juridisk rådgivning.';
|
||||
}
|
||||
return 'Legal information and preparation support, not final legal advice.';
|
||||
$language = dbnToolsNormalizeUiLanguage($language);
|
||||
return match ($language) {
|
||||
'no' => 'Juridisk informasjon og forberedelsesstøtte, ikke endelig juridisk rådgivning.',
|
||||
'uk' => 'Юридична інформація та підтримка підготовки, не остаточна юридична порада.',
|
||||
'pl' => 'Informacje prawne i wsparcie przygotowania, nie ostateczna porada prawna.',
|
||||
default => 'Legal information and preparation support, not final legal advice.',
|
||||
};
|
||||
}
|
||||
|
||||
function dbnToolsExcerpt(string $text, int $limit = 520): string
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
function dbnToolsSupportedLanguages(): array
|
||||
{
|
||||
return ['en', 'no', 'uk', 'pl'];
|
||||
}
|
||||
|
||||
function dbnToolsNormalizeUiLanguage(mixed $language): string
|
||||
{
|
||||
$language = strtolower(trim((string)$language));
|
||||
if ($language === 'nb') {
|
||||
return 'no';
|
||||
}
|
||||
return in_array($language, dbnToolsSupportedLanguages(), true) ? $language : 'en';
|
||||
}
|
||||
|
||||
function dbnToolsCurrentLanguage(): string
|
||||
{
|
||||
if (isset($_GET['lang'])) {
|
||||
$lang = dbnToolsNormalizeUiLanguage($_GET['lang']);
|
||||
$_SESSION['dbn_tools_lang'] = $lang;
|
||||
if (!headers_sent()) {
|
||||
setcookie('dbn_tools_lang', $lang, [
|
||||
'expires' => time() + 60 * 60 * 24 * 180,
|
||||
'path' => '/',
|
||||
'secure' => dbnToolsIsHttps(),
|
||||
'httponly' => false,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
}
|
||||
return $lang;
|
||||
}
|
||||
|
||||
if (!empty($_SESSION['dbn_tools_lang'])) {
|
||||
return dbnToolsNormalizeUiLanguage($_SESSION['dbn_tools_lang']);
|
||||
}
|
||||
|
||||
if (!empty($_COOKIE['dbn_tools_lang'])) {
|
||||
$lang = dbnToolsNormalizeUiLanguage($_COOKIE['dbn_tools_lang']);
|
||||
$_SESSION['dbn_tools_lang'] = $lang;
|
||||
return $lang;
|
||||
}
|
||||
|
||||
return 'en';
|
||||
}
|
||||
|
||||
function dbnToolsLanguageName(string $language): string
|
||||
{
|
||||
return match (dbnToolsNormalizeUiLanguage($language)) {
|
||||
'no' => 'Norwegian',
|
||||
'uk' => 'Ukrainian',
|
||||
'pl' => 'Polish',
|
||||
default => 'English',
|
||||
};
|
||||
}
|
||||
|
||||
function dbnToolsLanguageLabel(string $language): string
|
||||
{
|
||||
return match (dbnToolsNormalizeUiLanguage($language)) {
|
||||
'no' => 'NO',
|
||||
'uk' => 'UK',
|
||||
'pl' => 'PL',
|
||||
default => 'EN',
|
||||
};
|
||||
}
|
||||
|
||||
function dbnToolsTranslations(): array
|
||||
{
|
||||
return [
|
||||
'en' => [
|
||||
'meta_title' => 'Do Better Norge - AI Legal Tools',
|
||||
'brand_line' => 'Do Better Norge - tools.dobetternorge.no',
|
||||
'suite_title' => 'Legal Tools',
|
||||
'workspace_title' => 'Case Workbench',
|
||||
'session_active' => 'Session active',
|
||||
'health' => 'Health',
|
||||
'sign_out' => 'Sign out',
|
||||
'retention' => 'Session in memory - nothing stored by default',
|
||||
'disclaimer' => 'Legal information and preparation support, not final legal advice. Pasted text and uploads are processed in memory by default.',
|
||||
'manifesto_eyebrow' => 'Family rights - Norway - since 2019',
|
||||
'manifesto_title' => 'They took her child in twelve minutes.',
|
||||
'manifesto_sub' => 'Open a tool. Build a chronology, research the law, protect privacy, and prepare your next step with cited support.',
|
||||
'stat_echr' => 'ECHR violations since 2015',
|
||||
'stat_loss' => 'ECHR cases lost 2017-22',
|
||||
'stat_tribunal' => 'tribunal decisions analysed',
|
||||
'stat_pending' => 'pending Strasbourg cases',
|
||||
'reasoning_eyebrow' => 'File - Evidence trail',
|
||||
'reasoning_title' => 'Reasoning',
|
||||
'waiting_title' => 'Waiting',
|
||||
'waiting_text' => 'Run a tool to see interpretation, retrieval, confidence, uncertainty, and next step.',
|
||||
'dashboard_eyebrow' => 'Approved tools suite',
|
||||
'dashboard_title' => 'Choose a legal AI tool',
|
||||
'dashboard_sub' => 'Built for families, advocates, and supporters preparing Norwegian family-rights and child-welfare cases.',
|
||||
'open_tool' => 'Open tool',
|
||||
'landing_kicker' => 'AI legal preparation for family-rights cases in Norway',
|
||||
'landing_title' => 'Legal tools for families who need the record to make sense.',
|
||||
'landing_sub' => 'Transcribe meetings, build timelines, analyze Barnevernet documents, research ECHR and Norwegian sources, and prepare cited advocacy briefs.',
|
||||
'primary_access' => 'Continue with Do Better Norge / Google',
|
||||
'secondary_access' => 'Sign in with Caveau account',
|
||||
'member_note' => 'Use your Do Better Norge account. Google login is handled on the main site, then you return here securely.',
|
||||
'email' => 'Email',
|
||||
'password' => 'Password',
|
||||
'sign_in' => 'Sign in',
|
||||
'register' => 'Register free at dobetternorge.no',
|
||||
'cause_title' => 'Evidence over outrage.',
|
||||
'cause_text' => 'Every tool is designed around the same principle as the movement: document the facts, cite the law, and make the next practical step visible.',
|
||||
'privacy_title' => 'Private by design',
|
||||
'privacy_text' => 'Uploads are processed in memory by default. The app records only operational metadata such as tool name, latency, language, and anonymous session id.',
|
||||
'source_title' => 'Sources stay visible',
|
||||
'source_text' => 'Research tools keep citations, sections, source excerpts, and uncertainty notes next to the answer.',
|
||||
'tools_title' => 'Launched tools',
|
||||
],
|
||||
'no' => [
|
||||
'meta_title' => 'Do Better Norge - juridiske AI-verktøy',
|
||||
'brand_line' => 'Do Better Norge - tools.dobetternorge.no',
|
||||
'suite_title' => 'Juridiske verktøy',
|
||||
'workspace_title' => 'Saksarbeidsbenk',
|
||||
'session_active' => 'Økt aktiv',
|
||||
'health' => 'Helse',
|
||||
'sign_out' => 'Logg ut',
|
||||
'retention' => 'Økt i minnet - ingenting lagres som standard',
|
||||
'disclaimer' => 'Juridisk informasjon og forberedelsesstøtte, ikke endelig juridisk rådgivning. Tekst og opplastinger behandles som standard i minnet.',
|
||||
'manifesto_eyebrow' => 'Familierettigheter - Norge - siden 2019',
|
||||
'manifesto_title' => 'De tok barnet hennes på tolv minutter.',
|
||||
'manifesto_sub' => 'Åpne et verktøy. Bygg kronologi, undersøk loven, beskytt personvern og forbered neste steg med kilder.',
|
||||
'stat_echr' => 'EMD-brudd siden 2015',
|
||||
'stat_loss' => 'EMD-saker tapt 2017-22',
|
||||
'stat_tribunal' => 'nemndsvedtak analysert',
|
||||
'stat_pending' => 'saker venter i Strasbourg',
|
||||
'reasoning_eyebrow' => 'Fil - evidensspor',
|
||||
'reasoning_title' => 'Resonnement',
|
||||
'waiting_title' => 'Venter',
|
||||
'waiting_text' => 'Kjør et verktøy for å se tolkning, kilder, tillit, usikkerhet og neste steg.',
|
||||
'dashboard_eyebrow' => 'Godkjent verktøypakke',
|
||||
'dashboard_title' => 'Velg et juridisk AI-verktøy',
|
||||
'dashboard_sub' => 'Laget for familier, støttespillere og advokater som forbereder norske familie- og barnevernssaker.',
|
||||
'open_tool' => 'Åpne verktøy',
|
||||
'landing_kicker' => 'Juridisk AI-forberedelse for familierettssaker i Norge',
|
||||
'landing_title' => 'Juridiske verktøy for familier som trenger orden i saksbildet.',
|
||||
'landing_sub' => 'Transkriber møter, bygg tidslinjer, analyser barnevernsdokumenter, undersøk EMD og norske kilder, og forbered kildebelagte prosesskriv.',
|
||||
'primary_access' => 'Fortsett med Do Better Norge / Google',
|
||||
'secondary_access' => 'Logg inn med Caveau-konto',
|
||||
'member_note' => 'Bruk Do Better Norge-kontoen din. Google-pålogging skjer på hovedsiden, så kommer du trygt tilbake hit.',
|
||||
'email' => 'E-post',
|
||||
'password' => 'Passord',
|
||||
'sign_in' => 'Logg inn',
|
||||
'register' => 'Registrer deg gratis på dobetternorge.no',
|
||||
'cause_title' => 'Bevis fremfor raseri.',
|
||||
'cause_text' => 'Hvert verktøy følger samme prinsipp som bevegelsen: dokumenter fakta, vis lovgrunnlaget og gjør neste praktiske steg tydelig.',
|
||||
'privacy_title' => 'Personvern først',
|
||||
'privacy_text' => 'Opplastinger behandles som standard i minnet. Appen lagrer bare operasjonelle metadata som verktøy, tidsbruk, språk og anonym økt-id.',
|
||||
'source_title' => 'Kildene er synlige',
|
||||
'source_text' => 'Forskningsverktøyene holder sitater, paragrafer, kildeutdrag og usikkerhet ved siden av svaret.',
|
||||
'tools_title' => 'Lanserte verktøy',
|
||||
],
|
||||
'uk' => [
|
||||
'meta_title' => 'Do Better Norge - юридичні AI інструменти',
|
||||
'brand_line' => 'Do Better Norge - tools.dobetternorge.no',
|
||||
'suite_title' => 'Юридичні інструменти',
|
||||
'workspace_title' => 'Робочий простір справи',
|
||||
'session_active' => 'Сесія активна',
|
||||
'health' => 'Стан',
|
||||
'sign_out' => 'Вийти',
|
||||
'retention' => 'Сесія в памʼяті - за замовчуванням нічого не зберігається',
|
||||
'disclaimer' => 'Юридична інформація та підтримка підготовки, не остаточна юридична порада. Текст і файли за замовчуванням обробляються в памʼяті.',
|
||||
'manifesto_eyebrow' => 'Права сімʼї - Норвегія - з 2019',
|
||||
'manifesto_title' => 'Її дитину забрали за дванадцять хвилин.',
|
||||
'manifesto_sub' => 'Відкрийте інструмент. Побудуйте хронологію, дослідіть право, захистіть приватність і підготуйте наступний крок з джерелами.',
|
||||
'stat_echr' => 'порушень ЄСПЛ з 2015',
|
||||
'stat_loss' => 'справ ЄСПЛ програно 2017-22',
|
||||
'stat_tribunal' => 'рішень трибуналів проаналізовано',
|
||||
'stat_pending' => 'справ очікують у Страсбурзі',
|
||||
'reasoning_eyebrow' => 'Файл - слід доказів',
|
||||
'reasoning_title' => 'Обґрунтування',
|
||||
'waiting_title' => 'Очікування',
|
||||
'waiting_text' => 'Запустіть інструмент, щоб побачити тлумачення, джерела, впевненість, невизначеність і наступний крок.',
|
||||
'dashboard_eyebrow' => 'Схвалений набір інструментів',
|
||||
'dashboard_title' => 'Оберіть юридичний AI інструмент',
|
||||
'dashboard_sub' => 'Для сімей, представників і союзників, які готують справи про сімейні права та захист дітей у Норвегії.',
|
||||
'open_tool' => 'Відкрити інструмент',
|
||||
'landing_kicker' => 'AI підготовка для справ про сімейні права в Норвегії',
|
||||
'landing_title' => 'Юридичні інструменти для сімей, яким потрібно впорядкувати матеріали справи.',
|
||||
'landing_sub' => 'Транскрибуйте зустрічі, будуйте хронології, аналізуйте документи Barnevernet, досліджуйте ЄСПЛ і норвезькі джерела та готуйте аргументи з цитатами.',
|
||||
'primary_access' => 'Продовжити через Do Better Norge / Google',
|
||||
'secondary_access' => 'Увійти з обліковим записом Caveau',
|
||||
'member_note' => 'Використайте свій обліковий запис Do Better Norge. Google-вхід відбувається на основному сайті, після чого ви безпечно повертаєтесь сюди.',
|
||||
'email' => 'Email',
|
||||
'password' => 'Пароль',
|
||||
'sign_in' => 'Увійти',
|
||||
'register' => 'Зареєструватися безкоштовно на dobetternorge.no',
|
||||
'cause_title' => 'Докази важливіші за обурення.',
|
||||
'cause_text' => 'Кожен інструмент побудований на тому самому принципі: задокументувати факти, процитувати право і зробити наступний практичний крок видимим.',
|
||||
'privacy_title' => 'Приватність за задумом',
|
||||
'privacy_text' => 'Файли за замовчуванням обробляються в памʼяті. Зберігаються лише технічні метадані: інструмент, затримка, мова та анонімний id сесії.',
|
||||
'source_title' => 'Джерела залишаються видимими',
|
||||
'source_text' => 'Дослідницькі інструменти показують цитати, розділи, уривки джерел і примітки про невизначеність поруч із відповіддю.',
|
||||
'tools_title' => 'Запущені інструменти',
|
||||
],
|
||||
'pl' => [
|
||||
'meta_title' => 'Do Better Norge - prawne narzędzia AI',
|
||||
'brand_line' => 'Do Better Norge - tools.dobetternorge.no',
|
||||
'suite_title' => 'Narzędzia prawne',
|
||||
'workspace_title' => 'Panel pracy nad sprawą',
|
||||
'session_active' => 'Sesja aktywna',
|
||||
'health' => 'Stan',
|
||||
'sign_out' => 'Wyloguj',
|
||||
'retention' => 'Sesja w pamięci - domyślnie nic nie jest zapisywane',
|
||||
'disclaimer' => 'Informacje prawne i wsparcie przygotowania, nie ostateczna porada prawna. Tekst i pliki są domyślnie przetwarzane w pamięci.',
|
||||
'manifesto_eyebrow' => 'Prawa rodzinne - Norwegia - od 2019',
|
||||
'manifesto_title' => 'Zabrali jej dziecko w dwanaście minut.',
|
||||
'manifesto_sub' => 'Otwórz narzędzie. Zbuduj chronologię, zbadaj prawo, chroń prywatność i przygotuj kolejny krok z cytowanymi źródłami.',
|
||||
'stat_echr' => 'naruszeń ETPC od 2015',
|
||||
'stat_loss' => 'spraw ETPC przegranych 2017-22',
|
||||
'stat_tribunal' => 'decyzji trybunałów przeanalizowano',
|
||||
'stat_pending' => 'spraw oczekuje w Strasburgu',
|
||||
'reasoning_eyebrow' => 'Plik - ślad dowodów',
|
||||
'reasoning_title' => 'Uzasadnienie',
|
||||
'waiting_title' => 'Oczekiwanie',
|
||||
'waiting_text' => 'Uruchom narzędzie, aby zobaczyć interpretację, źródła, pewność, niepewność i następny krok.',
|
||||
'dashboard_eyebrow' => 'Zatwierdzony pakiet narzędzi',
|
||||
'dashboard_title' => 'Wybierz prawne narzędzie AI',
|
||||
'dashboard_sub' => 'Dla rodzin, rzeczników i sojuszników przygotowujących norweskie sprawy rodzinne i dotyczące ochrony dzieci.',
|
||||
'open_tool' => 'Otwórz narzędzie',
|
||||
'landing_kicker' => 'Prawne przygotowanie AI dla spraw rodzinnych w Norwegii',
|
||||
'landing_title' => 'Narzędzia prawne dla rodzin, które muszą uporządkować akta sprawy.',
|
||||
'landing_sub' => 'Transkrybuj spotkania, buduj osie czasu, analizuj dokumenty Barnevernet, badaj ETPC i norweskie źródła oraz przygotowuj argumenty z cytatami.',
|
||||
'primary_access' => 'Kontynuuj przez Do Better Norge / Google',
|
||||
'secondary_access' => 'Zaloguj przez konto Caveau',
|
||||
'member_note' => 'Użyj konta Do Better Norge. Logowanie Google odbywa się na głównej stronie, a potem bezpiecznie wracasz tutaj.',
|
||||
'email' => 'Email',
|
||||
'password' => 'Hasło',
|
||||
'sign_in' => 'Zaloguj',
|
||||
'register' => 'Zarejestruj się bezpłatnie na dobetternorge.no',
|
||||
'cause_title' => 'Dowody ponad oburzenie.',
|
||||
'cause_text' => 'Każde narzędzie opiera się na tej samej zasadzie: udokumentować fakty, przytoczyć prawo i pokazać następny praktyczny krok.',
|
||||
'privacy_title' => 'Prywatność w projekcie',
|
||||
'privacy_text' => 'Pliki są domyślnie przetwarzane w pamięci. Aplikacja zapisuje tylko metadane operacyjne, takie jak narzędzie, czas, język i anonimowy identyfikator sesji.',
|
||||
'source_title' => 'Źródła pozostają widoczne',
|
||||
'source_text' => 'Narzędzia badawcze pokazują cytaty, sekcje, fragmenty źródeł i notatki o niepewności obok odpowiedzi.',
|
||||
'tools_title' => 'Uruchomione narzędzia',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function dbnToolsT(string $key, ?string $language = null): string
|
||||
{
|
||||
$language = dbnToolsNormalizeUiLanguage($language ?? dbnToolsCurrentLanguage());
|
||||
$all = dbnToolsTranslations();
|
||||
return (string)($all[$language][$key] ?? $all['en'][$key] ?? $key);
|
||||
}
|
||||
|
||||
function dbnToolsLaunchedTools(?string $language = null): array
|
||||
{
|
||||
$language = dbnToolsNormalizeUiLanguage($language ?? dbnToolsCurrentLanguage());
|
||||
$copy = [
|
||||
'en' => [
|
||||
'transcribe' => ['Transcribe', 'Audio and meetings', 'Turn audio or video into text with speaker separation and legal vocabulary support.', 'Whisper / GPU'],
|
||||
'timeline' => ['Timeline', 'Events and deadlines', 'Extract dates, hearings, Barnevernet milestones, and legal deadlines from notes or files.', 'Process-and-forget'],
|
||||
'barnevernet' => ['BVJ Analyzer', 'Barnevernet documents', 'Analyze child-welfare documents from your perspective with procedural red flags and citations.', 'Document + RAG'],
|
||||
'advocate' => ['Advocate', 'Partisan brief', 'Choose who you represent and generate a source-grounded brief for that position.', 'ECHR + Lovdata'],
|
||||
'deep-research' => ['Deep Research', 'Agent + RAG', 'Expand a question into research angles, search legal slices, and synthesize a cited brief.', 'Family-legal'],
|
||||
'corpus' => ['Corpus', 'Legal knowledge base', 'Inspect indexed sources, corpus health, legal categories, and retrieval behavior.', '~220 K passages'],
|
||||
],
|
||||
'no' => [
|
||||
'transcribe' => ['Transkriber', 'Lyd og møter', 'Gjør lyd eller video om til tekst med talerinndeling og juridisk ordforråd.', 'Whisper / GPU'],
|
||||
'timeline' => ['Tidslinje', 'Hendelser og frister', 'Hent ut datoer, møter, barnevernsmilepæler og juridiske frister fra notater eller filer.', 'Behandles og glemmes'],
|
||||
'barnevernet' => ['BVJ-analyse', 'Barnevernsdokumenter', 'Analyser barnevernsdokumenter fra ditt perspektiv med prosessuelle røde flagg og kilder.', 'Dokument + RAG'],
|
||||
'advocate' => ['Advokatmodus', 'Partsinnlegg', 'Velg hvem du representerer og lag et kildebelagt innlegg for den posisjonen.', 'EMD + Lovdata'],
|
||||
'deep-research' => ['Dyp research', 'Agent + RAG', 'Utvid et spørsmål til forskningsvinkler, søk juridiske kilder og lag et kildebelagt notat.', 'Familierett'],
|
||||
'corpus' => ['Korpus', 'Juridisk kunnskapsbase', 'Se indekserte kilder, korpushelse, juridiske kategorier og søkeoppsett.', '~220 K utdrag'],
|
||||
],
|
||||
'uk' => [
|
||||
'transcribe' => ['Транскрипція', 'Аудіо та зустрічі', 'Перетворюйте аудіо або відео на текст із розділенням мовців і юридичною лексикою.', 'Whisper / GPU'],
|
||||
'timeline' => ['Хронологія', 'Події та строки', 'Витягуйте дати, слухання, етапи Barnevernet і юридичні строки з нотаток або файлів.', 'Обробити і забути'],
|
||||
'barnevernet' => ['BVJ аналізатор', 'Документи Barnevernet', 'Аналізуйте документи захисту дітей з вашої позиції, з процесуальними ризиками та джерелами.', 'Документ + RAG'],
|
||||
'advocate' => ['Адвокат', 'Позиційний бриф', 'Оберіть, кого представляєте, і створіть бриф із джерелами на підтримку цієї позиції.', 'ЄСПЛ + Lovdata'],
|
||||
'deep-research' => ['Глибоке дослідження', 'Agent + RAG', 'Розгортає питання в дослідницькі напрями, шукає юридичні джерела та створює бриф.', 'Сімейне право'],
|
||||
'corpus' => ['Корпус', 'Юридична база знань', 'Переглядайте індексовані джерела, стан корпусу, категорії та поведінку пошуку.', '~220 тис. уривків'],
|
||||
],
|
||||
'pl' => [
|
||||
'transcribe' => ['Transkrypcja', 'Audio i spotkania', 'Zamień audio lub wideo na tekst z rozdzieleniem mówców i słownictwem prawnym.', 'Whisper / GPU'],
|
||||
'timeline' => ['Oś czasu', 'Wydarzenia i terminy', 'Wyodrębniaj daty, rozprawy, etapy Barnevernet i terminy prawne z notatek lub plików.', 'Przetwórz i zapomnij'],
|
||||
'barnevernet' => ['Analizator BVJ', 'Dokumenty Barnevernet', 'Analizuj dokumenty opieki nad dziećmi z Twojej perspektywy, z ryzykami proceduralnymi i źródłami.', 'Dokument + RAG'],
|
||||
'advocate' => ['Adwokat', 'Stronniczy brief', 'Wybierz, kogo reprezentujesz, i wygeneruj brief oparty na źródłach dla tej pozycji.', 'ETPC + Lovdata'],
|
||||
'deep-research' => ['Głębokie badanie', 'Agent + RAG', 'Rozwija pytanie w kierunki badawcze, przeszukuje źródła prawne i tworzy brief z cytatami.', 'Prawo rodzinne'],
|
||||
'corpus' => ['Korpus', 'Prawna baza wiedzy', 'Sprawdzaj indeksowane źródła, stan korpusu, kategorie prawne i działanie wyszukiwania.', '~220 tys. fragmentów'],
|
||||
],
|
||||
];
|
||||
|
||||
$selected = $copy[$language] ?? $copy['en'];
|
||||
$order = ['transcribe', 'timeline', 'barnevernet', 'advocate', 'deep-research', 'corpus'];
|
||||
$icons = [
|
||||
'transcribe' => 'TR',
|
||||
'timeline' => 'TL',
|
||||
'barnevernet' => 'BVJ',
|
||||
'advocate' => 'ADV',
|
||||
'deep-research' => 'DR',
|
||||
'corpus' => 'KB',
|
||||
];
|
||||
$out = [];
|
||||
foreach ($order as $slug) {
|
||||
[$label, $sub, $description, $badge] = $selected[$slug];
|
||||
$out[$slug] = [
|
||||
'label' => $label,
|
||||
'sub' => $sub,
|
||||
'description' => $description,
|
||||
'badge' => $badge,
|
||||
'url' => $slug . '.php',
|
||||
'icon' => $icons[$slug],
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
+49
-28
@@ -8,55 +8,76 @@ if (!dbnToolsIsAuthenticated()) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$navItems = [
|
||||
'ask' => ['Ask', 'Source-grounded'],
|
||||
'search' => ['Search', 'Legal sources'],
|
||||
'deep-research' => ['Deep research', 'Agent + RAG'],
|
||||
'advocate' => ['Advocate', 'Take a side'],
|
||||
'barnevernet' => ['BVJ Analyzer', 'Document'],
|
||||
'summarize' => ['Summarize', 'Pasted text'],
|
||||
'timeline' => ['Timeline', 'Events'],
|
||||
'redact' => ['Redact', 'Privacy'],
|
||||
'transcribe' => ['Transcribe', 'Audio'],
|
||||
'corpus' => ['Corpus', 'Data & stack'],
|
||||
];
|
||||
$toolName = $toolName ?? 'ask';
|
||||
$toolTitle = $toolTitle ?? 'Legal Tools';
|
||||
$toolKind = $toolKind ?? '';
|
||||
$toolBadge = $toolBadge ?? '';
|
||||
$uiLang = dbnToolsCurrentLanguage();
|
||||
$navItems = dbnToolsLaunchedTools($uiLang);
|
||||
$toolName = $toolName ?? 'transcribe';
|
||||
$toolMeta = $navItems[$toolName] ?? null;
|
||||
$toolTitle = $toolMeta['label'] ?? ($toolTitle ?? dbnToolsT('suite_title', $uiLang));
|
||||
$toolKind = $toolMeta['sub'] ?? ($toolKind ?? '');
|
||||
$toolBadge = $toolMeta['badge'] ?? ($toolBadge ?? '');
|
||||
$langPath = strtok((string)($_SERVER['REQUEST_URI'] ?? '/'), '?') ?: '/';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="<?= htmlspecialchars($uiLang) ?>">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= htmlspecialchars($toolTitle) ?> — Do Better Norge</title>
|
||||
<title><?= htmlspecialchars($toolTitle) ?> - Do Better Norge</title>
|
||||
<link rel="stylesheet" href="assets/css/tools.css">
|
||||
</head>
|
||||
<body data-authenticated="true" data-active-tool="<?= htmlspecialchars($toolName) ?>">
|
||||
<script>window.DBN_TOOLS_AUTHENTICATED = true;</script>
|
||||
<script>
|
||||
window.DBN_TOOLS_AUTHENTICATED = true;
|
||||
window.DBN_TOOLS_LANG = <?= json_encode($uiLang, JSON_UNESCAPED_UNICODE) ?>;
|
||||
</script>
|
||||
<main id="appShell" class="app-shell">
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<p class="eyebrow">Do Better Norge</p>
|
||||
<h1>Legal Tools</h1>
|
||||
<p class="eyebrow"><?= htmlspecialchars(dbnToolsT('brand_line', $uiLang)) ?></p>
|
||||
<h1><?= htmlspecialchars(dbnToolsT('suite_title', $uiLang)) ?> <span class="title-mark">.</span> <?= htmlspecialchars(dbnToolsT('workspace_title', $uiLang)) ?></h1>
|
||||
<div class="case-no">
|
||||
<span class="pulse"></span>
|
||||
<span>family-legal</span>
|
||||
<span class="case-sep">.</span>
|
||||
<span><?= htmlspecialchars(dbnToolsT('retention', $uiLang)) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topbar-actions">
|
||||
<span id="healthPill" class="status-pill">Session active</span>
|
||||
<button id="healthButton" class="secondary-button" type="button">Health</button>
|
||||
<nav class="shell-lang-switcher" aria-label="Language">
|
||||
<?php foreach (dbnToolsSupportedLanguages() as $langCode): ?>
|
||||
<a href="<?= htmlspecialchars($langPath . '?lang=' . $langCode) ?>" class="<?= $langCode === $uiLang ? 'is-active' : '' ?>"><?= htmlspecialchars(dbnToolsLanguageLabel($langCode)) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
<span id="healthPill" class="status-pill"><?= htmlspecialchars(dbnToolsT('session_active', $uiLang)) ?></span>
|
||||
<button id="healthButton" class="secondary-button" type="button"><?= htmlspecialchars(dbnToolsT('health', $uiLang)) ?></button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="manifesto" role="banner">
|
||||
<div class="manifesto-copy">
|
||||
<p class="manifesto-eyebrow"><?= htmlspecialchars(dbnToolsT('manifesto_eyebrow', $uiLang)) ?></p>
|
||||
<h2 class="manifesto-title"><?= htmlspecialchars(dbnToolsT('manifesto_title', $uiLang)) ?></h2>
|
||||
<p class="manifesto-sub"><?= htmlspecialchars(dbnToolsT('manifesto_sub', $uiLang)) ?></p>
|
||||
</div>
|
||||
<div class="manifesto-stats" aria-label="Headline statistics">
|
||||
<div class="manifesto-stat"><strong>23</strong><span><?= htmlspecialchars(dbnToolsT('stat_echr', $uiLang)) ?></span></div>
|
||||
<div class="manifesto-stat"><strong>64%</strong><span><?= htmlspecialchars(dbnToolsT('stat_loss', $uiLang)) ?></span></div>
|
||||
<div class="manifesto-stat"><strong>1,731</strong><span><?= htmlspecialchars(dbnToolsT('stat_tribunal', $uiLang)) ?></span></div>
|
||||
<div class="manifesto-stat"><strong>20+</strong><span><?= htmlspecialchars(dbnToolsT('stat_pending', $uiLang)) ?></span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="disclaimer" role="note">
|
||||
Legal information and preparation support, not final legal advice. Pasted text is processed in memory by default.
|
||||
<?= htmlspecialchars(dbnToolsT('disclaimer', $uiLang)) ?>
|
||||
</div>
|
||||
|
||||
<section class="workspace" aria-label="Legal tools workspace">
|
||||
<nav class="tool-rail" aria-label="Tools">
|
||||
<?php foreach ($navItems as $slug => [$label, $sub]): ?>
|
||||
<a href="<?= $slug ?>.php" class="tool-tab<?= $slug === $toolName ? ' is-active' : '' ?>" data-tool="<?= $slug ?>"<?= $slug === $toolName ? ' aria-current="page"' : '' ?>>
|
||||
<span><?= $label ?></span>
|
||||
<small><?= $sub ?></small>
|
||||
<?php foreach ($navItems as $slug => $item): ?>
|
||||
<a href="<?= htmlspecialchars($item['url']) ?>" class="tool-tab<?= $slug === $toolName ? ' is-active' : '' ?>" data-tool="<?= htmlspecialchars($slug) ?>"<?= $slug === $toolName ? ' aria-current="page"' : '' ?>>
|
||||
<span><?= htmlspecialchars($item['label']) ?></span>
|
||||
<small><?= htmlspecialchars($item['sub']) ?></small>
|
||||
<em><?= htmlspecialchars($item['icon']) ?></em>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
<?= $reasoningPanelOverride ?>
|
||||
<?php else: ?>
|
||||
<div class="reasoning-head">
|
||||
<p class="eyebrow">Evidence trail</p>
|
||||
<h2 id="reasoningTitle">Reasoning</h2>
|
||||
<p class="eyebrow"><?= htmlspecialchars(dbnToolsT('reasoning_eyebrow', $uiLang ?? dbnToolsCurrentLanguage())) ?></p>
|
||||
<h2 id="reasoningTitle"><?= htmlspecialchars(dbnToolsT('reasoning_title', $uiLang ?? dbnToolsCurrentLanguage())) ?></h2>
|
||||
</div>
|
||||
<ol id="traceList" class="trace-list">
|
||||
<li>
|
||||
<span class="trace-status waiting"></span>
|
||||
<div>
|
||||
<strong>Waiting</strong>
|
||||
<p>Run a tool to see interpretation, retrieval, confidence, uncertainty, and next step.</p>
|
||||
<strong><?= htmlspecialchars(dbnToolsT('waiting_title', $uiLang ?? dbnToolsCurrentLanguage())) ?></strong>
|
||||
<p><?= htmlspecialchars(dbnToolsT('waiting_text', $uiLang ?? dbnToolsCurrentLanguage())) ?></p>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
Reference in New Issue
Block a user