575 lines
19 KiB
Markdown
575 lines
19 KiB
Markdown
# Документация для фронтенда: Генерация изображений для постов
|
||
|
||
## Обзор изменений
|
||
|
||
В систему добавлена автоматическая генерация изображений для постов в социальных сетях. Теперь при создании маркетинговой стратегии для каждого поста автоматически генерируется уникальное изображение с помощью OpenAI DALL-E API. Изображения сохраняются в MinIO и прикрепляются к постам при публикации в Facebook.
|
||
|
||
---
|
||
|
||
## Новые поля в API
|
||
|
||
### 1. PostCalendarItem (Календарь постов)
|
||
|
||
В объекте `PostCalendarItem` добавлены два новых поля для работы с изображениями:
|
||
|
||
| Поле | Тип | Описание | Обязательное |
|
||
| --------------- | ------ | ------------------------------------------------------------ | ------------ |
|
||
| `imageUrl` | string | Имя файла изображения (используется для получения через API) | Нет |
|
||
| `imageFilename` | string | Имя файла изображения | Нет |
|
||
|
||
**Важно:**
|
||
|
||
- Поля могут быть `null`, если генерация изображения не удалась
|
||
- В этом случае пост публикуется без изображения
|
||
- Оба поля содержат одинаковое значение (имя файла)
|
||
- Для получения изображения используйте API эндпоинт `/api/marketing/analysis/images/{imageFilename}`
|
||
|
||
### 2. PostingTask (Задачи публикации)
|
||
|
||
В объекте `PostingTask` также добавлены поля для изображений:
|
||
|
||
| Поле | Тип | Описание | Обязательное |
|
||
| --------------- | ------ | ------------------------------------------------------------ | ------------ |
|
||
| `imageUrl` | string | Имя файла изображения (используется для получения через API) | Нет |
|
||
| `imageFilename` | string | Имя файла изображения | Нет |
|
||
|
||
---
|
||
|
||
## Изменения в 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`
|
||
|
||
Аналогично, ответ включает поля изображений в календаре постов.
|
||
|
||
---
|
||
|
||
## Получение изображений
|
||
|
||
### Через бэкенд API
|
||
|
||
Все изображения должны получаться через бэкенд API. Прямой доступ к MinIO с фронтенда не предусмотрен.
|
||
|
||
#### Эндпоинт для получения изображения
|
||
|
||
**GET** `/api/marketing/analysis/images/{imageFilename}`
|
||
|
||
Возвращает изображение поста в формате PNG.
|
||
|
||
#### Параметры пути
|
||
|
||
| Параметр | Тип | Описание |
|
||
| --------------- | ------ | --------------------- |
|
||
| `imageFilename` | string | Имя файла изображения |
|
||
|
||
#### Заголовки запроса
|
||
|
||
| Заголовок | Тип | Обязательный | Описание |
|
||
| --------------- | ------ | ------------ | ------------------------------------ |
|
||
| `Authorization` | string | Да | JWT токен в формате `Bearer {token}` |
|
||
|
||
#### Пример запроса
|
||
|
||
```javascript
|
||
const imageUrl = `/api/marketing/analysis/images/${post.imageFilename}`;
|
||
|
||
fetch(imageUrl, {
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
},
|
||
})
|
||
.then((response) => {
|
||
if (response.ok) {
|
||
return response.blob();
|
||
}
|
||
throw new Error('Failed to load image');
|
||
})
|
||
.then((blob) => {
|
||
const imageObjectUrl = URL.createObjectURL(blob);
|
||
// Используйте imageObjectUrl для отображения
|
||
});
|
||
```
|
||
|
||
#### Пример ответа
|
||
|
||
- **Успешный ответ (200 OK):**
|
||
|
||
- Content-Type: `image/png`
|
||
- Body: бинарные данные изображения PNG
|
||
|
||
- **Ошибка 401 Unauthorized:**
|
||
|
||
- Токен отсутствует или невалиден
|
||
|
||
- **Ошибка 404 Not Found:**
|
||
|
||
- Изображение не найдено на сервере
|
||
|
||
- **Ошибка 500 Internal Server Error:**
|
||
- Внутренняя ошибка сервера при загрузке изображения
|
||
|
||
#### Кэширование
|
||
|
||
Сервер возвращает заголовок `Cache-Control: public, max-age=3600`, что позволяет браузеру кэшировать изображения на 1 час.
|
||
|
||
---
|
||
|
||
## Рекомендации по отображению
|
||
|
||
### 1. Проверка наличия изображения
|
||
|
||
Всегда проверяйте наличие изображения перед отображением:
|
||
|
||
```javascript
|
||
// Пример на JavaScript/TypeScript
|
||
const PostCard = ({ post, apiBaseUrl, authToken }) => {
|
||
const hasImage = post.imageUrl && post.imageFilename;
|
||
const imageUrl = hasImage
|
||
? `${apiBaseUrl}/api/marketing/analysis/images/${post.imageFilename}`
|
||
: null;
|
||
|
||
return (
|
||
<div className='post-card'>
|
||
<h3>{post.theme}</h3>
|
||
<p>{post.postText}</p>
|
||
|
||
{imageUrl ? (
|
||
<img
|
||
src={imageUrl}
|
||
alt={post.theme}
|
||
onError={(e) => {
|
||
// Fallback если изображение не загрузилось
|
||
e.target.style.display = 'none';
|
||
}}
|
||
// Если требуется авторизация, используйте fetch с заголовками
|
||
/>
|
||
) : (
|
||
<div className='no-image-placeholder'>Изображение не сгенерировано</div>
|
||
)}
|
||
|
||
<div className='hashtags'>
|
||
{post.hashtags.map((tag) => (
|
||
<span key={tag}>#{tag}</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
### 2. Обработка ошибок загрузки
|
||
|
||
Всегда предусматривайте fallback для случаев, когда:
|
||
|
||
- Изображение не сгенерировано (`imageUrl` = `null`)
|
||
- Изображение не найдено на сервере
|
||
- Ошибка при загрузке изображения
|
||
|
||
```javascript
|
||
const [imageError, setImageError] = useState(false);
|
||
|
||
const getImageUrl = (imageFilename) => {
|
||
if (!imageFilename) return null;
|
||
return `${API_BASE_URL}/api/marketing/analysis/images/${imageFilename}`;
|
||
};
|
||
|
||
const handleImageError = () => {
|
||
setImageError(true);
|
||
};
|
||
|
||
return (
|
||
<>
|
||
{post.imageUrl && !imageError ? (
|
||
<img
|
||
src={getImageUrl(post.imageFilename)}
|
||
alt={post.theme}
|
||
onError={handleImageError}
|
||
/>
|
||
) : (
|
||
<div className='image-placeholder'>
|
||
<Icon name='image' />
|
||
<span>Изображение недоступно</span>
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
```
|
||
|
||
### 3. Оптимизация загрузки
|
||
|
||
Рекомендуется использовать lazy loading для изображений:
|
||
|
||
```javascript
|
||
<img
|
||
src={
|
||
post.imageUrl
|
||
? `${API_BASE_URL}/api/marketing/analysis/images/${post.imageFilename}`
|
||
: null
|
||
}
|
||
alt={post.theme}
|
||
loading='lazy'
|
||
className='post-image'
|
||
/>
|
||
```
|
||
|
||
### 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;
|
||
apiBaseUrl: string;
|
||
}
|
||
|
||
const PostCard: React.FC<PostCardProps> = ({ post, apiBaseUrl }) => {
|
||
const [imageError, setImageError] = useState(false);
|
||
|
||
const getImageUrl = (): string | null => {
|
||
if (!post.imageFilename) return null;
|
||
return `${apiBaseUrl}/api/marketing/analysis/images/${post.imageFilename}`;
|
||
};
|
||
|
||
const imageUrl = getImageUrl();
|
||
|
||
return (
|
||
<div className='post-card'>
|
||
<div className='post-header'>
|
||
<span className='platform-badge'>{post.platform}</span>
|
||
<span className='content-type'>{post.contentType}</span>
|
||
<span className='publish-time'>{post.publishTime}</span>
|
||
</div>
|
||
|
||
<h3 className='post-theme'>{post.theme}</h3>
|
||
|
||
{imageUrl && !imageError ? (
|
||
<div className='post-image-container'>
|
||
<img
|
||
src={imageUrl}
|
||
alt={post.theme}
|
||
className='post-image'
|
||
loading='lazy'
|
||
onError={() => setImageError(true)}
|
||
/>
|
||
</div>
|
||
) : (
|
||
<div className='no-image-placeholder'>
|
||
<svg width='64' height='64' viewBox='0 0 24 24' fill='none'>
|
||
<path
|
||
d='M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z'
|
||
fill='currentColor'
|
||
/>
|
||
</svg>
|
||
<p>Изображение не сгенерировано</p>
|
||
</div>
|
||
)}
|
||
|
||
<p className='post-text'>{post.postText}</p>
|
||
|
||
<div className='post-hashtags'>
|
||
{post.hashtags.map((tag, index) => (
|
||
<span key={index} className='hashtag'>
|
||
{tag.startsWith('#') ? tag : `#${tag}`}
|
||
</span>
|
||
))}
|
||
</div>
|
||
|
||
<div className='post-footer'>
|
||
<span className='publish-date'>
|
||
{new Date(post.publishDate).toLocaleDateString('ru-RU')}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default PostCard;
|
||
```
|
||
|
||
### Vue компонент
|
||
|
||
```vue
|
||
<template>
|
||
<div class="post-card">
|
||
<div class="post-header">
|
||
<span class="platform-badge">{{ post.platform }}</span>
|
||
<span class="content-type">{{ post.contentType }}</span>
|
||
<span class="publish-time">{{ post.publishTime }}</span>
|
||
</div>
|
||
|
||
<h3 class="post-theme">{{ post.theme }}</h3>
|
||
|
||
<div v-if="imageUrl && !imageError" class="post-image-container">
|
||
<img
|
||
:src="imageUrl"
|
||
:alt="post.theme"
|
||
class="post-image"
|
||
loading="lazy"
|
||
@error="imageError = true"
|
||
/>
|
||
</div>
|
||
<div v-else class="no-image-placeholder">
|
||
<Icon name="image" />
|
||
<p>Изображение не сгенерировано</p>
|
||
</div>
|
||
|
||
<p class="post-text">{{ post.postText }}</p>
|
||
|
||
<div class="post-hashtags">
|
||
<span v-for="(tag, index) in post.hashtags" :key="index" class="hashtag">
|
||
{{ tag.startsWith('#') ? tag : `#${tag}` }}
|
||
</span>
|
||
</div>
|
||
|
||
<div class="post-footer">
|
||
<span class="publish-date">
|
||
{{ formatDate(post.publishDate) }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref } from 'vue';
|
||
|
||
interface PostCalendarItem {
|
||
publishDate: string;
|
||
platform: string;
|
||
contentType: string;
|
||
theme: string;
|
||
postText: string;
|
||
hashtags: string[];
|
||
publishTime: string;
|
||
imageUrl?: string | null;
|
||
imageFilename?: string | null;
|
||
}
|
||
|
||
const props = defineProps<{
|
||
post: PostCalendarItem;
|
||
apiBaseUrl: string;
|
||
}>();
|
||
|
||
const imageError = ref(false);
|
||
|
||
const imageUrl = computed(() => {
|
||
if (!props.post.imageFilename) return null;
|
||
return `${props.apiBaseUrl}/api/marketing/analysis/images/${props.post.imageFilename}`;
|
||
});
|
||
|
||
const formatDate = (dateString: string) => {
|
||
return new Date(dateString).toLocaleDateString('ru-RU');
|
||
};
|
||
</script>
|
||
```
|
||
|
||
---
|
||
|
||
## Важные замечания
|
||
|
||
### 1. Генерация изображений
|
||
|
||
- Изображения генерируются **автоматически** при создании стратегии
|
||
- Процесс генерации может занять время (обычно 10-30 секунд на изображение)
|
||
- Если генерация не удалась, пост все равно будет создан, но без изображения
|
||
|
||
### 2. Хранение изображений
|
||
|
||
- Все изображения хранятся на бэкенде
|
||
- Формат изображений: **PNG**
|
||
- Размер изображений: **1024x1024 пикселей**
|
||
- Имя файла уникально для каждого поста
|
||
- Доступ к изображениям только через API эндпоинт
|
||
|
||
### 3. Публикация постов
|
||
|
||
- При публикации поста в Facebook изображение автоматически прикрепляется
|
||
- Если изображение отсутствует, пост публикуется только с текстом
|
||
- Это не влияет на успешность публикации
|
||
|
||
### 4. Обратная совместимость
|
||
|
||
- Старые стратегии, созданные до добавления этой функции, не будут иметь изображений
|
||
- Поля `imageUrl` и `imageFilename` будут `null` для таких постов
|
||
- Фронтенд должен корректно обрабатывать `null` значения
|
||
|
||
---
|
||
|
||
## Конфигурация
|
||
|
||
Для работы с изображениями вам понадобится базовый URL вашего API:
|
||
|
||
```typescript
|
||
// config.ts
|
||
export const API_CONFIG = {
|
||
baseUrl: 'http://your-backend-url', // URL вашего бэкенда
|
||
// Например: 'http://localhost:8080' или 'https://api.example.com'
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## Миграция существующего кода
|
||
|
||
Если у вас уже есть компоненты для отображения постов, обновите их следующим образом:
|
||
|
||
1. **Добавьте проверку наличия изображения:**
|
||
|
||
```typescript
|
||
const hasImage = post.imageUrl && post.imageFilename;
|
||
```
|
||
|
||
2. **Добавьте отображение изображения:**
|
||
|
||
```jsx
|
||
{
|
||
hasImage && (
|
||
<img
|
||
src={`${API_BASE_URL}/api/marketing/analysis/images/${post.imageFilename}`}
|
||
alt={post.theme}
|
||
/>
|
||
);
|
||
}
|
||
```
|
||
|
||
3. **Обновите типы/интерфейсы:**
|
||
```typescript
|
||
interface PostCalendarItem {
|
||
// ... существующие поля
|
||
imageUrl?: string | null;
|
||
imageFilename?: string | null;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Поддержка
|
||
|
||
При возникновении проблем:
|
||
|
||
1. Проверьте, что `imageUrl` и `imageFilename` не `null`
|
||
2. Убедитесь, что используете правильный API эндпоинт: `/api/marketing/analysis/images/{imageFilename}`
|
||
3. Проверьте, что JWT токен валиден и передается в заголовке `Authorization`
|
||
4. Проверьте консоль браузера на наличие ошибок сети или авторизации
|
||
5. Убедитесь, что используете правильный базовый URL API
|
||
|
||
---
|
||
|
||
## Пример полного ответа 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
|