feat(dashboard): add corpus dashboard at /dashboard/

Full private corpus dashboard for tools.dobetternorge.no users — each SSO
account gets an auto-provisioned CaveauAI tenant (clients row, corpus) on
first visit. Includes upload (file/paste/URL), RAG chat with SSE streaming
and citation chips, document CRUD, FalkorDB graph relations tab, and
improved save-from-tool flow with tag/preview support.

- dashboard/{index,documents,document,upload,chat,settings}.php
- api/dashboard/{corpus-init,documents,upload,ingest-status,chat-stream,
  save-from-tool,graph}.php
- includes/{CorpusProvision,layout_dashboard,layout_dashboard_footer}.php
- assets/css/dashboard.css  assets/js/corpus-save.js (routing upgrade)
- includes/{bootstrap,layout}.php extended for dashboard provisioning

Migration 141 (clients.dbn_sso_uid + import_method enum) applied on chloe.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 17:15:40 +02:00
parent 83fc71414f
commit 06d01a3bce
20 changed files with 2632 additions and 28 deletions
+44
View File
@@ -365,6 +365,50 @@ function dbnToolsBootCaveau(): void
$booted = true;
}
/**
* Resolve (or lazily provision) the dashboard tenant for the current session.
*
* - CaveauAI sessions return the existing client_id/client_user_id and ensure a
* default corpus exists.
* - SSO sessions promote the user to their own CaveauAI client tenant on first
* call (via CorpusProvision), then cache the result on the session.
*
* Returns ['client_id', 'client_user_id', 'corpus_id', 'created'].
* Throws DbnToolsHttpException on auth/provisioning failure.
*/
function dbnToolsEnsureDashboardTenant(): array
{
if (!dbnToolsIsAuthenticated()) {
throw new DbnToolsHttpException('Dashboard requires an authenticated session.', 401, 'session_required');
}
$cached = $_SESSION['dbn_tools_dashboard_tenant'] ?? null;
if (is_array($cached) && !empty($cached['client_id']) && !empty($cached['corpus_id'])) {
return $cached + ['created' => false];
}
require_once __DIR__ . '/CorpusProvision.php';
if (dbnToolsIsFreeTier()) {
$ssoUid = (int)($_SESSION['dbn_tools_sso_uid'] ?? 0);
$email = (string)($_SESSION['dbn_tools_sso_email'] ?? $_SESSION['dbn_tools_user_email'] ?? '');
$displayName = (string)($_SESSION['dbn_tools_sso_name'] ?? $_SESSION['dbn_tools_user_name'] ?? '');
$tenant = CorpusProvision::ensureForSsoUser($ssoUid, $email, $displayName);
} else {
$clientId = (int)($_SESSION['dbn_tools_client_id'] ?? 0);
$email = (string)($_SESSION['dbn_tools_user_email'] ?? '');
$tenant = CorpusProvision::ensureForCaveauSession($clientId, $email);
$tenant['client_user_id'] = (int)($_SESSION['dbn_tools_user_id'] ?? $tenant['client_user_id']);
}
$_SESSION['dbn_tools_dashboard_tenant'] = [
'client_id' => (int)$tenant['client_id'],
'client_user_id' => (int)$tenant['client_user_id'],
'corpus_id' => (int)$tenant['corpus_id'],
];
return $tenant;
}
function dbnToolsDb(): PDO
{
dbnToolsBootCaveau();