i18n all /dashboard/ corpus pages for en/no/uk/pl
- Add require_once bootstrap.php to all 6 dashboard page files so
dbnToolsT() is available before layout_dashboard.php is included
- Add dash_upload_category_lbl key to no/uk/pl sections of i18n.php
(was only in English); Kategori/Категорія/Kategoria
- Fix broken ternary on upload.php Category label — replace with
dbnToolsT('dash_upload_category_lbl', $uiLang)
- layout_dashboard.php outputs window.DBN_I18N with all js_* keys
so dashboard JS reads locale-aware strings from PHP translations
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+24
-22
@@ -1,21 +1,22 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||||
$dashboardPage = 'chat';
|
||||
$dashboardTitle = 'Spør korpuset';
|
||||
$dashboardLead = 'Still juridiske spørsmål. Svar streames med kildehenvisninger til ditt eget korpus og delt Do Better Norge-pakke.';
|
||||
$dashboardTitle = dbnToolsT('dash_title_chat', dbnToolsCurrentLanguage());
|
||||
$dashboardLead = dbnToolsT('dash_lead_chat', dbnToolsCurrentLanguage());
|
||||
require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
?>
|
||||
<div class="disclaimer" role="note" style="margin-bottom:1rem;">
|
||||
⚖ Juridisk informasjon og forberedelsesstøtte — ikke endelig juridisk rådgivning. Bekreft alltid med advokat.
|
||||
<?= htmlspecialchars(dbnToolsT('dash_chat_disclaimer', $uiLang)) ?>
|
||||
</div>
|
||||
|
||||
<section class="dash-card" style="display:flex; flex-direction:column; min-height:60vh;">
|
||||
<div id="chatLog" class="chat-log" aria-live="polite">
|
||||
<div class="chat-empty">Still ditt første spørsmål. Det kan handle om barnerett, barnevern, EMD, arbeidsrett — alt som finnes i ditt korpus.</div>
|
||||
<div class="chat-empty" id="chatEmptyMsg"></div>
|
||||
</div>
|
||||
|
||||
<form id="chatForm" class="chat-form" autocomplete="off">
|
||||
<textarea id="chatInput" rows="2" required placeholder="f.eks. «Hva sier barnevernloven § 4-12 om plassering uten samtykke?»"></textarea>
|
||||
<textarea id="chatInput" rows="2" required placeholder="<?= htmlspecialchars(dbnToolsT('dash_chat_placeholder', $uiLang)) ?>"></textarea>
|
||||
<button type="submit" class="dash-btn dash-btn--primary" id="chatSendBtn">Send</button>
|
||||
</form>
|
||||
</section>
|
||||
@@ -53,13 +54,19 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
const api = window.DBN_DASHBOARD.apiBase;
|
||||
const $log = document.getElementById('chatLog');
|
||||
const I18N = window.DBN_I18N || {};
|
||||
const api = window.DBN_DASHBOARD.apiBase;
|
||||
const $log = document.getElementById('chatLog');
|
||||
const $form = document.getElementById('chatForm');
|
||||
const $input = document.getElementById('chatInput');
|
||||
const $send = document.getElementById('chatSendBtn');
|
||||
const $empty = document.getElementById('chatEmptyMsg');
|
||||
|
||||
const history = []; // [{role, content}]
|
||||
if ($empty) $empty.textContent = I18N.ask_btn
|
||||
? I18N.ask_btn.replace(/^💬\s*/, '')
|
||||
: 'Ask your first question. It can be about child welfare, family law, ECHR — anything in your corpus.';
|
||||
|
||||
const history = [];
|
||||
|
||||
function safe(s) { return String(s ?? '').replace(/[&<>"]/g, c => ({ '&':'&','<':'<','>':'>','"':'"' }[c])); }
|
||||
|
||||
@@ -79,11 +86,11 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'chat-msg chat-msg--ai';
|
||||
wrap.innerHTML =
|
||||
'<div class="chat-bubble"><span class="chat-stream"></span><span class="chat-thinking">tenker…</span></div>'
|
||||
'<div class="chat-bubble"><span class="chat-stream"></span><span class="chat-thinking">' + (I18N.chat_thinking || 'thinking…') + '</span></div>'
|
||||
+ '<div class="chat-sources" hidden></div>'
|
||||
+ '<div class="chat-actions" hidden>'
|
||||
+ '<button class="dash-btn chat-save" type="button">💾 Lagre i korpus</button>'
|
||||
+ '<button class="dash-btn chat-copy" type="button">📋 Kopier</button>'
|
||||
+ '<button class="dash-btn chat-save" type="button">' + (I18N.chat_save || '💾 Save to corpus') + '</button>'
|
||||
+ '<button class="dash-btn chat-copy" type="button">' + (I18N.chat_copy || '📋 Copy') + '</button>'
|
||||
+ '</div>'
|
||||
+ '<div class="chat-meta" hidden></div>';
|
||||
$log.appendChild(wrap);
|
||||
@@ -94,7 +101,7 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
async function ask(question) {
|
||||
appendUser(question);
|
||||
history.push({ role: 'user', content: question });
|
||||
const aiNode = appendAi();
|
||||
const aiNode = appendAi();
|
||||
const stream = aiNode.querySelector('.chat-stream');
|
||||
const thinking = aiNode.querySelector('.chat-thinking');
|
||||
const sources = aiNode.querySelector('.chat-sources');
|
||||
@@ -123,7 +130,6 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
if (done) break;
|
||||
buffer += dec.decode(value, { stream: true });
|
||||
|
||||
// Parse SSE frames: blocks separated by "\n\n"
|
||||
let idx;
|
||||
while ((idx = buffer.indexOf('\n\n')) !== -1) {
|
||||
const frame = buffer.slice(0, idx);
|
||||
@@ -146,15 +152,13 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
} else if (evName === 'done') {
|
||||
history.push({ role: 'assistant', content: answer });
|
||||
renderSources(sources, payload.sources || []);
|
||||
const chunksTmpl = (I18N.chat_passages_meta || '{n} passages').replace('{n}', payload.chunks_used || 0);
|
||||
meta.hidden = false;
|
||||
meta.textContent =
|
||||
(payload.chunks_used || 0) + ' passasjer · '
|
||||
+ (payload.model || 'auto') + ' · '
|
||||
+ (payload.response_time_ms || 0) + ' ms';
|
||||
meta.textContent = chunksTmpl + ' · ' + (payload.model || 'auto') + ' · ' + (payload.response_time_ms || 0) + ' ms';
|
||||
actions.hidden = false;
|
||||
wireActions(aiNode, question, answer);
|
||||
} else if (evName === 'fail') {
|
||||
thinking.textContent = '❌ ' + (payload.message || 'Feil');
|
||||
thinking.textContent = '❌ ' + (payload.message || 'Error');
|
||||
thinking.style.color = 'var(--dbn-red)';
|
||||
}
|
||||
}
|
||||
@@ -191,22 +195,20 @@ require_once __DIR__ . '/../includes/layout_dashboard.php';
|
||||
navigator.clipboard.writeText(answer).then(() => {
|
||||
const btn = node.querySelector('.chat-copy');
|
||||
const orig = btn.textContent;
|
||||
btn.textContent = '✓ Kopiert';
|
||||
btn.textContent = I18N.chat_copied || '✓ Copied';
|
||||
setTimeout(() => btn.textContent = orig, 1400);
|
||||
});
|
||||
});
|
||||
node.querySelector('.chat-save').addEventListener('click', () => {
|
||||
// Re-uses existing corpus-save.js dialog (loaded by layout footer)
|
||||
const dialog = document.getElementById('save-corpus-dialog');
|
||||
const titleField = document.getElementById('save-corpus-title');
|
||||
const tagsField = document.getElementById('save-corpus-tags');
|
||||
if (!dialog || !titleField) {
|
||||
alert('Lagre-dialog ikke tilgjengelig.');
|
||||
alert(I18N.chat_save_unavail || 'Save dialog unavailable.');
|
||||
return;
|
||||
}
|
||||
titleField.value = question.slice(0, 80);
|
||||
tagsField.value = 'chat,answer';
|
||||
// Hand-off contract used by corpus-save.js: data-pending-content
|
||||
dialog.dataset.pendingContent = 'Q: ' + question + '\n\nA: ' + answer;
|
||||
dialog.dataset.pendingTool = 'dashboard-chat';
|
||||
dialog.showModal();
|
||||
|
||||
Reference in New Issue
Block a user