Add feedback system functionality

- Implement feedback management system in admin interface
- Add feedback processing and AI-based features
- Update user interface with new feedback components
- Modify server-side API endpoints for feedback handling
- Enhance worker.js for background feedback processing
- Update deployment configuration for feedback components

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
dev
2026-10-06 13:10:14 +03:00
co-authored by openhands
parent a3169648f3
commit 98f24faaaf
13 changed files with 1717 additions and 31 deletions
+53 -1
View File
@@ -460,6 +460,32 @@ INSERT INTO settings (key, value) VALUES ('lesson_ai_prompt', 'Ты — реда
ON CONFLICT (key) DO NOTHING;
INSERT INTO settings (key, value) VALUES ('notify_lesson_ai_formatted', 'false')
ON CONFLICT (key) DO NOTHING;
INSERT INTO settings (key, value) VALUES ('feedback_ai_enabled','true') ON CONFLICT (key) DO NOTHING;
INSERT INTO settings (key, value) VALUES ('feedback_ai_prompt','Ты — помощник по написанию коротких деловых отзывов тьютора детской IT-школы.
ЗАДАЧА: Сформируй короткий, ёмкий, деловой отзыв по итогам занятия. По стилю он должен быть близок к «короткому сообщению для обратной связи»: 1–3 предложения, без воды, конкретно по делу.
КОНТЕКСТ, КОТОРЫЙ ТЫ ПОЛУЧИШЬ:
- Группа: ...
- Дата занятия: ...
- Резидент: ...
- Тема занятия: ...
- Позиция темы: ... (если есть)
- Прошлый отзыв: ... (если есть)
- Короткие сообщения (выбраны): список в формате "дата – тема – сообщение"
ПРАВИЛА:
- Пиши только отзыв. Без заголовков, списков, markdown, подписей и пояснений.
- Не упоминай группу, дату, время занятия в тексте отзыва.
- Не начинай со слов «Сегодня», «Вчера», «На занятии», «В ходе занятия», если они избыточны.
- Опирайся на факты из контекста: тема занятия, выбранные короткие сообщения и прошлый отзыв (если есть).
- Сохраняй деловой, спокойный тон. Без оценок типа «отлично», «замечательно», избегай шаблонных «молодец».
- Максимально кратко: 1–2 предложения. Если хватает одного — лучше одно.
- Не оборачивай ответ в кавычки.
- Не придумывай фактов, которых нет в контексте.
ФОРМАТ ОТВЕТА: только текст отзыва, без лишних элементов.') ON CONFLICT (key) DO NOTHING;
INSERT INTO settings (key, value) VALUES ('notify_feedback_ai_ready','false') ON CONFLICT (key) DO NOTHING;
CREATE TABLE IF NOT EXISTS short_messages (
id SERIAL PRIMARY KEY,
@@ -476,7 +502,33 @@ CREATE TABLE IF NOT EXISTS short_messages (
updated_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_short_messages_date ON short_messages(lesson_date DESC);
CREATE INDEX IF NOT EXISTS idx_short_messages_deleted ON short_messages(deleted_at);
CREATE INDEX IF NOT EXISTS idx_short_messages_date ON short_messages(lesson_date DESC);
INSERT INTO settings (key, value) VALUES ('short_messages_retention_days', '365') ON CONFLICT (key) DO NOTHING;
-- Обратная связь (отзывы тьютора)
CREATE TABLE IF NOT EXISTS feedbacks (
id SERIAL PRIMARY KEY,
feedback_date DATE NOT NULL,
group_id INT NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
resident VARCHAR(200) NOT NULL,
topic VARCHAR(500),
past_review TEXT,
short_message_ids INT[] DEFAULT '{}'::int[],
text TEXT NOT NULL DEFAULT '',
ai_status VARCHAR(20) NOT NULL DEFAULT 'none',
ai_checked_at TIMESTAMPTZ,
ai_error TEXT,
ai_profile VARCHAR(100),
sent_at TIMESTAMPTZ,
author_id INT REFERENCES users(id) ON DELETE SET NULL,
branch_id INT REFERENCES branches(id) ON DELETE SET NULL,
deleted_at TIMESTAMPTZ,
purge_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_feedbacks_date ON feedbacks(feedback_date DESC);
CREATE INDEX IF NOT EXISTS idx_feedbacks_deleted ON feedbacks(deleted_at);
CREATE INDEX IF NOT EXISTS idx_feedbacks_pending ON feedbacks(ai_status, id);
+101 -20
View File
@@ -456,31 +456,32 @@ INSERT INTO settings (key, value) VALUES ('lesson_ai_prompt', 'Ты — реда
ГЛАВНОЕ ПРАВИЛО:
Отрабатывай ровно по этому исходному тексту. Ничего из примера выше в свой текст не переноси: тема, программа, персонажи и детали из примера не твои.') ON CONFLICT (key) DO NOTHING;
UPDATE settings
SET value = 'Ты — редактор сообщений тьютора детской IT-школы.
Переработай исходный текст занятия так, чтобы он звучал естественно и грамотно, как будто его написал живой тьютор родителю, а не нейросеть.
ЦЕЛЬ
Привести текст отчёта о занятии к деловому стилю по строгому шаблону. Начало фразы должно соответствовать позиции темы (первое/промежуточное/последнее занятие модуля), исходя из формулировки «Позиция темы: ...».
ГЛАВНОЕ:
- Не добавляй информацию, которой нет в исходном тексте. Сохрани все факты и смысл.
- Исправь ошибки, повторы и неудачные формулировки. Убери канцелярит, шаблонные фразы и «ИИ-язык».
- Не используй чрезмерную похвалу и не превращай обычное занятие в достижение мирового масштала.
ПРАВИЛА ФОРМУЛИРОВКИ
- Всегда начинай фразу строго в соответствии с «Позиция темы»:
- если «первое занятие модуля (1 из N)» — начинай с «На занятии ребята начали...» (или близко по смыслу, без шаблонного «На данном занятии»)
- если «промежуточное занятие модуля (K из N), K > 1 и K < N» — начинай с «На занятии ребята продолжили...»
- если «последнее занятие модуля (N из N)» — начинай с «На занятии ребята завершили...» (при N==1 — это «последнее занятие модуля (1 из 1)», используй «завершили»)
- Определи позицию ТОЛЬКО по строке «Позиция темы: ...». Не угадывай по номеру темы сам.
- Если «Позиция темы» отсутствует или не содержит N/M — начни нейтрально, без жёсткого шаблона, но сохрани деловой стиль.
- Перепиши текст в деловом стиле, сохрани смысл, факты и ключевые термины.
- Убери разговорные слова, лишние повторы, воду.
- Не добавляй факты, которых нет в исходном тексте.
- Не используй оценочные слова («отлично», «хорошо», «замечательно»), не пиши «молодцы».
СТИЛЬ И СТРУКТУРА:
- Один цельный абзац, 2–4 предложения, от третьего лица.
- Первое предложение всегда начинай со слов «На занятии ребята …». Чем занимались в начале занятия — начало модуля, продолжение или завершение — определяй по правилу НАЧАЛО ФРАЗЫ.
- Если в исходном тексте описана практическая часть, начни её со слов «В конце занятия …» или «Затем …». Нет практики в исходнике — не придумывай её.
- Вместо «они» пиши «каждый». Обращение на «вы» не используй.
- Коротко скажи, с чем познакомились или что изучали; затем — что конкретно делали; в конце — что сделал самостоятельно.
- Сократи перечисления, объединяй их через «и», «а также», не повторяй одно и то же разными словами.
- Убери разговорные и оценочные обороты: «было весело», «очень», «классно».
ЗАПРЕТЫ
- Не упоминай дату, время, группу, номер занятия и модуль.
- Не начинай со слов «Сегодня», «Вчера».
- Не используй шаблон «На данном занятии», «В рамках занятия».
- Не пиши список (маркированный/нумерованный). Только сплошной текст.
НАЧАЛО ФРАЗЫ (определяется номером темы):
Система присылает готовую подсказку строкой «Позиция темы: …». Если такой строки нет, смотри номер вида N/M в конце строки «Тема занятия: …», где N — номер занятия в модуле, M — сколько занятий в модуле всего. Пользуйся только одной из этих подсказок и всегда проверяй, что N не больше M.
- «последнее занятие модуля» (N = M, например 2/2 или 7/7): занятие завершает модуль. Первое предложение обязательно начни с «завершили» или «закончили»: «На занятии ребята завершили изучение …», «На занятии ребята закончили знакомство с …».
- «промежуточное занятие модуля» (1 < N < M, например 2/4, 3/5, 6/7): изучение продолжается. Первое предложение обязательно начни с «продолжили», «продолжали», «закрепили» или «углубили»: «На занятии ребята продолжили изучение …», «На занятии ребята закрепили …».
- «первое занятие модуля» (N = 1 и N < M, например 1/4 или 1/7): модуль начинается. Первое предложение обязательно начни с «начали» или «приступили»: «На занятии ребята начали знакомство с …», «На занятии ребята начали изучение …».
- Если подсказки нет или номер нечитаем — характер занятия не выбирай и не додумывай. Начни нейтрально, прямо с сути: «На занятии ребята разобрались …», «На занятии ребята отработали …», «На занятии ребята попробовали …».
ГЛАВНОЕ ПРАВИЛО
- Это требование сильнее всего остального: даже если начало исходного текста уже хорошо звучит, перепиши его по правилу выше. Слово «модуль», цифры номера и любые оценки в текст отчёта не переноси: номер темы — внутренний признак для выбора формулировки.
ФОРМАТ:
@@ -501,7 +502,6 @@ SET value = 'Ты — редактор сообщений тьютора дет
«На занятии ребята продолжили знакомство со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей…».
Тот же исходный текст, но «Позиция темы: последнее занятие модуля (3 из 3)»:
«На занятии ребята завершили знакомство со Scratch Jr: научились выбирать фон, добавлять и изменять персонажей…».
ГЛАВНОЕ ПРАВИЛО:
Отрабатывай ровно по этому исходному тексту. Ничего из примера выше в свой текст не переноси: тема, программа, персонажи и детали из примера не твои.'
WHERE key = 'lesson_ai_prompt' AND value IN (
@@ -589,3 +589,84 @@ ALTER TABLE short_messages ADD COLUMN IF NOT EXISTS used_at DATE;
ALTER TABLE short_messages ADD COLUMN IF NOT EXISTS author_id INT REFERENCES users(id) ON DELETE SET NULL;
INSERT INTO settings (key, value) VALUES ('short_messages_retention_days', '365') ON CONFLICT (key) DO NOTHING;
-- Feedbacks
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'feedbacks') THEN
CREATE TABLE feedbacks (
id SERIAL PRIMARY KEY,
feedback_date DATE NOT NULL,
group_id INT NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
resident VARCHAR(200) NOT NULL,
topic VARCHAR(500),
past_review TEXT,
short_message_ids INT[] DEFAULT '{}'::int[],
text TEXT NOT NULL DEFAULT '',
ai_status VARCHAR(20) NOT NULL DEFAULT 'none',
ai_checked_at TIMESTAMPTZ,
ai_error TEXT,
ai_profile VARCHAR(100),
author_id INT REFERENCES users(id) ON DELETE SET NULL,
branch_id INT REFERENCES branches(id) ON DELETE SET NULL,
deleted_at TIMESTAMPTZ,
purge_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_feedbacks_date ON feedbacks(feedback_date DESC);
CREATE INDEX IF NOT EXISTS idx_feedbacks_deleted ON feedbacks(deleted_at);
CREATE INDEX IF NOT EXISTS idx_feedbacks_pending ON feedbacks(ai_status, id);
END IF;
END$$;
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS feedback_date DATE;
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS group_id INT;
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS resident VARCHAR(200);
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS topic VARCHAR(500);
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS past_review TEXT;
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS short_message_ids INT[] DEFAULT '{}'::int[];
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS text TEXT NOT NULL DEFAULT '';
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS ai_status VARCHAR(20) NOT NULL DEFAULT 'none';
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS ai_checked_at TIMESTAMPTZ;
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS ai_error TEXT;
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS ai_profile VARCHAR(100);
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS sent_at TIMESTAMPTZ;
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS author_id INT REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS branch_id INT REFERENCES branches(id) ON DELETE SET NULL;
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS purge_at TIMESTAMPTZ;
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ DEFAULT now();
ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT now();
-- Индексы (если таблица существовала без них)
CREATE INDEX IF NOT EXISTS idx_feedbacks_date ON feedbacks(feedback_date DESC);
CREATE INDEX IF NOT EXISTS idx_feedbacks_deleted ON feedbacks(deleted_at);
CREATE INDEX IF NOT EXISTS idx_feedbacks_pending ON feedbacks(ai_status, id);
INSERT INTO settings (key, value) VALUES ('feedback_ai_enabled','true') ON CONFLICT (key) DO NOTHING;
INSERT INTO settings (key, value) VALUES ('feedback_ai_prompt','Ты — помощник по написанию коротких деловых отзывов тьютора детской IT-школы.
ЗАДАЧА: Сформируй короткий, ёмкий, деловой отзыв по итогам занятия. По стилю он должен быть близок к «короткому сообщению для обратной связи»: 1–3 предложения, без воды, конкретно по делу.
КОНТЕКСТ, КОТОРЫЙ ТЫ ПОЛУЧИШЬ:
- Группа: ...
- Дата занятия: ...
- Резидент: ...
- Тема занятия: ...
- Позиция темы: ... (если есть)
- Прошлый отзыв: ... (если есть)
- Короткие сообщения (выбраны): список в формате "дата – тема – сообщение"
ПРАВИЛА:
- Пиши только отзыв. Без заголовков, списков, markdown, подписей и пояснений.
- Не упоминай группу, дату, время занятия в тексте отзыва.
- Не начинай со слов «Сегодня», «Вчера», «На занятии», «В ходе занятия», если они избыточны.
- Опирайся на факты из контекста: тема занятия, выбранные короткие сообщения и прошлый отзыв (если есть).
- Сохраняй деловой, спокойный тон. Без оценок типа «отлично», «замечательно», избегай шаблонных «молодец».
- Максимально кратко: 1–2 предложения. Если хватает одного — лучше одно.
- Не оборачивай ответ в кавычки.
- Не придумывай фактов, которых нет в контексте.
ФОРМАТ ОТВЕТА: только текст отзыва, без лишних элементов.') ON CONFLICT (key) DO NOTHING;
INSERT INTO settings (key, value) VALUES ('notify_feedback_ai_ready','false') ON CONFLICT (key) DO NOTHING;