@opencue/cli 0.0.0-stage → 1.0.41
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/LICENSE.md +12 -0
- package/README.md +1208 -2
- package/bin/dscode.exe +12 -0
- package/bin/stt/requirements.txt +20 -0
- package/bin/stt/stt_server.py +544 -0
- package/bin/tts/requirements.txt +21 -0
- package/bin/tts/tts_server.py +404 -0
- package/cli-wrapper.cjs +109 -0
- package/install.cjs +331 -0
- package/llms.txt +115 -0
- package/package.json +56 -4
- package/scripts/figure/compose.py +363 -0
- package/scripts/figure/replay-shots.mjs +205 -0
- package/web/dist/assets/AssistantsView-dhdYVIy0.js +1 -0
- package/web/dist/assets/AutomationView-D1dU5ZV7.js +1 -0
- package/web/dist/assets/ChannelConfigView-CEPVZntU.js +8 -0
- package/web/dist/assets/ChartsView-DdN-beHW.js +1 -0
- package/web/dist/assets/ConnectorsView-CseCss9C.js +1 -0
- package/web/dist/assets/FileTreeView-Dnc3-gRF.js +1 -0
- package/web/dist/assets/HookConfigView-DA2h4HdJ.js +1 -0
- package/web/dist/assets/InputArea-DAgOOjOc.js +14 -0
- package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/web/dist/assets/MemoryView-D6OUBi4-.js +1 -0
- package/web/dist/assets/MessageBubble-CCTRbIMZ.css +1 -0
- package/web/dist/assets/MessageBubble-CV16DZMG.js +453 -0
- package/web/dist/assets/ModelsView-BuqD9vWl.js +1 -0
- package/web/dist/assets/PluginsView-CIziDHFJ.js +2 -0
- package/web/dist/assets/ProjectsView-c6faWy32.js +1 -0
- package/web/dist/assets/SessionGrid-BZJHcklh.js +4 -0
- package/web/dist/assets/SessionGrid-Bk5CAXEn.css +1 -0
- package/web/dist/assets/SettingsModal-BqdGEGW-.js +4 -0
- package/web/dist/assets/SkillsView-CB6CKkmb.js +1 -0
- package/web/dist/assets/TemplatesTab-B8efVwVZ.js +1 -0
- package/web/dist/assets/ThreadsView-CDoM0aNb.js +2 -0
- package/web/dist/assets/ToolsView-BG-xsWsO.js +1 -0
- package/web/dist/assets/abnfDiagram-WEYOIA3C-DGUPHfRO.js +1 -0
- package/web/dist/assets/agentIconSet-Btsb_SC4.js +1 -0
- package/web/dist/assets/api-CCt9DRQm.js +1 -0
- package/web/dist/assets/arc-Y1ARJVZ4.js +1 -0
- package/web/dist/assets/architecture-WOLXFQ4H-BUERx_-B.js +1 -0
- package/web/dist/assets/architectureDiagram-ZHVYKT53-BPsQL7O5.js +36 -0
- package/web/dist/assets/array-BifhSqXX.js +1 -0
- package/web/dist/assets/blockDiagram-RJAE7QA3-pB7EXp6p.js +140 -0
- package/web/dist/assets/c4Diagram-XDPPPE54-BnPeCq_L.js +38 -0
- package/web/dist/assets/channel-CSrLW66f.js +1 -0
- package/web/dist/assets/channelMeta-BGQXQxgj.js +1 -0
- package/web/dist/assets/chunk-2E7WHVMD-xX282MrC.js +88 -0
- package/web/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/web/dist/assets/chunk-4EMNKR2T-BAQDLVqt.js +1 -0
- package/web/dist/assets/chunk-55XCU76K-BwAE3Eqq.js +1 -0
- package/web/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/web/dist/assets/chunk-7S6XSTFO-DwwaClMo.js +2 -0
- package/web/dist/assets/chunk-AQDTSKZB-DHG5Km_p.js +72 -0
- package/web/dist/assets/chunk-DSNLSHJZ-a6-B52h-.js +129 -0
- package/web/dist/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
- package/web/dist/assets/chunk-HAGFOTED-DZMcAj3o.js +62 -0
- package/web/dist/assets/chunk-HNE4Q36P-gl_4xI6B.js +1 -0
- package/web/dist/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
- package/web/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/web/dist/assets/chunk-NGNAAXSQ-Bw6tHVuc.js +136 -0
- package/web/dist/assets/chunk-NQOKRVIF-B8Q-YmhL.js +213 -0
- package/web/dist/assets/chunk-UHW7PFBP-isP5lYMB.js +1 -0
- package/web/dist/assets/chunk-VQ5W62QI-Bn68syIc.js +1 -0
- package/web/dist/assets/chunk-WQQL4CDK-CdZpmEYA.js +10 -0
- package/web/dist/assets/chunk-XXDRQBXY-CnbRkih_.js +1 -0
- package/web/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/web/dist/assets/chunk-YZO67HFG-DX9GhJkR.js +1 -0
- package/web/dist/assets/chunk-ZAUX2LTX-Ki22UlYp.js +1 -0
- package/web/dist/assets/chunk-ZCHMQSOR-BVj0Zx6L.js +2 -0
- package/web/dist/assets/classDiagram-v2-4MYWR3UA-C7PV1veb.js +217 -0
- package/web/dist/assets/cn-rx4uumOJ.js +1 -0
- package/web/dist/assets/common-BHXgEvgk.js +192 -0
- package/web/dist/assets/cose-bilkent-JH36ORCC-DHFtpmij.js +1 -0
- package/web/dist/assets/cynefin-EF2NZ3EQ-1rI2T0cc.js +1 -0
- package/web/dist/assets/cynefinDiagram-WF24UDRP-C8O_v8xU.js +62 -0
- package/web/dist/assets/cytoscape.esm-BY277UBX.js +321 -0
- package/web/dist/assets/dagre-NK4GIZVX-BWGoHIUd.js +4 -0
- package/web/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/web/dist/assets/diagram-CBK3MWTC-kf9v35RN.js +24 -0
- package/web/dist/assets/diagram-I5XIIV2D-Ro-ig56_.js +24 -0
- package/web/dist/assets/diagram-ODIL2UCW-CtGga01G.js +30 -0
- package/web/dist/assets/diagram-PFBEJUXE-C14lcR-6.js +3 -0
- package/web/dist/assets/diagram-RJA3I4VV-BBbqaSKU.js +41 -0
- package/web/dist/assets/diagram-TYUNFSNR-BynvjQsr.js +200 -0
- package/web/dist/assets/dist-CumnGVmJ.js +1 -0
- package/web/dist/assets/ebnfDiagram-V4P3TTPT-BdqUCZnk.js +1 -0
- package/web/dist/assets/elk-QONTBCTQ-CYCP_9Jy.js +27 -0
- package/web/dist/assets/erDiagram-KYQIGQAA-C3L55l_F.js +99 -0
- package/web/dist/assets/eventmodeling-K75KTNOO-CTQdA_1v.js +1 -0
- package/web/dist/assets/flowDiagram-IUGLLWBU-COLKOzBg.js +1 -0
- package/web/dist/assets/ganttDiagram-R7GPNTJY-Be4Qk8Hx.js +292 -0
- package/web/dist/assets/gitGraph-VSP46ZUC-B59HXvj1.js +1 -0
- package/web/dist/assets/gitGraphDiagram-PDYL7XHY-C2A0VA6q.js +106 -0
- package/web/dist/assets/graphlib-DS17s2tU.js +1 -0
- package/web/dist/assets/icons-BvYhTENi.js +1 -0
- package/web/dist/assets/index-BNI6CNsY.css +1 -0
- package/web/dist/assets/index-BX-d2OBW.js +130 -0
- package/web/dist/assets/info-OHQRW6UA-DWlkKvmi.js +1 -0
- package/web/dist/assets/infoDiagram-MVDRKAX5-B8e6dTjN.js +2 -0
- package/web/dist/assets/init-C-OQMol4.js +1 -0
- package/web/dist/assets/ishikawaDiagram-HPEHV2GE-udXPYi9H.js +70 -0
- package/web/dist/assets/journeyDiagram-BOSHW7O7-DTyDkf5d.js +139 -0
- package/web/dist/assets/kanban-definition-FNSA4HC5-BXgV20qe.js +89 -0
- package/web/dist/assets/katex-B940_AAO.js +257 -0
- package/web/dist/assets/line-DVabuQAu.js +1 -0
- package/web/dist/assets/linear-gLkGxN9m.js +1 -0
- package/web/dist/assets/markdown-DAQZeY5V.js +14 -0
- package/web/dist/assets/mermaid-parser.core-BrXnuEs7.js +7 -0
- package/web/dist/assets/mermaid.core-BGbz3k1k.js +44 -0
- package/web/dist/assets/mindmap-definition-6OY57UG3-DbtP-bmk.js +98 -0
- package/web/dist/assets/ordinal-BDEzSJ7C.js +1 -0
- package/web/dist/assets/packet-JDAUHWVQ-iW8w3JF5.js +1 -0
- package/web/dist/assets/panels-DYiQiVWh.js +1 -0
- package/web/dist/assets/path-fybaL0A-.js +1 -0
- package/web/dist/assets/pegDiagram-ECIUJNBX-rBWJbaq4.js +1 -0
- package/web/dist/assets/pie-XZMESJXO-KHXEq-Yx.js +1 -0
- package/web/dist/assets/pieDiagram-GNMPY74T-DsW7yHm0.js +39 -0
- package/web/dist/assets/preload-helper-BLdHbfdN.js +1 -0
- package/web/dist/assets/profileChange-xrd7jkii.js +1 -0
- package/web/dist/assets/quadrantDiagram-D57K4MC3-DF_pwNhM.js +7 -0
- package/web/dist/assets/radar-ABXABTNO-BkAUJ_Zd.js +1 -0
- package/web/dist/assets/railroad-I3PHUGI6-BdnWRdGt.js +1 -0
- package/web/dist/assets/railroad-abnf-LNEFI6M7-C8K36-8b.js +1 -0
- package/web/dist/assets/railroad-ebnf-DWYD2UWJ-BlS25T3f.js +1 -0
- package/web/dist/assets/railroad-peg-JR7BVNR7-CBKGDi75.js +1 -0
- package/web/dist/assets/railroadDiagram-Y72NRBL7-DmrlMIvv.js +1 -0
- package/web/dist/assets/react-vendor-BlngCquv.js +19 -0
- package/web/dist/assets/requirementDiagram-EQYSW5RA-cvUq9H3I.js +84 -0
- package/web/dist/assets/rolldown-runtime-COiEfhsA.js +1 -0
- package/web/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
- package/web/dist/assets/sankeyDiagram-SY24JHO5-BdxXAbER.js +40 -0
- package/web/dist/assets/sequenceDiagram-BLEHNHQU-Druumi0k.js +169 -0
- package/web/dist/assets/settingsStore-RLUAWgIk.js +1 -0
- package/web/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
- package/web/dist/assets/src-49LUQhkG.js +1 -0
- package/web/dist/assets/src-BWF7jMDv.js +1 -0
- package/web/dist/assets/state-_ZfNVAsd.js +1 -0
- package/web/dist/assets/stateDiagram-v2-A6IESKWZ-Jgh6qlrr.js +291 -0
- package/web/dist/assets/sttLanguages-aqoVC79g.js +2 -0
- package/web/dist/assets/swimlanes-B3ENRKFQ-4xKumvXQ.js +1 -0
- package/web/dist/assets/swimlanesDiagram-AYQRV4AK-bHcLKL3d.js +8 -0
- package/web/dist/assets/timeline-definition-JXYKGLGB-BYKhga9x.js +120 -0
- package/web/dist/assets/treeView-D4JQ5SDB-6-eTVUuH.js +1 -0
- package/web/dist/assets/treemap-SAJKECNS-Dxl8yCLE.js +1 -0
- package/web/dist/assets/ttsSettings-DHo3OsOq.js +15 -0
- package/web/dist/assets/types-JssxRFJn.js +1 -0
- package/web/dist/assets/useTranslation-BGmCDuQI.js +1 -0
- package/web/dist/assets/usecaseDiagram-WPOLBNNX-BaQ8nAv9.js +328 -0
- package/web/dist/assets/vennDiagram-Q3Y3IDCW-DkPvfmfT.js +34 -0
- package/web/dist/assets/wardley-7MLQ67FV-CZtI22aa.js +1 -0
- package/web/dist/assets/wardleyDiagram-IVAIDKWA-Dgi2OWQ4.js +78 -0
- package/web/dist/assets/websocket-C9e3h7VU.js +1 -0
- package/web/dist/assets/whatsNewStore-Bf_7lW8o.js +1 -0
- package/web/dist/assets/workspaceSemantics-RaIRer76.js +3 -0
- package/web/dist/assets/xychartDiagram-4URNHDU4-Ch96dep-.js +7 -0
- package/web/dist/favicon.svg +19 -0
- package/web/dist/fonts/OFL-Inter.txt +93 -0
- package/web/dist/fonts/inter-latin-400-normal.woff2 +0 -0
- package/web/dist/fonts/inter-latin-600-normal.woff2 +0 -0
- package/web/dist/icons/icon-1024.png +0 -0
- package/web/dist/icons/icon-180.png +0 -0
- package/web/dist/icons/icon-192.png +0 -0
- package/web/dist/icons/icon-512.png +0 -0
- package/web/dist/icons/icon-maskable-512.png +0 -0
- package/web/dist/index.html +90 -0
- package/web/dist/logo.svg +26 -0
- package/web/dist/manifest.webmanifest +16 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
compose.py — 把终端回放截图合成带标注的判读图(流水线第 ③ 阶段)。
|
|
4
|
+
|
|
5
|
+
配套使用:
|
|
6
|
+
|
|
7
|
+
packages/cli/scripts/slash-toggle-capture.tsx # ① 采集累计 ANSI 字节流 -> steps.json
|
|
8
|
+
scripts/figure/replay-shots.mjs # ② xterm.js 回放 -> shot-*.png
|
|
9
|
+
scripts/figure/compose.py # ③ 合成判读图 -> verdict-*.png ← 本脚本
|
|
10
|
+
|
|
11
|
+
所有内容都靠命令行参数描述,不往脚本里写死任何路径 —— 这样同一份脚本既能画
|
|
12
|
+
「四步视口对照」,也能画「修复前/修复后 + 指标表」,不用每次手写一遍 PIL。
|
|
13
|
+
|
|
14
|
+
版块顺序:标题 → 指标表 → 截图网格 → 前后对照 → 结论条(缺省参数即跳过该版块)。
|
|
15
|
+
|
|
16
|
+
用法:
|
|
17
|
+
py -3.12 scripts/figure/compose.py \
|
|
18
|
+
--shots packages/cli/.playwright-mcp/slash-toggle/fixb \
|
|
19
|
+
--out packages/cli/.playwright-mcp/slash-toggle/verdict.png \
|
|
20
|
+
--title "命令面板开合「溢出」验证 · 终端 120×30 · xterm.js 回放真实 ANSI 字节流" \
|
|
21
|
+
--subtitle "(欢迎横幅的 8fps ASCII 动画已冻结,保证前后可逐字节比对)" \
|
|
22
|
+
--panel "initial|① 初始|空输入 · 欢迎横幅" \
|
|
23
|
+
--panel "slash-open|② 输入 /|命令面板弹出(文档 17→36 行)" \
|
|
24
|
+
--panel "deleted|③ 退格删除|面板关闭(与 ① 逐字节相同)" \
|
|
25
|
+
--panel "slash-open-2|④ 再输入 /|面板再次弹出(与 ② 逐字节相同)" \
|
|
26
|
+
--conclusion "视口结论:不溢出、不错位。修复前/后 4 张截图逐字节完全相同(MD5 一致);" \
|
|
27
|
+
--conclusion "关闭后横幅完整复原,第 2 次弹出与第 1 次完全一致 —— 可见区没有任何叠加或残留。"
|
|
28
|
+
|
|
29
|
+
指标表 + 前后对照(--table-row 第 4 段可选 green/red/amber,给最后一格上色):
|
|
30
|
+
--table-row "指标|修复前|修复后|" \
|
|
31
|
+
--table-row "终端原生回滚区行数|70 行|0 行|green" \
|
|
32
|
+
--compare "prefix|fixb|after-cycles-scrolltop|修复前:回滚区 0 行 —— 干净|修复后:回滚区 70 行 —— 残带堆叠"
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import argparse
|
|
38
|
+
import os
|
|
39
|
+
import sys
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
43
|
+
except ImportError: # pragma: no cover
|
|
44
|
+
sys.exit(
|
|
45
|
+
"[figure-compose] 缺少 Pillow。本机默认 python 没有,请用 `py -3.12` 运行,"
|
|
46
|
+
"或先 `pip install pillow`。"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# ── 调色板(与仓库既有判读图保持一致) ────────────────────────────────────────
|
|
50
|
+
BG = (245, 246, 248)
|
|
51
|
+
INK = (24, 26, 30)
|
|
52
|
+
SUB = (110, 116, 128)
|
|
53
|
+
LINE = (210, 214, 222)
|
|
54
|
+
HEADBG = (232, 234, 238)
|
|
55
|
+
RED = (220, 50, 47)
|
|
56
|
+
GREEN = (30, 150, 80)
|
|
57
|
+
AMBER = (191, 120, 0)
|
|
58
|
+
RED_BG, RED_INK = (253, 236, 235), (150, 26, 24)
|
|
59
|
+
GREEN_BG, GREEN_INK = (232, 246, 238), (18, 100, 55)
|
|
60
|
+
AMBER_BG, AMBER_INK = (253, 244, 226), (120, 76, 0)
|
|
61
|
+
|
|
62
|
+
ACCENTS = {
|
|
63
|
+
"red": (RED, RED_BG, RED_INK),
|
|
64
|
+
"green": (GREEN, GREEN_BG, GREEN_INK),
|
|
65
|
+
"amber": (AMBER, AMBER_BG, AMBER_INK),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# 中文字体:Windows 用微软雅黑,Linux/macOS 兜底
|
|
69
|
+
FONT_CANDIDATES = [
|
|
70
|
+
r"C:\Windows\Fonts\msyh.ttc",
|
|
71
|
+
r"C:\Windows\Fonts\simhei.ttf",
|
|
72
|
+
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
|
73
|
+
"/System/Library/Fonts/PingFang.ttc",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def pick_font(explicit: str | None) -> str | None:
|
|
78
|
+
for path in ([explicit] if explicit else []) + FONT_CANDIDATES:
|
|
79
|
+
if path and os.path.exists(path):
|
|
80
|
+
return path
|
|
81
|
+
print("[figure-compose] 警告:没找到中文字体,中文可能显示为方块(--font 可指定)", file=sys.stderr)
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def font_at(path: str | None, size: int):
|
|
86
|
+
if path is None:
|
|
87
|
+
return ImageFont.load_default()
|
|
88
|
+
return ImageFont.truetype(path, size)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def load_scaled(path: str, width: int) -> Image.Image:
|
|
92
|
+
if not os.path.exists(path):
|
|
93
|
+
sys.exit(f"[figure-compose] 找不到截图:{path}")
|
|
94
|
+
im = Image.open(path).convert("RGB")
|
|
95
|
+
return im.resize((width, round(im.height * width / im.width)), Image.LANCZOS)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def shot_path(directory: str, name: str) -> str:
|
|
99
|
+
"""把 step 名解析成 shot-<name>.png(允许 name 里带相对路径)。"""
|
|
100
|
+
return os.path.join(directory, f"shot-{name}.png")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def split_spec(spec: str, parts: int, flag: str, min_parts: int) -> list[str]:
|
|
104
|
+
"""按 | 切分参数;不足 min_parts 段直接报错,多于 parts 段的并入最后一段。
|
|
105
|
+
|
|
106
|
+
分隔符用 | 而不是 :,因为 Windows 路径本身就含冒号。
|
|
107
|
+
"""
|
|
108
|
+
got = spec.split("|")
|
|
109
|
+
if len(got) < min_parts:
|
|
110
|
+
sys.exit(
|
|
111
|
+
f"[figure-compose] {flag} 至少需要 {min_parts} 段(用 | 分隔),收到:{spec!r}"
|
|
112
|
+
)
|
|
113
|
+
# 多余的段并入最后一段,避免描述文字里的 | 被误切
|
|
114
|
+
while len(got) > parts:
|
|
115
|
+
got[parts - 1] = got[parts - 1] + "|" + got.pop()
|
|
116
|
+
# 缺的尾段补空(可选参数)
|
|
117
|
+
while len(got) < parts:
|
|
118
|
+
got.append("")
|
|
119
|
+
return got
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
123
|
+
ap = argparse.ArgumentParser(
|
|
124
|
+
prog="compose.py",
|
|
125
|
+
description="把 xterm.js 回放截图合成带标注的终端判读图",
|
|
126
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
127
|
+
)
|
|
128
|
+
ap.add_argument("--out", required=True, help="输出 PNG 路径")
|
|
129
|
+
ap.add_argument("--title", required=True, help="标题(单行)")
|
|
130
|
+
ap.add_argument("--subtitle", default=None, help="标题下的灰色小字")
|
|
131
|
+
ap.add_argument("--shots", default=None, help="截图目录,--panel 从里面取 shot-<name>.png")
|
|
132
|
+
|
|
133
|
+
ap.add_argument(
|
|
134
|
+
"--panel",
|
|
135
|
+
action="append",
|
|
136
|
+
default=[],
|
|
137
|
+
metavar="NAME|LABEL|DESC",
|
|
138
|
+
help="截图网格里的一格,可重复;NAME 对应 shot-<NAME>.png",
|
|
139
|
+
)
|
|
140
|
+
ap.add_argument("--cols", type=int, default=2, help="网格列数(默认 2)")
|
|
141
|
+
ap.add_argument("--cell-width", type=int, default=900, help="每格图片像素宽(默认 900)")
|
|
142
|
+
|
|
143
|
+
ap.add_argument(
|
|
144
|
+
"--table-row",
|
|
145
|
+
action="append",
|
|
146
|
+
default=[],
|
|
147
|
+
metavar="CELL|CELL[|CELL][|green|red|amber]",
|
|
148
|
+
help="指标表的一行,可重复;第 1 行当表头;末段为颜色时给最后一格上色",
|
|
149
|
+
)
|
|
150
|
+
ap.add_argument(
|
|
151
|
+
"--compare",
|
|
152
|
+
action="append",
|
|
153
|
+
default=[],
|
|
154
|
+
metavar="LEFT_DIR|RIGHT_DIR|STEP[|LEFT_CAP|RIGHT_CAP]",
|
|
155
|
+
help="前后对照:两侧目录里同名 shot-<STEP>.png 并排,可重复",
|
|
156
|
+
)
|
|
157
|
+
ap.add_argument(
|
|
158
|
+
"--conclusion",
|
|
159
|
+
action="append",
|
|
160
|
+
default=[],
|
|
161
|
+
help="结论条的一行,可重复;默认绿色,--conclusion-red 改红",
|
|
162
|
+
)
|
|
163
|
+
ap.add_argument("--conclusion-red", action="store_true", help="结论条用红色(判为失败时)")
|
|
164
|
+
|
|
165
|
+
ap.add_argument("--scale", type=float, default=1.0, help="整体字号/间距缩放(默认 1.0)")
|
|
166
|
+
ap.add_argument("--font", default=None, help="中文字体文件路径")
|
|
167
|
+
ap.add_argument("--bg", default=None, help="覆盖底色,形如 245,246,248")
|
|
168
|
+
return ap.parse_args(argv)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def main(argv: list[str] | None = None) -> int:
|
|
172
|
+
args = parse_args(argv)
|
|
173
|
+
|
|
174
|
+
if not args.panel and not args.compare and not args.table_row:
|
|
175
|
+
sys.exit("[figure-compose] 至少要给一个 --panel / --table-row / --compare,否则图是空的")
|
|
176
|
+
|
|
177
|
+
global BG
|
|
178
|
+
if args.bg:
|
|
179
|
+
BG = tuple(int(v) for v in args.bg.split(","))
|
|
180
|
+
|
|
181
|
+
s = args.scale
|
|
182
|
+
pad = round(28 * s)
|
|
183
|
+
label_h = round(44 * s)
|
|
184
|
+
row_gap = round(22 * s)
|
|
185
|
+
block_gap = round(26 * s)
|
|
186
|
+
table_rh = round(44 * s)
|
|
187
|
+
caption_h = round(34 * s)
|
|
188
|
+
conc_line = round(32 * s)
|
|
189
|
+
conc_pad = round(16 * s)
|
|
190
|
+
|
|
191
|
+
fp = pick_font(args.font)
|
|
192
|
+
f_title = font_at(fp, round(36 * s))
|
|
193
|
+
f_step = font_at(fp, round(26 * s))
|
|
194
|
+
f_tag = font_at(fp, round(21 * s))
|
|
195
|
+
f_cell = font_at(fp, round(24 * s))
|
|
196
|
+
|
|
197
|
+
# ── 解析参数 ──────────────────────────────────────────────────────────────
|
|
198
|
+
panels = []
|
|
199
|
+
for spec in args.panel:
|
|
200
|
+
if not args.shots:
|
|
201
|
+
sys.exit("[figure-compose] 用了 --panel 就必须给 --shots 目录")
|
|
202
|
+
name, label, desc = split_spec(spec, 3, "--panel", 2)
|
|
203
|
+
panels.append((label, desc, shot_path(args.shots, name)))
|
|
204
|
+
|
|
205
|
+
table_rows = []
|
|
206
|
+
for spec in args.table_row:
|
|
207
|
+
cells = [c.strip() for c in spec.split("|")]
|
|
208
|
+
accent = None
|
|
209
|
+
if cells and cells[-1].lower() in ACCENTS:
|
|
210
|
+
accent = ACCENTS[cells.pop().lower()]
|
|
211
|
+
table_rows.append((cells, accent))
|
|
212
|
+
|
|
213
|
+
compares = []
|
|
214
|
+
for spec in args.compare:
|
|
215
|
+
left_dir, right_dir, step, left_cap, right_cap = split_spec(spec, 5, "--compare", 3)
|
|
216
|
+
compares.append(
|
|
217
|
+
(
|
|
218
|
+
shot_path(left_dir, step),
|
|
219
|
+
shot_path(right_dir, step),
|
|
220
|
+
left_cap,
|
|
221
|
+
right_cap,
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# ── 画布宽度:由版块自然宽度决定 ──────────────────────────────────────────
|
|
226
|
+
cell_w = round(args.cell_width * s)
|
|
227
|
+
cols = max(1, args.cols)
|
|
228
|
+
grid_w = pad * 2 + cols * cell_w + (cols - 1) * pad if panels else 0
|
|
229
|
+
cmp_w = pad * 2 + 2 * cell_w + pad if compares else 0
|
|
230
|
+
width = max(grid_w, cmp_w, round(900 * s))
|
|
231
|
+
|
|
232
|
+
# 对照版块在最终宽度下重新均分两栏
|
|
233
|
+
cmp_cell_w = (width - pad * 3) // 2
|
|
234
|
+
|
|
235
|
+
# ── 预加载图片(高度要参与排版,所以先全读进来) ──────────────────────────
|
|
236
|
+
panel_imgs = [load_scaled(p, cell_w) for _, _, p in panels]
|
|
237
|
+
compare_imgs = [
|
|
238
|
+
(load_scaled(l, cmp_cell_w), load_scaled(r, cmp_cell_w), lc, rc)
|
|
239
|
+
for l, r, lc, rc in compares
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
# ── 版块高度 ──────────────────────────────────────────────────────────────
|
|
243
|
+
title_band = round(72 * s) + (round(34 * s) if args.subtitle else 0)
|
|
244
|
+
|
|
245
|
+
table_h = table_rh * len(table_rows)
|
|
246
|
+
|
|
247
|
+
grid_h = 0
|
|
248
|
+
if panels:
|
|
249
|
+
for r in range(0, len(panels), cols):
|
|
250
|
+
chunk = panel_imgs[r : r + cols]
|
|
251
|
+
grid_h += label_h + max(im.height for im in chunk) + row_gap
|
|
252
|
+
|
|
253
|
+
compare_h = 0
|
|
254
|
+
for left, right, _, _ in compare_imgs:
|
|
255
|
+
compare_h += max(left.height, right.height) + caption_h + row_gap
|
|
256
|
+
|
|
257
|
+
conc_h = 0
|
|
258
|
+
if args.conclusion:
|
|
259
|
+
conc_h = conc_pad * 2 + conc_line * len(args.conclusion)
|
|
260
|
+
|
|
261
|
+
blocks = [h for h in (table_h, grid_h, compare_h) if h > 0]
|
|
262
|
+
height = title_band + sum(blocks) + block_gap * max(0, len(blocks) - 1) + conc_h + pad
|
|
263
|
+
|
|
264
|
+
canvas = Image.new("RGB", (width, height), BG)
|
|
265
|
+
d = ImageDraw.Draw(canvas)
|
|
266
|
+
|
|
267
|
+
# ── 标题 ──────────────────────────────────────────────────────────────────
|
|
268
|
+
d.text((pad, round(20 * s)), args.title, font=f_title, fill=INK)
|
|
269
|
+
if args.subtitle:
|
|
270
|
+
d.text((pad, round(20 * s) + round(48 * s)), args.subtitle, font=f_tag, fill=SUB)
|
|
271
|
+
|
|
272
|
+
y = title_band
|
|
273
|
+
first_block = True
|
|
274
|
+
|
|
275
|
+
def begin_block() -> None:
|
|
276
|
+
nonlocal y, first_block
|
|
277
|
+
if not first_block:
|
|
278
|
+
y += block_gap
|
|
279
|
+
first_block = False
|
|
280
|
+
|
|
281
|
+
# ── 指标表 ────────────────────────────────────────────────────────────────
|
|
282
|
+
if table_rows:
|
|
283
|
+
begin_block()
|
|
284
|
+
ncols = max(len(cells) for cells, _ in table_rows)
|
|
285
|
+
col_step = (width - pad * 2 - 28) // max(1, ncols)
|
|
286
|
+
col_x = [pad + 14 + i * col_step for i in range(ncols)]
|
|
287
|
+
|
|
288
|
+
for r, (cells, accent) in enumerate(table_rows):
|
|
289
|
+
ty = y + r * table_rh
|
|
290
|
+
if r == 0:
|
|
291
|
+
d.rectangle([pad, ty, width - pad, ty + table_rh], fill=HEADBG)
|
|
292
|
+
d.line([pad, ty, width - pad, ty], fill=LINE, width=1)
|
|
293
|
+
for i, cell in enumerate(cells):
|
|
294
|
+
if i >= ncols:
|
|
295
|
+
break
|
|
296
|
+
if r == 0:
|
|
297
|
+
fill = SUB
|
|
298
|
+
elif accent and i == len(cells) - 1:
|
|
299
|
+
fill = accent[2]
|
|
300
|
+
else:
|
|
301
|
+
fill = INK
|
|
302
|
+
d.text((col_x[i], ty + round(10 * s)), cell, font=f_cell, fill=fill)
|
|
303
|
+
d.line(
|
|
304
|
+
[pad, y + table_rh * len(table_rows), width - pad, y + table_rh * len(table_rows)],
|
|
305
|
+
fill=LINE,
|
|
306
|
+
width=1,
|
|
307
|
+
)
|
|
308
|
+
y += table_h
|
|
309
|
+
|
|
310
|
+
# ── 截图网格 ──────────────────────────────────────────────────────────────
|
|
311
|
+
if panels:
|
|
312
|
+
begin_block()
|
|
313
|
+
row_y = y
|
|
314
|
+
for r in range(0, len(panels), cols):
|
|
315
|
+
chunk = panel_imgs[r : r + cols]
|
|
316
|
+
row_h = max(im.height for im in chunk)
|
|
317
|
+
for c, im in enumerate(chunk):
|
|
318
|
+
label, desc, _ = panels[r + c]
|
|
319
|
+
x = pad + c * (cell_w + pad)
|
|
320
|
+
d.text((x, row_y), label, font=f_step, fill=INK)
|
|
321
|
+
d.text((x + round(150 * s), row_y + round(5 * s)), desc, font=f_tag, fill=SUB)
|
|
322
|
+
canvas.paste(im, (x, row_y + label_h))
|
|
323
|
+
d.rectangle(
|
|
324
|
+
[x - 2, row_y + label_h - 2, x + cell_w + 1, row_y + label_h + im.height + 1],
|
|
325
|
+
outline=LINE,
|
|
326
|
+
width=2,
|
|
327
|
+
)
|
|
328
|
+
row_y += label_h + row_h + row_gap
|
|
329
|
+
y = row_y
|
|
330
|
+
|
|
331
|
+
# ── 前后对照 ──────────────────────────────────────────────────────────────
|
|
332
|
+
if compare_imgs:
|
|
333
|
+
begin_block()
|
|
334
|
+
for left, right, left_cap, right_cap in compare_imgs:
|
|
335
|
+
ih = max(left.height, right.height)
|
|
336
|
+
for x, im, cap, accent in (
|
|
337
|
+
(pad, left, left_cap, ACCENTS["red"]),
|
|
338
|
+
(pad * 2 + cmp_cell_w, right, right_cap, ACCENTS["green"]),
|
|
339
|
+
):
|
|
340
|
+
canvas.paste(im, (x, y))
|
|
341
|
+
d.rectangle([x - 2, y - 2, x + cmp_cell_w + 1, y + im.height + 1], outline=accent[0], width=4)
|
|
342
|
+
if cap:
|
|
343
|
+
d.text((x, y + im.height + round(10 * s)), cap, font=f_tag, fill=accent[2])
|
|
344
|
+
y += ih + caption_h + row_gap
|
|
345
|
+
|
|
346
|
+
# ── 结论条 ────────────────────────────────────────────────────────────────
|
|
347
|
+
if args.conclusion:
|
|
348
|
+
begin_block()
|
|
349
|
+
stroke, fill_bg, fill_ink = ACCENTS["red" if args.conclusion_red else "green"]
|
|
350
|
+
d.rectangle([pad, y, width - pad, y + conc_h], fill=fill_bg, outline=stroke, width=3)
|
|
351
|
+
for i, line in enumerate(args.conclusion):
|
|
352
|
+
d.text((pad + round(20 * s), y + conc_pad + i * conc_line), line, font=f_tag, fill=fill_ink)
|
|
353
|
+
y += conc_h
|
|
354
|
+
|
|
355
|
+
out_dir = os.path.dirname(os.path.abspath(args.out))
|
|
356
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
357
|
+
canvas.save(args.out)
|
|
358
|
+
print(f"[figure-compose] {args.out} {canvas.size[0]}x{canvas.size[1]}")
|
|
359
|
+
return 0
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
if __name__ == "__main__":
|
|
363
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把 steps.json 里累计的 ANSI 字节流回放成 PNG 截图(终端判读图流水线第 ② 阶段)。
|
|
3
|
+
*
|
|
4
|
+
* - 用真实的 xterm.js 5.3.0(Chromium 内)回放「累计 ANSI 流」,
|
|
5
|
+
* terminal 固定 120x30,与采集时的 VirtualTerminal 尺寸一致 —— 因此截图
|
|
6
|
+
* 就是「终端里真实长什么样」的忠实还原。
|
|
7
|
+
* - 同时把每步的 scrollback(回滚缓冲)审计出来:横幅是否被重复夯进历史。
|
|
8
|
+
*
|
|
9
|
+
* SHOT_DIR=<采集产物目录> node --experimental-strip-types scripts/figure/replay-shots.mjs
|
|
10
|
+
*
|
|
11
|
+
* 环境变量:SHOT_DIR(必填)· SHOT_COLS · SHOT_ROWS · SHOT_FONT · SHOT_SCROLLTOP
|
|
12
|
+
* 上游采集器:packages/cli/scripts/slash-toggle-capture.tsx(产物含 steps.json)
|
|
13
|
+
*/
|
|
14
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
15
|
+
import { resolve, dirname } from 'node:path'
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
17
|
+
import { createRequire } from 'node:module'
|
|
18
|
+
|
|
19
|
+
const require = createRequire(import.meta.url)
|
|
20
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
21
|
+
|
|
22
|
+
const SHOT_DIR = process.env.SHOT_DIR
|
|
23
|
+
if (!SHOT_DIR) {
|
|
24
|
+
console.error(
|
|
25
|
+
'[shot] 需要 SHOT_DIR 指向采集产物目录(里面要有 steps.json)。\n' +
|
|
26
|
+
'[shot] 例:SHOT_DIR=.playwright-mcp/slash-toggle node --experimental-strip-types ' +
|
|
27
|
+
'scripts/figure/replay-shots.mjs',
|
|
28
|
+
)
|
|
29
|
+
process.exit(1)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const DIR = resolve(SHOT_DIR)
|
|
33
|
+
const IN = resolve(DIR, 'steps.json')
|
|
34
|
+
const OUT = DIR
|
|
35
|
+
|
|
36
|
+
const COLS = Number(process.env.SHOT_COLS || 120)
|
|
37
|
+
const ROWS = Number(process.env.SHOT_ROWS || 30)
|
|
38
|
+
const FONT_SIZE = Number(process.env.SHOT_FONT || 14)
|
|
39
|
+
|
|
40
|
+
const steps = JSON.parse(readFileSync(IN, 'utf8'))
|
|
41
|
+
|
|
42
|
+
// ── 1. 回滚缓冲审计(纯文本,不依赖浏览器) ────────────────────────────────
|
|
43
|
+
const BANNER = 'DeepSeekCode'
|
|
44
|
+
const countOf = (hay, needle) => {
|
|
45
|
+
let n = 0
|
|
46
|
+
for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) n++
|
|
47
|
+
return n
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log('================ 回滚缓冲审计 ================')
|
|
51
|
+
const audit = []
|
|
52
|
+
for (const s of steps) {
|
|
53
|
+
const screenRows = s.screen.split('\n').length
|
|
54
|
+
const histRows = s.history.split('\n').length
|
|
55
|
+
const scrollbackRows = histRows - screenRows
|
|
56
|
+
const bannerTotal = countOf(s.history, BANNER)
|
|
57
|
+
const bannerScrollback = countOf(s.history.split('\n').slice(0, scrollbackRows).join('\n'), BANNER)
|
|
58
|
+
audit.push({
|
|
59
|
+
name: s.name,
|
|
60
|
+
bytes: s.bytes,
|
|
61
|
+
bannerWrites: s.bannerWrites,
|
|
62
|
+
screenRows,
|
|
63
|
+
scrollbackRows,
|
|
64
|
+
bannerTotal,
|
|
65
|
+
bannerScrollback,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
console.table(audit)
|
|
69
|
+
|
|
70
|
+
// 打印每步回滚区(viewport 之上的那几行),看是否有半截横幅残留
|
|
71
|
+
for (const s of steps) {
|
|
72
|
+
const screenRows = s.screen.split('\n').length
|
|
73
|
+
const hist = s.history.split('\n')
|
|
74
|
+
const sb = hist.slice(0, hist.length - screenRows)
|
|
75
|
+
if (sb.length) {
|
|
76
|
+
console.log(`\n--- ${s.name}: scrollback ${sb.length} 行 ---`)
|
|
77
|
+
sb.forEach((l, i) => console.log(String(i).padStart(3) + '|' + l))
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ── 2. 用 xterm.js 渲染 PNG ────────────────────────────────────────────────
|
|
82
|
+
const xtermJs = readFileSync(require.resolve('xterm/lib/xterm.js'), 'utf8')
|
|
83
|
+
const xtermCss = readFileSync(require.resolve('xterm/css/xterm.css'), 'utf8')
|
|
84
|
+
|
|
85
|
+
const { chromium } = require('playwright')
|
|
86
|
+
|
|
87
|
+
const THEME = {
|
|
88
|
+
background: '#0c0c0c',
|
|
89
|
+
foreground: '#d4d4d4',
|
|
90
|
+
cursor: '#d4d4d4',
|
|
91
|
+
black: '#000000',
|
|
92
|
+
red: '#cd3131',
|
|
93
|
+
green: '#0dbc79',
|
|
94
|
+
yellow: '#e5e510',
|
|
95
|
+
blue: '#2472c8',
|
|
96
|
+
magenta: '#bc3fbc',
|
|
97
|
+
cyan: '#11a8cd',
|
|
98
|
+
white: '#e5e5e5',
|
|
99
|
+
brightBlack: '#666666',
|
|
100
|
+
brightRed: '#f14c4c',
|
|
101
|
+
brightGreen: '#23d18b',
|
|
102
|
+
brightYellow: '#f5f543',
|
|
103
|
+
brightBlue: '#3b8eea',
|
|
104
|
+
brightMagenta: '#d670d6',
|
|
105
|
+
brightCyan: '#29b8db',
|
|
106
|
+
brightWhite: '#e5e5e5',
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function pageHtml() {
|
|
110
|
+
return `<!doctype html><html><head><meta charset="utf-8">
|
|
111
|
+
<style>${xtermCss}</style>
|
|
112
|
+
<style>
|
|
113
|
+
html,body{margin:0;padding:0;background:#0c0c0c;}
|
|
114
|
+
#wrap{display:inline-block;padding:0;}
|
|
115
|
+
.xterm-viewport{overflow:hidden !important;}
|
|
116
|
+
</style></head>
|
|
117
|
+
<body><div id="wrap"></div></body></html>`
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 本仓库的 playwright 走 --remote-debugging-pipe 会抛 `Invalid URL: undefined`,
|
|
121
|
+
// 按 scripts/repro-browser.mjs 的做法改用 port 传输。
|
|
122
|
+
const browser = await chromium.launch({
|
|
123
|
+
headless: true,
|
|
124
|
+
args: ['--no-sandbox', '--remote-debugging-port=0'],
|
|
125
|
+
ignoreDefaultArgs: ['--remote-debugging-pipe'],
|
|
126
|
+
})
|
|
127
|
+
mkdirSync(OUT, { recursive: true })
|
|
128
|
+
const written = []
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
for (const step of steps) {
|
|
132
|
+
const page = await browser.newPage({
|
|
133
|
+
viewport: { width: 1600, height: 900 },
|
|
134
|
+
deviceScaleFactor: 2,
|
|
135
|
+
})
|
|
136
|
+
await page.setContent(pageHtml(), { waitUntil: 'load' })
|
|
137
|
+
await page.addScriptTag({ content: xtermJs })
|
|
138
|
+
|
|
139
|
+
const info = await page.evaluate(
|
|
140
|
+
async ({ cols, rows, ansi, fontSize, theme, scrollTop }) => {
|
|
141
|
+
// eslint-disable-next-line no-undef
|
|
142
|
+
const term = new window.Terminal({
|
|
143
|
+
cols,
|
|
144
|
+
rows,
|
|
145
|
+
fontSize,
|
|
146
|
+
fontFamily: 'Consolas, "Cascadia Mono", "Microsoft YaHei", monospace',
|
|
147
|
+
theme,
|
|
148
|
+
allowProposedApi: true,
|
|
149
|
+
convertEol: false,
|
|
150
|
+
// 保留回滚区,好把「被推入原生 scrollback 的残带」也拍出来
|
|
151
|
+
scrollback: 5000,
|
|
152
|
+
})
|
|
153
|
+
const wrap = document.getElementById('wrap')
|
|
154
|
+
term.open(wrap)
|
|
155
|
+
await new Promise((res) => term.write(ansi, res))
|
|
156
|
+
if (scrollTop) {
|
|
157
|
+
term.scrollToTop()
|
|
158
|
+
await new Promise((r) => setTimeout(r, 80))
|
|
159
|
+
}
|
|
160
|
+
await new Promise((r) => setTimeout(r, 120))
|
|
161
|
+
const buf = term.buffer.active
|
|
162
|
+
const el = document.querySelector('#wrap .xterm')
|
|
163
|
+
return {
|
|
164
|
+
width: el.getBoundingClientRect().width,
|
|
165
|
+
height: el.getBoundingClientRect().height,
|
|
166
|
+
scrollbackRows: buf.baseY,
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
cols: COLS,
|
|
171
|
+
rows: ROWS,
|
|
172
|
+
ansi: step.ansi,
|
|
173
|
+
fontSize: FONT_SIZE,
|
|
174
|
+
theme: THEME,
|
|
175
|
+
scrollTop: Boolean(process.env.SHOT_SCROLLTOP),
|
|
176
|
+
},
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
// 视口裁到真实像素尺寸
|
|
180
|
+
await page.setViewportSize({
|
|
181
|
+
width: Math.ceil(info.width) + 2,
|
|
182
|
+
height: Math.ceil(info.height) + 2,
|
|
183
|
+
})
|
|
184
|
+
await page.evaluate(() => {
|
|
185
|
+
document.body.style.background = '#0c0c0c'
|
|
186
|
+
})
|
|
187
|
+
await new Promise((r) => setTimeout(r, 120))
|
|
188
|
+
|
|
189
|
+
const el = await page.$('#wrap .xterm')
|
|
190
|
+
const suffix = process.env.SHOT_SCROLLTOP ? '-scrolltop' : ''
|
|
191
|
+
const file = resolve(OUT, `shot-${step.name}${suffix}.png`)
|
|
192
|
+
await el.screenshot({ path: file })
|
|
193
|
+
written.push(file)
|
|
194
|
+
console.log(
|
|
195
|
+
`[shot] ${step.name}${suffix}: ${Math.round(info.width)}x${Math.round(info.height)}css ` +
|
|
196
|
+
`scrollbackRows=${info.scrollbackRows} -> ${file}`,
|
|
197
|
+
)
|
|
198
|
+
await page.close()
|
|
199
|
+
}
|
|
200
|
+
} finally {
|
|
201
|
+
await browser.close()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
writeFileSync(resolve(OUT, 'audit.json'), JSON.stringify(audit, null, 2), 'utf8')
|
|
205
|
+
console.log('\n[shot] wrote:\n' + written.map((f) => ' ' + f).join('\n'))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{i as e}from"./rolldown-runtime-COiEfhsA.js";import{$ as t,At as n,J as r,L as i,Ur as a,d as o,ft as s,g as c,kr as l,n as u,ot as d,u as f,xr as p,yt as m,z as h}from"./icons-BvYhTENi.js";import{t as g}from"./useTranslation-BGmCDuQI.js";import{t as _}from"./api-CCt9DRQm.js";import{I as v}from"./markdown-DAQZeY5V.js";import{A as y,f as b,r as x}from"./common-BHXgEvgk.js";import{n as S,t as C}from"./channelMeta-BGQXQxgj.js";import{i as w}from"./index-BX-d2OBW.js";var T=e(a(),1),E=v(),D=(0,T.memo)(function({selectedSessionId:e,onSelectionChange:n,onOpenSession:r,onNavigate:i,onCollapse:a,onDeleted:o}){let{t:s}=g(),[c,u]=(0,T.useState)([]),[f,p]=(0,T.useState)(!0),[h,v]=(0,T.useState)(null),[y,x]=(0,T.useState)(null),[S,C]=(0,T.useState)(null),[w,D]=(0,T.useState)(!1),[O,A]=(0,T.useState)(null),[j,M]=(0,T.useState)(null),N=(0,T.useCallback)(async()=>{try{let e=await _.assistants.list();e.success&&Array.isArray(e.data)?(u(e.data),v(null)):v(e.error||s(`assistants.loadFailed`))}catch(e){v(e instanceof Error?e.message:String(e))}finally{p(!1)}},[s]);(0,T.useEffect)(()=>{N();let e=window.setInterval(()=>{N()},8e3);return()=>window.clearInterval(e)},[N]);let P=(0,T.useCallback)(async(e,t)=>{(await _.assistants.update(e,t)).success&&N()},[N]),F=(0,T.useCallback)(async()=>{D(!0),M(null);try{let e=await _.assistants.create({title:s(`assistants.newAssistantTitle`)});e.success&&e.data?(await N(),n(e.data),C(e.data.sessionId)):M(e.error||s(`assistants.createFailed`))}catch(e){M(e instanceof Error?e.message:String(e))}finally{D(!1)}},[N,n,s]),I=(0,T.useCallback)(async(e,t)=>{M(null);try{let n=await _.assistants.rename(e,t);n.success?(C(null),await N()):M(n.error||s(`assistants.renameFailed`))}catch(e){M(e instanceof Error?e.message:String(e))}},[N,s]),L=(0,T.useCallback)(async(t,n)=>{if(typeof window>`u`||typeof window.confirm!=`function`||window.confirm(s(`assistants.deleteConfirm`,{title:n}))){M(null),A(t);try{let n=await _.assistants.remove(t);n.success?(S===t&&C(null),e===t&&o?.(t),await N()):M(n.error||s(`assistants.deleteFailed`))}catch(e){M(e instanceof Error?e.message:String(e))}finally{A(null)}}},[S,e,o,N,s]);return(0,T.useEffect)(()=>{if(!e)return;let t=c.find(t=>t.sessionId===e);t&&n(t)},[c,e,n]),(0,E.jsxs)(`div`,{className:`flex h-full flex-col overflow-hidden`,children:[(0,E.jsxs)(`div`,{className:`flex items-center justify-between border-b border-dark-border px-4 py-2.5`,children:[(0,E.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,E.jsx)(l,{size:16,className:`text-accent-blue`,"aria-hidden":`true`}),(0,E.jsx)(`h2`,{className:`text-sm font-semibold text-dark-text`,children:s(`assistants.title`)}),!f&&(0,E.jsx)(`span`,{className:`rounded-full bg-dark-hover px-1.5 py-0.5 text-badge text-dark-muted`,children:c.length})]}),(0,E.jsxs)(`div`,{className:`flex items-center gap-0.5`,children:[(0,E.jsxs)(`button`,{type:`button`,onClick:()=>void F(),disabled:w,className:`flex items-center gap-1 rounded px-2 py-1 text-xs text-accent-blue hover:bg-dark-hover disabled:opacity-50`,title:s(`assistants.newAssistantHint`),"data-testid":`assistants-create`,children:[(0,E.jsx)(d,{size:12,"aria-hidden":`true`}),s(`assistants.newAssistant`)]}),(0,E.jsxs)(`button`,{type:`button`,onClick:()=>void N(),className:`flex items-center gap-1 rounded px-2 py-1 text-xs text-dark-muted hover:bg-dark-hover hover:text-dark-text`,title:s(`common.refresh`),children:[(0,E.jsx)(t,{size:12,className:f?`animate-spin`:``,"aria-hidden":`true`}),s(`common.refresh`)]}),a&&(0,E.jsx)(`button`,{type:`button`,onClick:a,className:`rounded p-1 text-dark-muted hover:bg-dark-hover hover:text-dark-text`,title:s(`assistants.collapseList`),"aria-label":s(`assistants.collapseList`),"aria-expanded":!0,"data-testid":`assistants-list-collapse`,children:(0,E.jsx)(m,{size:13,"aria-hidden":`true`})})]})]}),j&&(0,E.jsx)(`div`,{className:`border-b border-dark-border px-4 py-1.5 text-meta text-accent-red`,role:`alert`,children:j}),(0,E.jsx)(`div`,{className:`flex-1 min-h-0 overflow-y-auto p-2`,children:f&&c.length===0?(0,E.jsx)(`div`,{className:`py-2`,role:`status`,"aria-label":s(`assistants.loading`),children:(0,E.jsx)(b,{count:4})}):h&&c.length===0?(0,E.jsx)(`div`,{className:`py-8 text-center text-xs text-accent-red`,children:h}):c.length===0?(0,E.jsxs)(`div`,{className:`py-10 text-center text-xs text-dark-muted`,children:[(0,E.jsx)(l,{size:24,className:`mx-auto mb-2 text-dark-muted`,"aria-hidden":`true`}),(0,E.jsx)(`p`,{children:s(`assistants.empty`)}),(0,E.jsx)(`p`,{className:`mt-1 text-meta`,children:s(`assistants.emptyHint`)}),(0,E.jsx)(`button`,{type:`button`,onClick:()=>i(`channels`),className:`mt-3 rounded border border-dark-border px-2.5 py-1 text-meta text-dark-text hover:bg-dark-hover`,children:s(`assistants.goChannels`)})]}):(0,E.jsx)(`div`,{className:`flex flex-col gap-2`,children:c.map(t=>(0,E.jsx)(k,{assistant:t,selected:e===t.sessionId,onSelect:()=>n(t),expanded:y===t.sessionId,onToggleExpand:()=>x(y===t.sessionId?null:t.sessionId),onOpen:()=>r(t.sessionId,t.title),onUpdate:e=>void P(t.sessionId,e),editing:S===t.sessionId,onStartRename:()=>C(t.sessionId),onCancelRename:()=>C(null),onRename:e=>void I(t.sessionId,e),deleting:O===t.sessionId,onDelete:()=>void L(t.sessionId,t.title)},t.sessionId))})})]})});function O(e){if(e===`running`)return`running`;if(e===`paused`)return`waiting`;if(e===`error`)return`failed`}var k=(0,T.memo)(function({assistant:e,selected:t,onSelect:r,expanded:a,onToggleExpand:u,onOpen:d,onUpdate:p,editing:m,onStartRename:_,onCancelRename:v,onRename:y,deleting:b,onDelete:T}){let{t:D}=g(),k=e.channel?.peerKind===`group`,N=e.kind===`home`;return(0,E.jsxs)(`div`,{className:`rounded-lg border bg-dark-surface transition-colors ${t?`border-accent-blue/60 ring-1 ring-accent-blue/30`:`border-dark-border`}`,children:[(0,E.jsxs)(`button`,{type:`button`,onClick:r,"aria-current":t?`true`:void 0,className:`flex w-full items-center gap-2 rounded-t-lg px-3 py-2 text-start focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent-blue/60 ${t?`bg-accent-blue/5`:`hover:bg-dark-hover/40`}`,children:[(0,E.jsx)(x,{sessionId:e.sessionId,status:O(e.status),size:40}),(0,E.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,E.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[e.channel?(0,E.jsx)(`span`,{className:`text-base leading-none`,"aria-hidden":`true`,children:C(e.channel.type)}):(0,E.jsx)(l,{size:14,className:`shrink-0 text-dark-muted`,"aria-hidden":`true`}),(0,E.jsx)(`span`,{className:`min-w-0 flex-1 truncate text-xs font-medium text-dark-text`,title:e.title,children:e.title}),(0,E.jsx)(`span`,{className:`h-1.5 w-1.5 shrink-0 rounded-full ${w(e.status)}`,title:D(`assistants.status.${e.status}`,{defaultValue:e.status})}),e.channel&&(k?(0,E.jsx)(f,{size:11,className:`shrink-0 text-dark-muted`,"aria-label":D(`assistants.group`)}):(0,E.jsx)(o,{size:11,className:`shrink-0 text-dark-muted`,"aria-label":D(`assistants.dm`)}))]}),(0,E.jsxs)(`div`,{className:`mt-0.5 flex items-center gap-1.5 text-badge text-dark-muted`,children:[(0,E.jsx)(`span`,{className:`shrink-0`,children:e.channel?S(e.channel.type):D(N?`assistants.homeLabel`:`assistants.localLabel`)}),(0,E.jsx)(`span`,{className:`min-w-0 flex-1 truncate`,title:e.channel?.peerId,children:e.channel?e.channel.peerId:D(N?`assistants.homeHint`:`assistants.localHint`)}),e.stats.unread>0&&(0,E.jsx)(`span`,{className:`shrink-0 rounded-full bg-accent-red/15 px-1.5 text-avatar font-medium text-accent-fg`,children:e.stats.unread})]}),e.preview&&(0,E.jsx)(`p`,{className:`mt-1 line-clamp-2 text-meta leading-snug text-dark-muted`,title:e.preview,children:e.preview})]}),(0,E.jsx)(j,{mode:e.mode}),e.trust.level===`owner`?(0,E.jsx)(i,{size:12,className:`shrink-0 text-accent-green`,"aria-label":D(`assistants.trust.owner`)}):e.trust.level===`public`?(0,E.jsx)(h,{size:12,className:`shrink-0 text-accent-yellow`,"aria-label":D(`assistants.trust.public`)}):(0,E.jsx)(i,{size:12,className:`shrink-0 text-accent-blue`,"aria-label":D(`assistants.trust.team`)})]}),(0,E.jsxs)(`div`,{className:`flex items-center gap-1.5 border-t border-dark-border px-3 py-1.5`,children:[(0,E.jsxs)(`button`,{type:`button`,onClick:d,className:`flex items-center gap-1 rounded px-2 py-0.5 text-meta text-accent-blue hover:bg-dark-hover`,children:[(0,E.jsx)(n,{size:11,"aria-hidden":`true`}),D(`assistants.openSession`)]}),(0,E.jsx)(`button`,{type:`button`,onClick:u,className:`flex items-center gap-1 rounded px-2 py-0.5 text-meta text-dark-muted hover:bg-dark-hover hover:text-dark-text`,children:D(`assistants.policy`)}),!N&&(0,E.jsx)(`button`,{type:`button`,onClick:m?v:_,className:`flex items-center gap-1 rounded px-2 py-0.5 text-meta text-dark-muted hover:bg-dark-hover hover:text-dark-text`,title:D(`assistants.rename`),"aria-label":D(`assistants.rename`),"aria-pressed":m,"data-testid":`assistant-rename-${e.sessionId}`,children:(0,E.jsx)(s,{size:11,"aria-hidden":`true`})}),!N&&(0,E.jsx)(`button`,{type:`button`,onClick:T,disabled:b,className:`flex items-center gap-1 rounded px-2 py-0.5 text-meta text-dark-muted hover:bg-accent-red/10 hover:text-accent-red disabled:opacity-50`,title:D(`assistants.deleteHint`),"aria-label":D(`assistants.delete`),"data-testid":`assistant-delete-${e.sessionId}`,children:(0,E.jsx)(c,{size:11,"aria-hidden":`true`})}),(0,E.jsx)(`span`,{className:`flex-1`}),(0,E.jsx)(`span`,{className:`text-badge text-dark-muted`,children:D(`assistants.groupPolicy`,{mention:String(k?e.policy.mentionRequired:!1)})})]}),a&&(0,E.jsx)(M,{assistant:e,isGroup:k,onUpdate:p}),m&&(0,E.jsx)(A,{initial:e.title,onSubmit:y,onCancel:v})]})});function A({initial:e,onSubmit:t,onCancel:n}){let{t:r}=g(),[i,a]=(0,T.useState)(e),o=()=>{let r=i.trim();!r||r===e?n():t(r)};return(0,E.jsxs)(`div`,{className:`flex items-center gap-1.5 border-t border-dark-border px-3 py-1.5`,children:[(0,E.jsx)(`input`,{autoFocus:!0,type:`text`,value:i,onChange:e=>a(e.target.value),onKeyDown:e=>{e.key===`Enter`?(e.preventDefault(),o()):e.key===`Escape`&&(e.preventDefault(),n())},"aria-label":r(`assistants.renameLabel`),"data-testid":`assistant-rename-input`,className:`min-w-0 flex-1 rounded border border-dark-border bg-dark-surface px-1.5 py-0.5 text-meta text-dark-text`}),(0,E.jsx)(`button`,{type:`button`,onClick:o,className:`rounded p-1 text-accent-blue hover:bg-dark-hover`,title:r(`common.save`),"aria-label":r(`common.save`),"data-testid":`assistant-rename-save`,children:(0,E.jsx)(p,{size:12,"aria-hidden":`true`})}),(0,E.jsx)(`button`,{type:`button`,onClick:n,className:`rounded p-1 text-dark-muted hover:bg-dark-hover hover:text-dark-text`,title:r(`common.cancel`),"aria-label":r(`common.cancel`),children:(0,E.jsx)(u,{size:12,"aria-hidden":`true`})})]})}function j({mode:e}){let{t}=g();return(0,E.jsx)(`span`,{className:`shrink-0 rounded px-1.5 py-0.5 text-avatar font-medium ${e===`delegate`?`bg-accent-blue/15 text-accent-blue`:`bg-dark-hover text-dark-muted`}`,children:t(`assistants.mode.${e}`)})}function M({assistant:e,isGroup:t,onUpdate:n}){let{t:i}=g(),[a,o]=(0,T.useState)(e.mode),[s,c]=(0,T.useState)(e.policy.mentionRequired),[l,u]=(0,T.useState)(String(e.policy.cooldownSeconds)),[d,f]=(0,T.useState)((e.policy.keywords??[]).join(`, `)),[p,m]=(0,T.useState)(!1);return(0,E.jsxs)(`div`,{className:`border-t border-dark-border px-3 py-2.5 text-meta`,children:[(0,E.jsxs)(`div`,{className:`flex flex-wrap items-center gap-x-4 gap-y-2`,children:[(0,E.jsxs)(`label`,{className:`flex items-center gap-1.5 text-dark-muted`,children:[i(`assistants.mode.label`),(0,E.jsxs)(`select`,{value:a,onChange:e=>o(e.target.value),className:`rounded border border-dark-border bg-dark-surface px-1.5 py-0.5 text-dark-text`,children:[(0,E.jsx)(`option`,{value:`delegate`,children:i(`assistants.mode.delegate`)}),(0,E.jsx)(`option`,{value:`direct`,children:i(`assistants.mode.direct`)})]})]}),t&&(0,E.jsxs)(`label`,{className:`flex items-center gap-1.5 text-dark-muted`,children:[(0,E.jsx)(`input`,{type:`checkbox`,checked:s,onChange:e=>c(e.target.checked)}),i(`assistants.mentionRequired`)]}),(0,E.jsxs)(`label`,{className:`flex items-center gap-1.5 text-dark-muted`,children:[i(`assistants.cooldown`),(0,E.jsx)(`input`,{type:`number`,min:0,value:l,onChange:e=>u(e.target.value),className:`w-14 rounded border border-dark-border bg-dark-surface px-1.5 py-0.5 text-dark-text`}),`s`]})]}),(0,E.jsxs)(`label`,{className:`mt-2 flex items-center gap-1.5 text-dark-muted`,children:[i(`assistants.keywords`),(0,E.jsx)(`input`,{type:`text`,value:d,placeholder:i(`assistants.keywordsPlaceholder`)??``,onChange:e=>f(e.target.value),className:`min-w-0 flex-1 rounded border border-dark-border bg-dark-surface px-1.5 py-0.5 text-dark-text`})]}),(0,E.jsx)(`div`,{className:`mt-2 flex justify-end`,children:(0,E.jsx)(y,{size:`sm`,variant:`accent`,onClick:()=>{m(!0),n({mode:a,policy:{mentionRequired:t?s:!1,cooldownSeconds:Math.max(0,Number(l)||0),...d.trim()?{keywords:d.split(/[,,]/).map(e=>e.trim()).filter(Boolean)}:{}}}),window.setTimeout(()=>m(!1),600)},loading:p,leftIcon:(0,E.jsx)(r,{size:11,"aria-hidden":`true`}),children:i(`common.save`)})})]})}export{D as AssistantsView};
|