ba9cddf9a1
- Stripe: StripeClient.php, checkout/portal/webhook endpoints, idempotent event handling - FreeTier: tier-aware credits (free/light/pro/pro_plus), bonus_balance, hourly caps per tier - pricing.php + billing.php: 4-tier cards, 3 topups, Customer Portal, balance breakdown - Min Sak: CaseStore.php, AzureDocIntelligence.php, AzureSearchAdmin.php — per-user hybrid RAG - api/case/: upload, list, delete, ingest-callback (HMAC-auth'd from n8n) - award-survey-credits: inter-site HMAC endpoint for dobetternorge.no survey bonus - dashboard.php: tier badge, balance breakdown card, Min Sak CTA, survey CTA - KorrespondAgent + all 3 other agents: use_my_case toggle wired to dbnToolsCaseContext() - bootstrap.php: dbnToolsCaseContext(), dbnToolsIntersiteSecret(), dbnToolsCurrentTier() Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
287 lines
10 KiB
JavaScript
287 lines
10 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
const form = document.getElementById('workbenchForm');
|
|
const clearButton = document.getElementById('workbenchClear');
|
|
const status = document.getElementById('workbenchStatus');
|
|
const storageKey = 'dbn.caseWorkbench.v1';
|
|
let statusTimer = 0;
|
|
|
|
if (!form) {
|
|
return;
|
|
}
|
|
|
|
function setStatus(message) {
|
|
if (!status) {
|
|
return;
|
|
}
|
|
status.textContent = message;
|
|
window.clearTimeout(statusTimer);
|
|
statusTimer = window.setTimeout(function () {
|
|
status.textContent = '';
|
|
}, 2200);
|
|
}
|
|
|
|
function collect() {
|
|
const data = {};
|
|
const fields = form.querySelectorAll('input, select, textarea');
|
|
fields.forEach(function (field) {
|
|
if (!field.name) {
|
|
return;
|
|
}
|
|
if (field.type === 'checkbox') {
|
|
if (!Array.isArray(data[field.name])) {
|
|
data[field.name] = [];
|
|
}
|
|
if (field.checked) {
|
|
data[field.name].push(field.value);
|
|
}
|
|
return;
|
|
}
|
|
data[field.name] = field.value;
|
|
});
|
|
data.saved_at = new Date().toISOString();
|
|
return data;
|
|
}
|
|
|
|
function restore(data) {
|
|
if (!data || typeof data !== 'object') {
|
|
return;
|
|
}
|
|
const fields = form.querySelectorAll('input, select, textarea');
|
|
fields.forEach(function (field) {
|
|
if (!field.name) {
|
|
return;
|
|
}
|
|
if (field.type === 'checkbox') {
|
|
const values = Array.isArray(data[field.name]) ? data[field.name] : [];
|
|
field.checked = values.indexOf(field.value) !== -1;
|
|
return;
|
|
}
|
|
if (Object.prototype.hasOwnProperty.call(data, field.name)) {
|
|
field.value = data[field.name] || '';
|
|
}
|
|
});
|
|
}
|
|
|
|
function save() {
|
|
try {
|
|
window.sessionStorage.setItem(storageKey, JSON.stringify(collect()));
|
|
setStatus('Saved in this browser session.');
|
|
} catch (error) {
|
|
setStatus('Could not save this session locally.');
|
|
}
|
|
}
|
|
|
|
try {
|
|
const raw = window.sessionStorage.getItem(storageKey);
|
|
if (raw) {
|
|
restore(JSON.parse(raw));
|
|
setStatus('Restored local workbench notes.');
|
|
}
|
|
} catch (error) {
|
|
window.sessionStorage.removeItem(storageKey);
|
|
}
|
|
|
|
form.addEventListener('input', save);
|
|
form.addEventListener('change', save);
|
|
|
|
if (clearButton) {
|
|
clearButton.addEventListener('click', function () {
|
|
window.sessionStorage.removeItem(storageKey);
|
|
form.reset();
|
|
setStatus('Workbench session cleared.');
|
|
});
|
|
}
|
|
}());
|
|
|
|
// ── My Documents panel ────────────────────────────────────────────────────────
|
|
(function () {
|
|
'use strict';
|
|
|
|
var panel = document.querySelector('[data-my-docs="true"]');
|
|
var list = document.getElementById('myDocsList');
|
|
if (!panel || !list) return;
|
|
|
|
var lang = (window.DBN_TOOLS_LANG || 'en');
|
|
|
|
var i18n = {
|
|
empty: { en: 'No documents uploaded yet. Upload PDFs, DOCX, or TXT files in the AI Chat sidebar.', no: 'Ingen dokumenter lastet opp ennå. Last opp PDF, DOCX eller TXT i AI-chattens sidepanel.', uk: 'Документів ще немає. Завантажте файли у бічній панелі AI-чату.', pl: 'Brak dokumentów. Prześlij pliki w panelu bocznym czatu AI.' },
|
|
remove: { en: 'Remove', no: 'Fjern', uk: 'Видалити', pl: 'Usuń' },
|
|
ai: { en: 'AI Chat', no: 'AI-chat', uk: 'AI-чат', pl: 'Czat AI' },
|
|
tools: { en: 'Tools', no: 'Verktøy', uk: 'Інструменти', pl: 'Narzędzia' },
|
|
error: { en: 'Could not load documents.', no: 'Kunne ikke laste dokumenter.', uk: 'Не вдалося завантажити документи.', pl: 'Nie można załadować dokumentów.' },
|
|
};
|
|
|
|
function t(key) {
|
|
return (i18n[key] && i18n[key][lang]) || (i18n[key] && i18n[key]['en']) || key;
|
|
}
|
|
|
|
function formatDate(str) {
|
|
if (!str) return '';
|
|
try { return new Date(str).toLocaleDateString(); } catch (e) { return str; }
|
|
}
|
|
|
|
function renderDocs(docs) {
|
|
if (!docs.length) {
|
|
list.innerHTML = '<p class="workbench-docs__empty">' + t('empty') + '</p>';
|
|
return;
|
|
}
|
|
list.innerHTML = docs.map(function (doc) {
|
|
return '<div class="workbench-docs__item" role="listitem" data-doc-id="' + esc(doc.doc_id) + '">'
|
|
+ '<span class="workbench-docs__icon">📄</span>'
|
|
+ '<span class="workbench-docs__name" title="' + esc(doc.filename) + '">' + esc(doc.filename) + '</span>'
|
|
+ '<span class="workbench-docs__meta">' + esc(formatDate(doc.created_at)) + ' · ' + (doc.source === 'ai_chat' ? t('ai') : t('tools')) + '</span>'
|
|
+ '<button class="workbench-docs__remove" type="button" data-doc-id="' + esc(doc.doc_id) + '" aria-label="' + t('remove') + ' ' + esc(doc.filename) + '">' + t('remove') + '</button>'
|
|
+ '</div>';
|
|
}).join('');
|
|
|
|
list.querySelectorAll('.workbench-docs__remove').forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
var docId = btn.getAttribute('data-doc-id');
|
|
if (!docId) return;
|
|
btn.disabled = true;
|
|
fetch('/api/user-docs.php?id=' + encodeURIComponent(docId), { method: 'DELETE', credentials: 'include' })
|
|
.then(function () {
|
|
var item = list.querySelector('[data-doc-id="' + docId + '"]');
|
|
if (item) item.remove();
|
|
if (!list.querySelector('.workbench-docs__item')) {
|
|
list.innerHTML = '<p class="workbench-docs__empty">' + t('empty') + '</p>';
|
|
}
|
|
})
|
|
.catch(function () { btn.disabled = false; });
|
|
});
|
|
});
|
|
}
|
|
|
|
function esc(str) {
|
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
fetch('/api/user-docs.php', { credentials: 'include' })
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (data) {
|
|
if (data.ok) {
|
|
renderDocs(data.docs || []);
|
|
} else {
|
|
list.innerHTML = '<p class="workbench-docs__empty">' + t('error') + '</p>';
|
|
}
|
|
})
|
|
.catch(function () {
|
|
list.innerHTML = '<p class="workbench-docs__empty">' + t('error') + '</p>';
|
|
});
|
|
}());
|
|
|
|
// ── Upload zone ───────────────────────────────────────────────────────────────
|
|
(function () {
|
|
'use strict';
|
|
|
|
var zone = document.getElementById('wbUploadZone');
|
|
var input = document.getElementById('wbUploadInput');
|
|
var status = document.getElementById('wbUploadStatus');
|
|
var list = document.getElementById('myDocsList');
|
|
if (!zone || !input) return;
|
|
|
|
var lang = (window.DBN_TOOLS_LANG || 'en');
|
|
var statusTimer = 0;
|
|
|
|
var i18n = {
|
|
uploading: { en: 'Uploading…', no: 'Laster opp…', uk: 'Завантаження…', pl: 'Przesyłanie…' },
|
|
saved: { en: 'Saved.', no: 'Lagret.', uk: 'Збережено.', pl: 'Zapisano.' },
|
|
error: { en: 'Upload failed.', no: 'Opplasting mislyktes.', uk: 'Помилка завантаження.', pl: 'Błąd przesyłania.' },
|
|
remove: { en: 'Remove', no: 'Fjern', uk: 'Видалити', pl: 'Usuń' },
|
|
empty: { en: 'No documents yet.', no: 'Ingen dokumenter ennå.', uk: 'Документів ще немає.', pl: 'Brak dokumentów.' },
|
|
};
|
|
|
|
function t(key) {
|
|
return (i18n[key] && i18n[key][lang]) || (i18n[key] && i18n[key]['en']) || key;
|
|
}
|
|
|
|
function esc(str) {
|
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
function setStatus(msg, keep) {
|
|
if (!status) return;
|
|
status.textContent = msg;
|
|
window.clearTimeout(statusTimer);
|
|
if (!keep) {
|
|
statusTimer = window.setTimeout(function () { status.textContent = ''; }, 2400);
|
|
}
|
|
}
|
|
|
|
function prependDoc(doc) {
|
|
if (!list) return;
|
|
var empty = list.querySelector('.workbench-docs__empty, .workbench-docs__loading');
|
|
if (empty) empty.remove();
|
|
|
|
var el = document.createElement('div');
|
|
el.className = 'workbench-docs__item';
|
|
el.setAttribute('role', 'listitem');
|
|
el.setAttribute('data-doc-id', doc.doc_id);
|
|
el.innerHTML = '<span class="workbench-docs__icon">📄</span>'
|
|
+ '<span class="workbench-docs__name" title="' + esc(doc.filename) + '">' + esc(doc.filename) + '</span>'
|
|
+ '<span class="workbench-docs__meta">' + esc(new Date(doc.created_at).toLocaleDateString()) + ' · workbench</span>'
|
|
+ '<button class="workbench-docs__remove" type="button" data-doc-id="' + esc(doc.doc_id) + '" aria-label="' + t('remove') + ' ' + esc(doc.filename) + '">' + t('remove') + '</button>';
|
|
|
|
el.querySelector('.workbench-docs__remove').addEventListener('click', function () {
|
|
var btn = this;
|
|
btn.disabled = true;
|
|
fetch('/api/user-docs.php?id=' + encodeURIComponent(doc.doc_id), { method: 'DELETE', credentials: 'include' })
|
|
.then(function () {
|
|
var item = list.querySelector('[data-doc-id="' + doc.doc_id + '"]');
|
|
if (item) item.remove();
|
|
if (!list.querySelector('.workbench-docs__item')) {
|
|
list.innerHTML = '<p class="workbench-docs__empty">' + t('empty') + '</p>';
|
|
}
|
|
})
|
|
.catch(function () { btn.disabled = false; });
|
|
});
|
|
|
|
list.insertBefore(el, list.firstChild);
|
|
}
|
|
|
|
function uploadFile(file) {
|
|
var fd = new FormData();
|
|
fd.append('file', file);
|
|
setStatus(t('uploading'), true);
|
|
return fetch('/api/user-docs.php', { method: 'POST', credentials: 'include', body: fd })
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (data) {
|
|
if (data.ok && data.doc) {
|
|
prependDoc(data.doc);
|
|
setStatus(t('saved'));
|
|
} else {
|
|
setStatus((data.error || t('error')));
|
|
}
|
|
})
|
|
.catch(function () { setStatus(t('error')); });
|
|
}
|
|
|
|
function handleFiles(files) {
|
|
if (!files || !files.length) return;
|
|
var chain = Promise.resolve();
|
|
Array.prototype.forEach.call(files, function (file) {
|
|
chain = chain.then(function () { return uploadFile(file); });
|
|
});
|
|
}
|
|
|
|
zone.addEventListener('dragover', function (e) {
|
|
e.preventDefault();
|
|
zone.classList.add('is-drag-over');
|
|
});
|
|
zone.addEventListener('dragleave', function () {
|
|
zone.classList.remove('is-drag-over');
|
|
});
|
|
zone.addEventListener('drop', function (e) {
|
|
e.preventDefault();
|
|
zone.classList.remove('is-drag-over');
|
|
handleFiles(e.dataTransfer.files);
|
|
});
|
|
|
|
input.addEventListener('change', function () {
|
|
handleFiles(input.files);
|
|
input.value = '';
|
|
});
|
|
}());
|