@ptengine/inline-editor 0.0.0-stage → 0.1.1

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.
Files changed (169) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +286 -2
  3. package/dist/InlineEditor.d.ts +349 -0
  4. package/dist/InlineEditor.js +886 -0
  5. package/dist/PagePreview.d.ts +83 -0
  6. package/dist/PagePreview.js +111 -0
  7. package/dist/assets/AssetsPanel.d.ts +19 -0
  8. package/dist/assets/AssetsPanel.js +23 -0
  9. package/dist/assets/componentLibrary.d.ts +23 -0
  10. package/dist/assets/componentLibrary.js +12 -0
  11. package/dist/canvas-ui/index.d.ts +17 -0
  12. package/dist/canvas-ui/index.js +7 -0
  13. package/dist/canvas-ui/scope.d.ts +13 -0
  14. package/dist/canvas-ui/scope.js +1 -0
  15. package/dist/canvas-ui/selection/classNames.d.ts +30 -0
  16. package/dist/canvas-ui/selection/classNames.js +15 -0
  17. package/dist/canvas-ui/selection/tokens.css +204 -0
  18. package/dist/canvas-ui/tree/TreePanel.d.ts +47 -0
  19. package/dist/canvas-ui/tree/TreePanel.js +132 -0
  20. package/dist/canvas-ui/tree/classNames.d.ts +21 -0
  21. package/dist/canvas-ui/tree/classNames.js +8 -0
  22. package/dist/canvas-ui/tree/order.d.ts +38 -0
  23. package/dist/canvas-ui/tree/order.js +38 -0
  24. package/dist/canvas-ui/tree/tree.css +131 -0
  25. package/dist/chrome/EditorChrome.d.ts +83 -0
  26. package/dist/chrome/EditorChrome.js +10 -0
  27. package/dist/chrome/VersionMenu.d.ts +26 -0
  28. package/dist/chrome/VersionMenu.js +86 -0
  29. package/dist/chrome/topbarPrimitives.d.ts +69 -0
  30. package/dist/chrome/topbarPrimitives.js +39 -0
  31. package/dist/chrome/useDismissOnOutside.d.ts +23 -0
  32. package/dist/chrome/useDismissOnOutside.js +27 -0
  33. package/dist/dialogs/ClickActionDialog.d.ts +24 -0
  34. package/dist/dialogs/ClickActionDialog.js +35 -0
  35. package/dist/dialogs/HtmlEditDialog.d.ts +23 -0
  36. package/dist/dialogs/HtmlEditDialog.js +23 -0
  37. package/dist/dialogs/ImageEditDialog.d.ts +30 -0
  38. package/dist/dialogs/ImageEditDialog.js +57 -0
  39. package/dist/dialogs/ImageSettingDialog.d.ts +16 -0
  40. package/dist/dialogs/ImageSettingDialog.js +17 -0
  41. package/dist/dialogs/InsertElementDialog.d.ts +23 -0
  42. package/dist/dialogs/InsertElementDialog.js +28 -0
  43. package/dist/dialogs/ReplaceVideoDialog.d.ts +18 -0
  44. package/dist/dialogs/ReplaceVideoDialog.js +16 -0
  45. package/dist/dialogs/actionTypes.d.ts +53 -0
  46. package/dist/dialogs/actionTypes.js +74 -0
  47. package/dist/dialogs/htmlValidation.d.ts +24 -0
  48. package/dist/dialogs/htmlValidation.js +37 -0
  49. package/dist/dialogs/imageEdit.d.ts +49 -0
  50. package/dist/dialogs/imageEdit.js +30 -0
  51. package/dist/index.d.ts +101 -0
  52. package/dist/index.js +53 -0
  53. package/dist/kernel/CanvasContextBar.d.ts +23 -0
  54. package/dist/kernel/CanvasContextBar.js +12 -0
  55. package/dist/kernel/CanvasFrame.d.ts +56 -0
  56. package/dist/kernel/CanvasFrame.js +53 -0
  57. package/dist/kernel/ai/aiInlineApply.d.ts +330 -0
  58. package/dist/kernel/ai/aiInlineApply.js +477 -0
  59. package/dist/kernel/ai/imageSource.d.ts +24 -0
  60. package/dist/kernel/ai/imageSource.js +19 -0
  61. package/dist/kernel/ai/insertHtmlGate.d.ts +71 -0
  62. package/dist/kernel/ai/insertHtmlGate.js +123 -0
  63. package/dist/kernel/ai/postApplyCheck.d.ts +56 -0
  64. package/dist/kernel/ai/postApplyCheck.js +74 -0
  65. package/dist/kernel/ai/replaceHtmlGate.d.ts +71 -0
  66. package/dist/kernel/ai/replaceHtmlGate.js +147 -0
  67. package/dist/kernel/blocks/BlockOverlay.d.ts +16 -0
  68. package/dist/kernel/blocks/BlockOverlay.js +66 -0
  69. package/dist/kernel/blocks/annotateAndTile.d.ts +106 -0
  70. package/dist/kernel/blocks/annotateAndTile.js +122 -0
  71. package/dist/kernel/blocks/resolveBlocks.d.ts +153 -0
  72. package/dist/kernel/blocks/resolveBlocks.js +233 -0
  73. package/dist/kernel/canvasCapabilities.d.ts +31 -0
  74. package/dist/kernel/canvasCapabilities.js +39 -0
  75. package/dist/kernel/canvasCommands.d.ts +126 -0
  76. package/dist/kernel/canvasCommands.js +146 -0
  77. package/dist/kernel/canvasErrors.d.ts +1 -0
  78. package/dist/kernel/canvasErrors.js +14 -0
  79. package/dist/kernel/captureGeometry.d.ts +35 -0
  80. package/dist/kernel/captureGeometry.js +25 -0
  81. package/dist/kernel/captureOrchestration.d.ts +48 -0
  82. package/dist/kernel/captureOrchestration.js +43 -0
  83. package/dist/kernel/captureSlices.d.ts +36 -0
  84. package/dist/kernel/captureSlices.js +20 -0
  85. package/dist/kernel/changelist/ChangeListPanel.d.ts +13 -0
  86. package/dist/kernel/changelist/ChangeListPanel.js +47 -0
  87. package/dist/kernel/changelist/ChangeListPopover.d.ts +20 -0
  88. package/dist/kernel/changelist/ChangeListPopover.js +10 -0
  89. package/dist/kernel/changelist/builders.d.ts +89 -0
  90. package/dist/kernel/changelist/builders.js +81 -0
  91. package/dist/kernel/changelist/changeSummary.d.ts +19 -0
  92. package/dist/kernel/changelist/changeSummary.js +85 -0
  93. package/dist/kernel/changelist/clipboard.d.ts +16 -0
  94. package/dist/kernel/changelist/clipboard.js +4 -0
  95. package/dist/kernel/changelist/exportForSave.d.ts +30 -0
  96. package/dist/kernel/changelist/exportForSave.js +11 -0
  97. package/dist/kernel/changelist/mergeForSave.d.ts +79 -0
  98. package/dist/kernel/changelist/mergeForSave.js +40 -0
  99. package/dist/kernel/changelist/moveObserverSafety.d.ts +70 -0
  100. package/dist/kernel/changelist/moveObserverSafety.js +39 -0
  101. package/dist/kernel/changelist/overrides.d.ts +22 -0
  102. package/dist/kernel/changelist/overrides.js +63 -0
  103. package/dist/kernel/changelist/useChangeList.d.ts +200 -0
  104. package/dist/kernel/changelist/useChangeList.js +282 -0
  105. package/dist/kernel/config.d.ts +29 -0
  106. package/dist/kernel/config.js +10 -0
  107. package/dist/kernel/dataUrlToBlob.d.ts +8 -0
  108. package/dist/kernel/dataUrlToBlob.js +17 -0
  109. package/dist/kernel/linkHref.d.ts +25 -0
  110. package/dist/kernel/linkHref.js +16 -0
  111. package/dist/kernel/mobileFit.d.ts +46 -0
  112. package/dist/kernel/mobileFit.js +44 -0
  113. package/dist/kernel/moveState.d.ts +35 -0
  114. package/dist/kernel/moveState.js +14 -0
  115. package/dist/kernel/selection/SelectionOverlay.d.ts +10 -0
  116. package/dist/kernel/selection/SelectionOverlay.js +25 -0
  117. package/dist/kernel/selection/SelectionToolbar.d.ts +30 -0
  118. package/dist/kernel/selection/SelectionToolbar.js +82 -0
  119. package/dist/kernel/selection/SelectorPathBar.d.ts +27 -0
  120. package/dist/kernel/selection/SelectorPathBar.js +55 -0
  121. package/dist/kernel/selection/buildDescendantShelf.d.ts +40 -0
  122. package/dist/kernel/selection/buildDescendantShelf.js +50 -0
  123. package/dist/kernel/selection/buildRootShelf.d.ts +26 -0
  124. package/dist/kernel/selection/buildRootShelf.js +149 -0
  125. package/dist/kernel/selection/elementAiContext.d.ts +324 -0
  126. package/dist/kernel/selection/elementAiContext.js +122 -0
  127. package/dist/kernel/selection/elementGeometryForAi.d.ts +9 -0
  128. package/dist/kernel/selection/elementGeometryForAi.js +15 -0
  129. package/dist/kernel/selection/geometry.d.ts +55 -0
  130. package/dist/kernel/selection/geometry.js +18 -0
  131. package/dist/kernel/selection/resolveAnchors.d.ts +116 -0
  132. package/dist/kernel/selection/resolveAnchors.js +173 -0
  133. package/dist/kernel/selection/shelfRow.d.ts +33 -0
  134. package/dist/kernel/selection/shelfRow.js +21 -0
  135. package/dist/kernel/selection/tagLabels.d.ts +9 -0
  136. package/dist/kernel/selection/tagLabels.js +46 -0
  137. package/dist/kernel/selection/toolbarButtons.d.ts +60 -0
  138. package/dist/kernel/selection/toolbarButtons.js +105 -0
  139. package/dist/kernel/selection/useAiSelectionContext.d.ts +26 -0
  140. package/dist/kernel/selection/useAiSelectionContext.js +26 -0
  141. package/dist/kernel/selection/useCanvasGeometry.d.ts +32 -0
  142. package/dist/kernel/selection/useCanvasGeometry.js +47 -0
  143. package/dist/kernel/selection/useSelectionRestore.d.ts +18 -0
  144. package/dist/kernel/selection/useSelectionRestore.js +25 -0
  145. package/dist/kernel/shrinkImageForUpload.d.ts +45 -0
  146. package/dist/kernel/shrinkImageForUpload.js +61 -0
  147. package/dist/kernel/useCanvasBridge.d.ts +36 -0
  148. package/dist/kernel/useCanvasBridge.js +41 -0
  149. package/dist/kernel/useCanvasGate.d.ts +22 -0
  150. package/dist/kernel/useCanvasGate.js +35 -0
  151. package/dist/kernel/useCanvasSession.d.ts +44 -0
  152. package/dist/kernel/useCanvasSession.js +182 -0
  153. package/dist/kernel/useCanvasStream.d.ts +39 -0
  154. package/dist/kernel/useCanvasStream.js +45 -0
  155. package/dist/kernel/useMobileFit.d.ts +40 -0
  156. package/dist/kernel/useMobileFit.js +139 -0
  157. package/dist/locales.d.ts +1094 -0
  158. package/dist/locales.js +545 -0
  159. package/dist/panels/StylePanel.d.ts +24 -0
  160. package/dist/panels/StylePanel.js +40 -0
  161. package/dist/panels/styleFields.d.ts +39 -0
  162. package/dist/panels/styleFields.js +91 -0
  163. package/dist/tree/LayersPanel.d.ts +30 -0
  164. package/dist/tree/LayersPanel.js +15 -0
  165. package/dist/tree/domTreeAdapter.d.ts +63 -0
  166. package/dist/tree/domTreeAdapter.js +90 -0
  167. package/dist/useInlineEditor.d.ts +161 -0
  168. package/dist/useInlineEditor.js +346 -0
  169. package/package.json +58 -3
