feat: AI Press Secretary - content generation and publishing platform
Features: - AI-powered post generation from brand profiles - Image generation with templates - Content planning - Telegram publishing - Social profile analyzer - Community organization support - Markdown to HTML conversion for Telegram - Inline AI chat for post editing Deploy: - Add deployment scripts for Linux servers - Add PowerShell deploy script for Windows - Add Nginx and SSL setup scripts - Add backup and restore scripts
This commit is contained in:
1 parent
81625b2e6f
commit
c6048aeea0
37 files changed
+7037
-225
No files matched your search
+599
-16
@@ -6,20 +6,45 @@ from config import PIAPI_API_KEY, PIAPI_MODEL, get_proxies
|
||||
|
||||
PIAPI_URL = "https://api.piapi.ai/v1/chat/completions"
|
||||
|
||||
|
||||
class ContentGenerator:
|
||||
def __init__(self):
|
||||
"""Инициализация генератора контента"""
|
||||
def __init__(self, provider_settings=None):
|
||||
"""Инициализация генератора контента.
|
||||
provider_settings — опциональный dict с ключами:
|
||||
provider_type, api_key, base_url, text_model
|
||||
Если не передан — используются глобальные настройки PiAPI из config."""
|
||||
print("🔄 Инициализация ContentGenerator...")
|
||||
|
||||
# Проверяем, есть ли ключ PiAPI
|
||||
if PIAPI_API_KEY:
|
||||
if provider_settings and provider_settings.get("api_key"):
|
||||
# Per-user настройки
|
||||
self.api_key = provider_settings["api_key"]
|
||||
self.base_url = provider_settings.get("base_url") or PIAPI_URL.replace("/chat/completions", "")
|
||||
self.model = provider_settings.get("text_model") or PIAPI_MODEL
|
||||
self.use_piapi = True
|
||||
self.chat_url = self.base_url.rstrip("/") + "/chat/completions"
|
||||
self.provider_type = provider_settings.get("provider_type", "custom")
|
||||
# Прокси используется для всех провайдеров (если настроен в .env)
|
||||
# Это нужно для доступа из России к зарубежным API
|
||||
self.proxies = get_proxies()
|
||||
print(f"✅ Провайдер: {self.provider_type} | модель: {self.model} | прокси: {'да' if self.proxies else 'нет'}")
|
||||
elif PIAPI_API_KEY:
|
||||
# Глобальные настройки из config
|
||||
self.api_key = PIAPI_API_KEY
|
||||
self.base_url = PIAPI_URL.replace("/chat/completions", "")
|
||||
self.model = PIAPI_MODEL
|
||||
self.use_piapi = True
|
||||
print("✅ PiAPI подключен!")
|
||||
self.chat_url = PIAPI_URL
|
||||
self.provider_type = "piapi"
|
||||
self.proxies = get_proxies()
|
||||
print("✅ PiAPI подключен (глобальные настройки)!")
|
||||
else:
|
||||
print("⚠️ PiAPI ключ не найден, использую шаблоны")
|
||||
print("⚠️ API ключ не найден, использую шаблоны")
|
||||
self.use_piapi = False
|
||||
|
||||
self.api_key = None
|
||||
self.chat_url = PIAPI_URL
|
||||
self.model = PIAPI_MODEL
|
||||
self.provider_type = "piapi"
|
||||
self.proxies = get_proxies()
|
||||
# Шаблоны для запасного варианта (если PiAPI не работает)
|
||||
self.templates = {
|
||||
'instagram': [
|
||||
@@ -159,10 +184,10 @@ class ContentGenerator:
|
||||
"""
|
||||
|
||||
response = requests.post(
|
||||
PIAPI_URL,
|
||||
self.chat_url,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {PIAPI_API_KEY}"
|
||||
"Authorization": f"Bearer {self.api_key}"
|
||||
},
|
||||
json={
|
||||
"model": self.model,
|
||||
@@ -173,7 +198,7 @@ class ContentGenerator:
|
||||
"temperature": 0.9,
|
||||
"max_tokens": 500
|
||||
},
|
||||
proxies=get_proxies(),
|
||||
proxies=self.proxies,
|
||||
timeout=30
|
||||
)
|
||||
response.raise_for_status()
|
||||
@@ -215,10 +240,10 @@ class ContentGenerator:
|
||||
"""
|
||||
|
||||
response = requests.post(
|
||||
PIAPI_URL,
|
||||
self.chat_url,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {PIAPI_API_KEY}"
|
||||
"Authorization": f"Bearer {self.api_key}"
|
||||
},
|
||||
json={
|
||||
"model": self.model,
|
||||
@@ -229,7 +254,7 @@ class ContentGenerator:
|
||||
"temperature": 0.9,
|
||||
"max_tokens": 500
|
||||
},
|
||||
proxies=get_proxies(),
|
||||
proxies=self.proxies,
|
||||
timeout=30
|
||||
)
|
||||
response.raise_for_status()
|
||||
@@ -244,6 +269,470 @@ class ContentGenerator:
|
||||
post = self._generate_with_templates(business_type, business_description, platform)
|
||||
return f"{post}\n\n💭 Пожелания учтены частично (без ИИ): {wishes}"
|
||||
|
||||
def generate_post_from_brand_profile(self, brand_data, platform, style, wishes):
|
||||
"""Генерирует пост на основе медиа-образа (данных опроса brand_survey).
|
||||
brand_data — dict с полями опроса."""
|
||||
print(f"🔄 Генерация поста из медиа-образа для {platform}...")
|
||||
|
||||
brand_type = brand_data.get("brand_type", "personal")
|
||||
is_org = (brand_type == "organization")
|
||||
is_community = (brand_type == "community")
|
||||
|
||||
# Собираем ключевые поля медиа-образа
|
||||
client_name = brand_data.get("client_name", "")
|
||||
client_role = brand_data.get("client_role", "")
|
||||
brand_essence = brand_data.get("brand_essence", "")
|
||||
positioning_who = brand_data.get("positioning_who", "")
|
||||
positioning_problem = brand_data.get("positioning_problem", "")
|
||||
positioning_result = brand_data.get("positioning_result", "")
|
||||
usp = brand_data.get("usp", "")
|
||||
tov_description = brand_data.get("tov_description", "")
|
||||
tov_references = brand_data.get("tov_references", "")
|
||||
tov_axes = brand_data.get("tov_axes", "")
|
||||
prompt_context = brand_data.get("prompt_context", "")
|
||||
prompt_tov = brand_data.get("prompt_tov", "")
|
||||
prompt_structure = brand_data.get("prompt_structure", "")
|
||||
prompt_directions = brand_data.get("prompt_directions", "")
|
||||
prompt_formats = brand_data.get("prompt_formats", "")
|
||||
prompt_technical = brand_data.get("prompt_technical", "")
|
||||
|
||||
# Поля организации
|
||||
org_name = brand_data.get("org_name", "")
|
||||
org_industry = brand_data.get("org_industry", "")
|
||||
org_mission = brand_data.get("org_mission", "")
|
||||
org_products = brand_data.get("org_products", "")
|
||||
org_team_size = brand_data.get("org_team_size", "")
|
||||
positioning_difference = brand_data.get("positioning_difference", "")
|
||||
community_formats = brand_data.get("community_formats", "")
|
||||
|
||||
platform_names = {
|
||||
'instagram': 'Instagram',
|
||||
'linkedin': 'LinkedIn',
|
||||
'telegram': 'Telegram',
|
||||
'twitter': 'Twitter/X',
|
||||
'ad': 'рекламный пост'
|
||||
}
|
||||
|
||||
if is_community:
|
||||
identity_name = org_name
|
||||
identity_role = org_industry
|
||||
identity_block = f"""=== ОБЩЕСТВЕННАЯ ОРГАНИЗАЦИЯ ===
|
||||
Название: {org_name}
|
||||
Сфера: {org_industry}
|
||||
Миссия: {org_mission}
|
||||
Направления деятельности: {org_products}
|
||||
Участников: {org_team_size}
|
||||
Формат взаимодействия: {community_formats}
|
||||
Сущность бренда: {brand_essence}
|
||||
УТП: {usp}"""
|
||||
positioning_block = f"""=== ПОЗИЦИОНИРОВАНИЕ ===
|
||||
Для кого создано: {positioning_who}
|
||||
Какую проблему решает: {positioning_problem}
|
||||
Ценность для участника: {positioning_result}
|
||||
Чем отличаемся от других: {positioning_difference}"""
|
||||
tone_intro = "Ты — профессиональный копирайтер, создающий контент для общественной организации / бизнес-сообщества."
|
||||
post_intro = f"Напиши пост для {platform_names.get(platform, 'соцсетей')} от имени организации «{org_name}»."
|
||||
system_msg = f"Ты профессиональный копирайтер. Пишешь контент для бизнес-сообщества «{org_name}» в соответствии с его миссией, ценностями и Tone of Voice."
|
||||
elif is_org:
|
||||
identity_name = org_name
|
||||
identity_role = org_industry
|
||||
identity_block = f"""=== ОРГАНИЗАЦИЯ ===
|
||||
Название: {org_name}
|
||||
Сфера: {org_industry}
|
||||
Миссия: {org_mission}
|
||||
Продукты/услуги: {org_products}
|
||||
Сотрудников: {org_team_size}
|
||||
Сущность бренда: {brand_essence}
|
||||
УТП: {usp}"""
|
||||
positioning_block = f"""=== ПОЗИЦИОНИРОВАНИЕ ===
|
||||
Кому помогаем: {positioning_who}
|
||||
Какую проблему решаем: {positioning_problem}
|
||||
Какой результат получает клиент: {positioning_result}
|
||||
Почему выбирают нас: {positioning_difference}"""
|
||||
tone_intro = "Ты — профессиональный копирайтер, создающий контент для организации."
|
||||
post_intro = f"Напиши пост для {platform_names.get(platform, 'соцсетей')} от имени организации «{org_name}»."
|
||||
system_msg = f"Ты профессиональный копирайтер. Пишешь контент для организации «{org_name}» в соответствии с её Tone of Voice и позиционированием."
|
||||
else:
|
||||
identity_name = client_name
|
||||
identity_role = client_role
|
||||
identity_block = f"""=== ЛИЧНОСТЬ И КОНТЕКСТ ===
|
||||
Имя: {client_name}
|
||||
Роль: {client_role}
|
||||
Сущность бренда: {brand_essence}
|
||||
УТП: {usp}"""
|
||||
positioning_block = f"""=== ПОЗИЦИОНИРОВАНИЕ ===
|
||||
Кому помогаю: {positioning_who}
|
||||
Какую проблему решаю: {positioning_problem}
|
||||
Какой результат получает клиент: {positioning_result}"""
|
||||
tone_intro = "Ты — профессиональный копирайтер-психолог, который глубоко понимает образ и стратегию личного бренда."
|
||||
post_intro = f"Напиши пост для {platform_names.get(platform, 'соцсетей')} от лица автора медиа-образа."
|
||||
system_msg = "Ты профессиональный копирайтер-психолог. Пишешь в точном соответствии с образом и Tone of Voice автора."
|
||||
|
||||
if self.use_piapi:
|
||||
try:
|
||||
prompt = f"""
|
||||
{tone_intro}
|
||||
|
||||
{post_intro}
|
||||
|
||||
{identity_block}
|
||||
|
||||
{positioning_block}
|
||||
|
||||
=== TONE OF VOICE ===
|
||||
Описание тона: {tov_description}
|
||||
Референсы: {tov_references}
|
||||
Оси ToV: {tov_axes}
|
||||
|
||||
=== СТРУКТУРА МЫСЛИ ===
|
||||
{prompt_structure}
|
||||
|
||||
=== КОНТЕНТ-НАПРАВЛЕНИЯ ===
|
||||
{prompt_directions}
|
||||
|
||||
=== ФОРМАТЫ И ШАБЛОНЫ ===
|
||||
{prompt_formats}
|
||||
|
||||
=== ТЕХНИЧЕСКИЕ ТРЕБОВАНИЯ ===
|
||||
{prompt_technical}
|
||||
|
||||
=== ПАРАМЕТРЫ ЭТОГО ПОСТА ===
|
||||
Платформа: {platform_names.get(platform, 'соцсети')}
|
||||
Стиль: {style}
|
||||
Пожелания: {wishes}
|
||||
|
||||
Напиши только текст поста, без лишних пояснений. Следуй структуре мысли и Tone of Voice.
|
||||
"""
|
||||
response = requests.post(
|
||||
self.chat_url,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {self.api_key}"
|
||||
},
|
||||
json={
|
||||
"model": self.model,
|
||||
"messages": [
|
||||
{"role": "system", "content": system_msg},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
"temperature": 0.9,
|
||||
"max_tokens": 4000
|
||||
},
|
||||
proxies=self.proxies,
|
||||
timeout=90
|
||||
)
|
||||
response.raise_for_status()
|
||||
msg = response.json()["choices"][0]["message"]
|
||||
post = (msg.get("content") or "").strip()
|
||||
if post and len(post) > 20:
|
||||
print("✅ Пост из медиа-образа сгенерирован через API!")
|
||||
return post
|
||||
# Если content пустой — возможно reasoning-модель не успела завершить
|
||||
raise RuntimeError("API вернул пустой ответ (content пуст). Увеличьте max_tokens или попробуйте другую модель.")
|
||||
except requests.exceptions.HTTPError as e:
|
||||
status = e.response.status_code if e.response is not None else "?"
|
||||
body = e.response.text[:200] if e.response is not None else ""
|
||||
raise RuntimeError(f"API вернул ошибку {status}: {body}")
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
raise RuntimeError(f"Не удалось подключиться к API: {e}")
|
||||
except requests.exceptions.Timeout:
|
||||
raise RuntimeError("Превышен таймаут запроса к API (30 сек)")
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Ошибка API: {e}")
|
||||
|
||||
# Fallback — шаблонный пост с данными медиа-образа
|
||||
print("🔄 Использую шаблонный пост из медиа-образа...")
|
||||
return self._generate_template_brand_post(brand_data, platform, wishes)
|
||||
|
||||
def modify_post(self, original_post: str, instruction: str) -> str:
|
||||
"""Модифицирует существующий пост по инструкции пользователя.
|
||||
original_post — текущий текст поста
|
||||
instruction — что нужно сделать («сократи вдвое», «сделай официальнее» и т.д.)
|
||||
Возвращает новый текст поста.
|
||||
"""
|
||||
print(f"🔄 Модификация поста: «{instruction[:60]}»...")
|
||||
|
||||
prompt = f"""Ты — профессиональный копирайтер. Пользователь просит изменить пост.
|
||||
|
||||
=== ТЕКУЩИЙ ПОСТ ===
|
||||
{original_post}
|
||||
|
||||
=== ИНСТРУКЦИЯ ===
|
||||
{instruction}
|
||||
|
||||
=== ЗАДАЧА ===
|
||||
Перепиши пост, выполнив инструкцию. Сохрани общий смысл и стиль. Верни ТОЛЬКО готовый пост без пояснений и markdown."""
|
||||
|
||||
if self.use_piapi:
|
||||
try:
|
||||
response = requests.post(
|
||||
self.chat_url,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {self.api_key}"
|
||||
},
|
||||
json={
|
||||
"model": self.model,
|
||||
"messages": [
|
||||
{"role": "system", "content": "Ты профессиональный копирайтер. Ты редактируешь посты строго по инструкции."},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 4000
|
||||
},
|
||||
proxies=self.proxies,
|
||||
timeout=90
|
||||
)
|
||||
response.raise_for_status()
|
||||
post = response.json()["choices"][0]["message"]["content"].strip()
|
||||
if post and len(post) > 20:
|
||||
print("✅ Пост модифицирован!")
|
||||
return post
|
||||
except Exception as e:
|
||||
print(f"⚠️ Ошибка API при модификации: {e}")
|
||||
|
||||
# Если ИИ не сработал — возвращаем оригинал с пометкой
|
||||
return f"{original_post}\n\n💬 [ИИ не смог изменить пост: {instruction}]"
|
||||
|
||||
def _generate_template_brand_post(self, brand_data, platform, wishes):
|
||||
"""Простой пост из медиа-образа без обращения к ИИ (запасной вариант)."""
|
||||
client_name = brand_data.get("client_name", "Автор")
|
||||
brand_essence = brand_data.get("brand_essence", "")
|
||||
usp = brand_data.get("usp", "")
|
||||
positioning_result = brand_data.get("positioning_result", "")
|
||||
|
||||
platform_emoji = {
|
||||
"instagram": "📸", "linkedin": "💼", "telegram": "📱",
|
||||
"twitter": "🐦", "ad": "📢"
|
||||
}
|
||||
emoji = platform_emoji.get(platform, "📝")
|
||||
|
||||
post = (
|
||||
f"{emoji} {brand_essence}\n\n"
|
||||
f"Меня зовут {client_name}.\n"
|
||||
f"{usp}\n\n"
|
||||
f"Я помогаю получить: {positioning_result}\n\n"
|
||||
)
|
||||
if wishes:
|
||||
post += f"💭 {wishes}\n\n"
|
||||
post += "Напишите в комментариях, какая у вас сейчас задача 👇\n\n#личныйбренд #бизнес"
|
||||
return post
|
||||
|
||||
def suggest_image_concepts(self, post_text, brand_data=None):
|
||||
"""Анализирует текст поста и предлагает 4 варианта концепции изображения.
|
||||
Возвращает список dict: [{title, description, style, size}, ...]"""
|
||||
print(f"🔄 Анализ поста для подбора концепции изображения ({len(post_text)} символов)...")
|
||||
|
||||
if not self.use_piapi:
|
||||
# Fallback — базовые концепции без ИИ
|
||||
return [
|
||||
{"title": "Тематическая фотография", "description": "Профессиональная фотография, иллюстрирующая ключевую идею поста", "style": "photo", "size": "wide"},
|
||||
{"title": "Минималистичный баннер", "description": "Чистый баннер с текстом поста на минималистичном фоне", "style": "minimal", "size": "wide"},
|
||||
{"title": "Абстрактная иллюстрация", "description": "Цветная абстрактная иллюстрация в тон поста", "style": "illustration", "size": "square"},
|
||||
{"title": "Инфографика", "description": "Визуализация данных и ключевых тезисов поста", "style": "flat", "size": "tall"},
|
||||
]
|
||||
|
||||
brand_context = ""
|
||||
if brand_data:
|
||||
brand_context = f"""
|
||||
Контекст бренда:
|
||||
- Имя: {brand_data.get('client_name', '')}
|
||||
- Роль: {brand_data.get('client_role', '')}
|
||||
- Сущность: {brand_data.get('brand_essence', '')}
|
||||
- УТП: {brand_data.get('usp', '')}
|
||||
"""
|
||||
|
||||
prompt = f"""Ты — арт-директор и визуальный стратег. Проанализируй текст поста и предложи 4 разных варианта визуального оформления.
|
||||
|
||||
=== ТЕКСТ ПОСТА ===
|
||||
{post_text}
|
||||
{brand_context}
|
||||
=== ЗАДАЧА ===
|
||||
Предложи 4 контрастных варианта изображения. Каждый вариант — это отдельная идея с уникальным настроением и стилем.
|
||||
|
||||
Для каждого варианта укажи:
|
||||
- title: краткое название (2-4 слова на русском)
|
||||
- description: что изображено (1 предложение на русском)
|
||||
- style: один из: photo, illustration, minimal, 3d, flat
|
||||
- size: один из: square, wide, tall
|
||||
|
||||
Верни ТОЛЬКО JSON-массив без markdown и пояснений:
|
||||
[{{"title": "...", "description": "...", "style": "...", "size": "..."}}, ...]"""
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
self.chat_url,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {self.api_key}"
|
||||
},
|
||||
json={
|
||||
"model": self.model,
|
||||
"messages": [
|
||||
{"role": "system", "content": "Ты — арт-директор. Отвечай только валидным JSON без markdown-обёрток."},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
"temperature": 0.8,
|
||||
"max_tokens": 1000
|
||||
},
|
||||
proxies=self.proxies,
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
raw = response.json()["choices"][0]["message"]["content"].strip()
|
||||
|
||||
# Убираем markdown-обёртку если есть
|
||||
if raw.startswith("```"):
|
||||
raw = raw.split("\n", 1)[1]
|
||||
if raw.endswith("```"):
|
||||
raw = raw[:-3]
|
||||
raw = raw.strip()
|
||||
|
||||
concepts = json.loads(raw)
|
||||
if isinstance(concepts, list) and len(concepts) > 0:
|
||||
print(f"✅ Получено {len(concepts)} концепций от ИИ")
|
||||
return concepts[:5] # макс 5
|
||||
except Exception as e:
|
||||
print(f"⚠️ Ошибка при получении концепций: {e}")
|
||||
|
||||
# Fallback
|
||||
return [
|
||||
{"title": "Тематическая фотография", "description": "Профессиональная фотография, иллюстрирующая ключевую идею поста", "style": "photo", "size": "wide"},
|
||||
{"title": "Минималистичный баннер", "description": "Чистый баннер с текстом поста на минималистичном фоне", "style": "minimal", "size": "wide"},
|
||||
{"title": "Абстрактная иллюстрация", "description": "Цветная абстрактная иллюстрация в тон поста", "style": "illustration", "size": "square"},
|
||||
{"title": "Инфографика", "description": "Визуализация данных и ключевых тезисов поста", "style": "flat", "size": "tall"},
|
||||
]
|
||||
|
||||
def parse_brand_doc_to_survey(self, doc_text):
|
||||
"""Разбирает текст .docx документа стратегии личного бренда
|
||||
и извлекает структурированные поля опроса через PiAPI.
|
||||
Возвращает dict с полями brand_survey.data или None при ошибке."""
|
||||
print(f"🔄 Разбор документа медиа-образа ({len(doc_text)} символов)...")
|
||||
|
||||
if not self.use_piapi:
|
||||
print("⚠️ PiAPI недоступен — разбор документа невозможен")
|
||||
return None
|
||||
|
||||
# Ограничиваем длину, чтобы влезть в контекст
|
||||
text = doc_text[:12000]
|
||||
|
||||
prompt = f"""
|
||||
Ты — аналитик. Извлеки структурированные данные из документа стратегии личного бренда и верни их в виде JSON.
|
||||
|
||||
Документ:
|
||||
---
|
||||
{text}
|
||||
---
|
||||
|
||||
Извлеки следующие поля (если поле отсутствует в документе — оставь пустую строку ""):
|
||||
|
||||
{{
|
||||
"client_name": "Имя клиента",
|
||||
"client_role": "Профессия/роль",
|
||||
"client_location": "Город/регион",
|
||||
"current_situation": "Описание текущей ситуации",
|
||||
"goal_audience_number": "число подписчиков",
|
||||
"goal_audience_platform": "основная платформа",
|
||||
"goal_audience_description": "описание аудитории",
|
||||
"goal_expert_field": "область экспертизы",
|
||||
"goal_expert_speaking": "число выступлений в год",
|
||||
"goal_commercial_leads": "число заявок в месяц",
|
||||
"goal_commercial_services": "услуги",
|
||||
"goal_content_system": "описание контент-системы",
|
||||
"brand_essence": "сущность бренда одной строкой",
|
||||
"positioning_who": "кому помогаю",
|
||||
"positioning_problem": "какую проблему решаю",
|
||||
"positioning_result": "какой результат получает клиент",
|
||||
"usp": "УТП",
|
||||
"ideal_audiences": "идеальные подписчики, по строкам",
|
||||
"visual_concept": "концепция визуала",
|
||||
"color_main_name": "название основного цвета",
|
||||
"color_main_hex": "HEX основного цвета",
|
||||
"color_accent1_name": "название акцентного цвета 1",
|
||||
"color_accent1_hex": "HEX акцентного цвета 1",
|
||||
"color_accent2_name": "название акцентного цвета 2",
|
||||
"color_accent2_hex": "HEX акцентного цвета 2",
|
||||
"color_neutral": "нейтральная база",
|
||||
"typography_headings": "шрифт заголовков",
|
||||
"typography_body": "шрифт текста",
|
||||
"typography_accents": "шрифт акцентов",
|
||||
"tov_description": "описание тона",
|
||||
"tov_references": "референсы",
|
||||
"tov_axes": "оси ToV по строкам",
|
||||
"pillar1_name": "название столпа 1",
|
||||
"pillar1_goal": "цель столпа 1",
|
||||
"pillar1_formats": "форматы столпа 1 по строкам",
|
||||
"pillar1_frequency": "частота столпа 1",
|
||||
"pillar2_name": "название столпа 2",
|
||||
"pillar2_goal": "цель столпа 2",
|
||||
"pillar2_formats": "форматы столпа 2 по строкам",
|
||||
"pillar2_frequency": "частота столпа 2",
|
||||
"pillar3_name": "название столпа 3",
|
||||
"pillar3_goal": "цель столпа 3",
|
||||
"pillar3_formats": "форматы столпа 3 по строкам",
|
||||
"pillar3_frequency": "частота столпа 3",
|
||||
"special_formats": "особые форматы",
|
||||
"product_lead_magnet": "лид-магнит",
|
||||
"product_simple": "простая услуга",
|
||||
"product_converter": "услуга-конвертер",
|
||||
"product_complex": "комплексные решения",
|
||||
"product_expert": "экспертные продукты",
|
||||
"metrics_quantitative": "количественные метрики, формат: Название | Значение | Измерение, по строкам",
|
||||
"metrics_qualitative": "качественные метрики, тот же формат",
|
||||
"metrics_commercial": "коммерческие метрики, тот же формат",
|
||||
"risks": "риски, формат: Риск | Решение, по строкам",
|
||||
"recommendations": "рекомендации по строкам",
|
||||
"archetype_key_name": "название ключевого архетипа",
|
||||
"archetype_key_essence": "суть ключевого архетипа",
|
||||
"archetype_key_manifestations": "проявления по строкам",
|
||||
"archetype_supporting": "вспомогательные архетипы, формат: Название | Суть | Проявления, по строкам",
|
||||
"archetype_synthesis": "синтез архетипов",
|
||||
"archetype_table": "таблица проявлений, формат: Ситуация | Архетип | Описание, по строкам",
|
||||
"prompt_context": "контекст и личность для промпта",
|
||||
"prompt_tov": "Tone of Voice для промпта",
|
||||
"prompt_structure": "структура мысли",
|
||||
"prompt_directions": "контент-направления по строкам",
|
||||
"prompt_formats": "форматы и шаблоны",
|
||||
"prompt_technical": "технические требования к тексту"
|
||||
}}
|
||||
|
||||
Верни ТОЛЬКО валидный JSON без пояснений и markdown.
|
||||
"""
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
self.chat_url,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {self.api_key}"
|
||||
},
|
||||
json={
|
||||
"model": self.model,
|
||||
"messages": [
|
||||
{"role": "system", "content": "Ты отвечаешь только валидным JSON, без markdown и пояснений."},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
"temperature": 0.3,
|
||||
"max_tokens": 3000,
|
||||
"response_format": {"type": "json_object"}
|
||||
},
|
||||
proxies=self.proxies,
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
content = response.json()["choices"][0]["message"]["content"].strip()
|
||||
parsed = json.loads(content)
|
||||
print("✅ Документ медиа-образа разобран через PiAPI!")
|
||||
# Гарантируем наличие channels и funnel
|
||||
if "channels" not in parsed:
|
||||
parsed["channels"] = []
|
||||
if "funnel" not in parsed:
|
||||
parsed["funnel"] = []
|
||||
return parsed
|
||||
except Exception as e:
|
||||
print(f"⚠️ Ошибка разбора документа: {e}")
|
||||
return None
|
||||
|
||||
def generate_content_plan(self, business_type, business_description, days):
|
||||
"""Генерирует контент-план на несколько дней вперёд"""
|
||||
print(f"🔄 Генерация контент-плана на {days} дн...")
|
||||
@@ -263,10 +752,10 @@ class ContentGenerator:
|
||||
"""
|
||||
|
||||
response = requests.post(
|
||||
PIAPI_URL,
|
||||
self.chat_url,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {PIAPI_API_KEY}"
|
||||
"Authorization": f"Bearer {self.api_key}"
|
||||
},
|
||||
json={
|
||||
"model": self.model,
|
||||
@@ -278,7 +767,7 @@ class ContentGenerator:
|
||||
"max_tokens": 1000,
|
||||
"response_format": {"type": "json_object"}
|
||||
},
|
||||
proxies=get_proxies(),
|
||||
proxies=self.proxies,
|
||||
timeout=30
|
||||
)
|
||||
response.raise_for_status()
|
||||
@@ -294,6 +783,100 @@ class ContentGenerator:
|
||||
print("🔄 Использую шаблонный план...")
|
||||
return self._generate_template_plan(business_type, days)
|
||||
|
||||
def generate_content_plan_from_brand_profile(self, brand_data, days):
|
||||
"""Генерирует контент-план на основе медиа-образа."""
|
||||
print(f"🔄 Генерация контент-плана из медиа-образа на {days} дн...")
|
||||
|
||||
brand_type = brand_data.get("brand_type", "personal")
|
||||
is_org = (brand_type == "organization")
|
||||
is_community = (brand_type == "community")
|
||||
|
||||
client_name = brand_data.get("client_name", "")
|
||||
org_name = brand_data.get("org_name", "")
|
||||
brand_essence = brand_data.get("brand_essence", "")
|
||||
positioning_who = brand_data.get("positioning_who", "")
|
||||
usp = brand_data.get("usp", "")
|
||||
tov_description = brand_data.get("tov_description", "")
|
||||
prompt_directions = brand_data.get("prompt_directions", "")
|
||||
pillar1_formats = brand_data.get("pillar1_formats", "")
|
||||
pillar2_formats = brand_data.get("pillar2_formats", "")
|
||||
pillar3_formats = brand_data.get("pillar3_formats", "")
|
||||
|
||||
if is_community:
|
||||
entity_name = org_name
|
||||
pillar1_label = "О миссии"
|
||||
pillar2_label = "Мероприятия"
|
||||
pillar3_label = "Участники"
|
||||
entity_type = "Бизнес-сообщество / общественная организация"
|
||||
elif is_org:
|
||||
entity_name = org_name
|
||||
pillar1_label = "О продукте"
|
||||
pillar2_label = "Экспертиза"
|
||||
pillar3_label = "Вовлечение"
|
||||
entity_type = "Организация"
|
||||
else:
|
||||
entity_name = client_name
|
||||
pillar1_label = "Кто я"
|
||||
pillar2_label = "Решаю боль"
|
||||
pillar3_label = "Втягиваю"
|
||||
entity_type = "Автор"
|
||||
|
||||
if self.use_piapi:
|
||||
try:
|
||||
prompt = f"""
|
||||
Ты — SMM-стратег. Составь контент-план на {days} дней.
|
||||
|
||||
{entity_type}: {entity_name}
|
||||
Сущность бренда: {brand_essence}
|
||||
УТП: {usp}
|
||||
Аудитория: {positioning_who}
|
||||
Tone of Voice: {tov_description}
|
||||
|
||||
Контент-направления:
|
||||
{prompt_directions}
|
||||
|
||||
Три столпа контента:
|
||||
1. «{pillar1_label}» (форматы): {pillar1_formats}
|
||||
2. «{pillar2_label}» (форматы): {pillar2_formats}
|
||||
3. «{pillar3_label}» (форматы): {pillar3_formats}
|
||||
|
||||
Для каждого дня укажи платформу (instagram, linkedin, telegram, twitter или ad) и короткую идею поста (1 предложение), учитывая три столпа и Tone of Voice.
|
||||
|
||||
Верни ТОЛЬКО валидный JSON без пояснений в формате:
|
||||
{{"plan": [{{"day": 1, "platform": "instagram", "idea": "..."}}, ...]}}
|
||||
"""
|
||||
response = requests.post(
|
||||
self.chat_url,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {self.api_key}"
|
||||
},
|
||||
json={
|
||||
"model": self.model,
|
||||
"messages": [
|
||||
{"role": "system", "content": "Ты отвечаешь только валидным JSON, без markdown и пояснений."},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 1200,
|
||||
"response_format": {"type": "json_object"}
|
||||
},
|
||||
proxies=self.proxies,
|
||||
timeout=30
|
||||
)
|
||||
response.raise_for_status()
|
||||
content = response.json()["choices"][0]["message"]["content"].strip()
|
||||
parsed = json.loads(content)
|
||||
plan = parsed.get("plan")
|
||||
if plan:
|
||||
print("✅ Контент-план из медиа-образа сгенерирован через PiAPI!")
|
||||
return plan
|
||||
except Exception as e:
|
||||
print(f"⚠️ Ошибка PiAPI: {e}, использую шаблонный план")
|
||||
|
||||
print("🔄 Использую шаблонный план...")
|
||||
return self._generate_template_plan(brand_essence or client_name or "бизнес", days)
|
||||
|
||||
def _generate_template_plan(self, business_type, days):
|
||||
"""Простой контент-план без обращения к ИИ (запасной вариант)"""
|
||||
platforms = ['instagram', 'linkedin', 'telegram', 'twitter', 'ad']
|
||||
|
||||
Reference in new issue
Block a user