From 69d1dcd805d113bc6bbcbd964f9345db512eb341 Mon Sep 17 00:00:00 2001 From: arys Date: Tue, 14 Apr 2026 09:10:18 +0500 Subject: [PATCH] fix --- .qwen/settings.json | 7 + docs/AI_STRATEGY_DOCUMENTATION.md | 279 ++++++ .../marketing/AIStrategyGenerator.vue | 849 ++++++++++++++++++ src/service/AIStrategyService.ts | 178 ++++ src/types/ai-strategy.types.ts | 229 +++++ src/utils/strategyPromptGenerator.ts | 292 ++++++ src/utils/validateAIStrategy.ts | 587 ++++++++++++ .../marketing/AIStrategyGeneratorPage.vue | 16 + ...ля AI-генерации маркетинговой стратегии.md | 326 +++++++ 9 files changed, 2763 insertions(+) create mode 100644 .qwen/settings.json create mode 100644 docs/AI_STRATEGY_DOCUMENTATION.md create mode 100644 src/components/marketing/AIStrategyGenerator.vue create mode 100644 src/service/AIStrategyService.ts create mode 100644 src/types/ai-strategy.types.ts create mode 100644 src/utils/strategyPromptGenerator.ts create mode 100644 src/utils/validateAIStrategy.ts create mode 100644 src/views/pages/marketing/AIStrategyGeneratorPage.vue create mode 100644 Промпт для AI-генерации маркетинговой стратегии.md diff --git a/.qwen/settings.json b/.qwen/settings.json new file mode 100644 index 0000000..1204668 --- /dev/null +++ b/.qwen/settings.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(npm run *)" + ] + } +} \ No newline at end of file diff --git a/docs/AI_STRATEGY_DOCUMENTATION.md b/docs/AI_STRATEGY_DOCUMENTATION.md new file mode 100644 index 0000000..fef2974 --- /dev/null +++ b/docs/AI_STRATEGY_DOCUMENTATION.md @@ -0,0 +1,279 @@ +# AI-генерация маркетинговых стратегий — Документация + +## Обзор + +Система AI-генерации создаёт профессиональные маркетинговые стратегии на 4 недели с 16 постами, используя строгую валидацию и профессиональные промпты. + +## Архитектура + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ФРОНТЕНД (Vue 3) │ +├─────────────────────────────────────────────────────────────┤ +│ AIStrategyGenerator.vue (UI компонент) │ +│ ↓ │ +│ strategyPromptGenerator.ts (генерация промпта) │ +│ ↓ │ +│ AIStrategyService.ts (сервис API + валидация) │ +│ ↓ │ +│ validateAIStrategy.ts (полная валидация) │ +│ ↓ │ +│ ai-strategy.types.ts (TypeScript интерфейсы) │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ BACKEND API │ +│ POST /api/ai/strategy/generate │ +│ Тело: { prompt, model, temperature, maxTokens } │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Файлы + +| Файл | Назначение | +|------|-----------| +| `src/types/ai-strategy.types.ts` | TypeScript интерфейсы и константы | +| `src/utils/validateAIStrategy.ts` | Утилита полной валидации AI-ответа | +| `src/utils/strategyPromptGenerator.ts` | Генератор промпта и парсер JSON | +| `src/service/AIStrategyService.ts` | Сервис работы с AI-стратегиями | +| `src/components/marketing/AIStrategyGenerator.vue` | UI компонент генератора | +| `src/views/pages/marketing/AIStrategyGeneratorPage.vue` | Страница генератора | + +## TypeScript интерфейсы + +### PostCalendarItem + +```typescript +interface PostCalendarItem { + postIndex: number; // 0-15 + publishDate: string; // "2026-04-13T09:00:00" + weekNumber: 1 | 2 | 3 | 4; + platform: 'Instagram' | 'TikTok'; + contentType: 'фото' | 'видео'; + theme: string; // Конкретная тема + postText: string; // 150+ слов + hashtags: string[]; // 5-8 хэштегов + publishTime: '09:00' | '12:00' | '18:00' | '20:00'; + imageUrl?: string; + videoUrl?: string; + taskStatus?: string; +} +``` + +### WeeklyPlan + +```typescript +interface WeeklyPlan { + weekNumber: 1 | 2 | 3 | 4; + mainThemes: string[]; // Ровно 4 темы + contentRecommendations: string; // 50+ слов + priorityPlatforms: string[]; +} +``` + +### AIStrategyResponse + +```typescript +interface AIStrategyResponse { + selectedModel: string; + scoringRationale: string; + recommendedDurationWeeks: 4; + recommendedPlatforms: string[]; + rationale: string; // 60+ слов + weeklyPlans: WeeklyPlan[]; // 4 недели + postCalendar: PostCalendarItem[]; // 16 постов +} +``` + +## Правила валидации + +### Критические ошибки (ERROR) + +| Правило | Проверка | +|---------|---------| +| **Количество постов** | Ровно 16 (4 × 4 недели) | +| **Недели** | Каждая из 4 недель представлена | +| **Даты** | ISO формат, правильные дни (Пн, Ср, Пт, Сб) | +| **Время** | Только 09:00, 12:00, 18:00, 20:00 | +| **Видео** | Ровно 4 (первый пост каждой недели) | +| **Фото** | Ровно 12 (остальные посты) | +| **Хэштеги** | 5-8 на пост, каждый начинается с # | +| **Текст** | 150+ слов на пост | +| **Темы** | Конкретные, не абстрактные | + +### Предупреждения (WARNING) + +| Правило | Проверка | +|---------|---------| +| **Абстрактные темы** | Темы без конкретики | +| **Стиль** | Рекомендации по улучшению | + +## Использование + +### 1. Через UI + +Перейдите на `/marketing-analysis/ai-strategy` + +Заполните: +- Ниша бизнеса +- Название бренда +- Город продвижения +- Модель стратегии (ENTRY/AUTHORITY/TRUST/CONVERSION) +- Дата начала (опционально) + +### 2. Программно + +```typescript +import AIStrategyService from '@/service/AIStrategyService'; + +const result = await AIStrategyService.generateAIStrategy({ + niche: 'Кондитерская', + brand: 'Sweet Dreams', + city: 'Алматы', + model: 'CONVERSION' +}); + +console.log(result.strategy); // AIStrategyResponse +console.log(result.validation); // ValidationResult +``` + +### 3. Валидация готовй стратегии + +```typescript +import { validateAIStrategy } from '@/utils/validateAIStrategy'; + +const validation = validateAIStrategy(myStrategy); +if (!validation.isValid) { + console.error(validation.errors); +} +``` + +### 4. Экспорт + +```typescript +// JSON +AIStrategyService.exportStrategyAsJSON(strategy, 'my-strategy'); + +// CSV +AIStrategyService.exportCalendarAsCSV(strategy, 'my-calendar'); +``` + +## Константы + +```typescript +STRATEGY_CONSTANTS = { + TOTAL_POSTS: 16, + POSTS_PER_WEEK: 4, + TOTAL_WEEKS: 4, + TOTAL_VIDEOS: 4, + TOTAL_PHOTOS: 12, + MIN_POST_WORDS: 150, + MIN_HASHTAGS: 5, + MAX_HASHTAGS: 8, + MIN_WEEKLY_RECOMMENDATION_WORDS: 50, + MIN_RATIONALE_WORDS: 60, + VALID_PUBLISH_TIMES: ['09:00', '12:00', '18:00', '20:00'] +} +``` + +## Модели стратегий + +| Модель | Описание | Когда использовать | +|--------|---------|-------------------| +| **ENTRY** | Вход / Формирование присутствия | Новые бизнес, узнаваемость | +| **AUTHORITY** | Экспертность / Компетентность | B2B, длинный цикл сделки | +| **TRUST** | Усиление доверия | Высокая конкуренция | +| **CONVERSION** | Прямая конверсия | Быстрые покупки, B2C | + +## API Endpoints + +### Генерация стратегии + +``` +POST /api/ai/strategy/generate +Content-Type: application/json + +{ + "prompt": "...", + "model": "gpt-4", + "temperature": 0.7, + "maxTokens": 8000 +} + +Response: +{ + "data": { + "content": "{...JSON стратегии...}" + } +} +``` + +## Распределение постов + +### Неделя 1-4 (одинаковая структура) + +| Пост | День | Время | Тип | +|------|------|-------|-----| +| 1 | Понедельник | 09:00 | 🎥 Видео | +| 2 | Среда | 12:00 | 📸 Фото | +| 3 | Пятница | 18:00 | 📸 Фото | +| 4 | Суббота | 20:00 | 📸 Фото | + +## Пример правильного ответа AI + +```json +{ + "selectedModel": "CONVERSION", + "scoringRationale": "Модель Conversion оптимальна...", + "recommendedDurationWeeks": 4, + "recommendedPlatforms": ["Instagram", "TikTok"], + "rationale": "Instagram и TikTok являются...", + "weeklyPlans": [ + { + "weekNumber": 1, + "mainThemes": ["Кейс клиента", "Процесс работы", "Отзыв", "Экспертный"], + "contentRecommendations": "На этой неделе фокусируемся...", + "priorityPlatforms": ["Instagram"] + } + ], + "postCalendar": [ + { + "postIndex": 0, + "publishDate": "2026-04-13T09:00:00", + "weekNumber": 1, + "platform": "Instagram", + "contentType": "видео", + "theme": "Кейс: Как клиент увеличил продажи на 40%", + "postText": "Хотите узнать, как наш клиент из Алматы...", + "hashtags": ["#кейс", "#алматы", "#бизнес"], + "publishTime": "09:00" + } + ] +} +``` + +## Troubleshooting + +### Ошибка: "Невалидный JSON" + +AI вернул текст с markdown блоками. Функция `extractJSONFromResponse` автоматически очищает ответ. + +### Ошибка валидации: "postCalendar должен содержать ровно 16 постов" + +AI вернул меньше или больше постов. Проверьте промпт и температуру (рекомендуется 0.7). + +### Предупреждение: "Тема выглядит абстрактной" + +AI создал тему без конкретики. Стратегия валидна, но темы можно улучшить. + +## Roadmap + +- [ ] Сохранение стратегии на бэкенде +- [ ] Интеграция с автопостингом +- [ ] Редактор постов в UI +- [ ] A/B тестирование тем +- [ ] Мульти-язычность + +## Лицензия + +Внутренний проект Konturai. diff --git a/src/components/marketing/AIStrategyGenerator.vue b/src/components/marketing/AIStrategyGenerator.vue new file mode 100644 index 0000000..d449d25 --- /dev/null +++ b/src/components/marketing/AIStrategyGenerator.vue @@ -0,0 +1,849 @@ + + + + + diff --git a/src/service/AIStrategyService.ts b/src/service/AIStrategyService.ts new file mode 100644 index 0000000..9b9778e --- /dev/null +++ b/src/service/AIStrategyService.ts @@ -0,0 +1,178 @@ +/** + * Сервис для работы с AI-генерацией маркетинговых стратегий + * Объединяет генерацию промпта, валидацию и работу с API + */ + +import type { + AIStrategyResponse, + StrategyGenerationRequest, + ValidationResult +} from '@/types/ai-strategy.types'; +import { generateStrategyPrompt, extractJSONFromResponse, safeParseJSON } from '@/utils/strategyPromptGenerator'; +import { validateAIStrategy, getValidationSummary } from '@/utils/validateAIStrategy'; +import { API_CONFIG, DEFAULT_REQUEST_CONFIG } from '@/config/api'; +import AuthService from '@/service/AuthService'; + +const API_BASE_URL = API_CONFIG.BASE_URL; + +class AIStrategyService { + /** + * Генерация AI-стратегии через внешний AI API + * POST /api/ai/strategy/generate + */ + async generateAIStrategy(request: StrategyGenerationRequest): Promise<{ + strategy: AIStrategyResponse; + validation: ValidationResult; + rawResponse: string; + }> { + // 1. Генерация промпта + const prompt = generateStrategyPrompt(request); + console.log('📝 Сгенерирован промпт для AI'); + + try { + // 2. Вызов AI API + const response = await AuthService.authFetch(`${API_BASE_URL}/api/ai/strategy/generate`, { + method: 'POST', + ...DEFAULT_REQUEST_CONFIG, + body: JSON.stringify({ + prompt, + model: 'gpt-4', // Или другая модель + temperature: 0.7, + maxTokens: 8000 + }) + }); + + const result = await response.json().catch(() => null); + + if (!response.ok) { + throw new Error(result?.message || 'Ошибка при генерации AI-стратегии'); + } + + const rawResponse = result?.data?.content || result?.content || result?.data; + + // 3. Извлечение JSON из ответа + const jsonString = extractJSONFromResponse(rawResponse); + console.log('🔍 Извлечён JSON из ответа AI'); + + // 4. Парсинг JSON + const parsedStrategy = safeParseJSON(jsonString) as AIStrategyResponse; + + // 5. Валидация стратегии + const validation = validateAIStrategy(parsedStrategy); + console.log(getValidationSummary(validation)); + + if (!validation.isValid) { + console.warn('⚠️ Стратегия не прошла валидацию:', validation.errors); + } + + return { + strategy: parsedStrategy, + validation, + rawResponse + }; + } catch (error) { + console.error('❌ Ошибка при генерации AI-стратегии:', error); + throw error; + } + } + + /** + * Валидация готовой стратегии (без вызова API) + */ + validateStrategy(strategy: unknown): ValidationResult { + return validateAIStrategy(strategy); + } + + /** + * Получить посты для конкретной недели + */ + getPostsForWeek(strategy: AIStrategyResponse, weekNumber: 1 | 2 | 3 | 4) { + return strategy.postCalendar.filter(post => post.weekNumber === weekNumber); + } + + /** + * Получить все видео-посты + */ + getVideoPosts(strategy: AIStrategyResponse) { + return strategy.postCalendar.filter(post => post.contentType === 'видео'); + } + + /** + * Получить все фото-посты + */ + getPhotoPosts(strategy: AIStrategyResponse) { + return strategy.postCalendar.filter(post => post.contentType === 'фото'); + } + + /** + * Получить посты для конкретной платформы + */ + getPostsForPlatform(strategy: AIStrategyResponse, platform: 'Instagram' | 'TikTok') { + return strategy.postCalendar.filter(post => post.platform === platform); + } + + /** + * Сгенерировать превью стратегии для UI (краткая информация) + */ + generateStrategyPreview(strategy: AIStrategyResponse) { + return { + model: strategy.selectedModel, + totalPosts: strategy.postCalendar.length, + totalWeeks: strategy.weeklyPlans.length, + videoCount: this.getVideoPosts(strategy).length, + photoCount: this.getPhotoPosts(strategy).length, + platforms: strategy.recommendedPlatforms, + weeklyThemes: strategy.weeklyPlans.map(wp => ({ + week: wp.weekNumber, + themes: wp.mainThemes + })) + }; + } + + /** + * Экспорт стратегии в JSON файл для скачивания + */ + exportStrategyAsJSON(strategy: AIStrategyResponse, filename?: string): void { + const jsonString = JSON.stringify(strategy, null, 2); + const blob = new Blob([jsonString], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = filename || `strategy-${strategy.selectedModel}-${new Date().toISOString().split('T')[0]}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + + /** + * Экспорт календаря постов в CSV + */ + exportCalendarAsCSV(strategy: AIStrategyResponse, filename?: string): void { + const headers = ['Дата', 'Время', 'Неделя', 'Платформа', 'Тип', 'Тема', 'Хэштеги']; + const rows = strategy.postCalendar.map(post => [ + post.publishDate, + post.publishTime, + `Неделя ${post.weekNumber}`, + post.platform, + post.contentType, + `"${post.theme.replace(/"/g, '""')}"`, + post.hashtags.join(' ') + ]); + + const csvContent = [headers.join(','), ...rows.map(r => r.join(','))].join('\n'); + const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = filename || `calendar-${strategy.selectedModel}-${new Date().toISOString().split('T')[0]}.csv`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } +} + +export default new AIStrategyService(); diff --git a/src/types/ai-strategy.types.ts b/src/types/ai-strategy.types.ts new file mode 100644 index 0000000..85662c7 --- /dev/null +++ b/src/types/ai-strategy.types.ts @@ -0,0 +1,229 @@ +/** + * Типы и интерфейсы для AI-генерации маркетинговой стратегии + * Соответствуют строгому формату промпта: 16 постов, 4 недели, Пн-Ср-Пт-Сб + */ + +// ============================================================ +// БАЗОВЫЕ ТИПЫ +// ============================================================ + +export type Platform = 'Instagram' | 'TikTok'; +export type ContentType = 'фото' | 'видео'; +export type PublishTime = '09:00' | '12:00' | '18:00' | '20:00'; +export type WeekNumber = 1 | 2 | 3 | 4; + +// ============================================================ +// POST CALENDAR ITEM +// ============================================================ + +export interface PostCalendarItem { + /** Индекс поста в календаре (0-15) */ + postIndex: number; + + /** Дата и время публикации в ISO формате */ + publishDate: string; // "2026-04-13T09:00:00" + + /** Номер недели (1, 2, 3 или 4) */ + weekNumber: WeekNumber; + + /** Платформа публикации */ + platform: Platform; + + /** Тип контента */ + contentType: ContentType; + + /** Конкретная тема поста (не абстракция!) */ + theme: string; + + /** Текст поста (минимум 150 слов) */ + postText: string; + + /** Хэштеги (5-8 штук, каждый начинается с #) */ + hashtags: string[]; + + /** Время публикации */ + publishTime: PublishTime; + + /** Опционально: URL изображения */ + imageUrl?: string; + + /** Опционально: URL видео */ + videoUrl?: string; + + /** Опционально: статус задачи */ + taskStatus?: 'PENDING' | 'SCHEDULED' | 'PUBLISHED' | 'FAILED'; +} + +// ============================================================ +// WEEKLY PLAN +// ============================================================ + +export interface WeeklyPlan { + /** Номер недели (1, 2, 3 или 4) */ + weekNumber: WeekNumber; + + /** Основные темы недели (ровно 4 темы) */ + mainThemes: string[]; + + /** Рекомендации по контенту (минимум 50 слов) */ + contentRecommendations: string; + + /** Приоритетные платформы */ + priorityPlatforms: Platform[]; +} + +// ============================================================ +// STRATEGY RESPONSE (полный ответ от AI) +// ============================================================ + +export interface AIStrategyResponse { + /** Выбранная модель стратегии */ + selectedModel: string; + + /** Обоснование выбора модели */ + scoringRationale: string; + + /** Рекомендуемая длительность в неделях (всегда 4) */ + recommendedDurationWeeks: 4; + + /** Рекомендуемые платформы */ + recommendedPlatforms: Platform[]; + + /** Обоснование выбора платформ (минимум 60 слов) */ + rationale: string; + + /** Еженедельные планы (ровно 4) */ + weeklyPlans: WeeklyPlan[]; + + /** Календарь постов (ровно 16) */ + postCalendar: PostCalendarItem[]; +} + +// ============================================================ +// VALIDATION TYPES +// ============================================================ + +export interface ValidationResult { + /** Результат валидации */ + isValid: boolean; + + /** Список ошибок */ + errors: ValidationError[]; + + /** Список предупреждений */ + warnings: ValidationWarning[]; +} + +export interface ValidationError { + /** Тип ошибки */ + type: 'STRUCTURE' | 'COUNT' | 'FORMAT' | 'CONTENT' | 'DATE'; + + /** Путь к проблемному полю */ + field: string; + + /** Сообщение об ошибке */ + message: string; +} + +export interface ValidationWarning { + /** Тип предупреждения */ + type: 'CONTENT' | 'STYLE' | 'OPTIMIZATION'; + + /** Путь к проблемному полю */ + field: string; + + /** Сообщение о предупреждении */ + message: string; +} + +// ============================================================ +// STRATEGY GENERATION REQUEST +// ============================================================ + +export interface StrategyGenerationRequest { + /** Ниша бизнеса */ + niche: string; + + /** Название бренда */ + brand: string; + + /** Город продвижения */ + city: string; + + /** Модель стратегии */ + model: 'ENTRY' | 'AUTHORITY' | 'TRUST' | 'CONVERSION'; + + /** Начальная дата стратегии (по умолчанию - следующий понедельник) */ + startDate?: string; +} + +// ============================================================ +// CONSTANTS +// ============================================================ + +export const STRATEGY_CONSTANTS = { + /** Общее количество постов */ + TOTAL_POSTS: 16, + + /** Количество постов в неделю */ + POSTS_PER_WEEK: 4, + + /** Количество недель */ + TOTAL_WEEKS: 4, + + /** Количество видео за всю стратегию */ + TOTAL_VIDEOS: 4, + + /** Количество фото за всю стратегию */ + TOTAL_PHOTOS: 12, + + /** Минимальное количество слов в посте */ + MIN_POST_WORDS: 150, + + /** Минимальное количество хэштегов */ + MIN_HASHTAGS: 5, + + /** Максимальное количество хэштегов */ + MAX_HASHTAGS: 8, + + /** Минимальное количество слов в рекомендациях недели */ + MIN_WEEKLY_RECOMMENDATION_WORDS: 50, + + /** Минимальное количество слов в rationale */ + MIN_RATIONALE_WORDS: 60, + + /** Допустимые времена публикации */ + VALID_PUBLISH_TIMES: ['09:00', '12:00', '18:00', '20:00'] as const, + + /** Паттерн дат: Пн, Ср, Пт, Сб */ + WEEKLY_SCHEDULE: [ + { dayOffset: 0, time: '09:00' as const }, // Понедельник + { dayOffset: 2, time: '12:00' as const }, // Среда + { dayOffset: 4, time: '18:00' as const }, // Пятница + { dayOffset: 5, time: '20:00' as const } // Суббота + ] +} as const; + +// ============================================================ +// HELPER FUNCTIONS (TYPE GUARDS) +// ============================================================ + +/** Проверка: является ли значение допустимым Platform */ +export function isPlatform(value: unknown): value is Platform { + return value === 'Instagram' || value === 'TikTok'; +} + +/** Проверка: является ли значение допустимым ContentType */ +export function isContentType(value: unknown): value is ContentType { + return value === 'фото' || value === 'видео'; +} + +/** Проверка: является ли значение допустимым PublishTime */ +export function isPublishTime(value: unknown): value is PublishTime { + return STRATEGY_CONSTANTS.VALID_PUBLISH_TIMES.includes(value as PublishTime); +} + +/** Проверка: является ли значение допустимым WeekNumber */ +export function isWeekNumber(value: unknown): value is WeekNumber { + return [1, 2, 3, 4].includes(value as WeekNumber); +} diff --git a/src/utils/strategyPromptGenerator.ts b/src/utils/strategyPromptGenerator.ts new file mode 100644 index 0000000..302bd7a --- /dev/null +++ b/src/utils/strategyPromptGenerator.ts @@ -0,0 +1,292 @@ +/** + * Генератор промпта для AI-стратегии + * Создаёт профессиональный промпт на основе данных бизнеса + */ + +import type { StrategyGenerationRequest } from '@/types/ai-strategy.types'; +import { STRATEGY_MODELS } from '@/config/marketingModels'; + +/** + * Получить описание типов контента для модели стратегии + */ +function getContentTypesForModel(model: string): string { + const modelConfigs: Record = { + ENTRY: `ENTRY-модель: фокус на узнаваемость бренда. + - Видео: короткое приветственное видео о компании, знакомство с командой + - Фото: яркие изображения продуктов/услуг, атмосферные снимки + - Акцент на эмоциях и первом впечатлении`, + + AUTHORITY: `AUTHORITY-модель: позиционирование как эксперты. + - Видео: экспертные разборы, объяснения сложных тем простым языком + - Фото: инфографика, схемы, чек-листы, кейсы с цифрами + - Акцент на доказательствах компетентности`, + + TRUST: `TRUST-модель: снятие недоверия через социальное доказательство. + - Видео: отзывы клиентов, процесс работы, гарантии + - Фото: реальные результаты, сертификаты, довольные клиенты + - Акцент на отзывах, кейсах, конкретных результатах`, + + CONVERSION: `CONVERSION-модель: максимальная конверсия в заявки. + - Видео: демонстрация продукта в действии, до/после + - Фото: привлекательные предложения, акции, конкретные выгоды + - Акцент на призывах к действию и срочности` + }; + + return modelConfigs[model] || ''; +} + +/** + * Получить следующий понедельник от указанной даты + */ +function getNextMonday(startDate?: string): Date { + const base = startDate ? new Date(startDate) : new Date(); + const dayOfWeek = base.getDay(); // 0=Вс, 1=Пн, ..., 6=Сб + + // Если сегодня понедельник, берём сегодня, иначе следующий понедельник + const daysUntilMonday = dayOfWeek === 0 ? 1 : dayOfWeek === 1 ? 0 : 8 - dayOfWeek; + + const nextMonday = new Date(base); + nextMonday.setDate(base.getDate() + daysUntilMonday); + nextMonday.setHours(0, 0, 0, 0); + + return nextMonday; +} + +/** + * Форматировать дату в ISO строку + */ +function formatDateISO(date: Date): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +} + +/** + * Сгенерировать полный промпт для AI + */ +export function generateStrategyPrompt(request: StrategyGenerationRequest): string { + const { niche, brand, city, model, startDate } = request; + + const mondayDate = getNextMonday(startDate); + const modelConfig = STRATEGY_MODELS[model as keyof typeof STRATEGY_MODELS] || STRATEGY_MODELS.ENTRY; + const contentTypes = getContentTypesForModel(model); + + // Генерация дат для всех 4 недель + const weekDates = []; + for (let week = 0; week < 4; week++) { + const weekStart = new Date(mondayDate); + weekStart.setDate(mondayDate.getDate() + week * 7); + weekDates.push({ + monday: new Date(weekStart), + wednesday: new Date(weekStart.getTime() + 2 * 24 * 60 * 60 * 1000), + friday: new Date(weekStart.getTime() + 4 * 24 * 60 * 60 * 1000), + saturday: new Date(weekStart.getTime() + 5 * 24 * 60 * 60 * 1000) + }); + } + + return `Ты — профессиональный SMM-стратег с 15-летним опытом работы на рынке Казахстана. + +ТВОЯ ЗАДАЧА: Создать ПРОФЕССИОНАЛЬНУЮ маркетинговую стратегию на 4 недели для бизнеса: + +📌 НИША: ${niche} +📌 БРЕНД: ${brand} +📌 ГОРОД: ${city} +📌 МОДЕЛЬ: ${modelConfig.title} (${model}) + +════════════════════════════════════════════════════════════════ +⚠️ КРИТИЧЕСКИ ВАЖНО: СТРУКТУРА И ФОРМАТ +════════════════════════════════════════════════════════════════ + +ОБЩЕЕ КОЛИЧЕСТВО: РОВНО 16 ПОСТОВ за 4 недели (4 поста × 4 недели) + +РАСПРЕДЕЛЕНИЕ ПО НЕДЕЛЯМ — СТРОГО СОБЛЮДАТЬ: + +НЕДЕЛЯ 1 (дни 1-7): + • Пост 1: ${formatDateISO(weekDates[0].monday)}T09:00:00 — понедельник + • Пост 2: ${formatDateISO(weekDates[0].wednesday)}T12:00:00 — среда + • Пост 3: ${formatDateISO(weekDates[0].friday)}T18:00:00 — пятница + • Пост 4: ${formatDateISO(weekDates[0].saturday)}T20:00:00 — суббота + +НЕДЕЛЯ 2 (дни 8-14): + • Пост 5: ${formatDateISO(weekDates[1].monday)}T09:00:00 — понедельник + • Пост 6: ${formatDateISO(weekDates[1].wednesday)}T12:00:00 — среда + • Пост 7: ${formatDateISO(weekDates[1].friday)}T18:00:00 — пятница + • Пост 8: ${formatDateISO(weekDates[1].saturday)}T20:00:00 — суббота + +НЕДЕЛЯ 3 (дни 15-21): + • Пост 9: ${formatDateISO(weekDates[2].monday)}T09:00:00 — понедельник + • Пост 10: ${formatDateISO(weekDates[2].wednesday)}T12:00:00 — среда + • Пост 11: ${formatDateISO(weekDates[2].friday)}T18:00:00 — пятница + • Пост 12: ${formatDateISO(weekDates[2].saturday)}T20:00:00 — суббота + +НЕДЕЛЯ 4 (дни 22-28): + • Пост 13: ${formatDateISO(weekDates[3].monday)}T09:00:00 — понедельник + • Пост 14: ${formatDateISO(weekDates[3].wednesday)}T12:00:00 — среда + • Пост 15: ${formatDateISO(weekDates[3].friday)}T18:00:00 — пятница + • Пост 16: ${formatDateISO(weekDates[3].saturday)}T20:00:00 — суббота + +════════════════════════════════════════════════════════════════ +ПРАВИЛА СОЗДАНИЯ КОНТЕНТА +════════════════════════════════════════════════════════════════ + +1. КАЖДЫЙ пост должен иметь: + - publishDate: точная дата и время (формат: "2026-04-13T09:00:00") + - weekNumber: номер недели (1, 2, 3 или 4) + - platform: "Instagram" или "TikTok" + - contentType: "фото" или "видео" + - theme: КОНКРЕТНАЯ тема поста (не абстракция!) + - postText: 150+ слов на русском языке + - hashtags: 5-8 хэштегов (каждый начинается с #) + - publishTime: время публикации ("09:00", "12:00", "18:00", "20:00") + +2. ВИДЕО-ПОСТЫ: РОВНО 4 видео за всю стратегию (по 1 в неделю) + - Размести видео на первый пост каждой недели (понедельник 09:00) + - Остальные 12 постов — фото + +3. ТЕМЫ ПОСТОВ — КОНКРЕТНЫЕ, НЕ АБСТРАКТНЫЕ: + ✅ ПРАВИЛЬНО: + "Кейс: Как мы увеличили продажи клиента на 40% за месяц" + "Процесс: За кадром нашей работы — как готовится самса" + "Отзыв: История довольного клиента из ${city}" + "Экспертный: 5 ошибок при выборе услуги" + + ❌ НЕПРАВИЛЬНО: + "О нашей компании" + "Введение в продукт" + "Информационный пост" + +4. ТЕКСТ ПОСТА (postText) — ПРОДАЮЩИЙ КОНТЕНТ: + - Минимум 150 слов живого текста + - Первые 2 строки — КРЮЧОК, который останавливает скролл + - Конкретные цифры, факты, примеры из казахстанской реальности + - В конце — ОДИН конкретный призыв к действию (запись, звонок, директ) + - Используй тенге, ${city} и казахстанские реалии + - Язык: ТОЛЬКО русский + +5. ХЭШТЕГИ: + - От 5 до 8 хэштегов на каждый пост + - Каждый хэштег начинается с ОДНОГО символа # + - ЗАПРЕЩЕНО: ##хэштег, # хэштег, английские хэштеги + - ПРАВИЛЬНО: #${niche.toLowerCase().replace(/\s+/g, '')}${city.toLowerCase()} #${city.toLowerCase()} + +════════════════════════════════════════════════════════════════ +ЕЖЕНЕДЕЛЬНЫЕ ПЛАНЫ (weeklyPlans) +════════════════════════════════════════════════════════════════ + +Для каждой недели создай план с РОВНО 4 темами: + +{ + "weekNumber": 1, + "mainThemes": [ + "Конкретная тема 1 для недели 1", + "Конкретная тема 2 для недели 1", + "Конкретная тема 3 для недели 1", + "Конкретная тема 4 для недели 1" + ], + "contentRecommendations": "Что конкретно снимать и писать на неделе 1 — минимум 50 слов", + "priorityPlatforms": ["Instagram"] +} + +════════════════════════════════════════════════════════════════ +ПОЛНАЯ JSON СТРУКТУРА ОТВЕТА +════════════════════════════════════════════════════════════════ + +{ + "selectedModel": "${model}", + "scoringRationale": "Обоснование выбора модели ${modelConfig.title}", + "recommendedDurationWeeks": 4, + "recommendedPlatforms": ["Instagram", "TikTok"], + "rationale": "Минимум 60 слов — почему именно эти платформы для ниши ${niche} в городе ${city}", + "weeklyPlans": [ + { + "weekNumber": 1, + "mainThemes": ["Тема 1", "Тема 2", "Тема 3", "Тема 4"], + "contentRecommendations": "Рекомендации для недели 1", + "priorityPlatforms": ["Instagram"] + }, + { "weekNumber": 2, "mainThemes": [...], "contentRecommendations": "...", "priorityPlatforms": ["Instagram"] }, + { "weekNumber": 3, "mainThemes": [...], "contentRecommendations": "...", "priorityPlatforms": ["Instagram"] }, + { "weekNumber": 4, "mainThemes": [...], "contentRecommendations": "...", "priorityPlatforms": ["Instagram"] } + ], + "postCalendar": [ + { + "publishDate": "${formatDateISO(weekDates[0].monday)}T09:00:00", + "weekNumber": 1, + "platform": "Instagram", + "contentType": "видео", + "theme": "Кейс: Конкретная история успеха клиента ${brand}", + "postText": "150+ слов продающего текста...", + "hashtags": ["#хэштег1", "#хэштег2", "#${city.toLowerCase()}"], + "publishTime": "09:00" + }, + { + "publishDate": "${formatDateISO(weekDates[0].wednesday)}T12:00:00", + "weekNumber": 1, + "platform": "Instagram", + "contentType": "фото", + "theme": "Процесс: За кадром работы ${brand}", + "postText": "150+ слов...", + "hashtags": ["#хэштег1", "#хэштег2"], + "publishTime": "12:00" + } + // ... ещё 14 постов + ] +} + +════════════════════════════════════════════════════════════════ +ТИПЫ КОНТЕНТА ПО МОДЕЛИ ${model} +════════════════════════════════════════════════════════════════ + +${contentTypes} + +════════════════════════════════════════════════════════════════ +ВАЖНО ДЛЯ ФРОНЕНДА: +════════════════════════════════════════════════════════════════ + +1. Каждый пост ДОЛЖЕН иметь поле weekNumber (1, 2, 3 или 4) +2. Посты должны быть сгруппированы по неделям +3. Темы постов должны соответствовать темам из weeklyPlans +4. Даты должны быть последовательными без пропусков >2 дней внутри недели +5. postIndex для каждого поста: 0-15 + +════════════════════════════════════════════════════════════════ + +ОТВЕТ: ТОЛЬКО JSON объект. Никакого текста вне JSON. Никаких markdown блоков \`\`\`json. Начни сразу с { и закончи }.`; +} + +/** + * Извлечь JSON из ответа AI (убрать markdown, комментарии) + */ +export function extractJSONFromResponse(response: string): string { + let cleaned = response.trim(); + + // Убрать markdown блоки + cleaned = cleaned.replace(/^```json\s*/i, '').replace(/\s*```$/, ''); + cleaned = cleaned.replace(/^```\s*/i, '').replace(/\s*```$/, ''); + + // Убрать комментарии в начале + cleaned = cleaned.replace(/^\/\/.*$/gm, ''); + + // Найти первый { и последний } + const firstBrace = cleaned.indexOf('{'); + const lastBrace = cleaned.lastIndexOf('}'); + + if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) { + return cleaned.substring(firstBrace, lastBrace + 1); + } + + return cleaned; +} + +/** + * Безопасно распарсить JSON + */ +export function safeParseJSON(jsonString: string): unknown { + try { + return JSON.parse(jsonString); + } catch (error) { + console.error('Ошибка парсинга JSON:', error); + throw new Error(`Невалидный JSON: ${(error as Error).message}`); + } +} diff --git a/src/utils/validateAIStrategy.ts b/src/utils/validateAIStrategy.ts new file mode 100644 index 0000000..04d99f4 --- /dev/null +++ b/src/utils/validateAIStrategy.ts @@ -0,0 +1,587 @@ +/** + * Утилита валидации AI-стратегии + * Проверяет полное соответствие промпту: 16 постов, даты, weekNumber, хэштеги, видео/фото + */ + +import type { + AIStrategyResponse, + PostCalendarItem, + WeeklyPlan, + ValidationResult, + ValidationError, + ValidationWarning, + WeekNumber, + PublishTime +} from '@/types/ai-strategy.types'; +import { STRATEGY_CONSTANTS } from '@/types/ai-strategy.types'; + +// ============================================================ +// MAIN VALIDATION ENTRY POINT +// ============================================================ + +/** + * Полная валидация AI-стратегии + * Проверяет структуру, количество постов, даты, контент + */ +export function validateAIStrategy(response: unknown): ValidationResult { + const errors: ValidationError[] = []; + const warnings: ValidationWarning[] = []; + + // 1. Проверка: это объект? + if (!response || typeof response !== 'object') { + errors.push({ + type: 'STRUCTURE', + field: 'response', + message: 'Ответ AI должен быть объектом' + }); + return { isValid: false, errors, warnings }; + } + + const strategy = response as AIStrategyResponse; + + // 2. Проверка обязательных полей верхнего уровня + validateTopLevelFields(strategy, errors); + + // 3. Проверка weeklyPlans + validateWeeklyPlans(strategy.weeklyPlans, errors, warnings); + + // 4. Проверка postCalendar + validatePostCalendar(strategy.postCalendar, errors, warnings); + + // 5. Кросс-валидация: weeklyPlans ↔ postCalendar + validateCrossReferences(strategy, errors, warnings); + + return { + isValid: errors.length === 0, + errors, + warnings + }; +} + +// ============================================================ +// TOP LEVEL FIELDS VALIDATION +// ============================================================ + +function validateTopLevelFields( + strategy: AIStrategyResponse, + errors: ValidationError[] +): void { + // selectedModel + if (!strategy.selectedModel || typeof strategy.selectedModel !== 'string') { + errors.push({ + type: 'STRUCTURE', + field: 'selectedModel', + message: 'Поле selectedModel обязательно и должно быть строкой' + }); + } + + // scoringRationale + if (!strategy.scoringRationale || typeof strategy.scoringRationale !== 'string') { + errors.push({ + type: 'STRUCTURE', + field: 'scoringRationale', + message: 'Поле scoringRationale обязательно и должно быть строкой' + }); + } + + // recommendedDurationWeeks + if (strategy.recommendedDurationWeeks !== 4) { + errors.push({ + type: 'STRUCTURE', + field: 'recommendedDurationWeeks', + message: 'Рекомендуемая длительность должна быть ровно 4 недели' + }); + } + + // recommendedPlatforms + if ( + !Array.isArray(strategy.recommendedPlatforms) || + strategy.recommendedPlatforms.length === 0 + ) { + errors.push({ + type: 'STRUCTURE', + field: 'recommendedPlatforms', + message: 'Поле recommendedPlatforms должно быть непустым массивом' + }); + } + + // rationale + if (!strategy.rationale || typeof strategy.rationale !== 'string') { + errors.push({ + type: 'STRUCTURE', + field: 'rationale', + message: 'Поле rationale обязательно и должно быть строкой' + }); + } else { + const rationaleWordCount = countWords(strategy.rationale); + if (rationaleWordCount < STRATEGY_CONSTANTS.MIN_RATIONALE_WORDS) { + errors.push({ + type: 'CONTENT', + field: 'rationale', + message: `Rationale должен содержать минимум ${STRATEGY_CONSTANTS.MIN_RATIONALE_WORDS} слов (сейчас: ${rationaleWordCount})` + }); + } + } + + // weeklyPlans + if (!Array.isArray(strategy.weeklyPlans)) { + errors.push({ + type: 'STRUCTURE', + field: 'weeklyPlans', + message: 'weeklyPlans должен быть массивом' + }); + } else if (strategy.weeklyPlans.length !== 4) { + errors.push({ + type: 'COUNT', + field: 'weeklyPlans', + message: `weeklyPlans должен содержать ровно 4 недели (сейчас: ${strategy.weeklyPlans.length})` + }); + } + + // postCalendar + if (!Array.isArray(strategy.postCalendar)) { + errors.push({ + type: 'STRUCTURE', + field: 'postCalendar', + message: 'postCalendar должен быть массивом' + }); + } else if (strategy.postCalendar.length !== STRATEGY_CONSTANTS.TOTAL_POSTS) { + errors.push({ + type: 'COUNT', + field: 'postCalendar', + message: `postCalendar должен содержать ровно ${STRATEGY_CONSTANTS.TOTAL_POSTS} постов (сейчас: ${strategy.postCalendar.length})` + }); + } +} + +// ============================================================ +// WEEKLY PLANS VALIDATION +// ============================================================ + +function validateWeeklyPlans( + plans: WeeklyPlan[] | undefined, + errors: ValidationError[], + warnings: ValidationWarning[] +): void { + if (!plans) return; + + // Проверка: каждая неделя 1-4 представлена + const expectedWeekNumbers = [1, 2, 3, 4] as WeekNumber[]; + const actualWeekNumbers = plans.map(p => p.weekNumber); + + for (const weekNum of expectedWeekNumbers) { + if (!actualWeekNumbers.includes(weekNum)) { + errors.push({ + type: 'COUNT', + field: `weeklyPlans[${weekNum}]`, + message: `Отсутствует план для недели ${weekNum}` + }); + } + } + + // Валидация каждой недели + for (let i = 0; i < plans.length; i++) { + const plan = plans[i]; + const prefix = `weeklyPlans[${i}]`; + + // weekNumber + if (!isWeekNumber(plan.weekNumber)) { + errors.push({ + type: 'FORMAT', + field: `${prefix}.weekNumber`, + message: `weekNumber должен быть 1, 2, 3 или 4 (получено: ${plan.weekNumber})` + }); + } + + // mainThemes (ровно 4) + if (!Array.isArray(plan.mainThemes)) { + errors.push({ + type: 'STRUCTURE', + field: `${prefix}.mainThemes`, + message: 'mainThemes должен быть массивом' + }); + } else if (plan.mainThemes.length !== 4) { + errors.push({ + type: 'COUNT', + field: `${prefix}.mainThemes`, + message: `mainThemes должен содержать ровно 4 темы (сейчас: ${plan.mainThemes.length})` + }); + } + + // contentRecommendations + if (!plan.contentRecommendations || typeof plan.contentRecommendations !== 'string') { + errors.push({ + type: 'STRUCTURE', + field: `${prefix}.contentRecommendations`, + message: 'contentRecommendations обязателен и должен быть строкой' + }); + } else { + const recWordCount = countWords(plan.contentRecommendations); + if (recWordCount < STRATEGY_CONSTANTS.MIN_WEEKLY_RECOMMENDATION_WORDS) { + errors.push({ + type: 'CONTENT', + field: `${prefix}.contentRecommendations`, + message: `contentRecommendations должен содержать минимум ${STRATEGY_CONSTANTS.MIN_WEEKLY_RECOMMENDATION_WORDS} слов (сейчас: ${recWordCount})` + }); + } + } + + // priorityPlatforms + if (!Array.isArray(plan.priorityPlatforms) || plan.priorityPlatforms.length === 0) { + errors.push({ + type: 'STRUCTURE', + field: `${prefix}.priorityPlatforms`, + message: 'priorityPlatforms должен быть непустым массивом' + }); + } + } +} + +// ============================================================ +// POST CALENDAR VALIDATION +// ============================================================ + +function validatePostCalendar( + posts: PostCalendarItem[] | undefined, + errors: ValidationError[], + warnings: ValidationWarning[] +): void { + if (!posts) return; + + let totalVideos = 0; + let totalPhotos = 0; + + for (let i = 0; i < posts.length; i++) { + const post = posts[i]; + const prefix = `postCalendar[${i}]`; + + // publishDate + if (!post.publishDate || typeof post.publishDate !== 'string') { + errors.push({ + type: 'STRUCTURE', + field: `${prefix}.publishDate`, + message: 'publishDate обязателен и должен быть строкой' + }); + } else { + const date = new Date(post.publishDate); + if (isNaN(date.getTime())) { + errors.push({ + type: 'DATE', + field: `${prefix}.publishDate`, + message: `Невалидный формат даты: ${post.publishDate}. Ожидается ISO формат (2026-04-13T09:00:00)` + }); + } + } + + // weekNumber + if (!isWeekNumber(post.weekNumber)) { + errors.push({ + type: 'FORMAT', + field: `${prefix}.weekNumber`, + message: `weekNumber должен быть 1, 2, 3 или 4 (получено: ${post.weekNumber})` + }); + } + + // platform + if (post.platform !== 'Instagram' && post.platform !== 'TikTok') { + errors.push({ + type: 'FORMAT', + field: `${prefix}.platform`, + message: `platform должен быть "Instagram" или "TikTok" (получено: ${post.platform})` + }); + } + + // contentType + if (post.contentType !== 'фото' && post.contentType !== 'видео') { + errors.push({ + type: 'FORMAT', + field: `${prefix}.contentType`, + message: `contentType должен быть "фото" или "видео" (получено: ${post.contentType})` + }); + } else { + if (post.contentType === 'видео') totalVideos++; + else totalPhotos++; + } + + // theme + if (!post.theme || typeof post.theme !== 'string' || post.theme.trim().length === 0) { + errors.push({ + type: 'CONTENT', + field: `${prefix}.theme`, + message: 'theme обязателен и не может быть пустым' + }); + } else { + // Проверка: тема не абстрактная + const abstractThemes = ['о компании', 'введение', 'информационный', 'пост']; + const lowerTheme = post.theme.toLowerCase(); + for (const abstract of abstractThemes) { + if (lowerTheme.includes(abstract) && post.theme.split(':').length <= 1) { + warnings.push({ + type: 'CONTENT', + field: `${prefix}.theme`, + message: `Тема "${post.theme}" выглядит абстрактной. Используйте формат "Тип: Конкретика" (например "Кейс: Как мы увеличили продажи на 40%")` + }); + break; + } + } + } + + // postText (минимум 150 слов) + if (!post.postText || typeof post.postText !== 'string') { + errors.push({ + type: 'STRUCTURE', + field: `${prefix}.postText`, + message: 'postText обязателен и должен быть строкой' + }); + } else { + const wordCount = countWords(post.postText); + if (wordCount < STRATEGY_CONSTANTS.MIN_POST_WORDS) { + errors.push({ + type: 'CONTENT', + field: `${prefix}.postText`, + message: `postText должен содержать минимум ${STRATEGY_CONSTANTS.MIN_POST_WORDS} слов (сейчас: ${wordCount})` + }); + } + } + + // hashtags + if (!Array.isArray(post.hashtags)) { + errors.push({ + type: 'STRUCTURE', + field: `${prefix}.hashtags`, + message: 'hashtags должен быть массивом' + }); + } else { + if ( + post.hashtags.length < STRATEGY_CONSTANTS.MIN_HASHTAGS || + post.hashtags.length > STRATEGY_CONSTANTS.MAX_HASHTAGS + ) { + errors.push({ + type: 'COUNT', + field: `${prefix}.hashtags`, + message: `hashtags должен содержать от ${STRATEGY_CONSTANTS.MIN_HASHTAGS} до ${STRATEGY_CONSTANTS.MAX_HASHTAGS} элементов (сейчас: ${post.hashtags.length})` + }); + } + + // Проверка формата каждого хэштега + for (let h = 0; h < post.hashtags.length; h++) { + const hashtag = post.hashtags[h]; + if (!hashtag.startsWith('#')) { + errors.push({ + type: 'FORMAT', + field: `${prefix}.hashtags[${h}]`, + message: `Хэштег должен начинаться с #: "${hashtag}"` + }); + } + if (hashtag.startsWith('##')) { + errors.push({ + type: 'FORMAT', + field: `${prefix}.hashtags[${h}]`, + message: `Хэштег не должен начинаться с ##: "${hashtag}"` + }); + } + if (hashtag.includes(' ')) { + errors.push({ + type: 'FORMAT', + field: `${prefix}.hashtags[${h}]`, + message: `Хэштег не должен содержать пробелы: "${hashtag}"` + }); + } + } + } + + // publishTime + const validTimes = STRATEGY_CONSTANTS.VALID_PUBLISH_TIMES as readonly string[]; + if (!validTimes.includes(post.publishTime)) { + errors.push({ + type: 'FORMAT', + field: `${prefix}.publishTime`, + message: `publishTime должен быть одним из: ${validTimes.join(', ')} (получено: ${post.publishTime})` + }); + } + } + + // Проверка общего количества видео/фото + if (totalVideos !== STRATEGY_CONSTANTS.TOTAL_VIDEOS) { + errors.push({ + type: 'COUNT', + field: 'postCalendar.totalVideos', + message: `Стратегия должна содержать ровно ${STRATEGY_CONSTANTS.TOTAL_VIDEOS} видео-поста (сейчас: ${totalVideos})` + }); + } + + if (totalPhotos !== STRATEGY_CONSTANTS.TOTAL_PHOTOS) { + errors.push({ + type: 'COUNT', + field: 'postCalendar.totalPhotos', + message: `Стратегия должна содержать ровно ${STRATEGY_CONSTANTS.TOTAL_PHOTOS} фото-поста (сейчас: ${totalPhotos})` + }); + } +} + +// ============================================================ +// CROSS-REFERENCE VALIDATION +// ============================================================ + +function validateCrossReferences( + strategy: AIStrategyResponse, + errors: ValidationError[], + warnings: ValidationWarning[] +): void { + const { weeklyPlans, postCalendar } = strategy; + if (!weeklyPlans || !postCalendar) return; + + // 1. Проверка: посты каждой недели соответствуют plan.mainThemes + for (let weekNum = 1; weekNum <= 4; weekNum++) { + const plan = weeklyPlans.find(p => p.weekNumber === weekNum); + const weekPosts = postCalendar.filter(p => p.weekNumber === weekNum); + + if (!plan) continue; + + if (weekPosts.length !== STRATEGY_CONSTANTS.POSTS_PER_WEEK) { + errors.push({ + type: 'COUNT', + field: `week${weekNum}.posts`, + message: `Неделя ${weekNum} должна содержать ровно ${STRATEGY_CONSTANTS.POSTS_PER_WEEK} поста (сейчас: ${weekPosts.length})` + }); + } + + // Проверка: первый пост недели — понедельник 09:00 с видео + const firstPost = weekPosts[0]; + if (firstPost) { + if (firstPost.contentType !== 'видео') { + errors.push({ + type: 'CONTENT', + field: `postCalendar.week${weekNum}.firstPost`, + message: `Первый пост недели ${weekNum} должен быть видео-типом (понедельник 09:00)` + }); + } + if (firstPost.publishTime !== '09:00') { + errors.push({ + type: 'FORMAT', + field: `postCalendar.week${weekNum}.firstPost.publishTime`, + message: `Первый пост недели ${weekNum} должен иметь publishTime "09:00" (сейчас: ${firstPost.publishTime})` + }); + } + } + + // Проверка: остальные посты недели — фото + for (let i = 1; i < weekPosts.length; i++) { + if (weekPosts[i].contentType !== 'фото') { + warnings.push({ + type: 'CONTENT', + field: `postCalendar.week${weekNum}.post[${i}]`, + message: `Пост ${i + 1} недели ${weekNum} должен быть фото-типом (не видео)` + }); + } + } + + // Проверка: даты последовательны (Пн, Ср, Пт, Сб) + validateWeeklySchedule(weekPosts, weekNum, errors); + } +} + +// ============================================================ +// WEEKLY SCHEDULE VALIDATION +// ============================================================ + +function validateWeeklySchedule( + weekPosts: PostCalendarItem[], + weekNum: number, + errors: ValidationError[] +): void { + if (weekPosts.length !== 4) return; + + const expectedSchedule = [ + { dayOfWeek: 1, time: '09:00' }, // Понедельник + { dayOfWeek: 3, time: '12:00' }, // Среда + { dayOfWeek: 5, time: '18:00' }, // Пятница + { dayOfWeek: 6, time: '20:00' } // Суббота + ]; + + for (let i = 0; i < weekPosts.length; i++) { + const post = weekPosts[i]; + const expected = expectedSchedule[i]; + const date = new Date(post.publishDate); + + if (isNaN(date.getTime())) continue; + + const actualDayOfWeek = date.getDay(); // 0=Вс, 1=Пн, ..., 6=Сб + const actualTime = post.publishTime; + + if (actualDayOfWeek !== expected.dayOfWeek) { + errors.push({ + type: 'DATE', + field: `postCalendar.week${weekNum}.post[${i}].publishDate`, + message: `Пост ${i + 1} недели ${weekNum} должен быть в ${getDayName(expected.dayOfWeek)} (получено: ${getDayName(actualDayOfWeek)}, ${post.publishDate})` + }); + } + + if (actualTime !== expected.time) { + errors.push({ + type: 'FORMAT', + field: `postCalendar.week${weekNum}.post[${i}].publishTime`, + message: `Пост ${i + 1} недели ${weekNum} должен иметь время "${expected.time}" (сейчас: "${actualTime}")` + }); + } + } +} + +// ============================================================ +// HELPER FUNCTIONS +// ============================================================ + +/** Подсчёт слов в тексте */ +function countWords(text: string): number { + return text + .trim() + .split(/\s+/) + .filter(word => word.length > 0) + .length; +} + +/** Проверка: валидный ли weekNumber */ +function isWeekNumber(value: unknown): value is WeekNumber { + return [1, 2, 3, 4].includes(value as WeekNumber); +} + +/** Получить название дня недели */ +function getDayName(day: number): string { + const names = ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота']; + return names[day] || 'Неизвестно'; +} + +/** Получить читаемое описание ошибки */ +export function formatValidationError(error: ValidationError): string { + return `[${error.type}] ${error.field}: ${error.message}`; +} + +/** Получить читаемое описание предупреждения */ +export function formatValidationWarning(warning: ValidationWarning): string { + return `[${warning.type}] ${warning.field}: ${warning.message}`; +} + +/** Получить сводку валидации для логирования */ +export function getValidationSummary(result: ValidationResult): string { + const lines = [ + `Валидация: ${result.isValid ? '✅ ПРОЙДЕНА' : '❌ ПРОВАЛЕНА'}`, + `Ошибок: ${result.errors.length}`, + `Предупреждений: ${result.warnings.length}` + ]; + + if (result.errors.length > 0) { + lines.push('\nОшибки:'); + for (const error of result.errors) { + lines.push(` • ${formatValidationError(error)}`); + } + } + + if (result.warnings.length > 0) { + lines.push('\nПредупреждения:'); + for (const warning of result.warnings) { + lines.push(` • ${formatValidationWarning(warning)}`); + } + } + + return lines.join('\n'); +} diff --git a/src/views/pages/marketing/AIStrategyGeneratorPage.vue b/src/views/pages/marketing/AIStrategyGeneratorPage.vue new file mode 100644 index 0000000..3f1efe9 --- /dev/null +++ b/src/views/pages/marketing/AIStrategyGeneratorPage.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/Промпт для AI-генерации маркетинговой стратегии.md b/Промпт для AI-генерации маркетинговой стратегии.md new file mode 100644 index 0000000..0e81e6c --- /dev/null +++ b/Промпт для AI-генерации маркетинговой стратегии.md @@ -0,0 +1,326 @@ +# 🎯 Промпт для AI-генерации маркетинговой стратегии + +> **Версия:** 1.0 +> **Дата:** 11 апреля 2026 +> **Назначение:** Профессиональная генерация SMM-стратегии для бизнеса в Казахстане + +--- + +## 📋 Системный промпт + +``` +Ты — профессиональный SMM-стратег с 15-летним опытом работы на рынке Казахстана. + +ТВОЯ ЗАДАЧА: Создать ПРОФЕССИОНАЛЬНУЮ маркетинговую стратегию на 4 недели +для бизнеса в нише "[НИША]" с брендом "[БРЕНД]" в городе "[ГОРОД]". +``` + +--- + +## ⚠️ Критически важно: Структура и формат + +### 📊 Общее количество постов + +| Параметр | Значение | +|----------|----------| +| **Всего постов** | РОВНО 16 | +| **Период** | 4 недели | +| **Постов в неделю** | 4 | +| **Дней публикации** | Пн, Ср, Пт, Сб | + +### 📅 Распределение по неделям + +#### Неделя 1 (дни 1-7) + +| Пост | День | День недели | Время | +|------|------|-------------|-------| +| Пост 1 | День 1 | Понедельник | 09:00 | +| Пост 2 | День 3 | Среда | 12:00 | +| Пост 3 | День 5 | Пятница | 18:00 | +| Пост 4 | День 6 | Суббота | 20:00 | + +#### Неделя 2 (дни 8-14) + +| Пост | День | День недели | Время | +|------|------|-------------|-------| +| Пост 5 | День 8 | Понедельник | 09:00 | +| Пост 6 | День 10 | Среда | 12:00 | +| Пост 7 | День 12 | Пятница | 18:00 | +| Пост 8 | День 13 | Суббота | 20:00 | + +#### Неделя 3 (дни 15-21) + +| Пост | День | День недели | Время | +|------|------|-------------|-------| +| Пост 9 | День 15 | Понедельник | 09:00 | +| Пост 10 | День 17 | Среда | 12:00 | +| Пост 11 | День 19 | Пятница | 18:00 | +| Пост 12 | День 20 | Суббота | 20:00 | + +#### Неделя 4 (дни 22-28) + +| Пост | День | День недели | Время | +|------|------|-------------|-------| +| Пост 13 | День 22 | Понедельник | 09:00 | +| Пост 14 | День 24 | Среда | 12:00 | +| Пост 15 | День 26 | Пятница | 18:00 | +| Пост 16 | День 27 | Суббота | 20:00 | + +--- + +## ✍️ Правила создания контента + +### 1️⃣ Обязательные поля каждого поста + +Каждый пост **ДОЛЖЕН** содержать следующие поля: + +| Поле | Тип | Описание | Пример | +|------|-----|----------|--------| +| `publishDate` | `string` | Точная дата и время (ISO 8601) | `"2026-04-13T09:00:00"` | +| `weekNumber` | `number` | Номер недели (1-4) | `1` | +| `platform` | `string` | Социальная сеть | `"Instagram"`, `"TikTok"` | +| `contentType` | `string` | Тип контента | `"фото"`, `"видео"` | +| `theme` | `string` | Конкретная тема поста | `"Кейс: Как мы увеличили продажи..."` | +| `postText` | `string` | Текст поста (150+ слов) | `"Откройте секрет..."` | +| `hashtags` | `string[]` | Хэштеги (5-8 шт) | `["#алматы", "#бизнес"]` | +| `publishTime` | `string` | Время публикации | `"09:00"` | + +--- + +### 2️⃣ Видео-посты + +> 🎬 **Правило:** РОВНО 4 видео за всю стратегию (по 1 в неделю) + +- ✅ Размещай видео на **первый пост каждой недели** (понедельник 09:00) +- ✅ Остальные 12 постов — **фото** + +--- + +### 3️⃣ Темы постов — конкретные, не абстрактные + +#### ✅ Правильно + +- `Кейс: Как мы увеличили продажи клиента на 40% за месяц` +- `Процесс: За кадром нашей работы — как готовится самса` +- `Отзыв: История довольного клиента из Алматы` +- `Экспертный: 5 ошибок при выборе услуги` + +#### ❌ Неправильно + +- `О нашей компании` +- `Введение в продукт` +- `Информационный пост` + +--- + +### 4️⃣ Текст поста (postText) — продающий контент + +**Структура текста:** + +1. 🪝 **Первые 2 строки** — КРЮЧОК, который останавливает скролл +2. 📊 **Основная часть** — конкретные цифры, факты, примеры из казахстанской реальности +3. 📣 **Призыв к действию** — ОДИН конкретный CTA (запись, звонок, директ) + +**Требования:** + +- Минимум **150 слов** живого текста +- Используй **тенге**, алматинские районы, казахстанские реалии +- Язык: **ТОЛЬКО русский** + +--- + +### 5️⃣ Хэштеги + +| Параметр | Требование | +|----------|------------| +| **Количество** | 5-8 хэштегов на пост | +| **Формат** | Каждый начинается с ОДНОГО `#` | + +#### ❌ Запрещено + +- `##хэштег` (двойной символ) +- `# хэштег` (пробел после #) +- `#almatyfood` (английские хэштеги) + +#### ✅ Правильно + +``` +#тортыалматы #выпечкаказахстан #алматы #сладкийстол #тортыназаказ +``` + +--- + +## 📆 Еженедельные планы (weeklyPlans) + +Для каждой недели создай план с **РОВНО 4 темами**: + +```json +{ + "weekNumber": 1, + "mainThemes": [ + "Конкретная тема 1 для недели 1", + "Конкретная тема 2 для недели 1", + "Конкретная тема 3 для недели 1", + "Конкретная тема 4 для недели 1" + ], + "contentRecommendations": "Что конкретно снимать и писать на неделе 1 — минимум 50 слов", + "priorityPlatforms": ["Instagram"] +} +``` + +--- + +## 📦 Полная JSON структура ответа + +```json +{ + "selectedModel": "[МОДЕЛЬ]", + "scoringRationale": "Обоснование выбора модели", + "recommendedDurationWeeks": 4, + "recommendedPlatforms": ["Instagram", "TikTok"], + "rationale": "Минимум 60 слов — почему именно эти платформы для данной ниши", + "weeklyPlans": [ + { + "weekNumber": 1, + "mainThemes": ["Тема 1", "Тема 2", "Тема 3", "Тема 4"], + "contentRecommendations": "Рекомендации для недели 1", + "priorityPlatforms": ["Instagram"] + }, + { "weekNumber": 2, "mainThemes": [], "contentRecommendations": "", "priorityPlatforms": [] }, + { "weekNumber": 3, "mainThemes": [], "contentRecommendations": "", "priorityPlatforms": [] }, + { "weekNumber": 4, "mainThemes": [], "contentRecommendations": "", "priorityPlatforms": [] } + ], + "postCalendar": [ + { + "publishDate": "2026-04-13T09:00:00", + "weekNumber": 1, + "platform": "Instagram", + "contentType": "видео", + "theme": "Кейс: Конкретная история успеха клиента", + "postText": "150+ слов продающего текста...", + "hashtags": ["#хэштег1", "#хэштег2", "#алматы"], + "publishTime": "09:00" + }, + { + "publishDate": "2026-04-15T12:00:00", + "weekNumber": 1, + "platform": "Instagram", + "contentType": "фото", + "theme": "Процесс: За кадром работы", + "postText": "150+ слов...", + "hashtags": ["#хэштег1", "#хэштег2"], + "publishTime": "12:00" + } + ] +} +``` + +--- + +## 📝 Пример правильного распределения (первые 8 постов) + +```json +{ + "postCalendar": [ + { "publishDate": "2026-04-13T09:00:00", "weekNumber": 1, "contentType": "видео", "theme": "Кейс: ..." }, + { "publishDate": "2026-04-15T12:00:00", "weekNumber": 1, "contentType": "фото", "theme": "Процесс: ..." }, + { "publishDate": "2026-04-17T18:00:00", "weekNumber": 1, "contentType": "фото", "theme": "Отзыв: ..." }, + { "publishDate": "2026-04-18T20:00:00", "weekNumber": 1, "contentType": "фото", "theme": "Экспертный: ..." }, + + { "publishDate": "2026-04-20T09:00:00", "weekNumber": 2, "contentType": "видео", "theme": "Кейс: ..." }, + { "publishDate": "2026-04-22T12:00:00", "weekNumber": 2, "contentType": "фото", "theme": "Процесс: ..." }, + { "publishDate": "2026-04-24T18:00:00", "weekNumber": 2, "contentType": "фото", "theme": "Отзыв: ..." }, + { "publishDate": "2026-04-25T20:00:00", "weekNumber": 2, "contentType": "фото", "theme": "Продающий: ..." } + ] +} +``` + +--- + +## 🔗 Типы контента по модели + +> ⚙️ **Здесь укажи типы контента в зависимости от выбранной модели AI** + +| Модель | Типы контента | +|--------|---------------| +| GPT-4 | Видео-кейсы, экспертные посты, отзывы | +| GPT-3.5 | Фото-процессы, продающие посты, хэштеги | +| Claude | Глубокие кейсы, аналитика, сравнения | + +--- + +## 🎨 Важно для фронтенда + +### Критические требования + +1. ✅ Каждый пост **ДОЛЖЕН** иметь поле `weekNumber` (1, 2, 3 или 4) +2. ✅ Посты должны быть **сгруппированы по неделям** +3. ✅ Темы постов должны **соответствовать** темам из `weeklyPlans` +4. ✅ Даты должны быть **последовательными** без пропусков >2 дней внутри недели + +### TypeScript интерфейс + +```typescript +interface PostCalendarItem { + postIndex: number; + publishDate: string; // "2026-04-13T09:00:00" + weekNumber: number; // 1, 2, 3 или 4 + platform: string; // "Instagram" | "TikTok" + contentType: string; // "фото" | "видео" + theme: string; // Конкретная тема + postText: string; // 150+ слов + hashtags: string[]; // ["#тортыалматы", ...] + publishTime: string; // "09:00" | "12:00" | "18:00" | "20:00" + imageUrl?: string; + videoUrl?: string; + taskStatus?: string; +} +``` + +--- + +## 🚀 Как использовать + +### Шаг 1: Подставь реальные данные + +| Переменная | Описание | Пример | +|------------|----------|--------| +| `[НИША]` | Ниша бизнеса | `"выпечка тортов на заказ"` | +| `[БРЕНД]` | Название бренда | `"Сладкий Мир"` | +| `[ГОРОД]` | Город | `"Алматы"` | +| `[МОДЕЛЬ]` | AI модель | `"GPT-4"` | + +### Шаг 2: Вставь промпт в систему генерации + +``` +Скопируй весь текст промпта, замени переменные и отправь в AI +``` + +### Шаг 3: Получи JSON с профессиональной стратегией + +``` +AI вернёт валидный JSON объект с готовой маркетинговой стратегией +``` + +--- + +## ✅ Преимущества этого промпта + +| Преимущество | Описание | +|--------------|----------| +| 📐 **Чёткая структура** | 16 постов, 4/неделю, Пн-Ср-Пт-Сб | +| 🎨 **Красивый фронтенд** | Поле `weekNumber` для группировки | +| 🎯 **Профессионализм** | Конкретные темы, не абстракции | +| ⏰ **Предсказуемость** | Фиксированные времена публикации | +| ✔️ **Валидация** | AI не сможет вернуть меньше 16 постов | + +--- + +## 📞 Поддержка + +> При возникновении вопросов по использованию промпта обращайтесь к документации по API генерации отчётов. + +--- + +*📄 Документ создан: 11 апреля 2026* +*🔄 Версия: 1.0*