172 lines
9.5 KiB
PHP
172 lines
9.5 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
/**
|
|
* api/staff/v1/assistant.php: Verwaltung des digitalen Assistenten für Android- und Windows-App (wie staff/assistant.php).
|
|
* Nur Administratoren und Mitarbeiter mit dem Recht "Digitalen Assistenten verwalten".
|
|
*
|
|
* GET ?section=overview -> Einstellungen, Zustand, Abteilungen, Prioritäten, Synonyme
|
|
* GET ?section=answers -> zusätzliche Antworten
|
|
* GET ?section=log&result=&period=&q=&page= -> Fragen-Log mit Kennzahlen und häufigen Fragen ohne Antwort
|
|
* POST {action: save_settings|save_answer|delete_answer|save_synonyms|clear_log|test, ...}
|
|
*/
|
|
require_once __DIR__ . '/common.php';
|
|
require_once __DIR__ . '/../../../includes/assistant.php';
|
|
|
|
api_require_method('GET', 'POST');
|
|
$staff = api_require_staff();
|
|
if ($staff['role'] !== 'admin' && !has_permission($staff, 'manage_assistant')) {
|
|
api_error(403, 'FORBIDDEN', 'Für diese Aktion fehlt die erforderliche Berechtigung.');
|
|
}
|
|
$pdo = get_db();
|
|
|
|
/** Sind die Tabellen schon da? (sonst: Datenbank aktualisieren) */
|
|
function assistant_api_db_ready(PDO $pdo): bool
|
|
{
|
|
try { $pdo->query('SELECT id FROM assistant_answers LIMIT 1'); return true; } catch (Throwable $e) { return false; }
|
|
}
|
|
|
|
function assistant_api_answer(array $a): array
|
|
{
|
|
return ['id' => (int) $a['id'], 'title' => $a['title'], 'keywords' => (string) ($a['keywords'] ?? ''), 'answer' => $a['answer'],
|
|
'isActive' => (bool) $a['is_active'], 'hits' => (int) $a['hits'], 'updatedAt' => $a['updated_at']];
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|
$section = (string) ($_GET['section'] ?? 'overview');
|
|
if ($section === 'overview') {
|
|
$texts = assistant_texts();
|
|
api_json([
|
|
'dbReady' => assistant_api_db_ready($pdo),
|
|
'settings' => [
|
|
'enabled' => assistant_enabled(),
|
|
'mode' => get_setting('assistant_mode', 'offline_only') === 'always' ? 'always' : 'offline_only',
|
|
'name' => get_setting('assistant_name', ''),
|
|
'greeting' => get_setting('assistant_greeting', ''),
|
|
'noAnswer' => get_setting('assistant_no_answer', ''),
|
|
'departmentId' => (int) get_setting('assistant_department_id', '0'),
|
|
'priorityId' => (int) get_setting('assistant_priority_id', '0'),
|
|
'logDays' => assistant_log_days(),
|
|
'synonyms' => get_setting('assistant_synonyms', ''),
|
|
],
|
|
'defaults' => ['name' => $texts['name'], 'greeting' => $texts['greeting'], 'noAnswer' => $texts['no_answer']],
|
|
'state' => [
|
|
'activeNow' => assistant_active($pdo),
|
|
'liveChatEnabled' => get_setting('live_chat_enabled', '0') === '1',
|
|
'kbEnabled' => get_setting('kb_enabled', '1') === '1',
|
|
'helpdeskOffline' => get_setting('helpdesk_status', 'online') === 'offline',
|
|
],
|
|
'departments' => array_map(fn($d) => ['id' => (int) $d['id'], 'name' => $d['name']], $pdo->query('SELECT id, name FROM departments WHERE is_active = 1 ORDER BY name')->fetchAll()),
|
|
'priorities' => array_map(fn($p) => ['id' => (int) $p['id'], 'name' => $p['name']], $pdo->query('SELECT id, name FROM priorities ORDER BY sort_order')->fetchAll()),
|
|
]);
|
|
}
|
|
if ($section === 'answers') {
|
|
try {
|
|
$items = array_map('assistant_api_answer', $pdo->query('SELECT * FROM assistant_answers ORDER BY title')->fetchAll());
|
|
} catch (Throwable $e) {
|
|
api_error(503, 'DB_OUTDATED', 'Die Datenbank ist noch nicht aktuell. Bitte im Adminportal unter System-Update „Datenbank aktualisieren“ klicken.');
|
|
}
|
|
api_json(['items' => $items]);
|
|
}
|
|
if ($section === 'log') {
|
|
$period = in_array($_GET['period'] ?? '', ['today', '7', '30', 'all'], true) ? (string) $_GET['period'] : '30';
|
|
$filter = ['result' => (string) ($_GET['result'] ?? ''), 'period' => $period, 'q' => trim((string) ($_GET['q'] ?? ''))];
|
|
try {
|
|
$page = assistant_log_list($pdo, $filter, max(1, (int) ($_GET['page'] ?? 1)));
|
|
$stats = assistant_log_stats($pdo, $period);
|
|
$top = assistant_log_unanswered($pdo, $period);
|
|
} catch (Throwable $e) {
|
|
api_error(503, 'DB_OUTDATED', 'Das Log ist noch nicht verfügbar. Bitte im Adminportal unter System-Update „Datenbank aktualisieren“ klicken.');
|
|
}
|
|
api_json([
|
|
'filters' => assistant_log_filters(),
|
|
'stats' => $stats,
|
|
'top' => $top,
|
|
'total' => $page['total'], 'pages' => $page['pages'], 'page' => $page['page'],
|
|
'items' => array_map(fn(array $r) => [
|
|
'id' => (int) $r['id'], 'question' => $r['question'], 'result' => $r['result'], 'matchedTitle' => $r['matched_title'],
|
|
'ticketNumber' => $r['ticket_number'], 'helpful' => $r['helpful'] === null ? null : ((int) $r['helpful'] === 1), 'createdAt' => $r['created_at'],
|
|
], $page['rows']),
|
|
]);
|
|
}
|
|
api_error(422, 'INVALID_SECTION', 'Unbekannter Bereich.');
|
|
}
|
|
|
|
$body = api_request_body();
|
|
$action = (string) ($body['action'] ?? '');
|
|
|
|
if ($action === 'save_settings') {
|
|
$dept = (int) ($body['departmentId'] ?? 0);
|
|
$prio = (int) ($body['priorityId'] ?? 0);
|
|
if ($dept > 0) { $c = $pdo->prepare('SELECT COUNT(*) FROM departments WHERE id = ? AND is_active = 1'); $c->execute([$dept]); if (!(int) $c->fetchColumn()) $dept = 0; }
|
|
if ($prio > 0) { $c = $pdo->prepare('SELECT COUNT(*) FROM priorities WHERE id = ?'); $c->execute([$prio]); if (!(int) $c->fetchColumn()) $prio = 0; }
|
|
set_setting('assistant_enabled', !empty($body['enabled']) ? '1' : '0');
|
|
set_setting('assistant_mode', ($body['mode'] ?? 'offline_only') === 'always' ? 'always' : 'offline_only');
|
|
set_setting('assistant_name', mb_substr(trim(strip_tags((string) ($body['name'] ?? ''))), 0, 60));
|
|
set_setting('assistant_greeting', mb_substr(trim(strip_tags((string) ($body['greeting'] ?? ''))), 0, 500));
|
|
set_setting('assistant_no_answer', mb_substr(trim(strip_tags((string) ($body['noAnswer'] ?? ''))), 0, 500));
|
|
set_setting('assistant_department_id', (string) $dept);
|
|
set_setting('assistant_priority_id', (string) $prio);
|
|
set_setting('assistant_log_days', (string) max(0, min(3650, (int) ($body['logDays'] ?? 90))));
|
|
audit_log((int) $staff['id'], 'Einstellungen des digitalen Assistenten geändert (App)');
|
|
api_json(['ok' => true]);
|
|
}
|
|
|
|
if ($action === 'save_synonyms') {
|
|
$text = mb_substr(trim(strip_tags((string) ($body['text'] ?? ''))), 0, 5000);
|
|
set_setting('assistant_synonyms', $text);
|
|
audit_log((int) $staff['id'], 'Gleichbedeutende Wörter des digitalen Assistenten geändert (App)');
|
|
api_json(['ok' => true, 'groups' => count(assistant_custom_synonyms($text))]);
|
|
}
|
|
|
|
if ($action === 'save_answer') {
|
|
if (!assistant_api_db_ready($pdo)) api_error(503, 'DB_OUTDATED', 'Die Datenbank ist noch nicht aktuell. Bitte „Datenbank aktualisieren“ ausführen.');
|
|
$id = (int) ($body['id'] ?? 0);
|
|
$title = trim(mb_substr((string) ($body['title'] ?? ''), 0, 255));
|
|
$keywords = trim(mb_substr((string) ($body['keywords'] ?? ''), 0, 1000));
|
|
$answer = trim(mb_substr((string) ($body['answer'] ?? ''), 0, 4000));
|
|
$active = !empty($body['isActive']) ? 1 : 0;
|
|
if ($title === '' || $answer === '') api_error(422, 'FIELDS_REQUIRED', 'Bitte eine Frage (Titel) und die Antwort angeben.');
|
|
$now = db_now();
|
|
if ($id > 0) {
|
|
$pdo->prepare('UPDATE assistant_answers SET title=?, keywords=?, answer=?, is_active=?, updated_at=? WHERE id=?')->execute([$title, $keywords ?: null, $answer, $active, $now, $id]);
|
|
audit_log((int) $staff['id'], "Antwort des digitalen Assistenten geändert (App): {$title}");
|
|
} else {
|
|
$pdo->prepare('INSERT INTO assistant_answers (title, keywords, answer, is_active, created_at, updated_at) VALUES (?,?,?,?,?,?)')->execute([$title, $keywords ?: null, $answer, $active, $now, $now]);
|
|
$id = (int) $pdo->lastInsertId();
|
|
audit_log((int) $staff['id'], "Antwort des digitalen Assistenten angelegt (App): {$title}");
|
|
}
|
|
$row = $pdo->prepare('SELECT * FROM assistant_answers WHERE id = ?');
|
|
$row->execute([$id]);
|
|
api_json(['item' => assistant_api_answer($row->fetch())]);
|
|
}
|
|
|
|
if ($action === 'delete_answer') {
|
|
$id = (int) ($body['id'] ?? 0);
|
|
$t = $pdo->prepare('SELECT title FROM assistant_answers WHERE id = ?');
|
|
$t->execute([$id]);
|
|
$title = $t->fetchColumn();
|
|
if ($title === false) api_error(404, 'NOT_FOUND', 'Die Antwort gibt es nicht.');
|
|
$pdo->prepare('DELETE FROM assistant_answers WHERE id = ?')->execute([$id]);
|
|
audit_log((int) $staff['id'], "Antwort des digitalen Assistenten gelöscht (App): {$title}");
|
|
api_json(['ok' => true]);
|
|
}
|
|
|
|
if ($action === 'clear_log') {
|
|
try {
|
|
$n = $pdo->exec('DELETE FROM assistant_log');
|
|
} catch (Throwable $e) {
|
|
api_error(503, 'DB_OUTDATED', 'Das Log ist noch nicht verfügbar. Bitte „Datenbank aktualisieren“ ausführen.');
|
|
}
|
|
audit_log((int) $staff['id'], 'Fragen-Log des digitalen Assistenten geleert (App)');
|
|
api_json(['ok' => true, 'deleted' => (int) $n]);
|
|
}
|
|
|
|
if ($action === 'test') {
|
|
$q = trim(mb_substr((string) ($body['question'] ?? ''), 0, 300));
|
|
if ($q === '') api_error(422, 'QUESTION_REQUIRED', 'Bitte eine Frage eingeben.');
|
|
$r = assistant_search($pdo, $q, false);
|
|
api_json(['found' => $r !== null, 'result' => $r ? ['source' => $r['source'], 'title' => $r['title'], 'text' => $r['text'], 'others' => array_map(fn($o) => $o['title'], $r['others'])] : null]);
|
|
}
|
|
|
|
api_error(422, 'INVALID_ACTION', 'Unbekannte Aktion.');
|