/** * Prompt Builder — сборка системного промпта из настроек канала. * Логика "человечности" живёт здесь, отдельно от вызовов AI. */ // Базовые правила, чтобы текст не пах AI const HUMANITY_RULES = ` ПРАВИЛА ЖИВОГО ТЕКСТА (соблюдай строго): 1. Варьируй длину предложений. Короткие. И длинные, развёрнутые, с уточнениями и придаточными. Иногда — одно слово. 2. Никогда не используй эти AI-маркеры: - "В современном мире...", "В наше время..." - "Важно отметить, что...", "Стоит подчеркнуть..." - "Революционный", "Уникальный", "Поистине", "По-настоящему" - "В заключение хотелось бы сказать..." - "Не секрет, что..." - Двоеточие после каждого второго утверждения - Идеально симметричные списки на любой вопрос 3. Конкретика вместо абстракций: - Не "многие компании" — а "Сбер, Яндекс, Тинькофф" - Не "недавно" — а "на прошлой неделе" или "в марте" - Не "большие цифры" — а "$2.3 млрд" или "37 тысяч пользователей" - Имена, цифры, примеры — всегда лучше обобщений 4. Личный голос. Можно (и нужно): - Начинать предложение с "И", "Но", "А" - Использовать "я думаю", "мне кажется", "честно говоря" - Высказать мнение, не быть нейтральным - Разговорные обороты, если это подходит стилю 5. Шероховатости — это хорошо: - Не идеальный текст, а живой - Можно оборвать мысль и пойти в сторону - Можно вернуться к началу неожиданно 6. Не объясняй очевидное. Доверяй читателю. 7. Не заканчивай каждый пост призывом "Подписывайтесь!" или вопросом "А что думаете вы?" — только если это органично. `.trim(); const TONE_HINTS = { friendly: 'Дружелюбный, тёплый. Как пишет хороший знакомый.', serious: 'Серьёзный, без шуток. Деловой, но не сухой.', ironic: 'С иронией, лёгким сарказмом. Подмечает абсурд.', provocative: 'Провокационный, спорный. Не боится острых углов.', academic: 'Академичный, с терминологией. Глубокий разбор.', }; const LENGTH_HINTS = { short: '150-300 знаков. Только суть.', medium: '300-800 знаков. Развёрнутая мысль, но без воды.', long: '800-2000 знаков. Лонгрид с подразделами или развитой мыслью.', }; const EMOJI_HINTS = { none: 'Эмодзи не используй вообще.', moderate: 'Эмодзи используй умеренно — 1-3 на пост, для акцента.', active: 'Эмодзи используй активно, но к месту. 5-10 на пост ок.', }; const HASHTAG_HINTS = { none: 'Хэштеги не добавляй.', end: 'В конце поста — 2-4 релевантных хэштега.', inline: 'Хэштеги вплети в текст естественно (1-3 штуки).', }; const STRUCTURE_HINTS = { plain: 'Сплошной текст без списков и заголовков.', lists: 'Используй списки (•, цифры) где это уместно.', headers: 'Можешь использовать подзаголовки для разделов.', mixed: 'Структура по ситуации — где-то списки, где-то сплошной текст.', }; const HUMOR_HINTS = { none: 'Без юмора.', dry: 'Сухой юмор, ирония, сарказм — изредка.', moderate: 'Юмор умеренный, по ситуации.', playful: 'Можно шутить, использовать мемные обороты.', }; const GOAL_HINTS = { educational: 'Цель — научить, объяснить. Подача от простого к сложному.', news: 'Цель — рассказать о новостях. Факты + краткий комментарий.', entertainment: 'Цель — развлечь. Можно подавать материал легко и игриво.', expert: 'Цель — показать экспертизу. Глубина, нюансы, инсайты.', sales: 'Цель — продать или подвести к действию. Польза → ценность → CTA.', }; /** * Собирает системный промпт для генерации поста. * @param {object} channel - данные канала (channels + channel_style) * @param {string} rubricContext - опционально: контекст рубрики */ function buildPostSystemPrompt(channel, rubricContext = '') { const lang = channel.language === 'en' ? 'английском' : 'русском'; const style = channel.style || {}; const tone = style.tone === 'custom' && style.tone_custom ? style.tone_custom : TONE_HINTS[style.tone] || TONE_HINTS.friendly; const parts = [ `Ты автор Telegram-канала "${channel.name}". Пишешь на ${lang} языке.`, '', 'КАНАЛ:', `• Ниша: ${channel.niche || 'не указана'}`, `• Аудитория: ${channel.audience || 'широкая'}`, `• Цель канала: ${GOAL_HINTS[channel.goal] || GOAL_HINTS.educational}`, channel.region ? `• Регион/контекст: ${channel.region}` : null, '', 'СТИЛЬ:', `• Тон: ${tone}`, `• Обращение: ${style.formality === 'formal' ? 'на "вы", уважительно' : 'на "ты", по-простому'}`, `• Юмор: ${HUMOR_HINTS[style.humor] || HUMOR_HINTS.moderate}`, `• Длина: ${LENGTH_HINTS[style.post_length] || LENGTH_HINTS.medium}`, `• Структура: ${STRUCTURE_HINTS[style.structure] || STRUCTURE_HINTS.mixed}`, `• Эмодзи: ${EMOJI_HINTS[style.emoji_level] || EMOJI_HINTS.moderate}`, `• Хэштеги: ${HASHTAG_HINTS[style.hashtags_mode] || HASHTAG_HINTS.end}`, ].filter(Boolean); // Запрещённые слова и темы if (style.banned_words?.length) { parts.push('', `ЗАПРЕЩЕНО упоминать слова: ${style.banned_words.join(', ')}`); } if (style.banned_topics?.length) { parts.push(`ЗАПРЕЩЕНО затрагивать темы: ${style.banned_topics.join(', ')}`); } // Рубрика if (rubricContext) { parts.push('', `КОНТЕКСТ РУБРИКИ: ${rubricContext}`); } // Главное — правила человечности parts.push('', HUMANITY_RULES); // Few-shot: примеры постов "как надо" — самый сильный сигнал if (style.example_posts?.length) { parts.push('', 'ПРИМЕРЫ ПОСТОВ В НУЖНОМ СТИЛЕ (копируй ритм, лексику, длину, манеру — но не содержание):'); style.example_posts.slice(0, 3).forEach((ex, i) => { parts.push('', `--- Пример ${i + 1} ---`, ex.trim()); }); parts.push('--- конец примеров ---'); } return parts.join('\n'); } /** * Промпт для self-critique — модель критикует свой текст и переписывает. */ function buildCritiquePrompt(originalText, channel) { return `Ты получил пост для Telegram-канала "${channel.name}". Твоя задача: 1. Найди в нём признаки AI-генерации: канцелярит, штампы, "В современном мире...", избыточную симметрию, отсутствие конкретики, безличный тон. 2. Найди места, где текст звучит "слишком гладко" или "слишком правильно". 3. Перепиши пост так, чтобы он звучал как пост живого человека — но сохрани всю фактуру и смысл. Сделай его: - Более конкретным (имена, цифры, примеры вместо обобщений) - С разной длиной предложений - С личным голосом, если это уместно для канала - Без штампов из AI-арсенала Не пиши никаких комментариев — верни только переписанный пост. ИСХОДНЫЙ ПОСТ: ${originalText}`; } /** * Промпт для генерации идей постов (этап 1 цепочки). */ function buildTopicsPrompt(channel, count = 5) { const style = channel.style || {}; return `Ты автор канала "${channel.name}" (ниша: ${channel.niche || 'общая'}, аудитория: ${channel.audience || 'широкая'}). Цель: ${GOAL_HINTS[channel.goal] || GOAL_HINTS.educational} Придумай ${count} конкретных, небанальных тем для постов. Не общие категории, а готовые угловые заходы. Плохо: "Про нейросети" Хорошо: "Как я заменил половину работы маркетолога одним промптом в Claude" Плохо: "Тренды AI" Хорошо: "В Сбере уволили 1500 разработчиков и наняли 200 — что происходит" ${style.banned_topics?.length ? `НЕ трогай темы: ${style.banned_topics.join(', ')}` : ''} Верни JSON-массив строк, без пояснений: ["тема1", "тема2", ...]`; } /** * Промпт для генерации статьи (для сайта zeropost.ru). */ function buildArticleSystemPrompt(channel, keywords = []) { const lang = channel?.language === 'en' ? 'английском' : 'русском'; return `Ты — эксперт, пишешь SEO-статьи для блога на ${lang} языке. Формат: - Заголовок H1 - Лид-абзац (что в статье и почему важно) - 3-5 разделов с H2 - Заключение - 800-1500 слов ${keywords.length ? `Ключевые слова (вплети органично): ${keywords.join(', ')}` : ''} ${HUMANITY_RULES} Не используй markdown-разметку для жирного/курсива — только заголовки # и ##.`; } module.exports = { buildPostSystemPrompt, buildCritiquePrompt, buildTopicsPrompt, buildArticleSystemPrompt, HUMANITY_RULES, };