Files
alice-backend/server/public/app.js
T

110 lines
4.0 KiB
JavaScript

const telegram = window.Telegram?.WebApp;
const status = document.querySelector('#status');
const admin = document.querySelector('#admin');
const history = document.querySelector('#history');
const settings = document.querySelector('#settings');
const showHistory = document.querySelector('#show-history');
const showSettings = document.querySelector('#show-settings');
const clear = document.querySelector('#clear');
const form = document.querySelector('#form');
const turns = document.querySelector('#turns');
const dateFormat = new Intl.DateTimeFormat('ru', { dateStyle: 'medium', timeStyle: 'short' });
async function api(path = '', options = {}) {
const response = await fetch('/api/admin' + path, {
...options,
headers: { Authorization: 'tma ' + telegram.initData, 'Content-Type': 'application/json' },
});
if (response.status === 401) throw new Error('Доступ открыт владельцу. Если сессия истекла, заново открой приложение из меню бота.');
if (response.status === 400) throw new Error('Проверь ID модели, голос и системный промпт.');
if (!response.ok) throw new Error('Сервер недоступен. Попробуй ещё раз.');
return response.json();
}
function renderTurns(items) {
turns.replaceChildren();
clear.disabled = items.length === 0;
if (!items.length) {
const empty = document.createElement('p');
empty.className = 'empty';
empty.textContent = 'Здесь появятся последние ответы станции.';
turns.append(empty);
}
for (const turn of items) {
const article = document.createElement('article');
const date = document.createElement('time');
date.dateTime = turn.createdAt;
date.textContent = dateFormat.format(new Date(turn.createdAt));
const question = document.createElement('p');
question.className = 'question';
question.textContent = turn.user;
const answer = document.createElement('p');
answer.textContent = turn.assistant;
article.append(date, question, answer);
turns.append(article);
}
}
function selectTab(isHistory) {
showHistory.setAttribute('aria-pressed', String(isHistory));
showSettings.setAttribute('aria-pressed', String(!isHistory));
history.hidden = !isHistory;
settings.hidden = isHistory;
status.textContent = '';
}
showHistory.onclick = async () => {
selectTab(true);
try {
renderTurns((await api()).history);
} catch (error) {
status.textContent = error.message;
}
};
showSettings.onclick = () => selectTab(false);
form.onsubmit = async (event) => {
event.preventDefault();
const button = form.querySelector('button');
button.disabled = true;
try {
const body = JSON.stringify(Object.fromEntries(new FormData(form)));
await api('/settings', { method: 'PUT', body });
status.textContent = 'Сохранено. Настройки вступят в силу со следующего запроса.';
} catch (error) {
status.textContent = error.message;
} finally {
button.disabled = false;
}
};
clear.onclick = () => {
const erase = async (confirmed) => {
if (!confirmed) return;
clear.disabled = true;
try {
renderTurns((await api('/history', { method: 'DELETE' })).history);
} catch (error) {
status.textContent = error.message;
clear.disabled = false;
}
};
if (telegram.isVersionAtLeast('6.2')) telegram.showConfirm('Очистить историю разговоров?', erase);
else erase(window.confirm('Очистить историю разговоров?'));
};
telegram?.ready();
if (!telegram?.initData) {
status.textContent = 'Открой пульт из меню Telegram-бота.';
} else {
try {
const data = await api();
for (const [name, value] of Object.entries(data.settings)) form.elements.namedItem(name).value = value;
renderTurns(data.history);
status.textContent = '';
admin.hidden = false;
} catch (error) {
status.textContent = error.message;
}
}