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— 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f31d70ca3efb13cb4ab8a34983cd850e9b862d9b8c5636d4e6bcac7b9dc8484a
|
|
| MD5 |
4bf30589a6720652471cea9265a0c95c
|
|
| BLAKE2b-256 |
aaa8c9945b8c6c1cbd77dddc6580ecfa51bcfff8aff1828f6572227fc334e940
|
Provenance
The following attestation bundles were made for chatgen-0.2.1.tar.gz:
Publisher:
publish.yml on RuNeira/screenshots-generator
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
chatgen-0.2.1.tar.gz -
Subject digest:
f31d70ca3efb13cb4ab8a34983cd850e9b862d9b8c5636d4e6bcac7b9dc8484a - Sigstore transparency entry: 1690692026
- Sigstore integration time:
-
Permalink:
RuNeira/screenshots-generator@59507c202be830079ccab0ba8918d120954cca63 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/RuNeira
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@59507c202be830079ccab0ba8918d120954cca63 -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0599f84004ebb0830a83d3d982040d59b2f9572a9e63b8007eccb36903ec8b55
|
|
| MD5 |
3bd0c5cfbb4cad0797cfa4f98be72a0b
|
|
| BLAKE2b-256 |
5e0d8f2854025b9ed97406efa8f9a41297d4f59384b1fe2c03960463b99a35a4
|
Provenance
The following attestation bundles were made for chatgen-0.2.1-py3-none-any.whl:
Publisher:
publish.yml on RuNeira/screenshots-generator
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
chatgen-0.2.1-py3-none-any.whl -
Subject digest:
0599f84004ebb0830a83d3d982040d59b2f9572a9e63b8007eccb36903ec8b55 - Sigstore transparency entry: 1690692040
- Sigstore integration time:
-
Permalink:
RuNeira/screenshots-generator@59507c202be830079ccab0ba8918d120954cca63 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/RuNeira
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@59507c202be830079ccab0ba8918d120954cca63 -
Trigger Event:
release
-
Statement type: