feat: certificate page for print, API certificate data, AI prompts for full lessons

- Certificate: page /certificate/[courseId] with print-friendly design, GET /certificates/:courseId/data
- Buttons 'Получить сертификат' open certificate page instead of data-URL
- AI: prompts for longer courses (more chapters/lessons), full detailed lesson content with examples (1000–1500+ words)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
root
2026-02-06 11:32:58 +00:00
parent 5241144bc5
commit c809d049fe
8 changed files with 192 additions and 61 deletions

View File

@ -143,10 +143,10 @@ export class OpenRouterProvider {
Пользователь хочет создать курс. Твоя задача - задать уточняющие вопросы,
чтобы лучше понять его потребности и создать максимально релевантный курс.
Сгенерируй 3-5 вопросов. Обязательно включи вопрос про объём курса:
- Короткий (2-4 главы, введение в тему)
- Средний (4-7 глав, хорошее покрытие)
- Длинный / полный (6-12 глав, глубокое погружение)
Сгенерируй 3-5 вопросов. Обязательно включи вопрос про объём курса (важно для длины):
- Короткий (3-4 главы, по 2-4 урока — только введение в тему)
- Средний (5-7 глав, по 4-6 уроков — полноценное покрытие)
- Длинный / полный (7-12 глав, по 5-8 уроков — глубокое погружение, рекомендуемый вариант для серьёзного обучения)
Остальные вопросы: целевая аудитория, глубина материала, специфические темы.
@ -198,13 +198,13 @@ export class OpenRouterProvider {
const systemPrompt = `Ты - эксперт по созданию образовательных курсов.
Создай структуру курса на основе запроса пользователя и его ответов на уточняющие вопросы.
ОБЪЁМ КУРСА (соблюдай строго по ответам пользователя):
- Если пользователь выбрал короткий курс / введение: 24 главы, в каждой 24 урока. estimatedTotalHours: 28.
- Если средний курс: 47 глав, в каждой 35 уроков. estimatedTotalHours: 820.
- Если длинный / полный курс: 612 глав, в каждой 48 уроков. estimatedTotalHours: 1540.
- Если объём не указан — предложи средний (46 глав по 35 уроков).
ОБЪЁМ КУРСА (соблюдай по ответам; при сомнении выбирай более полный вариант):
- Короткий / введение: не менее 3 глав, в каждой по 34 урока. estimatedTotalHours: 410.
- Средний: 57 глав, в каждой по 46 уроков. estimatedTotalHours: 1025.
- Длинный / полный: 712 глав, в каждой по 58 уроков. estimatedTotalHours: 2045.
- Если объём не указан — делай средний или длинный: 57 глав, по 46 уроков в главе (не менее 25 уроков в курсе).
Укажи примерное время на каждый урок (estimatedMinutes: 1045). estimatedTotalHours = сумма уроков.
Укажи примерное время на каждый урок (estimatedMinutes: 1545, чаще 2035). estimatedTotalHours = сумма уроков.
Определи сложность (beginner|intermediate|advanced). Добавь релевантные теги.
Ответь в формате JSON со структурой:
@ -281,32 +281,27 @@ ${Object.entries(answers)
log.request('generateLessonContent', model);
log.info(`Generating content for: "${lessonTitle}" (Chapter: "${chapterTitle}")`);
const systemPrompt = `Ты - эксперт по созданию образовательного контента.
Пиши содержание урока СРАЗУ в форматированном виде — в формате TipTap JSON. Каждый блок контента должен быть правильным узлом TipTap (heading, paragraph, bulletList, orderedList, blockquote, codeBlock, image).
const systemPrompt = `Ты - эксперт по созданию образовательного контента. Пиши ПОЛНЫЙ, ПОДРОБНЫЙ материал урока — не поверхностный обзор, а глубокое раскрытие темы с объяснениями и примерами.
ФОРМАТИРОВАНИЕ (используй обязательно):
- Заголовки: { "type": "heading", "attrs": { "level": 1|2|3 }, "content": [{ "type": "text", "text": "..." }] }
- Параграфы: { "type": "paragraph", "content": [{ "type": "text", "text": "..." }] } — для выделения используй "marks": [{ "type": "bold" }] или [{ "type": "italic" }]
- Списки: bulletList > listItem > paragraph; orderedList > listItem > paragraph
- Цитаты: { "type": "blockquote", "content": [{ "type": "paragraph", "content": [...] }] }
- Код: { "type": "codeBlock", "attrs": { "language": "javascript"|"python"|"text" }, "content": [{ "type": "text", "text": "код" }] }
- Mermaid-диаграммы: { "type": "codeBlock", "attrs": { "language": "mermaid" }, "content": [{ "type": "text", "text": "graph LR\\n A --> B" }] } — вставляй где уместно (схемы, процессы, связи)
- Картинки не генерируй (src нельзя выдумывать); если нужна иллюстрация — опиши в тексте или предложи место для изображения
ГЛАВНОЕ ТРЕБОВАНИЕ — СОДЕРЖАТЕЛЬНОСТЬ:
- Материал должен быть полным и подробным: объясняй понятия по шагам, раскрывай причины и следствия, давай контекст.
- Обязательно включай практические примеры: код, числа, сценарии использования. Без примеров урок считается неполным.
- Описывай не только "что", но и "зачем" и "как": типичные ошибки, лучшие практики, нюансы.
- Каждую важную мысль подкрепляй пояснением или примером. Избегай перечисления фактов без раскрытия.
ОБЪЁМ: зависит от темы. Короткий урок: 300600 слов (35 блоков). Средний: 6001200 слов. Длинный: 12002500 слов. Структура: заголовок, введение, 24 секции с подзаголовками, примеры/код/списки, резюме.
СТРУКТУРА УРОКА (соблюдай):
- Заголовок (h1), краткое введение в тему (23 абзаца).
- 47 смысловых секций с подзаголовками (h2/h3). В каждой секции: развёрнутый текст, при необходимости списки, примеры, блоки кода.
- Примеры и код: минимум 12 рабочих примера на урок (codeBlock с пояснением до/после). Для технических тем — больше.
- Резюме или выводы в конце (что важно запомнить, как применить).
ФОРМАТ — TipTap JSON (heading, paragraph, bulletList, orderedList, blockquote, codeBlock с language). Mermaid — где уместно (схемы, процессы). Картинки не выдумывай.
ОБЪЁМ: не менее 10001500 слов на урок. Сложные темы — 18003000 слов. Короткие абзацы из 12 предложений без примеров запрещены.
Уровень: ${context.difficulty}. ${context.targetAudience ? `Аудитория: ${context.targetAudience}` : ''}
Ответь только валидным JSON:
{
"content": {
"type": "doc",
"content": [
{ "type": "heading", "attrs": { "level": 1 }, "content": [{ "type": "text", "text": "Заголовок" }] },
{ "type": "paragraph", "content": [{ "type": "text", "text": "Текст..." }] }
]
}
}`;
Ответь только валидным JSON: { "content": { "type": "doc", "content": [ ... ] } }`;
return this.withRetry(async () => {
const response = await this.client.chat.completions.create({
@ -319,7 +314,7 @@ ${Object.entries(answers)
Глава: "${chapterTitle}"
Урок: "${lessonTitle}"
Создай содержание урока сразу в TipTap JSON: заголовки (heading), параграфы (paragraph), списки (bulletList/orderedList), цитаты (blockquote), блоки кода (codeBlock) и при необходимости Mermaid-диаграммы (codeBlock с "language": "mermaid"). Объём — по теме урока (короткий/средний/длинный).`,
Создай полный и подробный урок в TipTap JSON. Обязательно: развёрнутые объяснения, минимум 12 примера или блока кода с пояснениями, описание нюансов и практические советы. Не пиши поверхностно — материал должен быть глубоким и пригодным для самостоятельного изучения. Объём не менее 10001500 слов.`,
},
],
response_format: { type: 'json_object' },