Add EGE support prototype v0.1.0
This commit is contained in:
1 parent
9944a41cca
commit
24cae23262
19 files changed
+2581
-2
No files matched your search
+567
@@ -0,0 +1,567 @@
|
||||
'use strict';
|
||||
|
||||
const byId = (id) => document.getElementById(id);
|
||||
const exampleField = byId('example');
|
||||
const actorField = byId('actor');
|
||||
const messageField = byId('message');
|
||||
const intake = byId('intake-form');
|
||||
const submit = byId('submit');
|
||||
const detail = byId('ticket-detail');
|
||||
const ticketRows = byId('ticket-rows');
|
||||
const regularFilter = byId('regular-filter');
|
||||
const spamFilter = byId('spam-filter');
|
||||
const panelView = byId('panel-view');
|
||||
const exitView = byId('panel-exit-view');
|
||||
const exitButton = byId('panel-exit');
|
||||
const returnButton = byId('panel-return');
|
||||
const clearButton = byId('clear-buffer');
|
||||
|
||||
let config;
|
||||
let examples = [];
|
||||
let tickets = [];
|
||||
let selectedId = null;
|
||||
let attempt = null;
|
||||
let queueRefresh = Promise.resolve();
|
||||
let folder = 'regular';
|
||||
let currentDetail = null;
|
||||
let generation = 0;
|
||||
let exited = false;
|
||||
let clearing = false;
|
||||
let queueTimer;
|
||||
|
||||
function isCurrent(expected) {
|
||||
return !exited && generation === expected;
|
||||
}
|
||||
|
||||
function invalidatePanel() {
|
||||
generation += 1;
|
||||
window.clearTimeout(queueTimer);
|
||||
queueRefresh = Promise.resolve();
|
||||
}
|
||||
|
||||
function element(tag, content = '', className = '') {
|
||||
const node = document.createElement(tag);
|
||||
node.textContent = content;
|
||||
node.className = className;
|
||||
return node;
|
||||
}
|
||||
|
||||
function option(value, title) {
|
||||
const node = element('option', title);
|
||||
node.value = value;
|
||||
return node;
|
||||
}
|
||||
|
||||
function fillSelect(select, values, selected) {
|
||||
select.replaceChildren(...Object.entries(values).map(([value, title]) => option(value, title)));
|
||||
select.value = selected;
|
||||
}
|
||||
|
||||
function feedback(id, text = '', isError = false) {
|
||||
const status = byId(id);
|
||||
status.textContent = text;
|
||||
status.dataset.error = isError;
|
||||
}
|
||||
|
||||
async function api(url, method = 'GET', body) {
|
||||
const options = { method };
|
||||
if (body !== undefined) {
|
||||
options.headers = { 'Content-Type': 'application/json' };
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(url, options);
|
||||
} catch {
|
||||
throw new Error('Сервер недоступен. Проверьте, что прототип запущен.');
|
||||
}
|
||||
let data;
|
||||
try {
|
||||
data = await response.json();
|
||||
} catch {
|
||||
throw new Error('Сервер вернул ответ в неожиданном формате. Проверьте, что прототип запущен.');
|
||||
}
|
||||
if (!response.ok) throw new Error(data.error || 'Не удалось выполнить действие.');
|
||||
return data;
|
||||
}
|
||||
|
||||
function modeLabel(ticket) {
|
||||
if (ticket.mode === 'demo') return 'Демо · правила';
|
||||
return ticket.provider === 'yandex' ? 'LLM · Yandex AI Studio' : 'LLM · OpenAI';
|
||||
}
|
||||
|
||||
function setIntakeDisabled(disabled) {
|
||||
for (const field of [exampleField, actorField, messageField, submit]) field.disabled = disabled;
|
||||
intake.setAttribute('aria-busy', String(disabled));
|
||||
}
|
||||
|
||||
exampleField.addEventListener('change', () => {
|
||||
if (exampleField.value === '') return;
|
||||
const example = examples[Number(exampleField.value)];
|
||||
actorField.value = example.actor;
|
||||
messageField.value = example.text;
|
||||
feedback('intake-feedback');
|
||||
});
|
||||
|
||||
intake.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
if (exited || clearing) return;
|
||||
const expected = generation;
|
||||
const actor = actorField.value;
|
||||
const text = messageField.value.trim();
|
||||
if (!text) {
|
||||
feedback('intake-feedback', 'Введите текст обращения.', true);
|
||||
messageField.focus();
|
||||
return;
|
||||
}
|
||||
if (!attempt || attempt.actor !== actor || attempt.text !== text) {
|
||||
attempt = { actor, text, requestId: crypto.randomUUID() };
|
||||
}
|
||||
setIntakeDisabled(true);
|
||||
submit.textContent = 'Обрабатываем…';
|
||||
feedback('intake-feedback');
|
||||
try {
|
||||
const { ticket } = await api('/api/tickets', 'POST', attempt);
|
||||
if (!isCurrent(expected)) return;
|
||||
attempt = null;
|
||||
exampleField.value = '';
|
||||
actorField.value = '';
|
||||
messageField.value = '';
|
||||
selectedId = ticket.id;
|
||||
folder = ticketFolder(ticket);
|
||||
feedback('intake-feedback', `Обращение №${ticket.id} записано.`);
|
||||
await refreshQueue().catch((error) => {
|
||||
if (isCurrent(expected)) feedback('queue-feedback', error.message, true);
|
||||
});
|
||||
} catch (error) {
|
||||
if (isCurrent(expected)) feedback('intake-feedback', error.message, true);
|
||||
} finally {
|
||||
if (isCurrent(expected)) {
|
||||
setIntakeDisabled(false);
|
||||
submit.textContent = 'Обработать и записать';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const dateFormat = new Intl.DateTimeFormat('ru-RU', { dateStyle: 'short', timeStyle: 'short' });
|
||||
|
||||
function ticketFolder(ticket) {
|
||||
return ticket.queue === 'spam' || ticket.category === 'spam' ? 'spam' : 'regular';
|
||||
}
|
||||
|
||||
function ticketChat(ticket, draft) {
|
||||
const section = element('section', '', 'ticket-chat');
|
||||
const heading = element('h4', 'Переписка');
|
||||
const scrollWindow = element('div', '', 'chat-window');
|
||||
const messages = element('ol', '', 'chat-log');
|
||||
messages.setAttribute('role', 'log');
|
||||
messages.setAttribute('aria-label', 'Переписка');
|
||||
const chatStatus = element('p', 'Загрузка переписки…', 'note');
|
||||
scrollWindow.append(messages);
|
||||
scrollWindow.hidden = true;
|
||||
section.append(heading, scrollWindow, chatStatus);
|
||||
|
||||
const replyForm = element('form');
|
||||
const replyLabel = element('label', 'Ответ в чат');
|
||||
replyLabel.htmlFor = 'chat-reply';
|
||||
const reply = element('textarea');
|
||||
reply.id = replyLabel.htmlFor;
|
||||
reply.rows = 3;
|
||||
reply.maxLength = 2500;
|
||||
reply.required = true;
|
||||
const copyDraft = element('button', 'Вставить черновик', 'secondary');
|
||||
copyDraft.type = 'button';
|
||||
copyDraft.addEventListener('click', () => {
|
||||
reply.value = draft.value;
|
||||
reply.focus();
|
||||
});
|
||||
const send = element('button', 'Отправить в чат');
|
||||
send.type = 'submit';
|
||||
const actions = element('div', '', 'reply-actions');
|
||||
actions.append(send, copyDraft);
|
||||
const replyStatus = element('p', '', 'feedback');
|
||||
replyStatus.setAttribute('role', 'status');
|
||||
replyForm.append(replyLabel, reply, actions);
|
||||
section.append(replyForm, replyStatus);
|
||||
|
||||
const endpoint = `/api/tickets/${ticket.id}/messages`;
|
||||
const authorLabels = { customer: 'Пользователь', staff: 'Сотрудник', moderator: 'Модерация', operator: 'Поддержка' };
|
||||
let renderedCount = 0;
|
||||
let refreshPending;
|
||||
let replyAttempt;
|
||||
|
||||
function updateCategory(category) {
|
||||
replyForm.hidden = category === 'spam';
|
||||
}
|
||||
|
||||
function renderMessages(items) {
|
||||
if (!section.isConnected || items.length < renderedCount) return;
|
||||
const wasAtBottom = scrollWindow.scrollHeight - scrollWindow.scrollTop - scrollWindow.clientHeight < 40;
|
||||
const fragment = document.createDocumentFragment();
|
||||
for (const item of items.slice(renderedCount)) {
|
||||
const bubble = element('li');
|
||||
bubble.dataset.role = item.role;
|
||||
const meta = element('div', '', 'chat-meta');
|
||||
const time = element('time', dateFormat.format(new Date(item.createdAt)));
|
||||
time.dateTime = item.createdAt;
|
||||
meta.append(element('strong', authorLabels[item.role] || 'Участник'), time);
|
||||
bubble.append(meta, element('p', item.text));
|
||||
fragment.append(bubble);
|
||||
}
|
||||
messages.append(fragment);
|
||||
renderedCount = items.length;
|
||||
scrollWindow.hidden = renderedCount === 0;
|
||||
chatStatus.hidden = renderedCount > 0;
|
||||
chatStatus.textContent = 'Сообщений пока нет.';
|
||||
if (wasAtBottom) scrollWindow.scrollTop = scrollWindow.scrollHeight;
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
if (refreshPending) return refreshPending;
|
||||
refreshPending = api(endpoint).then(({ messages: items }) => renderMessages(items)).catch(() => {
|
||||
if (!renderedCount) chatStatus.textContent = 'Не удалось загрузить переписку.';
|
||||
}).finally(() => { refreshPending = null; });
|
||||
return refreshPending;
|
||||
}
|
||||
|
||||
replyForm.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
const text = reply.value.trim();
|
||||
if (!text || send.disabled || replyForm.hidden || clearing || exited) return;
|
||||
const expected = generation;
|
||||
if (replyAttempt?.text !== text) replyAttempt = { requestId: crypto.randomUUID(), text };
|
||||
send.disabled = true;
|
||||
copyDraft.disabled = true;
|
||||
reply.disabled = true;
|
||||
replyStatus.textContent = '';
|
||||
try {
|
||||
const { messages: items } = await api(endpoint, 'POST', replyAttempt);
|
||||
if (!isCurrent(expected) || !section.isConnected) return;
|
||||
renderMessages(items);
|
||||
scrollWindow.scrollTop = scrollWindow.scrollHeight;
|
||||
replyAttempt = null;
|
||||
reply.value = '';
|
||||
replyStatus.textContent = 'Ответ отправлен.';
|
||||
replyStatus.dataset.error = false;
|
||||
await refreshQueue(true).catch(() => {});
|
||||
} catch (error) {
|
||||
if (isCurrent(expected) && section.isConnected) {
|
||||
replyStatus.textContent = error.message;
|
||||
replyStatus.dataset.error = true;
|
||||
}
|
||||
} finally {
|
||||
send.disabled = false;
|
||||
copyDraft.disabled = false;
|
||||
reply.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
updateCategory(ticket.category);
|
||||
return { section, refresh, updateCategory };
|
||||
}
|
||||
|
||||
function renderDetail(ticket) {
|
||||
currentDetail = null;
|
||||
detail.replaceChildren();
|
||||
detail.hidden = !ticket;
|
||||
if (!ticket) return;
|
||||
|
||||
const title = element('h3', `Обращение №${ticket.id}`);
|
||||
const created = dateFormat.format(new Date(ticket.createdAt));
|
||||
const meta = element('p', `${ticket.channel} · ${config.actors[ticket.actor]} · ${created} · ${modeLabel(ticket)}`, 'detail-meta');
|
||||
const text = element('p', ticket.text, 'detail-text');
|
||||
const reason = element('p', `Первичная оценка. ${ticket.reason}`, 'note');
|
||||
detail.append(title, meta);
|
||||
if (ticket.externalId) detail.append(element('p', `ID в источнике ${ticket.externalId}`, 'detail-meta source-id'));
|
||||
text.hidden = ticket.hasConversation;
|
||||
const review = element('p', 'Нужна проверка деталей', 'note review');
|
||||
review.hidden = !ticket.needsReview || ticket.status !== 'new';
|
||||
detail.append(text, reason, review);
|
||||
|
||||
const form = element('form');
|
||||
const controls = element('div', '', 'controls');
|
||||
const category = element('select');
|
||||
category.id = 'edit-category';
|
||||
fillSelect(category, config.categories, ticket.category);
|
||||
const status = element('select');
|
||||
status.id = 'edit-status';
|
||||
fillSelect(status, config.statuses, ticket.status);
|
||||
const categoryLabel = element('label', 'Категория');
|
||||
categoryLabel.htmlFor = category.id;
|
||||
const statusLabel = element('label', 'Статус');
|
||||
statusLabel.htmlFor = status.id;
|
||||
const categoryControl = element('div');
|
||||
categoryControl.append(categoryLabel, category);
|
||||
const statusControl = element('div');
|
||||
statusControl.append(statusLabel, status);
|
||||
controls.append(categoryControl, statusControl);
|
||||
form.append(controls);
|
||||
|
||||
const impact = element('input');
|
||||
impact.type = 'checkbox';
|
||||
impact.checked = ticket.studentImpact;
|
||||
if (ticket.actor === 'staff') {
|
||||
const impactLabel = element('label', '', 'checkbox');
|
||||
impactLabel.append(impact, element('span', 'Сбой уже затрагивает учеников'));
|
||||
form.append(impactLabel);
|
||||
}
|
||||
|
||||
const draft = element('textarea');
|
||||
draft.id = 'edit-draft';
|
||||
draft.rows = 4;
|
||||
draft.maxLength = 2500;
|
||||
draft.required = true;
|
||||
draft.value = ticket.draft;
|
||||
const draftLabel = element('label');
|
||||
draftLabel.htmlFor = draft.id;
|
||||
const updateDraftLabel = () => {
|
||||
draftLabel.textContent = category.value === 'spam' ? 'Рекомендация оператору' : 'Черновик ответа';
|
||||
};
|
||||
category.addEventListener('change', updateDraftLabel);
|
||||
updateDraftLabel();
|
||||
const save = element('button', 'Сохранить правку');
|
||||
save.type = 'submit';
|
||||
form.append(draftLabel, draft, save);
|
||||
const thread = ticket.hasConversation ? ticketChat(ticket, draft) : null;
|
||||
if (thread) detail.append(thread.section);
|
||||
detail.append(form);
|
||||
|
||||
const fieldValues = () => ({
|
||||
category: category.value, status: status.value, studentImpact: impact.checked, draft: draft.value,
|
||||
});
|
||||
let savedValues = fieldValues();
|
||||
let saving = false;
|
||||
|
||||
function applyFields(updated) {
|
||||
category.value = updated.category;
|
||||
status.value = updated.status;
|
||||
impact.checked = updated.studentImpact;
|
||||
draft.value = updated.draft;
|
||||
savedValues = fieldValues();
|
||||
updateDraftLabel();
|
||||
}
|
||||
|
||||
function updateDetail(updated) {
|
||||
if (!updated || updated.id !== ticket.id) return;
|
||||
reason.textContent = `Первичная оценка. ${updated.reason}`;
|
||||
review.hidden = !updated.needsReview || updated.status !== 'new';
|
||||
thread?.updateCategory(updated.category);
|
||||
const dirty = Object.entries(fieldValues()).some(([key, value]) => value !== savedValues[key]);
|
||||
if (!saving && !dirty && !form.contains(document.activeElement)) applyFields(updated);
|
||||
}
|
||||
|
||||
currentDetail = { update: updateDetail, refresh: thread?.refresh };
|
||||
thread?.refresh();
|
||||
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
if (saving || clearing || exited) return;
|
||||
const expected = generation;
|
||||
const patch = Object.fromEntries(Object.entries(fieldValues()).filter(([key, value]) => value !== savedValues[key]));
|
||||
if (!Object.keys(patch).length) return;
|
||||
const fields = [category, status, impact, draft, save];
|
||||
saving = true;
|
||||
for (const field of fields) field.disabled = true;
|
||||
feedback('queue-feedback');
|
||||
try {
|
||||
const { ticket: updated } = await api(`/api/tickets/${ticket.id}`, 'PATCH', patch);
|
||||
if (!isCurrent(expected) || !form.isConnected) return;
|
||||
applyFields(updated);
|
||||
if (selectedId === ticket.id) {
|
||||
folder = ticketFolder(updated);
|
||||
tickets = tickets.map((existing) => existing.id === updated.id ? updated : existing);
|
||||
renderQueue(true);
|
||||
}
|
||||
feedback('queue-feedback', `Правки к обращению №${ticket.id} сохранены.`);
|
||||
await refreshQueue(true).catch(() => {
|
||||
if (isCurrent(expected)) {
|
||||
feedback('queue-feedback', `Правки к обращению №${ticket.id} сохранены. Обновите страницу, чтобы загрузить очередь.`);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (isCurrent(expected) && form.isConnected) feedback('queue-feedback', error.message, true);
|
||||
} finally {
|
||||
saving = false;
|
||||
for (const field of fields) field.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderQueue(preserveDetail = false) {
|
||||
const selected = tickets.find((ticket) => ticket.id === selectedId);
|
||||
if (!selected) selectedId = null;
|
||||
const visibleTickets = tickets.filter((ticket) => ticketFolder(ticket) === folder);
|
||||
const focusedId = ticketRows.contains(document.activeElement) ? document.activeElement.dataset.ticket : null;
|
||||
const fragment = document.createDocumentFragment();
|
||||
for (const ticket of visibleTickets) {
|
||||
const row = element('tr');
|
||||
const summary = element('td');
|
||||
const preview = ticket.text.replace(/\s+/gu, ' ');
|
||||
const short = preview.length > 94 ? `${preview.slice(0, 93)}…` : preview;
|
||||
const show = element('button', `№${ticket.id} · ${short}`, 'ticket-button');
|
||||
show.type = 'button';
|
||||
show.setAttribute('aria-expanded', String(ticket.id === selectedId));
|
||||
show.setAttribute('aria-controls', detail.id);
|
||||
show.addEventListener('click', () => {
|
||||
selectedId = selectedId === ticket.id ? null : ticket.id;
|
||||
feedback('queue-feedback');
|
||||
renderQueue();
|
||||
ticketRows.querySelector(`[data-ticket="${ticket.id}"]`)?.focus();
|
||||
});
|
||||
show.dataset.ticket = ticket.id;
|
||||
summary.append(show, element('span', `${ticket.channel} · ${config.actors[ticket.actor]} · ${config.topics[ticket.topic]}`, 'ticket-meta'));
|
||||
|
||||
const category = element('td');
|
||||
const badge = element('span', config.categories[ticket.category], 'tag');
|
||||
badge.dataset.category = ticket.category;
|
||||
category.append(badge);
|
||||
row.append(summary, category, element('td', config.queues[ticket.queue]), element('td', config.statuses[ticket.status]));
|
||||
fragment.append(row);
|
||||
}
|
||||
|
||||
ticketRows.replaceChildren(fragment);
|
||||
byId('empty').hidden = visibleTickets.length > 0;
|
||||
byId('empty-title').textContent = folder === 'spam' ? 'Папка спама пуста' : 'Обращений пока нет';
|
||||
byId('empty-note').hidden = folder === 'spam';
|
||||
byId('table-container').hidden = visibleTickets.length === 0;
|
||||
byId('counter').textContent = `Всего ${visibleTickets.length} · новых ${visibleTickets.filter((ticket) => ticket.status === 'new').length}`;
|
||||
regularFilter.setAttribute('aria-pressed', String(folder === 'regular'));
|
||||
spamFilter.setAttribute('aria-pressed', String(folder === 'spam'));
|
||||
const spamCount = tickets.length - tickets.filter((ticket) => ticketFolder(ticket) === 'regular').length;
|
||||
spamFilter.textContent = spamCount ? `Спам (${spamCount})` : 'Спам';
|
||||
if (focusedId) ticketRows.querySelector(`[data-ticket="${focusedId}"]`)?.focus();
|
||||
if (preserveDetail && selected) currentDetail?.update(selected);
|
||||
else renderDetail(selected);
|
||||
}
|
||||
|
||||
function openFolder(next) {
|
||||
if (!config || folder === next) return;
|
||||
folder = next;
|
||||
selectedId = null;
|
||||
feedback('queue-feedback');
|
||||
renderQueue();
|
||||
}
|
||||
|
||||
regularFilter.addEventListener('click', () => openFolder('regular'));
|
||||
spamFilter.addEventListener('click', () => openFolder('spam'));
|
||||
|
||||
function refreshQueue(polling = false) {
|
||||
const expected = generation;
|
||||
const refresh = queueRefresh.catch(() => {}).then(async () => {
|
||||
if (!isCurrent(expected)) return;
|
||||
const { tickets: nextTickets } = await api('/api/tickets');
|
||||
if (!isCurrent(expected)) return;
|
||||
const changed = nextTickets.length !== tickets.length || nextTickets.some((ticket, index) => (
|
||||
ticket.id !== tickets[index].id || ticket.updatedAt !== tickets[index].updatedAt
|
||||
));
|
||||
tickets = nextTickets;
|
||||
if (!polling || changed) renderQueue(polling);
|
||||
else currentDetail?.update(tickets.find((ticket) => ticket.id === selectedId));
|
||||
});
|
||||
queueRefresh = refresh;
|
||||
return refresh;
|
||||
}
|
||||
|
||||
async function pollQueue() {
|
||||
const expected = generation;
|
||||
await Promise.allSettled([refreshQueue(true), currentDetail?.refresh?.()]);
|
||||
if (isCurrent(expected) && !clearing) scheduleQueuePoll();
|
||||
}
|
||||
|
||||
function scheduleQueuePoll() {
|
||||
window.clearTimeout(queueTimer);
|
||||
queueTimer = window.setTimeout(pollQueue, 4000);
|
||||
}
|
||||
|
||||
function resetPanel() {
|
||||
attempt = null;
|
||||
selectedId = null;
|
||||
folder = 'regular';
|
||||
tickets = [];
|
||||
exampleField.value = '';
|
||||
actorField.value = '';
|
||||
messageField.value = '';
|
||||
submit.textContent = 'Обработать и записать';
|
||||
feedback('intake-feedback');
|
||||
feedback('queue-feedback');
|
||||
renderQueue();
|
||||
}
|
||||
|
||||
clearButton.addEventListener('click', async () => {
|
||||
if (exited || clearing || !window.confirm('Удалить все обращения и историю обоих чатов? Это действие нельзя отменить.')) return;
|
||||
clearing = true;
|
||||
window.clearTimeout(queueTimer);
|
||||
clearButton.disabled = true;
|
||||
exitButton.disabled = true;
|
||||
panelView.inert = true;
|
||||
feedback('queue-feedback');
|
||||
try {
|
||||
await api('/api/tickets/clear', 'POST', {});
|
||||
invalidatePanel();
|
||||
resetPanel();
|
||||
setIntakeDisabled(false);
|
||||
feedback('queue-feedback', 'Буфер очищен. Обращения и история чатов удалены.');
|
||||
await refreshQueue().catch(() => {});
|
||||
} catch (error) {
|
||||
feedback('queue-feedback', error.message, true);
|
||||
} finally {
|
||||
clearing = false;
|
||||
clearButton.disabled = false;
|
||||
exitButton.disabled = false;
|
||||
panelView.inert = false;
|
||||
clearButton.focus();
|
||||
scheduleQueuePoll();
|
||||
}
|
||||
});
|
||||
|
||||
exitButton.addEventListener('click', () => {
|
||||
if (clearing) return;
|
||||
exited = true;
|
||||
invalidatePanel();
|
||||
resetPanel();
|
||||
panelView.hidden = true;
|
||||
exitView.hidden = false;
|
||||
exitButton.hidden = true;
|
||||
byId('mode').hidden = true;
|
||||
returnButton.focus();
|
||||
});
|
||||
|
||||
returnButton.addEventListener('click', () => {
|
||||
if (!exited) return;
|
||||
exited = false;
|
||||
panelView.hidden = false;
|
||||
exitView.hidden = true;
|
||||
exitButton.hidden = false;
|
||||
byId('mode').hidden = false;
|
||||
initialize();
|
||||
exitButton.focus();
|
||||
});
|
||||
|
||||
async function initialize() {
|
||||
const expected = generation;
|
||||
clearButton.disabled = true;
|
||||
setIntakeDisabled(true);
|
||||
byId('counter').textContent = 'Загрузка обращений';
|
||||
try {
|
||||
const [settings, sampleData] = await Promise.all([api('/api/config'), api('/api/examples')]);
|
||||
if (!isCurrent(expected)) return;
|
||||
config = settings;
|
||||
examples = sampleData.examples;
|
||||
byId('mode').textContent = modeLabel(config);
|
||||
fillSelect(actorField, { '': 'Выберите тип автора', ...config.actors }, '');
|
||||
messageField.maxLength = config.maxTextLength;
|
||||
exampleField.replaceChildren(option('', 'Выбрать из списка'), ...examples.map((example, index) => option(String(index), example.title)));
|
||||
setIntakeDisabled(false);
|
||||
await refreshQueue();
|
||||
if (isCurrent(expected)) {
|
||||
clearButton.disabled = false;
|
||||
scheduleQueuePoll();
|
||||
}
|
||||
} catch (error) {
|
||||
if (!isCurrent(expected)) return;
|
||||
byId('counter').textContent = 'Очередь недоступна';
|
||||
feedback('queue-feedback', error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
initialize();
|
||||
Reference in new issue
Block a user