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— aDialog(see model below).platform— aPlatformenum member or a registry-key string.view— aDeviceView:IPHONE_15_PRO,IPHONE_SE,PIXEL_8,ANDROID,WEB.theme—Theme.LIGHTorTheme.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:
- Backdrop —
gen.generate_preview(chats, "telegram", view, Theme.DARK), screenshot the.cg-device, then PillowGaussianBlur+ a ~45% black blend. - 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. - 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. - Compose —
alpha_compositeshadow → 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).
- One-time: on PyPI, add a GitHub publisher for project
chatgen→ ownerRuNeira, reposcreenshots-generator, workflowpublish.yml, environmentpypi. - Bump
versioninpyproject.toml, commit. - Tag and create a GitHub Release (
vX.Y.Z). Thepublish.ymlworkflow 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.