pmx-canvas 0.1.14 → 0.1.15
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/CHANGELOG.md +94 -0
- package/Readme.md +108 -1058
- package/dist/canvas/global.css +141 -0
- package/dist/canvas/index.js +129 -79
- package/dist/json-render/index.css +1 -1
- package/dist/types/client/nodes/HtmlNode.d.ts +5 -0
- package/dist/types/client/state/canvas-store.d.ts +5 -1
- package/dist/types/client/state/intent-bridge.d.ts +3 -1
- package/dist/types/client/types.d.ts +2 -2
- package/dist/types/json-render/catalog.d.ts +1 -1
- package/dist/types/mcp/canvas-access.d.ts +7 -1
- package/dist/types/server/agent-context.d.ts +1 -0
- package/dist/types/server/canvas-operations.d.ts +4 -2
- package/dist/types/server/canvas-provenance.d.ts +1 -1
- package/dist/types/server/canvas-serialization.d.ts +3 -0
- package/dist/types/server/canvas-state.d.ts +51 -4
- package/dist/types/server/demo.d.ts +5 -0
- package/dist/types/server/index.d.ts +13 -3
- package/dist/types/server/web-artifacts.d.ts +18 -0
- package/dist/types/shared/canvas-node-kind.d.ts +5 -0
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +43 -0
- package/skills/pmx-canvas-testing/SKILL.md +17 -0
- package/src/cli/agent.ts +52 -5
- package/src/cli/index.ts +2 -23
- package/src/client/canvas/AttentionHistory.tsx +14 -1
- package/src/client/canvas/CanvasNode.tsx +1 -1
- package/src/client/canvas/CanvasViewport.tsx +3 -0
- package/src/client/canvas/DockedNode.tsx +110 -12
- package/src/client/canvas/ExpandedNodeOverlay.tsx +5 -0
- package/src/client/canvas/Minimap.tsx +1 -0
- package/src/client/icons.tsx +1 -0
- package/src/client/nodes/HtmlNode.tsx +151 -0
- package/src/client/state/canvas-store.ts +24 -2
- package/src/client/state/intent-bridge.ts +4 -3
- package/src/client/state/sse-bridge.ts +1 -0
- package/src/client/theme/global.css +141 -0
- package/src/client/types.ts +3 -0
- package/src/mcp/canvas-access.ts +34 -7
- package/src/mcp/server.ts +178 -25
- package/src/server/agent-context.ts +50 -3
- package/src/server/canvas-operations.ts +20 -3
- package/src/server/canvas-provenance.ts +2 -1
- package/src/server/canvas-serialization.ts +38 -13
- package/src/server/canvas-state.ts +305 -34
- package/src/server/demo.ts +792 -0
- package/src/server/index.ts +33 -3
- package/src/server/server.ts +74 -13
- package/src/server/web-artifacts.ts +116 -3
- package/src/shared/canvas-node-kind.ts +14 -0
|
@@ -7,6 +7,7 @@ type OpenMcpAppResult = Awaited<ReturnType<PmxCanvas['openMcpApp']>>;
|
|
|
7
7
|
type AddDiagramInput = Parameters<PmxCanvas['addDiagram']>[0];
|
|
8
8
|
type AddJsonRenderNodeInput = Parameters<PmxCanvas['addJsonRenderNode']>[0];
|
|
9
9
|
type AddJsonRenderNodeResult = ReturnType<PmxCanvas['addJsonRenderNode']>;
|
|
10
|
+
type AddHtmlNodeInput = Parameters<PmxCanvas['addHtmlNode']>[0];
|
|
10
11
|
type AddGraphNodeInput = Parameters<PmxCanvas['addGraphNode']>[0];
|
|
11
12
|
type AddGraphNodeResult = ReturnType<PmxCanvas['addGraphNode']>;
|
|
12
13
|
type UpdateNodePatch = Parameters<PmxCanvas['updateNode']>[1];
|
|
@@ -23,8 +24,11 @@ type HistoryResult = ReturnType<PmxCanvas['getHistory']>;
|
|
|
23
24
|
type SetContextPinsResult = ReturnType<PmxCanvas['setContextPins']>;
|
|
24
25
|
type RunBatchInput = Parameters<PmxCanvas['runBatch']>[0];
|
|
25
26
|
type RunBatchResult = Awaited<ReturnType<PmxCanvas['runBatch']>>;
|
|
27
|
+
type SnapshotListOptions = Parameters<PmxCanvas['listSnapshots']>[0];
|
|
26
28
|
type SnapshotList = ReturnType<PmxCanvas['listSnapshots']>;
|
|
27
29
|
type DeleteSnapshotResult = ReturnType<PmxCanvas['deleteSnapshot']>;
|
|
30
|
+
type GcSnapshotsOptions = Parameters<PmxCanvas['gcSnapshots']>[0];
|
|
31
|
+
type GcSnapshotsResult = ReturnType<PmxCanvas['gcSnapshots']>;
|
|
28
32
|
type DiffSnapshotResult = ReturnType<PmxCanvas['diffSnapshot']>;
|
|
29
33
|
type CodeGraphResult = ReturnType<PmxCanvas['getCodeGraph']>;
|
|
30
34
|
type ValidationResult = ReturnType<PmxCanvas['validate']>;
|
|
@@ -45,6 +49,7 @@ export interface CanvasAccess {
|
|
|
45
49
|
openMcpApp(input: OpenMcpAppInput): Promise<OpenMcpAppResult>;
|
|
46
50
|
addDiagram(input: AddDiagramInput): Promise<OpenMcpAppResult>;
|
|
47
51
|
addJsonRenderNode(input: AddJsonRenderNodeInput): Promise<AddJsonRenderNodeResult>;
|
|
52
|
+
addHtmlNode(input: AddHtmlNodeInput): Promise<string>;
|
|
48
53
|
addGraphNode(input: AddGraphNodeInput): Promise<AddGraphNodeResult>;
|
|
49
54
|
buildWebArtifact(input: WebArtifactInput): Promise<WebArtifactResult>;
|
|
50
55
|
updateNode(id: string, patch: UpdateNodePatch): Promise<void>;
|
|
@@ -67,12 +72,13 @@ export interface CanvasAccess {
|
|
|
67
72
|
setContextPins(nodeIds: string[], mode?: 'set' | 'add' | 'remove'): Promise<SetContextPinsResult>;
|
|
68
73
|
getPinnedNodeIds(): Promise<string[]>;
|
|
69
74
|
runBatch(operations: RunBatchInput): Promise<RunBatchResult>;
|
|
70
|
-
listSnapshots(): Promise<SnapshotList>;
|
|
75
|
+
listSnapshots(options?: SnapshotListOptions): Promise<SnapshotList>;
|
|
71
76
|
saveSnapshot(name: string): Promise<CanvasSnapshot | null>;
|
|
72
77
|
restoreSnapshot(id: string): Promise<{
|
|
73
78
|
ok: boolean;
|
|
74
79
|
}>;
|
|
75
80
|
deleteSnapshot(id: string): Promise<DeleteSnapshotResult>;
|
|
81
|
+
gcSnapshots(options?: GcSnapshotsOptions): Promise<GcSnapshotsResult>;
|
|
76
82
|
diffSnapshot(idOrName: string): Promise<DiffSnapshotResult>;
|
|
77
83
|
getCodeGraph(): Promise<CodeGraphResult>;
|
|
78
84
|
validate(): Promise<ValidationResult>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { type CanvasEdge, type CanvasNodeState, type CanvasNodeUpdate, type CanvasSnapshot } from './canvas-state.js';
|
|
1
|
+
import { canvasState, type CanvasEdge, type CanvasNodeState, type CanvasNodeUpdate, type CanvasSnapshot } from './canvas-state.js';
|
|
2
2
|
import { type GraphNodeInput, type JsonRenderNodeInput, type JsonRenderSpec } from '../json-render/server.js';
|
|
3
3
|
export type CanvasArrangeMode = 'grid' | 'column' | 'flow';
|
|
4
4
|
export type CanvasPinMode = 'set' | 'add' | 'remove';
|
|
5
|
+
export declare function setCanvasLayoutUpdateEmitter(emitter: (() => void) | null): void;
|
|
5
6
|
export interface CanvasFitViewOptions {
|
|
6
7
|
width?: number;
|
|
7
8
|
height?: number;
|
|
@@ -150,7 +151,7 @@ export declare function setCanvasContextPins(nodeIds: string[], mode?: CanvasPin
|
|
|
150
151
|
count: number;
|
|
151
152
|
nodeIds: string[];
|
|
152
153
|
};
|
|
153
|
-
export declare function listCanvasSnapshots(): CanvasSnapshot[];
|
|
154
|
+
export declare function listCanvasSnapshots(options?: Parameters<typeof canvasState.listSnapshots>[0]): CanvasSnapshot[];
|
|
154
155
|
export declare function saveCanvasSnapshot(name: string): CanvasSnapshot | null;
|
|
155
156
|
export declare function restoreCanvasSnapshot(idOrName: string): Promise<{
|
|
156
157
|
ok: boolean;
|
|
@@ -158,6 +159,7 @@ export declare function restoreCanvasSnapshot(idOrName: string): Promise<{
|
|
|
158
159
|
export declare function deleteCanvasSnapshot(id: string): {
|
|
159
160
|
ok: boolean;
|
|
160
161
|
};
|
|
162
|
+
export declare function gcCanvasSnapshots(options?: Parameters<typeof canvasState.gcSnapshots>[0]): ReturnType<typeof canvasState.gcSnapshots>;
|
|
161
163
|
export declare function addCanvasEdge(input: {
|
|
162
164
|
from?: string;
|
|
163
165
|
to?: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type CanvasNodeType = 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'image' | 'group';
|
|
1
|
+
export type CanvasNodeType = 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'image' | 'html' | 'group';
|
|
2
2
|
export type CanvasNodeProvenanceSourceKind = 'workspace-file' | 'webpage-url' | 'mcp-tool' | 'artifact-file' | 'image-url';
|
|
3
3
|
export type CanvasNodeRefreshStrategy = 'file-watch' | 'file-read-write' | 'image-reload' | 'webpage-refresh' | 'mcp-app-rehydrate' | 'artifact-reopen';
|
|
4
4
|
export interface CanvasNodeProvenance {
|
|
@@ -11,10 +11,13 @@ export interface SerializedCanvasNode extends CanvasNodeState {
|
|
|
11
11
|
export interface SerializedCanvasLayout extends Omit<CanvasLayout, 'nodes'> {
|
|
12
12
|
nodes: SerializedCanvasNode[];
|
|
13
13
|
}
|
|
14
|
+
export declare function getCanvasNodeKind(node: CanvasNodeState, data: Record<string, unknown>): string;
|
|
14
15
|
export declare function getCanvasNodeTitle(node: CanvasNodeState): string | null;
|
|
15
16
|
export declare function getCanvasNodeContent(node: CanvasNodeState): string | null;
|
|
16
17
|
export declare function serializeCanvasNode(node: CanvasNodeState): SerializedCanvasNode;
|
|
18
|
+
export declare function serializeCanvasNodeWithBlobSummaries(node: CanvasNodeState): SerializedCanvasNode;
|
|
17
19
|
export declare function serializeCanvasLayout(layout: CanvasLayout): SerializedCanvasLayout;
|
|
20
|
+
export declare function serializeCanvasLayoutWithBlobSummaries(layout: CanvasLayout): SerializedCanvasLayout;
|
|
18
21
|
export interface CanvasSummary {
|
|
19
22
|
totalNodes: number;
|
|
20
23
|
totalEdges: number;
|
|
@@ -9,6 +9,21 @@
|
|
|
9
9
|
* workspace root on every mutation (debounced). Auto-loads on `loadFromDisk()`.
|
|
10
10
|
*/
|
|
11
11
|
export declare const PMX_CANVAS_DIR = ".pmx-canvas";
|
|
12
|
+
export interface PersistedBlobRef {
|
|
13
|
+
__pmxCanvasBlob: 'v1';
|
|
14
|
+
path: string;
|
|
15
|
+
sha256: string;
|
|
16
|
+
encoding: 'json+gzip';
|
|
17
|
+
bytes: number;
|
|
18
|
+
jsonBytes: number;
|
|
19
|
+
}
|
|
20
|
+
interface PersistedCanvasState {
|
|
21
|
+
version: number;
|
|
22
|
+
viewport: ViewportState;
|
|
23
|
+
nodes: CanvasNodeState[];
|
|
24
|
+
edges: CanvasEdge[];
|
|
25
|
+
contextPins: string[];
|
|
26
|
+
}
|
|
12
27
|
interface LoadFromDiskOptions {
|
|
13
28
|
clearExisting?: boolean;
|
|
14
29
|
}
|
|
@@ -20,9 +35,24 @@ export interface CanvasSnapshot {
|
|
|
20
35
|
nodeCount: number;
|
|
21
36
|
edgeCount: number;
|
|
22
37
|
}
|
|
38
|
+
export interface CanvasSnapshotListOptions {
|
|
39
|
+
limit?: number;
|
|
40
|
+
query?: string;
|
|
41
|
+
all?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface CanvasSnapshotGcOptions {
|
|
44
|
+
keep?: number;
|
|
45
|
+
dryRun?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface CanvasSnapshotGcResult {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
kept: number;
|
|
50
|
+
deleted: CanvasSnapshot[];
|
|
51
|
+
dryRun: boolean;
|
|
52
|
+
}
|
|
23
53
|
export interface CanvasNodeState {
|
|
24
54
|
id: string;
|
|
25
|
-
type: 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'image' | 'group';
|
|
55
|
+
type: 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'image' | 'html' | 'group';
|
|
26
56
|
position: {
|
|
27
57
|
x: number;
|
|
28
58
|
y: number;
|
|
@@ -92,7 +122,7 @@ declare class CanvasStateManager {
|
|
|
92
122
|
onChange(cb: (type: CanvasChangeType) => void): void;
|
|
93
123
|
private notifyChange;
|
|
94
124
|
private _mutationRecorder;
|
|
95
|
-
private
|
|
125
|
+
private _suppressRecordingDepth;
|
|
96
126
|
/** Register a mutation recorder. Used by mutation-history to capture undo/redo closures. */
|
|
97
127
|
onMutation(cb: (info: MutationRecordInfo) => void): void;
|
|
98
128
|
/** Run a function with mutation recording suppressed (for undo/redo replay and computed edges). */
|
|
@@ -111,6 +141,16 @@ declare class CanvasStateManager {
|
|
|
111
141
|
private _saveTimer;
|
|
112
142
|
/** Set the workspace root to enable auto-persistence. */
|
|
113
143
|
setWorkspaceRoot(workspaceRoot: string): void;
|
|
144
|
+
private get blobsDir();
|
|
145
|
+
private relativeBlobPath;
|
|
146
|
+
private resolveBlobPath;
|
|
147
|
+
private writeBlobValue;
|
|
148
|
+
private readBlobValue;
|
|
149
|
+
private externalizeNodeDataBlobs;
|
|
150
|
+
private resolveNodeDataBlobs;
|
|
151
|
+
isBlobReference(value: unknown): value is PersistedBlobRef;
|
|
152
|
+
resolveBlobReference(value: unknown): unknown;
|
|
153
|
+
private externalizePersistedStateBlobs;
|
|
114
154
|
/**
|
|
115
155
|
* One-time migration: rename files from the pre-consolidation layout
|
|
116
156
|
* (`.pmx-canvas.json` + `.pmx-canvas-snapshots/`) into `.pmx-canvas/`.
|
|
@@ -129,10 +169,15 @@ declare class CanvasStateManager {
|
|
|
129
169
|
private get snapshotsDir();
|
|
130
170
|
private applyPersistedState;
|
|
131
171
|
private readResolvedSnapshot;
|
|
172
|
+
getSnapshotDataForPersistence(idOrName: string): {
|
|
173
|
+
snapshot: CanvasSnapshot;
|
|
174
|
+
state: PersistedCanvasState;
|
|
175
|
+
} | null;
|
|
132
176
|
/** Save current canvas state as a named snapshot. */
|
|
133
177
|
saveSnapshot(name: string): CanvasSnapshot | null;
|
|
134
|
-
/** List
|
|
135
|
-
listSnapshots(): CanvasSnapshot[];
|
|
178
|
+
/** List saved snapshots, newest first. */
|
|
179
|
+
listSnapshots(options?: CanvasSnapshotListOptions): CanvasSnapshot[];
|
|
180
|
+
gcSnapshots(options?: CanvasSnapshotGcOptions): CanvasSnapshotGcResult;
|
|
136
181
|
/** Restore canvas state from a snapshot. */
|
|
137
182
|
restoreSnapshot(idOrName: string): boolean;
|
|
138
183
|
/** Read a snapshot's data without restoring it (for diff). Resolves by ID or name. */
|
|
@@ -150,12 +195,14 @@ declare class CanvasStateManager {
|
|
|
150
195
|
updateNode(id: string, patch: Partial<CanvasNodeState>): void;
|
|
151
196
|
removeNode(id: string): void;
|
|
152
197
|
getNode(id: string): CanvasNodeState | undefined;
|
|
198
|
+
getNodeForPersistence(id: string): CanvasNodeState | undefined;
|
|
153
199
|
addEdge(edge: CanvasEdge): boolean;
|
|
154
200
|
removeEdge(id: string): boolean;
|
|
155
201
|
getEdges(): CanvasEdge[];
|
|
156
202
|
getEdgesForNode(nodeId: string): CanvasEdge[];
|
|
157
203
|
private removeEdgesForNode;
|
|
158
204
|
getLayout(): CanvasLayout;
|
|
205
|
+
getLayoutForPersistence(): CanvasLayout;
|
|
159
206
|
applyUpdates(updates: CanvasNodeUpdate[]): {
|
|
160
207
|
applied: number;
|
|
161
208
|
skipped: number;
|
|
@@ -2,7 +2,7 @@ import { EventEmitter } from 'node:events';
|
|
|
2
2
|
import type { CanvasNodeState, CanvasEdge, CanvasLayout } from './canvas-state.js';
|
|
3
3
|
import { searchNodes } from './spatial-analysis.js';
|
|
4
4
|
import { diffLayouts } from './mutation-history.js';
|
|
5
|
-
import { fitCanvasView } from './canvas-operations.js';
|
|
5
|
+
import { fitCanvasView, gcCanvasSnapshots, listCanvasSnapshots } from './canvas-operations.js';
|
|
6
6
|
import { type WebArtifactBuildInput, type WebArtifactCanvasBuildResult } from './web-artifacts.js';
|
|
7
7
|
import { type ExternalMcpTransportConfig } from './mcp-app-runtime.js';
|
|
8
8
|
import { type DiagramPresetOpenInput } from './diagram-presets.js';
|
|
@@ -143,7 +143,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
143
143
|
count: number;
|
|
144
144
|
nodeIds: string[];
|
|
145
145
|
};
|
|
146
|
-
listSnapshots(): import("./canvas-state.js").CanvasSnapshot[];
|
|
146
|
+
listSnapshots(options?: Parameters<typeof listCanvasSnapshots>[0]): import("./canvas-state.js").CanvasSnapshot[];
|
|
147
147
|
saveSnapshot(name: string): import("./canvas-state.js").CanvasSnapshot | null;
|
|
148
148
|
restoreSnapshot(id: string): Promise<{
|
|
149
149
|
ok: boolean;
|
|
@@ -151,6 +151,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
151
151
|
deleteSnapshot(id: string): {
|
|
152
152
|
ok: boolean;
|
|
153
153
|
};
|
|
154
|
+
gcSnapshots(options?: Parameters<typeof gcCanvasSnapshots>[0]): ReturnType<typeof gcCanvasSnapshots>;
|
|
154
155
|
diffSnapshot(idOrName: string): {
|
|
155
156
|
ok: boolean;
|
|
156
157
|
text?: string;
|
|
@@ -233,6 +234,15 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
233
234
|
url: string;
|
|
234
235
|
spec: JsonRenderSpec;
|
|
235
236
|
};
|
|
237
|
+
addHtmlNode(input: {
|
|
238
|
+
html: string;
|
|
239
|
+
title?: string;
|
|
240
|
+
x?: number;
|
|
241
|
+
y?: number;
|
|
242
|
+
width?: number;
|
|
243
|
+
height?: number;
|
|
244
|
+
strictSize?: boolean;
|
|
245
|
+
}): string;
|
|
236
246
|
addGraphNode(input: GraphNodeInput): {
|
|
237
247
|
id: string;
|
|
238
248
|
url: string;
|
|
@@ -253,7 +263,7 @@ export type { CanvasNodeState, CanvasEdge, CanvasLayout, ViewportState } from '.
|
|
|
253
263
|
export type { CanvasAutomationWebViewOptions, CanvasAutomationWebViewStatus, PrimaryWorkbenchCanvasPromptRequest, PrimaryWorkbenchIntent, } from './server.js';
|
|
254
264
|
export { emitPrimaryWorkbenchEvent, consumePrimaryWorkbenchIntents, setPrimaryWorkbenchAutoOpenEnabled, setPrimaryWorkbenchCanvasPromptHandler, startCanvasServer, stopCanvasServer, getCanvasServerPort, openUrlInExternalBrowser, getCanvasAutomationWebViewStatus, startCanvasAutomationWebView, stopCanvasAutomationWebView, evaluateCanvasAutomationWebView, resizeCanvasAutomationWebView, screenshotCanvasAutomationWebView, } from './server.js';
|
|
255
265
|
export { canvasState } from './canvas-state.js';
|
|
256
|
-
export type { CanvasSnapshot } from './canvas-state.js';
|
|
266
|
+
export type { CanvasSnapshot, CanvasSnapshotGcResult, CanvasSnapshotListOptions } from './canvas-state.js';
|
|
257
267
|
export { findOpenCanvasPosition } from './placement.js';
|
|
258
268
|
export { searchNodes, buildSpatialContext, detectClusters, findNeighborhoods } from './spatial-analysis.js';
|
|
259
269
|
export type { SpatialCluster, SpatialContext, SpatialNeighbor, NodeSpatialInfo } from './spatial-analysis.js';
|
|
@@ -17,6 +17,7 @@ export interface WebArtifactBuildOutput {
|
|
|
17
17
|
fileSize: number;
|
|
18
18
|
projectPath: string;
|
|
19
19
|
metadata: Record<string, unknown>;
|
|
20
|
+
sourceContext: WebArtifactSourceContext;
|
|
20
21
|
logs?: {
|
|
21
22
|
stdout?: WebArtifactLogSummary;
|
|
22
23
|
stderr?: WebArtifactLogSummary;
|
|
@@ -30,6 +31,13 @@ export interface WebArtifactLogSummary {
|
|
|
30
31
|
truncated: boolean;
|
|
31
32
|
suppressedNoiseCount: number;
|
|
32
33
|
}
|
|
34
|
+
export interface WebArtifactSourceContext {
|
|
35
|
+
content: string;
|
|
36
|
+
sourceFiles: string[];
|
|
37
|
+
sourceFileCount: number;
|
|
38
|
+
sourcePreview: string;
|
|
39
|
+
deps?: string[];
|
|
40
|
+
}
|
|
33
41
|
export interface WebArtifactCanvasOpenResult {
|
|
34
42
|
nodeId: string;
|
|
35
43
|
url: string;
|
|
@@ -38,7 +46,10 @@ export interface WebArtifactCanvasBuildResult extends WebArtifactBuildOutput {
|
|
|
38
46
|
openedInCanvas: boolean;
|
|
39
47
|
nodeId?: string;
|
|
40
48
|
url?: string;
|
|
49
|
+
startedAt: string;
|
|
41
50
|
completedAt: string;
|
|
51
|
+
durationMs: number;
|
|
52
|
+
timeoutMs: number;
|
|
42
53
|
}
|
|
43
54
|
export declare function resolveWorkspacePath(pathLike: string, cwd?: string): string;
|
|
44
55
|
export declare function resolveWebArtifactScriptPath(kind: 'init' | 'bundle'): string;
|
|
@@ -46,6 +57,13 @@ export declare function executeWebArtifactBuild(input: WebArtifactBuildInput): P
|
|
|
46
57
|
export declare function openWebArtifactInCanvas(input: {
|
|
47
58
|
title: string;
|
|
48
59
|
filePath: string;
|
|
60
|
+
fileSize?: number;
|
|
61
|
+
projectPath?: string;
|
|
62
|
+
content?: string;
|
|
63
|
+
sourceFiles?: string[];
|
|
64
|
+
sourceFileCount?: number;
|
|
65
|
+
sourcePreview?: string;
|
|
66
|
+
deps?: string[];
|
|
49
67
|
}): WebArtifactCanvasOpenResult;
|
|
50
68
|
export declare function buildWebArtifactOnCanvas(input: WebArtifactBuildInput & {
|
|
51
69
|
openInCanvas?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Spatial canvas workbench for coding agents — infinite 2D canvas with agent-native CLI, MCP integration, nodes, edges, file watching, and snapshots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/server/index.ts",
|
|
@@ -225,6 +225,7 @@ The CLI targets `http://localhost:4313` by default. Override with `PMX_CANVAS_UR
|
|
|
225
225
|
| `mcp-app` | Hosted app/embed frame | Tool-backed MCP apps or external app content; not generic CLI-created notes |
|
|
226
226
|
| `json-render` | Native structured UI panel | Dashboards, forms, tables, interactive layouts from json-render specs |
|
|
227
227
|
| `graph` | Native chart panel | Line, bar, pie, area, scatter, radar, stacked-bar, and composed charts rendered inside the canvas |
|
|
228
|
+
| `html` | Sandboxed HTML+JS document | Self-contained HTML with optional inline `<script>` and CDN imports rendered in a sandbox-restricted iframe; canvas theme tokens are auto-injected |
|
|
228
229
|
| `group` | Spatial container/frame | Visually group related nodes together |
|
|
229
230
|
| `prompt` | Prompt thread root | Canvas-native prompt entry points for agent conversations. **Internal type — surfaces in `canvas://layout` for thread rendering but is not created via the public `canvas_add_node` API. Don't try to add one directly.** |
|
|
230
231
|
| `response` | Prompt reply / streamed answer | Agent responses linked to prompt threads. **Same internal-only restriction as `prompt`.** |
|
|
@@ -269,6 +270,7 @@ MCP node-type routing:
|
|
|
269
270
|
| Basic nodes (`markdown`, `status`, `context`, `ledger`, `trace`, `file`, `image`, `webpage`) | `canvas_add_node` |
|
|
270
271
|
| `json-render` | `canvas_add_json_render_node` |
|
|
271
272
|
| `graph` | `canvas_add_graph_node` |
|
|
273
|
+
| `html` | `canvas_add_html_node` |
|
|
272
274
|
| `web-artifact` | `canvas_build_web_artifact` |
|
|
273
275
|
| `external-app` / tool-backed `mcp-app` | `canvas_open_mcp_app` |
|
|
274
276
|
| `group` | `canvas_create_group` |
|
|
@@ -431,6 +433,27 @@ ID extraction for mixed tool responses:
|
|
|
431
433
|
**`canvas_ungroup`** — Release children from a group
|
|
432
434
|
- `groupId` (required): group to dissolve
|
|
433
435
|
|
|
436
|
+
### Group Layout Guidance
|
|
437
|
+
|
|
438
|
+
Use groups as spacious semantic regions, not as tight containers.
|
|
439
|
+
|
|
440
|
+
- Size the child nodes first, especially `graph`, `json-render`, `mcp-app`, image, and webpage
|
|
441
|
+
nodes whose rendered content may need more height than their visible title suggests.
|
|
442
|
+
- Give every group generous interior padding. Reserve extra top padding for the group header, then
|
|
443
|
+
keep children clear of the frame edges so headers, glow, resize handles, and node chrome do not
|
|
444
|
+
visually collide.
|
|
445
|
+
- If creating a group manually, compute its frame from the final child bounds plus padding. If the
|
|
446
|
+
group exists first, expand it before adding large children rather than shrinking children to fit.
|
|
447
|
+
- Use groups to label major regions of a board. Avoid wrapping every small relationship; too many
|
|
448
|
+
tight groups make the canvas harder to read than no groups.
|
|
449
|
+
- Keep edges local to a group where possible. Long cross-board edges can look like they come from
|
|
450
|
+
nowhere; use a nearby bridge/context node or split the relationship into shorter labeled edges.
|
|
451
|
+
- After grouping, verify the result in `canvas_get_layout` or the browser: child nodes should be
|
|
452
|
+
fully inside the group with padding, visible nodes should not overlap, and group headers should
|
|
453
|
+
not cover content.
|
|
454
|
+
- If a group makes important content less visible, enlarge the group, split it into clearer
|
|
455
|
+
regions, or remove the group. Visibility is more important than preserving a frame.
|
|
456
|
+
|
|
434
457
|
### Grouped Comparison Boards
|
|
435
458
|
|
|
436
459
|
Use groups as named comparison areas, not just visual boxes.
|
|
@@ -613,6 +636,26 @@ server's `ui://` resource as an iframe node on the canvas
|
|
|
613
636
|
- Prefer the dedicated `web-artifacts-builder` skill when you need the full React + shadcn workflow
|
|
614
637
|
- Use the `playwright-cli` skill when you need to validate the built artifact in a live browser
|
|
615
638
|
|
|
639
|
+
### HTML Nodes (Sandboxed iframe)
|
|
640
|
+
|
|
641
|
+
**`canvas_add_html_node`** — Add a self-contained HTML document rendered in a sandboxed iframe
|
|
642
|
+
- Required: `html` (full document or fragment; inline `<script>` and CDN `<script src="...">` are allowed)
|
|
643
|
+
- Optional: `title`, `x`, `y`, `width` (default 720), `height` (default 640), `strictSize`
|
|
644
|
+
- Iframe sandbox is `allow-scripts` only — no same-origin access, no top-navigation, no forms
|
|
645
|
+
- Canvas theme tokens are auto-injected as CSS custom properties (both `--c-*` and common `--color-*` aliases such as `--color-text-primary`, `--color-bg`, `--color-accent`) so authored HTML inherits the active theme
|
|
646
|
+
- Use for moderate-complexity visualizations and interactive widgets that need real JS but do not warrant a full React build (Chart.js demos, D3 sketches, custom HTML report views)
|
|
647
|
+
|
|
648
|
+
### Choosing the Right Visual Tier
|
|
649
|
+
|
|
650
|
+
When the output is more than markdown, pick the lightest tier that fits:
|
|
651
|
+
|
|
652
|
+
| Tier | Tool | Build cost | When to pick it |
|
|
653
|
+
|------|------|------------|-----------------|
|
|
654
|
+
| Declarative UI | `canvas_add_json_render_node` / `canvas_add_graph_node` | None | Schema-driven dashboards, forms, charts; agent-friendly to read back via `canvas_get_node` |
|
|
655
|
+
| Sandboxed HTML+JS | `canvas_add_html_node` | None | Self-contained HTML with inline JS or CDN scripts; one-off visualizations or report views |
|
|
656
|
+
| Hosted MCP app | `canvas_open_mcp_app` / `canvas_add_diagram` | None | Interactive editors backed by an external MCP server (e.g. Excalidraw) |
|
|
657
|
+
| Bundled React app | `canvas_build_web_artifact` | Heavy (npm install + bundle) | Multi-component UIs needing React state, routing, shadcn/ui, or Tailwind class composition |
|
|
658
|
+
|
|
616
659
|
### Native Structured UI
|
|
617
660
|
|
|
618
661
|
Use native `json-render` and `graph` nodes when the output should stay fully inside PMX Canvas:
|
|
@@ -72,6 +72,23 @@ Prefer extending the existing suites before inventing a one-off script.
|
|
|
72
72
|
- If a change spans server and client, add at least one server-side assertion and one browser or
|
|
73
73
|
API-level proof
|
|
74
74
|
|
|
75
|
+
## Layout And Embedded Content Checks
|
|
76
|
+
|
|
77
|
+
- For seeded or generated boards, add API-level geometry assertions: expected node/edge counts,
|
|
78
|
+
group counts, valid edge endpoints, no visible node overlaps, and group children contained with
|
|
79
|
+
header/padding space.
|
|
80
|
+
- For grouped layouts, test non-group node overlap separately from group containment. Group frames
|
|
81
|
+
are allowed to contain children; children should not overlap each other or collide with headers.
|
|
82
|
+
- For edge-heavy layouts, assert endpoints exist and long cross-board edges are intentional. If a
|
|
83
|
+
user says an edge “comes from nowhere,” add a regression check for missing endpoints or excessive
|
|
84
|
+
edge distance in that board.
|
|
85
|
+
- For `graph`, `json-render`, `mcp-app`, webpage, and image nodes, API geometry is not enough.
|
|
86
|
+
Verify the rendered browser frame when changing sizing: iframe/body `scrollHeight` and
|
|
87
|
+
`scrollWidth` should fit the available frame unless scrolling is the intended behavior.
|
|
88
|
+
- When checking embedded frame fit manually, start from a clean seeded state, rebuild stale bundles,
|
|
89
|
+
and inspect the actual iframe document in a browser. Server dimensions can look correct while the
|
|
90
|
+
embedded content is still clipped.
|
|
91
|
+
|
|
75
92
|
## Failure Handling
|
|
76
93
|
|
|
77
94
|
- Never wave away a failure without checking whether your change caused it
|
package/src/cli/agent.ts
CHANGED
|
@@ -154,7 +154,7 @@ function parseFlags(args: string[]): { positional: string[]; flags: Record<strin
|
|
|
154
154
|
const flags: Record<string, string | true> = {};
|
|
155
155
|
// Boolean-only flags (never take a value argument)
|
|
156
156
|
const BOOL_FLAGS = new Set([
|
|
157
|
-
'help', 'h', 'ids', 'stdin', 'yes', 'list', 'clear', 'set', 'animated', 'dry-run',
|
|
157
|
+
'help', 'h', 'ids', 'stdin', 'yes', 'list', 'clear', 'set', 'animated', 'dry-run', 'all',
|
|
158
158
|
'no-open-in-canvas', 'lock-arrange', 'unlock-arrange', 'json', 'compact', 'summary',
|
|
159
159
|
'verbose', 'include-logs', 'no-pan', 'schema', 'example', 'examples', 'strict-size', 'scroll-overflow',
|
|
160
160
|
]);
|
|
@@ -1081,7 +1081,7 @@ cmd('node add', 'Add a node to the canvas', [
|
|
|
1081
1081
|
applyStrictSizeFlags(body, flags);
|
|
1082
1082
|
if (type === 'trace') {
|
|
1083
1083
|
for (const field of TRACE_NODE_FIELDS) {
|
|
1084
|
-
const value = getStringFlag(flags, field);
|
|
1084
|
+
const value = getStringFlag(flags, field, field.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`));
|
|
1085
1085
|
if (value !== undefined) body[field] = value;
|
|
1086
1086
|
}
|
|
1087
1087
|
}
|
|
@@ -1315,6 +1315,11 @@ cmd('node update', 'Update a node by ID', [
|
|
|
1315
1315
|
|
|
1316
1316
|
applyStrictSizeFlags(body, flags);
|
|
1317
1317
|
|
|
1318
|
+
for (const field of TRACE_NODE_FIELDS) {
|
|
1319
|
+
const value = getStringFlag(flags, field, field.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`));
|
|
1320
|
+
if (value !== undefined) body[field] = value;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1318
1323
|
if (x !== undefined || y !== undefined || width !== undefined || frameHeight !== undefined || arrangeLocked !== undefined) {
|
|
1319
1324
|
const existing = await api('GET', `/api/canvas/node/${encodeURIComponent(id)}`) as {
|
|
1320
1325
|
position: { x: number; y: number };
|
|
@@ -1346,7 +1351,7 @@ cmd('node update', 'Update a node by ID', [
|
|
|
1346
1351
|
if (Object.keys(body).length === 0) {
|
|
1347
1352
|
die(
|
|
1348
1353
|
'No updates specified',
|
|
1349
|
-
'Use --title, --content, --x, --y, --width, --height, --strict-size, --pinned, --lock-arrange, --unlock-arrange, or --stdin',
|
|
1354
|
+
'Use --title, --content, --x, --y, --width, --height, --strict-size, --pinned, trace fields, --lock-arrange, --unlock-arrange, or --stdin',
|
|
1350
1355
|
);
|
|
1351
1356
|
}
|
|
1352
1357
|
|
|
@@ -1684,13 +1689,41 @@ cmd('snapshot save', 'Save a named snapshot of the current canvas', [
|
|
|
1684
1689
|
});
|
|
1685
1690
|
|
|
1686
1691
|
// ── snapshot list ────────────────────────────────────────────
|
|
1687
|
-
cmd('snapshot list', 'List
|
|
1692
|
+
cmd('snapshot list', 'List saved snapshots', [
|
|
1688
1693
|
'pmx-canvas snapshot list',
|
|
1694
|
+
'pmx-canvas snapshot list --limit 50 --query baseline',
|
|
1695
|
+
'pmx-canvas snapshot list --all',
|
|
1689
1696
|
], async (args) => {
|
|
1690
1697
|
const { flags } = parseFlags(args);
|
|
1691
1698
|
if (flags.help || flags.h) return showCommandHelp('snapshot list');
|
|
1692
1699
|
|
|
1693
|
-
const
|
|
1700
|
+
const params = new URLSearchParams();
|
|
1701
|
+
const limit = optionalNumberFlag(flags, 'limit', 'Use a positive integer, e.g. --limit 50');
|
|
1702
|
+
const query = getStringFlag(flags, 'query', 'q');
|
|
1703
|
+
if (limit !== undefined) params.set('limit', String(limit));
|
|
1704
|
+
if (query) params.set('q', query);
|
|
1705
|
+
if (flags.all) params.set('all', 'true');
|
|
1706
|
+
const result = await api('GET', `/api/canvas/snapshots${params.size > 0 ? `?${params.toString()}` : ''}`);
|
|
1707
|
+
output(result);
|
|
1708
|
+
});
|
|
1709
|
+
|
|
1710
|
+
// ── snapshot gc ──────────────────────────────────────────────
|
|
1711
|
+
cmd('snapshot gc', 'Delete old snapshots, keeping the newest N', [
|
|
1712
|
+
'pmx-canvas snapshot gc --keep 20 --dry-run',
|
|
1713
|
+
'pmx-canvas snapshot gc --keep 50 --yes',
|
|
1714
|
+
], async (args) => {
|
|
1715
|
+
const { flags } = parseFlags(args);
|
|
1716
|
+
if (flags.help || flags.h) return showCommandHelp('snapshot gc');
|
|
1717
|
+
|
|
1718
|
+
const keep = optionalNumberFlag(flags, 'keep', 'Use a positive integer, e.g. --keep 20');
|
|
1719
|
+
const dryRun = flags['dry-run'] === true;
|
|
1720
|
+
if (!dryRun && !flags.yes) {
|
|
1721
|
+
die('Destructive operation requires --yes flag', 'Preview with: pmx-canvas snapshot gc --keep 20 --dry-run');
|
|
1722
|
+
}
|
|
1723
|
+
const result = await api('POST', '/api/canvas/snapshots/gc', {
|
|
1724
|
+
...(keep !== undefined ? { keep } : {}),
|
|
1725
|
+
dryRun,
|
|
1726
|
+
});
|
|
1694
1727
|
output(result);
|
|
1695
1728
|
});
|
|
1696
1729
|
|
|
@@ -2285,12 +2318,25 @@ function showCommandHelp(name: string): void {
|
|
|
2285
2318
|
console.log('\nOutput control:');
|
|
2286
2319
|
console.log(' --summary Return only validation summary metadata');
|
|
2287
2320
|
}
|
|
2321
|
+
if (name === 'snapshot list') {
|
|
2322
|
+
console.log('\nOptions:');
|
|
2323
|
+
console.log(' --limit <number> Maximum snapshots to return (default 20)');
|
|
2324
|
+
console.log(' --query <text> Case-insensitive ID/name filter');
|
|
2325
|
+
console.log(' --all Return all snapshots');
|
|
2326
|
+
}
|
|
2327
|
+
if (name === 'snapshot gc') {
|
|
2328
|
+
console.log('\nOptions:');
|
|
2329
|
+
console.log(' --keep <number> Number of newest snapshots to keep (default 20)');
|
|
2330
|
+
console.log(' --dry-run Preview deletions without removing files');
|
|
2331
|
+
console.log(' --yes Confirm deletion');
|
|
2332
|
+
}
|
|
2288
2333
|
if (name === 'web-artifact build') {
|
|
2289
2334
|
console.log('\nDependencies:');
|
|
2290
2335
|
console.log(' --deps <list> Add npm dependencies before bundling, e.g. --deps recharts,zod');
|
|
2291
2336
|
console.log('\nOutput control:');
|
|
2292
2337
|
console.log(' --include-logs Include raw build stdout/stderr in the response');
|
|
2293
2338
|
console.log(' --verbose Alias for --include-logs');
|
|
2339
|
+
console.log(' --timeout-ms <number> Optional init/install/build timeout in milliseconds');
|
|
2294
2340
|
}
|
|
2295
2341
|
if (name === 'focus') {
|
|
2296
2342
|
console.log('\nViewport:');
|
|
@@ -2389,6 +2435,7 @@ History:
|
|
|
2389
2435
|
Snapshots:
|
|
2390
2436
|
pmx-canvas snapshot save --name X Save a named snapshot
|
|
2391
2437
|
pmx-canvas snapshot list List snapshots
|
|
2438
|
+
pmx-canvas snapshot gc --keep 20 Delete old snapshots
|
|
2392
2439
|
pmx-canvas snapshot restore <id> Restore from snapshot
|
|
2393
2440
|
pmx-canvas snapshot diff <id> Compare current canvas to snapshot
|
|
2394
2441
|
pmx-canvas snapshot delete <id> Delete a snapshot
|
package/src/cli/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { runAgentCli } from './agent.js';
|
|
7
7
|
import { createCanvas } from '../server/index.js';
|
|
8
|
+
import { seedDemoCanvas } from '../server/demo.js';
|
|
8
9
|
|
|
9
10
|
const args = process.argv.slice(2);
|
|
10
11
|
|
|
@@ -594,29 +595,7 @@ Examples:
|
|
|
594
595
|
process.exit(1);
|
|
595
596
|
}
|
|
596
597
|
|
|
597
|
-
if (demo && canvas.getLayout().nodes.length === 0)
|
|
598
|
-
const n1 = canvas.addNode({
|
|
599
|
-
type: 'markdown',
|
|
600
|
-
title: 'Welcome to PMX Canvas',
|
|
601
|
-
content: '# PMX Canvas Workbench\n\nA spatial canvas for coding agents.\n\n## Features\n- Infinite 2D canvas with pan/zoom\n- Multiple node types\n- Edges between nodes\n- Real-time SSE updates\n- HTTP API for agent control',
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
const n2 = canvas.addNode({
|
|
605
|
-
type: 'markdown',
|
|
606
|
-
title: 'Getting Started',
|
|
607
|
-
content: `# Quick Start\n\n\`\`\`bash\n# Add a node via CLI\npmx-canvas node add --type markdown --title "Hello" --content "# World"\n\n# List nodes\npmx-canvas node list\n\n# Get canvas state\npmx-canvas layout\n\`\`\``,
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
const n3 = canvas.addNode({
|
|
611
|
-
type: 'status',
|
|
612
|
-
title: 'Agent Status',
|
|
613
|
-
content: 'Ready',
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
canvas.addEdge({ from: n1, to: n2, type: 'flow', label: 'next' });
|
|
617
|
-
canvas.addEdge({ from: n2, to: n3, type: 'flow' });
|
|
618
|
-
canvas.arrange('grid');
|
|
619
|
-
}
|
|
598
|
+
if (demo && canvas.getLayout().nodes.length === 0) seedDemoCanvas();
|
|
620
599
|
|
|
621
600
|
console.log(`\n PMX Canvas running at http://localhost:${canvas.port}`);
|
|
622
601
|
console.log(` Health: http://localhost:${canvas.port}/health\n`);
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
closeAttentionHistory,
|
|
6
6
|
openAttentionHistory,
|
|
7
7
|
} from '../state/attention-store';
|
|
8
|
+
import { collapseDockedContextNodes, hasOpenDockedContextPanel } from '../state/canvas-store';
|
|
8
9
|
|
|
9
10
|
function formatTimestamp(timestamp: number): string {
|
|
10
11
|
return new Date(timestamp).toLocaleTimeString([], {
|
|
@@ -13,19 +14,31 @@ function formatTimestamp(timestamp: number): string {
|
|
|
13
14
|
});
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
function handleOpenUpdates(): void {
|
|
18
|
+
// Mutual exclusion with the Context panel — only one side panel open at a
|
|
19
|
+
// time (they share the same right-edge anchor).
|
|
20
|
+
collapseDockedContextNodes();
|
|
21
|
+
openAttentionHistory();
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
export function AttentionHistory() {
|
|
17
25
|
const entries = attentionHistory.value;
|
|
18
26
|
if (entries.length === 0) return null;
|
|
19
27
|
|
|
20
28
|
const isOpen = attentionHistoryOpen.value;
|
|
21
29
|
const unread = attentionHistoryUnread.value;
|
|
30
|
+
// Hide the collapsed Updates pill while the Context side panel is open —
|
|
31
|
+
// the panel sits at the same right-edge and would visually cover the pill.
|
|
32
|
+
// Mutual exclusion guarantees both can't be expanded simultaneously, so the
|
|
33
|
+
// pill only needs to hide while context is expanded.
|
|
34
|
+
if (!isOpen && hasOpenDockedContextPanel.value) return null;
|
|
22
35
|
|
|
23
36
|
if (!isOpen) {
|
|
24
37
|
return (
|
|
25
38
|
<button
|
|
26
39
|
type="button"
|
|
27
40
|
class="attention-history-tab"
|
|
28
|
-
onClick={
|
|
41
|
+
onClick={handleOpenUpdates}
|
|
29
42
|
aria-label={unread > 0 ? `Recent updates — ${unread} new` : 'Recent updates'}
|
|
30
43
|
title={unread > 0 ? `${unread} new updates since last viewed` : 'Recent updates'}
|
|
31
44
|
>
|
|
@@ -164,7 +164,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
164
164
|
window.clearTimeout(autoFitPersistTimer.current);
|
|
165
165
|
}
|
|
166
166
|
autoFitPersistTimer.current = window.setTimeout(() => {
|
|
167
|
-
persistLayout();
|
|
167
|
+
persistLayout({ recordHistory: false });
|
|
168
168
|
autoFitPersistTimer.current = null;
|
|
169
169
|
}, 0);
|
|
170
170
|
}
|
|
@@ -8,6 +8,7 @@ import { StatusNode } from '../nodes/StatusNode';
|
|
|
8
8
|
import { ImageNode } from '../nodes/ImageNode';
|
|
9
9
|
import { GroupNode } from '../nodes/GroupNode';
|
|
10
10
|
import { WebpageNode } from '../nodes/WebpageNode';
|
|
11
|
+
import { HtmlNode } from '../nodes/HtmlNode';
|
|
11
12
|
import { PromptNode } from '../nodes/PromptNode';
|
|
12
13
|
import { ResponseNode } from '../nodes/ResponseNode';
|
|
13
14
|
import { TraceNode } from '../nodes/TraceNode';
|
|
@@ -60,6 +61,8 @@ function renderNodeContent(node: CanvasNodeState) {
|
|
|
60
61
|
return <FileNode node={node} />;
|
|
61
62
|
case 'image':
|
|
62
63
|
return <ImageNode node={node} />;
|
|
64
|
+
case 'html':
|
|
65
|
+
return <HtmlNode node={node} />;
|
|
63
66
|
case 'group':
|
|
64
67
|
return <GroupNode node={node} />;
|
|
65
68
|
default:
|