-
Notifications
You must be signed in to change notification settings - Fork 414
New (Python) example: simple drawings (rectangle, ellipse, arrow) #238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
bb9e32f
Install python-dotenv and tabulate
gvanrossum 6737581
Small working demo that can draw boxes on a canvas
gvanrossum de03aa9
Add chat history to drawing demo
gvanrossum 2edaedd
[WIP] Hack on translation a bit
gvanrossum 09ea859
New API for chat history
gvanrossum c52aa53
Improved the schema with ChatGPT's help. Added Arrow and Ellipse.
gvanrossum 5d07a2a
rendering program using Tkinter
gvanrossum f46a836
Add a simple rendering function (most of it written by ChatGPT)
gvanrossum 131ad4f
Added dashed and dotted arrows (some help from ChatGPT)
gvanrossum 3f44129
Merge commit '66fd7bb' into drawing
gvanrossum b90faa7
Rip oput history, for now
gvanrossum 02e28e4
Make pyright happy
gvanrossum 3e1f3e6
Merge branch 'main' into drawing
gvanrossum e9ac5cf
Keep pydantic also happy
gvanrossum 9e14f42
Add (flawed) history based on prompt_preamble
gvanrossum 75c2efe
Prune history
gvanrossum 970d50b
Add temporary logging
gvanrossum 7560273
Alternate prompting scheme from #241
gvanrossum 439f521
Revert changes to translator.py
gvanrossum b0fdd8d
Merge branch 'main' into drawing
gvanrossum d385e40
Use Anders' suggestion for history
gvanrossum 42ce16a
Switch schema to dataclasses
gvanrossum c6da914
Black formatting
gvanrossum 390558e
Remove unused imports
gvanrossum e42b2dd
Clarify Failure output
gvanrossum 11edfb7
Revert verbose logging
gvanrossum 3f099dc
Reuse drawing window
gvanrossum 5c6e15f
Don't crash if we cannot import readline (e.g. on Windows)
gvanrossum 0894ef6
Add crude progress messages
gvanrossum b6798e6
Address PR #238 review-thread feedback in drawing example and interac…
robgruen 94c6735
Merge branch 'main' into drawing
robgruen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # Let's draw some diagrams |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import asyncio | ||
| import sys | ||
| import tkinter as tk | ||
|
|
||
| from dotenv import dotenv_values | ||
|
|
||
| import schema | ||
| from render import render_drawing | ||
|
|
||
| from typechat import ( | ||
| Success, | ||
| Failure, | ||
| TypeChatJsonTranslator, | ||
| TypeChatValidator, | ||
| create_language_model, | ||
| process_requests, | ||
| ) | ||
|
|
||
|
|
||
| async def main(file_path: str | None): | ||
| env_vals = dotenv_values() | ||
| model = create_language_model(env_vals) | ||
| validator = TypeChatValidator(schema.Drawing) | ||
| translator = TypeChatJsonTranslator(model, validator, schema.Drawing) | ||
| # print(translator._schema_str) | ||
|
|
||
| window = tk.Tk() | ||
| window.title("Click to continue...") | ||
| canvas = tk.Canvas(window, width=800, height=600, bg="white", highlightthickness=0) | ||
| canvas.pack(padx=10, pady=10) | ||
| canvas.bind("<Button-1>", lambda event: window.quit()) | ||
|
|
||
| history: list[str] = [] | ||
|
|
||
| async def request_handler(request: str): | ||
| print("[Sending request...]") | ||
| history.append(request) | ||
| result: Success[schema.Drawing] | Failure = await translator.translate("\n".join(history)) | ||
| if isinstance(result, Failure): | ||
| print("Failure:", result.message) | ||
| else: | ||
| value: schema.Drawing = result.value | ||
| print(value) | ||
| if any(isinstance(item, schema.UnknownText) for item in value.items): | ||
| print("Unknown text detected. Please provide more context:") | ||
| for item in value.items: | ||
| if isinstance(item, schema.UnknownText): | ||
| print(" ", item.text) | ||
|
|
||
| canvas.delete("all") | ||
| render_drawing(canvas, value) | ||
| print("Click in drawing to continue...") | ||
| window.mainloop() | ||
|
|
||
| await process_requests("~> ", file_path, request_handler) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| file_path = sys.argv[1] if len(sys.argv) == 2 else None | ||
| asyncio.run(main(file_path)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| draw three red squares in a diagonal | ||
| red is the fill color | ||
| make the corners touch | ||
| add labels "foo", etc. | ||
| make them pink |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import tkinter as tk | ||
|
|
||
| from schema import Style, Box, Ellipse, Arrow, Drawing, UnknownText | ||
|
|
||
|
|
||
| # Map line style to dash patterns | ||
| dash_pattern = { | ||
| "solid": "", | ||
| "dashed": (4, 4), # 4 pixels drawn, 4 pixels space | ||
| "dotted": (1, 1), # 1 pixel drawn, 1 pixel space | ||
| } | ||
|
|
||
|
|
||
| def render_drawing(canvas: tk.Canvas, drawing: Drawing): | ||
|
|
||
| def draw_box(box: Box): | ||
| x1, y1 = box.x, box.y | ||
| x2, y2 = x1 + box.width, y1 + box.height | ||
| canvas.create_rectangle( | ||
| x1, | ||
| y1, | ||
| x2, | ||
| y2, | ||
| outline=getattr(box.style, "line_color", None) or "black", | ||
| fill=getattr(box.style, "fill_color", None) or "", | ||
| ) | ||
| if box.text: | ||
| canvas.create_text((x1 + x2) / 2, (y1 + y2) / 2, text=box.text, fill="black") | ||
|
|
||
| def draw_ellipse(ellipse: Ellipse): | ||
| x1, y1 = ellipse.x, ellipse.y | ||
| x2, y2 = x1 + ellipse.width, y1 + ellipse.height | ||
| canvas.create_oval( | ||
| x1, | ||
| y1, | ||
| x2, | ||
| y2, | ||
| outline=getattr(ellipse.style, "line_color", None) or "black", | ||
| fill=getattr(ellipse.style, "fill_color", None) or "", | ||
| ) | ||
| if ellipse.text: | ||
| canvas.create_text((x1 + x2) / 2, (y1 + y2) / 2, text=ellipse.text, fill="black") | ||
|
|
||
| def draw_arrow(arrow: Arrow): | ||
| line_style = getattr(arrow.style, "line_style", None) or "solid" | ||
| dash = dash_pattern.get(line_style, dash_pattern["solid"]) | ||
| canvas.create_line( | ||
| arrow.start_x, | ||
| arrow.start_y, | ||
| arrow.end_x, | ||
| arrow.end_y, | ||
| dash=dash, | ||
| arrow=tk.LAST, | ||
| fill=getattr(arrow.style, "line_color", None) or "black", | ||
| ) | ||
|
|
||
| for item in drawing.items: | ||
| match item: | ||
| case Box(): | ||
| draw_box(item) | ||
| case Ellipse(): | ||
| draw_ellipse(item) | ||
| case Arrow(): | ||
| draw_arrow(item) | ||
| case UnknownText(): | ||
| print(f"Unknown text: {item.text}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| example_drawing = Drawing( | ||
| type="Drawing", | ||
| items=[ | ||
| Box( | ||
| type="Box", | ||
| x=50, | ||
| y=50, | ||
| width=100, | ||
| height=100, | ||
| text="Hello", | ||
| style=Style(type="Style"), | ||
| ), | ||
| Ellipse( | ||
| type="Ellipse", | ||
| x=200, | ||
| y=50, | ||
| width=150, | ||
| height=100, | ||
| text="World", | ||
| style=Style(type="Style", fill_color="lightblue"), | ||
| ), | ||
| Arrow( | ||
| type="Arrow", | ||
| start_x=50, | ||
| start_y=200, | ||
| end_x=150, | ||
| end_y=200, | ||
| style=Style(type="Style", line_style="dashed"), | ||
| ), | ||
| ], | ||
| ) | ||
|
|
||
| window = tk.Tk() | ||
| window.title("Drawing") | ||
| canvas = tk.Canvas(window, width=800, height=600, bg="white", highlightthickness=0) | ||
| canvas.pack(padx=10, pady=10) | ||
| render_drawing(canvas, example_drawing) | ||
| window.mainloop() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| """Schema for a drawing with boxes, ellipses, arrows, etc.""" | ||
|
|
||
| from dataclasses import dataclass | ||
| from typing_extensions import Literal, Annotated, Doc, Optional | ||
|
|
||
|
|
||
| @dataclass | ||
| class Style: | ||
| """Style settings for drawing elements.""" | ||
|
|
||
| type: Literal["Style"] | ||
|
|
||
| corners: Annotated[ | ||
| Optional[Literal["rounded", "sharp"]], | ||
| Doc("Corner style of the drawing elements."), | ||
| ] = None | ||
| line_thickness: Annotated[Optional[int], Doc("Thickness of the lines.")] = None | ||
| line_color: Annotated[Optional[str], Doc("CSS-style color code for line color.")] = None | ||
| fill_color: Annotated[Optional[str], Doc("CSS-style color code for fill color.")] = None | ||
| line_style: Annotated[ | ||
| Optional[Literal["solid", "dashed", "dotted"]], | ||
| Doc("Style of the line: 'solid', 'dashed', 'dotted'."), | ||
| ] = None | ||
|
|
||
|
|
||
| @dataclass | ||
| class Box: | ||
| """A rectangular box defined by a coordinate system with the origin at the top left.""" | ||
|
|
||
| type: Literal["Box"] | ||
|
|
||
| x: Annotated[int, Doc("X-coordinate of the top left corner.")] | ||
| y: Annotated[int, Doc("Y-coordinate of the top left corner.")] | ||
| width: Annotated[int, Doc("Width of the box.")] | ||
| height: Annotated[int, Doc("Height of the box.")] | ||
| text: Annotated[Optional[str], Doc("Optional text centered in the box.")] = None | ||
| style: Annotated[Optional[Style], Doc("Optional style settings for the box.")] = None | ||
|
|
||
|
|
||
| @dataclass | ||
| class Ellipse: | ||
| """An ellipse defined by its bounding box dimensions.""" | ||
|
|
||
| type: Literal["Ellipse"] | ||
|
|
||
| x: Annotated[int, Doc("X-coordinate of the top left corner of the bounding box.")] | ||
| y: Annotated[int, Doc("Y-coordinate of the top left corner of the bounding box.")] | ||
| width: Annotated[int, Doc("Width of the bounding box.")] | ||
| height: Annotated[int, Doc("Height of the bounding box.")] | ||
| text: Annotated[Optional[str], Doc("Optional text centered in the box.")] = None | ||
| style: Annotated[Optional[Style], Doc("Optional style settings for the ellipse.")] = None | ||
|
|
||
|
|
||
| @dataclass | ||
| class Arrow: | ||
| """A line with a directional arrow at the end, defined by start and end points.""" | ||
|
|
||
| type: Literal["Arrow"] | ||
|
|
||
| start_x: Annotated[int, Doc("Starting X-coordinate.")] | ||
| start_y: Annotated[int, Doc("Starting Y-coordinate.")] | ||
| end_x: Annotated[int, Doc("Ending X-coordinate.")] | ||
| end_y: Annotated[int, Doc("Ending Y-coordinate.")] | ||
| style: Annotated[Optional[Style], Doc("Optional style settings for the arrow.")] = None | ||
| head_size: Annotated[Optional[int], Doc("Size of the arrowhead, if present.")] = None | ||
|
|
||
|
|
||
| @dataclass | ||
| class UnknownText: | ||
| """Used for input that does not match any other specified type.""" | ||
|
|
||
| type: Literal["Unknown"] | ||
|
|
||
| text: Annotated[str, Doc("The text that wasn't understood.")] | ||
|
|
||
|
|
||
| @dataclass | ||
| class Drawing: | ||
| """A collection of graphical elements including boxes, ellipses, arrows, and unrecognized text.""" | ||
|
|
||
| type: Literal["Drawing"] | ||
|
|
||
| items: Annotated[list[Box | Arrow | Ellipse | UnknownText], Doc("List of drawable elements.")] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.