Skip to main content

Generate self-contained HTML mock-ups of chat conversations styled like popular messaging & dating apps.

Project description

chatgen

Generate self-contained HTML mock-ups of chat conversations styled exactly like popular platforms — messengers (iMessage, WhatsApp, Telegram, Instagram DM, Messenger, Signal, Discord, Snapchat, VK, Slack, LINE, X/Twitter DM, WeChat, Viber) and dating apps (Tinder, Bumble, Hinge). Feed it a dialog (participants + messages with timestamps), pick a platform, device, and theme, and get back ready-to-open HTML — or a PNG screenshot.

By default the output is a clean, edge-to-edge screenshot (just the screen, no device bezel). The phone frame is an opt-in.

Install

pip install chatgen          # or:  uv add chatgen

# optional, for PNG export:
pip install "chatgen[screenshots]"
# optional, for MP4/GIF/WebM video export (bundles ffmpeg):
pip install "chatgen[video]"
playwright install chromium

Quickstart

from datetime import datetime
from chatgen import Generator, Dialog, Participant, Message, Text, Image
from chatgen import Platform, DeviceView, Theme, MessageStatus

me  = Participant(id="me",  name="You", is_self=True)
her = Participant(id="her", name="Alex Rivera", subtitle="online")

dialog = Dialog(participants=[me, her], messages=[
    Message(her, datetime(2026, 5, 31, 9, 40), Text("Hey! How's it going? 😊")),
    Message(me,  datetime(2026, 5, 31, 9, 41), Text("Pretty good! You?"),
            status=MessageStatus.READ),
    Message(her, datetime(2026, 5, 31, 9, 42), Image(src="alex_selfie.jpg")),
])

gen = Generator()

# 1) Clean screenshot HTML (default — no device frame)
html = gen.generate(dialog, platform=Platform.WHATSAPP,
                    view=DeviceView.IPHONE_15_PRO, theme=Theme.DARK)
open("chat.html", "w").write(html)

# 2) With the phone bezel (opt-in)
html_framed = gen.generate(dialog, platform=Platform.IMESSAGE,
                           view=DeviceView.IPHONE_15_PRO, theme=Theme.LIGHT,
                           show_frame=True)

# 3) PNG screenshot (needs the 'screenshots' extra)
gen.generate_png(dialog, platform=Platform.TELEGRAM,
                 view=DeviceView.IPHONE_15_PRO, theme=Theme.LIGHT,
                 path="chat.png", scale=2)

API

Generator().generate(dialog, platform, view, theme, **kwargs) -> str       # HTML
Generator().generate_png(dialog, platform, view, theme, *, path=None, scale=2, **kwargs) -> bytes
Generator().generate_video(dialog, platform, view, theme, *, path, fmt=None, # mp4 | gif | webm
                           scale=2, fps=25, animate_delay=600, hold_ms=1400, **kwargs) -> str
Generator().generate_dataset(dialog, platform, view, theme, *, out_dir, name="sample",
                             scale=2, **kwargs) -> dict   # PNG + YOLO labels
Generator().generate_preview(chats, platform, view, theme, **kwargs) -> str  # inbox / chat-list HTML

generate_preview renders the chat-list / inbox screen (not a thread) from a list of ChatPreview rows. Supported on platforms with a standard inbox (WhatsApp, Telegram, iMessage, Instagram, Messenger, Signal, VK, Viber, LINE, WeChat, X/Twitter); others raise.

from chatgen import ChatPreview
chats = [
    ChatPreview(name="Alex Rivera", last="see you then!", time="9:41", unread=2),
    ChatPreview(name="Family", last="📷 Photo", time="Tue", is_group=True, muted=True),
]
html = gen.generate_preview(chats, platform="telegram",
                            view=DeviceView.IPHONE_15_PRO, theme=Theme.DARK)

generate_dataset renders a static PNG and YOLO bounding-box labels — one box per message (classes 0=me, 1=them) plus one box for the contact name (2=name). Boxes come from the real rendered DOM geometry. Writes images/<name>.png, labels/<name>.txt, classes.txt, data.yaml under out_dir (needs the screenshots extra). Supported on bubble platforms + Telegram + Snapchat.

generate_video records the conversation as the messages appear one-by-one (it turns on animate for you) and writes an MP4 / GIF / WebM — format inferred from the path extension. Needs the video extra (Playwright + ffmpeg):

gen.generate_video(dialog, platform=Platform.WHATSAPP, view=DeviceView.IPHONE_15_PRO,
                   theme=Theme.LIGHT, wallpaper="doodle", path="chat.mp4")
  • dialog — a Dialog (see model below).
  • platform — a Platform enum member or a registry-key string.
  • view — a DeviceView: IPHONE_15_PRO, IPHONE_SE, PIXEL_8, ANDROID, WEB.
  • themeTheme.LIGHT or Theme.DARK.

Common kwargs

