Skip to main content

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.
  • theme — Theme.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. Backdrop — gen.generate_preview(chats, "telegram", view, Theme.DARK), screenshot the .cg-device, then Pillow GaussianBlur + a ~45% black blend.
  2. Card — gen.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. Compose — alpha_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.

Metadata

Release files for chatgen 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for chatgen 0.2.1
File Size Uploaded Provenance Actions
chatgen-0.2.1.tar.gz 1.8 MB With provenance Details for chatgen-0.2.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for chatgen 0.2.1
File Interpreter ABI Platform Actions
chatgen-0.2.1-py3-none-any.whl Python 3 none any Details for chatgen-0.2.1-py3-none-any.whl

Total release size: 3.7 MB

Release files / chatgen-0.2.1.tar.gz

Download URL chatgen-0.2.1.tar.gz
Size 1.8 MB
Tags Source
SHA-256 checksum
How to use checksums
f31d70ca3efb13cb4ab8a34983cd850e9b862d9b8c5636d4e6bcac7b9dc8484a
BLAKE2b-256 checksum
How to use checksums
aaa8c9945b8c6c1cbd77dddc6580ecfa51bcfff8aff1828f6572227fc334e940
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 1, 2026.

Transparency log

Release files / chatgen-0.2.1-py3-none-any.whl

Download URL chatgen-0.2.1-py3-none-any.whl
Size 1.9 MB
Tags Python 3
SHA-256 checksum
How to use checksums
0599f84004ebb0830a83d3d982040d59b2f9572a9e63b8007eccb36903ec8b55
BLAKE2b-256 checksum
How to use checksums
5e0d8f2854025b9ed97406efa8f9a41297d4f59384b1fe2c03960463b99a35a4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 1, 2026.

Transparency log

Security for chatgen 0.2.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for chatgen-0.2.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page