From 897771597e29da1697d098e8c178505c0467f1e0 Mon Sep 17 00:00:00 2001 From: davegilligan Date: Sun, 24 May 2026 15:38:30 +0200 Subject: [PATCH] Overhaul dashboard: account bar, enhanced tool cards with MCP slugs, MCP quick-start, tool reference section - Replace manifesto section with compact account overview bar (tier badge, credits breakdown, next refill/billing date, upgrade/manage/top-up CTAs) - Convert tool cards from to keyboard-accessible
with footer bar showing Open link, About link (advocate/timeline/korrespond), MCP slug copy button - Add collapsible MCP quick-start section: token prefix fetch, stdio config, remote HTTP config, copy buttons, link to full mcp.php docs - Add 3-column tool reference section for Advocate / Timeline / Korrespond with about/guide/tech links, description, and copyable MCP slug - All new sections fully localised: en / no / uk / pl Co-Authored-By: Claude Sonnet 4.6 --- dashboard.php | 627 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 537 insertions(+), 90 deletions(-) diff --git a/dashboard.php b/dashboard.php index e17b3b3..9504287 100644 --- a/dashboard.php +++ b/dashboard.php @@ -9,24 +9,233 @@ if (!dbnToolsIsAuthenticated()) { exit; } -$uiLang = dbnToolsCurrentLanguage(); -$tools = dbnToolsLaunchedTools($uiLang); +$uiLang = dbnToolsCurrentLanguage(); +$tools = dbnToolsLaunchedTools($uiLang); $workbench = dbnToolsWorkbenchMeta($uiLang); -$langPath = '/dashboard.php'; +$langPath = '/dashboard.php'; + +$dashIsSso = dbnToolsIsFreeTier(); +$dashUserId = $dashIsSso ? (int)($_SESSION['dbn_tools_sso_uid'] ?? 0) : 0; +$dashTier = $dashIsSso ? FreeTier::tier($dashUserId) : 'caveau'; +$dashDetail = $dashIsSso ? FreeTier::balanceDetail($dashUserId) : null; -// Tier + balance for SSO users (CaveauAI sessions get no panel) -$dashIsSso = dbnToolsIsFreeTier(); -$dashTier = $dashIsSso ? FreeTier::tier((int)$_SESSION['dbn_tools_sso_uid']) : 'caveau'; -$dashDetail = $dashIsSso ? FreeTier::balanceDetail((int)$_SESSION['dbn_tools_sso_uid']) : null; $tierLabels = [ - 'free' => ['Gratis', '#f3f4f6', '#374151'], - 'plus' => ['Plus', '#ddd6fe', '#5b21b6'], - 'pro' => ['Pro Familie', '#bfdbfe', '#1e40af'], + 'free' => ['Free', '#f3f4f6', '#374151'], + 'plus' => ['Plus', '#ddd6fe', '#5b21b6'], + 'pro' => ['Pro Familie', '#bfdbfe', '#1e40af'], + 'caveau' => ['CaveauAI', '#d1fae5', '#065f46'], ]; -$tierLabel = $tierLabels[$dashTier] ?? ['CaveauAI', '#d1fae5', '#065f46']; +$tierLabel = $tierLabels[$dashTier] ?? $tierLabels['free']; $showSurveyCta = $dashIsSso && empty($dashDetail['survey_completed_at']); +// User display name +$dashAuthUser = dbnToolsAuthenticatedUser(); +$dashEmail = ''; +if ($dashAuthUser !== null) { + $e = (string)($dashAuthUser['email'] ?? ''); + $dashEmail = strstr($e, '@', true) ?: $e; +} + +// Next refill / billing date +$dashNextBilling = ''; +$dashNextBillingKey = 'next_refill'; +if ($dashIsSso && $dashDetail) { + if (!empty($dashDetail['subscription_period_end'])) { + $ts = strtotime((string)$dashDetail['subscription_period_end']); + $dashNextBilling = $ts ? date('j M Y', $ts) : ''; + $dashNextBillingKey = 'next_billing'; + } else { + $m = (int)date('m'); $y = (int)date('Y'); + if ($m === 12) { $m = 1; $y++; } else { $m++; } + $dashNextBilling = date('j M Y', mktime(0, 0, 0, $m, 1, $y)); + } +} + +// Tool → MCP slug +$toolMcpSlugs = [ + 'transcribe' => 'dbn.transcribe_audio', + 'timeline' => 'dbn.timeline', + 'redact' => 'dbn.redact', + 'korrespond' => 'dbn.korrespond', + 'barnevernet' => 'dbn.barnevernet_analyze', + 'advocate' => 'dbn.advocate_brief', + 'deep-research' => 'dbn.deep_research', + 'discrepancy' => 'dbn.discrepancy_find', + 'corpus' => 'dbn.list_documents', + 'citations' => 'dbn.citation_graph', +]; + +// Tool → About page +$toolAboutPages = [ + 'advocate' => '/advocate-about.php', + 'timeline' => '/timeline-about.php', + 'korrespond' => '/korrespond-about.php', +]; + +// Localized strings for new sections +$dashL = [ + 'en' => [ + 'acct_header' => 'Account', + 'signed_in_as' => 'Signed in as', + 'manage_plan' => 'Manage plan', + 'upgrade_plan' => 'Upgrade plan', + 'top_up' => 'Top up credits', + 'next_refill' => 'Credits refill', + 'next_billing' => 'Next billing', + 'monthly_quota' => 'monthly quota', + 'trial_badge' => 'Trial — %d days left', + 'about_link' => 'About', + 'mcp_copy_slug' => 'Copy MCP slug', + 'mcp_section' => 'Developers & MCP', + 'mcp_desc' => 'Connect Claude Desktop, Claude Code, Cursor, or any MCP-compatible client to the full tool suite.', + 'mcp_token_lbl' => 'API token', + 'mcp_no_token' => 'No active token', + 'mcp_copy' => 'Copy', + 'mcp_not_avail' => 'MCP tokens require Plus or Pro — upgrade to connect AI clients.', + 'mcp_stdio_lbl' => 'Claude Desktop / Claude Code (stdio)', + 'mcp_remote_lbl' => 'Remote HTTP — Cursor, Zed, Windsurf', + 'mcp_full_docs' => 'Full setup guide & token management →', + 'tool_ref_title' => 'Tool reference', + 'tool_ref_sub' => 'Deep-dive docs for the three flagship tools.', + 'guide_link' => 'Guide', + 'tech_link' => 'Technical', + 'open_tool' => 'Open tool', + ], + 'no' => [ + 'acct_header' => 'Konto', + 'signed_in_as' => 'Innlogget som', + 'manage_plan' => 'Administrer plan', + 'upgrade_plan' => 'Oppgrader plan', + 'top_up' => 'Kjøp kreditter', + 'next_refill' => 'Kreditter fornyes', + 'next_billing' => 'Neste fakturering', + 'monthly_quota' => 'månedlig kvote', + 'trial_badge' => 'Prøveperiode — %d dager igjen', + 'about_link' => 'Om', + 'mcp_copy_slug' => 'Kopier MCP-slug', + 'mcp_section' => 'Utviklere & MCP', + 'mcp_desc' => 'Koble Claude Desktop, Claude Code, Cursor eller annen MCP-klient til hele verktøysuiten.', + 'mcp_token_lbl' => 'API-token', + 'mcp_no_token' => 'Ingen aktiv token', + 'mcp_copy' => 'Kopier', + 'mcp_not_avail' => 'MCP-tokens krever Plus eller Pro — oppgrader for å koble til AI-klienter.', + 'mcp_stdio_lbl' => 'Claude Desktop / Claude Code (stdio)', + 'mcp_remote_lbl' => 'Ekstern HTTP — Cursor, Zed, Windsurf', + 'mcp_full_docs' => 'Full oppsettsguide & token-administrasjon →', + 'tool_ref_title' => 'Verktøyreferanse', + 'tool_ref_sub' => 'Dybdedokumentasjon for de tre flaggskipverktøyene.', + 'guide_link' => 'Guide', + 'tech_link' => 'Teknisk', + 'open_tool' => 'Åpne verktøy', + ], + 'uk' => [ + 'acct_header' => 'Обліковий запис', + 'signed_in_as' => 'Ввійшли як', + 'manage_plan' => 'Управляти планом', + 'upgrade_plan' => 'Покращити план', + 'top_up' => 'Поповнити кредити', + 'next_refill' => 'Кредити поновлюються', + 'next_billing' => 'Наступне списання', + 'monthly_quota' => 'місячна квота', + 'trial_badge' => 'Пробний — %d дн. залишилось', + 'about_link' => 'Про', + 'mcp_copy_slug' => 'Копіювати MCP-ідентифікатор', + 'mcp_section' => 'Розробники & MCP', + 'mcp_desc' => 'Підключайте Claude Desktop, Claude Code, Cursor або будь-який MCP-клієнт до повного набору інструментів.', + 'mcp_token_lbl' => 'API-токен', + 'mcp_no_token' => 'Немає активного токена', + 'mcp_copy' => 'Копіювати', + 'mcp_not_avail' => 'MCP-токени потребують Plus або Pro — оновіться для підключення AI-клієнтів.', + 'mcp_stdio_lbl' => 'Claude Desktop / Claude Code (stdio)', + 'mcp_remote_lbl' => 'Віддалений HTTP — Cursor, Zed, Windsurf', + 'mcp_full_docs' => 'Повна документація та управління токенами →', + 'tool_ref_title' => 'Довідник інструментів', + 'tool_ref_sub' => 'Детальна документація трьох флагманських інструментів.', + 'guide_link' => 'Посібник', + 'tech_link' => 'Технічний', + 'open_tool' => 'Відкрити', + ], + 'pl' => [ + 'acct_header' => 'Konto', + 'signed_in_as' => 'Zalogowany jako', + 'manage_plan' => 'Zarządzaj planem', + 'upgrade_plan' => 'Ulepsz plan', + 'top_up' => 'Doładuj kredyty', + 'next_refill' => 'Kredyty odnawiają się', + 'next_billing' => 'Następne rozliczenie', + 'monthly_quota' => 'miesięczny limit', + 'trial_badge' => 'Próba — %d dni pozostało', + 'about_link' => 'O narzędziu', + 'mcp_copy_slug' => 'Kopiuj identyfikator MCP', + 'mcp_section' => 'Deweloperzy & MCP', + 'mcp_desc' => 'Podłącz Claude Desktop, Claude Code, Cursor lub dowolnego klienta MCP do pełnego zestawu narzędzi.', + 'mcp_token_lbl' => 'Token API', + 'mcp_no_token' => 'Brak aktywnego tokenu', + 'mcp_copy' => 'Kopiuj', + 'mcp_not_avail' => 'Tokeny MCP wymagają Plus lub Pro — zaktualizuj, aby połączyć klientów AI.', + 'mcp_stdio_lbl' => 'Claude Desktop / Claude Code (stdio)', + 'mcp_remote_lbl' => 'Zdalny HTTP — Cursor, Zed, Windsurf', + 'mcp_full_docs' => 'Pełna dokumentacja i zarządzanie tokenami →', + 'tool_ref_title' => 'Dokumentacja narzędzi', + 'tool_ref_sub' => 'Szczegółowa dokumentacja trzech flagowych narzędzi.', + 'guide_link' => 'Poradnik', + 'tech_link' => 'Techniczny', + 'open_tool' => 'Otwórz', + ], +]; +$dl = $dashL[$uiLang] ?? $dashL['en']; + +// Three flagship tools reference data +$toolRefCards = [ + 'advocate' => [ + 'icon' => '⚖️', + 'mcp' => 'dbn.advocate_brief', + 'about' => '/advocate-about.php', + 'guide' => '/advocate-guide.php', + 'tech' => '/advocate-tech.php', + 'tool' => '/advocate.php', + 'name' => ['en' => 'Advocate', 'no' => 'Advokat', 'uk' => 'Адвокат', 'pl' => 'Adwokat'], + 'desc' => [ + 'en' => 'AI-generated partisan brief grounded in Lovdata + ECHR, framed for your party role. 9 role presets, 220 K+ verified legal passages.', + 'no' => 'AI-generert partisk prosedyre basert på Lovdata + EMD, formulert for din partsrolle. 9 roller, 220 K+ verifiserte rettskilder.', + 'uk' => 'ШІ-стислий виклад позиції на основі Lovdata + ЄСПЛ. 9 ролей, 220 K+ верифікованих правових пасажів.', + 'pl' => 'Brief AI oparty na Lovdata + ETPC, z Twojej perspektywy. 9 ról, 220 K+ zweryfikowanych źródeł prawnych.', + ], + ], + 'timeline' => [ + 'icon' => '📅', + 'mcp' => 'dbn.timeline', + 'about' => '/timeline-about.php', + 'guide' => '/timeline-guide.php', + 'tech' => '/timeline-tech.php', + 'tool' => '/timeline.php', + 'name' => ['en' => 'Timeline', 'no' => 'Tidslinje', 'uk' => 'Хронологія', 'pl' => 'Oś czasu'], + 'desc' => [ + 'en' => 'Extract every key date from case documents into a confidence-scored chronology. Recognises 12+ Norwegian date formats, 5 event types.', + 'no' => 'Trekk ut alle viktige datoer til en konfidensscorert kronologi. Gjenkjenner 12+ norske datoformater og 5 hendelsestyper.', + 'uk' => 'Витягуйте ключові дати з документів справи. 12+ форматів, 5 типів подій, оцінка достовірності.', + 'pl' => 'Wyodrębnij daty z dokumentów sprawy w ocenioną chronologię. 12+ formatów dat, 5 typów zdarzeń.', + ], + ], + 'korrespond' => [ + 'icon' => '✉️', + 'mcp' => 'dbn.korrespond', + 'about' => '/korrespond-about.php', + 'guide' => '/korrespond-guide.php', + 'tech' => '/korrespond-tech.php', + 'tool' => '/korrespond.php', + 'name' => ['en' => 'Korrespond', 'no' => 'Korrespond', 'uk' => 'Кореспонд', 'pl' => 'Korrespond'], + 'desc' => [ + 'en' => 'Statute-grounded authority letters in Norwegian bokmål + your language, side-by-side. Hard-RAG: every § verified — no hallucinated citations.', + 'no' => 'Lovsikre myndighetsbrev på norsk bokmål + ditt språk, side ved side. Hard-RAG: alle §§ verifisert — ingen hallusinerte kilder.', + 'uk' => 'Офіційні листи, підкріплені законами, норвезькою та вашою мовою. Hard-RAG: кожен § перевірений.', + 'pl' => 'Pisma urzędowe oparte na przepisach, po norwesku i w Twoim języku. Hard-RAG: każdy § zweryfikowany.', + ], + ], +]; + require_once __DIR__ . '/includes/tool-svgs.php'; +$langSuffix = $uiLang !== 'en' ? '?lang=' . urlencode($uiLang) : ''; ?> @@ -41,6 +250,19 @@ require_once __DIR__ . '/includes/tool-svgs.php'; + + +/* ── Corpus summary ─────────────────────────────────────────────────── */ - + +/* ── MCP section ────────────────────────────────────────────────────── */ +(function () { + /* chevron toggle */ + var det = document.querySelector('details.dash-mcp-details'); + if (det) { + det.addEventListener('toggle', function () { + var chev = det.querySelector('.dash-chevron'); + if (chev) chev.style.transform = det.open ? 'rotate(180deg)' : ''; + }); + } + + + /* load token prefix */ + fetch('/api/mcp-tokens.php', { credentials: 'same-origin' }) + .then(function (r) { return r.ok ? r.json() : null; }) + .then(function (data) { + if (!data || !data.ok) return; + var active = (data.tokens || []).filter(function (t) { return t.is_active; }); + var prefixEl = document.getElementById('dashMcpTokenPrefix'); + if (prefixEl) { + prefixEl.textContent = active.length > 0 + ? active[0].token_prefix + '…' + : ; + } + if (active.length > 0) { + var p = active[0].token_prefix + '…'; + var s = document.getElementById('dashStdioToken'); + var r = document.getElementById('dashRemoteToken'); + if (s) s.textContent = p; + if (r) r.textContent = p; + } + }) + .catch(function () {}); + + + /* copy a pre block */ + window.copyDashBlock = function (id, btn) { + var el = document.getElementById(id); + if (!el) return; + navigator.clipboard.writeText(el.innerText || el.textContent).then(function () { + var orig = btn.textContent; btn.textContent = '✓'; + setTimeout(function () { btn.textContent = orig; }, 1500); + }).catch(function () {}); + }; + + /* copy remote HTTP config */ + window.copyDashRemote = function (btn) { + var tok = document.getElementById('dashRemoteToken'); + var txt = 'URL: https://mcp.dobetternorge.no/mcp\nAuthorization: Bearer ' + (tok ? tok.textContent : 'dbn_user_mcp_…'); + navigator.clipboard.writeText(txt).then(function () { + var orig = btn.textContent; btn.textContent = '✓'; + setTimeout(function () { btn.textContent = orig; }, 1500); + }).catch(function () {}); + }; + + /* copy MCP slug from tool card button */ + window.copyDashSlug = function (btn) { + var slug = btn.getAttribute('data-slug'); + if (!slug) return; + navigator.clipboard.writeText(slug).then(function () { + var orig = btn.textContent; + btn.textContent = '✓ copied'; + btn.style.cssText += '; color:#065f46 !important; background:#d1fae5 !important; border-color:#6ee7b7 !important;'; + setTimeout(function () { + btn.textContent = orig; + btn.style.color = ''; + btn.style.background = ''; + btn.style.borderColor = ''; + }, 1500); + }).catch(function () {}); + }; + + /* copy MCP slug from tool reference cards */ + document.querySelectorAll('[data-copy-slug]').forEach(function (el) { + el.addEventListener('click', function () { + var slug = el.getAttribute('data-copy-slug'); + if (!slug) return; + navigator.clipboard.writeText(slug).then(function () { + var orig = el.textContent; el.textContent = '✓'; + setTimeout(function () { el.textContent = orig; }, 1200); + }).catch(function () {}); + }); + }); +}()); +