diff --git a/IMAGE_GENERATION_FRONTEND.md b/IMAGE_GENERATION_FRONTEND.md new file mode 100644 index 0000000..1c3dd51 --- /dev/null +++ b/IMAGE_GENERATION_FRONTEND.md @@ -0,0 +1,524 @@ +# Документация для фронтенда: Генерация изображений для постов + +## Обзор изменений + +В систему добавлена автоматическая генерация изображений для постов в социальных сетях. Теперь при создании маркетинговой стратегии для каждого поста автоматически генерируется уникальное изображение с помощью OpenAI DALL-E API. Изображения сохраняются в MinIO и прикрепляются к постам при публикации в Facebook. + +--- + +## Новые поля в API + +### 1. PostCalendarItem (Календарь постов) + +В объекте `PostCalendarItem` добавлены два новых поля для работы с изображениями: + +| Поле | Тип | Описание | Обязательное | +| --------------- | ------ | ------------------------------------------------------------------- | ------------ | +| `imageUrl` | string | Путь к изображению в MinIO (используется для получения изображения) | Нет | +| `imageFilename` | string | Имя файла изображения в MinIO | Нет | + +**Важно:** + +- Поля могут быть `null`, если генерация изображения не удалась +- В этом случае пост публикуется без изображения +- Оба поля содержат одинаковое значение (имя файла в MinIO) + +### 2. PostingTask (Задачи публикации) + +В объекте `PostingTask` также добавлены поля для изображений: + +| Поле | Тип | Описание | Обязательное | +| --------------- | ------ | ----------------------------- | ------------ | +| `imageUrl` | string | Путь к изображению в MinIO | Нет | +| `imageFilename` | string | Имя файла изображения в MinIO | Нет | + +--- + +## Изменения в API эндпоинтах + +### GET `/api/marketing/strategy/{strategyId}` + +Ответ теперь включает поля изображений в каждом элементе календаря постов. + +#### Пример ответа + +```json +{ + "success": true, + "data": { + "strategyId": "507f1f77bcf86cd799439011", + "analysisId": "507f1f77bcf86cd799439012", + "status": "completed", + "strategy": { + "postCalendar": [ + { + "publishDate": "2024-01-15T10:00:00", + "platform": "Facebook", + "contentType": "пост", + "theme": "Презентация нового продукта", + "postText": "Мы рады представить наш новый продукт...", + "hashtags": ["#новинка", "#продукт", "#маркетинг"], + "publishTime": "10:00", + "imageUrl": "post_image_1705312800000_1234567890.png", + "imageFilename": "post_image_1705312800000_1234567890.png" + } + ] + } + } +} +``` + +### GET `/api/marketing/analysis/{analysisId}/strategy` + +Аналогично, ответ включает поля изображений в календаре постов. + +--- + +## Получение изображений + +### Способ 1: Через MinIO API (рекомендуется) + +Если у вас есть доступ к MinIO API, используйте `imageUrl` или `imageFilename` для получения изображения: + +``` +GET {minio-endpoint}/{bucket-name}/{imageFilename} +``` + +Где: + +- `minio-endpoint` - адрес вашего MinIO сервера (например, `http://92.38.48.166:9000`) +- `bucket-name` - имя bucket (обычно `konturai`) +- `imageFilename` - значение из поля `imageFilename` или `imageUrl` + +### Способ 2: Через бэкенд API (если реализован) + +Если бэкенд предоставляет эндпоинт для получения изображений, используйте его: + +``` +GET /api/marketing/images/{imageFilename} +``` + +**Примечание:** На данный момент такой эндпоинт не реализован. При необходимости его можно добавить. + +--- + +## Рекомендации по отображению + +### 1. Проверка наличия изображения + +Всегда проверяйте наличие изображения перед отображением: + +```javascript +// Пример на JavaScript/TypeScript +const PostCard = ({ post }) => { + const hasImage = post.imageUrl && post.imageFilename; + + return ( +
+

{post.theme}

+

{post.postText}

