feat: generate 10-slide presentation with PiAPI images
This commit is contained in:
1 parent
41c584babf
commit
cab79b34cf
7 files changed
+490
No files matched your search
@@ -26,6 +26,9 @@ media/
|
|||||||
*.webp
|
*.webp
|
||||||
# Исключение: картинка для OpenGraph (og:image) должна быть в репозитории
|
# Исключение: картинка для OpenGraph (og:image) должна быть в репозитории
|
||||||
!cabinet_service/static/og-image.png
|
!cabinet_service/static/og-image.png
|
||||||
|
# Исключение: изображения для презентации
|
||||||
|
!deploy/presentation_images/**
|
||||||
|
!deploy/presentation_images/*.png
|
||||||
|
|
||||||
# Логи
|
# Логи
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
@@ -0,0 +1,423 @@
|
|||||||
|
"""Генерация 10-слайдовой презентации сервиса «ИИ Пресс-секретарь» по ТЗ."""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from pptx import Presentation
|
||||||
|
from pptx.util import Inches, Pt, Emu
|
||||||
|
from pptx.dml.color import RGBColor
|
||||||
|
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
||||||
|
from pptx.enum.shapes import MSO_SHAPE
|
||||||
|
|
||||||
|
# === Палитра (тёмная тема из ТЗ) ===
|
||||||
|
BG = RGBColor(0x0F, 0x17, 0x2A) # #0f172a
|
||||||
|
SURFACE = RGBColor(0x1E, 0x29, 0x3B) # #1e293b
|
||||||
|
SURFACE_2 = RGBColor(0x27, 0x34, 0x49) # #273449
|
||||||
|
TEXT = RGBColor(0xE2, 0xE8, 0xF0) # #e2e8f0
|
||||||
|
MUTED = RGBColor(0x94, 0xA3, 0xB8) # #94a3b8
|
||||||
|
ACCENT = RGBColor(0x60, 0xA5, 0xFA) # #60a5fa
|
||||||
|
ACCENT_DARK = RGBColor(0x25, 0x63, 0xEB) # #2563eb
|
||||||
|
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
||||||
|
BORDER = RGBColor(0x33, 0x41, 0x55) # #334155
|
||||||
|
|
||||||
|
FONT = "Inter"
|
||||||
|
FONT_FALLBACK = "Arial"
|
||||||
|
|
||||||
|
SLIDE_W = Inches(13.333)
|
||||||
|
SLIDE_H = Inches(7.5)
|
||||||
|
|
||||||
|
|
||||||
|
def set_font(run, size, color=TEXT, bold=False, name=FONT):
|
||||||
|
run.font.size = Pt(size)
|
||||||
|
run.font.color.rgb = color
|
||||||
|
run.font.bold = bold
|
||||||
|
run.font.name = name
|
||||||
|
|
||||||
|
|
||||||
|
def add_bg(slide, color=BG):
|
||||||
|
"""Заливает весь слайд цветом."""
|
||||||
|
slide.background.fill.solid()
|
||||||
|
slide.background.fill.fore_color.rgb = color
|
||||||
|
|
||||||
|
|
||||||
|
def add_rect(slide, x, y, w, h, fill=SURFACE, line=None, radius=False):
|
||||||
|
"""Добавляет прямоугольник (карточку)."""
|
||||||
|
shape_type = MSO_SHAPE.ROUNDED_RECTANGLE if radius else MSO_SHAPE.RECTANGLE
|
||||||
|
shape = slide.shapes.add_shape(shape_type, x, y, w, h)
|
||||||
|
if radius:
|
||||||
|
try:
|
||||||
|
shape.adjustments[0] = 0.06
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if fill is None:
|
||||||
|
shape.fill.background()
|
||||||
|
else:
|
||||||
|
shape.fill.solid()
|
||||||
|
shape.fill.fore_color.rgb = fill
|
||||||
|
if line is None:
|
||||||
|
shape.line.fill.background()
|
||||||
|
else:
|
||||||
|
shape.line.color.rgb = line
|
||||||
|
shape.line.width = Pt(1)
|
||||||
|
shape.shadow.inherit = False
|
||||||
|
return shape
|
||||||
|
|
||||||
|
|
||||||
|
def add_text(slide, x, y, w, h, text, size=18, color=TEXT, bold=False,
|
||||||
|
align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP, name=FONT):
|
||||||
|
"""Добавляет текстовое поле."""
|
||||||
|
box = slide.shapes.add_textbox(x, y, w, h)
|
||||||
|
tf = box.text_frame
|
||||||
|
tf.word_wrap = True
|
||||||
|
tf.vertical_anchor = anchor
|
||||||
|
p = tf.paragraphs[0]
|
||||||
|
p.alignment = align
|
||||||
|
run = p.add_run()
|
||||||
|
run.text = text
|
||||||
|
set_font(run, size, color, bold, name)
|
||||||
|
return box
|
||||||
|
|
||||||
|
|
||||||
|
def add_paragraph(tf, text, size=18, color=TEXT, bold=False, align=PP_ALIGN.LEFT,
|
||||||
|
space_after=8, bullet=False, name=FONT):
|
||||||
|
"""Добавляет абзац в существующий text_frame."""
|
||||||
|
p = tf.add_paragraph()
|
||||||
|
p.alignment = align
|
||||||
|
p.space_after = Pt(space_after)
|
||||||
|
run = p.add_run()
|
||||||
|
run.text = text
|
||||||
|
set_font(run, size, color, bold, name)
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def add_bullets(slide, x, y, w, h, items, size=18, color=TEXT, gap=10):
|
||||||
|
"""Добавляет маркированный список."""
|
||||||
|
box = slide.shapes.add_textbox(x, y, w, h)
|
||||||
|
tf = box.text_frame
|
||||||
|
tf.word_wrap = True
|
||||||
|
for i, item in enumerate(items):
|
||||||
|
if i == 0:
|
||||||
|
p = tf.paragraphs[0]
|
||||||
|
else:
|
||||||
|
p = tf.add_paragraph()
|
||||||
|
p.space_after = Pt(gap)
|
||||||
|
# Маркер
|
||||||
|
run = p.add_run()
|
||||||
|
run.text = "• "
|
||||||
|
set_font(run, size, ACCENT, True)
|
||||||
|
# Текст
|
||||||
|
run2 = p.add_run()
|
||||||
|
run2.text = item
|
||||||
|
set_font(run2, size, color)
|
||||||
|
return box
|
||||||
|
|
||||||
|
|
||||||
|
def add_slide_number(slide, num, total=10):
|
||||||
|
"""Добавляет номер слайда в правый нижний угол."""
|
||||||
|
add_text(slide, SLIDE_W - Inches(1.2), SLIDE_H - Inches(0.6),
|
||||||
|
Inches(0.9), Inches(0.4), f"{num} / {total}",
|
||||||
|
size=12, color=MUTED, align=PP_ALIGN.RIGHT)
|
||||||
|
|
||||||
|
|
||||||
|
def add_header(slide, title, subtitle=None):
|
||||||
|
"""Добавляет заголовок слайда."""
|
||||||
|
add_text(slide, Inches(0.8), Inches(0.5), Inches(11.7), Inches(0.9),
|
||||||
|
title, size=34, color=TEXT, bold=True)
|
||||||
|
if subtitle:
|
||||||
|
add_text(slide, Inches(0.8), Inches(1.35), Inches(11.7), Inches(0.6),
|
||||||
|
subtitle, size=16, color=MUTED)
|
||||||
|
|
||||||
|
|
||||||
|
def add_footer_bar(slide):
|
||||||
|
"""Добавляет нижнюю полосу с брендом."""
|
||||||
|
add_rect(slide, Inches(0), SLIDE_H - Inches(0.35), SLIDE_W, Inches(0.35),
|
||||||
|
fill=SURFACE_2)
|
||||||
|
add_text(slide, Inches(0.8), SLIDE_H - Inches(0.32), Inches(6), Inches(0.3),
|
||||||
|
"ИИ Пресс-секретарь · press.my-dpr.ru", size=11, color=MUTED)
|
||||||
|
|
||||||
|
|
||||||
|
def add_icon_circle(slide, x, y, size, emoji, bg=ACCENT):
|
||||||
|
"""Добавляет круг с эмодзи."""
|
||||||
|
shape = slide.shapes.add_shape(MSO_SHAPE.OVAL, x, y, size, size)
|
||||||
|
shape.fill.solid()
|
||||||
|
shape.fill.fore_color.rgb = bg
|
||||||
|
shape.line.fill.background()
|
||||||
|
shape.shadow.inherit = False
|
||||||
|
tf = shape.text_frame
|
||||||
|
tf.word_wrap = False
|
||||||
|
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
|
||||||
|
p = tf.paragraphs[0]
|
||||||
|
p.alignment = PP_ALIGN.CENTER
|
||||||
|
run = p.add_run()
|
||||||
|
run.text = emoji
|
||||||
|
run.font.size = Pt(int(int(size) / 12700 * 0.5))
|
||||||
|
return shape
|
||||||
|
|
||||||
|
|
||||||
|
def add_image(slide, img_path, x, y, w=None, h=None):
|
||||||
|
"""Добавляет изображение на слайд с сохранением пропорций."""
|
||||||
|
from PIL import Image
|
||||||
|
img = Image.open(img_path)
|
||||||
|
iw, ih = img.size
|
||||||
|
if w is None and h is None:
|
||||||
|
w = Inches(4)
|
||||||
|
if w is not None and h is None:
|
||||||
|
w_emu = int(w)
|
||||||
|
h = Emu(int(w_emu * ih / iw))
|
||||||
|
elif h is not None and w is None:
|
||||||
|
h_emu = int(h)
|
||||||
|
w = Emu(int(h_emu * iw / ih))
|
||||||
|
slide.shapes.add_picture(str(img_path), x, y, width=w, height=h)
|
||||||
|
|
||||||
|
|
||||||
|
def build():
|
||||||
|
prs = Presentation()
|
||||||
|
prs.slide_width = SLIDE_W
|
||||||
|
prs.slide_height = SLIDE_H
|
||||||
|
blank = prs.slide_layouts[6] # пустой макет
|
||||||
|
|
||||||
|
# Пути к сгенерированным изображениям
|
||||||
|
img_dir = Path(__file__).resolve().parent / "presentation_images"
|
||||||
|
img_title = img_dir / "title_robot.png"
|
||||||
|
img_solution = img_dir / "solution.png"
|
||||||
|
img_media = img_dir / "media_brand.png"
|
||||||
|
|
||||||
|
# ============ СЛАЙД 1: Титульный ============
|
||||||
|
s = prs.slides.add_slide(blank)
|
||||||
|
add_bg(s)
|
||||||
|
# Изображение робота (сгенерировано через PiAPI)
|
||||||
|
if img_title.exists():
|
||||||
|
add_image(s, img_title, Inches(4.67), Inches(0.7), w=Inches(4.0))
|
||||||
|
else:
|
||||||
|
add_icon_circle(s, Inches(5.92), Inches(1.2), Inches(1.5), "🤖", ACCENT)
|
||||||
|
# Название
|
||||||
|
add_text(s, Inches(1.5), Inches(4.6), Inches(10.3), Inches(1.2),
|
||||||
|
"ИИ Пресс-секретарь", size=54, color=TEXT, bold=True,
|
||||||
|
align=PP_ALIGN.CENTER)
|
||||||
|
# Слоган
|
||||||
|
add_text(s, Inches(1.5), Inches(5.7), Inches(10.3), Inches(0.8),
|
||||||
|
"Умный контент для вашего бизнеса", size=26, color=ACCENT,
|
||||||
|
align=PP_ALIGN.CENTER)
|
||||||
|
# Описание
|
||||||
|
add_text(s, Inches(2.0), Inches(6.4), Inches(9.3), Inches(0.8),
|
||||||
|
"Сервис, который создаёт контент-план, генерирует посты и картинки "
|
||||||
|
"для вашего бизнеса и публикует их в Telegram — всё в одном месте.",
|
||||||
|
size=15, color=MUTED, align=PP_ALIGN.CENTER)
|
||||||
|
add_text(s, Inches(0.8), SLIDE_H - Inches(0.5), Inches(6), Inches(0.3),
|
||||||
|
"press.my-dpr.ru", size=12, color=MUTED)
|
||||||
|
|
||||||
|
# ============ СЛАЙД 2: Проблема ============
|
||||||
|
s = prs.slides.add_slide(blank)
|
||||||
|
add_bg(s)
|
||||||
|
add_header(s, "Ведение соцсетей отнимает слишком много времени")
|
||||||
|
add_footer_bar(s)
|
||||||
|
add_slide_number(s, 2)
|
||||||
|
problems = [
|
||||||
|
"Нужно регулярно публиковать контент, но нет времени и идей",
|
||||||
|
"Сложно придумывать посты каждый день",
|
||||||
|
"Дорого нанимать копирайтера и дизайнера",
|
||||||
|
"Трудно поддерживать единый стиль и медиа-образ",
|
||||||
|
"Публикация в Telegram вручную — рутина",
|
||||||
|
]
|
||||||
|
add_bullets(s, Inches(1.2), Inches(2.2), Inches(10.9), Inches(4.0),
|
||||||
|
problems, size=22, gap=18)
|
||||||
|
|
||||||
|
# ============ СЛАЙД 3: Решение ============
|
||||||
|
s = prs.slides.add_slide(blank)
|
||||||
|
add_bg(s)
|
||||||
|
add_header(s, "ИИ Пресс-секретарь решает эту задачу")
|
||||||
|
add_footer_bar(s)
|
||||||
|
add_slide_number(s, 3)
|
||||||
|
solutions = [
|
||||||
|
"ИИ берёт на себя генерацию контента: посты, картинки, контент-план",
|
||||||
|
"Всё в одном личном кабинете — без копирайтера и дизайнера",
|
||||||
|
"Контент подстраивается под ваш медиа-образ и стиль",
|
||||||
|
"Публикация в Telegram в один клик",
|
||||||
|
"Экономия времени и денег",
|
||||||
|
]
|
||||||
|
add_bullets(s, Inches(0.8), Inches(2.2), Inches(6.5), Inches(4.0),
|
||||||
|
solutions, size=20, gap=16)
|
||||||
|
if img_solution.exists():
|
||||||
|
add_image(s, img_solution, Inches(7.6), Inches(2.0), w=Inches(5.0))
|
||||||
|
|
||||||
|
# ============ СЛАЙД 4: Ключевые возможности ============
|
||||||
|
s = prs.slides.add_slide(blank)
|
||||||
|
add_bg(s)
|
||||||
|
add_header(s, "Всё, что нужно для контента — в одном сервисе")
|
||||||
|
add_footer_bar(s)
|
||||||
|
add_slide_number(s, 4)
|
||||||
|
features = [
|
||||||
|
("✍️", "Генерация постов",
|
||||||
|
"ИИ пишет готовые тексты для вашего бизнеса с учётом вашего медиа-образа и стиля."),
|
||||||
|
("🎨", "Генерация картинок",
|
||||||
|
"Создавайте иллюстрации к постам автоматически — без дизайнера и фотостока."),
|
||||||
|
("📅", "Контент-план",
|
||||||
|
"Планируйте публикации на неделю вперёд и следите за выполнением плана."),
|
||||||
|
("📣", "Публикация в Telegram",
|
||||||
|
"Публикуйте посты в свои каналы прямо из личного кабинета."),
|
||||||
|
]
|
||||||
|
card_w = Inches(2.85)
|
||||||
|
card_h = Inches(3.6)
|
||||||
|
gap_x = Inches(0.25)
|
||||||
|
start_x = Inches(0.8)
|
||||||
|
y = Inches(2.2)
|
||||||
|
for i, (emoji, title, desc) in enumerate(features):
|
||||||
|
x = start_x + i * (card_w + gap_x)
|
||||||
|
card = add_rect(s, x, y, card_w, card_h, fill=SURFACE, line=BORDER, radius=True)
|
||||||
|
# Иконка
|
||||||
|
add_icon_circle(s, x + Inches(0.9), y + Inches(0.4), Inches(1.0), emoji, ACCENT)
|
||||||
|
# Заголовок
|
||||||
|
add_text(s, x + Inches(0.3), y + Inches(1.6), card_w - Inches(0.6), Inches(0.7),
|
||||||
|
title, size=18, color=TEXT, bold=True, align=PP_ALIGN.CENTER)
|
||||||
|
# Описание
|
||||||
|
add_text(s, x + Inches(0.3), y + Inches(2.3), card_w - Inches(0.6), Inches(1.2),
|
||||||
|
desc, size=13, color=MUTED, align=PP_ALIGN.CENTER)
|
||||||
|
|
||||||
|
# ============ СЛАЙД 5: Медиа-образ ============
|
||||||
|
s = prs.slides.add_slide(blank)
|
||||||
|
add_bg(s)
|
||||||
|
add_header(s, "Уникальный медиа-образ вашего бренда")
|
||||||
|
add_footer_bar(s)
|
||||||
|
add_slide_number(s, 5)
|
||||||
|
media_items = [
|
||||||
|
"Заполните опрос о своём бизнесе — ИИ изучит ваш стиль, миссию, ценности и целевую аудиторию",
|
||||||
|
"Сервис формирует медиа-образ (бренд-бук) вашей организации",
|
||||||
|
"Все посты генерируются в едином стиле, соответствующем вашему бренду",
|
||||||
|
"Поддержка 9 видов бизнеса и различных форматов",
|
||||||
|
"Импорт данных из существующих каналов: Telegram, VK, Tenchat, Instagram",
|
||||||
|
]
|
||||||
|
add_bullets(s, Inches(0.8), Inches(2.2), Inches(6.5), Inches(4.0),
|
||||||
|
media_items, size=18, gap=14)
|
||||||
|
if img_media.exists():
|
||||||
|
add_image(s, img_media, Inches(7.6), Inches(2.0), w=Inches(5.0))
|
||||||
|
|
||||||
|
# ============ СЛАЙД 6: Как это работает ============
|
||||||
|
s = prs.slides.add_slide(blank)
|
||||||
|
add_bg(s)
|
||||||
|
add_header(s, "Просто начните — 4 шага")
|
||||||
|
add_footer_bar(s)
|
||||||
|
add_slide_number(s, 6)
|
||||||
|
steps = [
|
||||||
|
("1", "Зарегистрируйтесь",
|
||||||
|
"Создайте аккаунт в личном кабинете за пару минут."),
|
||||||
|
("2", "Опишите медиа-образ",
|
||||||
|
"Расскажите о своём бизнесе — ИИ подстроится под ваш стиль."),
|
||||||
|
("3", "Генерируйте контент",
|
||||||
|
"Создавайте посты, картинки и контент-план автоматически."),
|
||||||
|
("4", "Публикуйте",
|
||||||
|
"Отправляйте готовые посты в свои Telegram-каналы."),
|
||||||
|
]
|
||||||
|
step_w = Inches(2.85)
|
||||||
|
step_h = Inches(3.6)
|
||||||
|
gap_x = Inches(0.25)
|
||||||
|
start_x = Inches(0.8)
|
||||||
|
y = Inches(2.2)
|
||||||
|
for i, (num, title, desc) in enumerate(steps):
|
||||||
|
x = start_x + i * (step_w + gap_x)
|
||||||
|
card = add_rect(s, x, y, step_w, step_h, fill=SURFACE, line=BORDER, radius=True)
|
||||||
|
# Номер
|
||||||
|
circle = add_rect(s, x + Inches(1.1), y + Inches(0.4), Inches(0.7), Inches(0.7),
|
||||||
|
fill=ACCENT, radius=True)
|
||||||
|
tf = circle.text_frame
|
||||||
|
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
|
||||||
|
p = tf.paragraphs[0]
|
||||||
|
p.alignment = PP_ALIGN.CENTER
|
||||||
|
run = p.add_run()
|
||||||
|
run.text = num
|
||||||
|
set_font(run, 22, WHITE, True)
|
||||||
|
# Заголовок
|
||||||
|
add_text(s, x + Inches(0.3), y + Inches(1.4), step_w - Inches(0.6), Inches(0.7),
|
||||||
|
title, size=18, color=TEXT, bold=True, align=PP_ALIGN.CENTER)
|
||||||
|
# Описание
|
||||||
|
add_text(s, x + Inches(0.3), y + Inches(2.1), step_w - Inches(0.6), Inches(1.3),
|
||||||
|
desc, size=13, color=MUTED, align=PP_ALIGN.CENTER)
|
||||||
|
|
||||||
|
# ============ СЛАЙД 7: Дополнительные возможности ============
|
||||||
|
s = prs.slides.add_slide(blank)
|
||||||
|
add_bg(s)
|
||||||
|
add_header(s, "Больше, чем генерация постов")
|
||||||
|
add_footer_bar(s)
|
||||||
|
add_slide_number(s, 7)
|
||||||
|
extra = [
|
||||||
|
"🤖 Чат с ИИ для редактирования и доработки постов",
|
||||||
|
"📊 Статистика и аналитика публикаций",
|
||||||
|
"🔗 Подключение нескольких Telegram-каналов",
|
||||||
|
"🖼️ Медиа-библиотека и история постов",
|
||||||
|
"📱 Адаптивный дизайн — работайте с телефона, планшета или компьютера",
|
||||||
|
"📄 Экспорт бренд-бука в документ (DOCX)",
|
||||||
|
]
|
||||||
|
add_bullets(s, Inches(1.2), Inches(2.2), Inches(10.9), Inches(4.0),
|
||||||
|
extra, size=20, gap=14)
|
||||||
|
|
||||||
|
# ============ СЛАЙД 8: Для кого ============
|
||||||
|
s = prs.slides.add_slide(blank)
|
||||||
|
add_bg(s)
|
||||||
|
add_header(s, "Кому подойдёт сервис")
|
||||||
|
add_footer_bar(s)
|
||||||
|
add_slide_number(s, 8)
|
||||||
|
audience = [
|
||||||
|
"Предпринимателям и владельцам бизнеса",
|
||||||
|
"Руководителям организаций и сообществ",
|
||||||
|
"Маркетологам и SMM-специалистам",
|
||||||
|
"Блогерам и экспертам, ведущим Telegram-каналы",
|
||||||
|
"Тем, кто хочет экономить время на контенте",
|
||||||
|
]
|
||||||
|
add_bullets(s, Inches(1.2), Inches(2.2), Inches(10.9), Inches(4.0),
|
||||||
|
audience, size=22, gap=18)
|
||||||
|
|
||||||
|
# ============ СЛАЙД 9: Преимущества ============
|
||||||
|
s = prs.slides.add_slide(blank)
|
||||||
|
add_bg(s)
|
||||||
|
add_header(s, "Почему выбирают ИИ Пресс-секретарь")
|
||||||
|
add_footer_bar(s)
|
||||||
|
add_slide_number(s, 9)
|
||||||
|
advantages = [
|
||||||
|
"⏱️ Экономия времени — контент создаётся за минуты",
|
||||||
|
"💰 Экономия бюджета — не нужны копирайтер и дизайнер",
|
||||||
|
"🎯 Единый стиль — контент соответствует вашему бренду",
|
||||||
|
"📈 Регулярность — контент-план помогает публиковать системно",
|
||||||
|
"🔒 Безопасность — данные и аккаунт защищены",
|
||||||
|
]
|
||||||
|
add_bullets(s, Inches(1.2), Inches(2.2), Inches(10.9), Inches(4.0),
|
||||||
|
advantages, size=22, gap=18)
|
||||||
|
|
||||||
|
# ============ СЛАЙД 10: Призыв к действию ============
|
||||||
|
s = prs.slides.add_slide(blank)
|
||||||
|
add_bg(s)
|
||||||
|
add_icon_circle(s, Inches(6.17), Inches(1.2), Inches(1.0), "🚀", ACCENT)
|
||||||
|
add_text(s, Inches(1.5), Inches(2.6), Inches(10.3), Inches(1.0),
|
||||||
|
"Готовы начать?", size=44, color=TEXT, bold=True,
|
||||||
|
align=PP_ALIGN.CENTER)
|
||||||
|
add_text(s, Inches(1.5), Inches(3.6), Inches(10.3), Inches(0.7),
|
||||||
|
"Зарегистрируйтесь и получите доступ к личному кабинету.",
|
||||||
|
size=18, color=MUTED, align=PP_ALIGN.CENTER)
|
||||||
|
# Кнопка регистрации
|
||||||
|
btn1 = add_rect(s, Inches(4.17), Inches(4.6), Inches(2.5), Inches(0.65),
|
||||||
|
fill=ACCENT, radius=True)
|
||||||
|
tf = btn1.text_frame
|
||||||
|
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
|
||||||
|
p = tf.paragraphs[0]
|
||||||
|
p.alignment = PP_ALIGN.CENTER
|
||||||
|
run = p.add_run()
|
||||||
|
run.text = "Зарегистрироваться"
|
||||||
|
set_font(run, 16, WHITE, True)
|
||||||
|
# Кнопка входа
|
||||||
|
btn2 = add_rect(s, Inches(6.87), Inches(4.6), Inches(2.3), Inches(0.65),
|
||||||
|
fill=SURFACE, line=ACCENT, radius=True)
|
||||||
|
tf = btn2.text_frame
|
||||||
|
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
|
||||||
|
p = tf.paragraphs[0]
|
||||||
|
p.alignment = PP_ALIGN.CENTER
|
||||||
|
run = p.add_run()
|
||||||
|
run.text = "Войти в кабинет"
|
||||||
|
set_font(run, 16, ACCENT, True)
|
||||||
|
# Ссылка на сайт
|
||||||
|
add_text(s, Inches(1.5), Inches(5.6), Inches(10.3), Inches(0.5),
|
||||||
|
"press.my-dpr.ru", size=16, color=ACCENT, align=PP_ALIGN.CENTER)
|
||||||
|
|
||||||
|
# Сохранение
|
||||||
|
out_path = Path(__file__).resolve().parent.parent / "ИИ_Пресс-секретарь_презентация.pptx"
|
||||||
|
prs.save(out_path)
|
||||||
|
print(f"✅ Презентация сохранена: {out_path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
build()
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"""Генерация изображений для презентации через PiAPI."""
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ai_core.image_generator import ImageGenerator
|
||||||
|
|
||||||
|
OUT_DIR = Path(__file__).resolve().parent / "presentation_images"
|
||||||
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Промпты для изображений презентации
|
||||||
|
IMAGES = [
|
||||||
|
{
|
||||||
|
"name": "title_robot.png",
|
||||||
|
"size": "square",
|
||||||
|
"prompt": (
|
||||||
|
"Friendly modern robot assistant character with a warm smile, "
|
||||||
|
"wearing a professional suit, holding a smartphone showing a Telegram app. "
|
||||||
|
"Surrounded by floating social media elements: a calendar, a chat bubble, "
|
||||||
|
"a megaphone, a pencil, a lightbulb. Dark navy blue background (#0f172a) "
|
||||||
|
"with soft blue gradient accents (#60a5fa). Clean, minimal, high-tech "
|
||||||
|
"corporate style, flat vector illustration, bold shapes, no text, no letters."
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "solution.png",
|
||||||
|
"size": "wide",
|
||||||
|
"prompt": (
|
||||||
|
"Modern flat vector illustration of an AI assistant solving content creation "
|
||||||
|
"tasks. A robot character at a desk with a laptop, generating posts, images "
|
||||||
|
"and a content calendar. Floating icons: pencil, image, calendar, megaphone. "
|
||||||
|
"Dark navy blue background (#0f172a) with blue accents (#60a5fa). "
|
||||||
|
"Clean minimal corporate style, no text, no letters."
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "media_brand.png",
|
||||||
|
"size": "wide",
|
||||||
|
"prompt": (
|
||||||
|
"Modern flat vector illustration of brand identity and media image creation. "
|
||||||
|
"A robot character building a brand book with color swatches, typography samples "
|
||||||
|
"and style boards. Floating elements: paint palette, color swatches, document, "
|
||||||
|
"target icon. Dark navy blue background (#0f172a) with blue accents (#60a5fa). "
|
||||||
|
"Clean minimal corporate style, no text, no letters."
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
gen = ImageGenerator()
|
||||||
|
for img in IMAGES:
|
||||||
|
out = OUT_DIR / img["name"]
|
||||||
|
print(f"Генерация {img['name']} ({img['size']})...")
|
||||||
|
try:
|
||||||
|
data = gen.generate(img["prompt"], size=img["size"])
|
||||||
|
out.write_bytes(data)
|
||||||
|
print(f" ✅ {out} ({len(data)} байт)")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Ошибка: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
Reference in new issue
Block a user