feat(corpus): add save-to-corpus + private corpus search scope

- POST /api/save-to-corpus.php — saves tool output text to user's default CaveauAI corpus via ClientRagPipeline
- api/case/upload.php — dual-writes uploaded PDFs to CaveauAI client_documents (best-effort)
- assets/js/corpus-save.js — shared <dialog> handler for .js-save-corpus buttons on all tool pages
- includes/layout_footer.php — injects corpus-save.js + shared save dialog markup
- korrespond/deep-research/barnevernet/discrepancy JS — save-to-corpus buttons on output sections
- api/search.php + LegalTools::search() — corpus_scope param ('shared'|'private'|'both'), merges personal CaveauAI corpus with shared legal library when 'both'
- includes/tool_form.php + assets/js/tools.js — corpus scope radio toggle shown on search tab
- api/user-docs.php — add POST upload method for non-SSO authenticated users

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 17:50:32 +02:00
parent ed329f9d05
commit b014638f39
13 changed files with 465 additions and 33 deletions
+45 -5
View File
@@ -40,11 +40,51 @@ if (strncmp($head, '%PDF-', 5) !== 0) {
try {
$doc = CaseStore::registerUpload($userId, $name, $tmp, $size);
CaseStore::caseEnqueueIngest((int)$doc['doc_id'], $userId);
dbnToolsRespond([
'ok' => true,
'doc_id' => $doc['doc_id'],
'filename' => $doc['filename'],
]);
} catch (Throwable $e) {
dbnToolsError($e->getMessage(), 400, 'upload_failed');
}
// Dual-write to CaveauAI corpus (best-effort — never fails the upload)
$caveauDocId = null;
$clientId = (int)($_SESSION['dbn_tools_client_id'] ?? 0);
if ($clientId > 0 && !empty($doc['storage_path'])) {
try {
dbnToolsBootCaveau();
$aiPortalRoot = dbnToolsAiPortalRoot();
$textExtractFile = $aiPortalRoot . '/platform/includes/text_extract.php';
if (is_file($textExtractFile)) {
require_once $textExtractFile;
$content = extractPdfText($doc['storage_path']);
if ($content !== '' && strlen($content) > 30) {
$caveauDb = getDb();
$corpusSt = $caveauDb->prepare(
'SELECT id FROM client_corpora WHERE client_id = ? AND is_default = 1 LIMIT 1'
);
$corpusSt->execute([$clientId]);
$corpusId = (int)($corpusSt->fetchColumn() ?: 0);
if ($corpusId > 0) {
$title = pathinfo($doc['filename'], PATHINFO_FILENAME);
$caveauDb->prepare("
INSERT INTO client_documents
(client_id, corpus_id, title, source_type, content, category,
import_method, word_count, status)
VALUES (?, ?, ?, 'pdf', ?, 'user-upload', 'dbn_upload', ?, 'pending')
")->execute([$clientId, $corpusId, $title, $content, str_word_count($content)]);
$caveauDocId = (int)$caveauDb->lastInsertId();
$rag = new ClientRagPipeline($clientId);
$rag->ingestDocument($caveauDocId);
}
}
}
} catch (Throwable $e) {
// Non-fatal: log and continue
error_log('[upload] CaveauAI dual-write failed for doc ' . ($doc['doc_id'] ?? '?') . ': ' . $e->getMessage());
}
}
dbnToolsRespond([
'ok' => true,
'doc_id' => $doc['doc_id'],
'filename' => $doc['filename'],
'caveau_doc_id' => $caveauDocId,
]);