Files

149 lines
6.1 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 ? 'Аккаунт подключён.' : 'Подключи аккаунт для управления музыкой.';
const playback = spotify.playback;
if (playback) {
spotifyStatus.textContent += playback.online
? ` Колонка онлайн${playback.active ? ', активна' : ', неактивна'}. ${playback.item?.name || ''}${playback.status === 'paused' ? ' — пауза' : ''}`
: ` Колонка офлайн.${playback.lastPlayback?.item?.name ? ' Последний трек — ' + playback.lastPlayback.item.name + '.' : ''}`;
}
spotifyConnect.disabled = !spotify.configured;
spotifyConnect.textContent = spotify.connected ? 'Переподключить Spotify' : 'Подключить 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 values = Object.fromEntries(new FormData(form));
const body = JSON.stringify({ ...values, followUpMs: Number(values.followUpMs), activationSound: form.elements.activationSound.checked });
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)) {
const input = form.elements.namedItem(name);
if (!input) continue;
if (input.type === 'checkbox') input.checked = value === true;
else input.value = value;
}
renderTurns(data.history);
renderSpotify(data.spotify);
status.textContent = '';
admin.hidden = false;
} catch (error) {
status.textContent = error.message;
}
}
setInterval(async () => {
if (settings.hidden || !telegram?.initData) return;
try { renderSpotify((await api()).spotify); } catch {}
}, 15000);