@openav/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.
Files changed (185) hide show
  1. package/LICENSE.md +12 -0
  2. package/README.md +1208 -2
  3. package/bin/dscode.exe +12 -0
  4. package/bin/stt/requirements.txt +20 -0
  5. package/bin/stt/stt_server.py +544 -0
  6. package/bin/tts/requirements.txt +21 -0
  7. package/bin/tts/tts_server.py +404 -0
  8. package/cli-wrapper.cjs +109 -0
  9. package/install.cjs +331 -0
  10. package/llms.txt +115 -0
  11. package/package.json +56 -4
  12. package/scripts/figure/compose.py +363 -0
  13. package/scripts/figure/replay-shots.mjs +205 -0
  14. package/web/dist/assets/AssistantsView-dhdYVIy0.js +1 -0
  15. package/web/dist/assets/AutomationView-D1dU5ZV7.js +1 -0
  16. package/web/dist/assets/ChannelConfigView-CEPVZntU.js +8 -0
  17. package/web/dist/assets/ChartsView-DdN-beHW.js +1 -0
  18. package/web/dist/assets/ConnectorsView-CseCss9C.js +1 -0
  19. package/web/dist/assets/FileTreeView-Dnc3-gRF.js +1 -0
  20. package/web/dist/assets/HookConfigView-DA2h4HdJ.js +1 -0
  21. package/web/dist/assets/InputArea-DAgOOjOc.js +14 -0
  22. package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  23. package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  24. package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  25. package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  26. package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  27. package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  28. package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  29. package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  30. package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  31. package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  32. package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  33. package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  34. package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  35. package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  36. package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  37. package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  38. package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  39. package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  40. package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  41. package/web/dist/assets/MemoryView-D6OUBi4-.js +1 -0
  42. package/web/dist/assets/MessageBubble-CCTRbIMZ.css +1 -0
  43. package/web/dist/assets/MessageBubble-CV16DZMG.js +453 -0
  44. package/web/dist/assets/ModelsView-BuqD9vWl.js +1 -0
  45. package/web/dist/assets/PluginsView-CIziDHFJ.js +2 -0
  46. package/web/dist/assets/ProjectsView-c6faWy32.js +1 -0
  47. package/web/dist/assets/SessionGrid-BZJHcklh.js +4 -0
  48. package/web/dist/assets/SessionGrid-Bk5CAXEn.css +1 -0
  49. package/web/dist/assets/SettingsModal-BqdGEGW-.js +4 -0
  50. package/web/dist/assets/SkillsView-CB6CKkmb.js +1 -0
  51. package/web/dist/assets/TemplatesTab-B8efVwVZ.js +1 -0
  52. package/web/dist/assets/ThreadsView-CDoM0aNb.js +2 -0
  53. package/web/dist/assets/ToolsView-BG-xsWsO.js +1 -0
  54. package/web/dist/assets/abnfDiagram-WEYOIA3C-DGUPHfRO.js +1 -0
  55. package/web/dist/assets/agentIconSet-Btsb_SC4.js +1 -0
  56. package/web/dist/assets/api-CCt9DRQm.js +1 -0
  57. package/web/dist/assets/arc-Y1ARJVZ4.js +1 -0
  58. package/web/dist/assets/architecture-WOLXFQ4H-BUERx_-B.js +1 -0
  59. package/web/dist/assets/architectureDiagram-ZHVYKT53-BPsQL7O5.js +36 -0
  60. package/web/dist/assets/array-BifhSqXX.js +1 -0
  61. package/web/dist/assets/blockDiagram-RJAE7QA3-pB7EXp6p.js +140 -0
  62. package/web/dist/assets/c4Diagram-XDPPPE54-BnPeCq_L.js +38 -0
  63. package/web/dist/assets/channel-CSrLW66f.js +1 -0
  64. package/web/dist/assets/channelMeta-BGQXQxgj.js +1 -0
  65. package/web/dist/assets/chunk-2E7WHVMD-xX282MrC.js +88 -0
  66. package/web/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  67. package/web/dist/assets/chunk-4EMNKR2T-BAQDLVqt.js +1 -0
  68. package/web/dist/assets/chunk-55XCU76K-BwAE3Eqq.js +1 -0
  69. package/web/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  70. package/web/dist/assets/chunk-7S6XSTFO-DwwaClMo.js +2 -0
  71. package/web/dist/assets/chunk-AQDTSKZB-DHG5Km_p.js +72 -0
  72. package/web/dist/assets/chunk-DSNLSHJZ-a6-B52h-.js +129 -0
  73. package/web/dist/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
  74. package/web/dist/assets/chunk-HAGFOTED-DZMcAj3o.js +62 -0
  75. package/web/dist/assets/chunk-HNE4Q36P-gl_4xI6B.js +1 -0
  76. package/web/dist/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
  77. package/web/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  78. package/web/dist/assets/chunk-NGNAAXSQ-Bw6tHVuc.js +136 -0
  79. package/web/dist/assets/chunk-NQOKRVIF-B8Q-YmhL.js +213 -0
  80. package/web/dist/assets/chunk-UHW7PFBP-isP5lYMB.js +1 -0
  81. package/web/dist/assets/chunk-VQ5W62QI-Bn68syIc.js +1 -0
  82. package/web/dist/assets/chunk-WQQL4CDK-CdZpmEYA.js +10 -0
  83. package/web/dist/assets/chunk-XXDRQBXY-CnbRkih_.js +1 -0
  84. package/web/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  85. package/web/dist/assets/chunk-YZO67HFG-DX9GhJkR.js +1 -0
  86. package/web/dist/assets/chunk-ZAUX2LTX-Ki22UlYp.js +1 -0
  87. package/web/dist/assets/chunk-ZCHMQSOR-BVj0Zx6L.js +2 -0
  88. package/web/dist/assets/classDiagram-v2-4MYWR3UA-C7PV1veb.js +217 -0
  89. package/web/dist/assets/cn-rx4uumOJ.js +1 -0
  90. package/web/dist/assets/common-BHXgEvgk.js +192 -0
  91. package/web/dist/assets/cose-bilkent-JH36ORCC-DHFtpmij.js +1 -0
  92. package/web/dist/assets/cynefin-EF2NZ3EQ-1rI2T0cc.js +1 -0
  93. package/web/dist/assets/cynefinDiagram-WF24UDRP-C8O_v8xU.js +62 -0
  94. package/web/dist/assets/cytoscape.esm-BY277UBX.js +321 -0
  95. package/web/dist/assets/dagre-NK4GIZVX-BWGoHIUd.js +4 -0
  96. package/web/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
  97. package/web/dist/assets/diagram-CBK3MWTC-kf9v35RN.js +24 -0
  98. package/web/dist/assets/diagram-I5XIIV2D-Ro-ig56_.js +24 -0
  99. package/web/dist/assets/diagram-ODIL2UCW-CtGga01G.js +30 -0
  100. package/web/dist/assets/diagram-PFBEJUXE-C14lcR-6.js +3 -0
  101. package/web/dist/assets/diagram-RJA3I4VV-BBbqaSKU.js +41 -0
  102. package/web/dist/assets/diagram-TYUNFSNR-BynvjQsr.js +200 -0
  103. package/web/dist/assets/dist-CumnGVmJ.js +1 -0
  104. package/web/dist/assets/ebnfDiagram-V4P3TTPT-BdqUCZnk.js +1 -0
  105. package/web/dist/assets/elk-QONTBCTQ-CYCP_9Jy.js +27 -0
  106. package/web/dist/assets/erDiagram-KYQIGQAA-C3L55l_F.js +99 -0
  107. package/web/dist/assets/eventmodeling-K75KTNOO-CTQdA_1v.js +1 -0
  108. package/web/dist/assets/flowDiagram-IUGLLWBU-COLKOzBg.js +1 -0
  109. package/web/dist/assets/ganttDiagram-R7GPNTJY-Be4Qk8Hx.js +292 -0
  110. package/web/dist/assets/gitGraph-VSP46ZUC-B59HXvj1.js +1 -0
  111. package/web/dist/assets/gitGraphDiagram-PDYL7XHY-C2A0VA6q.js +106 -0
  112. package/web/dist/assets/graphlib-DS17s2tU.js +1 -0
  113. package/web/dist/assets/icons-BvYhTENi.js +1 -0
  114. package/web/dist/assets/index-BNI6CNsY.css +1 -0
  115. package/web/dist/assets/index-BX-d2OBW.js +130 -0
  116. package/web/dist/assets/info-OHQRW6UA-DWlkKvmi.js +1 -0
  117. package/web/dist/assets/infoDiagram-MVDRKAX5-B8e6dTjN.js +2 -0
  118. package/web/dist/assets/init-C-OQMol4.js +1 -0
  119. package/web/dist/assets/ishikawaDiagram-HPEHV2GE-udXPYi9H.js +70 -0
  120. package/web/dist/assets/journeyDiagram-BOSHW7O7-DTyDkf5d.js +139 -0
  121. package/web/dist/assets/kanban-definition-FNSA4HC5-BXgV20qe.js +89 -0
  122. package/web/dist/assets/katex-B940_AAO.js +257 -0
  123. package/web/dist/assets/line-DVabuQAu.js +1 -0
  124. package/web/dist/assets/linear-gLkGxN9m.js +1 -0
  125. package/web/dist/assets/markdown-DAQZeY5V.js +14 -0
  126. package/web/dist/assets/mermaid-parser.core-BrXnuEs7.js +7 -0
  127. package/web/dist/assets/mermaid.core-BGbz3k1k.js +44 -0
  128. package/web/dist/assets/mindmap-definition-6OY57UG3-DbtP-bmk.js +98 -0
  129. package/web/dist/assets/ordinal-BDEzSJ7C.js +1 -0
  130. package/web/dist/assets/packet-JDAUHWVQ-iW8w3JF5.js +1 -0
  131. package/web/dist/assets/panels-DYiQiVWh.js +1 -0
  132. package/web/dist/assets/path-fybaL0A-.js +1 -0
  133. package/web/dist/assets/pegDiagram-ECIUJNBX-rBWJbaq4.js +1 -0
  134. package/web/dist/assets/pie-XZMESJXO-KHXEq-Yx.js +1 -0
  135. package/web/dist/assets/pieDiagram-GNMPY74T-DsW7yHm0.js +39 -0
  136. package/web/dist/assets/preload-helper-BLdHbfdN.js +1 -0
  137. package/web/dist/assets/profileChange-xrd7jkii.js +1 -0
  138. package/web/dist/assets/quadrantDiagram-D57K4MC3-DF_pwNhM.js +7 -0
  139. package/web/dist/assets/radar-ABXABTNO-BkAUJ_Zd.js +1 -0
  140. package/web/dist/assets/railroad-I3PHUGI6-BdnWRdGt.js +1 -0
  141. package/web/dist/assets/railroad-abnf-LNEFI6M7-C8K36-8b.js +1 -0
  142. package/web/dist/assets/railroad-ebnf-DWYD2UWJ-BlS25T3f.js +1 -0
  143. package/web/dist/assets/railroad-peg-JR7BVNR7-CBKGDi75.js +1 -0
  144. package/web/dist/assets/railroadDiagram-Y72NRBL7-DmrlMIvv.js +1 -0
  145. package/web/dist/assets/react-vendor-BlngCquv.js +19 -0
  146. package/web/dist/assets/requirementDiagram-EQYSW5RA-cvUq9H3I.js +84 -0
  147. package/web/dist/assets/rolldown-runtime-COiEfhsA.js +1 -0
  148. package/web/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
  149. package/web/dist/assets/sankeyDiagram-SY24JHO5-BdxXAbER.js +40 -0
  150. package/web/dist/assets/sequenceDiagram-BLEHNHQU-Druumi0k.js +169 -0
  151. package/web/dist/assets/settingsStore-RLUAWgIk.js +1 -0
  152. package/web/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  153. package/web/dist/assets/src-49LUQhkG.js +1 -0
  154. package/web/dist/assets/src-BWF7jMDv.js +1 -0
  155. package/web/dist/assets/state-_ZfNVAsd.js +1 -0
  156. package/web/dist/assets/stateDiagram-v2-A6IESKWZ-Jgh6qlrr.js +291 -0
  157. package/web/dist/assets/sttLanguages-aqoVC79g.js +2 -0
  158. package/web/dist/assets/swimlanes-B3ENRKFQ-4xKumvXQ.js +1 -0
  159. package/web/dist/assets/swimlanesDiagram-AYQRV4AK-bHcLKL3d.js +8 -0
  160. package/web/dist/assets/timeline-definition-JXYKGLGB-BYKhga9x.js +120 -0
  161. package/web/dist/assets/treeView-D4JQ5SDB-6-eTVUuH.js +1 -0
  162. package/web/dist/assets/treemap-SAJKECNS-Dxl8yCLE.js +1 -0
  163. package/web/dist/assets/ttsSettings-DHo3OsOq.js +15 -0
  164. package/web/dist/assets/types-JssxRFJn.js +1 -0
  165. package/web/dist/assets/useTranslation-BGmCDuQI.js +1 -0
  166. package/web/dist/assets/usecaseDiagram-WPOLBNNX-BaQ8nAv9.js +328 -0
  167. package/web/dist/assets/vennDiagram-Q3Y3IDCW-DkPvfmfT.js +34 -0
  168. package/web/dist/assets/wardley-7MLQ67FV-CZtI22aa.js +1 -0
  169. package/web/dist/assets/wardleyDiagram-IVAIDKWA-Dgi2OWQ4.js +78 -0
  170. package/web/dist/assets/websocket-C9e3h7VU.js +1 -0
  171. package/web/dist/assets/whatsNewStore-Bf_7lW8o.js +1 -0
  172. package/web/dist/assets/workspaceSemantics-RaIRer76.js +3 -0
  173. package/web/dist/assets/xychartDiagram-4URNHDU4-Ch96dep-.js +7 -0
  174. package/web/dist/favicon.svg +19 -0
  175. package/web/dist/fonts/OFL-Inter.txt +93 -0
  176. package/web/dist/fonts/inter-latin-400-normal.woff2 +0 -0
  177. package/web/dist/fonts/inter-latin-600-normal.woff2 +0 -0
  178. package/web/dist/icons/icon-1024.png +0 -0
  179. package/web/dist/icons/icon-180.png +0 -0
  180. package/web/dist/icons/icon-192.png +0 -0
  181. package/web/dist/icons/icon-512.png +0 -0
  182. package/web/dist/icons/icon-maskable-512.png +0 -0
  183. package/web/dist/index.html +90 -0
  184. package/web/dist/logo.svg +26 -0
  185. 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};