@@ -0,0 +1,122 @@
1
+ export const ANNOTATE_DEFAULTS = { tileHeight: 2000, maxTiles: 6 };
2
+ export const MIN_DOWNSCALE = 0.5;
3
+ export const BLOCK_COLORS = ['#1D4ED8', '#DB2777'];
4
+ const STROKE_PX = 3;
5
+ const LABEL_PAD = 4;
6
+ const LABEL_FONT_PX = 16;
7
+ const LABEL_H = LABEL_FONT_PX + 8;
8
+ export const TILE_JPEG_QUALITY = 0.82;
9
+ export function planAnnotateAndTile(input) {
10
+ const tileHeight = Math.max(1, Math.trunc(input.tileHeight ?? ANNOTATE_DEFAULTS.tileHeight));
11
+ const maxTiles = Math.max(1, Math.trunc(input.maxTiles ?? ANNOTATE_DEFAULTS.maxTiles));
12
+ const pageWidthPx = input.pageWidthPx && input.pageWidthPx > 0 ? input.pageWidthPx : input.imageWidth;
13
+ const dpr = input.imageWidth / pageWidthPx;
14
+ const budget = tileHeight * maxTiles;
15
+ const downscale = input.imageHeight > budget ? Math.max(MIN_DOWNSCALE, budget / input.imageHeight) : 1;
16
+ const width = Math.max(1, Math.round(input.imageWidth * downscale));
17
+ const height = Math.max(1, Math.round(input.imageHeight * downscale));
18
+ const pxPerPagePx = dpr * downscale;
19
+ const tiles = [];
20
+ for (let top = 0, index = 0; top < height; top += tileHeight, index++) {
21
+ tiles.push({ index, top, height: Math.min(tileHeight, height - top) });
22
+ }
23
+ const truncated = tiles.length > maxTiles;
24
+ const boxes = [];
25
+ input.blocks.forEach((b, i) => {
26
+ const rawY = b.top * pxPerPagePx;
27
+ const rawBottom = (b.top + b.height) * pxPerPagePx;
28
+ const y = Math.max(0, Math.round(rawY));
29
+ const bottom = Math.min(height, Math.round(rawBottom));
30
+ if (bottom <= y)
31
+ return;
32
+ const color = BLOCK_COLORS[i % 2];
33
+ boxes.push({
34
+ id: b.id,
35
+ x: 0,
36
+ y,
37
+ width,
38
+ height: bottom - y,
39
+ color,
40
+ label: {
41
+ x: LABEL_PAD,
42
+ y: y + LABEL_PAD,
43
+ width: Math.round(LABEL_FONT_PX * 0.62 * b.id.length + LABEL_PAD * 2),
44
+ height: LABEL_H,
45
+ text: b.id
46
+ }
47
+ });
48
+ });
49
+ return {
50
+ downscale,
51
+ pxPerPagePx,
52
+ width,
53
+ height,
54
+ pageHeight: Math.round(input.imageHeight / dpr),
55
+ tileHeight,
56
+ tiles: tiles.slice(0, maxTiles),
57
+ boxes,
58
+ truncated
59
+ };
60
+ }
61
+ function toBlob(canvas, type, quality) {
62
+ return new Promise((resolve, reject) => {
63
+ canvas.toBlob(b => (b ? resolve(b) : reject(new Error('toBlob returned null'))), type, quality);
64
+ });
65
+ }
66
+ const browserDeps = {
67
+ async decode(blob) {
68
+ const bitmap = await createImageBitmap(blob);
69
+ return { width: bitmap.width, height: bitmap.height, source: bitmap };
70
+ },
71
+ async render(image, plan) {
72
+ const full = document.createElement('canvas');
73
+ full.width = plan.width;
74
+ full.height = plan.height;
75
+ const ctx = full.getContext('2d');
76
+ if (!ctx)
77
+ throw new Error('canvas 2d context unavailable');
78
+ ctx.drawImage(image.source, 0, 0, plan.width, plan.height);
79
+ ctx.lineWidth = STROKE_PX;
80
+ ctx.font = `bold ${LABEL_FONT_PX}px system-ui, -apple-system, sans-serif`;
81
+ ctx.textBaseline = 'middle';
82
+ for (const box of plan.boxes) {
83
+ ctx.strokeStyle = box.color;
84
+ ctx.strokeRect(box.x + STROKE_PX / 2, box.y + STROKE_PX / 2, Math.max(0, box.width - STROKE_PX), Math.max(0, box.height - STROKE_PX));
85
+ ctx.fillStyle = box.color;
86
+ ctx.fillRect(box.label.x, box.label.y, box.label.width, box.label.height);
87
+ ctx.fillStyle = '#FFFFFF';
88
+ ctx.fillText(box.label.text, box.label.x + LABEL_PAD, box.label.y + box.label.height / 2);
89
+ }
90
+ const out = [];
91
+ for (const tile of plan.tiles) {
92
+ const c = document.createElement('canvas');
93
+ c.width = plan.width;
94
+ c.height = tile.height;
95
+ const tctx = c.getContext('2d');
96
+ if (!tctx)
97
+ throw new Error('canvas 2d context unavailable');
98
+ tctx.drawImage(full, 0, tile.top, plan.width, tile.height, 0, 0, plan.width, tile.height);
99
+ out.push(await toBlob(c, 'image/jpeg', TILE_JPEG_QUALITY));
100
+ }
101
+ return out;
102
+ }
103
+ };
104
+ export async function annotateAndTile(fullPage, blocks, opts = {}, deps = browserDeps) {
105
+ const image = await deps.decode(fullPage);
106
+ const plan = planAnnotateAndTile({
107
+ imageWidth: image.width,
108
+ imageHeight: image.height,
109
+ pageWidthPx: opts.pageWidthPx,
110
+ blocks,
111
+ tileHeight: opts.tileHeight,
112
+ maxTiles: opts.maxTiles
113
+ });
114
+ const tiles = await deps.render(image, plan);
115
+ return {
116
+ tiles,
117
+ scale: plan.pxPerPagePx,
118
+ pageHeight: plan.pageHeight,
119
+ tileHeight: plan.tileHeight,
120
+ truncated: plan.truncated
121
+ };
122
+ }
@@ -0,0 +1,153 @@
1
+ import type { DomTreeNode, ElementsInBandResult, GetDomTreeParams, ResolveHeatmapSelectorResult } from '@ptengine/inline-core/protocol';
2
+ /**
3
+ * 分析画布:把热图的**区块配置**(MySQL 里那份,apps/x 读来)画到客户活页面上,
4
+ * 并给每块找一个锚点元素 + 一份 AI 能读的摘要(标题 / 文案 / CTA)。
5
+ *
6
+ * ## 框来自配置,锚点来自页面
7
+ *
8
+ * **区块框恒等于配置的 `yStart/yEnd`**,与老热图 `CommonBlockMap.vue` 同一个契约
9
+ * (那边 `top: yStart*scale` / `height: (yEnd-yStart)*scale`,从不问 DOM)。这里解析出来的
10
+ * 元素**只决定锚点**(`members[].selector`:点进哪个元素、给 agent 交哪条 selector),
11
+ * 不回头改写框的位置。
12
+ *
13
+ * 曾经不是这样:框由解析到的元素矩形首尾相接推出来。实测(vivaia margot-walker,16 块配置)
14
+ * 的结果是 B2 吞掉 B3 上半段、B6 塌成 15px 细条**并排到了 B5 前面**、边界整体漂 300-500px ——
15
+ * 因为那页配置里的 selector 全都断在一个已被站点删掉的 `#wrapper` 祖先上,16 块全部退到几何
16
+ * 兜底,而几何只要 IoU ≥ 0.3 就认。职责一分开,解析失败就从「画错框」降级成「少个锚点」。
17
+ *
18
+ * 三条路(只影响锚点与摘要),按优先级:
19
+ * 1. **selector** —— 配置里的 jQuery 方言 selector 翻译后在活页面上唯一命中(`resolveHeatmapSelector`)。
20
+ * 命中的每条是一个 member;区块矩形 = 所有 member 的并(min top → max bottom)。
21
+ * 2. **geometry** —— selector 全失效时(客户改版,vivaia.jp 实测 0/33),拿 `yStart/yEnd`
22
+ * 去活页面上找最贴合的容器(`elementsInBand`,1-D IoU),取最佳一个当 member,记下 `iou`。
23
+ * 刻意**只在 selector 失败时才跑**:它要滚动取样,一块几十毫秒,十几块就是一秒多。
24
+ * 3. **none** —— 都没有:members 空。区块框照画(位置本来就来自配置,一样准),
25
+ * 只是点不进任何元素、也没有 selector 可以交给 agent。
26
+ *
27
+ * 编号 `B1..Bn` 按输入的 `yStart` 升序,与模型看到的瓦片标注一致(`annotateAndTile`)。
28
+ *
29
+ * 三条跨域命令经 `deps` 注入:编排逻辑不需要插件也不需要浏览器就能测。任何一条失败都
30
+ * **降级而不是抛**(老 agent 不认新命令 → AGENT_STALE;frame 失联 → FRAME_UNREACHABLE):
31
+ * 分析画布宁可少几块摘要,也不能因为一块失败让整页黑掉。
32
+ */
33
+ export interface HeatmapBlockConfig {
34
+ id: string | number;
35
+ name: string;
36
+ /** 文档 px,按热图量测时的视口宽(mobile 375 / pc 1440)。 */
37
+ yStart: number;
38
+ yEnd: number;
39
+ /** 热图 / SDK 的 jQuery 方言(`body>#wrapper>div:eq(0)…`,可能带 `v2>` 前缀)。 */
40
+ selectors: string[];
41
+ }
42
+ export interface PageModelBlockMember {
43
+ /** 内核方言(`buildSelector`)—— 画布闸门唯一认的那种。 */
44
+ selector: string;
45
+ tag: string;
46
+ /** 该 member 子树的文案摘要(折叠空白、≤80)。拉树失败为空串。 */
47
+ text: string;
48
+ hasChildren: boolean;
49
+ }
50
+ export interface PageModelBlock {
51
+ /** `'B1'`,`'B2'`… 按 `yStart` 升序。 */
52
+ id: string;
53
+ /**
54
+ * 热图配置里的块名。**原样保留**——`page_block_metrics` 的 `blockName` 靠它对上号。
55
+ * 自动识别常常拿图片文件名凑(`Frame 2007695980.png__PID:…`),给人看的那份在 `label`。
56
+ */
57
+ name: string;
58
+ /**
59
+ * 给人看的名字:`name` 像自动生成的、且这一块解析出了标题时 = 该标题。
60
+ * 缺省 = `name` 本身就能读。画布芯片、`<page-model>`、假设卡都显示它。
61
+ */
62
+ label?: string;
63
+ /**
64
+ * 文档 px,**恒等于配置的 `yStart`**(不随解析结果变)。
65
+ * 与 `<page-model>` / 截图编号框 / 老热图共用同一套坐标,三处永远对得上。
66
+ */
67
+ top: number;
68
+ /** 文档 px,**恒等于配置的 `yEnd - yStart`**。 */
69
+ height: number;
70
+ members: PageModelBlockMember[];
71
+ /** 根节点的 heading(agent 侧 h1-h4 → 首段 ≥4 字直属文本的兜底)。 */
72
+ heading?: string;
73
+ /** a / button / 链接节点的短文案(整棵子树的文字,≤20 字),最多 4 条,去重。 */
74
+ ctas: string[];
75
+ /**
76
+ * 这一块里可点的链接(`<a href>` 与 PageFly 图片链接 / 商品卡的 `data-href`):`count` 个元素,
77
+ * 按去向(`linkPath`,查询串去掉)聚合、次数多的在前。**没有链接时不写这个键**(老扩展的树也没有)。
78
+ *
79
+ * 与 `ctas` 是两件事:图片链接没有文字,是导线但进不了 cta。真机 2026-10-15(vivaia allday)只有
80
+ * cta 一栏时,agent 把「没有 cta」读成了「全页没有购买导线」,而那页几乎每张图都链到商品页。
81
+ */
82
+ links?: BlockLinks;
83
+ /** 全部 member 子树文案拼接、折叠空白、≤80。 */
84
+ text: string;
85
+ resolved: 'selector' | 'geometry' | 'none';
86
+ /** 只有 `resolved === 'geometry'` 时有:最佳候选的 1-D IoU。 */
87
+ iou?: number;
88
+ }
89
+ export interface BlockLinks {
90
+ count: number;
91
+ targets: Array<{
92
+ path: string;
93
+ count: number;
94
+ }>;
95
+ }
96
+ export interface ResolveBlocksDeps {
97
+ resolveHeatmapSelector: (sessionId: string, selectors: string[]) => Promise<ResolveHeatmapSelectorResult>;
98
+ elementsInBand: (sessionId: string, band: {
99
+ yStart: number;
100
+ yEnd: number;
101
+ minIou?: number;
102
+ }) => Promise<ElementsInBandResult>;
103
+ getDomTree: (sessionId: string, opts: Omit<GetDomTreeParams, 'command' | 'sessionId'>) => Promise<DomTreeNode>;
104
+ }
105
+ export declare const BLOCK_TEXT_MAX = 80;
106
+ export declare const CTA_TEXT_MAX = 20;
107
+ export declare const CTA_LIMIT = 4;
108
+ /**
109
+ * 摘要子树的总节点数封顶。真机(vivaia allday)一个 PageFly section 的可见节点只有 8-51 个,
110
+ * 留足余量;它只是防一块异常巨大的区块把整次 kickoff 拖慢。
111
+ */
112
+ export declare const SUMMARY_TREE_MAX_NODES = 400;
113
+ export interface TreeSummary {
114
+ heading?: string;
115
+ text: string;
116
+ ctas: string[];
117
+ /** 子树里每个链接节点的去向(`linkPath`),前序 DFS 顺序、不去重 —— 计数要用。 */
118
+ links: string[];
119
+ hasChildren: boolean;
120
+ }
121
+ /**
122
+ * 从一棵子树派生摘要。纯函数。
123
+ *
124
+ * - `heading`:根节点的 `heading`(agent 已做 h1-h4 → 直属文本兜底),空串视作没有;
125
+ * - `text`:前序 DFS 拼接每个节点的 `textSummary`(那是**直属**文本,所以拼起来不重复),
126
+ * 折叠空白、截到 80;
127
+ * - `ctas`:a / button / 带 `href` 的节点,取**整棵子树**的文字(`a>u>strong>span>span` 的直属
128
+ * 文本在最里层,`<a>` 自己是空的),≤20 字的非空文案,去重,最多 4 条;
129
+ * - `links`:带 `href` 的节点(`<a href>`、PageFly 的 `<img data-href>`),链接里面的链接不重复计。
130
+ *
131
+ * `hidden` 的节点连同子树整棵跳过:看不见的文字与链接不是这块的内容(取树时已 `visibleOnly`,
132
+ * 这里兜不带它的老调用方)。
133
+ */
134
+ export declare function summarizeTree(root: DomTreeNode): TreeSummary;
135
+ export interface ResolveBlocksOptions {
136
+ /**
137
+ * 画布当前施加在 `<html>` 上的缩放比例(`useInlineEditor().mobileFit.scale`),默认 1。
138
+ *
139
+ * 热图配置的 `yStart/yEnd` 是客户页**自然**文档坐标,而 agent 在缩放后的页面上量到的
140
+ * 元素位置是**视觉**坐标 —— `elementsInBand` 的 IoU 两边口径必须一致,所以 band 乘上
141
+ * 比例再发。产出的 `top/height` **不变**(仍是配置原值):`<page-model>`、截图标注、老热图
142
+ * 三处共用这套坐标,由各自的消费者在画的时候乘比例。
143
+ */
144
+ scale?: number;
145
+ }
146
+ export declare function resolveBlocks(sessionId: string, blocks: HeatmapBlockConfig[], deps?: ResolveBlocksDeps, opts?: ResolveBlocksOptions): Promise<PageModelBlock[]>;
147
+ /**
148
+ * 块名是不是自动识别凑出来的(图片文件名 / 设计稿图层名 / uuid / 占位名)。
149
+ *
150
+ * 判据只认**明确像机器生成**的形状,宁可漏判也不误判:真人写的名字被换掉,用户会以为
151
+ * 区块对错了。
152
+ */
153
+ export declare function isAutoGeneratedBlockName(name: string): boolean;
@@ -0,0 +1,233 @@
1
+ import { elementsInBand as elementsInBandCommand, getDomTree as getDomTreeCommand, resolveHeatmapSelector as resolveHeatmapSelectorCommand } from '../canvasCommands.js';
2
+ import { linkPath } from '../linkHref.js';
3
+ import { SHELF_TREE_MAX_DEPTH } from '../selection/elementAiContext.js';
4
+ const defaultDeps = {
5
+ resolveHeatmapSelector: resolveHeatmapSelectorCommand,
6
+ elementsInBand: elementsInBandCommand,
7
+ getDomTree: getDomTreeCommand
8
+ };
9
+ export const BLOCK_TEXT_MAX = 80;
10
+ export const CTA_TEXT_MAX = 20;
11
+ export const CTA_LIMIT = 4;
12
+ export const SUMMARY_TREE_MAX_NODES = 400;
13
+ const TREE_OPTS = {
14
+ maxDepth: SHELF_TREE_MAX_DEPTH,
15
+ maxChildren: 20,
16
+ maxNodes: SUMMARY_TREE_MAX_NODES,
17
+ visibleOnly: true,
18
+ includeRect: true
19
+ };
20
+ const CTA_TAGS = new Set(['a', 'button']);
21
+ function collapse(text) {
22
+ return text.replace(/\s+/g, ' ').trim();
23
+ }
24
+ function clamp(text, max) {
25
+ return text.length > max ? text.slice(0, max) : text;
26
+ }
27
+ function subtreeText(n) {
28
+ if (n.hidden)
29
+ return '';
30
+ return [n.textSummary ?? '', ...(n.children ?? []).map(subtreeText)].join(' ');
31
+ }
32
+ export function summarizeTree(root) {
33
+ const texts = [];
34
+ const ctas = [];
35
+ const links = [];
36
+ const seen = new Set();
37
+ const walk = (n, insideCta) => {
38
+ if (n.hidden)
39
+ return;
40
+ const t = collapse(n.textSummary ?? '');
41
+ if (t)
42
+ texts.push(t);
43
+ const isCta = !insideCta && (!!n.href || CTA_TAGS.has(n.tag));
44
+ if (isCta) {
45
+ if (n.href)
46
+ links.push(linkPath(n.href));
47
+ const label = collapse(subtreeText(n));
48
+ if (label && label.length <= CTA_TEXT_MAX && !seen.has(label) && ctas.length < CTA_LIMIT) {
49
+ seen.add(label);
50
+ ctas.push(label);
51
+ }
52
+ }
53
+ for (const c of n.children ?? [])
54
+ walk(c, insideCta || isCta);
55
+ };
56
+ walk(root, false);
57
+ const heading = collapse(root.heading ?? '');
58
+ return {
59
+ heading: heading || undefined,
60
+ text: clamp(collapse(texts.join(' ')), BLOCK_TEXT_MAX),
61
+ ctas,
62
+ links,
63
+ hasChildren: (root.childCount ?? 0) > 0 || (root.children?.length ?? 0) > 0
64
+ };
65
+ }
66
+ function aggregateLinks(paths) {
67
+ if (paths.length === 0)
68
+ return undefined;
69
+ const counts = new Map();
70
+ for (const p of paths)
71
+ counts.set(p, (counts.get(p) ?? 0) + 1);
72
+ const targets = Array.from(counts, ([path, count]) => ({ path, count })).sort((a, b) => b.count - a.count);
73
+ return { count: paths.length, targets };
74
+ }
75
+ function geometryOf(cfg) {
76
+ return { top: cfg.yStart, height: Math.max(0, cfg.yEnd - cfg.yStart) };
77
+ }
78
+ function idOf(index) {
79
+ return `B${index + 1}`;
80
+ }
81
+ function bestCandidate(candidates) {
82
+ let best = null;
83
+ for (const c of candidates)
84
+ if (!best || c.iou > best.iou)
85
+ best = c;
86
+ return best;
87
+ }
88
+ export async function resolveBlocks(sessionId, blocks, deps = defaultDeps, opts = {}) {
89
+ if (blocks.length === 0)
90
+ return [];
91
+ const scale = opts.scale && opts.scale > 0 ? opts.scale : 1;
92
+ const ordered = blocks
93
+ .map((b, i) => ({ b, i }))
94
+ .sort((x, y) => x.b.yStart - y.b.yStart || x.i - y.i)
95
+ .map(x => x.b);
96
+ const allSelectors = Array.from(new Set(ordered.flatMap(b => (b.selectors ?? []).filter(s => typeof s === 'string' && s.length > 0))));
97
+ const matches = new Map();
98
+ if (allSelectors.length > 0) {
99
+ try {
100
+ const { results } = await deps.resolveHeatmapSelector(sessionId, allSelectors);
101
+ for (const r of results ?? [])
102
+ matches.set(r.input, r);
103
+ }
104
+ catch {
105
+ }
106
+ }
107
+ const out = [];
108
+ for (let i = 0; i < ordered.length; i++) {
109
+ const cfg = ordered[i];
110
+ const base = { id: idOf(i), name: cfg.name };
111
+ const hits = [];
112
+ const seen = new Set();
113
+ for (const s of cfg.selectors ?? []) {
114
+ const m = matches.get(s);
115
+ if (!m || m.matchCount !== 1 || !m.kernelSelector || !m.rect)
116
+ continue;
117
+ if (seen.has(m.kernelSelector))
118
+ continue;
119
+ seen.add(m.kernelSelector);
120
+ hits.push({ selector: m.kernelSelector, tag: m.tag ?? '', rect: m.rect });
121
+ }
122
+ if (hits.length > 0) {
123
+ out.push(await enrich(sessionId, deps, {
124
+ ...base,
125
+ ...geometryOf(cfg),
126
+ members: hits.map(h => ({
127
+ selector: h.selector,
128
+ tag: h.tag,
129
+ text: '',
130
+ hasChildren: false
131
+ })),
132
+ ctas: [],
133
+ text: '',
134
+ resolved: 'selector'
135
+ }));
136
+ continue;
137
+ }
138
+ let candidate = null;
139
+ try {
140
+ const { candidates } = await deps.elementsInBand(sessionId, {
141
+ yStart: cfg.yStart * scale,
142
+ yEnd: cfg.yEnd * scale
143
+ });
144
+ candidate = bestCandidate(candidates ?? []);
145
+ }
146
+ catch {
147
+ candidate = null;
148
+ }
149
+ if (candidate) {
150
+ out.push(await enrich(sessionId, deps, {
151
+ ...base,
152
+ ...geometryOf(cfg),
153
+ members: [
154
+ {
155
+ selector: candidate.kernelSelector,
156
+ tag: candidate.tag,
157
+ text: '',
158
+ hasChildren: false
159
+ }
160
+ ],
161
+ ctas: [],
162
+ text: '',
163
+ resolved: 'geometry',
164
+ iou: candidate.iou
165
+ }));
166
+ continue;
167
+ }
168
+ out.push({
169
+ ...base,
170
+ ...geometryOf(cfg),
171
+ members: [],
172
+ ctas: [],
173
+ text: '',
174
+ resolved: 'none'
175
+ });
176
+ }
177
+ return out;
178
+ }
179
+ export function isAutoGeneratedBlockName(name) {
180
+ const n = name.trim();
181
+ if (!n)
182
+ return true;
183
+ if (/\.(png|jpe?g|webp|gif|svg|avif)\b/i.test(n))
184
+ return true;
185
+ if (/__PID:/i.test(n))
186
+ return true;
187
+ if (/^(frame|group|rectangle|image|img|component|layer)[\s_-]*\d+$/i.test(n))
188
+ return true;
189
+ if (/^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}/i.test(n))
190
+ return true;
191
+ if (/^unnamed\b/i.test(n))
192
+ return true;
193
+ return false;
194
+ }
195
+ async function enrich(sessionId, deps, block) {
196
+ const summaries = await Promise.all(block.members.map(async (m) => {
197
+ try {
198
+ const tree = await deps.getDomTree(sessionId, {
199
+ rootSelector: m.selector,
200
+ ...TREE_OPTS
201
+ });
202
+ return summarizeTree(tree);
203
+ }
204
+ catch {
205
+ return null;
206
+ }
207
+ }));
208
+ const members = block.members.map((m, i) => {
209
+ const s = summaries[i];
210
+ return s ? { ...m, text: s.text, hasChildren: s.hasChildren } : m;
211
+ });
212
+ const heading = summaries.find(s => s?.heading)?.heading;
213
+ const ctas = [];
214
+ const seen = new Set();
215
+ for (const s of summaries) {
216
+ for (const c of s?.ctas ?? []) {
217
+ if (seen.has(c) || ctas.length >= CTA_LIMIT)
218
+ continue;
219
+ seen.add(c);
220
+ ctas.push(c);
221
+ }
222
+ }
223
+ const text = clamp(collapse(summaries.map(s => s?.text ?? '').join(' ')), BLOCK_TEXT_MAX);
224
+ const enriched = { ...block, members, ctas, text };
225
+ if (heading)
226
+ enriched.heading = heading;
227
+ const links = aggregateLinks(summaries.flatMap(s => s?.links ?? []));
228
+ if (links)
229
+ enriched.links = links;
230
+ if (heading && isAutoGeneratedBlockName(block.name))
231
+ enriched.label = heading;
232
+ return enriched;
233
+ }
@@ -0,0 +1,31 @@
1
+ import { type HandshakeResult, type ReadyPayload } from '@ptengine/inline-core/protocol';
2
+ /**
3
+ * 画布能力协商(契约 v11,设计见 inline-core `docs/contract-v11-design.md` §3)。
4
+ *
5
+ * 门禁不再按「产品页版本 ≤ 插件版本」整页拦截,而是:
6
+ * - 插件回报的能力集(v11+ 的 `capabilities`;旧插件按 SINCE 表由版本推导);
7
+ * - 减去产品页侧**关掉**的能力(`configureInlineEditor` 的 `disabledCapabilities`)—— 插件发出去收不回,
8
+ * v11 的任何 bug 都要能在产品页侧关掉对应能力、退回旧行为,而不是等下一次插件发版;
9
+ * - 只有缺 CORE 才整页拦截,其余能力由各消费方 `hasCanvasCapability()` 逐项启用 / 降级。
10
+ */
11
+ /** 解析能力名列表(命令名或 `feature:*`):逗号分隔字符串或字符串数组,trim、去空。 */
12
+ export declare function parseDisabledCapabilities(raw: unknown): Set<string>;
13
+ /** 消费方经 `configureInlineEditor({ disabledCapabilities })` 配置的「即使插件支持也不启用」的能力。 */
14
+ export declare function disabledCapabilities(): Set<string>;
15
+ /**
16
+ * 握手 → 本会话可用的能力集。
17
+ *
18
+ * CORE 能力**不能被关掉**:关掉它们等于整页关闭画布,那应该用别的开关明确做,而不是
19
+ * 借能力开关让门禁在一个看起来无关的配置下报 CONTRACT_MISMATCH。
20
+ */
21
+ export declare function capabilitiesFromHandshake(handshake: Pick<HandshakeResult, 'contractVersion' | 'capabilities'>, disabled?: Set<string>): Set<string>;
22
+ /**
23
+ * 按 agent 自报的能力收窄。SW 与 agent 可能版本不一致(已打开 tab 里残留旧 content script):
24
+ * 产品页以 `handshake ∩ ready` 为准。agent 不会回报 SW 命令,所以 SW 命令恒保留。
25
+ * 旧 agent 不带 `capabilities` → 原样返回(SW 侧的 AGENT_STALE 机制继续兜底)。
26
+ */
27
+ export declare function narrowByReady(caps: Set<string>, ready: Pick<ReadyPayload, 'capabilities'> | undefined): Set<string>;
28
+ /** 缺了哪些 CORE 能力;非空即整页拦截。 */
29
+ export declare function missingCoreCapabilities(caps: Set<string>): string[];
30
+ /** 消费方按此逐项启用 / 降级。能力集还没就绪(门禁前)一律按不可用。 */
31
+ export declare function hasCanvasCapability(caps: Set<string> | undefined, name: string): boolean;
@@ -0,0 +1,39 @@
1
+ import { CANVAS_CORE_CAPABILITIES, CANVAS_SW_COMMANDS, resolveCapabilities } from '@ptengine/inline-core/protocol';
2
+ import { getInlineEditorConfig } from './config.js';
3
+ export function parseDisabledCapabilities(raw) {
4
+ const items = typeof raw === 'string' ? raw.split(',') : Array.isArray(raw) ? raw : [];
5
+ return new Set(items
6
+ .filter((s) => typeof s === 'string')
7
+ .map(s => s.trim())
8
+ .filter(Boolean));
9
+ }
10
+ export function disabledCapabilities() {
11
+ return parseDisabledCapabilities(getInlineEditorConfig().disabledCapabilities);
12
+ }
13
+ export function capabilitiesFromHandshake(handshake, disabled = disabledCapabilities()) {
14
+ const caps = resolveCapabilities(handshake);
15
+ const core = new Set(CANVAS_CORE_CAPABILITIES);
16
+ for (const name of disabled) {
17
+ if (!core.has(name))
18
+ caps.delete(name);
19
+ }
20
+ return caps;
21
+ }
22
+ export function narrowByReady(caps, ready) {
23
+ if (!ready || !Array.isArray(ready.capabilities))
24
+ return caps;
25
+ const agent = new Set(ready.capabilities);
26
+ const sw = new Set(CANVAS_SW_COMMANDS);
27
+ const out = new Set();
28
+ for (const c of caps) {
29
+ if (agent.has(c) || sw.has(c))
30
+ out.add(c);
31
+ }
32
+ return out;
33
+ }
34
+ export function missingCoreCapabilities(caps) {
35
+ return CANVAS_CORE_CAPABILITIES.filter(c => !caps.has(c));
36
+ }
37
+ export function hasCanvasCapability(caps, name) {
38
+ return !!caps && caps.has(name);
39
+ }