131 lines
5.2 KiB
JavaScript
131 lines
5.2 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 spotifyStatus = document.querySelector('#spotify-status');
|
|
const spotifyConnect = document.querySelector('#spotify-connect');
|
|
|
|
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;
|
|
}
|
|
};
|
|
function renderSpotify(spotify) {
|
|
spotifyStatus.textContent = !spotify.configured ? 'Spotify пока не настроен на сервере.' : spotify.connected ? 'Аккаунт подключён.' : 'Подключи аккаунт для управления музыкой.';
|
|
spotifyConnect.disabled = !spotify.configured;
|
|
spotifyConnect.textContent = spotify.connected ? 'Подключить другой аккаунт' : 'Подключить Spotify';
|
|
}
|
|
showSettings.onclick = async () => {
|
|
selectTab(false);
|
|
try { renderSpotify((await api()).spotify); } catch (error) { status.textContent = error.message; }
|
|
};
|
|
spotifyConnect.onclick = async () => {
|
|
spotifyConnect.disabled = true;
|
|
try {
|
|
const response = await fetch('/api/spotify/start', { method: 'POST', headers: { Authorization: 'tma ' + telegram.initData } });
|
|
if (!response.ok) throw new Error('Не удалось начать подключение Spotify.');
|
|
const { url } = await response.json();
|
|
telegram.openLink(url);
|
|
} catch (error) { status.textContent = error.message; }
|
|
finally { spotifyConnect.disabled = 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);
|
|
renderSpotify(data.spotify);
|
|
status.textContent = '';
|
|
admin.hidden = false;
|
|
} catch (error) {
|
|
status.textContent = error.message;
|
|
}
|
|
}
|