|
4 | 4 | "emoji": "🔲", |
5 | 5 | "title": "Interactive Canvas", |
6 | 6 | "iconURL": "https://custom.typingmind.com/assets/plugins/interactive-canvas.webp", |
7 | | - "openaiSpec": { |
8 | | - "name": "render_interactive_canvas", |
9 | | - "parameters": { |
10 | | - "type": "object", |
11 | | - "required": [ |
12 | | - "htmlSource" |
13 | | - ], |
14 | | - "properties": { |
15 | | - "htmlSource": { |
16 | | - "type": "string", |
17 | | - "description": "The full HTML source to render to the canvas. Must be a complete document (e.g. starting with <!DOCTYPE html> or <html>). After this initial render, prefer the `update_interactive_canvas` tool to make small edits — it is much faster and uses far fewer tokens than re-emitting the entire HTML." |
| 7 | + "pluginFunctions": [ |
| 8 | + { |
| 9 | + "id": "LEGACY_DEFAULT_FUNCTION", |
| 10 | + "name": "Render", |
| 11 | + "implementationType": "javascript", |
| 12 | + "outputType": "render_html", |
| 13 | + "openaiSpec": { |
| 14 | + "name": "render_interactive_canvas", |
| 15 | + "parameters": { |
| 16 | + "type": "object", |
| 17 | + "required": [ |
| 18 | + "htmlSource" |
| 19 | + ], |
| 20 | + "properties": { |
| 21 | + "htmlSource": { |
| 22 | + "type": "string", |
| 23 | + "description": "The full HTML source to render to the canvas. Must be a complete document (e.g. starting with <!DOCTYPE html> or <html>). To use an image from the chat attachments, write its URL as `tm-attachment://<id>` with an attachment id from the <CHAT_ATTACHMENTS> list, e.g. `<img src=\"tm-attachment://<id>\">` or `background-image: url(tm-attachment://<id>)`; it is replaced with the real image when the app is displayed. Never embed the image data yourself. After this initial render, prefer the `update_interactive_canvas` tool to make small edits — it is much faster and uses far fewer tokens than re-emitting the entire HTML." |
| 24 | + }, |
| 25 | + "canvasHeight": { |
| 26 | + "type": "number", |
| 27 | + "description": "The height of the canvas in pixels. Default is 500." |
| 28 | + } |
| 29 | + } |
18 | 30 | }, |
19 | | - "canvasHeight": { |
20 | | - "type": "number", |
21 | | - "description": "The height of the canvas in pixels. Default is 500." |
22 | | - } |
23 | | - } |
| 31 | + "description": "Render an interactive canvas with HTML source to the user interface. The HTML source can include JavaScript and CSS to create interactive elements. This can be used to create custom user interfaces, games, demos, charts, and more. The canvas width is always 100% of the container width, and the height can be specified in pixels. Use this for the FIRST render and for full rewrites. For small follow-up tweaks to a canvas you have already rendered, prefer `update_interactive_canvas`. Use search_interactive_canvas_source and read_interactive_canvas_lines to inspect the latest source before editing. Use read_interactive_canvas_source only when you need the full source." |
| 32 | + }, |
| 33 | + "code": "async function render_interactive_canvas(params, userSettings) {\n return params.htmlSource;\n}\n" |
24 | 34 | }, |
25 | | - "description": "Render an interactive canvas with HTML source to the user interface. The HTML source can include JavaScript and CSS to create interactive elements. This can be used to create custom user interfaces, games, demos, charts, and more. The canvas width is always 100% of the container width, and the height can be specified in pixels. Use this for the FIRST render and for full rewrites. For small follow-up tweaks to a canvas you have already rendered, prefer `update_interactive_canvas`." |
26 | | - }, |
27 | | - "implementationType": "javascript", |
28 | | - "outputType": "render_html", |
29 | | - "pluginFunctions": [ |
30 | 35 | { |
31 | 36 | "id": "8c83cae9-7b82-4fcf-a58c-beb1ede6dbd4", |
32 | | - "name": "Update Interactive Canvas", |
| 37 | + "name": "Update", |
33 | 38 | "implementationType": "javascript", |
34 | 39 | "outputType": "render_html", |
35 | 40 | "openaiSpec": { |
|
64 | 69 | } |
65 | 70 | } |
66 | 71 | }, |
67 | | - "description": "Apply small, targeted edits to the most recently rendered interactive canvas instead of re-emitting the full HTML. Each edit performs an exact, literal find-and-replace on the current HTML. Use this WHENEVER you only need to tweak parts of an existing canvas you previously rendered with `render_interactive_canvas` — it is much faster and uses far fewer output tokens than re-emitting the entire HTML.\n\nRules:\n- Do NOT use this for the very first render — use `render_interactive_canvas` instead.\n- Each `oldText` must match EXACTLY ONCE in the current HTML; include enough surrounding context to make it unique. If a match is ambiguous (matches more than once) or missing, the call will fail and you should retry with more context, or fall back to `render_interactive_canvas` with the full updated HTML.\n- Edits are applied in order; later edits see the result of earlier ones.\n- Prefer many small edits over one big rewrite — it makes failures localised and easier to retry." |
| 72 | + "description": "Apply small, targeted edits to the most recently rendered interactive canvas instead of re-emitting the full HTML. Each edit performs an exact, literal find-and-replace on the current HTML. Use this WHENEVER you only need to tweak parts of an existing canvas you previously rendered with `render_interactive_canvas` — it is much faster and uses far fewer output tokens than re-emitting the entire HTML.\n\nRules:\n- Do NOT use this for the very first render — use `render_interactive_canvas` instead.\n- Each `oldText` must match EXACTLY ONCE in the current HTML; include enough surrounding context to make it unique. If a match is ambiguous (matches more than once) or missing, the call will fail and you should retry with more context, or fall back to `render_interactive_canvas` with the full updated HTML.\n- Edits are applied in order; later edits see the result of earlier ones.\n- Prefer many small edits over one big rewrite — it makes failures localised and easier to retry.\n- Use search_interactive_canvas_source to locate code, then read_interactive_canvas_lines for the surrounding source before choosing oldText. Line number prefixes in those results are not part of the source." |
68 | 73 | }, |
69 | 74 | "code": "async function update_interactive_canvas(params, userSettings, authorizedResources) {\n var previous =\n (authorizedResources && authorizedResources.previousRunOutput) || '';\n\n if (typeof previous !== 'string' || !previous.length) {\n throw new Error(\n 'No previously rendered interactive canvas found to patch. Call `render_interactive_canvas` first to render the initial HTML, then use `update_interactive_canvas` for follow-up edits.',\n );\n }\n\n var edits = params && params.edits;\n if (!Array.isArray(edits) || edits.length === 0) {\n throw new Error(\n '`edits` must be a non-empty array of {oldText, newText} objects.',\n );\n }\n\n var html = previous;\n for (var i = 0; i < edits.length; i++) {\n var edit = edits[i] || {};\n var oldText = edit.oldText;\n var newText = edit.newText;\n\n if (typeof oldText !== 'string' || typeof newText !== 'string') {\n throw new Error(\n 'Edit #' +\n (i + 1) +\n ' must have string `oldText` and `newText` properties.',\n );\n }\n if (!oldText.length) {\n throw new Error(\n 'Edit #' +\n (i + 1) +\n ' has an empty `oldText`. Provide a non-empty substring to match.',\n );\n }\n\n var firstIndex = html.indexOf(oldText);\n if (firstIndex === -1) {\n throw new Error(\n 'Edit #' +\n (i + 1) +\n \": `oldText` was not found in the current HTML. Make sure it is copied EXACTLY (including whitespace and casing) from the current HTML, or fall back to `render_interactive_canvas` with the full updated HTML.\",\n );\n }\n var lastIndex = html.lastIndexOf(oldText);\n if (firstIndex !== lastIndex) {\n throw new Error(\n 'Edit #' +\n (i + 1) +\n \": `oldText` matched more than once in the current HTML. Include more surrounding context so it matches exactly once.\",\n );\n }\n\n html =\n html.slice(0, firstIndex) + newText + html.slice(firstIndex + oldText.length);\n }\n\n return html;\n}\n" |
| 75 | + }, |
| 76 | + { |
| 77 | + "id": "b369f7e3-96ec-4d8c-9b86-723a5d09e5a4", |
| 78 | + "name": "Read Source", |
| 79 | + "implementationType": "javascript", |
| 80 | + "outputType": "respond_to_ai", |
| 81 | + "openaiSpec": { |
| 82 | + "name": "read_interactive_canvas_source", |
| 83 | + "parameters": { |
| 84 | + "type": "object", |
| 85 | + "properties": {} |
| 86 | + }, |
| 87 | + "description": "Read the full, exact HTML source of the latest interactive canvas rendered or updated by this plugin. Does not change the canvas. Attachment placeholders remain unchanged. Prefer search_interactive_canvas_source and read_interactive_canvas_lines when you only need part of a large canvas." |
| 88 | + }, |
| 89 | + "code": "async function read_interactive_canvas_source(params, userSettings, authorizedResources) {\n const source = authorizedResources?.previousRunOutput;\n if (typeof source !== 'string' || !source.length) {\n throw new Error('No interactive canvas source found. Call render_interactive_canvas first.');\n }\n return source;\n}\n" |
| 90 | + }, |
| 91 | + { |
| 92 | + "id": "b77c85ff-fa0d-4354-ad49-eb699a9fc01b", |
| 93 | + "name": "Search Source", |
| 94 | + "implementationType": "javascript", |
| 95 | + "outputType": "respond_to_ai", |
| 96 | + "openaiSpec": { |
| 97 | + "name": "search_interactive_canvas_source", |
| 98 | + "parameters": { |
| 99 | + "type": "object", |
| 100 | + "required": [ |
| 101 | + "keyword" |
| 102 | + ], |
| 103 | + "properties": { |
| 104 | + "keyword": { |
| 105 | + "type": "string", |
| 106 | + "minLength": 1, |
| 107 | + "description": "Literal text to find within each line. Matching is case-insensitive; regular expressions are not supported." |
| 108 | + }, |
| 109 | + "limit": { |
| 110 | + "type": "integer", |
| 111 | + "minimum": 1, |
| 112 | + "maximum": 200, |
| 113 | + "default": 50, |
| 114 | + "description": "Maximum number of matching lines to return, starting from the top of the source. Defaults to 50." |
| 115 | + } |
| 116 | + } |
| 117 | + }, |
| 118 | + "description": "Search the latest interactive canvas HTML for a literal keyword, ignoring case. Return matching lines with 1-based line numbers and the total match count. Does not change the canvas. Use read_interactive_canvas_lines to read nearby lines before editing. Line number prefixes are not part of the source." |
| 119 | + }, |
| 120 | + "code": "async function search_interactive_canvas_source(\n params,\n userSettings,\n authorizedResources,\n) {\n const source = authorizedResources?.previousRunOutput;\n if (typeof source !== 'string' || !source.length) {\n throw new Error('No interactive canvas source found. Call render_interactive_canvas first.');\n }\n if (typeof params.keyword !== 'string' || !params.keyword.trim()) {\n throw new Error('keyword must be a non-empty string.');\n }\n const limit = params.limit ?? 50;\n if (!Number.isInteger(limit) || limit < 1 || limit > 200) {\n throw new Error('limit must be an integer from 1 to 200.');\n }\n\n const lines = source.split(/\\r\\n|\\n|\\r/);\n const keyword = params.keyword.toLowerCase();\n const matches = [];\n let totalMatches = 0;\n lines.forEach((line, index) => {\n if (line.toLowerCase().includes(keyword)) {\n totalMatches++;\n if (matches.length < limit) {\n matches.push(`${index + 1}: ${line}`);\n }\n }\n });\n\n return [\n `${totalMatches} matching lines; showing ${matches.length}. Source has ${lines.length} lines.`,\n ...matches,\n ].join('\\n');\n}\n" |
| 121 | + }, |
| 122 | + { |
| 123 | + "id": "58cb6d01-cad9-4e86-9c99-6262bef17af2", |
| 124 | + "name": "Read Lines", |
| 125 | + "implementationType": "javascript", |
| 126 | + "outputType": "respond_to_ai", |
| 127 | + "openaiSpec": { |
| 128 | + "name": "read_interactive_canvas_lines", |
| 129 | + "parameters": { |
| 130 | + "type": "object", |
| 131 | + "required": [ |
| 132 | + "startLine", |
| 133 | + "endLine" |
| 134 | + ], |
| 135 | + "properties": { |
| 136 | + "startLine": { |
| 137 | + "type": "integer", |
| 138 | + "minimum": 1, |
| 139 | + "description": "First line to read, starting at 1." |
| 140 | + }, |
| 141 | + "endLine": { |
| 142 | + "type": "integer", |
| 143 | + "minimum": 1, |
| 144 | + "description": "Last line to read, inclusive. Must be at least startLine." |
| 145 | + } |
| 146 | + } |
| 147 | + }, |
| 148 | + "description": "Read an inclusive range of lines from the latest interactive canvas HTML, with 1-based line numbers. Does not change the canvas. endLine is capped at the final source line. Line number prefixes are not part of the source; omit them when building update_interactive_canvas edits." |
| 149 | + }, |
| 150 | + "code": "async function read_interactive_canvas_lines(params, userSettings, authorizedResources) {\n const source = authorizedResources?.previousRunOutput;\n if (typeof source !== 'string' || !source.length) {\n throw new Error('No interactive canvas source found. Call render_interactive_canvas first.');\n }\n const { startLine, endLine } = params;\n if (\n !Number.isInteger(startLine) ||\n !Number.isInteger(endLine) ||\n startLine < 1 ||\n endLine < startLine\n ) {\n throw new Error('Use integer line numbers with 1 <= startLine <= endLine.');\n }\n\n const lines = source.split(/\\r\\n|\\n|\\r/);\n if (startLine > lines.length) {\n throw new Error(\n `startLine exceeds the source length of ${lines.length} lines.`,\n );\n }\n const lastLine = Math.min(endLine, lines.length);\n return [\n `Lines ${startLine}-${lastLine} of ${lines.length}:`,\n ...lines\n .slice(startLine - 1, lastLine)\n .map((line, index) => `${startLine + index}: ${line}`),\n ].join('\\n');\n}\n" |
70 | 151 | } |
| 152 | + ], |
| 153 | + "previousRunOutputTypes": [ |
| 154 | + "render_html", |
| 155 | + "html" |
71 | 156 | ] |
72 | 157 | } |
0 commit comments