kwarg default meaning
show_frame False draw the phone bezel + drop shadow (screenshot-first by default)
show_status_bar True render the OS status bar (clock/signal/battery)
clock "9:41" status-bar time
battery 100 status-bar battery percent
typing False show a "typing…" indicator bubble for the other person
wallpaper None chat background — a named preset ("sunset", "ocean", … see list_wallpapers()) or any raw CSS color/gradient — WhatsApp & Telegram
notification None a Notification(app, title, body, icon, time, style) push-banner that drops in from the top — from any app/person, independent of the chat. Static on PNG; slides in on video/animation. iOS/Android style auto-picked per device.
blocked None show a blocked-contact bar over the composer (platforms where blocking has UI: telegram, whatsapp, signal, instagram, messenger, vk, viber, line, wechat, snapchat). True = default label, or pass a custom string (e.g. "Разблокировать").
animate False when the HTML page opens, reveal messages one-by-one (fade + slide-up, scrolling to follow). Opt-in; works on every platform & device. generate_png ignores it (a still can't animate).
animate_start 300 ms before the first message appears (with animate)
animate_delay 450 ms between each message appearing (with animate)

animate degrades gracefully: with JavaScript disabled or prefers-reduced-motion, all messages show immediately.

Message(reply_to=...) renders an inline reply quote inside the bubble.

Devices (view): iphone_15_pro, iphone_16_pro, iphone_15_pro_max, iphone_se, pixel_8, pixel_9, galaxy_s24, android, web.

Platform-specific kwargs

platform kwargs
imessage show_contact_avatar
whatsapp show_wallpaper, is_group
telegram accent (hex color)
tinder show_match_banner (uses dialog.meta["matched_at"])
bumble show_expiry, expiry_label
hinge reads dialog.meta["liked"] for the context card

Data model

Participant(id, name, avatar=None, is_self=False, subtitle=None, age=None, distance=None, bio=None)
Dialog(participants, messages, meta={})
Message(sender, timestamp, content, status=MessageStatus.SENT, reactions=[], reply_to=None)
Reaction(emoji, by)

Content types: Text(body), Image(src, caption), Voice(duration_seconds, transcript), Sticker(src), GIF(src, caption), File(name, size), Location(label, map_image), System(text).

Avatars & images (avatar / src) accept any of: an http(s) URL, a local file path (embedded as a data-URI so the HTML stays self-contained), an existing data: URI, or None (a deterministic initials-on-color placeholder is generated).

Graceful degradation: each platform declares which content types it natively renders; unsupported types fall back to a readable bubble (e.g. Tinder renders a Voice as "🎤 Voice message") instead of breaking.

Supported platforms

from chatgen import list_platforms
list_platforms()
# ['bumble', 'discord', 'hinge', 'imessage', 'instagram', 'line', 'messenger',
#  'signal', 'slack', 'snapchat', 'telegram', 'tinder', 'twitter', 'viber',
#  'vk', 'wechat', 'whatsapp']

from chatgen import list_wallpapers
list_wallpapers()  # 123 named wallpaper themes for the `wallpaper` kwarg
# mood gradients:   sunset, ocean, dusk, forest, rose, midnight, peach, lavender,
#                   mint, graphite, fire, ice, grape, coral, emerald, twilight,
#                   sakura, steel, gold, denim, noir, candy
# Telegram-style:   aurora, tropic, berry, lagoon, blossom, instaglow, seafoam,
# (multi-point      peony, marine, nebula, citrus, orchid, ember, glacier,
#  "freeform")      meadow, rosegold, cobalt
# solid colors:     sand, sage, fog, linen, clay, stone, ink, espresso, mist,
#                   almond, moss, dove, pine, navy, wine, slateblue
# editor/aesthetic: dracula, nord, solarized, solarized_dark, gruvbox, monokai,
#                   catppuccin, tokyonight, onedark, github_dark, vaporwave,
#                   synthwave, matrix, terminal, paper, kraft, chalkboard
# css patterns:     dots, dots_dark, grid, cross, blueprint, carbon, graph, stars
# image patterns:   doodle, polka, hearts, triangles, hexagons, waves, memphis,
# (SVG, embedded)   confetti, circuit, starfield, zigzag, seigaiha, plus,
#                   diamonds, bubbles, leafy, herringbone, rings, crosshatch2,
#                   terrazzo, scales, stars_small, toxic
# photo wallpapers: photo_mountain, photo_forest, photo_ocean, photo_city,
# (JPEG, embedded)  photo_desert, photo_dunes, photo_leaves, photo_sky,
#                   photo_coast, photo_woods, photo_alps, photo_pines,
#                   photo_planks, photo_marina, photo_harvest, photo_canal,
#                   photo_village, photo_trail, photo_lights, photo_greenery

Image wallpapers (SVG patterns + photos, in chatgen/assets/wallpapers/) are base64-embedded into the output, so the HTML stays self-contained. Preview them all on one conversation: python examples/wallpaper_grid.py.

(Or pass any raw CSS background value — color, gradient, or url(...) — directly.)

Chat peek preview (long-press)

iOS shows a peek preview when you long-press a chat in the list: the conversation pops up as a scaled, rounded floating card over the blurred + dimmed chat list, with a context menu below. There's no dedicated mode for this — you compose it from the renders you already have: the inbox (generate_preview) is the blurred backdrop, a normal conversation (generate) is the card, and a small context-menu HTML is rendered on its own. Pillow stitches them together (blur, dim, round corners, drop shadow), so fonts, emoji and icons stay pixel-accurate because every piece is a real render.

pip install "chatgen[screenshots]" pillow
playwright install chromium
python examples/gen_peek.py        # -> examples/out/tg_peek.png

The script (examples/gen_peek.py) is the reference recipe — the moving parts:

  1. Backdropgen.generate_preview(chats, "telegram", view, Theme.DARK), screenshot the .cg-device, then Pillow GaussianBlur + a ~45% black blend.
  2. Cardgen.generate(dialog, "telegram", view, Theme.DARK); read the .tg-nav (header) and .tg-input (composer) box geometry and crop between them so the card has no OS status bar and no input field, like the real peek. Scale to ~92% width, apply a rounded-corner alpha mask + a blurred drop shadow.
  3. Context menu — a tiny standalone HTML (translucent dark panel, monochrome SVG icons) screenshot with omit_background=True, so the blurred backdrop shows through the vibrancy panel when composited.
  4. Composealpha_composite shadow → card → menu onto the backdrop.

Swap "telegram" for any platform with an inbox (generate_preview) to peek a different app; adjust the menu rows/labels and the card crop selectors to match that platform's header/composer classes.

Adding a platform

Platforms are plugins. Create a renderer, a screen.html, and a style.css:

# chatgen/platforms/<group>/<name>/renderer.py
from chatgen.platforms.base import Capabilities, PlatformRenderer
from chatgen.registry import register_platform

@register_platform("signal")
class SignalRenderer(PlatformRenderer):
    capabilities = Capabilities()          # toggle off unsupported content types
    kwargs_schema = {"accent": "#3a76f0"}  # platform-specific options + defaults

    def build_context(self, dialog, device, theme, **kwargs):
        other = dialog.others[0] if dialog.others else None
        return {"header_name": other.name if other else "Signal"}

Templates live in <name>/templates/screen.html (import shared macros with {% import "macros.html" as m %} and use m.content_block(msg, caps, "<prefix>")), styled by <name>/style.css. Register the module in chatgen/platforms/__init__.py.

Examples gallery

python examples/generate_gallery.py
# open examples/out/index.html

Development

Managed with uv:

uv sync              # create .venv with the project + dev group
uv run pytest -q     # run the test suite
uv build             # build sdist + wheel into dist/
uvx twine check dist/*

CI runs the suite on Python 3.10–3.13 (.github/workflows/ci.yml).

Releasing (PyPI)

Publishing is automated via PyPI Trusted Publishing (OIDC — no API token stored).

  1. One-time: on PyPI, add a GitHub publisher for project chatgen → owner RuNeira, repo screenshots-generator, workflow publish.yml, environment pypi.
  2. Bump version in pyproject.toml, commit.
  3. Tag and create a GitHub Release (vX.Y.Z). The publish.yml workflow verifies the tag matches the version, builds, and uploads to PyPI.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

chatgen-0.2.1.tar.gz (1.8 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

chatgen-0.2.1-py3-none-any.whl (1.9 MB view details)

Uploaded Python 3

File details

Details for the file chatgen-0.2.1.tar.gz.

File metadata

  • Download URL: chatgen-0.2.1.tar.gz
  • Upload date:
  • Size: 1.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for chatgen-0.2.1.tar.gz
Algorithm Hash digest
SHA256 f31d70ca3efb13cb4ab8a34983cd850e9b862d9b8c5636d4e6bcac7b9dc8484a
MD5 4bf30589a6720652471cea9265a0c95c
BLAKE2b-256 aaa8c9945b8c6c1cbd77dddc6580ecfa51bcfff8aff1828f6572227fc334e940

See more details on using hashes here.

Provenance

The following attestation bundles were made for chatgen-0.2.1.tar.gz:

Publisher: publish.yml on RuNeira/screenshots-generator

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file chatgen-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: chatgen-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 1.9 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for chatgen-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 0599f84004ebb0830a83d3d982040d59b2f9572a9e63b8007eccb36903ec8b55
MD5 3bd0c5cfbb4cad0797cfa4f98be72a0b
BLAKE2b-256 5e0d8f2854025b9ed97406efa8f9a41297d4f59384b1fe2c03960463b99a35a4

See more details on using hashes here.

Provenance

The following attestation bundles were made for chatgen-0.2.1-py3-none-any.whl:

Publisher: publish.yml on RuNeira/screenshots-generator

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page