fix
This commit is contained in:
@@ -67,7 +67,7 @@ public class MarketingStrategyV3Service {
|
||||
}
|
||||
strategy.setStrategyData(initialData);
|
||||
|
||||
addStatusHistoryEntry(strategy, "queued", "Запуск генерации. ИИ подбирает оптимальные платформы...");
|
||||
addStatusHistoryEntry(strategy, "queued", "Запуск генерации. ИИ проектирует массированный контент-план...");
|
||||
|
||||
strategy = repository.save(strategy);
|
||||
processStrategyGenerationAsync(strategy.getId(), analysisDoc, strategy);
|
||||
@@ -79,7 +79,7 @@ public class MarketingStrategyV3Service {
|
||||
public void processStrategyGenerationAsync(String strategyId, MarketingAnalysisV3Document analysis, MarketingStrategy strategy) {
|
||||
try {
|
||||
strategy.setStatus("processing");
|
||||
addStatusHistoryEntry(strategy, "processing", "Расчет баллов, выбор платформ и генерация контент-плана...");
|
||||
addStatusHistoryEntry(strategy, "processing", "Расчет баллов, генерация постов (минимум 3 в неделю)...");
|
||||
repository.save(strategy);
|
||||
|
||||
// 1. Считаем лучшую модель
|
||||
@@ -108,7 +108,10 @@ public class MarketingStrategyV3Service {
|
||||
populateStrategyEntity(strategy, strategyData);
|
||||
repository.save(strategy);
|
||||
|
||||
// 5. Генерируем медиа НАДЕЖНЫМ способом из V1
|
||||
// 5. Генерируем медиа НАДЕЖНЫМ способом с FALLBACK
|
||||
addStatusHistoryEntry(strategy, "processing", "Создание фото и видео креативов. Это займет несколько минут...");
|
||||
repository.save(strategy);
|
||||
|
||||
generateMediaAssets(strategy, analysis);
|
||||
|
||||
strategy.setStatus("completed");
|
||||
@@ -122,6 +125,7 @@ public class MarketingStrategyV3Service {
|
||||
}
|
||||
}
|
||||
|
||||
// Тот самый надежный калькулятор
|
||||
public StrategyModel calculateBestScoringModel(kz.konturai.parser.dto.MarketingAnalysisV3Request req) {
|
||||
int entry = 0, authority = 0, trust = 0, conversion = 0;
|
||||
|
||||
@@ -201,16 +205,15 @@ public class MarketingStrategyV3Service {
|
||||
for (Map<String, Object> c : calendarMap) {
|
||||
MarketingStrategy.PostCalendarItem item = new MarketingStrategy.PostCalendarItem();
|
||||
|
||||
// Безопасный парсинг даты как в V1
|
||||
String dateStr = (String) c.get("publishDate");
|
||||
if (dateStr != null) {
|
||||
try {
|
||||
item.setPublishDate(LocalDateTime.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||
} catch (Exception e) {
|
||||
item.setPublishDate(startDate.plusDays(postCalendar.size()));
|
||||
item.setPublishDate(startDate.plusDays(postCalendar.size() * 2L)); // Распределяем по дням
|
||||
}
|
||||
} else {
|
||||
item.setPublishDate(startDate.plusDays(postCalendar.size()));
|
||||
item.setPublishDate(startDate.plusDays(postCalendar.size() * 2L));
|
||||
}
|
||||
|
||||
item.setPlatform((String) c.get("platform"));
|
||||
@@ -227,7 +230,7 @@ public class MarketingStrategyV3Service {
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// НАДЕЖНАЯ ГЕНЕРАЦИЯ МЕДИА (ПОРТИРОВАНО ИЗ V1)
|
||||
// ЖЕЛЕЗОБЕТОННАЯ ГЕНЕРАЦИЯ МЕДИА С FALLBACK
|
||||
// ==========================================
|
||||
|
||||
private void generateMediaAssets(MarketingStrategy strategy, MarketingAnalysisV3Document analysis) {
|
||||
@@ -241,93 +244,114 @@ public class MarketingStrategyV3Service {
|
||||
InputStream logoStream = minIOService.downloadFile(logoFilename);
|
||||
clientLogoBytes = logoStream.readAllBytes();
|
||||
logoStream.close();
|
||||
log.info("Загружен логотип: {}", logoFilename);
|
||||
log.info("Загружен логотип для генерации: {}", logoFilename);
|
||||
} catch (Exception e) {
|
||||
log.error("Не удалось скачать логотип {}: {}", logoFilename, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
for (MarketingStrategy.PostCalendarItem item : strategy.getPostCalendar()) {
|
||||
String contentType = item.getContentType() != null ? item.getContentType().toLowerCase() : "";
|
||||
String contentType = item.getContentType() != null ? item.getContentType().toLowerCase() : "фото";
|
||||
|
||||
try {
|
||||
if (contentType.contains("видео") || contentType.contains("reels") || contentType.contains("tiktok")) {
|
||||
String videoPrompt = "Кинематографичное видео. Бизнес: " + businessContext + ". Сюжет: " + item.getTheme();
|
||||
byte[] videoBytes = geminiVideoService.generateVideo(videoPrompt);
|
||||
// ПРОБУЕМ СГЕНЕРИРОВАТЬ ВИДЕО
|
||||
String videoPrompt = "High quality cinematic commercial video. Business niche: " + businessContext + ". Scene: " + item.getTheme() + ". Photorealistic, dynamic motion, 4k.";
|
||||
log.info("Попытка генерации ВИДЕО: {}", videoPrompt);
|
||||
|
||||
byte[] videoBytes = null;
|
||||
try {
|
||||
videoBytes = geminiVideoService.generateVideo(videoPrompt);
|
||||
} catch (Exception e) {
|
||||
log.warn("Сервис видео упал с ошибкой: {}", e.getMessage());
|
||||
}
|
||||
|
||||
if (videoBytes != null && videoBytes.length > 0) {
|
||||
String filename = "video_" + System.currentTimeMillis() + "_" + item.hashCode() + ".mp4";
|
||||
minIOService.uploadFile(filename, videoBytes, "video/mp4");
|
||||
item.setVideoUrl(filename);
|
||||
item.setVideoFilename(filename);
|
||||
log.info("Видео успешно сгенерировано: {}", filename);
|
||||
} else {
|
||||
// ФОЛБЭК: ВИДЕО НЕ ПОЛУЧИЛОСЬ -> ДЕЛАЕМ ФОТО
|
||||
log.warn("Видео не сгенерировалось (вернулся null). Включаю FALLBACK -> генерирую ФОТО для темы: {}", item.getTheme());
|
||||
item.setContentType("фото"); // Меняем тип поста, чтобы фронт отобразил картинку
|
||||
doGenerateImage(item, businessContext, brandName, clientLogoBytes);
|
||||
}
|
||||
} else {
|
||||
// ИСПОЛЬЗУЕМ СТАРЫЙ ДОБРЫЙ BUILDER ПРОМПТОВ!
|
||||
String imagePrompt = buildImagePrompt(item, businessContext, brandName);
|
||||
log.info("Generating image with prompt: {}", imagePrompt);
|
||||
|
||||
byte[] imageBytes = imageGenerationService.generateImageWithReference(imagePrompt, clientLogoBytes);
|
||||
|
||||
if (imageBytes != null && imageBytes.length > 0) {
|
||||
String filename = "image_" + System.currentTimeMillis() + "_" + item.hashCode() + ".png";
|
||||
minIOService.uploadFile(filename, imageBytes, MediaType.IMAGE_PNG.toString());
|
||||
item.setImageUrl(filename);
|
||||
item.setImageFilename(filename);
|
||||
}
|
||||
// ГЕНЕРИРУЕМ ФОТО
|
||||
doGenerateImage(item, businessContext, brandName, clientLogoBytes);
|
||||
}
|
||||
|
||||
if (delayBetweenRequestsMs > 0) Thread.sleep(delayBetweenRequestsMs);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Ошибка создания медиа для поста '{}': {}", item.getTheme(), e.getMessage());
|
||||
log.error("Критическая ошибка создания медиа для поста '{}': {}", item.getTheme(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Выделенный надежный метод для фото
|
||||
private void doGenerateImage(MarketingStrategy.PostCalendarItem item, String businessContext, String brandName, byte[] clientLogoBytes) {
|
||||
String imagePrompt = buildImagePrompt(item, businessContext, brandName);
|
||||
log.info("Генерация ФОТО: {}", imagePrompt);
|
||||
try {
|
||||
byte[] imageBytes = imageGenerationService.generateImageWithReference(imagePrompt, clientLogoBytes);
|
||||
if (imageBytes != null && imageBytes.length > 0) {
|
||||
String filename = "image_" + System.currentTimeMillis() + "_" + item.hashCode() + ".png";
|
||||
minIOService.uploadFile(filename, imageBytes, MediaType.IMAGE_PNG_VALUE);
|
||||
item.setImageUrl(filename);
|
||||
item.setImageFilename(filename);
|
||||
} else {
|
||||
log.warn("NanoBanana вернул пустой массив байтов для темы: {}", item.getTheme());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Генерация фото упала с ошибкой: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String getBusinessContext(MarketingAnalysisV3Document analysis) {
|
||||
if (analysis == null || analysis.getRequestData() == null) return "";
|
||||
return "Ниша: " + analysis.getRequestData().getBusinessNiche() + ", Продукт: " + analysis.getRequestData().getProductName();
|
||||
return analysis.getRequestData().getBusinessNiche() + ", product: " + analysis.getRequestData().getProductName();
|
||||
}
|
||||
|
||||
// ПРОМПТ ДЛЯ ФОТО (НА АНГЛИЙСКОМ ДЛЯ ЛУЧШЕГО КАЧЕСТВА)
|
||||
private String buildImagePrompt(MarketingStrategy.PostCalendarItem item, String businessContext, String brandName) {
|
||||
StringBuilder prompt = new StringBuilder();
|
||||
|
||||
String style = "высококачественная фотография, реалистичное освещение, 8k, высокая детализация";
|
||||
String style = "high-end commercial photography, photorealistic, 8k resolution, highly detailed, professional lighting";
|
||||
if (item.getPlatform() != null) {
|
||||
String platform = item.getPlatform().toLowerCase();
|
||||
if (platform.contains("linkedin")) {
|
||||
style += ", корпоративный стиль, профессионально, минимализм, чисто";
|
||||
style += ", corporate style, professional, minimalist, clean";
|
||||
} else if (platform.contains("instagram") || platform.contains("tiktok")) {
|
||||
style += ", лайфстайл, эстетика, яркие цвета, привлекательно, инстаграм-стиль";
|
||||
} else if (platform.contains("facebook")) {
|
||||
style += ", уютно, дружелюбно, для сообщества";
|
||||
style += ", lifestyle, aesthetic, vibrant colors, engaging, instagram-style";
|
||||
}
|
||||
}
|
||||
|
||||
prompt.append("Фотореалистичное изображение. ");
|
||||
prompt.append(style).append(". ");
|
||||
|
||||
if (businessContext != null && !businessContext.isEmpty()) {
|
||||
prompt.append("Контекст: ").append(businessContext).append(". ");
|
||||
prompt.append("Context: ").append(businessContext).append(". ");
|
||||
}
|
||||
|
||||
if (brandName != null && !brandName.isEmpty()) {
|
||||
prompt.append("Органично и нативно интегрируй название бренда '").append(brandName).append("' в окружение (например, на вывеске, упаковке, кружке или блокноте). ");
|
||||
prompt.append("Subtly and naturally integrate the brand name '").append(brandName).append("' into the physical environment (e.g., on a sign, package, mug, or notebook). ");
|
||||
}
|
||||
|
||||
if (item.getTheme() != null && !item.getTheme().isEmpty()) {
|
||||
prompt.append("Сюжет изображения: ").append(item.getTheme()).append(". ");
|
||||
// Переводим тему на английский контекст (ИИ поймет)
|
||||
prompt.append("Main subject/action: ").append(item.getTheme()).append(". ");
|
||||
}
|
||||
|
||||
prompt.append("Композиция: продукт или услуга в центре внимания. ");
|
||||
prompt.append("Если на фото есть люди, они должны взаимодействовать с продуктом естественно. ");
|
||||
prompt.append("Избегать: абстрактных концепций, искусственных поз, размытия, искажений. ");
|
||||
prompt.append("Визуальный стиль: ").append(style).append(".");
|
||||
prompt.append("Composition: Product or service is the main focus. People (if any) should look natural. ");
|
||||
prompt.append("Avoid: abstract concepts, people staring blankly at camera, fake poses, text overlay, blur, distortion.");
|
||||
|
||||
return prompt.toString();
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ПРОМПТЫ ДЛЯ ИИ (ТЕПЕРЬ БЕЗ ПРОМПТОВ ДЛЯ КАРТИНОК В JSON)
|
||||
// ПРОМПТЫ ДЛЯ ИИ (СТРОГИЕ ПРАВИЛА КОЛИЧЕСТВА)
|
||||
// ==========================================
|
||||
|
||||
private String buildSystemPrompt(StrategyModel bestModel) {
|
||||
@@ -336,9 +360,14 @@ public class MarketingStrategyV3Service {
|
||||
Бэкенд-система произвела расчет и выбрала доминирующую стратегию: %s.
|
||||
|
||||
Твоя задача — проанализировать нишу и САМОСТОЯТЕЛЬНО:
|
||||
1. Выбрать наиболее эффективные платформы (от 1 до 3 платформ из: Instagram, Facebook, LinkedIn, Telegram, TikTok, YouTube).
|
||||
2. Определить оптимальную длительность первой итерации стратегии (от 2 до 8 недель).
|
||||
3. Сгенерировать контент-план на основе твоих решений.
|
||||
1. Выбрать наиболее эффективные платформы (от 1 до 3 шт).
|
||||
2. Определить оптимальную длительность стратегии (рекомендую 4 недели).
|
||||
3. Сгенерировать МАСШТАБНЫЙ контент-план.
|
||||
|
||||
КРИТИЧЕСКОЕ ПРАВИЛО МАТЕМАТИКИ:
|
||||
Ты ОБЯЗАН сгенерировать МИНИМУМ 3 поста на КАЖДУЮ неделю.
|
||||
Если ты выбрал длительность 4 недели, в массиве 'postCalendar' должно быть РОВНО 12 постов (или больше).
|
||||
Обязательно чередуй типы контента: 'фото' и 'видео'.
|
||||
|
||||
ПРАВИЛА МОДЕЛИ:
|
||||
%s
|
||||
@@ -351,7 +380,6 @@ public class MarketingStrategyV3Service {
|
||||
String clientDtoJson = objectMapper.writeValueAsString(analysis.getRequestData());
|
||||
LocalDateTime startDate = LocalDateTime.now().plusDays(1);
|
||||
|
||||
// УБРАЛИ imageGenerationPrompt ИЗ ШАБЛОНА, ЧТОБЫ ИИ НЕ ЛОМАЛ JSON!
|
||||
return """
|
||||
ДАННЫЕ БИЗНЕСА:
|
||||
%s
|
||||
@@ -359,16 +387,16 @@ public class MarketingStrategyV3Service {
|
||||
Старт публикаций: %s
|
||||
ВЫБРАННАЯ МОДЕЛЬ: %s
|
||||
|
||||
ВЫВЕДИ ТОЛЬКО JSON СТРОГО ПО ЭТОЙ СТРУКТУРЕ:
|
||||
ВЫВЕДИ ТОЛЬКО JSON СТРОГО ПО ЭТОЙ СТРУКТУРЕ (СГЕНЕРИРУЙ 12 ПОСТОВ ЕСЛИ ВЫБРАЛ 4 НЕДЕЛИ):
|
||||
{
|
||||
"selectedModel": "%s",
|
||||
"rationale": "Обоснование выбора платформ",
|
||||
"recommendedDurationWeeks": 4,
|
||||
"recommendedPlatforms": ["Instagram"],
|
||||
"recommendedPlatforms": ["Instagram", "TikTok"],
|
||||
"weeklyPlans": [
|
||||
{
|
||||
"weekNumber": 1,
|
||||
"mainThemes": ["Тема 1"],
|
||||
"mainThemes": ["Тема 1", "Тема 2", "Тема 3"],
|
||||
"contentRecommendations": "Что снимать и писать",
|
||||
"priorityPlatforms": ["Instagram"]
|
||||
}
|
||||
@@ -378,10 +406,19 @@ public class MarketingStrategyV3Service {
|
||||
"publishDate": "2024-01-15T10:00:00",
|
||||
"platform": "Instagram",
|
||||
"contentType": "фото",
|
||||
"theme": "Тема поста",
|
||||
"theme": "Тема первого поста",
|
||||
"postText": "Текст поста...",
|
||||
"hashtags": ["#тег"],
|
||||
"publishTime": "10:00"
|
||||
},
|
||||
{
|
||||
"publishDate": "2024-01-17T14:00:00",
|
||||
"platform": "TikTok",
|
||||
"contentType": "видео",
|
||||
"theme": "Тема второго поста",
|
||||
"postText": "Текст поста...",
|
||||
"hashtags": ["#тег"],
|
||||
"publishTime": "14:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -393,15 +430,10 @@ public class MarketingStrategyV3Service {
|
||||
);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ (ИЗ V1)
|
||||
// ==========================================
|
||||
|
||||
private String extractCleanJson(String response) {
|
||||
if (response == null || response.trim().isEmpty()) return null;
|
||||
String cleaned = response.trim();
|
||||
|
||||
// Надежное извлечение как в старом коде
|
||||
int startIdx = cleaned.indexOf("{");
|
||||
int endIdx = cleaned.lastIndexOf("}");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user