+ + {hasImage ? ( + {post.theme} { + // Fallback если изображение не загрузилось + e.target.style.display = 'none'; + }} + /> + ) : ( +
Изображение не сгенерировано
+ )} + +
+ {post.hashtags.map((tag) => ( + #{tag} + ))} +
+
+ ); +}; +``` + +### 2. Обработка ошибок загрузки + +Всегда предусматривайте fallback для случаев, когда: + +- Изображение не сгенерировано (`imageUrl` = `null`) +- Изображение не найдено в MinIO +- Ошибка при загрузке изображения + +```javascript +const [imageError, setImageError] = useState(false); + +const handleImageError = () => { + setImageError(true); +}; + +return ( + <> + {post.imageUrl && !imageError ? ( + {post.theme} + ) : ( +
+ + Изображение недоступно +
+ )} + +); +``` + +### 3. Оптимизация загрузки + +Рекомендуется использовать lazy loading для изображений: + +```javascript +{post.theme} +``` + +### 4. Размеры изображений + +Изображения генерируются в размере **1024x1024 пикселей** (квадратные). При отображении учитывайте это при настройке CSS: + +```css +.post-image { + width: 100%; + max-width: 512px; + height: auto; + border-radius: 8px; + object-fit: cover; +} +``` + +--- + +## Примеры использования + +### React компонент для отображения поста + +```typescript +import React, { useState } from 'react'; + +interface PostCalendarItem { + publishDate: string; + platform: string; + contentType: string; + theme: string; + postText: string; + hashtags: string[]; + publishTime: string; + imageUrl?: string | null; + imageFilename?: string | null; +} + +interface PostCardProps { + post: PostCalendarItem; + minioEndpoint: string; + bucketName: string; +} + +const PostCard: React.FC = ({ + post, + minioEndpoint, + bucketName, +}) => { + const [imageError, setImageError] = useState(false); + + const getImageUrl = (): string | null => { + if (!post.imageFilename) return null; + return `${minioEndpoint}/${bucketName}/${post.imageFilename}`; + }; + + const imageUrl = getImageUrl(); + + return ( +
+
+ {post.platform} + {post.contentType} + {post.publishTime} +
+ +

{post.theme}

+ + {imageUrl && !imageError ? ( +
+ {post.theme} setImageError(true)} + /> +
+ ) : ( +
+ + + +

Изображение не сгенерировано

+
+ )} + +

{post.postText}

+ +
+ {post.hashtags.map((tag, index) => ( + + {tag.startsWith('#') ? tag : `#${tag}`} + + ))} +
+ +
+ + {new Date(post.publishDate).toLocaleDateString('ru-RU')} + +
+
+ ); +}; + +export default PostCard; +``` + +### Vue компонент + +```vue + + + +``` + +--- + +## Важные замечания + +### 1. Генерация изображений + +- Изображения генерируются **автоматически** при создании стратегии +- Процесс генерации может занять время (обычно 10-30 секунд на изображение) +- Если генерация не удалась, пост все равно будет создан, но без изображения + +### 2. Хранение изображений + +- Все изображения хранятся в MinIO +- Формат изображений: **PNG** +- Размер изображений: **1024x1024 пикселей** +- Имя файла уникально для каждого поста + +### 3. Публикация постов + +- При публикации поста в Facebook изображение автоматически прикрепляется +- Если изображение отсутствует, пост публикуется только с текстом +- Это не влияет на успешность публикации + +### 4. Обратная совместимость + +- Старые стратегии, созданные до добавления этой функции, не будут иметь изображений +- Поля `imageUrl` и `imageFilename` будут `null` для таких постов +- Фронтенд должен корректно обрабатывать `null` значения + +--- + +## Конфигурация + +Для работы с изображениями вам понадобятся следующие настройки: + +```typescript +// config.ts +export const MINIO_CONFIG = { + endpoint: 'http://92.38.48.166:9000', // или ваш MinIO endpoint + bucketName: 'konturai', + // Если требуется авторизация: + // accessKey: 'your-access-key', + // secretKey: 'your-secret-key', +}; +``` + +--- + +## Миграция существующего кода + +Если у вас уже есть компоненты для отображения постов, обновите их следующим образом: + +1. **Добавьте проверку наличия изображения:** + + ```typescript + const hasImage = post.imageUrl && post.imageFilename; + ``` + +2. **Добавьте отображение изображения:** + + ```jsx + { + hasImage && {post.theme}; + } + ``` + +3. **Обновите типы/интерфейсы:** + ```typescript + interface PostCalendarItem { + // ... существующие поля + imageUrl?: string | null; + imageFilename?: string | null; + } + ``` + +--- + +## Поддержка + +При возникновении проблем: + +1. Проверьте, что `imageUrl` и `imageFilename` не `null` +2. Убедитесь, что MinIO доступен и изображение существует +3. Проверьте консоль браузера на наличие ошибок CORS (если обращаетесь к MinIO напрямую) +4. Убедитесь, что используете правильный endpoint и bucket name + +--- + +## Пример полного ответа API + +```json +{ + "success": true, + "data": { + "strategyId": "507f1f77bcf86cd799439011", + "analysisId": "507f1f77bcf86cd799439012", + "status": "completed", + "createdAt": "2024-01-15T10:00:00", + "completedAt": "2024-01-15T10:05:00", + "durationWeeks": 4, + "priorityPlatforms": ["Facebook", "Instagram"], + "strategy": { + "weeklyPlans": [ + { + "weekNumber": 1, + "mainThemes": ["Презентация продукта", "Преимущества"], + "contentRecommendations": "Создавайте контент...", + "priorityPlatforms": ["Facebook"] + } + ], + "postCalendar": [ + { + "publishDate": "2024-01-16T10:00:00", + "platform": "Facebook", + "contentType": "пост", + "theme": "Презентация нового продукта", + "postText": "Мы рады представить наш новый продукт, который поможет вам...", + "hashtags": ["#новинка", "#продукт", "#маркетинг"], + "publishTime": "10:00", + "imageUrl": "post_image_1705312800000_1234567890.png", + "imageFilename": "post_image_1705312800000_1234567890.png" + }, + { + "publishDate": "2024-01-18T14:00:00", + "platform": "Instagram", + "contentType": "сторис", + "theme": "Преимущества продукта", + "postText": "Узнайте о главных преимуществах нашего продукта...", + "hashtags": ["#преимущества", "#качество"], + "publishTime": "14:00", + "imageUrl": null, + "imageFilename": null + } + ] + } + } +} +``` + +Обратите внимание, что второй пост не имеет изображения (`imageUrl` и `imageFilename` равны `null`). Это нормальная ситуация, если генерация изображения не удалась. + +--- + +**Дата обновления:** 2024-01-15 +**Версия API:** 1.0 diff --git a/marketing-strategy-api-frontend.md b/marketing-strategy-api-frontend.md index b16fbe7..0ce29d0 100644 --- a/marketing-strategy-api-frontend.md +++ b/marketing-strategy-api-frontend.md @@ -343,6 +343,8 @@ GET /api/marketing/strategy/507f1f77bcf86cd799439012 | `data.strategy.postCalendar[].postText` | string | Полный текст поста (готовый к публикации) | | `data.strategy.postCalendar[].hashtags` | array | Список хештегов (массив строк) | | `data.strategy.postCalendar[].publishTime` | string | Время публикации в формате HH:mm | +| `data.strategy.postCalendar[].imageUrl` | string | Путь к изображению в MinIO (может быть null) | +| `data.strategy.postCalendar[].imageFilename` | string | Имя файла изображения в MinIO (может быть null) | #### Пример ошибки (404 Not Found) diff --git a/src/main/java/kz/konturai/parser/service/OpenAIImageGenerationService.java b/src/main/java/kz/konturai/parser/service/OpenAIImageGenerationService.java index 36fd679..07d912d 100644 --- a/src/main/java/kz/konturai/parser/service/OpenAIImageGenerationService.java +++ b/src/main/java/kz/konturai/parser/service/OpenAIImageGenerationService.java @@ -8,6 +8,7 @@ import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; +import org.springframework.web.reactive.function.client.ExchangeStrategies; import reactor.core.publisher.Mono; import reactor.netty.http.client.HttpClient; import org.springframework.http.client.reactive.ReactorClientHttpConnector; @@ -58,9 +59,16 @@ public class OpenAIImageGenerationService { HttpClient httpClient = HttpClient.create() .responseTimeout(Duration.ofMillis(90000)); + // Configure exchange strategies to increase buffer limit for large base64 + // responses + ExchangeStrategies strategies = ExchangeStrategies.builder() + .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024)) // 10 MB + .build(); + this.webClient = WebClient.builder() .baseUrl(DALL_E_API_URL) .clientConnector(new ReactorClientHttpConnector(httpClient)) + .exchangeStrategies(strategies) .build(); } @@ -101,7 +109,8 @@ public class OpenAIImageGenerationService { requestBody.put("quality", imageQuality); requestBody.put("response_format", "b64_json"); // Получаем изображение в base64 - logger.info("Requesting image generation from DALL-E with prompt: {}", prompt.substring(0, Math.min(100, prompt.length()))); + logger.info("Requesting image generation from DALL-E with prompt: {}", + prompt.substring(0, Math.min(100, prompt.length()))); Map response = webClient.post() .header("Authorization", "Bearer " + apiKey) @@ -109,7 +118,8 @@ public class OpenAIImageGenerationService { .accept(MediaType.APPLICATION_JSON) .bodyValue(requestBody) .retrieve() - .bodyToMono(new ParameterizedTypeReference>() {}) + .bodyToMono(new ParameterizedTypeReference>() { + }) .retryWhen(createRetrySpec("generateImage")) .onErrorResume(err -> { logger.error("DALL-E API request failed after retries: {} - {}", err.getMessage(), @@ -117,7 +127,8 @@ public class OpenAIImageGenerationService { if (err instanceof WebClientResponseException) { WebClientResponseException wcre = (WebClientResponseException) err; if (wcre.getStatusCode().value() == 401) { - logger.error("OpenAI API key is invalid or expired. Please check your openai.api.key configuration."); + logger.error( + "OpenAI API key is invalid or expired. Please check your openai.api.key configuration."); } else if (wcre.getStatusCode().value() == 429) { logger.error("OpenAI API rate limit exceeded after all retry attempts."); } else if (wcre.getStatusCode().value() >= 500) { @@ -210,4 +221,3 @@ public class OpenAIImageGenerationService { }); } } -