testingwebapp fixes, weeek tasks 2948-2958
This commit is contained in:
@@ -7,6 +7,8 @@
|
||||
data-test-id="{{ test_id }}"
|
||||
data-initial='{{ content | tojson | safe }}'>
|
||||
|
||||
<div id="editor-gen-toast" class="editor-gen-toast" role="status" aria-live="polite" hidden></div>
|
||||
|
||||
<section class="cabinet-brick cabinet-brick--hero hero-brick">
|
||||
<a href="{{ url_for('tests.tests_list_page') }}" class="link-back">← К тестам</a>
|
||||
<textarea id="test-title" maxlength="200" rows="1" placeholder="Название теста"
|
||||
@@ -164,6 +166,10 @@
|
||||
placeholder="Например: охрана труда на производстве"
|
||||
style="resize:none; overflow:hidden; font-family:inherit;"></textarea>
|
||||
</label>
|
||||
<label class="mt-2 inline-flex items-start gap-2 text-sm text-ink-700 cursor-pointer select-none">
|
||||
<input type="checkbox" id="ai-keep-title" class="mt-1 rounded border-ink-300 text-brand-600 focus:ring-brand-500" />
|
||||
<span>Не менять название теста в редакторе после генерации (оставить текущее)</span>
|
||||
</label>
|
||||
<div class="mt-2">
|
||||
<button id="ai-generate-test"
|
||||
class="btn btn-ghost" type="button" style="min-height:43px;">
|
||||
@@ -185,7 +191,18 @@
|
||||
<p class="text-xs text-ink-500 leading-snug mb-3">
|
||||
<span class="font-medium text-ink-600">Предложить улучшение</span> — ИИ предложит правки по каждому вопросу (было → стало); вы отметите, что применить к черновику.
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="flex flex-col sm:flex-row sm:flex-wrap sm:items-end gap-2">
|
||||
<label class="flex flex-col gap-1 min-w-[12rem] flex-1">
|
||||
<span class="text-xs font-medium text-ink-600">Область улучшения</span>
|
||||
<select id="ai-improve-focus"
|
||||
class="form-input text-sm py-2 rounded-lg border border-ink-300 bg-white">
|
||||
<option value="all">Всё: вопросы и варианты</option>
|
||||
<option value="questions">Только формулировки вопросов</option>
|
||||
<option value="distractors">Только неверные варианты (дистракторы)</option>
|
||||
<option value="options">Все варианты ответа (без смены верности)</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button id="ai-check"
|
||||
class="btn btn-ghost" type="button" style="min-height:43px;">
|
||||
<span class="material-symbols-outlined text-base" style="vertical-align:-3px;">fact_check</span>
|
||||
@@ -196,13 +213,14 @@
|
||||
<span class="material-symbols-outlined text-base" style="vertical-align:-3px;">auto_fix_high</span>
|
||||
Предложить улучшение
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ── Документ в вопросы ──────────────────────────────────── #}
|
||||
<div class="question-editor-block test-detail-subsection test-detail-subsection--import">
|
||||
<h3 class="test-detail-subsection__title">Документ в вопросы</h3>
|
||||
<p class="text-xs text-ink-500 leading-snug mb-2">
|
||||
<p class="text-xs text-ink-500 leading-snug mb-2 text-center">
|
||||
<span class="font-medium text-ink-600">Сгенерировать из документа</span> — из файла извлекается текст; ИИ составляет вопросы по содержанию и шаблону из «Параметров» (число вопросов, вариантов, несколько верных и т.д.), с учётом поля «Пожелания», если оно заполнено. Перед заменой откроется предпросмотр: «Применить» подставит черновик вместо текущих вопросов; дальше сохраните тест на сервер — подсказки и версии ведут себя так же, как при генерации по теме.
|
||||
</p>
|
||||
<label id="ai-import-dropzone"
|
||||
@@ -210,10 +228,15 @@
|
||||
px-4 py-5 rounded-xl bg-white border-2 border-dashed border-ink-300/70
|
||||
hover:border-brand-400 hover:bg-brand-50/40 cursor-pointer transition-colors">
|
||||
<span class="material-symbols-outlined text-2xl text-brand-400">upload_file</span>
|
||||
<span id="ai-import-dropzone-label" class="text-sm font-medium text-ink-700">Перетащите файл сюда или нажмите</span>
|
||||
<span class="text-xs text-ink-400">PDF, DOCX, TXT, MD · до 16 МБ</span>
|
||||
<input id="ai-import-file" type="file" accept=".pdf,.docx,.txt,.md" class="hidden" />
|
||||
<span id="ai-import-dropzone-label" class="text-sm font-medium text-ink-700 text-center block w-full">Перетащите файлы сюда или нажмите</span>
|
||||
<span class="text-xs text-ink-400 text-center block w-full">PDF, DOCX, TXT, MD · до 5 за раз · до 16 МБ · повторный выбор добавляет к уже загруженным; полный сброс — «Сбросить загрузку».</span>
|
||||
<input id="ai-import-file" type="file" accept=".pdf,.docx,.txt,.md" multiple class="hidden" />
|
||||
</label>
|
||||
<div class="mt-2 flex flex-wrap gap-2">
|
||||
<button type="button" id="ai-import-clear" class="btn btn-ghost btn--sm text-sm" style="min-height:36px;">
|
||||
Сбросить загрузку
|
||||
</button>
|
||||
</div>
|
||||
<label class="block mt-3">
|
||||
<span class="form-label">Пожелания по содержанию <span class="text-ink-400 font-normal">(необязательно)</span></span>
|
||||
<textarea id="doc-user-hint" rows="1"
|
||||
@@ -226,7 +249,6 @@
|
||||
<span class="material-symbols-outlined text-base" style="vertical-align:-3px;">auto_awesome</span>
|
||||
Сгенерировать из документа
|
||||
</button>
|
||||
<p id="doc-progress" class="mt-2 text-xs text-ink-500 min-h-[1rem]"></p>
|
||||
</div>
|
||||
|
||||
{# ── Модалка результата импорта документа ─────────────────── #}
|
||||
@@ -238,7 +260,6 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<p id="ai-status" class="editor-generation-panel__status text-sm text-ink-500"></p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
@@ -257,7 +278,7 @@
|
||||
<h2 class="font-semibold text-ink-900">Вопросы (<span id="q-count-mirror">0</span>)</h2>
|
||||
</div>
|
||||
<ol id="questions" class="mt-3 space-y-4"></ol>
|
||||
<div class="mt-3 flex justify-center">
|
||||
<div class="mt-3 flex justify-center flex-wrap gap-2">
|
||||
<button id="add-question"
|
||||
class="inline-flex items-center gap-1 px-3 py-2 rounded-lg
|
||||
bg-white border border-ink-300/60 hover:border-brand-300 text-sm min-h-10
|
||||
@@ -265,6 +286,14 @@
|
||||
<span class="material-symbols-outlined text-base">add</span>
|
||||
<span>Добавить вопрос</span>
|
||||
</button>
|
||||
<button id="add-question-ai" type="button"
|
||||
class="inline-flex items-center gap-1 px-3 py-2 rounded-lg
|
||||
bg-brand-50 border border-brand-200 hover:border-brand-400 text-sm min-h-10
|
||||
btn btn-ghost question-editor__add-question-ai"
|
||||
title="Добавляет пустой блок и вызывает ИИ для одного нового вопроса с вариантами">
|
||||
<span class="material-symbols-outlined text-base">auto_awesome</span>
|
||||
<span>Новый вопрос (ИИ)</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
@@ -396,11 +425,26 @@
|
||||
<input type="checkbox"
|
||||
class="opt-correct w-5 h-5 rounded border-ink-300 text-brand-600 focus:ring-brand-500" />
|
||||
</label>
|
||||
<textarea rows="1"
|
||||
class="opt-text flex-1 min-w-0 rounded-lg border border-ink-300 px-3 py-2
|
||||
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20"
|
||||
placeholder="Вариант ответа"
|
||||
style="resize:none; overflow:hidden; font-family:inherit; line-height:1.55;"></textarea>
|
||||
<div class="opt-text-wrap relative flex-1 min-w-0 self-start">
|
||||
<textarea rows="1"
|
||||
class="opt-text w-full rounded-lg border border-ink-300 px-3 py-2
|
||||
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20"
|
||||
placeholder="Вариант ответа"
|
||||
style="resize:none; overflow:hidden; font-family:inherit; line-height:1.55;"></textarea>
|
||||
<div class="opt-ai-overlay hidden absolute inset-0 rounded-lg z-[5]
|
||||
bg-white/85 backdrop-blur-[2px] flex flex-col items-center justify-center gap-1
|
||||
border border-ink-200/80 shadow-sm">
|
||||
<span class="inline-block w-6 h-6 rounded-full
|
||||
border-[3px] border-brand-200 border-t-brand-600 animate-spin"></span>
|
||||
<span class="text-xs text-ink-600 font-medium px-2 text-center leading-snug">Улучшаю…</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="opt-ai shrink-0 inline-flex items-center justify-center
|
||||
rounded hover:bg-brand-50 text-brand-700 px-1.5 pt-1.5"
|
||||
style="min-height:2.5rem; min-width:2.25rem;"
|
||||
title="Улучшить только этот вариант (ИИ)" aria-label="Улучшить вариант ИИ">
|
||||
<span class="material-symbols-outlined text-base">auto_fix_high</span>
|
||||
</button>
|
||||
<button class="opt-delete shrink-0 w-10 inline-flex items-center justify-center
|
||||
rounded hover:bg-red-50 text-red-600 pt-1.5"
|
||||
style="min-height:2.5rem;"
|
||||
@@ -428,6 +472,40 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
{# ── Выбор режима ИИ для непустого вопроса (улучшить / дистракторы) ── #}
|
||||
<dialog id="dlg-q-ai-mode" class="save-modal" style="max-width: 24rem; width: calc(100% - 2rem);">
|
||||
<div class="save-modal__inner">
|
||||
<h3 class="text-base font-semibold text-ink-900 mb-1">Что сделать с вопросом?</h3>
|
||||
<p class="text-xs text-ink-500 mb-3">Выберите, что должен сделать ИИ.</p>
|
||||
<div class="flex flex-col gap-2">
|
||||
<button type="button" id="q-ai-mode-distractors"
|
||||
class="px-3 py-2.5 rounded-lg border border-ink-200 text-left text-sm
|
||||
hover:bg-brand-50 hover:border-brand-200 transition-colors">
|
||||
<span class="font-medium text-ink-800">Добавить дистракторы</span>
|
||||
<span class="block text-ink-500 text-xs mt-0.5">Заполнить пустые поля вариантов</span>
|
||||
</button>
|
||||
<button type="button" id="q-ai-mode-question"
|
||||
class="px-3 py-2.5 rounded-lg border border-ink-200 text-left text-sm
|
||||
hover:bg-brand-50 hover:border-brand-200 transition-colors">
|
||||
<span class="font-medium text-ink-800">Улучшить только вопрос</span>
|
||||
<span class="block text-ink-500 text-xs mt-0.5">Переформулировать текст вопроса</span>
|
||||
</button>
|
||||
<button type="button" id="q-ai-mode-options"
|
||||
class="px-3 py-2.5 rounded-lg border border-ink-200 text-left text-sm
|
||||
hover:bg-brand-50 hover:border-brand-200 transition-colors">
|
||||
<span class="font-medium text-ink-800">Улучшить только варианты</span>
|
||||
<span class="block text-ink-500 text-xs mt-0.5">Тексты ответов, без смены верности</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end">
|
||||
<button type="button" id="q-ai-mode-cancel"
|
||||
class="px-3 py-2 rounded-lg text-ink-600 hover:bg-ink-100 text-sm">
|
||||
Отмена
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
{# ── Модалка результата AI-проверки/улучшения (fullscreen на мобиле) ── #}
|
||||
<dialog id="ai-modal"
|
||||
class="m-0 p-0 w-full h-full sm:h-auto sm:max-w-3xl sm:w-full sm:max-h-[90vh]
|
||||
|
||||
@@ -19,16 +19,37 @@
|
||||
{% block content %}
|
||||
<section class="legacy-list-shell">
|
||||
<h1 class="font-headline legacy-list-title">Тесты</h1>
|
||||
<div class="legacy-list-toolbar">
|
||||
<button id="btn-create-test" class="btn btn-ghost" type="button">
|
||||
<div class="legacy-list-toolbar legacy-list-toolbar--wrap flex flex-wrap items-center gap-3">
|
||||
<div class="flex flex-col sm:flex-row flex-1 min-w-0 gap-2 sm:gap-3">
|
||||
<label class="flex flex-col gap-1 flex-1 min-w-[12rem] max-w-md">
|
||||
<span class="text-xs font-medium text-ink-600">Поиск по названию</span>
|
||||
<input id="catalog-search" type="search" autocomplete="off" placeholder="Начните вводить…"
|
||||
class="rounded-lg border border-ink-300 px-3 py-2 text-sm w-full
|
||||
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20" />
|
||||
</label>
|
||||
<label class="flex flex-col gap-1 w-full sm:w-52 shrink-0">
|
||||
<span class="text-xs font-medium text-ink-600">Автор</span>
|
||||
<select id="catalog-author"
|
||||
class="rounded-lg border border-ink-300 px-3 py-2 text-sm w-full bg-white
|
||||
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20">
|
||||
<option value="">Все авторы</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<button id="btn-create-test" class="btn btn-ghost shrink-0" type="button">
|
||||
Создать
|
||||
</button>
|
||||
</div>
|
||||
<p id="catalog-filter-empty" class="text-sm text-ink-500 mt-2 hidden" role="status"></p>
|
||||
|
||||
{% if visible %}
|
||||
<ul class="list-stack" aria-label="Тесты в общем списке">
|
||||
{% for t in visible %}
|
||||
<li class="list-row list-row--split">
|
||||
<li class="list-row list-row--split list-row--catalog"
|
||||
data-catalog-row
|
||||
data-title-lower="{{ (t.title or '')|lower }}"
|
||||
data-author-id="{{ t.created_by or '' }}"
|
||||
data-author-name="{{ (t.author_full_name or '—')|e }}">
|
||||
<div class="list-row__main">
|
||||
<a href="{{ url_for('tests.tests_editor_page', test_id=t.id) }}" class="list-row__link">
|
||||
<span class="list-row__title">{{ t.title }}</span>
|
||||
@@ -55,7 +76,11 @@
|
||||
<h2 class="font-headline legacy-list-subtitle">Скрытые вами из списка</h2>
|
||||
<ul class="list-stack" aria-label="Скрытые тесты автора">
|
||||
{% for t in hidden %}
|
||||
<li class="list-row list-row--split list-row--hidden">
|
||||
<li class="list-row list-row--split list-row--hidden list-row--catalog"
|
||||
data-catalog-row
|
||||
data-title-lower="{{ (t.title or '')|lower }}"
|
||||
data-author-id="{{ t.created_by or '' }}"
|
||||
data-author-name="{{ (t.author_full_name or '—')|e }}">
|
||||
<div class="list-row__main">
|
||||
<a href="{{ url_for('tests.tests_editor_page', test_id=t.id) }}" class="list-row__link">
|
||||
<span class="list-row__title">{{ t.title }}</span>
|
||||
@@ -124,6 +149,58 @@ class="m-0 p-0 w-full sm:w-full sm:max-w-md
|
||||
const dlg = document.getElementById('dlg-create');
|
||||
const titleEl = document.getElementById('new-test-title');
|
||||
const descEl = document.getElementById('new-test-desc');
|
||||
const catalogSearch = document.getElementById('catalog-search');
|
||||
const catalogAuthor = document.getElementById('catalog-author');
|
||||
const catalogEmpty = document.getElementById('catalog-filter-empty');
|
||||
|
||||
(function initCatalogFilter() {
|
||||
if (!catalogSearch || !catalogAuthor) return;
|
||||
const rows = Array.from(document.querySelectorAll('[data-catalog-row]'));
|
||||
const byAuthor = new Map();
|
||||
rows.forEach((row) => {
|
||||
const id = (row.dataset.authorId || '').trim();
|
||||
const name = (row.dataset.authorName || '').trim() || '—';
|
||||
if (id && !byAuthor.has(id)) byAuthor.set(id, name);
|
||||
});
|
||||
const sorted = Array.from(byAuthor.entries()).sort((a, b) => a[1].localeCompare(b[1], 'ru'));
|
||||
sorted.forEach(([id, name]) => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = id;
|
||||
opt.textContent = name;
|
||||
catalogAuthor.appendChild(opt);
|
||||
});
|
||||
|
||||
function applyFilter() {
|
||||
const q = (catalogSearch.value || '').trim().toLowerCase();
|
||||
const author = (catalogAuthor.value || '').trim();
|
||||
let visibleCount = 0;
|
||||
rows.forEach((row) => {
|
||||
const title = row.dataset.titleLower || '';
|
||||
const aid = (row.dataset.authorId || '').trim();
|
||||
const matchQ = !q || title.includes(q);
|
||||
const matchA = !author || aid === author;
|
||||
const show = matchQ && matchA;
|
||||
row.style.display = show ? '' : 'none';
|
||||
if (show) visibleCount += 1;
|
||||
});
|
||||
if (catalogEmpty) {
|
||||
if (rows.length && visibleCount === 0) {
|
||||
catalogEmpty.textContent = 'Ничего не найдено — измените запрос или фильтр.';
|
||||
catalogEmpty.classList.remove('hidden');
|
||||
} else {
|
||||
catalogEmpty.textContent = '';
|
||||
catalogEmpty.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let t = null;
|
||||
catalogSearch.addEventListener('input', () => {
|
||||
clearTimeout(t);
|
||||
t = setTimeout(applyFilter, 120);
|
||||
});
|
||||
catalogAuthor.addEventListener('change', applyFilter);
|
||||
})();
|
||||
|
||||
document.getElementById('btn-create-test').addEventListener('click', () => {
|
||||
titleEl.value = '';
|
||||
|
||||
Reference in New Issue
Block a user