From 65aac211af1a14ad3212a48d325100aa4b39f58a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:21:23 +0000 Subject: [PATCH 1/4] Add TypeScript drawing example mirroring PR 238 --- typescript/examples/README.md | 1 + typescript/examples/drawing/README.md | 17 ++++ typescript/examples/drawing/package.json | 24 +++++ .../examples/drawing/src/drawingSchema.ts | 75 ++++++++++++++ typescript/examples/drawing/src/input.txt | 5 + typescript/examples/drawing/src/main.ts | 43 ++++++++ typescript/examples/drawing/src/render.ts | 98 +++++++++++++++++++ typescript/examples/drawing/src/tsconfig.json | 16 +++ 8 files changed, 279 insertions(+) create mode 100644 typescript/examples/drawing/README.md create mode 100644 typescript/examples/drawing/package.json create mode 100644 typescript/examples/drawing/src/drawingSchema.ts create mode 100644 typescript/examples/drawing/src/input.txt create mode 100644 typescript/examples/drawing/src/main.ts create mode 100644 typescript/examples/drawing/src/render.ts create mode 100644 typescript/examples/drawing/src/tsconfig.json diff --git a/typescript/examples/README.md b/typescript/examples/README.md index dab7569a..01751304 100644 --- a/typescript/examples/README.md +++ b/typescript/examples/README.md @@ -15,6 +15,7 @@ We recommend reading each example in the following order. | [Calendar](https://github.com/microsoft/TypeChat/tree/main/typescript/examples/calendar) | An intelligent scheduler. This sample translates user intent into a sequence of actions to modify a calendar. | | [Restaurant](https://github.com/microsoft/TypeChat/tree/main/typescript/examples/restaurant) | An intelligent agent for taking orders at a restaurant. Similar to the coffee shop example, but uses a more complex schema to model more complex linguistic input. The prose files illustrate the line between simpler and more advanced language models in handling compound sentences, distractions, and corrections. This example also shows how we can use TypeScript to provide a user intent summary. | | [Math](https://github.com/microsoft/TypeChat/tree/main/typescript/examples/math) | Translate calculations into simple programs given an API that can perform the 4 basic mathematical operators. This example highlights TypeChat's program generation capabilities. | +| [Drawing](https://github.com/microsoft/TypeChat/tree/main/typescript/examples/drawing) | Translate drawing requests into structured shapes (boxes, ellipses, arrows), then render the result as SVG. | | [Music](https://github.com/microsoft/TypeChat/tree/main/typescript/examples/music) | An app for playing music, creating playlists, etc. on Spotify through natural language. Each user intent is translated into a series of actions in JSON which correspond to a simple dataflow program, where each step can consume data produced from previous step. | ## Step 1: Configure your development environment diff --git a/typescript/examples/drawing/README.md b/typescript/examples/drawing/README.md new file mode 100644 index 00000000..4f52235a --- /dev/null +++ b/typescript/examples/drawing/README.md @@ -0,0 +1,17 @@ +# Drawing + +The Drawing example mirrors the Python drawing sample from PR #238. It translates natural language requests into a [`Drawing`](./src/drawingSchema.ts) object containing boxes, ellipses, arrows, and unknown text. + +For each successful translation, it writes an SVG rendering to `dist/drawing.svg`. + +# Try Drawing +To run the Drawing example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). + +# Usage +Example prompts can be found in [`src/input.txt`](./src/input.txt). + +From the `drawing` directory: + +```sh +node ./dist/main.js ./dist/input.txt +``` diff --git a/typescript/examples/drawing/package.json b/typescript/examples/drawing/package.json new file mode 100644 index 00000000..1cc8d5e9 --- /dev/null +++ b/typescript/examples/drawing/package.json @@ -0,0 +1,24 @@ +{ + "name": "drawing", + "version": "0.0.1", + "private": true, + "description": "", + "main": "dist/main.js", + "scripts": { + "build": "tsc -p src", + "postbuild": "copyfiles -u 1 src/**/*Schema.ts src/**/*.txt dist" + }, + "author": "", + "license": "MIT", + "dependencies": { + "dotenv": "^16.3.1", + "find-config": "^1.0.0", + "typechat": "^0.1.0", + "typescript": "^5.3.3" + }, + "devDependencies": { + "@types/find-config": "1.0.4", + "@types/node": "^20.10.4", + "copyfiles": "^2.4.1" + } +} diff --git a/typescript/examples/drawing/src/drawingSchema.ts b/typescript/examples/drawing/src/drawingSchema.ts new file mode 100644 index 00000000..f627e084 --- /dev/null +++ b/typescript/examples/drawing/src/drawingSchema.ts @@ -0,0 +1,75 @@ +// Schema for a drawing with boxes, ellipses, arrows, and unknown text. + +export interface Style { + type: "Style"; + // Corner style for boxes. + corners?: "rounded" | "sharp"; + // Thickness of outlines and arrows. + line_thickness?: number; + // CSS color for outlines and arrows. + line_color?: string; + // CSS color used to fill boxes and ellipses. + fill_color?: string; + // Style of arrow lines. + line_style?: "solid" | "dashed" | "dotted"; +} + +export interface Box { + type: "Box"; + // X-coordinate of top left corner. + x: number; + // Y-coordinate of top left corner. + y: number; + // Width in pixels. + width: number; + // Height in pixels. + height: number; + // Optional label centered in the box. + text?: string; + // Optional style settings. + style?: Style; +} + +export interface Ellipse { + type: "Ellipse"; + // X-coordinate of top left corner of bounding box. + x: number; + // Y-coordinate of top left corner of bounding box. + y: number; + // Width in pixels. + width: number; + // Height in pixels. + height: number; + // Optional label centered in the ellipse. + text?: string; + // Optional style settings. + style?: Style; +} + +export interface Arrow { + type: "Arrow"; + // Starting X-coordinate. + start_x: number; + // Starting Y-coordinate. + start_y: number; + // Ending X-coordinate. + end_x: number; + // Ending Y-coordinate. + end_y: number; + // Optional style settings. + style?: Style; + // Optional arrowhead size hint. + head_size?: number; +} + +export interface UnknownText { + type: "UnknownText"; + // Text that was not understood. + text: string; +} + +export interface Drawing { + type: "Drawing"; + // Items in the drawing. + items: Array; +} diff --git a/typescript/examples/drawing/src/input.txt b/typescript/examples/drawing/src/input.txt new file mode 100644 index 00000000..72f46146 --- /dev/null +++ b/typescript/examples/drawing/src/input.txt @@ -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 diff --git a/typescript/examples/drawing/src/main.ts b/typescript/examples/drawing/src/main.ts new file mode 100644 index 00000000..75d7c737 --- /dev/null +++ b/typescript/examples/drawing/src/main.ts @@ -0,0 +1,43 @@ +import assert from "assert"; +import dotenv from "dotenv"; +import findConfig from "find-config"; +import fs from "fs"; +import path from "path"; +import { createJsonTranslator, createLanguageModel } from "typechat"; +import { processRequests } from "typechat/interactive"; +import { createTypeScriptJsonValidator } from "typechat/ts"; +import { Drawing } from "./drawingSchema"; +import { renderDrawingToSvg } from "./render"; + +const dotEnvPath = findConfig(".env"); +assert(dotEnvPath, ".env file not found!"); +dotenv.config({ path: dotEnvPath }); + +const model = createLanguageModel(process.env); +const schema = fs.readFileSync(path.join(__dirname, "drawingSchema.ts"), "utf8"); +const validator = createTypeScriptJsonValidator(schema, "Drawing"); +const translator = createJsonTranslator(model, validator); +const outputPath = path.join(__dirname, "drawing.svg"); + +const history: string[] = []; + +// Process requests interactively or from the input file specified on the command line +processRequests("🎨> ", process.argv[2], async (request) => { + history.push(request); + const response = await translator.translate(history.join("\n")); + if (!response.success) { + console.log(response.message); + return; + } + + const drawing = response.data; + console.log(JSON.stringify(drawing, undefined, 2)); + for (const item of drawing.items) { + if (item.type === "UnknownText") { + console.log(`Unknown text: ${item.text}`); + } + } + + fs.writeFileSync(outputPath, renderDrawingToSvg(drawing), "utf8"); + console.log(`Wrote ${outputPath}`); +}); diff --git a/typescript/examples/drawing/src/render.ts b/typescript/examples/drawing/src/render.ts new file mode 100644 index 00000000..38667cc4 --- /dev/null +++ b/typescript/examples/drawing/src/render.ts @@ -0,0 +1,98 @@ +import { Arrow, Box, Drawing, Ellipse, Style } from "./drawingSchema"; + +function escapeXml(text: string): string { + return text + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll("\"", """) + .replaceAll("'", "'"); +} + +function lineDash(style?: Style): string | undefined { + switch (style?.line_style) { + case "dashed": + return "6 4"; + case "dotted": + return "2 3"; + default: + return undefined; + } +} + +function stroke(style?: Style): string { + return style?.line_color ?? "black"; +} + +function strokeWidth(style?: Style): number { + return style?.line_thickness ?? 1; +} + +function fill(style?: Style): string { + return style?.fill_color ?? "none"; +} + +function renderBox(item: Box): string { + const rounded = item.style?.corners === "rounded"; + const text = item.text ? `${escapeXml(item.text)}` : ""; + return `${text}`; +} + +function renderEllipse(item: Ellipse): string { + const cx = item.x + item.width / 2; + const cy = item.y + item.height / 2; + const text = item.text ? `${escapeXml(item.text)}` : ""; + return `${text}`; +} + +function renderArrow(item: Arrow): string { + const dash = lineDash(item.style); + return ``; +} + +function getCanvasSize(drawing: Drawing): { width: number; height: number } { + let maxX = 800; + let maxY = 600; + for (const item of drawing.items) { + switch (item.type) { + case "Box": + case "Ellipse": + maxX = Math.max(maxX, item.x + item.width + 40); + maxY = Math.max(maxY, item.y + item.height + 40); + break; + case "Arrow": + maxX = Math.max(maxX, item.start_x + 40, item.end_x + 40); + maxY = Math.max(maxY, item.start_y + 40, item.end_y + 40); + break; + } + } + return { width: maxX, height: maxY }; +} + +export function renderDrawingToSvg(drawing: Drawing): string { + const { width, height } = getCanvasSize(drawing); + const shapes = drawing.items.flatMap((item) => { + switch (item.type) { + case "Box": + return [renderBox(item)]; + case "Ellipse": + return [renderEllipse(item)]; + case "Arrow": + return [renderArrow(item)]; + default: + return []; + } + }); + + return [ + ``, + "", + "", + "", + "", + "", + "", + ...shapes, + "", + ].join("\n"); +} diff --git a/typescript/examples/drawing/src/tsconfig.json b/typescript/examples/drawing/src/tsconfig.json new file mode 100644 index 00000000..0f8bbec6 --- /dev/null +++ b/typescript/examples/drawing/src/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "es2021", + "lib": ["es2021"], + "module": "node16", + "types": ["node"], + "outDir": "../dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "exactOptionalPropertyTypes": true, + "inlineSourceMap": true + } +} From 3ec350dc743bd758b06dd66bc19bbce7f3af0f96 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:34:57 +0000 Subject: [PATCH 2/4] Update package-lock.json to include drawing workspace --- typescript/package-lock.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 7b582146..4ee059c2 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -94,6 +94,21 @@ "copyfiles": "^2.4.1" } }, + "examples/drawing": { + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "dotenv": "^16.3.1", + "find-config": "^1.0.0", + "typechat": "^0.1.0", + "typescript": "^5.3.3" + }, + "devDependencies": { + "@types/find-config": "1.0.4", + "@types/node": "^20.10.4", + "copyfiles": "^2.4.1" + } + }, "examples/healthData": { "name": "health-data", "version": "0.0.1", @@ -1022,6 +1037,10 @@ "url": "https://dotenvx.com" } }, + "node_modules/drawing": { + "resolved": "examples/drawing", + "link": true + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", From 69f08cb87041694752c8b84f2ae2e72bcb4ceaad Mon Sep 17 00:00:00 2001 From: robgruen Date: Mon, 1 Jun 2026 14:49:10 -0700 Subject: [PATCH 3/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- typescript/examples/drawing/src/main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typescript/examples/drawing/src/main.ts b/typescript/examples/drawing/src/main.ts index 75d7c737..bab1a0bd 100644 --- a/typescript/examples/drawing/src/main.ts +++ b/typescript/examples/drawing/src/main.ts @@ -23,12 +23,12 @@ const history: string[] = []; // Process requests interactively or from the input file specified on the command line processRequests("🎨> ", process.argv[2], async (request) => { - history.push(request); - const response = await translator.translate(history.join("\n")); + const response = await translator.translate([...history, request].join("\n")); if (!response.success) { console.log(response.message); return; } + history.push(request); const drawing = response.data; console.log(JSON.stringify(drawing, undefined, 2)); From 457bbab250edad711f0fed3515af110b1e3d0244 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:52:47 +0000 Subject: [PATCH 4/4] Rename drawing schema properties to camelCase --- .../examples/drawing/src/drawingSchema.ts | 18 +++++++++--------- typescript/examples/drawing/src/render.ts | 14 +++++++------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/typescript/examples/drawing/src/drawingSchema.ts b/typescript/examples/drawing/src/drawingSchema.ts index f627e084..f00c05e2 100644 --- a/typescript/examples/drawing/src/drawingSchema.ts +++ b/typescript/examples/drawing/src/drawingSchema.ts @@ -5,13 +5,13 @@ export interface Style { // Corner style for boxes. corners?: "rounded" | "sharp"; // Thickness of outlines and arrows. - line_thickness?: number; + lineThickness?: number; // CSS color for outlines and arrows. - line_color?: string; + lineColor?: string; // CSS color used to fill boxes and ellipses. - fill_color?: string; + fillColor?: string; // Style of arrow lines. - line_style?: "solid" | "dashed" | "dotted"; + lineStyle?: "solid" | "dashed" | "dotted"; } export interface Box { @@ -49,17 +49,17 @@ export interface Ellipse { export interface Arrow { type: "Arrow"; // Starting X-coordinate. - start_x: number; + startX: number; // Starting Y-coordinate. - start_y: number; + startY: number; // Ending X-coordinate. - end_x: number; + endX: number; // Ending Y-coordinate. - end_y: number; + endY: number; // Optional style settings. style?: Style; // Optional arrowhead size hint. - head_size?: number; + headSize?: number; } export interface UnknownText { diff --git a/typescript/examples/drawing/src/render.ts b/typescript/examples/drawing/src/render.ts index 38667cc4..52d785f1 100644 --- a/typescript/examples/drawing/src/render.ts +++ b/typescript/examples/drawing/src/render.ts @@ -10,7 +10,7 @@ function escapeXml(text: string): string { } function lineDash(style?: Style): string | undefined { - switch (style?.line_style) { + switch (style?.lineStyle) { case "dashed": return "6 4"; case "dotted": @@ -21,15 +21,15 @@ function lineDash(style?: Style): string | undefined { } function stroke(style?: Style): string { - return style?.line_color ?? "black"; + return style?.lineColor ?? "black"; } function strokeWidth(style?: Style): number { - return style?.line_thickness ?? 1; + return style?.lineThickness ?? 1; } function fill(style?: Style): string { - return style?.fill_color ?? "none"; + return style?.fillColor ?? "none"; } function renderBox(item: Box): string { @@ -47,7 +47,7 @@ function renderEllipse(item: Ellipse): string { function renderArrow(item: Arrow): string { const dash = lineDash(item.style); - return ``; + return ``; } function getCanvasSize(drawing: Drawing): { width: number; height: number } { @@ -61,8 +61,8 @@ function getCanvasSize(drawing: Drawing): { width: number; height: number } { maxY = Math.max(maxY, item.y + item.height + 40); break; case "Arrow": - maxX = Math.max(maxX, item.start_x + 40, item.end_x + 40); - maxY = Math.max(maxY, item.start_y + 40, item.end_y + 40); + maxX = Math.max(maxX, item.startX + 40, item.endX + 40); + maxY = Math.max(maxY, item.startY + 40, item.endY + 40); break; } }