dialog-forge 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: dialog-forge
3
+ Version: 0.1.0
4
+ Summary: Generate realistic chat conversation mocks from dict/JSON input
5
+ Author: Dialog Forge Contributors
6
+ License: MIT
7
+ Keywords: chat,mock,conversation,whatsapp,instagram,facebook
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ Provides-Extra: export
19
+ Requires-Dist: playwright>=1.40; extra == "export"
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7.0; extra == "dev"
22
+ Requires-Dist: playwright>=1.40; extra == "dev"
23
+
24
+ # Dialog Forge
25
+
26
+ Generate realistic chat conversation mocks from dict/JSON input with platform-specific styling.
27
+
28
+ ## Supported Platforms
29
+
30
+ - **WhatsApp** — green bubbles, double-check marks, timestamps
31
+ - **Instagram** — gradient bubbles, circular avatars, "Seen" labels
32
+ - **Facebook Messenger** — blue bubbles, rounded UI, delivery indicators
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install dialog-forge
38
+
39
+ # With image export support (PNG/JPG via Playwright)
40
+ pip install dialog-forge[export]
41
+ ```
42
+
43
+ ## Quick Start
44
+
45
+ ```python
46
+ from dialog_forge import render_chat
47
+
48
+ conversation = {
49
+ "platform": "whatsapp",
50
+ "title": "Family Group",
51
+ "participants": {
52
+ "me": {"name": "Alice", "avatar": None},
53
+ "friend": {"name": "Bob", "avatar": None},
54
+ },
55
+ "messages": [
56
+ {
57
+ "sender": "friend",
58
+ "text": "Hey! Are you coming tonight?",
59
+ "timestamp": "18:30",
60
+ "status": "read",
61
+ },
62
+ {
63
+ "sender": "me",
64
+ "text": "Yes! On my way 🚗",
65
+ "timestamp": "18:32",
66
+ "status": "delivered",
67
+ },
68
+ ],
69
+ }
70
+
71
+ # Get HTML string
72
+ html = render_chat(conversation)
73
+
74
+ # Get PNG bytes
75
+ png_bytes = render_chat(conversation, format="png")
76
+
77
+ # Debug mode — prints the intermediate HTML path
78
+ html_debug = render_chat(conversation, format="png", debug=True)
79
+ ```
80
+
81
+ ## API
82
+
83
+ ### `render_chat(data, format="html", debug=False)`
84
+
85
+ | Parameter | Type | Default | Description |
86
+ |-----------|--------|---------|-------------|
87
+ | `data` | `dict` | — | Conversation data (see schema below) |
88
+ | `format` | `str` | `"html"` | Output format: `"html"`, `"png"`, or `"jpg"` |
89
+ | `debug` | `bool` | `False` | When `True`, saves intermediate HTML to a temp file and prints its path |
90
+
91
+ **Returns:** `str` (HTML) or `bytes` (image data)
92
+
93
+ ### Conversation Schema
94
+
95
+ ```json
96
+ {
97
+ "platform": "whatsapp | instagram | facebook",
98
+ "title": "Chat Title",
99
+ "participants": {
100
+ "sender_id": {
101
+ "name": "Display Name",
102
+ "avatar": "https://url-to-avatar.png (optional)",
103
+ "gender": "male | female (optional)"
104
+ }
105
+ },
106
+ "messages": [
107
+ {
108
+ "sender": "sender_id",
109
+ "text": "Message content",
110
+ "timestamp": "HH:MM",
111
+ "status": "sent | delivered | read",
112
+ "type": "text"
113
+ }
114
+ ]
115
+ }
116
+ ```
117
+
118
+ ## Adding a New Platform
119
+
120
+ 1. Create `src/dialog_forge/platforms/your_platform.py`
121
+ 2. Subclass `BaseRenderer` and implement `render(conversation) -> str`
122
+ 3. Register it in `src/dialog_forge/platforms/__init__.py`
123
+
124
+ ## License
125
+
126
+ MIT
@@ -0,0 +1,103 @@
1
+ # Dialog Forge
2
+
3
+ Generate realistic chat conversation mocks from dict/JSON input with platform-specific styling.
4
+
5
+ ## Supported Platforms
6
+
7
+ - **WhatsApp** — green bubbles, double-check marks, timestamps
8
+ - **Instagram** — gradient bubbles, circular avatars, "Seen" labels
9
+ - **Facebook Messenger** — blue bubbles, rounded UI, delivery indicators
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install dialog-forge
15
+
16
+ # With image export support (PNG/JPG via Playwright)
17
+ pip install dialog-forge[export]
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```python
23
+ from dialog_forge import render_chat
24
+
25
+ conversation = {
26
+ "platform": "whatsapp",
27
+ "title": "Family Group",
28
+ "participants": {
29
+ "me": {"name": "Alice", "avatar": None},
30
+ "friend": {"name": "Bob", "avatar": None},
31
+ },
32
+ "messages": [
33
+ {
34
+ "sender": "friend",
35
+ "text": "Hey! Are you coming tonight?",
36
+ "timestamp": "18:30",
37
+ "status": "read",
38
+ },
39
+ {
40
+ "sender": "me",
41
+ "text": "Yes! On my way 🚗",
42
+ "timestamp": "18:32",
43
+ "status": "delivered",
44
+ },
45
+ ],
46
+ }
47
+
48
+ # Get HTML string
49
+ html = render_chat(conversation)
50
+
51
+ # Get PNG bytes
52
+ png_bytes = render_chat(conversation, format="png")
53
+
54
+ # Debug mode — prints the intermediate HTML path
55
+ html_debug = render_chat(conversation, format="png", debug=True)
56
+ ```
57
+
58
+ ## API
59
+
60
+ ### `render_chat(data, format="html", debug=False)`
61
+
62
+ | Parameter | Type | Default | Description |
63
+ |-----------|--------|---------|-------------|
64
+ | `data` | `dict` | — | Conversation data (see schema below) |
65
+ | `format` | `str` | `"html"` | Output format: `"html"`, `"png"`, or `"jpg"` |
66
+ | `debug` | `bool` | `False` | When `True`, saves intermediate HTML to a temp file and prints its path |
67
+
68
+ **Returns:** `str` (HTML) or `bytes` (image data)
69
+
70
+ ### Conversation Schema
71
+
72
+ ```json
73
+ {
74
+ "platform": "whatsapp | instagram | facebook",
75
+ "title": "Chat Title",
76
+ "participants": {
77
+ "sender_id": {
78
+ "name": "Display Name",
79
+ "avatar": "https://url-to-avatar.png (optional)",
80
+ "gender": "male | female (optional)"
81
+ }
82
+ },
83
+ "messages": [
84
+ {
85
+ "sender": "sender_id",
86
+ "text": "Message content",
87
+ "timestamp": "HH:MM",
88
+ "status": "sent | delivered | read",
89
+ "type": "text"
90
+ }
91
+ ]
92
+ }
93
+ ```
94
+
95
+ ## Adding a New Platform
96
+
97
+ 1. Create `src/dialog_forge/platforms/your_platform.py`
98
+ 2. Subclass `BaseRenderer` and implement `render(conversation) -> str`
99
+ 3. Register it in `src/dialog_forge/platforms/__init__.py`
100
+
101
+ ## License
102
+
103
+ MIT
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "dialog-forge"
7
+ version = "0.1.0"
8
+ description = "Generate realistic chat conversation mocks from dict/JSON input"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ {name = "Dialog Forge Contributors"},
14
+ ]
15
+ keywords = ["chat", "mock", "conversation", "whatsapp", "instagram", "facebook"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Topic :: Software Development :: Libraries",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ export = ["playwright>=1.40"]
29
+ dev = ["pytest>=7.0", "playwright>=1.40"]
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["src"]
33
+
34
+ [tool.pytest.ini_options]
35
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,12 @@
1
+ """
2
+ Dialog Forge — Generate realistic chat conversation mocks.
3
+
4
+ Public API:
5
+ render_chat(data, format="html", debug=False) -> str | bytes
6
+ """
7
+
8
+ from dialog_forge.api import render_chat
9
+ from dialog_forge.models import Conversation, Message, Sender
10
+
11
+ __all__ = ["render_chat", "Conversation", "Message", "Sender"]
12
+ __version__ = "0.1.0"
@@ -0,0 +1,78 @@
1
+ """
2
+ Public API for dialog_forge.
3
+
4
+ Provides the main entry point: render_chat()
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import tempfile
10
+ import os
11
+
12
+ from dialog_forge.models import Conversation
13
+ from dialog_forge.renderer import RendererRegistry
14
+
15
+ # Ensure built-in platforms are registered on first import
16
+ import dialog_forge.platforms # noqa: F401
17
+
18
+
19
+ def render_chat(
20
+ data: dict,
21
+ format: str = "html",
22
+ debug: bool = False,
23
+ ) -> str | bytes:
24
+ """
25
+ Render a chat conversation mock from a dict/JSON input.
26
+
27
+ Args:
28
+ data: Dictionary describing the conversation (see README for schema).
29
+ format: Output format — "html", "png", or "jpg".
30
+ debug: When True and format is an image, saves the intermediate HTML
31
+ to a temp file and prints its path for visual tuning.
32
+
33
+ Returns:
34
+ str: The HTML document (when format="html").
35
+ bytes: Image data (when format="png" or "jpg").
36
+
37
+ Raises:
38
+ ValueError: If the platform is not recognized or format is invalid.
39
+ ImportError: If image export is requested but Playwright is not installed.
40
+ """
41
+ fmt = format.lower()
42
+ if fmt not in ("html", "png", "jpg", "jpeg"):
43
+ raise ValueError(
44
+ f"Unsupported format '{format}'. Use 'html', 'png', or 'jpg'."
45
+ )
46
+
47
+ # Parse input into a Conversation model
48
+ conversation = Conversation.from_dict(data)
49
+
50
+ # Select the renderer for the given platform
51
+ renderer = RendererRegistry.get(conversation.platform)
52
+
53
+ # Render to HTML
54
+ html = renderer.render(conversation)
55
+
56
+ # HTML output mode
57
+ if fmt == "html":
58
+ if debug:
59
+ path = _save_debug_html(html)
60
+ print(f"[dialog_forge debug] HTML saved to: {path}")
61
+ return html
62
+
63
+ # Image output mode
64
+ if debug:
65
+ path = _save_debug_html(html)
66
+ print(f"[dialog_forge debug] Intermediate HTML saved to: {path}")
67
+
68
+ from dialog_forge.exporters import export_to_image
69
+
70
+ return export_to_image(html, fmt=fmt)
71
+
72
+
73
+ def _save_debug_html(html: str) -> str:
74
+ """Save HTML to a temporary file and return its path."""
75
+ fd, path = tempfile.mkstemp(suffix=".html", prefix="dialog_forge_debug_")
76
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
77
+ f.write(html)
78
+ return path
@@ -0,0 +1,109 @@
1
+ """
2
+ Image export functionality for dialog_forge.
3
+
4
+ Uses Playwright (headless Chromium) to render HTML to PNG or JPG.
5
+ Playwright is an optional dependency — install with:
6
+ pip install dialog-forge[export]
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import sys
13
+
14
+
15
+ def _check_playwright_available() -> None:
16
+ """Raise a helpful error if Playwright is not installed."""
17
+ try:
18
+ import playwright # noqa: F401
19
+ except ImportError:
20
+ raise ImportError(
21
+ "Image export requires Playwright. "
22
+ "Install it with: pip install dialog-forge[export]\n"
23
+ "Then run: python -m playwright install chromium"
24
+ )
25
+
26
+
27
+ async def _render_html_to_image(
28
+ html: str,
29
+ fmt: str = "png",
30
+ width: int = 420,
31
+ ) -> bytes:
32
+ """
33
+ Render an HTML string to image bytes using Playwright.
34
+
35
+ Args:
36
+ html: Complete HTML document string.
37
+ fmt: Image format — "png" or "jpg"/"jpeg".
38
+ width: Viewport width in pixels (height auto-adjusts to content).
39
+
40
+ Returns:
41
+ Image bytes in the requested format.
42
+ """
43
+ from playwright.async_api import async_playwright
44
+
45
+ fmt_lower = fmt.lower()
46
+ if fmt_lower in ("jpg", "jpeg"):
47
+ screenshot_type = "jpeg"
48
+ else:
49
+ screenshot_type = "png"
50
+
51
+ async with async_playwright() as p:
52
+ browser = await p.chromium.launch(headless=True)
53
+ page = await browser.new_page(viewport={"width": width, "height": 800})
54
+
55
+ await page.set_content(html, wait_until="networkidle")
56
+
57
+ # Auto-size the viewport height to fit the content
58
+ content_height = await page.evaluate(
59
+ "() => document.documentElement.scrollHeight"
60
+ )
61
+ await page.set_viewport_size({"width": width, "height": content_height})
62
+
63
+ screenshot_bytes = await page.screenshot(
64
+ type=screenshot_type,
65
+ full_page=True,
66
+ quality=95 if screenshot_type == "jpeg" else None,
67
+ )
68
+
69
+ await browser.close()
70
+
71
+ return screenshot_bytes
72
+
73
+
74
+ def export_to_image(
75
+ html: str,
76
+ fmt: str = "png",
77
+ width: int = 420,
78
+ ) -> bytes:
79
+ """
80
+ Synchronous wrapper to export HTML to an image.
81
+
82
+ Args:
83
+ html: Complete HTML document string.
84
+ fmt: Image format — "png" or "jpg"/"jpeg".
85
+ width: Viewport width in pixels.
86
+
87
+ Returns:
88
+ Image file bytes.
89
+ """
90
+ _check_playwright_available()
91
+
92
+ # Handle event loop — if one is already running, use it; otherwise create one
93
+ try:
94
+ loop = asyncio.get_running_loop()
95
+ except RuntimeError:
96
+ loop = None
97
+
98
+ if loop and loop.is_running():
99
+ # We're inside an async context; run in a new thread to avoid deadlock
100
+ import concurrent.futures
101
+
102
+ with concurrent.futures.ThreadPoolExecutor() as pool:
103
+ future = pool.submit(
104
+ asyncio.run,
105
+ _render_html_to_image(html, fmt, width),
106
+ )
107
+ return future.result()
108
+ else:
109
+ return asyncio.run(_render_html_to_image(html, fmt, width))
@@ -0,0 +1,115 @@
1
+ """
2
+ Data models for dialog_forge.
3
+
4
+ Defines the core dataclasses used to represent a chat conversation:
5
+ - Sender: a participant in the conversation
6
+ - Message: a single chat message
7
+ - Conversation: the full conversation container
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass, field
13
+ from typing import Optional
14
+
15
+
16
+ @dataclass
17
+ class Sender:
18
+ """Represents a chat participant."""
19
+
20
+ name: str
21
+ avatar: Optional[str] = None # URL or base64 data URI
22
+ gender: Optional[str] = None # "male", "female", or None
23
+
24
+ @classmethod
25
+ def from_dict(cls, data: dict) -> Sender:
26
+ """Create a Sender from a raw dictionary."""
27
+ return cls(
28
+ name=data["name"],
29
+ avatar=data.get("avatar"),
30
+ gender=data.get("gender"),
31
+ )
32
+
33
+
34
+ @dataclass
35
+ class Message:
36
+ """Represents a single chat message."""
37
+
38
+ sender: str # Key referencing a participant in the conversation
39
+ text: str
40
+ timestamp: str = "" # Display string like "18:30"
41
+ status: str = "sent" # "sent", "delivered", "read"
42
+ type: str = "text" # "text" (extensible for future types)
43
+
44
+ # Resolved at render time — not expected from user input
45
+ _sender_obj: Optional[Sender] = field(default=None, repr=False)
46
+
47
+ @classmethod
48
+ def from_dict(cls, data: dict) -> Message:
49
+ """Create a Message from a raw dictionary."""
50
+ return cls(
51
+ sender=data["sender"],
52
+ text=data["text"],
53
+ timestamp=data.get("timestamp", ""),
54
+ status=data.get("status", "sent"),
55
+ type=data.get("type", "text"),
56
+ )
57
+
58
+
59
+ @dataclass
60
+ class Conversation:
61
+ """
62
+ Represents a full chat conversation.
63
+
64
+ Attributes:
65
+ platform: Target platform for styling ("whatsapp", "instagram", "facebook").
66
+ title: Display title of the chat (e.g. contact name or group name).
67
+ participants: Mapping of participant IDs to Sender objects.
68
+ messages: Ordered list of Message objects.
69
+ me: The participant ID representing the local/owner user. Defaults to "me".
70
+ """
71
+
72
+ platform: str
73
+ title: str
74
+ participants: dict[str, Sender]
75
+ messages: list[Message]
76
+ me: str = "me" # Which participant ID is "us" (outgoing messages)
77
+
78
+ @classmethod
79
+ def from_dict(cls, data: dict) -> Conversation:
80
+ """
81
+ Parse a raw dictionary (or deserialized JSON) into a Conversation.
82
+
83
+ Expected schema:
84
+ {
85
+ "platform": "whatsapp",
86
+ "title": "Chat Title",
87
+ "me": "alice", # optional, defaults to "me"
88
+ "participants": {
89
+ "alice": {"name": "Alice", "avatar": null},
90
+ "bob": {"name": "Bob", "avatar": null},
91
+ },
92
+ "messages": [
93
+ {"sender": "bob", "text": "Hello!", "timestamp": "18:30", "status": "read"},
94
+ ...
95
+ ]
96
+ }
97
+ """
98
+ participants = {
99
+ pid: Sender.from_dict(pdata)
100
+ for pid, pdata in data.get("participants", {}).items()
101
+ }
102
+
103
+ messages = [Message.from_dict(m) for m in data.get("messages", [])]
104
+
105
+ # Resolve sender objects on each message for convenience
106
+ for msg in messages:
107
+ msg._sender_obj = participants.get(msg.sender)
108
+
109
+ return cls(
110
+ platform=data["platform"],
111
+ title=data.get("title", "Chat"),
112
+ participants=participants,
113
+ messages=messages,
114
+ me=data.get("me", "me"),
115
+ )
@@ -0,0 +1,16 @@
1
+ """
2
+ Platform renderer registration.
3
+
4
+ Importing this module registers all built-in platform renderers
5
+ with the global RendererRegistry.
6
+ """
7
+
8
+ from dialog_forge.renderer import RendererRegistry
9
+ from dialog_forge.platforms.whatsapp import WhatsAppRenderer
10
+ from dialog_forge.platforms.instagram import InstagramRenderer
11
+ from dialog_forge.platforms.facebook import FacebookRenderer
12
+
13
+ # Register all built-in renderers
14
+ RendererRegistry.register("whatsapp", WhatsAppRenderer)
15
+ RendererRegistry.register("instagram", InstagramRenderer)
16
+ RendererRegistry.register("facebook", FacebookRenderer)