20 KiB
Документация для фронтенда: Генерация изображений для постов
Обзор изменений
В систему добавлена автоматическая генерация изображений для постов в социальных сетях. Теперь при создании маркетинговой стратегии для каждого поста автоматически генерируется уникальное изображение с помощью 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}
Ответ теперь включает поля изображений в каждом элементе календаря постов.
Пример ответа
{
"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} |
Пример запроса
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
- Content-Type:
-
Ошибка 401 Unauthorized:
- Токен отсутствует или невалиден
-
Ошибка 404 Not Found:
- Изображение не найдено на сервере
-
Ошибка 500 Internal Server Error:
- Внутренняя ошибка сервера при загрузке изображения
Кэширование
Сервер возвращает заголовок Cache-Control: public, max-age=3600, что позволяет браузеру кэшировать изображения на 1 час.
Рекомендации по отображению
1. Проверка наличия изображения
Всегда проверяйте наличие изображения перед отображением:
// Пример на 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) - Изображение не найдено на сервере
- Ошибка при загрузке изображения
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 для изображений:
<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:
.post-image {
width: 100%;
max-width: 512px;
height: auto;
border-radius: 8px;
object-fit: cover;
}
Примеры использования
React компонент для отображения поста
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 компонент
<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:
// config.ts
export const API_CONFIG = {
baseUrl: 'http://your-backend-url' // URL вашего бэкенда
// Например: 'http://localhost:8080' или 'https://api.example.com'
};
Миграция существующего кода
Если у вас уже есть компоненты для отображения постов, обновите их следующим образом:
-
Добавьте проверку наличия изображения:
const hasImage = post.imageUrl && post.imageFilename; -
Добавьте отображение изображения:
{ hasImage && <img src={`${API_BASE_URL}/api/marketing/analysis/images/${post.imageFilename}`} alt={post.theme} />; } -
Обновите типы/интерфейсы:
interface PostCalendarItem { // ... существующие поля imageUrl?: string | null; imageFilename?: string | null; }
Поддержка
При возникновении проблем:
- Проверьте, что
imageUrlиimageFilenameнеnull - Убедитесь, что используете правильный API эндпоинт:
/api/marketing/analysis/images/{imageFilename} - Проверьте, что JWT токен валиден и передается в заголовке
Authorization - Проверьте консоль браузера на наличие ошибок сети или авторизации
- Убедитесь, что используете правильный базовый URL API
Пример полного ответа API
{
"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