diff --git a/src/main/java/kz/konturai/parser/controller/MarketingAnalysisV3Controller.java b/src/main/java/kz/konturai/parser/controller/MarketingAnalysisV3Controller.java index 463dba5..a0d6206 100644 --- a/src/main/java/kz/konturai/parser/controller/MarketingAnalysisV3Controller.java +++ b/src/main/java/kz/konturai/parser/controller/MarketingAnalysisV3Controller.java @@ -5,6 +5,7 @@ import kz.konturai.parser.dto.ApiResponse; import kz.konturai.parser.dto.ErrorResponse; import kz.konturai.parser.dto.MarketingAnalysisV3Request; import kz.konturai.parser.dto.MarketingStrategyRequest; +import kz.konturai.parser.dto.StrategyHistoryResponse; import kz.konturai.parser.enums.StrategyModel; import kz.konturai.parser.model.MarketingAnalysisV3Document; import kz.konturai.parser.model.MarketingStrategy; @@ -26,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; @RestController @RequestMapping("/api/marketing/v3") @@ -50,10 +52,6 @@ public class MarketingAnalysisV3Controller { } } - // ========================================== - // БЛОК 1: АНАЛИЗ (ОПРОСНИК И ПАРСИНГ) - // ========================================== - @PostMapping("/start") public ResponseEntity startAnalysis( @RequestHeader(value = "Authorization", required = false) String authHeader, @@ -113,10 +111,6 @@ public class MarketingAnalysisV3Controller { } } - // ========================================== - // БЛОК 2: СТРАТЕГИЯ (СКОРИНГ, ЛОГОТИПЫ, МЕДИА) - // ========================================== - @GetMapping("/{analysisId}/strategy-preview") public ResponseEntity previewStrategy( @RequestHeader(value = "Authorization", required = false) String authHeader, @@ -145,7 +139,6 @@ public class MarketingAnalysisV3Controller { } } - @PostMapping(value = "/{analysisId}/strategy/generate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity generateStrategy( @RequestHeader(value = "Authorization", required = false) String authHeader, @@ -170,7 +163,6 @@ public class MarketingAnalysisV3Controller { log.info("Логотип клиента успешно загружен в MinIO: {}", logoFilename); } - // Защита от NullPointerException, если фронтенд вообще не прислал объект request if (request == null) { request = new MarketingStrategyRequest(); } @@ -212,9 +204,39 @@ public class MarketingAnalysisV3Controller { } } - // ========================================== - // БЛОК 3: ОБРАБОТКА ОШИБОК И ОТВЕТЫ - // ========================================== + @GetMapping("/strategy/my") + public ResponseEntity getMyStrategies( + @RequestHeader(value = "Authorization", required = false) String authHeader + ) { + String userId = extractUserIdFromHeader(authHeader); + if (userId == null) return unauthorizedResponse(); + + try { + List strategies = strategyService.getUserStrategies(userId); + List responseList = strategies.stream() + .map(this::convertToStrategyHistoryResponse) + .collect(Collectors.toList()); + + return ResponseEntity.ok(ApiResponse.success(responseList)); + } catch (Exception e) { + log.error("Error fetching user strategies", e); + return internalErrorResponse(e); + } + } + + private StrategyHistoryResponse convertToStrategyHistoryResponse(MarketingStrategy strategy) { + StrategyHistoryResponse response = new StrategyHistoryResponse(); + response.setStrategyId(strategy.getId()); + response.setAnalysisId(strategy.getAnalysisId()); + response.setStatus(strategy.getStatus()); + response.setUserId(strategy.getUserId()); + response.setDurationWeeks(strategy.getDurationWeeks()); + response.setPriorityPlatforms(strategy.getPriorityPlatforms()); + response.setCreatedAt(strategy.getCreatedAt()); + response.setCompletedAt(strategy.getCompletedAt()); + response.setStatusHistory(strategy.getStatusHistory()); + return response; + } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity> handleValidationException(MethodArgumentNotValidException ex) { diff --git a/src/main/java/kz/konturai/parser/dto/StrategyHistoryResponse.java b/src/main/java/kz/konturai/parser/dto/StrategyHistoryResponse.java index e94c34b..c8c2f57 100644 --- a/src/main/java/kz/konturai/parser/dto/StrategyHistoryResponse.java +++ b/src/main/java/kz/konturai/parser/dto/StrategyHistoryResponse.java @@ -1,8 +1,17 @@ package kz.konturai.parser.dto; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + import java.time.LocalDateTime; import java.util.List; +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor public class StrategyHistoryResponse { private String strategyId; private String analysisId; @@ -13,80 +22,5 @@ public class StrategyHistoryResponse { private LocalDateTime createdAt; private LocalDateTime completedAt; private List statusHistory; - - public StrategyHistoryResponse() { - } - - public String getStrategyId() { - return strategyId; - } - - public void setStrategyId(String strategyId) { - this.strategyId = strategyId; - } - - public String getAnalysisId() { - return analysisId; - } - - public void setAnalysisId(String analysisId) { - this.analysisId = analysisId; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public Integer getDurationWeeks() { - return durationWeeks; - } - - public void setDurationWeeks(Integer durationWeeks) { - this.durationWeeks = durationWeeks; - } - - public List getPriorityPlatforms() { - return priorityPlatforms; - } - - public void setPriorityPlatforms(List priorityPlatforms) { - this.priorityPlatforms = priorityPlatforms; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getCompletedAt() { - return completedAt; - } - - public void setCompletedAt(LocalDateTime completedAt) { - this.completedAt = completedAt; - } - - public List getStatusHistory() { - return statusHistory; - } - - public void setStatusHistory(List statusHistory) { - this.statusHistory = statusHistory; - } } diff --git a/src/main/java/kz/konturai/parser/service/MarketingStrategyV3Service.java b/src/main/java/kz/konturai/parser/service/MarketingStrategyV3Service.java index 9a8b7e2..f40c47e 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingStrategyV3Service.java +++ b/src/main/java/kz/konturai/parser/service/MarketingStrategyV3Service.java @@ -51,13 +51,17 @@ public class MarketingStrategyV3Service { return repository.findById(id); } + public List getUserStrategies(String userId) { + return repository.findByUserIdOrderByCreatedAtDesc(userId); + } + public MarketingStrategy generateStrategy(String analysisId, MarketingStrategyRequest request, String userId, String uploadedLogoFilename) { MarketingAnalysisV3Document analysisDoc = analysisRepository.findById(analysisId) .orElseThrow(() -> new IllegalArgumentException("Анализ V3 не найден")); MarketingStrategy strategy = new MarketingStrategy(analysisId); strategy.setUserId(userId); - strategy.setDurationWeeks(4); // Дефолт, ИИ его перезапишет + strategy.setDurationWeeks(4); strategy.setPriorityPlatforms(new ArrayList<>()); strategy.setStatus("queued"); @@ -82,11 +86,9 @@ public class MarketingStrategyV3Service { addStatusHistoryEntry(strategy, "processing", "Расчет баллов, генерация постов (минимум 3 в неделю)..."); repository.save(strategy); - // 1. Считаем лучшую модель StrategyModel bestModel = calculateBestScoringModel(analysis.getRequestData()); log.info("[Strategy ID: {}] Выбрана модель: {}", strategyId, bestModel.name()); - // 2. Генерируем JSON String systemPrompt = buildSystemPrompt(bestModel); String userPrompt = buildUserPrompt(analysis, bestModel); @@ -97,18 +99,15 @@ public class MarketingStrategyV3Service { throw new IllegalStateException("AI не вернул валидный JSON"); } - // 3. Парсим ответ Map strategyData = objectMapper.readValue(jsonResponse, new TypeReference<>() {}); if (strategy.getStrategyData() != null) { strategy.getStrategyData().forEach(strategyData::putIfAbsent); } - // 4. Заполняем сущность populateStrategyEntity(strategy, strategyData); repository.save(strategy); - // 5. Генерируем медиа НАДЕЖНЫМ способом с FALLBACK addStatusHistoryEntry(strategy, "processing", "Создание фото и видео креативов. Это займет несколько минут..."); repository.save(strategy); @@ -125,7 +124,6 @@ public class MarketingStrategyV3Service { } } - // Тот самый надежный калькулятор public StrategyModel calculateBestScoringModel(kz.konturai.parser.dto.MarketingAnalysisV3Request req) { int entry = 0, authority = 0, trust = 0, conversion = 0; @@ -210,7 +208,7 @@ public class MarketingStrategyV3Service { try { item.setPublishDate(LocalDateTime.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME)); } catch (Exception e) { - item.setPublishDate(startDate.plusDays(postCalendar.size() * 2L)); // Распределяем по дням + item.setPublishDate(startDate.plusDays(postCalendar.size() * 2L)); } } else { item.setPublishDate(startDate.plusDays(postCalendar.size() * 2L)); @@ -229,10 +227,6 @@ public class MarketingStrategyV3Service { strategy.setPostCalendar(postCalendar); } - // ========================================== - // ЖЕЛЕЗОБЕТОННАЯ ГЕНЕРАЦИЯ МЕДИА С FALLBACK - // ========================================== - private void generateMediaAssets(MarketingStrategy strategy, MarketingAnalysisV3Document analysis) { String businessContext = getBusinessContext(analysis); String brandName = analysis.getRequestData().getProductName(); @@ -255,7 +249,6 @@ public class MarketingStrategyV3Service { try { if (contentType.contains("видео") || contentType.contains("reels") || contentType.contains("tiktok")) { - // ПРОБУЕМ СГЕНЕРИРОВАТЬ ВИДЕО String videoPrompt = "High quality cinematic commercial video. Business niche: " + businessContext + ". Scene: " + item.getTheme() + ". Photorealistic, dynamic motion, 4k."; log.info("Попытка генерации ВИДЕО: {}", videoPrompt); @@ -273,13 +266,11 @@ public class MarketingStrategyV3Service { item.setVideoFilename(filename); log.info("Видео успешно сгенерировано: {}", filename); } else { - // ФОЛБЭК: ВИДЕО НЕ ПОЛУЧИЛОСЬ -> ДЕЛАЕМ ФОТО log.warn("Видео не сгенерировалось (вернулся null). Включаю FALLBACK -> генерирую ФОТО для темы: {}", item.getTheme()); - item.setContentType("фото"); // Меняем тип поста, чтобы фронт отобразил картинку + item.setContentType("фото"); doGenerateImage(item, businessContext, brandName, clientLogoBytes); } } else { - // ГЕНЕРИРУЕМ ФОТО doGenerateImage(item, businessContext, brandName, clientLogoBytes); } @@ -291,7 +282,6 @@ public class MarketingStrategyV3Service { } } - // Выделенный надежный метод для фото private void doGenerateImage(MarketingStrategy.PostCalendarItem item, String businessContext, String brandName, byte[] clientLogoBytes) { String imagePrompt = buildImagePrompt(item, businessContext, brandName); log.info("Генерация ФОТО: {}", imagePrompt); @@ -315,7 +305,6 @@ public class MarketingStrategyV3Service { return analysis.getRequestData().getBusinessNiche() + ", product: " + analysis.getRequestData().getProductName(); } - // ПРОМПТ ДЛЯ ФОТО (НА АНГЛИЙСКОМ ДЛЯ ЛУЧШЕГО КАЧЕСТВА) private String buildImagePrompt(MarketingStrategy.PostCalendarItem item, String businessContext, String brandName) { StringBuilder prompt = new StringBuilder(); @@ -340,7 +329,6 @@ public class MarketingStrategyV3Service { } if (item.getTheme() != null && !item.getTheme().isEmpty()) { - // Переводим тему на английский контекст (ИИ поймет) prompt.append("Main subject/action: ").append(item.getTheme()).append(". "); } @@ -350,10 +338,6 @@ public class MarketingStrategyV3Service { return prompt.toString(); } - // ========================================== - // ПРОМПТЫ ДЛЯ ИИ (СТРОГИЕ ПРАВИЛА КОЛИЧЕСТВА) - // ========================================== - private String buildSystemPrompt(StrategyModel bestModel) { return """ РОЛЬ: Ты — топовый Голливудский Креативный Директор и Chief Media Planner.