pmx-canvas 0.1.19 → 0.1.21
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 +159 -0
- package/Readme.md +19 -6
- package/dist/canvas/global.css +123 -2
- package/dist/canvas/index.js +103 -68
- package/dist/json-render/index.js +109 -109
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/icons.d.ts +2 -0
- package/dist/types/client/nodes/HtmlNode.d.ts +12 -1
- package/dist/types/client/state/canvas-store.d.ts +2 -0
- package/dist/types/client/types.d.ts +3 -2
- package/dist/types/json-render/charts/components.d.ts +5 -1
- package/dist/types/json-render/renderer/index.d.ts +1 -0
- package/dist/types/json-render/server.d.ts +1 -0
- package/dist/types/mcp/canvas-access.d.ts +3 -0
- package/dist/types/server/canvas-operations.d.ts +4 -0
- package/dist/types/server/canvas-schema.d.ts +19 -3
- package/dist/types/server/canvas-serialization.d.ts +1 -0
- package/dist/types/server/canvas-state.d.ts +6 -2
- package/dist/types/server/html-node-summary.d.ts +2 -0
- package/dist/types/server/html-primitives.d.ts +42 -0
- package/dist/types/server/index.d.ts +26 -0
- package/docs/cli.md +4 -1
- package/docs/http-api.md +11 -1
- package/docs/mcp.md +10 -4
- package/docs/node-types.md +54 -4
- package/docs/screenshot.png +0 -0
- package/docs/sdk.md +12 -0
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +17 -3
- package/skills/pmx-canvas/references/html-primitives.md +132 -0
- package/src/cli/agent.ts +159 -5
- package/src/cli/index.ts +1 -1
- package/src/client/App.tsx +21 -2
- package/src/client/canvas/AnnotationLayer.tsx +33 -12
- package/src/client/canvas/CanvasViewport.tsx +88 -7
- package/src/client/canvas/CommandPalette.tsx +2 -2
- package/src/client/canvas/ContextMenu.tsx +2 -2
- package/src/client/canvas/ExpandedNodeOverlay.tsx +112 -3
- package/src/client/canvas/auto-fit.ts +5 -1
- package/src/client/icons.tsx +13 -0
- package/src/client/nodes/HtmlNode.tsx +125 -13
- package/src/client/nodes/McpAppNode.tsx +12 -4
- package/src/client/state/canvas-store.ts +15 -5
- package/src/client/state/sse-bridge.ts +5 -4
- package/src/client/theme/global.css +123 -2
- package/src/client/types.ts +2 -1
- package/src/json-render/charts/components.tsx +41 -7
- package/src/json-render/charts/extra-components.tsx +13 -12
- package/src/json-render/renderer/index.tsx +1 -0
- package/src/json-render/server.ts +3 -1
- package/src/mcp/canvas-access.ts +54 -1
- package/src/mcp/server.ts +98 -28
- package/src/server/agent-context.ts +39 -0
- package/src/server/canvas-operations.ts +99 -38
- package/src/server/canvas-provenance.ts +8 -6
- package/src/server/canvas-schema.ts +94 -3
- package/src/server/canvas-serialization.ts +16 -4
- package/src/server/canvas-state.ts +9 -4
- package/src/server/demo-state.json +1143 -0
- package/src/server/demo.ts +25 -777
- package/src/server/html-node-summary.ts +141 -0
- package/src/server/html-primitives.ts +1300 -0
- package/src/server/index.ts +63 -3
- package/src/server/server.ts +154 -17
- package/src/server/spatial-analysis.ts +5 -3
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const HTML_CONTENT_SUMMARY_MAX_LENGTH = 900;
|
|
2
|
+
const HTML_AGENT_SUMMARY_MAX_LENGTH = 1200;
|
|
3
|
+
const HTML_REFERENCE_LIMIT = 12;
|
|
4
|
+
|
|
5
|
+
function pickString(value: unknown): string | null {
|
|
6
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function strings(value: unknown): string[] {
|
|
10
|
+
return Array.isArray(value)
|
|
11
|
+
? value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0)
|
|
12
|
+
: [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function truncateText(value: string, maxLength: number): string {
|
|
16
|
+
if (value.length <= maxLength) return value;
|
|
17
|
+
return `${value.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeWhitespace(value: string): string {
|
|
21
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function decodeHtmlEntities(value: string): string {
|
|
25
|
+
const named: Record<string, string> = {
|
|
26
|
+
amp: '&',
|
|
27
|
+
gt: '>',
|
|
28
|
+
lt: '<',
|
|
29
|
+
nbsp: ' ',
|
|
30
|
+
quot: '"',
|
|
31
|
+
apos: "'",
|
|
32
|
+
};
|
|
33
|
+
return value.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (match, entity) => {
|
|
34
|
+
const lower = entity.toLowerCase();
|
|
35
|
+
if (lower.startsWith('#x')) {
|
|
36
|
+
const codePoint = Number.parseInt(lower.slice(2), 16);
|
|
37
|
+
return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff ? String.fromCodePoint(codePoint) : match;
|
|
38
|
+
}
|
|
39
|
+
if (lower.startsWith('#')) {
|
|
40
|
+
const codePoint = Number.parseInt(lower.slice(1), 10);
|
|
41
|
+
return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff ? String.fromCodePoint(codePoint) : match;
|
|
42
|
+
}
|
|
43
|
+
return named[lower] ?? match;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function summarizeHtmlText(html: string): string | null {
|
|
48
|
+
const withoutNoise = html
|
|
49
|
+
.replace(/<!--[\s\S]*?-->/g, ' ')
|
|
50
|
+
.replace(/<script\b[\s\S]*?<\/script>/gi, ' ')
|
|
51
|
+
.replace(/<style\b[\s\S]*?<\/style>/gi, ' ')
|
|
52
|
+
.replace(/<noscript\b[\s\S]*?<\/noscript>/gi, ' ');
|
|
53
|
+
const text = withoutNoise
|
|
54
|
+
.replace(/<(?:h[1-6]|p|li|br|div|section|article|header|footer|main|aside|summary|figcaption|blockquote|tr|td|th)\b[^>]*>/gi, '\n')
|
|
55
|
+
.replace(/<[^>]+>/g, ' ');
|
|
56
|
+
const normalized = normalizeWhitespace(decodeHtmlEntities(text));
|
|
57
|
+
return normalized.length > 0 ? truncateText(normalized, HTML_CONTENT_SUMMARY_MAX_LENGTH) : null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function uniqueLimited(values: string[]): string[] {
|
|
61
|
+
const seen = new Set<string>();
|
|
62
|
+
const unique: string[] = [];
|
|
63
|
+
for (const value of values) {
|
|
64
|
+
const trimmed = value.trim();
|
|
65
|
+
if (!trimmed || seen.has(trimmed)) continue;
|
|
66
|
+
seen.add(trimmed);
|
|
67
|
+
unique.push(trimmed);
|
|
68
|
+
if (unique.length >= HTML_REFERENCE_LIMIT) break;
|
|
69
|
+
}
|
|
70
|
+
return unique;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function extractHtmlNodeIds(html: string): string[] {
|
|
74
|
+
const ids: string[] = [];
|
|
75
|
+
for (const match of html.matchAll(/\b(?:node|graph|json-render|web-artifact|mcp-app|group)-[a-z0-9-]+\b/gi)) {
|
|
76
|
+
ids.push(match[0]);
|
|
77
|
+
}
|
|
78
|
+
return uniqueLimited(ids);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function extractHtmlUrls(html: string): string[] {
|
|
82
|
+
const urls: string[] = [];
|
|
83
|
+
for (const match of html.matchAll(/\b(?:src|href)\s*=\s*["']([^"']+)["']/gi)) {
|
|
84
|
+
const url = match[1]?.trim();
|
|
85
|
+
if (!url) continue;
|
|
86
|
+
if (/^(?:https?:)?\/\//i.test(url) || url.startsWith('/') || url.startsWith('ui://')) {
|
|
87
|
+
urls.push(url);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return uniqueLimited(urls);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function joinSummaryParts(parts: string[]): string | null {
|
|
94
|
+
const summary = parts
|
|
95
|
+
.map((part) => part.trim())
|
|
96
|
+
.filter(Boolean)
|
|
97
|
+
.filter((part, index, all) => all.findIndex((candidate) => candidate === part) === index)
|
|
98
|
+
.join('\n');
|
|
99
|
+
return summary ? truncateText(summary, HTML_AGENT_SUMMARY_MAX_LENGTH) : null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function normalizeHtmlNodeSemanticData<T extends Record<string, unknown>>(data: T): T {
|
|
103
|
+
const {
|
|
104
|
+
agentSummary: _agentSummary,
|
|
105
|
+
contentSummary: _contentSummary,
|
|
106
|
+
embeddedNodeIds: _embeddedNodeIds,
|
|
107
|
+
embeddedUrls: _embeddedUrls,
|
|
108
|
+
embeddedNodeId: _embeddedNodeId,
|
|
109
|
+
embeddedGraphId: _embeddedGraphId,
|
|
110
|
+
sourceNodeId: _sourceNodeId,
|
|
111
|
+
...base
|
|
112
|
+
} = data;
|
|
113
|
+
|
|
114
|
+
const html = pickString(base.html);
|
|
115
|
+
const explicitSummary = pickString(base.summary) ?? pickString(base.description);
|
|
116
|
+
const primitive = pickString(base.htmlPrimitive);
|
|
117
|
+
const contentSummary = html ? summarizeHtmlText(html) : null;
|
|
118
|
+
const explicitNodeIds = [
|
|
119
|
+
...strings(data.embeddedNodeIds),
|
|
120
|
+
pickString(data.embeddedNodeId),
|
|
121
|
+
pickString(data.embeddedGraphId),
|
|
122
|
+
pickString(data.sourceNodeId),
|
|
123
|
+
].filter((value): value is string => value !== null);
|
|
124
|
+
const embeddedNodeIds = uniqueLimited([...explicitNodeIds, ...(html ? extractHtmlNodeIds(html) : [])]);
|
|
125
|
+
const embeddedUrls = uniqueLimited([...strings(data.embeddedUrls), ...(html ? extractHtmlUrls(html) : [])]);
|
|
126
|
+
const agentSummary = pickString(data.agentSummary) ?? joinSummaryParts([
|
|
127
|
+
primitive ? `HTML primitive: ${primitive}` : '',
|
|
128
|
+
explicitSummary ?? '',
|
|
129
|
+
contentSummary ?? '',
|
|
130
|
+
embeddedNodeIds.length > 0 ? `Embedded canvas nodes: ${embeddedNodeIds.join(', ')}` : '',
|
|
131
|
+
embeddedUrls.length > 0 ? `Embedded URLs: ${embeddedUrls.join(', ')}` : '',
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
...base,
|
|
136
|
+
...(contentSummary ? { contentSummary } : {}),
|
|
137
|
+
...(agentSummary ? { agentSummary } : {}),
|
|
138
|
+
...(embeddedNodeIds.length > 0 ? { embeddedNodeIds } : {}),
|
|
139
|
+
...(embeddedUrls.length > 0 ? { embeddedUrls } : {}),
|
|
140
|
+
} as T;
|
|
141
|
+
}
|