agent-canvas 0.1.32 → 0.1.34
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.
- package/bun.lock +3 -3
- package/daemon/client/components/Callout.tsx +2 -1
- package/daemon/client/components/Choice.tsx +45 -29
- package/daemon/client/components/Note.tsx +2 -1
- package/daemon/client/components/Section.tsx +2 -1
- package/daemon/client/components/Task.tsx +2 -1
- package/daemon/client/components/renderMarkdownChildren.tsx +52 -0
- package/daemon/client/exportMarkdown.ts +6 -0
- package/daemon/dist/client.js +6 -6
- package/daemon/dist/components.js +56 -56
- package/daemon/src/compiler.ts +5 -4
- package/package.json +2 -2
- package/workers/canvas-share/public/assets/client.js +6 -6
- package/workers/canvas-share/public/assets/components.js +56 -56
package/bun.lock
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
"": {
|
|
6
6
|
"name": "agent-canvas",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@aj-archipelago/merval": "^1.0.7",
|
|
9
8
|
"marked": "^17.0.4",
|
|
9
|
+
"mermaid-parser-bundle": "^0.1.2",
|
|
10
10
|
"preact": "^10.25.0",
|
|
11
11
|
"preact-render-to-string": "^6.6.6",
|
|
12
12
|
},
|
|
@@ -34,8 +34,6 @@
|
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
36
|
"packages": {
|
|
37
|
-
"@aj-archipelago/merval": ["@aj-archipelago/merval@1.0.7", "", {}, "sha512-z2/cmv5t04fUxlPJMMV0j9o9ZFG5PtuQq3pSpwKRPVpsZvAfHp7zuUJfyKcf+Uj9YKGWfnLeZr23DkwhT00iSg=="],
|
|
38
|
-
|
|
39
37
|
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="],
|
|
40
38
|
|
|
41
39
|
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg=="],
|
|
@@ -306,6 +304,8 @@
|
|
|
306
304
|
|
|
307
305
|
"marked": ["marked@17.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA=="],
|
|
308
306
|
|
|
307
|
+
"mermaid-parser-bundle": ["mermaid-parser-bundle@0.1.2", "", {}, "sha512-vQ4gNKChTZR25183+PmWcQ8zaqN/e7l+jDi9VUH+hxe5t8DymvWrROIR35LrEObwnFqUjo7RKUKmHeaPftWudg=="],
|
|
308
|
+
|
|
309
309
|
"miniflare": ["miniflare@4.20260409.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.4", "workerd": "1.20260409.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-ayl6To4av0YuXsSivGgWLj+Ug8xZ0Qz3sGV8+Ok2LhNVl6m8m5ktEBM3LX9iT9MtLZRJwBlJrKcraNs/DlZQfA=="],
|
|
310
310
|
|
|
311
311
|
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { renderMarkdownChildren } from "./renderMarkdownChildren";
|
|
2
3
|
|
|
3
4
|
interface CalloutProps {
|
|
4
5
|
type?: "info" | "warning" | "danger" | "tip";
|
|
@@ -19,7 +20,7 @@ export function Callout({ type = "info", children }: CalloutProps) {
|
|
|
19
20
|
<span className={`${cfg.iconColor} opacity-70 text-base leading-relaxed flex-shrink-0 mt-px`}>
|
|
20
21
|
{cfg.icon}
|
|
21
22
|
</span>
|
|
22
|
-
<div className="text-body text-text-secondary leading-relaxed">{children}</div>
|
|
23
|
+
<div className="text-body text-text-secondary leading-relaxed">{renderMarkdownChildren(children)}</div>
|
|
23
24
|
</div>
|
|
24
25
|
);
|
|
25
26
|
}
|
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState } from "react";
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { useAnnotations } from "#canvas/runtime";
|
|
3
3
|
import { ResponseNote } from "./ResponseNote";
|
|
4
4
|
|
|
5
|
+
export type ChoiceOption = string | { value: string; label?: string };
|
|
6
|
+
|
|
7
|
+
type NormalizedOption = { value: string; label: string };
|
|
8
|
+
|
|
9
|
+
function normalizeOptions(options: ChoiceOption[]): NormalizedOption[] {
|
|
10
|
+
return options.map((opt) =>
|
|
11
|
+
typeof opt === "string"
|
|
12
|
+
? { value: opt, label: opt }
|
|
13
|
+
: { value: opt.value, label: opt.label ?? opt.value },
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
/** Radio — pick one option */
|
|
6
18
|
interface ChoiceProps {
|
|
7
19
|
id: string;
|
|
8
20
|
label: string;
|
|
9
|
-
options:
|
|
21
|
+
options: ChoiceOption[];
|
|
10
22
|
required?: boolean;
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
export function Choice({ id, label, options, required }: ChoiceProps) {
|
|
14
26
|
const { responses, setResponse } = useAnnotations();
|
|
27
|
+
const normalized = useMemo(() => normalizeOptions(options), [options]);
|
|
15
28
|
const current = responses.get(id);
|
|
16
29
|
const selected = current?.value as string | undefined;
|
|
17
30
|
const note = current?.note || "";
|
|
@@ -19,11 +32,11 @@ export function Choice({ id, label, options, required }: ChoiceProps) {
|
|
|
19
32
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
20
33
|
|
|
21
34
|
if (!responses.has(id)) {
|
|
22
|
-
setResponse(id, { id, type: "radio", label, value: null, options, required });
|
|
35
|
+
setResponse(id, { id, type: "radio", label, value: null, options: normalized.map((o) => o.value), required });
|
|
23
36
|
}
|
|
24
37
|
|
|
25
|
-
const handleSelect = (
|
|
26
|
-
setResponse(id, { ...current!, value
|
|
38
|
+
const handleSelect = (value: string) => {
|
|
39
|
+
setResponse(id, { ...current!, value });
|
|
27
40
|
};
|
|
28
41
|
|
|
29
42
|
// Keyboard selection via custom event from PlanRenderer
|
|
@@ -32,12 +45,13 @@ export function Choice({ id, label, options, required }: ChoiceProps) {
|
|
|
32
45
|
if (!el) return;
|
|
33
46
|
const handler = (e: Event) => {
|
|
34
47
|
const target = (e.target as HTMLElement).closest("[data-md-label]");
|
|
35
|
-
const
|
|
36
|
-
|
|
48
|
+
const labelAttr = target?.getAttribute("data-md-label");
|
|
49
|
+
const match = normalized.find((o) => o.label === labelAttr);
|
|
50
|
+
if (match) handleSelect(match.value);
|
|
37
51
|
};
|
|
38
52
|
el.addEventListener("kb-select", handler);
|
|
39
53
|
return () => el.removeEventListener("kb-select", handler);
|
|
40
|
-
}, [
|
|
54
|
+
}, [normalized, handleSelect]);
|
|
41
55
|
|
|
42
56
|
const showError = current?.required && !selected;
|
|
43
57
|
|
|
@@ -48,26 +62,26 @@ export function Choice({ id, label, options, required }: ChoiceProps) {
|
|
|
48
62
|
{required && <span className="text-[10px] text-accent-red font-body">*</span>}
|
|
49
63
|
</div>
|
|
50
64
|
<div className="space-y-1">
|
|
51
|
-
{
|
|
65
|
+
{normalized.map(({ value, label: optLabel }) => (
|
|
52
66
|
<label
|
|
53
|
-
key={
|
|
67
|
+
key={value}
|
|
54
68
|
data-md="choice-option"
|
|
55
|
-
data-md-label={
|
|
56
|
-
onClick={() => handleSelect(
|
|
69
|
+
data-md-label={optLabel}
|
|
70
|
+
onClick={() => handleSelect(value)}
|
|
57
71
|
className={`flex items-center gap-2.5 px-3 py-2 rounded-lg cursor-pointer transition-all duration-150 ${
|
|
58
|
-
selected ===
|
|
72
|
+
selected === value
|
|
59
73
|
? "bg-highlight-selected"
|
|
60
74
|
: "hover:bg-bg-elevated"
|
|
61
75
|
}`}
|
|
62
76
|
>
|
|
63
77
|
<span className={`w-4 h-4 rounded-full border-[1.5px] flex items-center justify-center flex-shrink-0 transition-all ${
|
|
64
|
-
selected ===
|
|
78
|
+
selected === value
|
|
65
79
|
? "border-accent-amber bg-accent-amber"
|
|
66
80
|
: "border-border-strong"
|
|
67
81
|
}`}>
|
|
68
|
-
{selected ===
|
|
82
|
+
{selected === value && <span className="w-1.5 h-1.5 rounded-full bg-text-inverse" />}
|
|
69
83
|
</span>
|
|
70
|
-
<span className="text-[13px] font-body text-text-secondary">{
|
|
84
|
+
<span className="text-[13px] font-body text-text-secondary">{optLabel}</span>
|
|
71
85
|
</label>
|
|
72
86
|
))}
|
|
73
87
|
</div>
|
|
@@ -81,12 +95,13 @@ export function Choice({ id, label, options, required }: ChoiceProps) {
|
|
|
81
95
|
interface MultiChoiceProps {
|
|
82
96
|
id: string;
|
|
83
97
|
label: string;
|
|
84
|
-
options:
|
|
98
|
+
options: ChoiceOption[];
|
|
85
99
|
required?: boolean;
|
|
86
100
|
}
|
|
87
101
|
|
|
88
102
|
export function MultiChoice({ id, label, options, required }: MultiChoiceProps) {
|
|
89
103
|
const { responses, setResponse } = useAnnotations();
|
|
104
|
+
const normalized = useMemo(() => normalizeOptions(options), [options]);
|
|
90
105
|
const current = responses.get(id);
|
|
91
106
|
const selected: string[] = (current?.value as string[]) || [];
|
|
92
107
|
const note = current?.note || "";
|
|
@@ -94,11 +109,11 @@ export function MultiChoice({ id, label, options, required }: MultiChoiceProps)
|
|
|
94
109
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
95
110
|
|
|
96
111
|
if (!responses.has(id)) {
|
|
97
|
-
setResponse(id, { id, type: "checkbox", label, value: [], options, required });
|
|
112
|
+
setResponse(id, { id, type: "checkbox", label, value: [], options: normalized.map((o) => o.value), required });
|
|
98
113
|
}
|
|
99
114
|
|
|
100
|
-
const toggle = (
|
|
101
|
-
const next = selected.includes(
|
|
115
|
+
const toggle = (value: string) => {
|
|
116
|
+
const next = selected.includes(value) ? selected.filter((o) => o !== value) : [...selected, value];
|
|
102
117
|
setResponse(id, { ...current!, value: next });
|
|
103
118
|
};
|
|
104
119
|
|
|
@@ -108,12 +123,13 @@ export function MultiChoice({ id, label, options, required }: MultiChoiceProps)
|
|
|
108
123
|
if (!el) return;
|
|
109
124
|
const handler = (e: Event) => {
|
|
110
125
|
const target = (e.target as HTMLElement).closest("[data-md-label]");
|
|
111
|
-
const
|
|
112
|
-
|
|
126
|
+
const labelAttr = target?.getAttribute("data-md-label");
|
|
127
|
+
const match = normalized.find((o) => o.label === labelAttr);
|
|
128
|
+
if (match) toggle(match.value);
|
|
113
129
|
};
|
|
114
130
|
el.addEventListener("kb-select", handler);
|
|
115
131
|
return () => el.removeEventListener("kb-select", handler);
|
|
116
|
-
}, [
|
|
132
|
+
}, [normalized, toggle]);
|
|
117
133
|
|
|
118
134
|
const showError = current?.required && selected.length === 0;
|
|
119
135
|
|
|
@@ -124,14 +140,14 @@ export function MultiChoice({ id, label, options, required }: MultiChoiceProps)
|
|
|
124
140
|
{required && <span className="text-[10px] text-accent-red font-body">*</span>}
|
|
125
141
|
</div>
|
|
126
142
|
<div className="space-y-1">
|
|
127
|
-
{
|
|
128
|
-
const checked = selected.includes(
|
|
143
|
+
{normalized.map(({ value, label: optLabel }) => {
|
|
144
|
+
const checked = selected.includes(value);
|
|
129
145
|
return (
|
|
130
146
|
<label
|
|
131
|
-
key={
|
|
147
|
+
key={value}
|
|
132
148
|
data-md="multichoice-option"
|
|
133
|
-
data-md-label={
|
|
134
|
-
onClick={() => toggle(
|
|
149
|
+
data-md-label={optLabel}
|
|
150
|
+
onClick={() => toggle(value)}
|
|
135
151
|
className={`flex items-center gap-2.5 px-3 py-2 rounded-lg cursor-pointer transition-all duration-150 ${
|
|
136
152
|
checked ? "bg-highlight-selected" : "hover:bg-bg-elevated"
|
|
137
153
|
}`}
|
|
@@ -147,7 +163,7 @@ export function MultiChoice({ id, label, options, required }: MultiChoiceProps)
|
|
|
147
163
|
</svg>
|
|
148
164
|
)}
|
|
149
165
|
</span>
|
|
150
|
-
<span className="text-[13px] font-body text-text-secondary">{
|
|
166
|
+
<span className="text-[13px] font-body text-text-secondary">{optLabel}</span>
|
|
151
167
|
</label>
|
|
152
168
|
);
|
|
153
169
|
})}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { renderMarkdownChildren } from "./renderMarkdownChildren";
|
|
2
3
|
|
|
3
4
|
interface NoteProps {
|
|
4
5
|
children?: React.ReactNode;
|
|
@@ -7,7 +8,7 @@ interface NoteProps {
|
|
|
7
8
|
export function Note({ children }: NoteProps) {
|
|
8
9
|
return (
|
|
9
10
|
<div className="relative bg-bg-elevated rounded-lg px-5 py-4 mt-3 text-body text-text-secondary italic leading-relaxed" data-md="note">
|
|
10
|
-
{children}
|
|
11
|
+
{renderMarkdownChildren(children)}
|
|
11
12
|
</div>
|
|
12
13
|
);
|
|
13
14
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
|
+
import { renderMarkdownChildren } from "./renderMarkdownChildren";
|
|
2
3
|
|
|
3
4
|
interface SectionProps {
|
|
4
5
|
title: string;
|
|
@@ -26,7 +27,7 @@ export function Section({ title, children }: SectionProps) {
|
|
|
26
27
|
{title}
|
|
27
28
|
</h2>
|
|
28
29
|
</div>
|
|
29
|
-
<div className={collapsed ? "mt-6 hidden" : "mt-6"}>{children}</div>
|
|
30
|
+
<div className={collapsed ? "mt-6 hidden" : "mt-6"}>{renderMarkdownChildren(children)}</div>
|
|
30
31
|
</div>
|
|
31
32
|
);
|
|
32
33
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { renderMarkdownChildren } from "./renderMarkdownChildren";
|
|
2
3
|
|
|
3
4
|
type Status = "todo" | "done" | "blocked" | "in-progress";
|
|
4
5
|
type BadgeVariant = "info" | "success" | "warning" | "danger" | "neutral";
|
|
@@ -61,7 +62,7 @@ export function Item({ id, label, status, badge, badgeVariant = "neutral", child
|
|
|
61
62
|
)}
|
|
62
63
|
</div>
|
|
63
64
|
{children && (
|
|
64
|
-
<div className="text-body text-text-secondary mt-1 leading-relaxed">{children}</div>
|
|
65
|
+
<div className="text-body text-text-secondary mt-1 leading-relaxed">{renderMarkdownChildren(children)}</div>
|
|
65
66
|
)}
|
|
66
67
|
</div>
|
|
67
68
|
</div>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { marked } from "marked";
|
|
3
|
+
|
|
4
|
+
marked.setOptions({ gfm: true, breaks: false });
|
|
5
|
+
|
|
6
|
+
function renderTextAsMarkdown(text: string, key: string): React.ReactNode {
|
|
7
|
+
const trimmed = text.trim();
|
|
8
|
+
if (!trimmed) return null;
|
|
9
|
+
const html = marked.parse(trimmed, { async: false }) as string;
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
key={key}
|
|
13
|
+
className="prose-canvas"
|
|
14
|
+
data-md="markdown"
|
|
15
|
+
data-md-source={trimmed}
|
|
16
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Auto-render markdown for string children, passing JSX children through.
|
|
23
|
+
* Consecutive string runs are grouped into a single markdown block so multi-paragraph
|
|
24
|
+
* prose between JSX elements renders correctly.
|
|
25
|
+
*/
|
|
26
|
+
export function renderMarkdownChildren(children: React.ReactNode): React.ReactNode {
|
|
27
|
+
const kids = React.Children.toArray(children);
|
|
28
|
+
if (kids.length === 0) return null;
|
|
29
|
+
|
|
30
|
+
const result: React.ReactNode[] = [];
|
|
31
|
+
let buffer = "";
|
|
32
|
+
let bufferIdx = 0;
|
|
33
|
+
|
|
34
|
+
const flush = () => {
|
|
35
|
+
if (buffer) {
|
|
36
|
+
const node = renderTextAsMarkdown(buffer, `md-${bufferIdx++}`);
|
|
37
|
+
if (node) result.push(node);
|
|
38
|
+
buffer = "";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
for (const child of kids) {
|
|
43
|
+
if (typeof child === "string" || typeof child === "number") {
|
|
44
|
+
buffer += String(child);
|
|
45
|
+
} else {
|
|
46
|
+
flush();
|
|
47
|
+
result.push(child);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
flush();
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
@@ -70,6 +70,7 @@ function walkNode(el: HTMLElement, lines: string[], depth: number) {
|
|
|
70
70
|
case "codeblock": return handleCodeBlock(el, lines);
|
|
71
71
|
case "callout": return handleCallout(el, lines);
|
|
72
72
|
case "note": return handleNote(el, lines);
|
|
73
|
+
case "markdown": return handleMarkdown(el, lines);
|
|
73
74
|
case "table": return handleTable(el, lines);
|
|
74
75
|
case "checklist": return handleChecklist(el, lines);
|
|
75
76
|
case "diff": return handleDiff(el, lines);
|
|
@@ -208,6 +209,11 @@ function handleMermaid(el: HTMLElement, lines: string[]) {
|
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
211
|
|
|
212
|
+
function handleMarkdown(el: HTMLElement, lines: string[]) {
|
|
213
|
+
const source = el.getAttribute("data-md-source") || "";
|
|
214
|
+
if (source) lines.push(source, "");
|
|
215
|
+
}
|
|
216
|
+
|
|
211
217
|
function handleChoice(el: HTMLElement, lines: string[]) {
|
|
212
218
|
const label = el.getAttribute("data-md-label") || "";
|
|
213
219
|
lines.push(`**${label}:**`);
|