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]"
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
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 |
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() # 122 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
# 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.)
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.1.0.tar.gz.
File metadata
- Download URL: chatgen-0.1.0.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 |
caf11595b51e473a6ea81372b4cb9f42661f4260be751709763386c5edb54170
|
|
| MD5 |
3a1ff1b1d4cd72171d7c26cfd18680a9
|
|
| BLAKE2b-256 |
fc0e328899d3c03b55819f6f45763ec0fbfa2f2f73634ddbc24e5c47b22091c7
|
Provenance
The following attestation bundles were made for chatgen-0.1.0.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.1.0.tar.gz -
Subject digest:
caf11595b51e473a6ea81372b4cb9f42661f4260be751709763386c5edb54170 - Sigstore transparency entry: 1683808942
- Sigstore integration time:
-
Permalink:
RuNeira/screenshots-generator@c3c9a7e385de63c97fcdc118b1c1b1f406a97326 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/RuNeira
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c3c9a7e385de63c97fcdc118b1c1b1f406a97326 -
Trigger Event:
release
-
Statement type:
File details
Details for the file chatgen-0.1.0-py3-none-any.whl.
File metadata
- Download URL: chatgen-0.1.0-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 |
2913d0bfc96535ba469ffb912ba56301276c680e5f01ea4ee17ed78727d8bde3
|
|
| MD5 |
e5b5de8230b2fc5947c4c773cca9f9e9
|
|
| BLAKE2b-256 |
363dde5b811e7f00a2a5eafc4f50834314aee5f25f5e6ebb85e2cb331708581a
|
Provenance
The following attestation bundles were made for chatgen-0.1.0-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.1.0-py3-none-any.whl -
Subject digest:
2913d0bfc96535ba469ffb912ba56301276c680e5f01ea4ee17ed78727d8bde3 - Sigstore transparency entry: 1683809103
- Sigstore integration time:
-
Permalink:
RuNeira/screenshots-generator@c3c9a7e385de63c97fcdc118b1c1b1f406a97326 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/RuNeira
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c3c9a7e385de63c97fcdc118b1c1b1f406a97326 -
Trigger Event:
release
-
Statement type: