Add Citation Explorer tool and graph-expansion badges to Advocate results
- citations.php + assets/js/citations.js: new tool page for browsing the FalkorDB citation graph by title/ID, with autocomplete, action pills (cites/cited_by/implements/chain), hop-by-hop navigation, and exploration trail - advocate.js: tag graph-expanded source cards with 'via citation graph' badge - DeepResearchAgent: propagate _graph_expanded flag through normalizeCorpusChunk and top_sources serialization so it reaches the frontend - tools.css: add .dr-source-tag--graph variant (green pill) - i18n.php: register 'citations' tool in all 4 languages with CIT icon Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2673,6 +2673,7 @@ p {
|
||||
|
||||
.dr-source-tag--upload { background: #fff0e8; color: #8a4524; }
|
||||
.dr-source-tag--score { background: #eef3fb; color: #314158; }
|
||||
.dr-source-tag--graph { background: #f0fdf4; color: #166534; }
|
||||
|
||||
.dr-source-excerpt {
|
||||
color: var(--muted);
|
||||
|
||||
@@ -833,6 +833,7 @@
|
||||
const score = s.reranker_score != null ? s.reranker_score : s.similarity;
|
||||
const originTagClass = s.source_origin === 'upload' ? 'dr-source-tag dr-source-tag--upload' : 'dr-source-tag';
|
||||
const originLabel = s.source_origin === 'upload' ? 'upload' : 'corpus';
|
||||
const graphExpanded = s.graph_expanded === true;
|
||||
const link = s.deep_link || s.source_url;
|
||||
const titleHtml = link
|
||||
? `<a href="${escapeHtml(link)}" target="_blank" rel="noopener" class="dr-source-title-link">${escapeHtml(s.title || 'Untitled')} <span class="dr-external-link" aria-hidden="true">↗</span></a>`
|
||||
@@ -844,6 +845,7 @@
|
||||
${s.section ? `<div class="dr-source-meta"><span class="dr-source-tag">${escapeHtml(s.section)}</span></div>` : ''}
|
||||
<div class="dr-source-meta">
|
||||
<span class="${originTagClass}">${originLabel}</span>
|
||||
${graphExpanded ? `<span class="dr-source-tag dr-source-tag--graph">via citation graph</span>` : ''}
|
||||
${s.authority_label ? `<span class="dr-source-tag">${escapeHtml(s.authority_label)}</span>` : ''}
|
||||
<span class="dr-source-tag dr-source-tag--score">${escapeHtml(s.package_or_corpus || '—')}</span>
|
||||
${(s.matched_sub_questions || []).map((q) => `<span class="dr-source-tag">${escapeHtml(q)}</span>`).join('')}
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
if (document.body.dataset.activeTool !== 'citations') return;
|
||||
|
||||
const form = document.getElementById('citationsForm');
|
||||
const titleInput = document.getElementById('titleInput');
|
||||
const docIdInput = document.getElementById('docIdHidden');
|
||||
const acList = document.getElementById('citAutocomplete');
|
||||
const depthRow = document.getElementById('depthRow');
|
||||
const depthRange = document.getElementById('depthRange');
|
||||
const depthValue = document.getElementById('depthValue');
|
||||
const runBtn = document.getElementById('citeRunBtn');
|
||||
const statusEl = document.getElementById('citStatus');
|
||||
const resultsDiv = document.getElementById('citationResults');
|
||||
const sourceCard = document.getElementById('citSourceCard');
|
||||
const resultsArea = document.getElementById('citResultsArea');
|
||||
const trailList = document.getElementById('trailList');
|
||||
|
||||
const trail = [];
|
||||
|
||||
// ── Utilities ─────────────────────────────────────────────────────────────
|
||||
|
||||
function esc(s) {
|
||||
return String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function setStatus(msg, kind) {
|
||||
statusEl.textContent = msg;
|
||||
statusEl.style.color = kind === 'error' ? '#b41e1e' : kind === 'ok' ? 'var(--teal-dark)' : 'var(--muted)';
|
||||
}
|
||||
|
||||
function debounce(fn, ms) {
|
||||
let t;
|
||||
return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); };
|
||||
}
|
||||
|
||||
function authorityLabel(atype) {
|
||||
const map = {
|
||||
case_law: 'Case law', guidance: 'Guidance', report: 'Report',
|
||||
law: 'Statute', treaty: 'Treaty', ombudsman: 'Ombudsman',
|
||||
tribunal: 'Tribunal', regulatory: 'Regulatory',
|
||||
};
|
||||
return map[atype] || atype || '';
|
||||
}
|
||||
|
||||
function relChipClass(action, relType) {
|
||||
if (action === 'chain') return 'rel-chip--reachable';
|
||||
if (action === 'cited_by') return 'rel-chip--cited-by';
|
||||
if (action === 'cites') return 'rel-chip--cites';
|
||||
const rt = (relType || '').toUpperCase();
|
||||
if (rt === 'REPEALS') return 'rel-chip--repeals';
|
||||
return 'rel-chip--implements';
|
||||
}
|
||||
|
||||
function relChipLabel(action, relType) {
|
||||
if (action === 'chain') return 'Reachable';
|
||||
if (action === 'cited_by') return 'Cited by';
|
||||
if (action === 'cites') return 'Cites';
|
||||
if (relType) return relType.replace(/_/g, ' ');
|
||||
return 'Implements';
|
||||
}
|
||||
|
||||
function yearFrom(dateStr) {
|
||||
if (!dateStr) return '';
|
||||
return String(dateStr).slice(0, 4);
|
||||
}
|
||||
|
||||
// ── Autocomplete ──────────────────────────────────────────────────────────
|
||||
|
||||
function hideAc() { acList.hidden = true; }
|
||||
|
||||
function renderAc(docs) {
|
||||
if (!docs.length) { hideAc(); return; }
|
||||
acList.innerHTML = docs.map(d => {
|
||||
const al = authorityLabel(d.authority_type);
|
||||
return `<li role="option" data-id="${esc(String(d.id))}" data-title="${esc(d.title || '')}">`
|
||||
+ esc(d.title || '(Untitled)')
|
||||
+ (al ? `<span class="cit-ac-atype">${esc(al)}</span>` : '')
|
||||
+ `</li>`;
|
||||
}).join('');
|
||||
acList.hidden = false;
|
||||
}
|
||||
|
||||
const fetchAc = debounce(async (q) => {
|
||||
if (!q || q.length < 2) { hideAc(); return; }
|
||||
try {
|
||||
const res = await fetch('api/corpus-documents.php?' + new URLSearchParams({ title: q, limit: 15 }), { credentials: 'same-origin' });
|
||||
if (!res.ok) { hideAc(); return; }
|
||||
const data = await res.json();
|
||||
renderAc(data.documents || []);
|
||||
} catch { hideAc(); }
|
||||
}, 300);
|
||||
|
||||
titleInput.addEventListener('input', (e) => {
|
||||
const val = e.target.value.trim();
|
||||
if (/^\d+$/.test(val)) {
|
||||
docIdInput.value = val;
|
||||
hideAc();
|
||||
} else {
|
||||
docIdInput.value = '';
|
||||
fetchAc(val);
|
||||
}
|
||||
});
|
||||
|
||||
acList.addEventListener('mousedown', (e) => {
|
||||
const li = e.target.closest('li[data-id]');
|
||||
if (!li) return;
|
||||
e.preventDefault();
|
||||
titleInput.value = li.dataset.title;
|
||||
docIdInput.value = li.dataset.id;
|
||||
hideAc();
|
||||
});
|
||||
|
||||
titleInput.addEventListener('blur', () => setTimeout(hideAc, 150));
|
||||
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') hideAc(); });
|
||||
|
||||
// ── Action radios + depth slider ──────────────────────────────────────────
|
||||
|
||||
document.querySelectorAll('input[name="citAction"]').forEach(r => {
|
||||
r.addEventListener('change', () => { depthRow.hidden = r.value !== 'chain'; });
|
||||
});
|
||||
|
||||
depthRange.addEventListener('input', () => { depthValue.textContent = depthRange.value; });
|
||||
|
||||
// ── Form submit ───────────────────────────────────────────────────────────
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
let docId = parseInt(docIdInput.value, 10) || parseInt(titleInput.value.trim(), 10);
|
||||
if (!docId || docId <= 0) {
|
||||
setStatus('Enter a document title or numeric ID.', 'error');
|
||||
titleInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const action = document.querySelector('input[name="citAction"]:checked').value;
|
||||
const params = { action, doc_id: docId, limit: 50 };
|
||||
if (action === 'chain') params.depth = parseInt(depthRange.value, 10);
|
||||
|
||||
setStatus('Querying citation graph…', 'busy');
|
||||
runBtn.disabled = true;
|
||||
|
||||
try {
|
||||
const url = 'https://ai.bluenotelogic.com/api/graph-search.php?' + new URLSearchParams(params);
|
||||
const res = await fetch(url, { credentials: 'omit' });
|
||||
|
||||
if (res.status === 404) {
|
||||
setStatus('', '');
|
||||
renderNotFound(docId);
|
||||
return;
|
||||
}
|
||||
if (!res.ok) {
|
||||
setStatus('Graph error (' + res.status + ').', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
if (data.error) { setStatus(data.error, 'error'); return; }
|
||||
|
||||
setStatus('', '');
|
||||
renderResults(data, action);
|
||||
addToTrail(data.source);
|
||||
|
||||
} catch (err) {
|
||||
setStatus('Network error: ' + err.message, 'error');
|
||||
} finally {
|
||||
runBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// ── Render ────────────────────────────────────────────────────────────────
|
||||
|
||||
function renderNotFound(docId) {
|
||||
resultsDiv.hidden = false;
|
||||
sourceCard.innerHTML = '';
|
||||
resultsArea.innerHTML = '<p class="cit-no-results">Document '
|
||||
+ esc(String(docId))
|
||||
+ ' is not in the citation graph yet. The graph grows as new documents are scraped and resolved.</p>';
|
||||
}
|
||||
|
||||
function renderResults(data, action) {
|
||||
const src = data.source;
|
||||
const atLabel = authorityLabel(src.atype);
|
||||
const year = yearFrom(src.valid_from);
|
||||
|
||||
sourceCard.innerHTML =
|
||||
'<p class="cit-source-card__title">' + esc(src.title || '(Untitled)') + '</p>'
|
||||
+ '<div class="cit-source-card__meta">'
|
||||
+ (atLabel ? '<span class="source-badge badge--teal">' + esc(atLabel) + '</span>' : '')
|
||||
+ (year ? '<span>' + esc(year) + '</span>' : '')
|
||||
+ (src.jurisdiction ? '<span>' + esc(src.jurisdiction) + '</span>' : '')
|
||||
+ '<span style="color:var(--muted)">ID ' + esc(String(src.doc_id)) + '</span>'
|
||||
+ (src.source_url ? '<a href="' + esc(src.source_url) + '" target="_blank" rel="noopener" style="color:var(--teal)">Source ↗</a>' : '')
|
||||
+ '</div>';
|
||||
|
||||
const results = data.results || [];
|
||||
const count = data.count ?? results.length;
|
||||
|
||||
const actionLabels = {
|
||||
cites: 'Documents cited',
|
||||
cited_by: 'Documents citing this',
|
||||
implements: 'Documents implementing / amending',
|
||||
chain: 'Reachable documents (citation chain)',
|
||||
};
|
||||
|
||||
let html = '<div class="cit-results-header">'
|
||||
+ '<p class="eyebrow">' + esc(actionLabels[action] || action) + '</p>'
|
||||
+ '<span class="cit-results-count">' + esc(String(count)) + ' document' + (count !== 1 ? 's' : '') + '</span>'
|
||||
+ '</div>';
|
||||
|
||||
if (results.length === 0) {
|
||||
html += '<p class="cit-no-results">No connected documents found for this action.</p>';
|
||||
} else {
|
||||
html += '<div class="cit-grid">';
|
||||
for (const r of results) {
|
||||
const chipCls = relChipClass(action, r.rel_type);
|
||||
const chipLbl = relChipLabel(action, r.rel_type);
|
||||
const unresolved = r.doc_id === null || r.doc_id === undefined;
|
||||
const cardTitle = r.title || '';
|
||||
const atl = authorityLabel(r.atype);
|
||||
const yr = yearFrom(r.valid_from);
|
||||
|
||||
html += '<div class="cit-card' + (unresolved ? ' cit-card--unresolved' : '') + '">'
|
||||
+ '<span class="rel-chip ' + esc(chipCls) + '">' + esc(chipLbl) + '</span>'
|
||||
+ (cardTitle ? '<span class="cit-card__title">' + esc(cardTitle) + '</span>' : '')
|
||||
+ (!cardTitle && r.ref ? '<span class="cit-card__ref">' + esc(r.ref) + '</span>' : '')
|
||||
+ '<div class="cit-card__meta">'
|
||||
+ (atl ? '<span class="source-badge badge--muted">' + esc(atl) + '</span>' : '')
|
||||
+ (yr ? '<span>' + esc(yr) + '</span>' : '')
|
||||
+ '</div>'
|
||||
+ (!unresolved ? '<button class="cit-explore-btn" type="button" data-explore-id="' + esc(String(r.doc_id)) + '" data-explore-title="' + esc(cardTitle) + '">Explore →</button>' : '')
|
||||
+ '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
resultsArea.innerHTML = html;
|
||||
resultsDiv.hidden = false;
|
||||
|
||||
resultsArea.querySelectorAll('.cit-explore-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => exploreDoc(parseInt(btn.dataset.exploreId, 10), btn.dataset.exploreTitle));
|
||||
});
|
||||
}
|
||||
|
||||
// ── Trail ─────────────────────────────────────────────────────────────────
|
||||
|
||||
function addToTrail(source) {
|
||||
if (trail.length && trail[trail.length - 1].doc_id === source.doc_id) return;
|
||||
trail.push({ doc_id: source.doc_id, title: source.title || '(Untitled)', atype: source.atype });
|
||||
renderTrail();
|
||||
}
|
||||
|
||||
function renderTrail() {
|
||||
const placeholder = document.getElementById('trailPlaceholder');
|
||||
if (placeholder) placeholder.remove();
|
||||
|
||||
trailList.innerHTML = '';
|
||||
for (let i = trail.length - 1; i >= 0; i--) {
|
||||
const entry = trail[i];
|
||||
const isCurrent = i === trail.length - 1;
|
||||
const li = document.createElement('li');
|
||||
li.className = 'cit-trail-item' + (isCurrent ? ' is-active' : '');
|
||||
li.innerHTML =
|
||||
'<span class="trace-status ' + (isCurrent ? 'running' : 'complete') + '"></span>'
|
||||
+ '<div>'
|
||||
+ '<strong>' + esc(entry.title) + '</strong>'
|
||||
+ '<p>' + esc(authorityLabel(entry.atype) || 'Document') + ' · ID ' + esc(String(entry.doc_id)) + '</p>'
|
||||
+ '</div>';
|
||||
if (!isCurrent) {
|
||||
li.addEventListener('click', () => exploreDoc(entry.doc_id, entry.title));
|
||||
}
|
||||
trailList.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Navigation ────────────────────────────────────────────────────────────
|
||||
|
||||
function exploreDoc(docId, title) {
|
||||
titleInput.value = title || String(docId);
|
||||
docIdInput.value = String(docId);
|
||||
hideAc();
|
||||
form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
|
||||
}
|
||||
|
||||
})();
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
$toolName = 'citations';
|
||||
$extraScripts = ['assets/js/citations.js'];
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="reasoning-head">
|
||||
<p class="eyebrow">Exploration trail</p>
|
||||
<h2 id="reasoningTitle">Navigation history</h2>
|
||||
</div>
|
||||
<ol id="trailList" class="trace-list" aria-live="polite">
|
||||
<li id="trailPlaceholder">
|
||||
<span class="trace-status waiting"></span>
|
||||
<div>
|
||||
<strong>Nothing explored yet</strong>
|
||||
<p>Search for a document to start.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
<?php
|
||||
$reasoningPanelOverride = ob_get_clean();
|
||||
require_once __DIR__ . '/includes/layout.php';
|
||||
?>
|
||||
|
||||
<style>
|
||||
.cit-search-wrap { position: relative; }
|
||||
.cit-autocomplete {
|
||||
position: absolute; top: 100%; left: 0; right: 0; z-index: 50;
|
||||
background: var(--panel); border: 1px solid var(--line);
|
||||
border-radius: 0 0 8px 8px; box-shadow: 0 6px 20px rgba(0,0,0,0.12);
|
||||
list-style: none; margin: 0; padding: 0; max-height: 280px; overflow-y: auto;
|
||||
}
|
||||
.cit-autocomplete li {
|
||||
padding: 9px 14px; cursor: pointer; font-size: 0.87rem;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.cit-autocomplete li:last-child { border-bottom: none; }
|
||||
.cit-autocomplete li:hover, .cit-autocomplete li.is-focused { background: var(--soft-teal); }
|
||||
.cit-autocomplete .cit-ac-atype { font-size: 0.72rem; color: var(--muted); margin-left: 6px; }
|
||||
|
||||
.cit-source-card {
|
||||
background: var(--panel); border: 1px solid var(--teal);
|
||||
border-radius: 8px; padding: 14px 16px; margin-bottom: 16px;
|
||||
}
|
||||
.cit-source-card__title { font-size: 1rem; font-weight: 700; color: var(--ink); margin: 0 0 6px; }
|
||||
.cit-source-card__meta {
|
||||
display: flex; flex-wrap: wrap; gap: 8px;
|
||||
align-items: center; font-size: 0.78rem; color: var(--muted);
|
||||
}
|
||||
|
||||
.cit-results-header {
|
||||
display: flex; align-items: baseline;
|
||||
justify-content: space-between; margin-bottom: 10px;
|
||||
}
|
||||
.cit-results-count { font-size: 0.78rem; color: var(--muted); }
|
||||
|
||||
.cit-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.cit-card {
|
||||
background: var(--panel); border: 1px solid var(--line);
|
||||
border-radius: 8px; padding: 12px 14px;
|
||||
display: flex; flex-direction: column; gap: 6px;
|
||||
}
|
||||
.cit-card--unresolved { opacity: 0.72; }
|
||||
|
||||
.rel-chip {
|
||||
display: inline-block; font-size: 0.68rem; font-weight: 700;
|
||||
letter-spacing: 0.04em; text-transform: uppercase;
|
||||
padding: 2px 8px; border-radius: 99px; align-self: flex-start;
|
||||
}
|
||||
.rel-chip--cites { background: var(--soft-teal); color: var(--teal-dark); }
|
||||
.rel-chip--cited-by { background: #dbeafe; color: #1d4ed8; }
|
||||
.rel-chip--implements { background: #fef3cd; color: var(--amber); }
|
||||
.rel-chip--repeals { background: var(--soft-coral); color: var(--coral); }
|
||||
.rel-chip--reachable { background: #eef0f5; color: var(--muted); }
|
||||
|
||||
.cit-card__title { font-size: 0.87rem; font-weight: 700; color: var(--ink); line-height: 1.3; }
|
||||
.cit-card__ref { font-size: 0.82rem; color: var(--muted); font-style: italic; }
|
||||
.cit-card__meta { font-size: 0.75rem; color: var(--muted); display: flex; gap: 8px; }
|
||||
|
||||
.cit-explore-btn {
|
||||
margin-top: auto; align-self: flex-start;
|
||||
font-size: 0.78rem; padding: 3px 12px;
|
||||
border: 1px solid var(--teal); border-radius: 4px;
|
||||
background: var(--soft-teal); color: var(--teal-dark); cursor: pointer;
|
||||
}
|
||||
.cit-explore-btn:hover { background: var(--teal); color: #fff; }
|
||||
|
||||
.cit-no-results { font-size: 0.88rem; color: var(--muted); font-style: italic; padding: 12px 0; }
|
||||
|
||||
.cit-trail-item { cursor: pointer; }
|
||||
.cit-trail-item:hover strong { text-decoration: underline; color: var(--teal); }
|
||||
.cit-trail-item.is-active { background: var(--soft-teal); border-radius: 6px; padding: 4px 6px; }
|
||||
</style>
|
||||
|
||||
<form id="citationsForm" class="tool-form" autocomplete="off" novalidate>
|
||||
|
||||
<label class="input-label" for="titleInput">Document title or ID</label>
|
||||
<div class="cit-search-wrap">
|
||||
<input type="text" id="titleInput" class="corpus-search-input"
|
||||
placeholder="e.g. Barnevernloven, HR-2020-661-A, LOV-1999-07-02-63, 12345"
|
||||
autocomplete="off" spellcheck="false"
|
||||
aria-autocomplete="list" aria-haspopup="listbox" aria-controls="citAutocomplete">
|
||||
<ul id="citAutocomplete" class="cit-autocomplete" hidden role="listbox" aria-label="Matching documents"></ul>
|
||||
</div>
|
||||
<input type="hidden" id="docIdHidden">
|
||||
|
||||
<div class="control-row" role="group" aria-label="Relationship direction">
|
||||
<label><input type="radio" name="citAction" value="cites" checked> Cites</label>
|
||||
<label><input type="radio" name="citAction" value="cited_by"> Cited by</label>
|
||||
<label><input type="radio" name="citAction" value="implements"> Implements / amends</label>
|
||||
<label><input type="radio" name="citAction" value="chain"> Citation chain</label>
|
||||
</div>
|
||||
|
||||
<div id="depthRow" class="control-row" hidden>
|
||||
<label class="input-label" for="depthRange" style="margin:0">
|
||||
Chain depth: <strong id="depthValue">2</strong> hops
|
||||
</label>
|
||||
<input type="range" id="depthRange" min="1" max="3" value="2" style="width:120px">
|
||||
</div>
|
||||
|
||||
<div class="form-footer">
|
||||
<button type="submit" id="citeRunBtn" class="secondary-button">Explore</button>
|
||||
<span id="citStatus" class="form-status"></span>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<div id="citationResults" hidden>
|
||||
<div id="citSourceCard" class="cit-source-card"></div>
|
||||
<div id="citResultsArea"></div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/layout_footer.php'; ?>
|
||||
@@ -374,6 +374,7 @@ final class DbnDeepResearchAgent
|
||||
'source_url' => $s['source_url'] ?? null,
|
||||
'source_origin' => $s['source_origin'] ?? 'corpus',
|
||||
'authority_label'=> $s['authority_label'] ?? null,
|
||||
'graph_expanded' => $s['graph_expanded'] ?? false,
|
||||
'excerpt' => $s['excerpt'] ?? '',
|
||||
], $topSources),
|
||||
];
|
||||
@@ -733,6 +734,7 @@ PROMPT;
|
||||
'similarity' => $similarity,
|
||||
'reranker_score' => $rerankerScore,
|
||||
'document_id' => isset($chunk['document_id']) ? (int)$chunk['document_id'] : null,
|
||||
'graph_expanded' => !empty($chunk['_graph_expanded']),
|
||||
'source_origin' => 'corpus',
|
||||
'authority_type' => $chunk['authority_type'] ?? null,
|
||||
'jurisdiction' => $chunk['jurisdiction'] ?? null,
|
||||
|
||||
+6
-1
@@ -446,6 +446,7 @@ function dbnToolsLaunchedTools(?string $language = null): array
|
||||
'deep-research' => ['Deep Research', 'Agent + RAG', 'Expand a question into research angles, search legal slices, and synthesize a cited brief.', 'Family-legal'],
|
||||
'discrepancy' => ['Discrepancy Finder', 'Document comparison', 'Upload two versions of a Barnevernet document and find contradictions, deleted facts, and new allegations.', 'Cross-document AI'],
|
||||
'corpus' => ['Corpus', 'Legal knowledge base', 'Inspect indexed sources, corpus health, legal categories, and retrieval behavior.', '~220 K passages'],
|
||||
'citations' => ['Citations', 'Citation graph', 'Browse the legal citation graph — what a statute cites, what cites it, and what implements or amends it.', 'Graph topology'],
|
||||
],
|
||||
'no' => [
|
||||
'transcribe' => ['Transkriber', 'Lyd og møter', 'Gjør lyd eller video om til tekst med talerinndeling og juridisk ordforråd.', 'Whisper / GPU'],
|
||||
@@ -456,6 +457,7 @@ function dbnToolsLaunchedTools(?string $language = null): array
|
||||
'deep-research' => ['Dyp research', 'Agent + RAG', 'Utvid et spørsmål til forskningsvinkler, søk juridiske kilder og lag et kildebelagt notat.', 'Familierett'],
|
||||
'discrepancy' => ['Avviksfinner', 'Dokumentsammenligning', 'Last opp to versjoner av et barneverndokument og finn motsigelser, slettede fakta og nye påstander.', 'Kryssdokument AI'],
|
||||
'corpus' => ['Korpus', 'Juridisk kunnskapsbase', 'Se indekserte kilder, korpushelse, juridiske kategorier og søkeoppsett.', '~220 K utdrag'],
|
||||
'citations' => ['Siteringer', 'Siteringsgraf', 'Utforsk siteringsgrafen — hva et dokument siterer, hva som siterer det, og hva som implementerer det.', 'Grafstruktur'],
|
||||
],
|
||||
'uk' => [
|
||||
'transcribe' => ['Транскрипція', 'Аудіо та зустрічі', 'Перетворюйте аудіо або відео на текст із розділенням мовців і юридичною лексикою.', 'Whisper / GPU'],
|
||||
@@ -466,6 +468,7 @@ function dbnToolsLaunchedTools(?string $language = null): array
|
||||
'deep-research' => ['Глибоке дослідження', 'Agent + RAG', 'Розгортає питання в дослідницькі напрями, шукає юридичні джерела та створює бриф.', 'Сімейне право'],
|
||||
'discrepancy' => ['Пошук розбіжностей', 'Порівняння документів', 'Завантажте дві версії документа Barnevernet і знайдіть суперечності, видалені факти та нові твердження.', 'Міждокументний AI'],
|
||||
'corpus' => ['Корпус', 'Юридична база знань', 'Переглядайте індексовані джерела, стан корпусу, категорії та поведінку пошуку.', '~220 тис. уривків'],
|
||||
'citations' => ['Граф цитувань', 'Мережа посилань', 'Граф правових посилань — що цитує документ, хто цитує його, що його реалізує.', 'Граф-топологія'],
|
||||
],
|
||||
'pl' => [
|
||||
'transcribe' => ['Transkrypcja', 'Audio i spotkania', 'Zamień audio lub wideo na tekst z rozdzieleniem mówców i słownictwem prawnym.', 'Whisper / GPU'],
|
||||
@@ -476,11 +479,12 @@ function dbnToolsLaunchedTools(?string $language = null): array
|
||||
'deep-research' => ['Głębokie badanie', 'Agent + RAG', 'Rozwija pytanie w kierunki badawcze, przeszukuje źródła prawne i tworzy brief z cytatami.', 'Prawo rodzinne'],
|
||||
'discrepancy' => ['Wyszukiwacz rozbieżności', 'Porównanie dokumentów', 'Prześlij dwie wersje dokumentu Barnevernet i znajdź sprzeczności, usunięte fakty i nowe zarzuty.', 'AI Między-dokumentowe'],
|
||||
'corpus' => ['Korpus', 'Prawna baza wiedzy', 'Sprawdzaj indeksowane źródła, stan korpusu, kategorie prawne i działanie wyszukiwania.', '~220 tys. fragmentów'],
|
||||
'citations' => ['Graf cytowań', 'Sieć cytowań', 'Przeglądaj sieć cytowań — co cytuje dokument, kto go cytuje i co go implementuje.', 'Topologia grafu'],
|
||||
],
|
||||
];
|
||||
|
||||
$selected = $copy[$language] ?? $copy['en'];
|
||||
$order = ['transcribe', 'timeline', 'redact', 'barnevernet', 'advocate', 'deep-research', 'discrepancy', 'corpus'];
|
||||
$order = ['transcribe', 'timeline', 'redact', 'barnevernet', 'advocate', 'deep-research', 'discrepancy', 'corpus', 'citations'];
|
||||
$icons = [
|
||||
'transcribe' => 'TR',
|
||||
'timeline' => 'TL',
|
||||
@@ -490,6 +494,7 @@ function dbnToolsLaunchedTools(?string $language = null): array
|
||||
'deep-research' => 'DR',
|
||||
'discrepancy' => 'DC',
|
||||
'corpus' => 'KB',
|
||||
'citations' => 'CIT',
|
||||
];
|
||||
$out = [];
|
||||
foreach ($order as $slug) {
|
||||
|
||||
Reference in New Issue
Block a user