cx-viewer 1.0.3 → 1.0.4
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/README.md +49 -20
- package/build.js +15 -0
- package/cli.js +102 -18
- package/concepts/ar/BodyDiffJSON.md +1 -1
- package/concepts/ar/BodyFields.md +30 -33
- package/concepts/ar/GlobalSettings.md +165 -167
- package/concepts/ar/MainAgent.md +3 -4
- package/concepts/ar/ProxySwitch.md +3 -3
- package/concepts/ar/QRCode.md +1 -1
- package/concepts/ar/ResponseFields.md +29 -41
- package/concepts/ar/SubAgent-Search.md +12 -12
- package/concepts/ar/Teammate.md +16 -16
- package/concepts/ar/ToolsFirst.md +42 -48
- package/concepts/ar/TranslateContextPollution.md +9 -9
- package/concepts/ar/UltraPlan.md +38 -38
- package/concepts/da/BodyDiffJSON.md +2 -2
- package/concepts/da/BodyFields.md +30 -33
- package/concepts/da/GlobalSettings.md +165 -167
- package/concepts/da/MainAgent.md +3 -4
- package/concepts/da/ProxySwitch.md +3 -3
- package/concepts/da/QRCode.md +1 -1
- package/concepts/da/ResponseFields.md +29 -41
- package/concepts/da/SubAgent-Search.md +11 -11
- package/concepts/da/Teammate.md +16 -16
- package/concepts/da/ToolsFirst.md +42 -48
- package/concepts/da/TranslateContextPollution.md +9 -9
- package/concepts/da/UltraPlan.md +38 -38
- package/concepts/de/BodyDiffJSON.md +2 -2
- package/concepts/de/BodyFields.md +30 -33
- package/concepts/de/GlobalSettings.md +165 -167
- package/concepts/de/MainAgent.md +3 -4
- package/concepts/de/ProxySwitch.md +3 -3
- package/concepts/de/QRCode.md +1 -1
- package/concepts/de/ResponseFields.md +29 -41
- package/concepts/de/SubAgent-Search.md +11 -11
- package/concepts/de/Teammate.md +15 -15
- package/concepts/de/ToolsFirst.md +41 -47
- package/concepts/de/TranslateContextPollution.md +9 -9
- package/concepts/de/UltraPlan.md +38 -38
- package/concepts/en/BodyDiffJSON.md +5 -5
- package/concepts/en/BodyFields.md +17 -20
- package/concepts/en/GlobalSettings.md +49 -51
- package/concepts/en/MainAgent.md +8 -8
- package/concepts/en/ProxySwitch.md +3 -3
- package/concepts/en/QRCode.md +6 -6
- package/concepts/en/ResponseFields.md +7 -19
- package/concepts/en/SubAgent-Search.md +2 -2
- package/concepts/en/Teammate.md +4 -4
- package/concepts/en/Tool-apply_patch.md +11 -0
- package/concepts/en/Tool-create_goal.md +8 -0
- package/concepts/en/Tool-exec.md +88 -0
- package/concepts/en/Tool-followup_task.md +10 -0
- package/concepts/en/Tool-get_goal.md +5 -0
- package/concepts/en/Tool-image_generation.md +5 -0
- package/concepts/en/Tool-interrupt_agent.md +9 -0
- package/concepts/en/Tool-list_agents.md +9 -0
- package/concepts/en/Tool-list_mcp_resource_templates.md +8 -0
- package/concepts/en/Tool-list_mcp_resources.md +8 -0
- package/concepts/en/Tool-read_mcp_resource.md +10 -0
- package/concepts/en/Tool-request_user_input.md +9 -0
- package/concepts/en/Tool-send_message.md +10 -0
- package/concepts/en/Tool-shell_command.md +12 -0
- package/concepts/en/Tool-spawn_agent.md +11 -0
- package/concepts/en/Tool-tool_search.md +12 -0
- package/concepts/en/Tool-update_goal.md +9 -0
- package/concepts/en/Tool-update_plan.md +9 -0
- package/concepts/en/Tool-view_image.md +10 -0
- package/concepts/en/Tool-wait.md +12 -0
- package/concepts/en/Tool-wait_agent.md +9 -0
- package/concepts/en/Tool-web_search.md +11 -0
- package/concepts/en/Tools.md +29 -58
- package/concepts/en/ToolsFirst.md +24 -30
- package/concepts/en/TranslateContextPollution.md +10 -38
- package/concepts/en/UltraPlan.md +7 -7
- package/concepts/es/BodyDiffJSON.md +1 -1
- package/concepts/es/BodyFields.md +35 -38
- package/concepts/es/GlobalSettings.md +165 -167
- package/concepts/es/MainAgent.md +3 -4
- package/concepts/es/ProxySwitch.md +3 -3
- package/concepts/es/QRCode.md +1 -1
- package/concepts/es/ResponseFields.md +28 -40
- package/concepts/es/SubAgent-Search.md +12 -12
- package/concepts/es/Teammate.md +17 -17
- package/concepts/es/ToolsFirst.md +41 -47
- package/concepts/es/TranslateContextPollution.md +9 -9
- package/concepts/es/UltraPlan.md +38 -38
- package/concepts/fr/BodyDiffJSON.md +2 -2
- package/concepts/fr/BodyFields.md +35 -38
- package/concepts/fr/GlobalSettings.md +165 -167
- package/concepts/fr/MainAgent.md +3 -4
- package/concepts/fr/ProxySwitch.md +3 -3
- package/concepts/fr/QRCode.md +1 -1
- package/concepts/fr/ResponseFields.md +25 -37
- package/concepts/fr/SubAgent-Search.md +12 -12
- package/concepts/fr/Teammate.md +16 -16
- package/concepts/fr/ToolsFirst.md +40 -46
- package/concepts/fr/TranslateContextPollution.md +9 -9
- package/concepts/fr/UltraPlan.md +38 -38
- package/concepts/it/BodyDiffJSON.md +2 -2
- package/concepts/it/BodyFields.md +35 -38
- package/concepts/it/GlobalSettings.md +165 -167
- package/concepts/it/MainAgent.md +3 -4
- package/concepts/it/ProxySwitch.md +3 -3
- package/concepts/it/QRCode.md +1 -1
- package/concepts/it/ResponseFields.md +28 -40
- package/concepts/it/SubAgent-Search.md +12 -12
- package/concepts/it/Teammate.md +17 -17
- package/concepts/it/ToolsFirst.md +41 -47
- package/concepts/it/TranslateContextPollution.md +9 -9
- package/concepts/it/UltraPlan.md +38 -38
- package/concepts/ja/BodyDiffJSON.md +1 -1
- package/concepts/ja/BodyFields.md +30 -33
- package/concepts/ja/GlobalSettings.md +165 -167
- package/concepts/ja/MainAgent.md +3 -4
- package/concepts/ja/ProxySwitch.md +3 -3
- package/concepts/ja/QRCode.md +1 -1
- package/concepts/ja/ResponseFields.md +29 -41
- package/concepts/ja/SubAgent-Search.md +12 -12
- package/concepts/ja/Teammate.md +17 -17
- package/concepts/ja/ToolsFirst.md +41 -47
- package/concepts/ja/TranslateContextPollution.md +9 -9
- package/concepts/ja/UltraPlan.md +38 -38
- package/concepts/ko/BodyDiffJSON.md +1 -1
- package/concepts/ko/BodyFields.md +30 -33
- package/concepts/ko/GlobalSettings.md +165 -167
- package/concepts/ko/MainAgent.md +3 -4
- package/concepts/ko/ProxySwitch.md +3 -3
- package/concepts/ko/QRCode.md +1 -1
- package/concepts/ko/ResponseFields.md +29 -41
- package/concepts/ko/SubAgent-Search.md +12 -12
- package/concepts/ko/Teammate.md +17 -17
- package/concepts/ko/ToolsFirst.md +41 -47
- package/concepts/ko/TranslateContextPollution.md +9 -9
- package/concepts/ko/UltraPlan.md +38 -38
- package/concepts/no/BodyDiffJSON.md +2 -2
- package/concepts/no/BodyFields.md +30 -33
- package/concepts/no/GlobalSettings.md +165 -167
- package/concepts/no/MainAgent.md +3 -4
- package/concepts/no/ProxySwitch.md +3 -3
- package/concepts/no/QRCode.md +1 -1
- package/concepts/no/ResponseFields.md +29 -41
- package/concepts/no/SubAgent-Search.md +12 -12
- package/concepts/no/Teammate.md +17 -17
- package/concepts/no/ToolsFirst.md +42 -48
- package/concepts/no/TranslateContextPollution.md +9 -9
- package/concepts/no/UltraPlan.md +38 -38
- package/concepts/pl/BodyDiffJSON.md +2 -2
- package/concepts/pl/BodyFields.md +30 -33
- package/concepts/pl/GlobalSettings.md +165 -167
- package/concepts/pl/MainAgent.md +3 -4
- package/concepts/pl/ProxySwitch.md +3 -3
- package/concepts/pl/QRCode.md +1 -1
- package/concepts/pl/ResponseFields.md +29 -41
- package/concepts/pl/SubAgent-Search.md +12 -12
- package/concepts/pl/Teammate.md +17 -17
- package/concepts/pl/ToolsFirst.md +42 -48
- package/concepts/pl/TranslateContextPollution.md +9 -9
- package/concepts/pl/UltraPlan.md +38 -38
- package/concepts/pt-BR/BodyDiffJSON.md +2 -2
- package/concepts/pt-BR/BodyFields.md +30 -33
- package/concepts/pt-BR/GlobalSettings.md +165 -167
- package/concepts/pt-BR/MainAgent.md +3 -4
- package/concepts/pt-BR/ProxySwitch.md +3 -3
- package/concepts/pt-BR/QRCode.md +1 -1
- package/concepts/pt-BR/ResponseFields.md +29 -41
- package/concepts/pt-BR/SubAgent-Search.md +12 -12
- package/concepts/pt-BR/Teammate.md +17 -17
- package/concepts/pt-BR/ToolsFirst.md +41 -47
- package/concepts/pt-BR/TranslateContextPollution.md +9 -9
- package/concepts/pt-BR/UltraPlan.md +38 -38
- package/concepts/ru/BodyDiffJSON.md +1 -1
- package/concepts/ru/BodyFields.md +30 -33
- package/concepts/ru/GlobalSettings.md +165 -167
- package/concepts/ru/MainAgent.md +3 -4
- package/concepts/ru/ProxySwitch.md +3 -3
- package/concepts/ru/QRCode.md +1 -1
- package/concepts/ru/ResponseFields.md +29 -41
- package/concepts/ru/SubAgent-Search.md +12 -12
- package/concepts/ru/Teammate.md +17 -17
- package/concepts/ru/ToolsFirst.md +41 -47
- package/concepts/ru/TranslateContextPollution.md +9 -9
- package/concepts/ru/UltraPlan.md +38 -38
- package/concepts/th/BodyDiffJSON.md +1 -1
- package/concepts/th/BodyFields.md +30 -33
- package/concepts/th/GlobalSettings.md +165 -167
- package/concepts/th/MainAgent.md +3 -4
- package/concepts/th/ProxySwitch.md +3 -3
- package/concepts/th/QRCode.md +1 -1
- package/concepts/th/ResponseFields.md +29 -41
- package/concepts/th/SubAgent-Search.md +12 -12
- package/concepts/th/Teammate.md +17 -17
- package/concepts/th/ToolsFirst.md +42 -48
- package/concepts/th/TranslateContextPollution.md +9 -9
- package/concepts/th/UltraPlan.md +38 -38
- package/concepts/tr/BodyDiffJSON.md +1 -1
- package/concepts/tr/BodyFields.md +30 -33
- package/concepts/tr/GlobalSettings.md +165 -167
- package/concepts/tr/MainAgent.md +3 -4
- package/concepts/tr/ProxySwitch.md +3 -3
- package/concepts/tr/QRCode.md +1 -1
- package/concepts/tr/ResponseFields.md +29 -41
- package/concepts/tr/SubAgent-Search.md +12 -12
- package/concepts/tr/Teammate.md +16 -16
- package/concepts/tr/ToolsFirst.md +42 -48
- package/concepts/tr/TranslateContextPollution.md +9 -9
- package/concepts/tr/UltraPlan.md +38 -38
- package/concepts/uk/BodyDiffJSON.md +1 -1
- package/concepts/uk/BodyFields.md +30 -33
- package/concepts/uk/GlobalSettings.md +165 -167
- package/concepts/uk/MainAgent.md +3 -4
- package/concepts/uk/ProxySwitch.md +3 -3
- package/concepts/uk/QRCode.md +1 -1
- package/concepts/uk/ResponseFields.md +29 -41
- package/concepts/uk/SubAgent-Search.md +12 -12
- package/concepts/uk/Teammate.md +17 -17
- package/concepts/uk/ToolsFirst.md +42 -48
- package/concepts/uk/TranslateContextPollution.md +9 -9
- package/concepts/uk/UltraPlan.md +38 -38
- package/concepts/zh/BodyDiffJSON.md +4 -4
- package/concepts/zh/BodyFields.md +17 -20
- package/concepts/zh/GlobalSettings.md +26 -28
- package/concepts/zh/MainAgent.md +6 -6
- package/concepts/zh/ProxySwitch.md +1 -1
- package/concepts/zh/ResponseFields.md +7 -18
- package/concepts/zh/SubAgent-Search.md +2 -2
- package/concepts/zh/Teammate.md +4 -4
- package/concepts/zh/Tool-apply_patch.md +11 -0
- package/concepts/zh/Tool-create_goal.md +8 -0
- package/concepts/zh/Tool-exec.md +88 -0
- package/concepts/zh/Tool-followup_task.md +10 -0
- package/concepts/zh/Tool-get_goal.md +5 -0
- package/concepts/zh/Tool-image_generation.md +5 -0
- package/concepts/zh/Tool-interrupt_agent.md +9 -0
- package/concepts/zh/Tool-list_agents.md +9 -0
- package/concepts/zh/Tool-list_mcp_resource_templates.md +8 -0
- package/concepts/zh/Tool-list_mcp_resources.md +8 -0
- package/concepts/zh/Tool-read_mcp_resource.md +10 -0
- package/concepts/zh/Tool-request_user_input.md +9 -0
- package/concepts/zh/Tool-send_message.md +10 -0
- package/concepts/zh/Tool-shell_command.md +12 -0
- package/concepts/zh/Tool-spawn_agent.md +11 -0
- package/concepts/zh/Tool-tool_search.md +12 -0
- package/concepts/zh/Tool-update_goal.md +9 -0
- package/concepts/zh/Tool-update_plan.md +9 -0
- package/concepts/zh/Tool-view_image.md +10 -0
- package/concepts/zh/Tool-wait.md +12 -0
- package/concepts/zh/Tool-wait_agent.md +9 -0
- package/concepts/zh/Tool-web_search.md +11 -0
- package/concepts/zh/Tools.md +29 -58
- package/concepts/zh/ToolsFirst.md +21 -27
- package/concepts/zh/TranslateContextPollution.md +10 -38
- package/concepts/zh/UltraPlan.md +7 -7
- package/concepts/zh-TW/BodyFields.md +27 -30
- package/concepts/zh-TW/GlobalSettings.md +26 -28
- package/concepts/zh-TW/MainAgent.md +0 -1
- package/concepts/zh-TW/ProxySwitch.md +22 -22
- package/concepts/zh-TW/ResponseFields.md +29 -40
- package/concepts/zh-TW/SubAgent-Search.md +12 -12
- package/concepts/zh-TW/Teammate.md +16 -16
- package/concepts/zh-TW/ToolsFirst.md +41 -47
- package/concepts/zh-TW/UltraPlan.md +38 -38
- package/dist/assets/App-BZ9oclBj.js +2 -0
- package/dist/assets/App-rWON7PuS.css +1 -0
- package/dist/assets/MdxEditorPanel-B4lZ7Z_A.js +1 -0
- package/dist/assets/MdxEditorPanel-Da_ceLFo.css +1 -0
- package/dist/assets/Mobile-B5V_-6Hc.css +1 -0
- package/dist/assets/Mobile-C7Hy-TIM.js +1 -0
- package/dist/assets/abnfDiagram-VRR7QNED-DYYppIQV.js +1 -0
- package/dist/assets/apl-B8ZmNFqK.js +1 -0
- package/dist/assets/arc-BT3um7r4.js +1 -0
- package/dist/assets/architectureDiagram-ZJ3FMSHR-uweBqnh-.js +1 -0
- package/dist/assets/asciiarmor-CIte6N-S.js +1 -0
- package/dist/assets/asn1-Ax1-z1nk.js +1 -0
- package/dist/assets/asterisk-CLHf-iRz.js +1 -0
- package/dist/assets/blockDiagram-677ZJIJ3-DzeI4GyV.js +1 -0
- package/dist/assets/brainfuck-CdLnmYpe.js +1 -0
- package/dist/assets/c4Diagram-LMCZKHZV-DBI0K7wS.js +1 -0
- package/dist/assets/channel-BrN6yx0e.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-DK9JA8Ct.js +1 -0
- package/dist/assets/chunk-32BRIVSS-VNg3Ylzs.js +1 -0
- package/dist/assets/chunk-5VM5RSS4-DFhufY9o.js +1 -0
- package/dist/assets/chunk-EX3LRPZG-B93MHNBT.js +1 -0
- package/dist/assets/chunk-JWPE2WC7-C54OW1iN.js +1 -0
- package/dist/assets/chunk-MOJQB5TN-lKhWWIDe.js +1 -0
- package/dist/assets/chunk-RYQCIY6F-BK2jDXwB.js +1 -0
- package/dist/assets/chunk-V7JOEXUC-aLHBmfOc.js +1 -0
- package/dist/assets/chunk-VR4S4FIN-Bomuor1h.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-BPj05P7g.js +1 -0
- package/dist/assets/classDiagram-OUVF2IWQ-COm0X4uJ.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-COm0X4uJ.js +1 -0
- package/dist/assets/clike-BDgEuk0n.js +1 -0
- package/dist/assets/clojure-CrPxgQZl.js +1 -0
- package/dist/assets/cmake-BknZiLBu.js +1 -0
- package/dist/assets/cobol-DN1mHbgw.js +1 -0
- package/dist/assets/coffeescript-DTAwRC7M.js +1 -0
- package/dist/assets/commonlisp-B4DJ--Bo.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-DU44QHSb.js +1 -0
- package/dist/assets/crystal-DoipYOOv.js +1 -0
- package/dist/assets/css-BeQRP3p5.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-C0dowxsF.js +15 -0
- package/dist/assets/cynefinDiagram-TSTJHNR4-C6vNWPUV.js +1 -0
- package/dist/assets/cypher-BzELKutH.js +1 -0
- package/dist/assets/cytoscape.esm-D0POlJZ1.js +9 -0
- package/dist/assets/d-Mh2Rawoc.js +1 -0
- package/dist/assets/dagre-VKFMJZFB-aLG4uEb-.js +1 -0
- package/dist/assets/defaultLocale-DZ7E7S9R.js +1 -0
- package/dist/assets/diagram-FQU43EPY-D5C8GfdU.js +1 -0
- package/dist/assets/diagram-G47NLZAW-C9tRw_xi.js +1 -0
- package/dist/assets/diagram-NH7WQ7WH-AYxTxwJ2.js +1 -0
- package/dist/assets/diagram-OA4YK3LP-BuBE5n9n.js +1 -0
- package/dist/assets/diagram-WEI45ONY-Cda7zYeO.js +1 -0
- package/dist/assets/diff-B1OtBbfz.js +1 -0
- package/dist/assets/dockerfile-Du7niAGi.js +1 -0
- package/dist/assets/dtd-So5hjSa8.js +1 -0
- package/dist/assets/dylan-Co15g-RX.js +1 -0
- package/dist/assets/ebnf-DWrAnno4.js +1 -0
- package/dist/assets/ebnfDiagram-CCIWWBDH-DC_OGyoh.js +1 -0
- package/dist/assets/ecl-8tbf0rUT.js +1 -0
- package/dist/assets/eiffel-BXtlUtEg.js +1 -0
- package/dist/assets/elm-BwiUluiH.js +1 -0
- package/dist/assets/erDiagram-Q63AITRT-D2G21PVY.js +1 -0
- package/dist/assets/erlang-CrHBLi-m.js +1 -0
- package/dist/assets/factor-C7nC06Ta.js +1 -0
- package/dist/assets/fcl-DKWfryFT.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-CEQPYHrk.js +1 -0
- package/dist/assets/forth-Cg8ASoTj.js +1 -0
- package/dist/assets/fortran-BHf_qfFx.js +1 -0
- package/dist/assets/ganttDiagram-NO4QXBWP-JStxUtFR.js +1 -0
- package/dist/assets/gas-cj_2N_z1.js +1 -0
- package/dist/assets/gherkin-BiuTaH40.js +1 -0
- package/dist/assets/gitGraphDiagram-IHSO6WYX-BiojPH-V.js +1 -0
- package/dist/assets/graph-CnJdn47S.js +1 -0
- package/dist/assets/groovy-Bq8cXYUU.js +1 -0
- package/dist/assets/haskell-1ANuaeie.js +1 -0
- package/dist/assets/haxe-BUNSvWf_.js +1 -0
- package/dist/assets/html2canvas.esm-HFdlUwfn.js +20 -0
- package/dist/assets/http-DPC2IRQA.js +1 -0
- package/dist/assets/idl-DDbIciu_.js +1 -0
- package/dist/assets/index-BEyKG3O5.css +1 -0
- package/dist/assets/index-BduJoLQ5.js +1 -0
- package/dist/assets/index-BqwR8F0v.js +1 -0
- package/dist/assets/index-BsycQyrv.js +1 -0
- package/dist/assets/index-CFSDxZyA.js +1 -0
- package/dist/assets/index-CunNLTHc.js +1 -0
- package/dist/assets/index-OGkDFNTK.js +1 -0
- package/dist/assets/index-oxOyrL07.js +2 -0
- package/dist/assets/index-wvaycTGT.js +1 -0
- package/dist/assets/infoDiagram-FWYZ7A6U-Csl_aBIC.js +1 -0
- package/dist/assets/init-DMZMl1PR.js +1 -0
- package/dist/assets/ishikawaDiagram-FXEZZL3T-CjgGuMwr.js +1 -0
- package/dist/assets/javascript-Cet1BUca.js +1 -0
- package/dist/assets/journeyDiagram-5HDEW3XC-DUOy1PCq.js +1 -0
- package/dist/assets/jszip.min-BxIXdRg4.js +12 -0
- package/dist/assets/julia-CdxLCr68.js +1 -0
- package/dist/assets/kanban-definition-HUTT4EX6-Cx7GSrSj.js +1 -0
- package/dist/assets/katex-CfXf84Fz.js +1 -0
- package/dist/assets/layout-CCUE9ZRw.js +1 -0
- package/dist/assets/linear-GeBi6jJp.js +1 -0
- package/dist/assets/livescript-Bw1Vw7Ae.js +1 -0
- package/dist/assets/lua-DM1wbcnc.js +1 -0
- package/dist/assets/map-DQeg2hXV.js +1 -0
- package/dist/assets/mathematica-BXSv1_O1.js +1 -0
- package/dist/assets/mbox-CWY0keg3.js +1 -0
- package/dist/assets/mermaid.core-Cy9iVxov.js +7 -0
- package/dist/assets/mindmap-definition-LN4V7U3C-CUVxVkK5.js +1 -0
- package/dist/assets/mirc-CMLrLVMT.js +1 -0
- package/dist/assets/mllike-naqhAFf1.js +1 -0
- package/dist/assets/modelica-BvadsaHY.js +1 -0
- package/dist/assets/mscgen-DOr-Q10p.js +1 -0
- package/dist/assets/mumps-DKTl5Nyj.js +1 -0
- package/dist/assets/nginx-DWR90-d1.js +1 -0
- package/dist/assets/nsis-ooFGduGn.js +1 -0
- package/dist/assets/ntriples-q5jkU4X1.js +1 -0
- package/dist/assets/octave-Deu9Ci7_.js +1 -0
- package/dist/assets/orbiting-BCD4Vuij.svg +7 -0
- package/dist/assets/ordinal-DA-_8pMy.js +1 -0
- package/dist/assets/oz-cTQm1aoB.js +1 -0
- package/dist/assets/pascal-Oh3VmczP.js +1 -0
- package/dist/assets/pegDiagram-2B236MQR-BCF1byLY.js +1 -0
- package/dist/assets/perl-BddWF1HQ.js +1 -0
- package/dist/assets/pieDiagram-ENE6RG2P-D2cZTo8-.js +1 -0
- package/dist/assets/pig-g5ZQNIDr.js +1 -0
- package/dist/assets/powershell-CNmQNFVJ.js +1 -0
- package/dist/assets/properties-B41DXfv6.js +1 -0
- package/dist/assets/protobuf-eEBl31OJ.js +1 -0
- package/dist/assets/pug-DRSo4eCK.js +1 -0
- package/dist/assets/puppet-ClfRubAD.js +1 -0
- package/dist/assets/python-BSflc812.js +1 -0
- package/dist/assets/q-BBDx3N9Y.js +1 -0
- package/dist/assets/quadrantDiagram-ABIIQ3AL-Bwsn2--F.js +1 -0
- package/dist/assets/r-CByC1eO8.js +1 -0
- package/dist/assets/railroadDiagram-RFXS5EU6-CLGLpWHs.js +1 -0
- package/dist/assets/requirementDiagram-TGXJPOKE-BfmBYwZl.js +1 -0
- package/dist/assets/rpm-8t935XkD.js +1 -0
- package/dist/assets/ruby-CAgbkLZh.js +1 -0
- package/dist/assets/sankeyDiagram-HTMAVEWB-CqZhbyFn.js +1 -0
- package/dist/assets/sas-C-JWcRvo.js +1 -0
- package/dist/assets/scheme-D7o9RjUX.js +1 -0
- package/dist/assets/seqResourceLoaders-Bkh2Rb9i.js +2 -0
- package/dist/assets/seqResourceLoaders-Do7Kqg3B.css +41 -0
- package/dist/assets/sequenceDiagram-DBY2YBRQ-CcOdXkN2.js +1 -0
- package/dist/assets/shell-BXA79DV6.js +1 -0
- package/dist/assets/shimmer-BBDejmDf.svg +7 -0
- package/dist/assets/sieve-DNwyl0rd.js +1 -0
- package/dist/assets/simple-mode-CoQRusFX.js +1 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-B4OVmPqu.js +1 -0
- package/dist/assets/smalltalk-DggktV6S.js +1 -0
- package/dist/assets/solr-D7NRuIPM.js +1 -0
- package/dist/assets/sparql-Cr71deJQ.js +1 -0
- package/dist/assets/spreadsheet-BMD3tYqw.js +1 -0
- package/dist/assets/sql-CbOjXfBR.js +1 -0
- package/dist/assets/stateDiagram-2N3HPSRC-Dysun0oO.js +1 -0
- package/dist/assets/stateDiagram-v2-6OUMAXLB-BFet8xAz.js +1 -0
- package/dist/assets/stex-iDPIkRmr.js +1 -0
- package/dist/assets/stylus-nWZizI-c.js +1 -0
- package/dist/assets/swift-DOhLK5SG.js +1 -0
- package/dist/assets/swimlanes-5IMT3BWC-_30OXwXy.js +2 -0
- package/dist/assets/swimlanesDiagram-G3AALYLV-C19viY0G.js +1 -0
- package/dist/assets/tcl-CQhJd1WY.js +1 -0
- package/dist/assets/textile-D9XqfVtL.js +1 -0
- package/dist/assets/tiddlywiki-DfOYgVky.js +1 -0
- package/dist/assets/tiki-a0_YLWvV.js +1 -0
- package/dist/assets/timeline-definition-FHXFAJF6-X1Xk3onD.js +1 -0
- package/dist/assets/toml-ekl3F1YB.js +1 -0
- package/dist/assets/troff-Bh_pcXF1.js +1 -0
- package/dist/assets/ttcn-M6363IRW.js +1 -0
- package/dist/assets/ttcn-cfg-7I2DgJEN.js +1 -0
- package/dist/assets/turtle-B5jdxH6D.js +1 -0
- package/dist/assets/vb-DM-xPmVv.js +1 -0
- package/dist/assets/vbscript-B0qu41Yc.js +1 -0
- package/dist/assets/velocity-FCgsuVHf.js +1 -0
- package/dist/assets/vendor-antd-Bzi1WuBb.js +17 -0
- package/dist/assets/vendor-codemirror-CKTrJd47.js +1 -0
- package/dist/assets/vendor-highlight-IFTJ9nxW.js +1 -0
- package/dist/assets/vendor-markdown-BN3QLsBu.js +3 -0
- package/dist/assets/vendor-mdxeditor-4PbKH6K7.js +2 -0
- package/dist/assets/vendor-qrcode-Bnjm0rcu.js +6 -0
- package/dist/assets/vendor-react-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-virtuoso-2P4SowHC.js +1 -0
- package/dist/assets/vendor-xterm-DckPrCx1.js +37 -0
- package/dist/assets/vennDiagram-L72KCM5P-D3fQnGq7.js +1 -0
- package/dist/assets/verilog-nrfFvR12.js +1 -0
- package/dist/assets/vhdl-WlDXtV-W.js +1 -0
- package/dist/assets/wardleyDiagram-EHGQE667-CRhqI6-g.js +1 -0
- package/dist/assets/webidl-RIQoSXDx.js +1 -0
- package/dist/assets/xquery-BN45G3lJ.js +1 -0
- package/dist/assets/xychartDiagram-FW5EYKEG-Cmtgmmtg.js +1 -0
- package/dist/assets/yacas-U6gaIVhD.js +1 -0
- package/dist/assets/z80-CFe5BHbK.js +1 -0
- package/dist/index.html +25 -6
- package/dist/voice-packs/default/A_choice_for_your_consideration_sir.MP3 +0 -0
- package/dist/voice-packs/default/It_is_done_sir.MP3 +0 -0
- package/dist/voice-packs/default/The_plan_awaits_your_approval_sir.MP3 +0 -0
- package/dist/voice-packs/default/pack.json +24 -0
- package/dist/voice-packs/sanguo/ask.MP3 +0 -0
- package/dist/voice-packs/sanguo/end.MP3 +0 -0
- package/dist/voice-packs/sanguo/pack.json +24 -0
- package/dist/voice-packs/sanguo/plan.MP3 +0 -0
- package/electron/diag.js +110 -0
- package/electron/main.js +1432 -0
- package/electron/menu-model.js +107 -0
- package/electron/tab-bar.html +575 -0
- package/electron/tab-content-preload.js +66 -0
- package/electron/tab-preload.js +24 -0
- package/electron/tab-worker.js +142 -0
- package/electron/window-state.js +60 -0
- package/electron/workspace-preload.js +19 -0
- package/findcx.js +4 -0
- package/i18n.js +3 -423
- package/index.html +30 -0
- package/interceptor.js +144 -70
- package/jsconfig.json +25 -0
- package/lib/approval-reviewer.js +23 -0
- package/lib/appserver-bridge.js +1753 -237
- package/lib/ask-bridge.js +5 -5
- package/lib/cli-args.js +1 -0
- package/lib/codex-config.js +157 -18
- package/lib/context-watcher.js +6 -65
- package/lib/delta-reconstructor.js +74 -52
- package/lib/ensure-hooks.js +37 -30
- package/lib/interceptor-core.js +490 -43
- package/lib/log-watcher.js +3 -8
- package/lib/main-agent-entry.js +68 -0
- package/lib/openai-body.js +95 -0
- package/lib/otel-adapter.js +3 -2
- package/lib/otel-receiver.js +1 -2
- package/lib/perm-bridge.js +84 -44
- package/lib/proxy-env.js +45 -1
- package/lib/sdk-adapter.js +241 -51
- package/lib/sdk-manager.js +523 -370
- package/lib/skills-api.js +364 -0
- package/lib/stats-worker.js +21 -21
- package/lib/token-usage.js +29 -0
- package/lib/updater.js +3 -7
- package/package.json +27 -6
- package/plugins/.gitkeep +0 -0
- package/plugins/README.md +8 -0
- package/proxy.js +187 -241
- package/pty-manager.js +17 -1
- package/public/favicon.ico +0 -0
- package/public/voice-packs/default/A_choice_for_your_consideration_sir.MP3 +0 -0
- package/public/voice-packs/default/It_is_done_sir.MP3 +0 -0
- package/public/voice-packs/default/The_plan_awaits_your_approval_sir.MP3 +0 -0
- package/public/voice-packs/default/pack.json +24 -0
- package/public/voice-packs/sanguo/ask.MP3 +0 -0
- package/public/voice-packs/sanguo/end.MP3 +0 -0
- package/public/voice-packs/sanguo/pack.json +24 -0
- package/public/voice-packs/sanguo/plan.MP3 +0 -0
- package/scripts/check-no-control-bytes.js +130 -0
- package/scripts/check-sign-config.sh +85 -0
- package/scripts/gen-default-voicepack.js +176 -0
- package/scripts/mac-sign.sh +47 -0
- package/scripts/npm-pack-dry-run.mjs +20 -0
- package/server/lib/approval-modal-prefs.js +72 -0
- package/server/lib/context-rules.js +70 -0
- package/server/lib/delta-reconstructor.js +403 -0
- package/server/lib/tools-xml-formatter.js +91 -0
- package/server/lib/voice-pack-events.js +79 -0
- package/server.js +420 -86
- package/src/App.jsx +638 -0
- package/src/App.module.css +1088 -0
- package/src/AppBase.jsx +2803 -0
- package/src/Mobile.jsx +1348 -0
- package/src/components/approval/ApprovalModal.jsx +319 -0
- package/src/components/approval/ApprovalModal.module.css +160 -0
- package/src/components/approval/ToolApprovalPanel.jsx +111 -0
- package/src/components/approval/ToolApprovalPanel.module.css +250 -0
- package/src/components/chat/ApprovalPortalContext.js +26 -0
- package/src/components/chat/AskQuestionForm.jsx +279 -0
- package/src/components/chat/AskTimeoutCountdown.jsx +113 -0
- package/src/components/chat/ChatInputBar.jsx +584 -0
- package/src/components/chat/ChatInputBar.module.css +656 -0
- package/src/components/chat/ChatMessage.jsx +1587 -0
- package/src/components/chat/ChatMessage.module.css +1560 -0
- package/src/components/chat/ChatView.jsx +3533 -0
- package/src/components/chat/ChatView.module.css +418 -0
- package/src/components/chat/DiffView.jsx +111 -0
- package/src/components/chat/DiffView.module.css +141 -0
- package/src/components/chat/RoleFilterBar.jsx +35 -0
- package/src/components/chat/RoleFilterBar.module.css +62 -0
- package/src/components/chat/controllers/askFlowController.js +789 -0
- package/src/components/chat/controllers/permissionController.js +84 -0
- package/src/components/chat/controllers/ptyPromptController.js +166 -0
- package/src/components/chat/controllers/scrollHighlightController.js +50 -0
- package/src/components/chat/controllers/splitDragController.js +135 -0
- package/src/components/chat/controllers/toolFileChangeController.js +216 -0
- package/src/components/chat/interactionOwnership.js +106 -0
- package/src/components/chat/uploadDeferLogic.js +46 -0
- package/src/components/common/ConceptHelp.jsx +97 -0
- package/src/components/common/ConceptHelp.module.css +151 -0
- package/src/components/common/ConfirmRemoveButton.jsx +78 -0
- package/src/components/common/CountryFlag.jsx +105 -0
- package/src/components/common/CountryFlag.module.css +36 -0
- package/src/components/common/DingTalkIcon.jsx +20 -0
- package/src/components/common/DiscordIcon.jsx +21 -0
- package/src/components/common/FeishuIcon.jsx +23 -0
- package/src/components/common/HoverPreviewTip.jsx +62 -0
- package/src/components/common/HoverPreviewTip.module.css +21 -0
- package/src/components/common/ImageLightbox.jsx +244 -0
- package/src/components/common/ImageLightbox.module.css +120 -0
- package/src/components/common/MemoryDetailModal.jsx +75 -0
- package/src/components/common/OpenFolderIcon.jsx +53 -0
- package/src/components/common/OpenFolderIcon.module.css +7 -0
- package/src/components/common/PanelResizer.jsx +48 -0
- package/src/components/common/PanelResizer.module.css +11 -0
- package/src/components/common/QuickAutoApproveRows.jsx +66 -0
- package/src/components/common/RefreshIcon.jsx +50 -0
- package/src/components/common/RefreshIcon.module.css +26 -0
- package/src/components/common/SnapLineOverlay.jsx +47 -0
- package/src/components/common/SnapLineOverlay.module.css +31 -0
- package/src/components/common/ToolsHelp.jsx +113 -0
- package/src/components/common/ToolsHelp.module.css +85 -0
- package/src/components/common/WeComIcon.jsx +22 -0
- package/src/components/common/quickMenuIcons.jsx +99 -0
- package/src/components/common/sharedChrome.module.css +274 -0
- package/src/components/dashboard/AppHeader.jsx +2085 -0
- package/src/components/dashboard/AppHeader.module.css +676 -0
- package/src/components/dashboard/CachePopoverContent.jsx +538 -0
- package/src/components/dashboard/CachePopoverContent.module.css +191 -0
- package/src/components/dashboard/ContextTab.jsx +769 -0
- package/src/components/dashboard/ContextTab.module.css +565 -0
- package/src/components/dashboard/DetailPanel.jsx +813 -0
- package/src/components/dashboard/DetailPanel.module.css +513 -0
- package/src/components/dashboard/LiveTagPopover.jsx +89 -0
- package/src/components/dashboard/LiveTagPopover.module.css +67 -0
- package/src/components/dashboard/RequestList.jsx +146 -0
- package/src/components/dashboard/RequestList.module.css +146 -0
- package/src/components/dashboard/TeamSessionPanel.jsx +584 -0
- package/src/components/dashboard/TeamSessionPanel.module.css +457 -0
- package/src/components/dashboard/UsageWindowPill.jsx +166 -0
- package/src/components/dashboard/UsageWindowPill.module.css +140 -0
- package/src/components/dashboard/WorkflowRunsPanel.jsx +108 -0
- package/src/components/dashboard/WorkflowRunsPanel.module.css +72 -0
- package/src/components/dashboard/WorkspaceList.jsx +410 -0
- package/src/components/dashboard/WorkspaceList.module.css +254 -0
- package/src/components/files/FileContentView.jsx +1013 -0
- package/src/components/files/FileContentView.module.css +418 -0
- package/src/components/files/FileExplorer.jsx +937 -0
- package/src/components/files/FileExplorer.module.css +173 -0
- package/src/components/files/MdxEditorPanel.base.css +9 -0
- package/src/components/files/MdxEditorPanel.jsx +181 -0
- package/src/components/files/MdxEditorPanel.module.css +917 -0
- package/src/components/git/DiffMiniMap.jsx +98 -0
- package/src/components/git/DiffMiniMap.module.css +17 -0
- package/src/components/git/FullFileDiffView.jsx +265 -0
- package/src/components/git/FullFileDiffView.module.css +176 -0
- package/src/components/git/GitChanges.jsx +461 -0
- package/src/components/git/GitChanges.module.css +337 -0
- package/src/components/git/GitDiffView.jsx +161 -0
- package/src/components/git/GitDiffView.module.css +205 -0
- package/src/components/mobile/MobileDrawerCloseButton.jsx +18 -0
- package/src/components/mobile/MobileFileExplorer.jsx +231 -0
- package/src/components/mobile/MobileFileExplorer.module.css +171 -0
- package/src/components/mobile/MobileGitDiff.jsx +293 -0
- package/src/components/mobile/MobileGitDiff.module.css +317 -0
- package/src/components/mobile/MobileStats.jsx +211 -0
- package/src/components/mobile/MobileStats.module.css +75 -0
- package/src/components/settings/ImAppendSystemModal.jsx +104 -0
- package/src/components/settings/ImConversationModal.jsx +328 -0
- package/src/components/settings/ImConversationModal.module.css +49 -0
- package/src/components/settings/ImPlatformSettings.jsx +453 -0
- package/src/components/settings/ImPlatformSettings.module.css +110 -0
- package/src/components/settings/ImSkillsModal.jsx +94 -0
- package/src/components/settings/ImStatusChip.jsx +80 -0
- package/src/components/settings/ImStatusChip.module.css +36 -0
- package/src/components/settings/MessagingModal.jsx +65 -0
- package/src/components/settings/MessagingModal.module.css +95 -0
- package/src/components/settings/ModelPromptTabs.jsx +118 -0
- package/src/components/settings/PluginModal.jsx +423 -0
- package/src/components/settings/PluginModal.module.css +136 -0
- package/src/components/settings/PreferencesForm.jsx +104 -0
- package/src/components/settings/PreferencesForm.module.css +27 -0
- package/src/components/settings/ProcessModal.jsx +175 -0
- package/src/components/settings/ProcessModal.module.css +21 -0
- package/src/components/settings/ProjectAliasEditor.jsx +138 -0
- package/src/components/settings/ProjectAliasEditor.module.css +87 -0
- package/src/components/settings/ProjectPrefsManagerModal.jsx +123 -0
- package/src/components/settings/ProjectPrefsManagerModal.module.css +52 -0
- package/src/components/settings/ProxyModal.jsx +286 -0
- package/src/components/settings/ProxyModal.module.css +141 -0
- package/src/components/settings/SkillsManagerModal.jsx +143 -0
- package/src/components/settings/SkillsManagerModal.module.css +143 -0
- package/src/components/settings/SystemTextModal.jsx +312 -0
- package/src/components/settings/SystemTextModal.module.css +331 -0
- package/src/components/settings/VoicePackSettings.jsx +319 -0
- package/src/components/settings/VoicePackSettings.module.css +151 -0
- package/src/components/settings/imPlatforms.js +177 -0
- package/src/components/terminal/CustomUltraplanEditModal.jsx +227 -0
- package/src/components/terminal/CustomUltraplanEditModal.module.css +189 -0
- package/src/components/terminal/PresetExpertPickerModal.jsx +116 -0
- package/src/components/terminal/PresetExpertPickerModal.module.css +90 -0
- package/src/components/terminal/PresetModal.jsx +118 -0
- package/src/components/terminal/ScratchTerminal.jsx +359 -0
- package/src/components/terminal/TerminalPanel.jsx +2021 -0
- package/src/components/terminal/TerminalPanel.module.css +872 -0
- package/src/components/terminal/TerminalWsContext.jsx +226 -0
- package/src/components/terminal/UltraPlanModal.jsx +328 -0
- package/src/components/terminal/UltraPlanModal.module.css +527 -0
- package/src/components/terminal/UltraplanExpertManagerModal.jsx +114 -0
- package/src/components/terminal/UltraplanExpertManagerModal.module.css +91 -0
- package/src/components/terminal/UltraplanPanel.jsx +307 -0
- package/src/components/terminal/UltraplanPanel.module.css +540 -0
- package/src/components/terminal/terminalThemes.js +33 -0
- package/src/components/viewers/ImageViewer.jsx +288 -0
- package/src/components/viewers/ImageViewer.module.css +203 -0
- package/src/components/viewers/JsonViewer.jsx +40 -0
- package/src/components/viewers/JsonViewer.module.css +9 -0
- package/src/components/viewers/LogTable.jsx +131 -0
- package/src/components/viewers/MarkdownBlock.jsx +112 -0
- package/src/components/viewers/MarkdownBlock.module.css +157 -0
- package/src/components/viewers/ToolResultView.jsx +389 -0
- package/src/components/viewers/ToolResultView.module.css +108 -0
- package/src/components/viewers/WebResultsView.jsx +74 -0
- package/src/components/viewers/WebResultsView.module.css +85 -0
- package/src/components/viewers/WorkflowLiveHud.jsx +153 -0
- package/src/components/viewers/WorkflowLiveHud.module.css +256 -0
- package/src/components/viewers/WorkflowPanel.jsx +243 -0
- package/src/components/viewers/WorkflowPanel.module.css +265 -0
- package/src/components/viewers/WorkflowTimeline.jsx +83 -0
- package/src/components/viewers/WorkflowTimeline.module.css +116 -0
- package/src/contexts/SettingsContext.jsx +140 -0
- package/src/env.js +54 -0
- package/src/global.css +607 -0
- package/src/hooks/useMarkdownExport.js +146 -0
- package/src/hooks/useMermaidRender.js +193 -0
- package/src/hooks/useProjectAlias.js +29 -0
- package/src/hooks/useSessionStoragePersistedSet.js +41 -0
- package/src/i18n/mdxTranslations.js +472 -0
- package/src/i18n.js +12409 -0
- package/src/img/ccv-logo.png +0 -0
- package/src/img/codex/entrance.svg +7 -0
- package/src/img/codex/exit.svg +7 -0
- package/src/img/codex/orbiting.svg +7 -0
- package/src/img/codex/shimmer.svg +7 -0
- package/src/img/codex/thinking.svg +7 -0
- package/src/img/codex/tickle.svg +7 -0
- package/src/img/codex/waiting.svg +7 -0
- package/src/img/codex/writing.svg +7 -0
- package/src/img/default-avatar.svg +1 -0
- package/src/img/default-model-avatar.svg +1 -0
- package/src/img/loading-pet.gif +0 -0
- package/src/img/model-codex.svg +2 -0
- package/src/img/model-deepseek.svg +2 -0
- package/src/img/model-gemini.svg +2 -0
- package/src/img/model-glm.svg +2 -0
- package/src/img/model-kimi.svg +2 -0
- package/src/img/model-minimax.svg +2 -0
- package/src/img/model-openai.svg +2 -0
- package/src/img/model-qwen.svg +2 -0
- package/src/img/teammates/DESIGN.md +165 -0
- package/src/img/teammates/analyst.svg +32 -0
- package/src/img/teammates/auditor.svg +28 -0
- package/src/img/teammates/builder.svg +33 -0
- package/src/img/teammates/default.svg +25 -0
- package/src/img/teammates/designer.svg +25 -0
- package/src/img/teammates/executor.svg +31 -0
- package/src/img/teammates/expert.svg +30 -0
- package/src/img/teammates/explorer.svg +30 -0
- package/src/img/teammates/implementer.svg +34 -0
- package/src/img/teammates/investigator.svg +29 -0
- package/src/img/teammates/marvel/analyst.svg +26 -0
- package/src/img/teammates/marvel/auditor.svg +28 -0
- package/src/img/teammates/marvel/builder.svg +15 -0
- package/src/img/teammates/marvel/default.svg +25 -0
- package/src/img/teammates/marvel/designer.svg +21 -0
- package/src/img/teammates/marvel/executor.svg +25 -0
- package/src/img/teammates/marvel/expert.svg +31 -0
- package/src/img/teammates/marvel/explorer.svg +22 -0
- package/src/img/teammates/marvel/implementer.svg +30 -0
- package/src/img/teammates/marvel/investigator.svg +21 -0
- package/src/img/teammates/marvel/researcher.svg +30 -0
- package/src/img/teammates/marvel/reviewer.svg +21 -0
- package/src/img/teammates/marvel/scanner.svg +23 -0
- package/src/img/teammates/marvel/security.svg +30 -0
- package/src/img/teammates/marvel/tracer.svg +19 -0
- package/src/img/teammates/marvel/translator.svg +24 -0
- package/src/img/teammates/marvel/worker.svg +16 -0
- package/src/img/teammates/researcher.svg +35 -0
- package/src/img/teammates/reviewer.svg +23 -0
- package/src/img/teammates/scanner.svg +31 -0
- package/src/img/teammates/security.svg +31 -0
- package/src/img/teammates/tracer.svg +30 -0
- package/src/img/teammates/translator.svg +32 -0
- package/src/img/teammates/worker.svg +29 -0
- package/src/main.jsx +30 -0
- package/src/tokenInterceptor.js +60 -0
- package/src/utils/apiUrl.js +26 -0
- package/src/utils/approvalReviewerOptions.js +27 -0
- package/src/utils/askFallback.js +21 -0
- package/src/utils/askOptionDesc.js +55 -0
- package/src/utils/askPortalMatcher.js +54 -0
- package/src/utils/autoApproveOptions.js +23 -0
- package/src/utils/avatarAnimationPostPass.js +46 -0
- package/src/utils/builtinPresets.js +18 -0
- package/src/utils/clearCheckpoint.js +154 -0
- package/src/utils/commandValidator.js +45 -0
- package/src/utils/contentFilter.js +675 -0
- package/src/utils/contextRaw.js +17 -0
- package/src/utils/contextSidebarNavigation.js +16 -0
- package/src/utils/conversationEntryNormalize.js +497 -0
- package/src/utils/displayScaleHelper.js +47 -0
- package/src/utils/entry-slim.js +594 -0
- package/src/utils/entryCache.js +179 -0
- package/src/utils/errorReport.js +40 -0
- package/src/utils/fileExpandedPathsStorage.js +70 -0
- package/src/utils/fileIcons.jsx +36 -0
- package/src/utils/fileOpen.js +37 -0
- package/src/utils/formatters.js +48 -0
- package/src/utils/gitApi.js +50 -0
- package/src/utils/gitTreeBuilder.js +19 -0
- package/src/utils/helpers.js +462 -0
- package/src/utils/identityHeal.js +83 -0
- package/src/utils/imOrigin.js +24 -0
- package/src/utils/imTr.js +7 -0
- package/src/utils/imageCompress.js +83 -0
- package/src/utils/imageDownscale.js +28 -0
- package/src/utils/imageResize.js +112 -0
- package/src/utils/ingestPipeline.js +47 -0
- package/src/utils/lazyWithReload.js +53 -0
- package/src/utils/markdown.js +30 -0
- package/src/utils/markdownIncremental.js +39 -0
- package/src/utils/markdownProfiler.js +113 -0
- package/src/utils/mdExtensionDetect.js +64 -0
- package/src/utils/memoryLinkParser.js +44 -0
- package/src/utils/modalMask.js +15 -0
- package/src/utils/monotime.js +6 -0
- package/src/utils/pinnedMenu.js +55 -0
- package/src/utils/presetShortcuts.js +14 -0
- package/src/utils/projectAlias.js +135 -0
- package/src/utils/promptClassifier.js +111 -0
- package/src/utils/promptDetect.js +196 -0
- package/src/utils/promptNav.js +60 -0
- package/src/utils/ptyChunkBuilder.js +251 -0
- package/src/utils/quickMenuHoverIntent.js +35 -0
- package/src/utils/rateLimitParser.js +249 -0
- package/src/utils/readResultPool.js +106 -0
- package/src/utils/refreshCachedItemProp.js +32 -0
- package/src/utils/requestTools.js +19 -0
- package/src/utils/requestType.js +284 -0
- package/src/utils/resizeCalc.js +21 -0
- package/src/utils/resolveLocalized.js +17 -0
- package/src/utils/seqResourceLoaders.js +79 -0
- package/src/utils/sessionManager.js +694 -0
- package/src/utils/sessionMerge.js +290 -0
- package/src/utils/skillModalController.js +111 -0
- package/src/utils/skillsParser.js +116 -0
- package/src/utils/slashCommandLabels.js +80 -0
- package/src/utils/splitDragCalc.js +73 -0
- package/src/utils/stickyBottomController.js +643 -0
- package/src/utils/svgSanitize.js +48 -0
- package/src/utils/systemTags.js +47 -0
- package/src/utils/tCodex.js +20 -0
- package/src/utils/teamModalBuilder.js +347 -0
- package/src/utils/teamSessionParser.js +186 -0
- package/src/utils/teammateAvatars.js +194 -0
- package/src/utils/teammateDetector.js +106 -0
- package/src/utils/termDiag.js +114 -0
- package/src/utils/terminalClipboard.js +117 -0
- package/src/utils/terminalWriteQueue.js +398 -0
- package/src/utils/thinkingMerge.js +49 -0
- package/src/utils/tokenUsage.js +1 -0
- package/src/utils/toolCatalog.js +31 -0
- package/src/utils/toolNameAliases.js +13 -0
- package/src/utils/toolResultBuilder.js +312 -0
- package/src/utils/toolResultClassifier.js +16 -0
- package/src/utils/toolResultCore.js +161 -0
- package/src/utils/toolsDiff.js +44 -0
- package/src/utils/toolsXmlFormatter.js +3 -0
- package/src/utils/ultraplanController.js +109 -0
- package/src/utils/ultraplanExperts.js +86 -0
- package/src/utils/ultraplanTemplates.js +152 -0
- package/src/utils/userImageRefs.js +49 -0
- package/src/utils/voicePackPlayer.js +233 -0
- package/src/utils/webResultGrouping.js +111 -0
- package/src/utils/workflowFormat.js +52 -0
- package/src/utils/workflowRuns.js +118 -0
- package/src/utils/workflowStore.js +94 -0
- package/vite.config.js +106 -0
- package/concepts/ar/CacheRebuild.md +0 -36
- package/concepts/ar/KVCacheContent.md +0 -49
- package/concepts/ar/Tool-Agent.md +0 -125
- package/concepts/ar/Tool-AskUserQuestion.md +0 -75
- package/concepts/ar/Tool-Bash.md +0 -167
- package/concepts/ar/Tool-Edit.md +0 -46
- package/concepts/ar/Tool-EnterPlanMode.md +0 -128
- package/concepts/ar/Tool-EnterWorktree.md +0 -56
- package/concepts/ar/Tool-ExitPlanMode.md +0 -62
- package/concepts/ar/Tool-Glob.md +0 -39
- package/concepts/ar/Tool-Grep.md +0 -57
- package/concepts/ar/Tool-NotebookEdit.md +0 -33
- package/concepts/ar/Tool-Read.md +0 -56
- package/concepts/ar/Tool-SendMessage.md +0 -41
- package/concepts/ar/Tool-Skill.md +0 -56
- package/concepts/ar/Tool-Task.md +0 -51
- package/concepts/ar/Tool-TaskCreate.md +0 -78
- package/concepts/ar/Tool-TaskGet.md +0 -56
- package/concepts/ar/Tool-TaskList.md +0 -56
- package/concepts/ar/Tool-TaskOutput.md +0 -40
- package/concepts/ar/Tool-TaskStop.md +0 -32
- package/concepts/ar/Tool-TaskUpdate.md +0 -123
- package/concepts/ar/Tool-TeamCreate.md +0 -38
- package/concepts/ar/Tool-TeamDelete.md +0 -30
- package/concepts/ar/Tool-WebFetch.md +0 -55
- package/concepts/ar/Tool-WebSearch.md +0 -58
- package/concepts/ar/Tool-Write.md +0 -39
- package/concepts/ar/Tool-executeCode.md +0 -41
- package/concepts/ar/Tool-getDiagnostics.md +0 -32
- package/concepts/ar/Tools.md +0 -67
- package/concepts/da/CacheRebuild.md +0 -36
- package/concepts/da/KVCacheContent.md +0 -49
- package/concepts/da/Tool-Agent.md +0 -125
- package/concepts/da/Tool-AskUserQuestion.md +0 -75
- package/concepts/da/Tool-Bash.md +0 -167
- package/concepts/da/Tool-Edit.md +0 -46
- package/concepts/da/Tool-EnterPlanMode.md +0 -128
- package/concepts/da/Tool-EnterWorktree.md +0 -56
- package/concepts/da/Tool-ExitPlanMode.md +0 -62
- package/concepts/da/Tool-Glob.md +0 -39
- package/concepts/da/Tool-Grep.md +0 -57
- package/concepts/da/Tool-NotebookEdit.md +0 -33
- package/concepts/da/Tool-Read.md +0 -56
- package/concepts/da/Tool-SendMessage.md +0 -41
- package/concepts/da/Tool-Skill.md +0 -56
- package/concepts/da/Tool-Task.md +0 -51
- package/concepts/da/Tool-TaskCreate.md +0 -78
- package/concepts/da/Tool-TaskGet.md +0 -56
- package/concepts/da/Tool-TaskList.md +0 -56
- package/concepts/da/Tool-TaskOutput.md +0 -40
- package/concepts/da/Tool-TaskStop.md +0 -32
- package/concepts/da/Tool-TaskUpdate.md +0 -123
- package/concepts/da/Tool-TeamCreate.md +0 -38
- package/concepts/da/Tool-TeamDelete.md +0 -30
- package/concepts/da/Tool-WebFetch.md +0 -55
- package/concepts/da/Tool-WebSearch.md +0 -58
- package/concepts/da/Tool-Write.md +0 -39
- package/concepts/da/Tool-executeCode.md +0 -41
- package/concepts/da/Tool-getDiagnostics.md +0 -32
- package/concepts/da/Tools.md +0 -67
- package/concepts/de/CacheRebuild.md +0 -36
- package/concepts/de/KVCacheContent.md +0 -49
- package/concepts/de/Tool-Agent.md +0 -125
- package/concepts/de/Tool-AskUserQuestion.md +0 -75
- package/concepts/de/Tool-Bash.md +0 -167
- package/concepts/de/Tool-Edit.md +0 -46
- package/concepts/de/Tool-EnterPlanMode.md +0 -128
- package/concepts/de/Tool-EnterWorktree.md +0 -56
- package/concepts/de/Tool-ExitPlanMode.md +0 -62
- package/concepts/de/Tool-Glob.md +0 -39
- package/concepts/de/Tool-Grep.md +0 -57
- package/concepts/de/Tool-NotebookEdit.md +0 -33
- package/concepts/de/Tool-Read.md +0 -56
- package/concepts/de/Tool-SendMessage.md +0 -41
- package/concepts/de/Tool-Skill.md +0 -56
- package/concepts/de/Tool-Task.md +0 -51
- package/concepts/de/Tool-TaskCreate.md +0 -78
- package/concepts/de/Tool-TaskGet.md +0 -56
- package/concepts/de/Tool-TaskList.md +0 -56
- package/concepts/de/Tool-TaskOutput.md +0 -40
- package/concepts/de/Tool-TaskStop.md +0 -32
- package/concepts/de/Tool-TaskUpdate.md +0 -123
- package/concepts/de/Tool-TeamCreate.md +0 -38
- package/concepts/de/Tool-TeamDelete.md +0 -30
- package/concepts/de/Tool-WebFetch.md +0 -55
- package/concepts/de/Tool-WebSearch.md +0 -58
- package/concepts/de/Tool-Write.md +0 -39
- package/concepts/de/Tool-executeCode.md +0 -41
- package/concepts/de/Tool-getDiagnostics.md +0 -32
- package/concepts/de/Tools.md +0 -67
- package/concepts/en/CacheRebuild.md +0 -36
- package/concepts/en/KVCacheContent.md +0 -49
- package/concepts/en/Tool-Agent.md +0 -125
- package/concepts/en/Tool-AskUserQuestion.md +0 -75
- package/concepts/en/Tool-Bash.md +0 -167
- package/concepts/en/Tool-Edit.md +0 -46
- package/concepts/en/Tool-EnterPlanMode.md +0 -128
- package/concepts/en/Tool-EnterWorktree.md +0 -56
- package/concepts/en/Tool-ExitPlanMode.md +0 -62
- package/concepts/en/Tool-Glob.md +0 -39
- package/concepts/en/Tool-Grep.md +0 -57
- package/concepts/en/Tool-NotebookEdit.md +0 -33
- package/concepts/en/Tool-Read.md +0 -56
- package/concepts/en/Tool-SendMessage.md +0 -41
- package/concepts/en/Tool-Skill.md +0 -56
- package/concepts/en/Tool-Task.md +0 -51
- package/concepts/en/Tool-TaskCreate.md +0 -78
- package/concepts/en/Tool-TaskGet.md +0 -56
- package/concepts/en/Tool-TaskList.md +0 -56
- package/concepts/en/Tool-TaskOutput.md +0 -40
- package/concepts/en/Tool-TaskStop.md +0 -32
- package/concepts/en/Tool-TaskUpdate.md +0 -123
- package/concepts/en/Tool-TeamCreate.md +0 -38
- package/concepts/en/Tool-TeamDelete.md +0 -30
- package/concepts/en/Tool-WebFetch.md +0 -55
- package/concepts/en/Tool-WebSearch.md +0 -58
- package/concepts/en/Tool-Write.md +0 -39
- package/concepts/en/Tool-executeCode.md +0 -41
- package/concepts/en/Tool-getDiagnostics.md +0 -32
- package/concepts/es/CacheRebuild.md +0 -36
- package/concepts/es/KVCacheContent.md +0 -49
- package/concepts/es/Tool-Agent.md +0 -125
- package/concepts/es/Tool-AskUserQuestion.md +0 -75
- package/concepts/es/Tool-Bash.md +0 -167
- package/concepts/es/Tool-Edit.md +0 -46
- package/concepts/es/Tool-EnterPlanMode.md +0 -128
- package/concepts/es/Tool-EnterWorktree.md +0 -56
- package/concepts/es/Tool-ExitPlanMode.md +0 -62
- package/concepts/es/Tool-Glob.md +0 -39
- package/concepts/es/Tool-Grep.md +0 -57
- package/concepts/es/Tool-NotebookEdit.md +0 -33
- package/concepts/es/Tool-Read.md +0 -56
- package/concepts/es/Tool-SendMessage.md +0 -41
- package/concepts/es/Tool-Skill.md +0 -56
- package/concepts/es/Tool-Task.md +0 -51
- package/concepts/es/Tool-TaskCreate.md +0 -78
- package/concepts/es/Tool-TaskGet.md +0 -56
- package/concepts/es/Tool-TaskList.md +0 -56
- package/concepts/es/Tool-TaskOutput.md +0 -40
- package/concepts/es/Tool-TaskStop.md +0 -32
- package/concepts/es/Tool-TaskUpdate.md +0 -123
- package/concepts/es/Tool-TeamCreate.md +0 -38
- package/concepts/es/Tool-TeamDelete.md +0 -30
- package/concepts/es/Tool-WebFetch.md +0 -55
- package/concepts/es/Tool-WebSearch.md +0 -58
- package/concepts/es/Tool-Write.md +0 -39
- package/concepts/es/Tool-executeCode.md +0 -41
- package/concepts/es/Tool-getDiagnostics.md +0 -32
- package/concepts/es/Tools.md +0 -67
- package/concepts/fr/CacheRebuild.md +0 -36
- package/concepts/fr/KVCacheContent.md +0 -49
- package/concepts/fr/Tool-Agent.md +0 -125
- package/concepts/fr/Tool-AskUserQuestion.md +0 -75
- package/concepts/fr/Tool-Bash.md +0 -167
- package/concepts/fr/Tool-Edit.md +0 -46
- package/concepts/fr/Tool-EnterPlanMode.md +0 -128
- package/concepts/fr/Tool-EnterWorktree.md +0 -56
- package/concepts/fr/Tool-ExitPlanMode.md +0 -62
- package/concepts/fr/Tool-Glob.md +0 -39
- package/concepts/fr/Tool-Grep.md +0 -57
- package/concepts/fr/Tool-NotebookEdit.md +0 -33
- package/concepts/fr/Tool-Read.md +0 -56
- package/concepts/fr/Tool-SendMessage.md +0 -41
- package/concepts/fr/Tool-Skill.md +0 -56
- package/concepts/fr/Tool-Task.md +0 -51
- package/concepts/fr/Tool-TaskCreate.md +0 -78
- package/concepts/fr/Tool-TaskGet.md +0 -56
- package/concepts/fr/Tool-TaskList.md +0 -56
- package/concepts/fr/Tool-TaskOutput.md +0 -40
- package/concepts/fr/Tool-TaskStop.md +0 -32
- package/concepts/fr/Tool-TaskUpdate.md +0 -123
- package/concepts/fr/Tool-TeamCreate.md +0 -38
- package/concepts/fr/Tool-TeamDelete.md +0 -30
- package/concepts/fr/Tool-WebFetch.md +0 -55
- package/concepts/fr/Tool-WebSearch.md +0 -58
- package/concepts/fr/Tool-Write.md +0 -39
- package/concepts/fr/Tool-executeCode.md +0 -41
- package/concepts/fr/Tool-getDiagnostics.md +0 -32
- package/concepts/fr/Tools.md +0 -67
- package/concepts/it/CacheRebuild.md +0 -36
- package/concepts/it/KVCacheContent.md +0 -49
- package/concepts/it/Tool-Agent.md +0 -125
- package/concepts/it/Tool-AskUserQuestion.md +0 -75
- package/concepts/it/Tool-Bash.md +0 -167
- package/concepts/it/Tool-Edit.md +0 -46
- package/concepts/it/Tool-EnterPlanMode.md +0 -128
- package/concepts/it/Tool-EnterWorktree.md +0 -56
- package/concepts/it/Tool-ExitPlanMode.md +0 -62
- package/concepts/it/Tool-Glob.md +0 -39
- package/concepts/it/Tool-Grep.md +0 -57
- package/concepts/it/Tool-NotebookEdit.md +0 -33
- package/concepts/it/Tool-Read.md +0 -56
- package/concepts/it/Tool-SendMessage.md +0 -41
- package/concepts/it/Tool-Skill.md +0 -56
- package/concepts/it/Tool-Task.md +0 -51
- package/concepts/it/Tool-TaskCreate.md +0 -78
- package/concepts/it/Tool-TaskGet.md +0 -56
- package/concepts/it/Tool-TaskList.md +0 -56
- package/concepts/it/Tool-TaskOutput.md +0 -40
- package/concepts/it/Tool-TaskStop.md +0 -32
- package/concepts/it/Tool-TaskUpdate.md +0 -123
- package/concepts/it/Tool-TeamCreate.md +0 -38
- package/concepts/it/Tool-TeamDelete.md +0 -30
- package/concepts/it/Tool-WebFetch.md +0 -55
- package/concepts/it/Tool-WebSearch.md +0 -58
- package/concepts/it/Tool-Write.md +0 -39
- package/concepts/it/Tool-executeCode.md +0 -41
- package/concepts/it/Tool-getDiagnostics.md +0 -32
- package/concepts/it/Tools.md +0 -67
- package/concepts/ja/CacheRebuild.md +0 -36
- package/concepts/ja/KVCacheContent.md +0 -49
- package/concepts/ja/Tool-Agent.md +0 -125
- package/concepts/ja/Tool-AskUserQuestion.md +0 -75
- package/concepts/ja/Tool-Bash.md +0 -167
- package/concepts/ja/Tool-Edit.md +0 -46
- package/concepts/ja/Tool-EnterPlanMode.md +0 -128
- package/concepts/ja/Tool-EnterWorktree.md +0 -56
- package/concepts/ja/Tool-ExitPlanMode.md +0 -62
- package/concepts/ja/Tool-Glob.md +0 -39
- package/concepts/ja/Tool-Grep.md +0 -57
- package/concepts/ja/Tool-NotebookEdit.md +0 -33
- package/concepts/ja/Tool-Read.md +0 -56
- package/concepts/ja/Tool-SendMessage.md +0 -41
- package/concepts/ja/Tool-Skill.md +0 -56
- package/concepts/ja/Tool-Task.md +0 -51
- package/concepts/ja/Tool-TaskCreate.md +0 -78
- package/concepts/ja/Tool-TaskGet.md +0 -56
- package/concepts/ja/Tool-TaskList.md +0 -56
- package/concepts/ja/Tool-TaskOutput.md +0 -40
- package/concepts/ja/Tool-TaskStop.md +0 -32
- package/concepts/ja/Tool-TaskUpdate.md +0 -123
- package/concepts/ja/Tool-TeamCreate.md +0 -38
- package/concepts/ja/Tool-TeamDelete.md +0 -30
- package/concepts/ja/Tool-WebFetch.md +0 -55
- package/concepts/ja/Tool-WebSearch.md +0 -58
- package/concepts/ja/Tool-Write.md +0 -39
- package/concepts/ja/Tool-executeCode.md +0 -41
- package/concepts/ja/Tool-getDiagnostics.md +0 -32
- package/concepts/ja/Tools.md +0 -67
- package/concepts/ko/CacheRebuild.md +0 -36
- package/concepts/ko/KVCacheContent.md +0 -49
- package/concepts/ko/Tool-Agent.md +0 -125
- package/concepts/ko/Tool-AskUserQuestion.md +0 -75
- package/concepts/ko/Tool-Bash.md +0 -167
- package/concepts/ko/Tool-Edit.md +0 -46
- package/concepts/ko/Tool-EnterPlanMode.md +0 -128
- package/concepts/ko/Tool-EnterWorktree.md +0 -56
- package/concepts/ko/Tool-ExitPlanMode.md +0 -62
- package/concepts/ko/Tool-Glob.md +0 -39
- package/concepts/ko/Tool-Grep.md +0 -57
- package/concepts/ko/Tool-NotebookEdit.md +0 -33
- package/concepts/ko/Tool-Read.md +0 -56
- package/concepts/ko/Tool-SendMessage.md +0 -41
- package/concepts/ko/Tool-Skill.md +0 -56
- package/concepts/ko/Tool-Task.md +0 -51
- package/concepts/ko/Tool-TaskCreate.md +0 -78
- package/concepts/ko/Tool-TaskGet.md +0 -56
- package/concepts/ko/Tool-TaskList.md +0 -56
- package/concepts/ko/Tool-TaskOutput.md +0 -40
- package/concepts/ko/Tool-TaskStop.md +0 -32
- package/concepts/ko/Tool-TaskUpdate.md +0 -123
- package/concepts/ko/Tool-TeamCreate.md +0 -38
- package/concepts/ko/Tool-TeamDelete.md +0 -30
- package/concepts/ko/Tool-WebFetch.md +0 -55
- package/concepts/ko/Tool-WebSearch.md +0 -58
- package/concepts/ko/Tool-Write.md +0 -39
- package/concepts/ko/Tool-executeCode.md +0 -41
- package/concepts/ko/Tool-getDiagnostics.md +0 -32
- package/concepts/ko/Tools.md +0 -67
- package/concepts/no/CacheRebuild.md +0 -36
- package/concepts/no/KVCacheContent.md +0 -49
- package/concepts/no/Tool-Agent.md +0 -125
- package/concepts/no/Tool-AskUserQuestion.md +0 -75
- package/concepts/no/Tool-Bash.md +0 -167
- package/concepts/no/Tool-Edit.md +0 -46
- package/concepts/no/Tool-EnterPlanMode.md +0 -128
- package/concepts/no/Tool-EnterWorktree.md +0 -56
- package/concepts/no/Tool-ExitPlanMode.md +0 -62
- package/concepts/no/Tool-Glob.md +0 -39
- package/concepts/no/Tool-Grep.md +0 -57
- package/concepts/no/Tool-NotebookEdit.md +0 -33
- package/concepts/no/Tool-Read.md +0 -56
- package/concepts/no/Tool-SendMessage.md +0 -41
- package/concepts/no/Tool-Skill.md +0 -56
- package/concepts/no/Tool-Task.md +0 -51
- package/concepts/no/Tool-TaskCreate.md +0 -78
- package/concepts/no/Tool-TaskGet.md +0 -56
- package/concepts/no/Tool-TaskList.md +0 -56
- package/concepts/no/Tool-TaskOutput.md +0 -40
- package/concepts/no/Tool-TaskStop.md +0 -32
- package/concepts/no/Tool-TaskUpdate.md +0 -123
- package/concepts/no/Tool-TeamCreate.md +0 -38
- package/concepts/no/Tool-TeamDelete.md +0 -30
- package/concepts/no/Tool-WebFetch.md +0 -55
- package/concepts/no/Tool-WebSearch.md +0 -58
- package/concepts/no/Tool-Write.md +0 -39
- package/concepts/no/Tool-executeCode.md +0 -41
- package/concepts/no/Tool-getDiagnostics.md +0 -32
- package/concepts/no/Tools.md +0 -67
- package/concepts/pl/CacheRebuild.md +0 -36
- package/concepts/pl/KVCacheContent.md +0 -49
- package/concepts/pl/Tool-Agent.md +0 -125
- package/concepts/pl/Tool-AskUserQuestion.md +0 -75
- package/concepts/pl/Tool-Bash.md +0 -167
- package/concepts/pl/Tool-Edit.md +0 -46
- package/concepts/pl/Tool-EnterPlanMode.md +0 -128
- package/concepts/pl/Tool-EnterWorktree.md +0 -56
- package/concepts/pl/Tool-ExitPlanMode.md +0 -62
- package/concepts/pl/Tool-Glob.md +0 -39
- package/concepts/pl/Tool-Grep.md +0 -57
- package/concepts/pl/Tool-NotebookEdit.md +0 -33
- package/concepts/pl/Tool-Read.md +0 -56
- package/concepts/pl/Tool-SendMessage.md +0 -41
- package/concepts/pl/Tool-Skill.md +0 -56
- package/concepts/pl/Tool-Task.md +0 -51
- package/concepts/pl/Tool-TaskCreate.md +0 -78
- package/concepts/pl/Tool-TaskGet.md +0 -56
- package/concepts/pl/Tool-TaskList.md +0 -56
- package/concepts/pl/Tool-TaskOutput.md +0 -40
- package/concepts/pl/Tool-TaskStop.md +0 -32
- package/concepts/pl/Tool-TaskUpdate.md +0 -123
- package/concepts/pl/Tool-TeamCreate.md +0 -38
- package/concepts/pl/Tool-TeamDelete.md +0 -30
- package/concepts/pl/Tool-WebFetch.md +0 -55
- package/concepts/pl/Tool-WebSearch.md +0 -58
- package/concepts/pl/Tool-Write.md +0 -39
- package/concepts/pl/Tool-executeCode.md +0 -41
- package/concepts/pl/Tool-getDiagnostics.md +0 -32
- package/concepts/pl/Tools.md +0 -67
- package/concepts/pt-BR/CacheRebuild.md +0 -36
- package/concepts/pt-BR/KVCacheContent.md +0 -49
- package/concepts/pt-BR/Tool-Agent.md +0 -125
- package/concepts/pt-BR/Tool-AskUserQuestion.md +0 -75
- package/concepts/pt-BR/Tool-Bash.md +0 -167
- package/concepts/pt-BR/Tool-Edit.md +0 -46
- package/concepts/pt-BR/Tool-EnterPlanMode.md +0 -128
- package/concepts/pt-BR/Tool-EnterWorktree.md +0 -56
- package/concepts/pt-BR/Tool-ExitPlanMode.md +0 -62
- package/concepts/pt-BR/Tool-Glob.md +0 -39
- package/concepts/pt-BR/Tool-Grep.md +0 -57
- package/concepts/pt-BR/Tool-NotebookEdit.md +0 -33
- package/concepts/pt-BR/Tool-Read.md +0 -56
- package/concepts/pt-BR/Tool-SendMessage.md +0 -41
- package/concepts/pt-BR/Tool-Skill.md +0 -56
- package/concepts/pt-BR/Tool-Task.md +0 -51
- package/concepts/pt-BR/Tool-TaskCreate.md +0 -78
- package/concepts/pt-BR/Tool-TaskGet.md +0 -56
- package/concepts/pt-BR/Tool-TaskList.md +0 -56
- package/concepts/pt-BR/Tool-TaskOutput.md +0 -40
- package/concepts/pt-BR/Tool-TaskStop.md +0 -32
- package/concepts/pt-BR/Tool-TaskUpdate.md +0 -123
- package/concepts/pt-BR/Tool-TeamCreate.md +0 -38
- package/concepts/pt-BR/Tool-TeamDelete.md +0 -30
- package/concepts/pt-BR/Tool-WebFetch.md +0 -55
- package/concepts/pt-BR/Tool-WebSearch.md +0 -58
- package/concepts/pt-BR/Tool-Write.md +0 -39
- package/concepts/pt-BR/Tool-executeCode.md +0 -41
- package/concepts/pt-BR/Tool-getDiagnostics.md +0 -32
- package/concepts/pt-BR/Tools.md +0 -67
- package/concepts/ru/CacheRebuild.md +0 -36
- package/concepts/ru/KVCacheContent.md +0 -49
- package/concepts/ru/Tool-Agent.md +0 -125
- package/concepts/ru/Tool-AskUserQuestion.md +0 -75
- package/concepts/ru/Tool-Bash.md +0 -167
- package/concepts/ru/Tool-Edit.md +0 -46
- package/concepts/ru/Tool-EnterPlanMode.md +0 -128
- package/concepts/ru/Tool-EnterWorktree.md +0 -56
- package/concepts/ru/Tool-ExitPlanMode.md +0 -62
- package/concepts/ru/Tool-Glob.md +0 -39
- package/concepts/ru/Tool-Grep.md +0 -57
- package/concepts/ru/Tool-NotebookEdit.md +0 -33
- package/concepts/ru/Tool-Read.md +0 -56
- package/concepts/ru/Tool-SendMessage.md +0 -41
- package/concepts/ru/Tool-Skill.md +0 -56
- package/concepts/ru/Tool-Task.md +0 -51
- package/concepts/ru/Tool-TaskCreate.md +0 -78
- package/concepts/ru/Tool-TaskGet.md +0 -56
- package/concepts/ru/Tool-TaskList.md +0 -56
- package/concepts/ru/Tool-TaskOutput.md +0 -40
- package/concepts/ru/Tool-TaskStop.md +0 -32
- package/concepts/ru/Tool-TaskUpdate.md +0 -123
- package/concepts/ru/Tool-TeamCreate.md +0 -38
- package/concepts/ru/Tool-TeamDelete.md +0 -30
- package/concepts/ru/Tool-WebFetch.md +0 -55
- package/concepts/ru/Tool-WebSearch.md +0 -58
- package/concepts/ru/Tool-Write.md +0 -39
- package/concepts/ru/Tool-executeCode.md +0 -41
- package/concepts/ru/Tool-getDiagnostics.md +0 -32
- package/concepts/ru/Tools.md +0 -67
- package/concepts/th/CacheRebuild.md +0 -36
- package/concepts/th/KVCacheContent.md +0 -49
- package/concepts/th/Tool-Agent.md +0 -125
- package/concepts/th/Tool-AskUserQuestion.md +0 -75
- package/concepts/th/Tool-Bash.md +0 -167
- package/concepts/th/Tool-Edit.md +0 -46
- package/concepts/th/Tool-EnterPlanMode.md +0 -128
- package/concepts/th/Tool-EnterWorktree.md +0 -56
- package/concepts/th/Tool-ExitPlanMode.md +0 -62
- package/concepts/th/Tool-Glob.md +0 -39
- package/concepts/th/Tool-Grep.md +0 -57
- package/concepts/th/Tool-NotebookEdit.md +0 -33
- package/concepts/th/Tool-Read.md +0 -56
- package/concepts/th/Tool-SendMessage.md +0 -41
- package/concepts/th/Tool-Skill.md +0 -56
- package/concepts/th/Tool-Task.md +0 -51
- package/concepts/th/Tool-TaskCreate.md +0 -78
- package/concepts/th/Tool-TaskGet.md +0 -56
- package/concepts/th/Tool-TaskList.md +0 -56
- package/concepts/th/Tool-TaskOutput.md +0 -40
- package/concepts/th/Tool-TaskStop.md +0 -32
- package/concepts/th/Tool-TaskUpdate.md +0 -123
- package/concepts/th/Tool-TeamCreate.md +0 -38
- package/concepts/th/Tool-TeamDelete.md +0 -30
- package/concepts/th/Tool-WebFetch.md +0 -55
- package/concepts/th/Tool-WebSearch.md +0 -58
- package/concepts/th/Tool-Write.md +0 -39
- package/concepts/th/Tool-executeCode.md +0 -41
- package/concepts/th/Tool-getDiagnostics.md +0 -32
- package/concepts/th/Tools.md +0 -67
- package/concepts/tr/CacheRebuild.md +0 -36
- package/concepts/tr/KVCacheContent.md +0 -49
- package/concepts/tr/Tool-Agent.md +0 -125
- package/concepts/tr/Tool-AskUserQuestion.md +0 -75
- package/concepts/tr/Tool-Bash.md +0 -167
- package/concepts/tr/Tool-Edit.md +0 -46
- package/concepts/tr/Tool-EnterPlanMode.md +0 -128
- package/concepts/tr/Tool-EnterWorktree.md +0 -56
- package/concepts/tr/Tool-ExitPlanMode.md +0 -62
- package/concepts/tr/Tool-Glob.md +0 -39
- package/concepts/tr/Tool-Grep.md +0 -57
- package/concepts/tr/Tool-NotebookEdit.md +0 -33
- package/concepts/tr/Tool-Read.md +0 -56
- package/concepts/tr/Tool-SendMessage.md +0 -41
- package/concepts/tr/Tool-Skill.md +0 -56
- package/concepts/tr/Tool-Task.md +0 -51
- package/concepts/tr/Tool-TaskCreate.md +0 -78
- package/concepts/tr/Tool-TaskGet.md +0 -56
- package/concepts/tr/Tool-TaskList.md +0 -56
- package/concepts/tr/Tool-TaskOutput.md +0 -40
- package/concepts/tr/Tool-TaskStop.md +0 -32
- package/concepts/tr/Tool-TaskUpdate.md +0 -123
- package/concepts/tr/Tool-TeamCreate.md +0 -38
- package/concepts/tr/Tool-TeamDelete.md +0 -30
- package/concepts/tr/Tool-WebFetch.md +0 -55
- package/concepts/tr/Tool-WebSearch.md +0 -58
- package/concepts/tr/Tool-Write.md +0 -39
- package/concepts/tr/Tool-executeCode.md +0 -41
- package/concepts/tr/Tool-getDiagnostics.md +0 -32
- package/concepts/tr/Tools.md +0 -67
- package/concepts/uk/CacheRebuild.md +0 -36
- package/concepts/uk/KVCacheContent.md +0 -49
- package/concepts/uk/Tool-Agent.md +0 -125
- package/concepts/uk/Tool-AskUserQuestion.md +0 -75
- package/concepts/uk/Tool-Bash.md +0 -167
- package/concepts/uk/Tool-Edit.md +0 -46
- package/concepts/uk/Tool-EnterPlanMode.md +0 -128
- package/concepts/uk/Tool-EnterWorktree.md +0 -56
- package/concepts/uk/Tool-ExitPlanMode.md +0 -62
- package/concepts/uk/Tool-Glob.md +0 -39
- package/concepts/uk/Tool-Grep.md +0 -57
- package/concepts/uk/Tool-NotebookEdit.md +0 -33
- package/concepts/uk/Tool-Read.md +0 -56
- package/concepts/uk/Tool-SendMessage.md +0 -41
- package/concepts/uk/Tool-Skill.md +0 -56
- package/concepts/uk/Tool-Task.md +0 -51
- package/concepts/uk/Tool-TaskCreate.md +0 -78
- package/concepts/uk/Tool-TaskGet.md +0 -56
- package/concepts/uk/Tool-TaskList.md +0 -56
- package/concepts/uk/Tool-TaskOutput.md +0 -40
- package/concepts/uk/Tool-TaskStop.md +0 -32
- package/concepts/uk/Tool-TaskUpdate.md +0 -123
- package/concepts/uk/Tool-TeamCreate.md +0 -38
- package/concepts/uk/Tool-TeamDelete.md +0 -30
- package/concepts/uk/Tool-WebFetch.md +0 -55
- package/concepts/uk/Tool-WebSearch.md +0 -58
- package/concepts/uk/Tool-Write.md +0 -39
- package/concepts/uk/Tool-executeCode.md +0 -41
- package/concepts/uk/Tool-getDiagnostics.md +0 -32
- package/concepts/uk/Tools.md +0 -67
- package/concepts/zh/CacheRebuild.md +0 -36
- package/concepts/zh/KVCacheContent.md +0 -49
- package/concepts/zh/Tool-Agent.md +0 -125
- package/concepts/zh/Tool-AskUserQuestion.md +0 -75
- package/concepts/zh/Tool-Bash.md +0 -167
- package/concepts/zh/Tool-Edit.md +0 -46
- package/concepts/zh/Tool-EnterPlanMode.md +0 -128
- package/concepts/zh/Tool-EnterWorktree.md +0 -56
- package/concepts/zh/Tool-ExitPlanMode.md +0 -62
- package/concepts/zh/Tool-Glob.md +0 -39
- package/concepts/zh/Tool-Grep.md +0 -57
- package/concepts/zh/Tool-NotebookEdit.md +0 -33
- package/concepts/zh/Tool-Read.md +0 -56
- package/concepts/zh/Tool-SendMessage.md +0 -41
- package/concepts/zh/Tool-Skill.md +0 -56
- package/concepts/zh/Tool-Task.md +0 -51
- package/concepts/zh/Tool-TaskCreate.md +0 -78
- package/concepts/zh/Tool-TaskGet.md +0 -56
- package/concepts/zh/Tool-TaskList.md +0 -56
- package/concepts/zh/Tool-TaskOutput.md +0 -40
- package/concepts/zh/Tool-TaskStop.md +0 -32
- package/concepts/zh/Tool-TaskUpdate.md +0 -123
- package/concepts/zh/Tool-TeamCreate.md +0 -38
- package/concepts/zh/Tool-TeamDelete.md +0 -30
- package/concepts/zh/Tool-WebFetch.md +0 -55
- package/concepts/zh/Tool-WebSearch.md +0 -58
- package/concepts/zh/Tool-Write.md +0 -39
- package/concepts/zh/Tool-executeCode.md +0 -41
- package/concepts/zh/Tool-getDiagnostics.md +0 -32
- package/concepts/zh-TW/CacheRebuild.md +0 -36
- package/concepts/zh-TW/KVCacheContent.md +0 -49
- package/concepts/zh-TW/Tool-Agent.md +0 -125
- package/concepts/zh-TW/Tool-AskUserQuestion.md +0 -75
- package/concepts/zh-TW/Tool-Bash.md +0 -167
- package/concepts/zh-TW/Tool-Edit.md +0 -46
- package/concepts/zh-TW/Tool-EnterPlanMode.md +0 -128
- package/concepts/zh-TW/Tool-EnterWorktree.md +0 -56
- package/concepts/zh-TW/Tool-ExitPlanMode.md +0 -62
- package/concepts/zh-TW/Tool-Glob.md +0 -39
- package/concepts/zh-TW/Tool-Grep.md +0 -57
- package/concepts/zh-TW/Tool-NotebookEdit.md +0 -33
- package/concepts/zh-TW/Tool-Read.md +0 -56
- package/concepts/zh-TW/Tool-SendMessage.md +0 -41
- package/concepts/zh-TW/Tool-Skill.md +0 -56
- package/concepts/zh-TW/Tool-Task.md +0 -51
- package/concepts/zh-TW/Tool-TaskCreate.md +0 -78
- package/concepts/zh-TW/Tool-TaskGet.md +0 -56
- package/concepts/zh-TW/Tool-TaskList.md +0 -56
- package/concepts/zh-TW/Tool-TaskOutput.md +0 -40
- package/concepts/zh-TW/Tool-TaskStop.md +0 -32
- package/concepts/zh-TW/Tool-TaskUpdate.md +0 -123
- package/concepts/zh-TW/Tool-TeamCreate.md +0 -38
- package/concepts/zh-TW/Tool-TeamDelete.md +0 -30
- package/concepts/zh-TW/Tool-WebFetch.md +0 -55
- package/concepts/zh-TW/Tool-WebSearch.md +0 -58
- package/concepts/zh-TW/Tool-Write.md +0 -39
- package/concepts/zh-TW/Tool-executeCode.md +0 -41
- package/concepts/zh-TW/Tool-getDiagnostics.md +0 -32
- package/concepts/zh-TW/Tools.md +0 -67
- package/dist/assets/App-BN2YmKSu.js +0 -41
- package/dist/assets/App-fG2r3jFq.css +0 -1
- package/dist/assets/AppHeader-BqUGBbBx.css +0 -41
- package/dist/assets/AppHeader.module-CvH9GQQ2.js +0 -1250
- package/dist/assets/Mobile-BnDkLcBv.css +0 -1
- package/dist/assets/Mobile-CbC8YD3O.js +0 -10
- package/dist/assets/_baseUniq-B2ddYY2A.js +0 -1
- package/dist/assets/arc-DFPOHgcd.js +0 -1
- package/dist/assets/architectureDiagram-Q4EWVU46-1tuXixQv.js +0 -36
- package/dist/assets/blockDiagram-DXYQGD6D-BfVKQPAV.js +0 -132
- package/dist/assets/c4Diagram-AHTNJAMY-Bua8O4-1.js +0 -10
- package/dist/assets/channel-Cf9uPxYW.js +0 -1
- package/dist/assets/chunk-4BX2VUAB-YOIkSNn_.js +0 -1
- package/dist/assets/chunk-4TB4RGXK-CjZt-To5.js +0 -206
- package/dist/assets/chunk-55IACEB6-BAEfMF5S.js +0 -1
- package/dist/assets/chunk-EDXVE4YY-BUYDmLXU.js +0 -1
- package/dist/assets/chunk-FMBD7UC4-Cw_LDap-.js +0 -15
- package/dist/assets/chunk-OYMX7WX6-PlfZFFxd.js +0 -231
- package/dist/assets/chunk-QZHKN3VN-C2ReFZl_.js +0 -1
- package/dist/assets/chunk-YZCP3GAM-B7IVGkPB.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-DdwVeNLZ.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-DdwVeNLZ.js +0 -1
- package/dist/assets/clone-C8Lz0121.js +0 -1
- package/dist/assets/cose-bilkent-S5V4N54A-M6rBKf0y.js +0 -1
- package/dist/assets/cytoscape.esm-DxGcaOPV.js +0 -331
- package/dist/assets/dagre-KV5264BT-UPacTWnL.js +0 -4
- package/dist/assets/defaultLocale-DX6XiGOO.js +0 -1
- package/dist/assets/diagram-5BDNPKRD-Cjjtnh0e.js +0 -10
- package/dist/assets/diagram-G4DWMVQ6-CjXILMP0.js +0 -24
- package/dist/assets/diagram-MMDJMWI5-C_-XZ9gc.js +0 -43
- package/dist/assets/diagram-TYMM5635-Dxe2GxUR.js +0 -24
- package/dist/assets/erDiagram-SMLLAGMA-1w-wtgMd.js +0 -85
- package/dist/assets/flowDiagram-DWJPFMVM-B9naMGCj.js +0 -162
- package/dist/assets/ganttDiagram-T4ZO3ILL-BPZBdhPv.js +0 -292
- package/dist/assets/gitGraphDiagram-UUTBAWPF-_P9PdiGG.js +0 -106
- package/dist/assets/graph-CjOY8lRz.js +0 -1
- package/dist/assets/html2canvas.esm-QH1iLAAe.js +0 -22
- package/dist/assets/index-BcCW9op4.js +0 -41
- package/dist/assets/index-DCtZ2KSX.css +0 -1
- package/dist/assets/infoDiagram-42DDH7IO-Ch8SOTtq.js +0 -2
- package/dist/assets/init-Gi6I4Gst.js +0 -1
- package/dist/assets/ishikawaDiagram-UXIWVN3A-XLscQrJT.js +0 -70
- package/dist/assets/journeyDiagram-VCZTEJTY-C3FQ_QPk.js +0 -139
- package/dist/assets/kanban-definition-6JOO6SKY-DIQDTOi3.js +0 -89
- package/dist/assets/katex-DkKDou_j.js +0 -257
- package/dist/assets/layout-DO9W0X_b.js +0 -1
- package/dist/assets/linear-ClCJqwn1.js +0 -1
- package/dist/assets/mermaid.core-BW02B1TS.js +0 -308
- package/dist/assets/min-BuzClhio.js +0 -1
- package/dist/assets/mindmap-definition-QFDTVHPH-DuCaTeZu.js +0 -96
- package/dist/assets/ordinal-Cboi1Yqb.js +0 -1
- package/dist/assets/pieDiagram-DEJITSTG-BZoZ6CI1.js +0 -30
- package/dist/assets/quadrantDiagram-34T5L4WZ-DFNwvtmc.js +0 -7
- package/dist/assets/requirementDiagram-MS252O5E-DPG-7raV.js +0 -84
- package/dist/assets/sankeyDiagram-XADWPNL6-Bp92e1ie.js +0 -10
- package/dist/assets/sequenceDiagram-FGHM5R23-C6ADwqJY.js +0 -157
- package/dist/assets/stateDiagram-FHFEXIEX-BfrwZGmF.js +0 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-4gBW7EPI.js +0 -1
- package/dist/assets/timeline-definition-GMOUNBTQ-Bb-TxYx2.js +0 -120
- package/dist/assets/vennDiagram-DHZGUBPP-DTRiNXJu.js +0 -34
- package/dist/assets/wardley-RL74JXVD-C8j_kjXS.js +0 -162
- package/dist/assets/wardleyDiagram-NUSXRM2D-CvICMc3N.js +0 -20
- package/dist/assets/xychartDiagram-5P7HB3ND-DI0bBGwq.js +0 -7
- package/lib/kv-cache-analyzer.js +0 -164
|
@@ -0,0 +1,2021 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// 主 terminal 组件 —— 渲染 Codex TUI 的"大 terminal"
|
|
3
|
+
// 工具栏下方的"小/scratch terminal"是另外一个独立组件,见 ScratchTerminal.jsx
|
|
4
|
+
// CSS:主 terminal 用 .terminalContainer + .terminalHost;scratch 用 .scratchInner + .scratchHost
|
|
5
|
+
// ============================================================================
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { message, Tooltip, Popover, Popconfirm, Button, Checkbox, Modal } from 'antd';
|
|
8
|
+
import { Terminal } from '@xterm/xterm';
|
|
9
|
+
import { FitAddon } from '@xterm/addon-fit';
|
|
10
|
+
import { WebglAddon } from '@xterm/addon-webgl';
|
|
11
|
+
import { WebLinksAddon } from '@xterm/addon-web-links';
|
|
12
|
+
import { Unicode11Addon } from '@xterm/addon-unicode11';
|
|
13
|
+
import '@xterm/xterm/css/xterm.css';
|
|
14
|
+
import { t } from '../../i18n';
|
|
15
|
+
import { TerminalWsContext } from './TerminalWsContext';
|
|
16
|
+
import { apiUrl } from '../../utils/apiUrl';
|
|
17
|
+
import { isMobile, isIOS, isAndroid, isPad, isWindows, isMac } from '../../env';
|
|
18
|
+
import styles from './TerminalPanel.module.css';
|
|
19
|
+
import { BUILTIN_PRESETS } from '../../utils/builtinPresets.js';
|
|
20
|
+
import { buildLocalUltraplan } from '../../utils/ultraplanTemplates';
|
|
21
|
+
import { UltraplanController } from '../../utils/ultraplanController';
|
|
22
|
+
import { buildBracketPasteSubmitChunks, BRACKET_PASTE_SUBMIT_SETTLE_MS, sanitizeBracketPasteText } from '../../utils/ptyChunkBuilder';
|
|
23
|
+
import { clipboardKeyAction, copyTextToClipboard, planPasteSend } from '../../utils/terminalClipboard';
|
|
24
|
+
import CustomUltraplanEditModal from './CustomUltraplanEditModal';
|
|
25
|
+
import UltraplanExpertManagerModal from './UltraplanExpertManagerModal';
|
|
26
|
+
import { visibleExpertKeys } from '../../utils/ultraplanExperts';
|
|
27
|
+
import { TerminalWriteQueue, INBAND_RESET } from '../../utils/terminalWriteQueue';
|
|
28
|
+
import { installTermDiag, diagCount } from '../../utils/termDiag';
|
|
29
|
+
import { downscaleForRetina } from '../../utils/imageDownscale';
|
|
30
|
+
import { buildPresetShortcutsPayload } from '../../utils/presetShortcuts';
|
|
31
|
+
import ImageLightbox from '../common/ImageLightbox';
|
|
32
|
+
import ConfirmRemoveButton from '../common/ConfirmRemoveButton';
|
|
33
|
+
import ScratchTerminal from './ScratchTerminal';
|
|
34
|
+
import { darkTerminalTheme, lightTerminalTheme, terminalFontFamily } from './terminalThemes';
|
|
35
|
+
import { resizeImageIfNeeded } from '../../utils/imageResize';
|
|
36
|
+
import UltraplanPanel, { readUltraplanPopoverSize, ultraplanOverlayInnerStyle } from './UltraplanPanel';
|
|
37
|
+
import { AgentTeamIcon, UploadIcon, TrashIcon, SPARKLE_MASK_STYLE, ULTRAPLAN_MASK_STYLE } from '../common/quickMenuIcons';
|
|
38
|
+
import QuickAutoApproveRows from '../common/QuickAutoApproveRows';
|
|
39
|
+
import { createQuickMenuHoverIntent } from '../../utils/quickMenuHoverIntent';
|
|
40
|
+
import chrome from '../common/sharedChrome.module.css';
|
|
41
|
+
|
|
42
|
+
// WebGL longtask 自动降级常量
|
|
43
|
+
const WEBGL_STALL_MS = 200; // 单次 longtask 阈值 (ms)
|
|
44
|
+
const WEBGL_STALL_COUNT = 3; // 触发降级的 stall 次数
|
|
45
|
+
const WEBGL_STALL_WINDOW_MS = 30000; // 滑动窗口 (ms)
|
|
46
|
+
const WEBGL_GRACE_MS = 5000; // 初始化宽限期 (ms)
|
|
47
|
+
const WEBGL_STICKY_KEY = 'cxv_webgl_disabled_until';
|
|
48
|
+
const WEBGL_STICKY_TTL_MS = 7 * 24 * 3600 * 1000; // 7 天后自动重试
|
|
49
|
+
|
|
50
|
+
// 渲染器平台策略:Android(移动端 DOM 卡顿明显)无条件 WebGL;macOS 桌面在 longtask 守卫
|
|
51
|
+
// 可用时启用 WebGL(能力门:Safari 无 'longtask' PerformanceObserver,守卫失效则不冒险,
|
|
52
|
+
// 留 DOM;Chrome/Edge/Electron 带完整降级安全网)。Windows/Linux 维持 DOM(GPU 长任务/
|
|
53
|
+
// 花屏实测多发)。isIOS 排除 iPadOS(iPad 桌面 UA 的 navigator.platform 是 MacIntel,
|
|
54
|
+
// 会误命中 isMac)。
|
|
55
|
+
const WEBGL_RENDERER = isAndroid || (isMac && !isIOS
|
|
56
|
+
&& typeof PerformanceObserver !== 'undefined'
|
|
57
|
+
&& !!PerformanceObserver.supportedEntryTypes?.includes('longtask'));
|
|
58
|
+
|
|
59
|
+
const SCRATCH_OPEN_KEY = 'cx-viewer-scratch-open';
|
|
60
|
+
const SCRATCH_HEIGHT_KEY = 'cx-viewer-scratch-height';
|
|
61
|
+
const SCRATCH_TABS_KEY = 'cx-viewer-scratch-tabs';
|
|
62
|
+
const SCRATCH_ACTIVE_TAB_KEY = 'cx-viewer-scratch-active-tab';
|
|
63
|
+
// 注:.scratchWrap 用 outline + outline-offset:-4px 画 focus 环(不占布局),存储/clamp 的高度
|
|
64
|
+
// 即可见高度本身,不再被边框吞噬;fitAddon 自动 refit,与历史 session 存储值兼容。
|
|
65
|
+
const SCRATCH_HEIGHT_MIN = 100;
|
|
66
|
+
const SCRATCH_HEIGHT_MAX = 600;
|
|
67
|
+
const SCRATCH_HEIGHT_DEFAULT = 200;
|
|
68
|
+
const SCRATCH_TAB_ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
69
|
+
const SCRATCH_TAB_MAX = 8;
|
|
70
|
+
|
|
71
|
+
function readScratchOpen() {
|
|
72
|
+
try { return localStorage.getItem(SCRATCH_OPEN_KEY) === 'true'; } catch { return false; }
|
|
73
|
+
}
|
|
74
|
+
function readScratchHeight() {
|
|
75
|
+
try {
|
|
76
|
+
const v = parseInt(localStorage.getItem(SCRATCH_HEIGHT_KEY), 10);
|
|
77
|
+
if (!Number.isFinite(v)) return SCRATCH_HEIGHT_DEFAULT;
|
|
78
|
+
return Math.max(SCRATCH_HEIGHT_MIN, Math.min(SCRATCH_HEIGHT_MAX, v));
|
|
79
|
+
} catch { return SCRATCH_HEIGHT_DEFAULT; }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function genScratchTabId() {
|
|
83
|
+
// 与服务端 SCRATCH_ID_RE `/^[A-Za-z0-9_-]{1,64}$/` 兼容
|
|
84
|
+
const rand = (typeof crypto !== 'undefined' && crypto.randomUUID)
|
|
85
|
+
? crypto.randomUUID().replace(/-/g, '')
|
|
86
|
+
: Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
87
|
+
return ('t-' + rand).slice(0, 64);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function readScratchTabs() {
|
|
91
|
+
try {
|
|
92
|
+
const raw = localStorage.getItem(SCRATCH_TABS_KEY);
|
|
93
|
+
if (!raw) return [];
|
|
94
|
+
const arr = JSON.parse(raw);
|
|
95
|
+
if (!Array.isArray(arr)) return [];
|
|
96
|
+
const out = [];
|
|
97
|
+
for (const t of arr) {
|
|
98
|
+
if (t && typeof t.id === 'string' && SCRATCH_TAB_ID_RE.test(t.id)) {
|
|
99
|
+
out.push({ id: t.id });
|
|
100
|
+
if (out.length >= SCRATCH_TAB_MAX) break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
} catch { return []; }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function readScratchActiveTab(tabs) {
|
|
108
|
+
try {
|
|
109
|
+
const v = localStorage.getItem(SCRATCH_ACTIVE_TAB_KEY);
|
|
110
|
+
if (v && tabs.some(t => t.id === v)) return v;
|
|
111
|
+
} catch {}
|
|
112
|
+
return tabs[0]?.id ?? '';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function writeScratchTabs(tabs) {
|
|
116
|
+
try { localStorage.setItem(SCRATCH_TABS_KEY, JSON.stringify(tabs)); } catch {}
|
|
117
|
+
}
|
|
118
|
+
function writeScratchActiveTab(id) {
|
|
119
|
+
try { localStorage.setItem(SCRATCH_ACTIVE_TAB_KEY, id); } catch {}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 真实 $SHELL basename 由后端 WS state 消息上报后填进 state.scratchShellBasename,
|
|
123
|
+
// 在拿到之前用 'zsh' 作为占位(macOS 默认 shell;新 server 到达 state 后会按真实 basename 覆盖)
|
|
124
|
+
|
|
125
|
+
function PlusIcon() {
|
|
126
|
+
return (
|
|
127
|
+
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
128
|
+
<line x1="12" y1="5" x2="12" y2="19" />
|
|
129
|
+
<line x1="5" y1="12" x2="19" y2="12" />
|
|
130
|
+
</svg>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// SparkleIcon / ShieldCheckIcon / PlanClipboardIcon / AgentTeamIcon / UltraplanIcon /
|
|
135
|
+
// UploadIcon / TrashIcon 已迁至 ../common/quickMenuIcons.jsx(与 ChatInputBar 四芒星菜单共用)
|
|
136
|
+
|
|
137
|
+
function CloseIcon() {
|
|
138
|
+
return (
|
|
139
|
+
<svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
140
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
141
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
142
|
+
</svg>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function ScratchTerminalIcon() {
|
|
147
|
+
return (
|
|
148
|
+
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
149
|
+
<rect x="3" y="4" width="18" height="16" rx="2" />
|
|
150
|
+
<polyline points="7 9 10 12 7 15" />
|
|
151
|
+
<line x1="13" y1="15" x2="17" y2="15" />
|
|
152
|
+
</svg>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 虚拟按键定义:label 显示文字,seq 为发送到终端的转义序列
|
|
157
|
+
const VIRTUAL_KEYS = [
|
|
158
|
+
{ label: '↑', seq: '\x1b[A' },
|
|
159
|
+
{ label: '↓', seq: '\x1b[B' },
|
|
160
|
+
{ label: '←', seq: '\x1b[D' },
|
|
161
|
+
{ label: '→', seq: '\x1b[C' },
|
|
162
|
+
{ label: 'Enter', seq: '\r' },
|
|
163
|
+
{ label: 'Tab', seq: '\t' },
|
|
164
|
+
{ label: 'Esc', seq: '\x1b' },
|
|
165
|
+
{ label: 'Ctrl+C', seq: '\x03' },
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
export async function uploadFileAndGetPath(file) {
|
|
169
|
+
const MAX_SIZE = 100 * 1024 * 1024; // 100MB
|
|
170
|
+
let upload = file;
|
|
171
|
+
// 图片压缩失败直接用原文件,保证上传流程不中断
|
|
172
|
+
try { upload = await resizeImageIfNeeded(file, 2000); } catch { upload = file; }
|
|
173
|
+
if (upload.size > MAX_SIZE) throw new Error('File too large (max 100MB)');
|
|
174
|
+
const form = new FormData();
|
|
175
|
+
form.append('file', upload);
|
|
176
|
+
const res = await fetch(apiUrl('/api/upload'), { method: 'POST', body: form });
|
|
177
|
+
const data = await res.json();
|
|
178
|
+
if (!data.ok) throw new Error(data.error || 'Upload failed');
|
|
179
|
+
return data.path;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
class TerminalPanel extends React.Component {
|
|
183
|
+
// 通过 Context 共享 App 层的单条 /ws/terminal,this.context = { send, isOpen, addMessageHandler, addStateListener }
|
|
184
|
+
static contextType = TerminalWsContext;
|
|
185
|
+
|
|
186
|
+
// 兼容 stub:同 ChatView,getter 模拟旧 this.ws 的 send/readyState API → 映射到 context。
|
|
187
|
+
// 这样所有 `this.ws.send(JSON.stringify(...))` 和 `this.ws.readyState === WebSocket.OPEN` 不用改。
|
|
188
|
+
get ws() {
|
|
189
|
+
const ctx = this.context;
|
|
190
|
+
if (!ctx || typeof ctx.send !== 'function') return null;
|
|
191
|
+
return {
|
|
192
|
+
get readyState() { return ctx.isOpen && ctx.isOpen() ? WebSocket.OPEN : WebSocket.CLOSED; },
|
|
193
|
+
send: (s) => {
|
|
194
|
+
let obj;
|
|
195
|
+
try { obj = JSON.parse(s); } catch { return false; }
|
|
196
|
+
return ctx.send(obj);
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
constructor(props) {
|
|
202
|
+
super(props);
|
|
203
|
+
this.containerRef = React.createRef();
|
|
204
|
+
this.fileInputRef = React.createRef();
|
|
205
|
+
// UltraPlan 文件 / 自定义专家纯逻辑(与 ChatView 共享,见 ../utils/ultraplanController)。
|
|
206
|
+
// host 适配器桥接 state / 上传 / 提示 / 偏好 / 关闭编辑器;下方 6 个方法退化为委托。
|
|
207
|
+
this._ultraplan = new UltraplanController({
|
|
208
|
+
getState: () => this.state,
|
|
209
|
+
setState: (updater) => this.setState(updater),
|
|
210
|
+
onUpdatePreferences: (p) => this.props.onUpdatePreferences?.(p),
|
|
211
|
+
uploadFile: (file) => uploadFileAndGetPath(file),
|
|
212
|
+
messageError: (msg) => message.error(msg),
|
|
213
|
+
closeEditor: () => this.closeCustomUltraplanEditor(),
|
|
214
|
+
});
|
|
215
|
+
this.terminal = null;
|
|
216
|
+
this.fitAddon = null;
|
|
217
|
+
// ws 现在是 getter(挂在原型),不在 constructor 上设字段,避免覆盖 getter
|
|
218
|
+
this._unsubWsHandler = null;
|
|
219
|
+
this._unsubWsState = null;
|
|
220
|
+
this.resizeObserver = null;
|
|
221
|
+
this.state = {
|
|
222
|
+
terminalFocused: false,
|
|
223
|
+
quickSettingsOpen: false,
|
|
224
|
+
quickSettingsExpanded: null, // null | 'agentteam' | 'perm' | 'plan' —— 同时只展开一个子菜单
|
|
225
|
+
|
|
226
|
+
ultraplanOpen: false,
|
|
227
|
+
ultraplanVariant: 'codeExpert',
|
|
228
|
+
ultraplanPrompt: '',
|
|
229
|
+
ultraplanFiles: [],
|
|
230
|
+
// PC UltraPlan popover 拖拽尺寸,持久化到 localStorage 两 key;手机/iPad 不走这条路径
|
|
231
|
+
// (那边用 UltraPlanModal),所以无 gate,但 popover 入口本身 isMobile 不显示终端工具栏。
|
|
232
|
+
ultraplanPopoverSize: readUltraplanPopoverSize(),
|
|
233
|
+
customUltraplanExperts: [],
|
|
234
|
+
// 专家显隐 / 排序(管理弹窗)。键 = 'codeExpert' / 'researchExpert' / 'custom:'+id;
|
|
235
|
+
// 落服务端 preferences(ultraplanExpertOrder / ultraplanExpertHidden),缺省=自然序、全可见。
|
|
236
|
+
ultraplanExpertOrder: [],
|
|
237
|
+
ultraplanExpertHidden: [],
|
|
238
|
+
ultraplanManagerOpen: false,
|
|
239
|
+
customUltraplanEditOpen: false,
|
|
240
|
+
customUltraplanEditing: null,
|
|
241
|
+
presetModalVisible: false,
|
|
242
|
+
presetItems: [],
|
|
243
|
+
presetSelected: new Set(),
|
|
244
|
+
presetAddVisible: false,
|
|
245
|
+
presetAddText: '',
|
|
246
|
+
presetAddName: '',
|
|
247
|
+
presetEditId: null,
|
|
248
|
+
lightbox: null,
|
|
249
|
+
ultraplanLightbox: null,
|
|
250
|
+
ultraplanConfirming: false,
|
|
251
|
+
scratchOpen: readScratchOpen(),
|
|
252
|
+
scratchHeight: readScratchHeight(),
|
|
253
|
+
isDraggingScratch: false,
|
|
254
|
+
scratchFocused: false,
|
|
255
|
+
scratchTabs: (() => {
|
|
256
|
+
const t = readScratchTabs();
|
|
257
|
+
return t.length > 0 ? t : [{ id: genScratchTabId() }];
|
|
258
|
+
})(),
|
|
259
|
+
activeScratchTabId: '',
|
|
260
|
+
scratchShellBasename: '',
|
|
261
|
+
};
|
|
262
|
+
// 持久化 active id(先用 readScratchActiveTab 选;下面 mount 后再 sync 到 localStorage)
|
|
263
|
+
this.state.activeScratchTabId = readScratchActiveTab(this.state.scratchTabs);
|
|
264
|
+
this._scratchWrapRef = React.createRef();
|
|
265
|
+
this._scratchRefs = new Map(); // id -> React.createRef()
|
|
266
|
+
this._scratchDragging = false;
|
|
267
|
+
this._scratchDragLastH = null;
|
|
268
|
+
this._scratchPointerId = null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
_getScratchRef(id) {
|
|
272
|
+
let ref = this._scratchRefs.get(id);
|
|
273
|
+
if (!ref) {
|
|
274
|
+
ref = React.createRef();
|
|
275
|
+
this._scratchRefs.set(id, ref);
|
|
276
|
+
}
|
|
277
|
+
return ref;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
handleScratchTabClick = (id) => {
|
|
281
|
+
if (id === this.state.activeScratchTabId) return;
|
|
282
|
+
this.setState({ activeScratchTabId: id }, () => {
|
|
283
|
+
writeScratchActiveTab(id);
|
|
284
|
+
const r = this._scratchRefs.get(id);
|
|
285
|
+
if (r?.current) {
|
|
286
|
+
r.current.refit();
|
|
287
|
+
r.current.focus();
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
handleScratchTabAdd = () => {
|
|
293
|
+
if (this.state.scratchTabs.length >= SCRATCH_TAB_MAX) return;
|
|
294
|
+
const newId = genScratchTabId();
|
|
295
|
+
const tabs = [...this.state.scratchTabs, { id: newId }];
|
|
296
|
+
this.setState({ scratchTabs: tabs, activeScratchTabId: newId }, () => {
|
|
297
|
+
writeScratchTabs(tabs);
|
|
298
|
+
writeScratchActiveTab(newId);
|
|
299
|
+
// 等下一帧 ScratchTerminal mount + 显示后再 refit/focus
|
|
300
|
+
Promise.resolve().then(() => {
|
|
301
|
+
const r = this._scratchRefs.get(newId);
|
|
302
|
+
r?.current?.refit();
|
|
303
|
+
r?.current?.focus();
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
handleScratchTabClose = (id, e) => {
|
|
309
|
+
if (e) { e.stopPropagation(); e.preventDefault(); }
|
|
310
|
+
if (this.state.scratchTabs.length <= 1) return; // 最少保留 1
|
|
311
|
+
const ref = this._scratchRefs.get(id);
|
|
312
|
+
try { ref?.current?.requestKill(); } catch {}
|
|
313
|
+
this._scratchRefs.delete(id);
|
|
314
|
+
const idx = this.state.scratchTabs.findIndex(t => t.id === id);
|
|
315
|
+
const tabs = this.state.scratchTabs.filter(t => t.id !== id);
|
|
316
|
+
let active = this.state.activeScratchTabId;
|
|
317
|
+
if (active === id) {
|
|
318
|
+
// 取右邻居,否则左邻居
|
|
319
|
+
active = (this.state.scratchTabs[idx + 1] ?? this.state.scratchTabs[idx - 1])?.id ?? tabs[0]?.id ?? '';
|
|
320
|
+
}
|
|
321
|
+
this.setState({ scratchTabs: tabs, activeScratchTabId: active }, () => {
|
|
322
|
+
writeScratchTabs(tabs);
|
|
323
|
+
writeScratchActiveTab(active);
|
|
324
|
+
if (active) {
|
|
325
|
+
const r = this._scratchRefs.get(active);
|
|
326
|
+
r?.current?.refit();
|
|
327
|
+
r?.current?.focus();
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
// 仅 active tab 的 focus/blur 事件影响 scratchFocused,避免 tab 切换时新旧并发触发抖动
|
|
333
|
+
handleScratchTabFocusChange = (id, focused) => {
|
|
334
|
+
if (id !== this.state.activeScratchTabId) return;
|
|
335
|
+
if (focused !== this.state.scratchFocused) {
|
|
336
|
+
this.setState({ scratchFocused: focused });
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// 后端首条 state 消息携带 shellBasename;所有 tab 共用一个 $SHELL,只需取第一次到达的
|
|
341
|
+
handleScratchShellInfo = (name) => {
|
|
342
|
+
if (!name || this.state.scratchShellBasename) return;
|
|
343
|
+
this.setState({ scratchShellBasename: name });
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
toggleScratch = () => {
|
|
347
|
+
const next = !this.state.scratchOpen;
|
|
348
|
+
this.setState({ scratchOpen: next });
|
|
349
|
+
try { localStorage.setItem(SCRATCH_OPEN_KEY, String(next)); } catch {}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// 用 DOM 直写 style.height 而非 React JSX inline style:
|
|
353
|
+
// 1) 防 theme MutationObserver / preset-changed 等无关 setState 在拖拽中途把高度 snap 回去
|
|
354
|
+
// 2) 拖拽期间每帧 setState 抖动开销大;mouseup 时一次性 setState + localStorage 提交
|
|
355
|
+
_applyScratchHeight = () => {
|
|
356
|
+
const el = this._scratchWrapRef.current;
|
|
357
|
+
if (el) el.style.height = this.state.scratchHeight + 'px';
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// Pointer Events + setPointerCapture:自动覆盖 mouseup 飞出窗口、iPad 触摸;不挂 document 全局监听
|
|
361
|
+
handleScratchResizerPointerDown = (e) => {
|
|
362
|
+
if (e.button !== undefined && e.button !== 0) return; // 仅主键
|
|
363
|
+
e.preventDefault();
|
|
364
|
+
this._scratchDragging = true;
|
|
365
|
+
this._scratchDragStartY = e.clientY;
|
|
366
|
+
this._scratchDragStartH = this.state.scratchHeight;
|
|
367
|
+
this._scratchDragLastH = this.state.scratchHeight;
|
|
368
|
+
this._scratchPointerId = e.pointerId;
|
|
369
|
+
try { e.currentTarget.setPointerCapture(e.pointerId); } catch {}
|
|
370
|
+
document.body.style.cursor = 'row-resize';
|
|
371
|
+
document.body.style.userSelect = 'none';
|
|
372
|
+
this.setState({ isDraggingScratch: true });
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
handleScratchResizerPointerMove = (e) => {
|
|
376
|
+
if (!this._scratchDragging) return;
|
|
377
|
+
const newH = Math.max(
|
|
378
|
+
SCRATCH_HEIGHT_MIN,
|
|
379
|
+
Math.min(SCRATCH_HEIGHT_MAX, this._scratchDragStartH + (this._scratchDragStartY - e.clientY))
|
|
380
|
+
);
|
|
381
|
+
const el = this._scratchWrapRef.current;
|
|
382
|
+
if (!el) return;
|
|
383
|
+
el.style.height = newH + 'px';
|
|
384
|
+
this._scratchDragLastH = newH;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
handleScratchResizerPointerUp = (e) => {
|
|
388
|
+
if (!this._scratchDragging) return;
|
|
389
|
+
this._endScratchDrag(e);
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
_endScratchDrag = (e) => {
|
|
393
|
+
this._scratchDragging = false;
|
|
394
|
+
document.body.style.cursor = '';
|
|
395
|
+
document.body.style.userSelect = '';
|
|
396
|
+
if (e && this._scratchPointerId != null && e.currentTarget) {
|
|
397
|
+
try { e.currentTarget.releasePointerCapture(this._scratchPointerId); } catch {}
|
|
398
|
+
}
|
|
399
|
+
this._scratchPointerId = null;
|
|
400
|
+
const h = this._scratchDragLastH;
|
|
401
|
+
this._scratchDragLastH = null;
|
|
402
|
+
if (h != null) {
|
|
403
|
+
try { localStorage.setItem(SCRATCH_HEIGHT_KEY, String(h)); } catch {}
|
|
404
|
+
this.setState({ scratchHeight: h, isDraggingScratch: false });
|
|
405
|
+
} else {
|
|
406
|
+
this.setState({ isDraggingScratch: false });
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
componentDidMount() {
|
|
411
|
+
// 幂等:window.__cxvTermDiag 快照 + longtask 计数 + cxv_term_diag 周期日志。
|
|
412
|
+
// 有意不在 unmount 配对卸载——app-lifetime 单例(计数器/定时器全局唯一,
|
|
413
|
+
// 跨面板共享;_installed 守卫防重复安装)。
|
|
414
|
+
installTermDiag();
|
|
415
|
+
this.initTerminal();
|
|
416
|
+
// 注册 ws 消息 + 状态 handler。Provider 已在 App/Mobile 层根据 cliMode/terminalVisible 决定是否建立 ws。
|
|
417
|
+
if (this.context && this.context.addMessageHandler) {
|
|
418
|
+
this._unsubWsHandler = this.context.addMessageHandler(this._onTerminalWsMessage);
|
|
419
|
+
}
|
|
420
|
+
if (this.context && this.context.addStateListener) {
|
|
421
|
+
this._unsubWsState = this.context.addStateListener(this._onTerminalWsState);
|
|
422
|
+
}
|
|
423
|
+
// 若 ws 已 OPEN(本组件 mount 较 Provider 晚的常见场景),立即 sendResize 让 PTY 用当前 cols/rows。
|
|
424
|
+
if (this.context && this.context.isOpen && this.context.isOpen()) {
|
|
425
|
+
this.sendResize();
|
|
426
|
+
}
|
|
427
|
+
this.setupResizeObserver();
|
|
428
|
+
// 定时强制刷新,按渲染器分流:
|
|
429
|
+
// - Android(WebGL):纹理脏真实存在,保留 60s 轻量维护(clearTextureAtlas + refresh;
|
|
430
|
+
// 可动态 fit 的端再 fit + sendResize)。phone 上 canFit=false 自动退化为 atlas+refresh。
|
|
431
|
+
// - mac(WebGL)/Windows·Linux·iPad(DOM):120s 轻量分支——atlas 清理(mac WebGL 防
|
|
432
|
+
// sleep-wake/GPU 切换的纹理脏累积,DOM 渲染器下为 no-op)+ 全行 refresh 被动自愈;
|
|
433
|
+
// 不做 fit/sendResize,避免定时 resize 抖动。iPhone 维持无定时器(内存/电量预算)。
|
|
434
|
+
// tab 隐藏时跳过(无渲染需求)。
|
|
435
|
+
if (isAndroid) {
|
|
436
|
+
this._autoRefreshInterval = setInterval(() => {
|
|
437
|
+
if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
|
|
438
|
+
this._refreshAndroidTerminalRendering();
|
|
439
|
+
}, 60000);
|
|
440
|
+
} else if (!isMobile || isPad) {
|
|
441
|
+
this._autoRefreshInterval = setInterval(() => {
|
|
442
|
+
if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
|
|
443
|
+
if (!this.terminal) return;
|
|
444
|
+
try { this.terminal.clearTextureAtlas?.(); } catch {}
|
|
445
|
+
this.terminal.refresh(0, this.terminal.rows - 1);
|
|
446
|
+
}, 120000);
|
|
447
|
+
}
|
|
448
|
+
// 加载预置 (props.preferences 已 ready 时立即,否则 componentDidUpdate 接力)
|
|
449
|
+
this._loadPresetShortcuts();
|
|
450
|
+
this._onFocusTerminal = () => { if (this.terminal && this.containerRef?.current?.offsetWidth > 0) this.terminal.focus(); };
|
|
451
|
+
window.addEventListener('cxv-focus-terminal', this._onFocusTerminal);
|
|
452
|
+
this._themeObserver = new MutationObserver(() => {
|
|
453
|
+
if (this.terminal) {
|
|
454
|
+
const isDark = document.documentElement.getAttribute('data-theme') !== 'light';
|
|
455
|
+
this.terminal.options.theme = isDark ? darkTerminalTheme : lightTerminalTheme;
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
this._themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
|
459
|
+
if (this.state.scratchOpen) this._applyScratchHeight();
|
|
460
|
+
// mount 时 sync tab 列表 / active id 到 localStorage(兼容旧版本只有 open/height 的存档)
|
|
461
|
+
writeScratchTabs(this.state.scratchTabs);
|
|
462
|
+
if (this.state.activeScratchTabId) writeScratchActiveTab(this.state.activeScratchTabId);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
componentDidUpdate(prevProps, prevState) {
|
|
466
|
+
// SettingsContext 异步 fetch 完成后,props.preferences 才到达;这里接力加载 AgentTeam 预置。
|
|
467
|
+
if (prevProps.preferences !== this.props.preferences) {
|
|
468
|
+
this._loadPresetShortcuts();
|
|
469
|
+
}
|
|
470
|
+
if (prevState.scratchOpen !== this.state.scratchOpen) {
|
|
471
|
+
if (this.state.scratchOpen) {
|
|
472
|
+
// componentDidUpdate 在 React commit 之后、浏览器 paint 之前同步触发,
|
|
473
|
+
// 此时 ref.current 已是最新 DOM;直接写 style.height,不走 microtask 防 1 帧闪烁
|
|
474
|
+
this._applyScratchHeight();
|
|
475
|
+
} else if (this._scratchDragging) {
|
|
476
|
+
// 拖拽过程中 scratchOpen 被外部翻 false:resizer 已卸载、pointerup 不会再触达,
|
|
477
|
+
// 这里兜底恢复 body 样式与拖拽标志,防止 cursor/userSelect 残留
|
|
478
|
+
document.body.style.cursor = '';
|
|
479
|
+
document.body.style.userSelect = '';
|
|
480
|
+
this._scratchDragging = false;
|
|
481
|
+
this._scratchDragLastH = null;
|
|
482
|
+
this._scratchPointerId = null;
|
|
483
|
+
this.setState({ isDraggingScratch: false });
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
_loadPresetShortcuts() {
|
|
489
|
+
// 数据从 props.preferences 派生(SettingsContext 集中 fetch);未 ready 时静默返回,
|
|
490
|
+
// componentDidUpdate 接力。
|
|
491
|
+
const data = this.props.preferences;
|
|
492
|
+
if (!data) return;
|
|
493
|
+
const dismissed = Array.isArray(data.dismissedBuiltinPresets) ? new Set(data.dismissedBuiltinPresets) : new Set();
|
|
494
|
+
this._dismissedBuiltinPresets = dismissed;
|
|
495
|
+
let items = [];
|
|
496
|
+
if (Array.isArray(data.presetShortcuts)) {
|
|
497
|
+
items = data.presetShortcuts.map((item, i) => {
|
|
498
|
+
if (typeof item === 'string') return { id: Date.now() + i, teamName: '', description: item };
|
|
499
|
+
return {
|
|
500
|
+
id: Date.now() + i,
|
|
501
|
+
teamName: item.teamName || '',
|
|
502
|
+
description: item.description || '',
|
|
503
|
+
...(item.builtinId ? { builtinId: item.builtinId } : {}),
|
|
504
|
+
...(item.modified ? { modified: true } : {}),
|
|
505
|
+
};
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
// 合并内置预置:未被用户删除且不在已有列表中的
|
|
509
|
+
const existingBuiltinIds = new Set(items.filter(i => i.builtinId).map(i => i.builtinId));
|
|
510
|
+
for (const bp of BUILTIN_PRESETS) {
|
|
511
|
+
if (dismissed.has(bp.builtinId) || existingBuiltinIds.has(bp.builtinId)) continue;
|
|
512
|
+
items.unshift({ id: Date.now() + Math.random(), builtinId: bp.builtinId, teamName: bp.teamName, description: bp.description });
|
|
513
|
+
}
|
|
514
|
+
const customExperts = Array.isArray(data.customUltraplanExperts) ? data.customUltraplanExperts : [];
|
|
515
|
+
const expertOrder = Array.isArray(data.ultraplanExpertOrder) ? data.ultraplanExpertOrder : [];
|
|
516
|
+
const expertHidden = Array.isArray(data.ultraplanExpertHidden) ? data.ultraplanExpertHidden : [];
|
|
517
|
+
const next = { presetItems: items, customUltraplanExperts: customExperts, ultraplanExpertOrder: expertOrder, ultraplanExpertHidden: expertHidden };
|
|
518
|
+
// 若当前选中的变体已不可见(被另一端删除 / 隐藏),回退到首个可见专家(无可见则 codeExpert)。
|
|
519
|
+
const current = this.state.ultraplanVariant;
|
|
520
|
+
const visible = visibleExpertKeys(customExperts, expertOrder, expertHidden);
|
|
521
|
+
if (typeof current === 'string' && !visible.includes(current)) {
|
|
522
|
+
next.ultraplanVariant = visible[0] || 'codeExpert';
|
|
523
|
+
}
|
|
524
|
+
this.setState(next);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
componentWillUnmount() {
|
|
528
|
+
this._qmHover.cancel();
|
|
529
|
+
// mid-drag 卸载兜底:恢复 body 样式,标记终止
|
|
530
|
+
if (this._scratchDragging) {
|
|
531
|
+
document.body.style.cursor = '';
|
|
532
|
+
document.body.style.userSelect = '';
|
|
533
|
+
this._scratchDragging = false;
|
|
534
|
+
this._scratchDragLastH = null;
|
|
535
|
+
this._scratchPointerId = null;
|
|
536
|
+
}
|
|
537
|
+
if (this.terminal?.textarea) {
|
|
538
|
+
this.terminal.textarea.removeEventListener('focus', this._handleTermFocus);
|
|
539
|
+
this.terminal.textarea.removeEventListener('blur', this._handleTermBlur);
|
|
540
|
+
}
|
|
541
|
+
if (this._themeObserver) { this._themeObserver.disconnect(); this._themeObserver = null; }
|
|
542
|
+
window.removeEventListener('cxv-focus-terminal', this._onFocusTerminal);
|
|
543
|
+
if (this._stopMobileMomentum) this._stopMobileMomentum();
|
|
544
|
+
// unmount 前同步排空 buffer 给 xterm,防最后 16ms 数据丢失(既有 bug 缓解)。
|
|
545
|
+
// dispose 后 push 静默忽略、rAF 取消,与 terminal.dispose 顺序无关。
|
|
546
|
+
if (this._writeQ) {
|
|
547
|
+
try { this._writeQ.drain(); } catch {}
|
|
548
|
+
this._writeQ.dispose();
|
|
549
|
+
}
|
|
550
|
+
if (this._wsReconnectTimer) clearTimeout(this._wsReconnectTimer);
|
|
551
|
+
if (this._unsubWsHandler) { try { this._unsubWsHandler(); } catch {} this._unsubWsHandler = null; }
|
|
552
|
+
if (this._unsubWsState) { try { this._unsubWsState(); } catch {} this._unsubWsState = null; }
|
|
553
|
+
if (this._resizeDebounceTimer) clearTimeout(this._resizeDebounceTimer);
|
|
554
|
+
if (this._webglRecoveryTimer) clearTimeout(this._webglRecoveryTimer);
|
|
555
|
+
if (this._autoRefreshInterval) { clearInterval(this._autoRefreshInterval); this._autoRefreshInterval = null; }
|
|
556
|
+
if (this._initRafId) { cancelAnimationFrame(this._initRafId); this._initRafId = 0; }
|
|
557
|
+
if (this.resizeObserver) {
|
|
558
|
+
this.resizeObserver.disconnect();
|
|
559
|
+
}
|
|
560
|
+
if (this.webglAddon || this._webglLongtaskObserver) {
|
|
561
|
+
this._disposeWebgl();
|
|
562
|
+
}
|
|
563
|
+
if (this.terminal) {
|
|
564
|
+
if (this.terminal.textarea) {
|
|
565
|
+
this.terminal.textarea.removeEventListener('paste', this._handlePaste, true);
|
|
566
|
+
}
|
|
567
|
+
this.terminal.dispose();
|
|
568
|
+
// 置 null 让晚到的异步回调(如 document.fonts.ready 重 fit)的 !this.terminal
|
|
569
|
+
// 守卫真正生效,与 ScratchTerminal 行为对齐
|
|
570
|
+
this.terminal = null;
|
|
571
|
+
}
|
|
572
|
+
// fitAddon 随 terminal.dispose 失效,置 null 让晚到回调的 !this.fitAddon 守卫生效
|
|
573
|
+
this.fitAddon = null;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
initTerminal() {
|
|
577
|
+
const isDark = document.documentElement.getAttribute('data-theme') !== 'light';
|
|
578
|
+
this.terminal = new Terminal({
|
|
579
|
+
cursorBlink: false,
|
|
580
|
+
cursorStyle: 'bar',
|
|
581
|
+
cursorWidth: 1,
|
|
582
|
+
cursorInactiveStyle: 'none',
|
|
583
|
+
fontSize: (isMobile && !isPad) ? 11 : 13,
|
|
584
|
+
fontFamily: terminalFontFamily,
|
|
585
|
+
theme: isDark ? darkTerminalTheme : lightTerminalTheme,
|
|
586
|
+
allowProposedApi: true,
|
|
587
|
+
// Windows:超出 cell 宽度的字形(CJK 落回退字体时常见)按 cell 缩放,配合
|
|
588
|
+
// terminalFontFamily 治 IME 中文输入整体偏移;仅 win 开启,mac 渲染零变化。
|
|
589
|
+
// 注:DOM 渲染器下该选项无效(WebGL/Canvas 有效),字体栈修复不受影响。
|
|
590
|
+
rescaleOverlappingGlyphs: isWindows,
|
|
591
|
+
scrollback: isPad ? 2000 : isIOS ? 200 : isMobile ? 1000 : 2000,
|
|
592
|
+
smoothScrollDuration: 0,
|
|
593
|
+
scrollOnUserInput: true,
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
this.fitAddon = new FitAddon();
|
|
597
|
+
this.terminal.loadAddon(this.fitAddon);
|
|
598
|
+
this.terminal.loadAddon(new WebLinksAddon());
|
|
599
|
+
|
|
600
|
+
const unicode11 = new Unicode11Addon();
|
|
601
|
+
this.terminal.loadAddon(unicode11);
|
|
602
|
+
this.terminal.unicode.activeVersion = '11';
|
|
603
|
+
|
|
604
|
+
this.terminal.open(this.containerRef.current);
|
|
605
|
+
|
|
606
|
+
// 终端 focus/blur → 边框高亮 (xterm v6 removed onFocus/onBlur, use DOM events)
|
|
607
|
+
this._handleTermFocus = () => this.setState({ terminalFocused: true });
|
|
608
|
+
this._handleTermBlur = () => this.setState({ terminalFocused: false });
|
|
609
|
+
const termTextarea = this.terminal.textarea;
|
|
610
|
+
if (termTextarea) {
|
|
611
|
+
termTextarea.addEventListener('focus', this._handleTermFocus);
|
|
612
|
+
termTextarea.addEventListener('blur', this._handleTermBlur);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// 渲染器选择:Android + macOS 桌面(Chromium 系)启用 WebGL,详见 WEBGL_RENDERER 常量;
|
|
616
|
+
// Windows/Linux/iOS/Safari 走 xterm.js 6.0 内置 DOM 渲染器——Windows 上实测 DOM 比
|
|
617
|
+
// WebGL 更稳定(无纹理脏花屏 / context loss / GPU 长任务降级等问题)。
|
|
618
|
+
if (WEBGL_RENDERER) {
|
|
619
|
+
this._loadWebglAddon(false);
|
|
620
|
+
} else {
|
|
621
|
+
// WebGL 时代 longtask 降级留下的 sticky key 在 DOM 平台已是死状态,一次性清理
|
|
622
|
+
try { localStorage.removeItem(WEBGL_STICKY_KEY); } catch {}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// 写入节流:批量合并高频输出,避免逐条触发渲染。
|
|
626
|
+
// 用 TerminalWriteQueue 替代原「string += / slice」实现,消除大流量时
|
|
627
|
+
// O(n²) 字符串切片热点(trace3 显示 _flushWrite 794ms self),同时
|
|
628
|
+
// 修复 UTF-16 surrogate 边界切碎、unmount 16ms 数据丢失等隐患。
|
|
629
|
+
// 节奏与原实现等价:每帧 1 个 chunk,不做激进 multi-chunk drain。
|
|
630
|
+
// 移动端内存预算低,积压自保水位减半(默认桌面 2MB/512KB)。
|
|
631
|
+
// chunk 初值:Windows DOM 渲染器解析慢,16KB 保守起步(其余平台 32KB),
|
|
632
|
+
// 运行期由 write callback 计时 AIMD 自适应收敛(详见 terminalWriteQueue 头注释)。
|
|
633
|
+
this._writeQ = new TerminalWriteQueue(
|
|
634
|
+
() => this.terminal,
|
|
635
|
+
{
|
|
636
|
+
...((isMobile && !isPad) ? { highWaterBytes: 1024 * 1024, trimTargetBytes: 256 * 1024 } : null),
|
|
637
|
+
...(isWindows ? { initialChunkBytes: 16 * 1024 } : null),
|
|
638
|
+
// 积压丢弃后向服务端请求权威快照对齐(整项丢弃的中段对增量输出流不会自愈)
|
|
639
|
+
onTrim: () => this._requestResync(),
|
|
640
|
+
}
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
if (isMobile && !isPad) {
|
|
644
|
+
// 移动端:基于屏幕尺寸一次性计算固定 cols/rows,避免动态 fit 导致渲染抖动
|
|
645
|
+
this._initRafId = requestAnimationFrame(() => {
|
|
646
|
+
this._initRafId = 0;
|
|
647
|
+
if (!this.terminal) return; // mount 后一帧内 unmount 的竞态守卫
|
|
648
|
+
this._mobileFixedResize();
|
|
649
|
+
});
|
|
650
|
+
} else {
|
|
651
|
+
this._initRafId = requestAnimationFrame(() => {
|
|
652
|
+
this._initRafId = 0;
|
|
653
|
+
if (!this.terminal || !this.fitAddon) return; // unmount 竞态:fit/focus 作用于尸体会抛
|
|
654
|
+
this.fitAddon.fit();
|
|
655
|
+
this.terminal.focus();
|
|
656
|
+
});
|
|
657
|
+
// 字体异步就绪后重 fit + 重绘:初始 fit 可能基于回退字体的 cell 测量,
|
|
658
|
+
// 字体加载完成后宽度变化会造成错位(Windows CJK 场景尤甚)。
|
|
659
|
+
if (typeof document !== 'undefined' && document.fonts?.ready?.then) {
|
|
660
|
+
document.fonts.ready.then(() => {
|
|
661
|
+
if (!this.terminal) return;
|
|
662
|
+
this._fitPreservingScroll();
|
|
663
|
+
try { this.terminal.refresh(0, this.terminal.rows - 1); } catch { /* noop */ }
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Shift+Enter: 发 ESC+CR(Alt+Enter 的 escape 码),和 Codex `/terminal-setup`
|
|
669
|
+
// 写进 VS Code/Cursor keybindings 的 `\x1b\r` 等价。Codex CLI 识别这个序列为
|
|
670
|
+
// "插入换行而非提交"。之前用 bracketed-paste-LF 对老版可能有效,2.x 版已不兼容。
|
|
671
|
+
this.terminal.attachCustomKeyEventHandler((e) => {
|
|
672
|
+
// Win/Linux 智能复制粘贴(见 utils/terminalClipboard.js 注释):xterm 6 把 Ctrl+C/Ctrl+V
|
|
673
|
+
// 当控制字符并 preventDefault,压掉原生 paste。这里在最先执行的 customKeyEventHandler 接管。
|
|
674
|
+
const clipAction = clipboardKeyAction(e, { isMac });
|
|
675
|
+
if (clipAction === 'paste') {
|
|
676
|
+
// 主动读剪贴板(不依赖被 xterm 压掉的原生 paste 默认动作 → 同样修复 Electron 端),
|
|
677
|
+
// 终端走 _pasteText 安全路径(bracketed-paste 包裹 + 注入消毒,保留图片粘贴)。
|
|
678
|
+
if (navigator.clipboard && (navigator.clipboard.read || navigator.clipboard.readText)) {
|
|
679
|
+
e.preventDefault();
|
|
680
|
+
e.stopPropagation();
|
|
681
|
+
// 双重粘贴防护:主动读剪贴板期间,若个别浏览器仍触发原生 paste(preventDefault 抑制
|
|
682
|
+
// paste 是约定行为、非规范硬保证),让 _handlePaste 早退,避免叠加。下个事件循环清闸。
|
|
683
|
+
this._activePasteInFlight = true;
|
|
684
|
+
setTimeout(() => { this._activePasteInFlight = false; }, 0);
|
|
685
|
+
this._activePaste();
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
// 非安全上下文(如 LAN HTTP)无 clipboard API:不 preventDefault,放行原生 paste → _handlePaste
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
if (clipAction === 'copy') {
|
|
692
|
+
const sel = this.terminal?.getSelection?.();
|
|
693
|
+
if (sel) {
|
|
694
|
+
// 有选区:复制(对齐 VS Code / Windows Terminal)。仅复制成功才清选区——失败(非安全
|
|
695
|
+
// 上下文且 execCommand 也失败)时保留选区,用户可改用 Ctrl+Insert 重试,不致"选区被吞"。
|
|
696
|
+
e.preventDefault();
|
|
697
|
+
e.stopPropagation();
|
|
698
|
+
copyTextToClipboard(sel).then((ok) => { if (ok) this.terminal?.clearSelection?.(); });
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
return true; // 无选区:交回 xterm 发 \x03(SIGINT),保留中断能力
|
|
702
|
+
}
|
|
703
|
+
if (e.type === 'keydown' && e.key === 'Enter' && e.shiftKey) {
|
|
704
|
+
// 必须显式 preventDefault:xterm customKeyEventHandler 返回 false 只阻 xterm 内部处理,
|
|
705
|
+
// 不阻浏览器 textarea 默认行为(Enter 会往隐藏 textarea 塞 \n 再被 xterm onData 转发到 PTY)。
|
|
706
|
+
// 不 preventDefault 会让 PTY 同时收到 \x1b\r(我们显式发的)和 \n(textarea 漏进来的),
|
|
707
|
+
// 后者被 Codex 当作 Enter 提交,于是"看起来换行没生效"。
|
|
708
|
+
e.preventDefault();
|
|
709
|
+
e.stopPropagation();
|
|
710
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
711
|
+
this.ws.send(JSON.stringify({ type: 'input', data: '\x1b\r' }));
|
|
712
|
+
}
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
// Enter: 如果有 pending 文件,先注入路径到终端输入行(不带回车),
|
|
716
|
+
// 用户可以看到路径后再按 Enter 确认发送
|
|
717
|
+
// 跳过 alternate screen(vim/less 等交互程序),避免误注入
|
|
718
|
+
if (e.type === 'keydown' && e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
|
719
|
+
const pending = this.props.pendingImages;
|
|
720
|
+
const inAlternateScreen = this.terminal?.buffer?.active?.type === 'alternate';
|
|
721
|
+
if (pending?.length > 0 && !inAlternateScreen && this.ws?.readyState === WebSocket.OPEN) {
|
|
722
|
+
// 必须 preventDefault(同上 Shift+Enter 分支):仅 return false 不阻浏览器默认,
|
|
723
|
+
// Enter 的 keypress 仍被 xterm 转发 \r 到 PTY → 路径注入后立即提交,违背
|
|
724
|
+
// "用户看到路径再按 Enter 确认" 的设计。
|
|
725
|
+
e.preventDefault();
|
|
726
|
+
e.stopPropagation();
|
|
727
|
+
const paths = pending.map(img => `'${img.path.replace(/'/g, "'\\''")}'`).join(' ');
|
|
728
|
+
this.ws.send(JSON.stringify({ type: 'input', data: paths + ' ' }));
|
|
729
|
+
this.props.onClearPendingImages?.();
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return true;
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
// alt 屏(Codex Ink 等 TUI,buffer.hasScrollback=false)下,xterm 默认把 wheel
|
|
737
|
+
// 翻译成 ↑/↓ 发 PTY,Ink 输入会把它当作历史翻页。这里拦截:正常屏让 xterm 自己滚
|
|
738
|
+
// scrollback;alt 屏转发滚动到外层 chat scroller(Virtuoso),由 ChatView 通过
|
|
739
|
+
// getChatScroller prop 传入。祖先链上没有可滚元素,必须显式拿这个 sibling ref。
|
|
740
|
+
this.terminal.attachCustomWheelEventHandler((ev) => {
|
|
741
|
+
if (this.terminal?.buffer?.active?.type !== 'alternate') return true;
|
|
742
|
+
const scroller = this.props.getChatScroller?.();
|
|
743
|
+
if (scroller) {
|
|
744
|
+
const px = ev.deltaMode === 1 ? ev.deltaY * 16
|
|
745
|
+
: ev.deltaMode === 2 ? ev.deltaY * (scroller.clientHeight || 0)
|
|
746
|
+
: ev.deltaY;
|
|
747
|
+
scroller.scrollTop += px;
|
|
748
|
+
}
|
|
749
|
+
ev.preventDefault();
|
|
750
|
+
return false;
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
this.terminal.onData((data) => {
|
|
754
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
755
|
+
this.ws.send(JSON.stringify({ type: 'input', data }));
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
// 拦截粘贴事件,用 bracketed paste 转义序列包裹,
|
|
760
|
+
// 防止多行粘贴时换行符被当作 Enter 逐行执行
|
|
761
|
+
// 使用 capture 阶段确保在 xterm.js 自身的 paste handler 之前执行
|
|
762
|
+
if (this.terminal.textarea) {
|
|
763
|
+
this.terminal.textarea.addEventListener('paste', this._handlePaste, true);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (isMobile) {
|
|
767
|
+
this._setupMobileTouchScroll();
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* 手机端触摸滚动:xterm 的 viewport 在 screen 层之下,原生触摸无法滚动。
|
|
773
|
+
* 使用 terminal.scrollLines() 官方 API 代替直接操作 scrollTop,
|
|
774
|
+
* 确保与 xterm 内部状态同步。通过 rAF 批量处理 + 惯性动画实现流畅滚动。
|
|
775
|
+
* 参考: https://github.com/xtermjs/xterm.js/issues/594
|
|
776
|
+
*/
|
|
777
|
+
_setupMobileTouchScroll() {
|
|
778
|
+
const screen = this.containerRef.current?.querySelector('.xterm-screen');
|
|
779
|
+
if (!screen) return;
|
|
780
|
+
|
|
781
|
+
const term = this.terminal;
|
|
782
|
+
// 获取行高(用于将像素 delta 转为行数)
|
|
783
|
+
const getLineHeight = () => {
|
|
784
|
+
const cellDims = term._core?._renderService?.dimensions?.css?.cell;
|
|
785
|
+
return cellDims?.height || 15;
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
let lastY = 0;
|
|
789
|
+
let lastTime = 0;
|
|
790
|
+
let momentumRaf = null;
|
|
791
|
+
// 像素级累积器,不足一行时保留小数部分
|
|
792
|
+
let pixelAccum = 0;
|
|
793
|
+
let pendingDy = 0;
|
|
794
|
+
let scrollRaf = null;
|
|
795
|
+
let velocitySamples = [];
|
|
796
|
+
|
|
797
|
+
const stopMomentum = () => {
|
|
798
|
+
if (momentumRaf) {
|
|
799
|
+
cancelAnimationFrame(momentumRaf);
|
|
800
|
+
momentumRaf = null;
|
|
801
|
+
}
|
|
802
|
+
if (scrollRaf) {
|
|
803
|
+
cancelAnimationFrame(scrollRaf);
|
|
804
|
+
scrollRaf = null;
|
|
805
|
+
}
|
|
806
|
+
pendingDy = 0;
|
|
807
|
+
pixelAccum = 0;
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
// 将累积的像素偏移转化为行滚动
|
|
811
|
+
const flushScroll = () => {
|
|
812
|
+
scrollRaf = null;
|
|
813
|
+
if (pendingDy === 0) return;
|
|
814
|
+
pixelAccum += pendingDy;
|
|
815
|
+
pendingDy = 0;
|
|
816
|
+
const lh = getLineHeight();
|
|
817
|
+
const lines = Math.trunc(pixelAccum / lh);
|
|
818
|
+
if (lines !== 0) {
|
|
819
|
+
term.scrollLines(lines);
|
|
820
|
+
pixelAccum -= lines * lh;
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
screen.addEventListener('touchstart', (e) => {
|
|
825
|
+
stopMomentum();
|
|
826
|
+
if (e.touches.length !== 1) return;
|
|
827
|
+
lastY = e.touches[0].clientY;
|
|
828
|
+
lastTime = performance.now();
|
|
829
|
+
velocitySamples = [];
|
|
830
|
+
}, { passive: true });
|
|
831
|
+
|
|
832
|
+
screen.addEventListener('touchmove', (e) => {
|
|
833
|
+
if (e.touches.length !== 1) return;
|
|
834
|
+
const y = e.touches[0].clientY;
|
|
835
|
+
const now = performance.now();
|
|
836
|
+
const dt = now - lastTime;
|
|
837
|
+
const dy = lastY - y; // 正值 = 向上滚
|
|
838
|
+
|
|
839
|
+
if (dt > 0) {
|
|
840
|
+
const v = dy / dt * 16;
|
|
841
|
+
velocitySamples.push({ v, t: now });
|
|
842
|
+
// 只保留最近 100ms 的样本
|
|
843
|
+
while (velocitySamples.length > 0 && now - velocitySamples[0].t > 100) {
|
|
844
|
+
velocitySamples.shift();
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
pendingDy += dy;
|
|
849
|
+
if (!scrollRaf) {
|
|
850
|
+
scrollRaf = requestAnimationFrame(flushScroll);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
lastY = y;
|
|
854
|
+
lastTime = now;
|
|
855
|
+
}, { passive: true });
|
|
856
|
+
|
|
857
|
+
screen.addEventListener('touchend', () => {
|
|
858
|
+
// 刷掉剩余 pending
|
|
859
|
+
if (scrollRaf) {
|
|
860
|
+
cancelAnimationFrame(scrollRaf);
|
|
861
|
+
scrollRaf = null;
|
|
862
|
+
}
|
|
863
|
+
if (pendingDy !== 0) {
|
|
864
|
+
pixelAccum += pendingDy;
|
|
865
|
+
pendingDy = 0;
|
|
866
|
+
const lh = getLineHeight();
|
|
867
|
+
const lines = Math.trunc(pixelAccum / lh);
|
|
868
|
+
if (lines !== 0) term.scrollLines(lines);
|
|
869
|
+
pixelAccum = 0;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// 用加权平均计算末速度(像素/帧)
|
|
873
|
+
let velocity = 0;
|
|
874
|
+
if (velocitySamples.length >= 2) {
|
|
875
|
+
let totalWeight = 0;
|
|
876
|
+
let weightedV = 0;
|
|
877
|
+
const latest = velocitySamples[velocitySamples.length - 1].t;
|
|
878
|
+
for (const s of velocitySamples) {
|
|
879
|
+
const w = Math.max(0, 1 - (latest - s.t) / 100);
|
|
880
|
+
weightedV += s.v * w;
|
|
881
|
+
totalWeight += w;
|
|
882
|
+
}
|
|
883
|
+
velocity = totalWeight > 0 ? weightedV / totalWeight : 0;
|
|
884
|
+
}
|
|
885
|
+
velocitySamples = [];
|
|
886
|
+
|
|
887
|
+
// 惯性滚动(仍用像素级累积器保证精度)
|
|
888
|
+
if (Math.abs(velocity) < 0.5) return;
|
|
889
|
+
const friction = 0.95;
|
|
890
|
+
let mAccum = 0;
|
|
891
|
+
const tick = () => {
|
|
892
|
+
if (Math.abs(velocity) < 0.3) {
|
|
893
|
+
// 最后残余不足一行则四舍五入
|
|
894
|
+
const lh = getLineHeight();
|
|
895
|
+
const rest = Math.round(mAccum / lh);
|
|
896
|
+
if (rest !== 0) term.scrollLines(rest);
|
|
897
|
+
momentumRaf = null;
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
mAccum += velocity;
|
|
901
|
+
const lh = getLineHeight();
|
|
902
|
+
const lines = Math.trunc(mAccum / lh);
|
|
903
|
+
if (lines !== 0) {
|
|
904
|
+
term.scrollLines(lines);
|
|
905
|
+
mAccum -= lines * lh;
|
|
906
|
+
}
|
|
907
|
+
velocity *= friction;
|
|
908
|
+
momentumRaf = requestAnimationFrame(tick);
|
|
909
|
+
};
|
|
910
|
+
momentumRaf = requestAnimationFrame(tick);
|
|
911
|
+
}, { passive: true });
|
|
912
|
+
|
|
913
|
+
this._stopMobileMomentum = stopMomentum;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// 通过 TerminalWsContext 共享 ws — 不再自建。本方法接收 Provider 派发的所有消息,
|
|
917
|
+
// 自己 switch type;ChatView/TerminalPanel 各自只处理关心的类型,互不干扰。
|
|
918
|
+
// (原 hook/sdk-* 类消息在合并 ws 后也会进来,但这里不识别 → try/catch 之外也无作用,自然忽略。)
|
|
919
|
+
_onTerminalWsMessage = (msg) => {
|
|
920
|
+
try {
|
|
921
|
+
if (msg.type === 'data') {
|
|
922
|
+
this._throttledWrite(msg.data);
|
|
923
|
+
} else if (msg.type === 'data-resync') {
|
|
924
|
+
// 服务端反压恢复:丢弃本地积压、重置 xterm、写快照一步对齐到服务端当前末态
|
|
925
|
+
// (与 ws close→重连全量 replay 的既有恢复惯例同款;TUI 重绘由服务端 SIGWINCH/resize 抖动驱动)。
|
|
926
|
+
// 有意取舍:全量重置会连洪泛前的 scrollback 一起清掉——保留 scrollback 改写
|
|
927
|
+
// 分隔条+追加快照的方案会让快照开头与已渲染内容重叠重复,且洪泛流的半截转义残留可能
|
|
928
|
+
// 卡坏 ANSI 状态机;reset 换取干净对齐,黄字提示已向用户明示发生过跳过。
|
|
929
|
+
// 重置必须走带内 INBAND_RESET 而非带外 terminal.reset()——后者不清 xterm 内部
|
|
930
|
+
// WriteBuffer,残留字节在 reset 后以 ground state 续解析,半截序列按字面渲染成
|
|
931
|
+
// `0;134m` 类残片且永久留在新 scrollback(见 terminalWriteQueue.INBAND_RESET doc)。
|
|
932
|
+
diagCount('resyncCount');
|
|
933
|
+
this._writeQ.reset();
|
|
934
|
+
this._writeQ.push(INBAND_RESET + '\x1b[33m[cx-viewer] output skipped during congestion\x1b[0m\r\n');
|
|
935
|
+
if (msg.data) this._writeQ.push(msg.data);
|
|
936
|
+
} else if (msg.type === 'exit') {
|
|
937
|
+
this._flushWrite();
|
|
938
|
+
this.terminal.write(`\r\n\x1b[33m${t('ui.terminal.exited', { code: msg.exitCode ?? '?' })}\x1b[0m\r\n`);
|
|
939
|
+
this.terminal.write(`\x1b[90m${t('ui.terminal.pressEnterForShell')}\x1b[0m\r\n`);
|
|
940
|
+
} else if (msg.type === 'editor-open') {
|
|
941
|
+
if (this.props.onEditorOpen) {
|
|
942
|
+
this.props.onEditorOpen(msg.sessionId, msg.filePath);
|
|
943
|
+
}
|
|
944
|
+
} else if (msg.type === 'state') {
|
|
945
|
+
if (!msg.running && msg.exitCode !== null) {
|
|
946
|
+
this._flushWrite();
|
|
947
|
+
this.terminal.write(`\x1b[33m${t('ui.terminal.exited', { code: msg.exitCode })}\x1b[0m\r\n`);
|
|
948
|
+
this.terminal.write(`\x1b[90m${t('ui.terminal.pressEnterForShell')}\x1b[0m\r\n`);
|
|
949
|
+
}
|
|
950
|
+
} else if (msg.type === 'toast') {
|
|
951
|
+
this._flushWrite();
|
|
952
|
+
this.terminal.write(`\r\n\x1b[33m⚠ ${msg.message}\x1b[0m\r\n`);
|
|
953
|
+
}
|
|
954
|
+
} catch {}
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
// ws 状态变更:open 时 sendResize(原 onopen 行为);close 时 reset xterm(避免残留半截 ANSI)。
|
|
958
|
+
// 重连本身由 Provider 内部 2s 退避完成,组件无感。
|
|
959
|
+
_onTerminalWsState = (state) => {
|
|
960
|
+
if (state === 'open') {
|
|
961
|
+
this.sendResize();
|
|
962
|
+
} else if (state === 'close') {
|
|
963
|
+
// writeQ 与 terminal 都要 reset(同 data-resync 分支):close 时队列若有积压,
|
|
964
|
+
// 重连 replay 会把这些字节再发一遍 → 局部重复。只 reset xterm 漏了写队列。
|
|
965
|
+
// 重置走带内序列防 WriteBuffer 残留撕裂(同 data-resync 分支注释)。
|
|
966
|
+
this._writeQ?.reset();
|
|
967
|
+
this._writeQ?.push(INBAND_RESET);
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
// write-queue 积压丢弃后请求服务端快照对齐(服务端回 data-resync,见上方分支)。
|
|
972
|
+
// 持续过载期 _maybeTrim 每次 push 都可能触发——2s 节流防请求风暴(服务端另有冷却兜底)。
|
|
973
|
+
_requestResync() {
|
|
974
|
+
const nowTs = Date.now();
|
|
975
|
+
if (nowTs - (this._lastResyncReqAt || 0) < 2000) return;
|
|
976
|
+
this._lastResyncReqAt = nowTs;
|
|
977
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
978
|
+
try { this.ws.send(JSON.stringify({ type: 'resync-request' })); } catch {}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
sendResize() {
|
|
983
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN && this.terminal) {
|
|
984
|
+
const msg = {
|
|
985
|
+
type: 'resize',
|
|
986
|
+
cols: this.terminal.cols,
|
|
987
|
+
rows: this.terminal.rows,
|
|
988
|
+
};
|
|
989
|
+
if (isMobile) msg.mobile = true;
|
|
990
|
+
this.ws.send(JSON.stringify(msg));
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Android 定时渲染维护:走 xterm 官方 escape hatch,不通过改 DOM 高度骗 fit() 重建渲染层。
|
|
995
|
+
_refreshAndroidTerminalRendering = () => {
|
|
996
|
+
const term = this.terminal;
|
|
997
|
+
if (!term) return;
|
|
998
|
+
|
|
999
|
+
try { term.clearTextureAtlas?.(); } catch {}
|
|
1000
|
+
try { term.refresh(0, term.rows - 1); } catch {}
|
|
1001
|
+
// 仅动态 fit 路径才允许 fit();移动端非 iPad 由 _mobileFixedResize 维护固定 60 列,
|
|
1002
|
+
// 调 fit() 会把固定列覆盖成按容器算的动态列,破坏移动端契约。
|
|
1003
|
+
const canFit = !isMobile || isPad;
|
|
1004
|
+
if (canFit) {
|
|
1005
|
+
this._fitPreservingScroll();
|
|
1006
|
+
this.sendResize();
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
// 调 fitAddon.fit() 并尽量保持用户的 scrollTop —— fit() 会重排 viewport,scrollHeight 变后
|
|
1011
|
+
// scrollTop 默认会被重置。贴底时直接贴底,其他情况按 prev/new scrollHeight 比例换算。
|
|
1012
|
+
// ResizeObserver 路径和 Android 60s 自动维护都走它,行为统一。
|
|
1013
|
+
_fitPreservingScroll = () => {
|
|
1014
|
+
if (!this.fitAddon || !this.containerRef.current) return;
|
|
1015
|
+
// 0/极小尺寸守卫(同 ScratchTerminal):容器可见但高度≈0 时 FitAddon 会算出 2×1
|
|
1016
|
+
// (MINIMUM_COLS=2/ROWS=1) 并经 sendResize 发给 PTY → ConPTY 全屏 reflow 崩坏 +
|
|
1017
|
+
// 恢复时二次重绘。尺寸无效时跳过 fit,等容器恢复后 ResizeObserver 再触发。
|
|
1018
|
+
const el = this.containerRef.current;
|
|
1019
|
+
if (el.offsetWidth <= 0 || el.offsetHeight <= 0) return;
|
|
1020
|
+
try {
|
|
1021
|
+
const vp = this.containerRef.current.querySelector('.xterm-viewport');
|
|
1022
|
+
const prevScrollTop = vp?.scrollTop ?? 0;
|
|
1023
|
+
const prevScrollHeight = vp?.scrollHeight ?? 1;
|
|
1024
|
+
const wasAtBottom = vp ? (prevScrollTop + vp.clientHeight >= prevScrollHeight - 5) : true;
|
|
1025
|
+
this.fitAddon.fit();
|
|
1026
|
+
if (vp) {
|
|
1027
|
+
if (wasAtBottom) {
|
|
1028
|
+
vp.scrollTop = vp.scrollHeight;
|
|
1029
|
+
} else {
|
|
1030
|
+
const ratio = prevScrollHeight > 0 ? prevScrollTop / prevScrollHeight : 0;
|
|
1031
|
+
vp.scrollTop = ratio * vp.scrollHeight;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
} catch {}
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1037
|
+
setupResizeObserver() {
|
|
1038
|
+
// 移动端使用固定尺寸,不需要 ResizeObserver(iPad 例外,走动态 fit)
|
|
1039
|
+
if (isMobile && !isPad) return;
|
|
1040
|
+
|
|
1041
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
1042
|
+
if (this._resizeDebounceTimer) clearTimeout(this._resizeDebounceTimer);
|
|
1043
|
+
this._resizeDebounceTimer = setTimeout(() => {
|
|
1044
|
+
this._resizeDebounceTimer = null;
|
|
1045
|
+
this._fitPreservingScroll();
|
|
1046
|
+
this.sendResize();
|
|
1047
|
+
}, 150);
|
|
1048
|
+
});
|
|
1049
|
+
if (this.containerRef.current) {
|
|
1050
|
+
this.resizeObserver.observe(this.containerRef.current);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
_loadWebglAddon(isRetry) {
|
|
1055
|
+
// Sticky disable 检查:7 天内曾因 GPU 长任务降级过,跳过 WebGL 加载
|
|
1056
|
+
try {
|
|
1057
|
+
const until = localStorage.getItem(WEBGL_STICKY_KEY);
|
|
1058
|
+
if (until && Date.now() < Number(until)) return;
|
|
1059
|
+
} catch {}
|
|
1060
|
+
|
|
1061
|
+
try {
|
|
1062
|
+
this.webglAddon = new WebglAddon();
|
|
1063
|
+
this.webglAddon.onContextLoss(() => {
|
|
1064
|
+
this._disposeWebgl();
|
|
1065
|
+
if (!isRetry) {
|
|
1066
|
+
this._webglRecoveryTimer = setTimeout(() => {
|
|
1067
|
+
this._webglRecoveryTimer = null;
|
|
1068
|
+
this._loadWebglAddon(true);
|
|
1069
|
+
}, 1000);
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
this.terminal.loadAddon(this.webglAddon);
|
|
1073
|
+
this._installWebglLongtaskGuard();
|
|
1074
|
+
} catch {
|
|
1075
|
+
// 若 loadAddon(activate) 中途抛出,addon 可能已创建 GL context/canvas/onContextLoss
|
|
1076
|
+
// disposable——只置 null 会泄漏,先显式 dispose 再置 null。
|
|
1077
|
+
try { this.webglAddon?.dispose(); } catch {}
|
|
1078
|
+
this.webglAddon = null;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
_disposeWebgl() {
|
|
1083
|
+
// 清掉 pending 的 onContextLoss 恢复重试:longtask 降级(写 sticky)与 1s 恢复 timer
|
|
1084
|
+
// 交错时,不再依赖 sticky 检查巧合拦截重载已判废的 addon(mac 上 context loss 更频繁)。
|
|
1085
|
+
if (this._webglRecoveryTimer) {
|
|
1086
|
+
clearTimeout(this._webglRecoveryTimer);
|
|
1087
|
+
this._webglRecoveryTimer = null;
|
|
1088
|
+
}
|
|
1089
|
+
if (this._webglLongtaskObserver) {
|
|
1090
|
+
this._webglLongtaskObserver.disconnect();
|
|
1091
|
+
this._webglLongtaskObserver = null;
|
|
1092
|
+
}
|
|
1093
|
+
if (this.webglAddon) {
|
|
1094
|
+
try { this.webglAddon.dispose(); } catch {}
|
|
1095
|
+
this.webglAddon = null;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// GPU 长任务监测:滑动窗口内累积 stall 达到阈值则自动降级到 DOM 渲染器
|
|
1100
|
+
_installWebglLongtaskGuard() {
|
|
1101
|
+
if (typeof PerformanceObserver === 'undefined') return;
|
|
1102
|
+
const startTime = performance.now();
|
|
1103
|
+
const recentStalls = [];
|
|
1104
|
+
|
|
1105
|
+
this._webglLongtaskObserver = new PerformanceObserver((list) => {
|
|
1106
|
+
if (!this.webglAddon) return;
|
|
1107
|
+
const now = performance.now();
|
|
1108
|
+
if (now - startTime < WEBGL_GRACE_MS) return;
|
|
1109
|
+
for (const entry of list.getEntries()) {
|
|
1110
|
+
if (entry.duration >= WEBGL_STALL_MS) {
|
|
1111
|
+
recentStalls.push(now);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
while (recentStalls.length > 0 && now - recentStalls[0] > WEBGL_STALL_WINDOW_MS) {
|
|
1115
|
+
recentStalls.shift();
|
|
1116
|
+
}
|
|
1117
|
+
if (recentStalls.length >= WEBGL_STALL_COUNT) {
|
|
1118
|
+
console.warn('[cx-viewer] WebGL longtask guard: %d stalls in %ds, falling back to DOM renderer',
|
|
1119
|
+
recentStalls.length, WEBGL_STALL_WINDOW_MS / 1000);
|
|
1120
|
+
try { localStorage.setItem(WEBGL_STICKY_KEY, String(Date.now() + WEBGL_STICKY_TTL_MS)); } catch {}
|
|
1121
|
+
this._disposeWebgl();
|
|
1122
|
+
this.terminal?.refresh(0, this.terminal.rows - 1);
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
try {
|
|
1126
|
+
this._webglLongtaskObserver.observe({ type: 'longtask', buffered: false });
|
|
1127
|
+
} catch {
|
|
1128
|
+
this._webglLongtaskObserver = null;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* 移动端固定 60 列:通过调整 fontSize 使 60 列恰好撑满屏幕宽度,
|
|
1134
|
+
* 行数根据缩放后的行高和可用高度动态计算。
|
|
1135
|
+
*/
|
|
1136
|
+
_mobileFixedResize() {
|
|
1137
|
+
if (!this.terminal) return;
|
|
1138
|
+
|
|
1139
|
+
// 从 xterm 渲染器获取当前字符尺寸
|
|
1140
|
+
const cellDims = this.terminal._core?._renderService?.dimensions?.css?.cell;
|
|
1141
|
+
if (!cellDims || !cellDims.width || !cellDims.height) {
|
|
1142
|
+
// 渲染器尚未就绪,延迟重试
|
|
1143
|
+
setTimeout(() => this._mobileFixedResize(), 50);
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
const MOBILE_COLS = 60;
|
|
1148
|
+
const padX = 16; // 8px * 2 容器内边距
|
|
1149
|
+
const padY = 8; // 4px * 2
|
|
1150
|
+
const topBarHeight = 40;
|
|
1151
|
+
const keybarHeight = 52;
|
|
1152
|
+
|
|
1153
|
+
const availableWidth = window.innerWidth - padX;
|
|
1154
|
+
const availableHeight = window.innerHeight - topBarHeight - keybarHeight - padY;
|
|
1155
|
+
|
|
1156
|
+
// 根据当前 fontSize 和 charWidth 的比例,计算让 60 列恰好填满宽度所需的 fontSize
|
|
1157
|
+
const currentFontSize = this.terminal.options.fontSize;
|
|
1158
|
+
const currentCharWidth = cellDims.width;
|
|
1159
|
+
const targetFontSize = Math.floor(currentFontSize * availableWidth / (MOBILE_COLS * currentCharWidth) * 10) / 10;
|
|
1160
|
+
|
|
1161
|
+
// 更新字号,xterm 会重新渲染
|
|
1162
|
+
this.terminal.options.fontSize = targetFontSize;
|
|
1163
|
+
|
|
1164
|
+
// 等渲染器更新后再计算行数
|
|
1165
|
+
requestAnimationFrame(() => {
|
|
1166
|
+
if (!this.terminal) return; // unmount 竞态:this.terminal._core 对 null 取属性会抛
|
|
1167
|
+
const newCellDims = this.terminal._core?._renderService?.dimensions?.css?.cell;
|
|
1168
|
+
const lineHeight = newCellDims?.height || cellDims.height;
|
|
1169
|
+
const rows = Math.max(5, Math.min(Math.floor(availableHeight / lineHeight), 100));
|
|
1170
|
+
|
|
1171
|
+
this.terminal.resize(MOBILE_COLS, rows);
|
|
1172
|
+
this.sendResize();
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* 写入节流:委托给 TerminalWriteQueue(src/utils/terminalWriteQueue.js)。
|
|
1178
|
+
* 行为与原实现等价 —— 每帧最多 write 一个 32KB chunk,rAF 续约。
|
|
1179
|
+
* 收益:消除原 `_writeBuffer = _writeBuffer.slice(N)` 的 O(n²) 字符串切片
|
|
1180
|
+
* + UTF-16 surrogate 守卫 + 异常时不死循环 + drain 修 unmount 数据丢失。
|
|
1181
|
+
*/
|
|
1182
|
+
_throttledWrite(data) {
|
|
1183
|
+
this._writeQ.push(data);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// 同步排空(exit/state/toast 路径在自身 write 前调用,保留既有顺序语义)。
|
|
1187
|
+
// 注:与原实现一样,这里只 drain 已积累 buffer,不影响 xterm 内部 parser 异步队列。
|
|
1188
|
+
_flushWrite() {
|
|
1189
|
+
this._writeQ.drain();
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
_handlePaste = (e) => {
|
|
1193
|
+
// 主动粘贴进行中:阻止原生 paste 叠加(见 attachCustomKeyEventHandler 的 _activePasteInFlight 注释)
|
|
1194
|
+
if (this._activePasteInFlight) { e.preventDefault?.(); e.stopPropagation?.(); return; }
|
|
1195
|
+
// 检查剪贴板中是否包含图片,如有则上传并将路径插入终端
|
|
1196
|
+
const items = e.clipboardData?.items;
|
|
1197
|
+
if (items) {
|
|
1198
|
+
for (const item of items) {
|
|
1199
|
+
if (item.type.startsWith('image/')) {
|
|
1200
|
+
e.preventDefault();
|
|
1201
|
+
e.stopPropagation();
|
|
1202
|
+
const file = item.getAsFile();
|
|
1203
|
+
if (file) this._uploadClipboardImage(file);
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const rawText = e.clipboardData?.getData('text');
|
|
1210
|
+
if (!rawText || !this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
1211
|
+
// paste-injection 防护 + bracketed-paste 包裹决策抽到 planPasteSend(见 utils/terminalClipboard.js):
|
|
1212
|
+
// 剪贴板内嵌的 \x1b[201~ 会提前闭合包裹、余下字节作真实按键注入;xterm 6.0 自动包裹不 sanitize
|
|
1213
|
+
// (上游 7.0 才修),故含该序列 / 多行时我们接管(preventDefault + 自行 sanitize 包裹),
|
|
1214
|
+
// 否则返回 null 交回 xterm / 浏览器原生处理。
|
|
1215
|
+
const data = planPasteSend(rawText, {
|
|
1216
|
+
bracketedPasteMode: this.terminal?.modes?.bracketedPasteMode,
|
|
1217
|
+
active: false,
|
|
1218
|
+
sanitize: sanitizeBracketPasteText,
|
|
1219
|
+
});
|
|
1220
|
+
if (data != null) {
|
|
1221
|
+
e.preventDefault();
|
|
1222
|
+
e.stopPropagation();
|
|
1223
|
+
this.ws.send(JSON.stringify({ type: 'input', data }));
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
// 主动粘贴的安全送出:复用 planPasteSend(active=true)。区别于 _handlePaste:这里没有原生
|
|
1228
|
+
// paste 事件让 xterm 自动包裹,故 bracketedPasteMode 时也自行包裹(planPasteSend 内已处理)。
|
|
1229
|
+
_pasteText = (rawText) => {
|
|
1230
|
+
if (!rawText || !this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
1231
|
+
const data = planPasteSend(rawText, {
|
|
1232
|
+
bracketedPasteMode: this.terminal?.modes?.bracketedPasteMode,
|
|
1233
|
+
active: true,
|
|
1234
|
+
sanitize: sanitizeBracketPasteText,
|
|
1235
|
+
});
|
|
1236
|
+
if (data != null) this.ws.send(JSON.stringify({ type: 'input', data }));
|
|
1237
|
+
};
|
|
1238
|
+
|
|
1239
|
+
// Ctrl+V 主动读剪贴板:优先取图片(与 _handlePaste 一致地走上传),否则取文本走 _pasteText。
|
|
1240
|
+
// 取不到文本(如剪贴板仅 text/html)或 read() 不可用 / 失败时,显式回退到 readText。
|
|
1241
|
+
_activePaste = async () => {
|
|
1242
|
+
const clip = navigator.clipboard;
|
|
1243
|
+
if (clip?.read) {
|
|
1244
|
+
let items = null;
|
|
1245
|
+
try { items = await clip.read(); }
|
|
1246
|
+
catch (err) { console.warn('[CX Viewer] clipboard.read() 失败,回退 readText', err); }
|
|
1247
|
+
if (items) {
|
|
1248
|
+
for (const item of items) {
|
|
1249
|
+
const imgType = item.types?.find((ty) => ty.startsWith('image/'));
|
|
1250
|
+
if (imgType) {
|
|
1251
|
+
try {
|
|
1252
|
+
const blob = await item.getType(imgType);
|
|
1253
|
+
this._uploadClipboardImage(new File([blob], 'clipboard-image', { type: imgType }));
|
|
1254
|
+
return;
|
|
1255
|
+
} catch { /* 取图失败 → 落到文本 */ }
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
// 无图片:从 ClipboardItem 取 text/plain;取到则发送返回,取不到(仅 html 等)显式落到 readText
|
|
1259
|
+
const withText = items.find((it) => it.types?.includes('text/plain'));
|
|
1260
|
+
if (withText) {
|
|
1261
|
+
try {
|
|
1262
|
+
const text = await (await withText.getType('text/plain')).text();
|
|
1263
|
+
if (text) this._pasteText(text);
|
|
1264
|
+
return;
|
|
1265
|
+
} catch { /* 取文本失败 → 落到 readText */ }
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
if (clip?.readText) {
|
|
1270
|
+
try {
|
|
1271
|
+
const text = await clip.readText();
|
|
1272
|
+
if (text) this._pasteText(text);
|
|
1273
|
+
} catch (err) {
|
|
1274
|
+
console.warn('[CX Viewer] active paste failed; fall back to Ctrl+Shift+V', err);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
_uploadClipboardImage = async (file) => {
|
|
1280
|
+
try {
|
|
1281
|
+
const optimized = await downscaleForRetina(file);
|
|
1282
|
+
const path = await uploadFileAndGetPath(optimized);
|
|
1283
|
+
if (this.props.onFilePath) this.props.onFilePath(path);
|
|
1284
|
+
// Notify other views/devices about the uploaded image
|
|
1285
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
1286
|
+
this.ws.send(JSON.stringify({ type: 'image-upload-notify', path, source: 'terminal' }));
|
|
1287
|
+
}
|
|
1288
|
+
if (this.terminal) this.terminal.focus();
|
|
1289
|
+
} catch (err) {
|
|
1290
|
+
console.error('[CX Viewer] Clipboard image upload failed:', err);
|
|
1291
|
+
message.error(t('ui.terminal.pasteImageFailed'));
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
handleVirtualKey = (seq) => {
|
|
1297
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
1298
|
+
this.ws.send(JSON.stringify({ type: 'input', data: seq }));
|
|
1299
|
+
}
|
|
1300
|
+
// 手机上不 focus 终端,避免弹出系统软键盘;主动 blur 防止先前已聚焦
|
|
1301
|
+
if (isMobile && !isPad) {
|
|
1302
|
+
const ta = this.containerRef.current?.querySelector('.xterm-helper-textarea');
|
|
1303
|
+
if (ta) ta.blur();
|
|
1304
|
+
} else {
|
|
1305
|
+
this.terminal?.focus();
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
|
|
1309
|
+
/**
|
|
1310
|
+
* 移动端虚拟按键触摸处理:区分点击与拖动滚动。
|
|
1311
|
+
* 仅当触摸位移 < 阈值时才视为点击并触发按键,否则视为滚动不触发。
|
|
1312
|
+
*/
|
|
1313
|
+
_vkTouchStart = (e) => {
|
|
1314
|
+
e.preventDefault(); // 阻止触摸导致 xterm textarea 获焦弹出键盘
|
|
1315
|
+
const touch = e.touches[0];
|
|
1316
|
+
this._vkStartX = touch.clientX;
|
|
1317
|
+
this._vkStartY = touch.clientY;
|
|
1318
|
+
this._vkMoved = false;
|
|
1319
|
+
this._vkTarget = e.currentTarget;
|
|
1320
|
+
this._vkTarget.classList.add(styles.virtualKeyPressed);
|
|
1321
|
+
};
|
|
1322
|
+
|
|
1323
|
+
_vkTouchMove = (e) => {
|
|
1324
|
+
if (this._vkMoved) return;
|
|
1325
|
+
const touch = e.touches[0];
|
|
1326
|
+
const dx = touch.clientX - this._vkStartX;
|
|
1327
|
+
const dy = touch.clientY - this._vkStartY;
|
|
1328
|
+
if (dx * dx + dy * dy > 64) { // 8px 阈值
|
|
1329
|
+
this._vkMoved = true;
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
|
|
1333
|
+
_vkTouchEnd = (action, e) => {
|
|
1334
|
+
e.preventDefault(); // 阻止后续 ghost click
|
|
1335
|
+
this._vkTarget?.classList.remove(styles.virtualKeyPressed);
|
|
1336
|
+
this._vkTarget = null;
|
|
1337
|
+
if (!this._vkMoved) {
|
|
1338
|
+
action();
|
|
1339
|
+
}
|
|
1340
|
+
};
|
|
1341
|
+
|
|
1342
|
+
handleFileUpload = async (e) => {
|
|
1343
|
+
const file = e.target.files?.[0];
|
|
1344
|
+
if (!file) return;
|
|
1345
|
+
try {
|
|
1346
|
+
const path = await uploadFileAndGetPath(file);
|
|
1347
|
+
if (this.props.onFilePath) this.props.onFilePath(path);
|
|
1348
|
+
// Notify other views/devices about the uploaded file
|
|
1349
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
1350
|
+
this.ws.send(JSON.stringify({ type: 'image-upload-notify', path, source: 'terminal' }));
|
|
1351
|
+
}
|
|
1352
|
+
// refocus terminal after upload (skip on mobile to avoid system keyboard popup)
|
|
1353
|
+
if ((!isMobile || isPad) && this.terminal) this.terminal.focus();
|
|
1354
|
+
} catch (err) {
|
|
1355
|
+
console.error('[CX Viewer] Upload failed:', err);
|
|
1356
|
+
}
|
|
1357
|
+
// reset so same file can be re-selected
|
|
1358
|
+
e.target.value = '';
|
|
1359
|
+
};
|
|
1360
|
+
|
|
1361
|
+
// --- 预置快捷方式相关 ---
|
|
1362
|
+
_savePresetShortcuts = (items, dismissed) => {
|
|
1363
|
+
if (this.props.onUpdatePreferences) {
|
|
1364
|
+
this.props.onUpdatePreferences(buildPresetShortcutsPayload(items, dismissed));
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
|
|
1368
|
+
handlePresetAdd = () => {
|
|
1369
|
+
const description = this.state.presetAddText.trim();
|
|
1370
|
+
const teamName = this.state.presetAddName.trim();
|
|
1371
|
+
if (!description && !teamName) return;
|
|
1372
|
+
const { presetEditId, presetItems } = this.state;
|
|
1373
|
+
let next;
|
|
1374
|
+
if (presetEditId) {
|
|
1375
|
+
next = presetItems.map(i => {
|
|
1376
|
+
if (i.id !== presetEditId) return i;
|
|
1377
|
+
const updated = { ...i, teamName, description };
|
|
1378
|
+
if (i.builtinId) updated.modified = true;
|
|
1379
|
+
return updated;
|
|
1380
|
+
});
|
|
1381
|
+
} else {
|
|
1382
|
+
next = [...presetItems, { id: Date.now(), teamName, description }];
|
|
1383
|
+
}
|
|
1384
|
+
this.setState({ presetItems: next, presetAddVisible: false, presetAddText: '', presetAddName: '', presetEditId: null });
|
|
1385
|
+
this._savePresetShortcuts(next);
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
handlePresetDelete = () => {
|
|
1389
|
+
const { presetItems, presetSelected } = this.state;
|
|
1390
|
+
if (presetSelected.size === 0) return;
|
|
1391
|
+
// 收集被删除的内置项 builtinId
|
|
1392
|
+
const dismissed = new Set(this._dismissedBuiltinPresets || []);
|
|
1393
|
+
for (const item of presetItems) {
|
|
1394
|
+
if (presetSelected.has(item.id) && item.builtinId) {
|
|
1395
|
+
dismissed.add(item.builtinId);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
this._dismissedBuiltinPresets = dismissed;
|
|
1399
|
+
const next = presetItems.filter(i => !presetSelected.has(i.id));
|
|
1400
|
+
this.setState({ presetItems: next, presetSelected: new Set() });
|
|
1401
|
+
this._savePresetShortcuts(next, dismissed);
|
|
1402
|
+
};
|
|
1403
|
+
|
|
1404
|
+
handlePresetToggle = (id) => {
|
|
1405
|
+
this.setState(prev => {
|
|
1406
|
+
const next = new Set(prev.presetSelected);
|
|
1407
|
+
next.has(id) ? next.delete(id) : next.add(id);
|
|
1408
|
+
return { presetSelected: next };
|
|
1409
|
+
});
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1412
|
+
// --- 拖拽排序 ---
|
|
1413
|
+
_dragIdx = null;
|
|
1414
|
+
_dragOverIdx = null;
|
|
1415
|
+
|
|
1416
|
+
handleDragStart = (idx, e) => {
|
|
1417
|
+
e.stopPropagation();
|
|
1418
|
+
this._dragIdx = idx;
|
|
1419
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
1420
|
+
e.dataTransfer.setData('text/x-preset-reorder', String(idx));
|
|
1421
|
+
requestAnimationFrame(() => this.forceUpdate());
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1424
|
+
handleDragOver = (idx, e) => {
|
|
1425
|
+
e.preventDefault();
|
|
1426
|
+
e.stopPropagation();
|
|
1427
|
+
e.dataTransfer.dropEffect = 'move';
|
|
1428
|
+
if (this._dragOverIdx !== idx) {
|
|
1429
|
+
this._dragOverIdx = idx;
|
|
1430
|
+
this.forceUpdate();
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
|
|
1434
|
+
handleDragEnd = (e) => {
|
|
1435
|
+
if (e) e.stopPropagation();
|
|
1436
|
+
this._dragIdx = null;
|
|
1437
|
+
this._dragOverIdx = null;
|
|
1438
|
+
this.forceUpdate();
|
|
1439
|
+
};
|
|
1440
|
+
|
|
1441
|
+
handleDragLeave = (idx, e) => {
|
|
1442
|
+
e.stopPropagation();
|
|
1443
|
+
if (this._dragOverIdx === idx) {
|
|
1444
|
+
this._dragOverIdx = null;
|
|
1445
|
+
this.forceUpdate();
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
|
|
1449
|
+
handleDrop = (idx, e) => {
|
|
1450
|
+
e.preventDefault();
|
|
1451
|
+
e.stopPropagation();
|
|
1452
|
+
const from = this._dragIdx;
|
|
1453
|
+
if (from === null || from === idx) { this.handleDragEnd(); return; }
|
|
1454
|
+
const items = [...this.state.presetItems];
|
|
1455
|
+
const [moved] = items.splice(from, 1);
|
|
1456
|
+
items.splice(from < idx ? idx - 1 : idx, 0, moved);
|
|
1457
|
+
this.setState({ presetItems: items });
|
|
1458
|
+
this._savePresetShortcuts(items);
|
|
1459
|
+
this.handleDragEnd();
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1462
|
+
// 级联子菜单 hover-intent 共享实现见 utils/quickMenuHoverIntent。
|
|
1463
|
+
// iPad 无 hover 不参与(tap 的 synthetic mouseEnter 会与 click 切换互抵),由点击行切换兜底;
|
|
1464
|
+
_qmHover = createQuickMenuHoverIntent({
|
|
1465
|
+
getExpanded: () => this.state.quickSettingsExpanded,
|
|
1466
|
+
setExpanded: (k) => this.setState({ quickSettingsExpanded: k }),
|
|
1467
|
+
skip: () => isPad,
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1470
|
+
handlePresetSend = (description) => {
|
|
1471
|
+
if (!description) return;
|
|
1472
|
+
this.setState({ quickSettingsOpen: false, quickSettingsExpanded: null });
|
|
1473
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
1474
|
+
this.ws.send(JSON.stringify({
|
|
1475
|
+
type: 'input-sequential',
|
|
1476
|
+
chunks: buildBracketPasteSubmitChunks(description),
|
|
1477
|
+
settleMs: BRACKET_PASTE_SUBMIT_SETTLE_MS,
|
|
1478
|
+
}));
|
|
1479
|
+
}
|
|
1480
|
+
if ((!isMobile || isPad) && this.terminal) this.terminal.focus();
|
|
1481
|
+
};
|
|
1482
|
+
|
|
1483
|
+
handleClearContext = () => {
|
|
1484
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
1485
|
+
this.ws.send(JSON.stringify({
|
|
1486
|
+
type: 'input-sequential',
|
|
1487
|
+
chunks: buildBracketPasteSubmitChunks('/clear'),
|
|
1488
|
+
settleMs: BRACKET_PASTE_SUBMIT_SETTLE_MS,
|
|
1489
|
+
}));
|
|
1490
|
+
this.props.onClearContextOptimistic?.();
|
|
1491
|
+
}
|
|
1492
|
+
if ((!isMobile || isPad) && this.terminal) this.terminal.focus();
|
|
1493
|
+
};
|
|
1494
|
+
|
|
1495
|
+
handleUltraplanSend = () => {
|
|
1496
|
+
const trimmed = this.state.ultraplanPrompt.trim();
|
|
1497
|
+
if (!trimmed && this.state.ultraplanFiles.length === 0) return;
|
|
1498
|
+
const filePaths = this.state.ultraplanFiles.map(f => `"${f.path}"`).join(' ');
|
|
1499
|
+
const userInput = filePaths ? (trimmed ? `${filePaths} ${trimmed}` : filePaths) : trimmed;
|
|
1500
|
+
const variant = this.state.ultraplanVariant;
|
|
1501
|
+
let assembled;
|
|
1502
|
+
if (typeof variant === 'string' && variant.startsWith('custom:')) {
|
|
1503
|
+
const id = variant.slice('custom:'.length);
|
|
1504
|
+
const item = this.state.customUltraplanExperts.find(e => e.id === id);
|
|
1505
|
+
if (!item) { return; }
|
|
1506
|
+
assembled = buildLocalUltraplan(userInput, 'custom', undefined, item.content);
|
|
1507
|
+
} else {
|
|
1508
|
+
assembled = buildLocalUltraplan(userInput, variant);
|
|
1509
|
+
}
|
|
1510
|
+
// 先校验再重置,避免空模板导致用户输入被静默清空
|
|
1511
|
+
if (!assembled) return;
|
|
1512
|
+
this.setState({ ultraplanOpen: false, ultraplanPrompt: '', ultraplanVariant: 'codeExpert', ultraplanFiles: [] });
|
|
1513
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
1514
|
+
this.ws.send(JSON.stringify({
|
|
1515
|
+
type: 'input-sequential',
|
|
1516
|
+
chunks: buildBracketPasteSubmitChunks(assembled),
|
|
1517
|
+
settleMs: BRACKET_PASTE_SUBMIT_SETTLE_MS,
|
|
1518
|
+
}));
|
|
1519
|
+
}
|
|
1520
|
+
if ((!isMobile || isPad) && this.terminal) this.terminal.focus();
|
|
1521
|
+
};
|
|
1522
|
+
|
|
1523
|
+
openCustomUltraplanEditor = (item) => {
|
|
1524
|
+
// UltraPlan 是 Antd Popover(自带"点击外部即关闭"),编辑器是 Antd Modal(portal 挂 body)。
|
|
1525
|
+
// 打开编辑器时不主动收 Popover——保留 UltraPlan 在背景里(编辑器自带蒙层会压暗);
|
|
1526
|
+
// 编辑器 mask 引起的"外部点击"由下面 Popover 的 onOpenChange 守卫拦下(customUltraplanEditOpen)。
|
|
1527
|
+
this.setState({
|
|
1528
|
+
customUltraplanEditOpen: true,
|
|
1529
|
+
customUltraplanEditing: item || null,
|
|
1530
|
+
});
|
|
1531
|
+
};
|
|
1532
|
+
|
|
1533
|
+
closeCustomUltraplanEditor = () => {
|
|
1534
|
+
this.setState({
|
|
1535
|
+
customUltraplanEditOpen: false,
|
|
1536
|
+
customUltraplanEditing: null,
|
|
1537
|
+
});
|
|
1538
|
+
};
|
|
1539
|
+
|
|
1540
|
+
// UltraPlan 文件 / 专家逻辑委托给共享控制器(见 ../utils/ultraplanController)。方法名保持不变,render 零改动。
|
|
1541
|
+
saveCustomUltraplanExpert = (...a) => this._ultraplan.saveExpert(...a);
|
|
1542
|
+
|
|
1543
|
+
deleteCustomUltraplanExpert = (...a) => this._ultraplan.deleteExpert(...a);
|
|
1544
|
+
|
|
1545
|
+
// tab 条上每个专家的图标:内置 code=<> / research=放大镜,自定义=星形。
|
|
1546
|
+
handleUltraplanUpload = (...a) => this._ultraplan.handleUpload(...a);
|
|
1547
|
+
|
|
1548
|
+
handleUltraplanPaste = (...a) => this._ultraplan.handlePaste(...a);
|
|
1549
|
+
|
|
1550
|
+
handleUltraplanRemoveFile = (...a) => this._ultraplan.handleRemoveFile(...a);
|
|
1551
|
+
|
|
1552
|
+
render() {
|
|
1553
|
+
const { pendingImages, onRemovePendingImage } = this.props;
|
|
1554
|
+
return (
|
|
1555
|
+
<div className={styles.terminalPanel}>
|
|
1556
|
+
{/* === 主 terminal (Codex TUI 渲染区) ===
|
|
1557
|
+
外层 .terminalContainer:padding + focus 边线;内层 .terminalHost:xterm 实际父容器,
|
|
1558
|
+
margin-bottom 4px 让 fitAddon 拿到的高度始终 -4px,xterm-screen 接触不到下方 toolbar */}
|
|
1559
|
+
<div
|
|
1560
|
+
className={`${styles.terminalContainer}${this.state.terminalFocused ? ` ${styles.terminalContainerFocused}` : ''}`}
|
|
1561
|
+
>
|
|
1562
|
+
<div ref={this.containerRef} className={styles.terminalHost} />
|
|
1563
|
+
</div>
|
|
1564
|
+
{pendingImages?.length > 0 && (
|
|
1565
|
+
<div className={styles.pendingFileStrip}>
|
|
1566
|
+
{pendingImages.map((img, i) => {
|
|
1567
|
+
const fileName = img.path.split('/').pop() || img.path;
|
|
1568
|
+
const isImage = /\.(png|jpe?g|gif|svg|bmp|webp|avif|ico|icns)$/i.test(fileName);
|
|
1569
|
+
const src = apiUrl(`/api/file-raw?path=${encodeURIComponent(img.path)}`);
|
|
1570
|
+
return isImage ? (
|
|
1571
|
+
<div key={img.path} className={styles.pendingImageItem}>
|
|
1572
|
+
<img
|
|
1573
|
+
src={src}
|
|
1574
|
+
className={styles.pendingImageThumb}
|
|
1575
|
+
alt={fileName}
|
|
1576
|
+
role="button"
|
|
1577
|
+
tabIndex={0}
|
|
1578
|
+
onClick={() => this.setState({ lightbox: { src, alt: fileName } })}
|
|
1579
|
+
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.setState({ lightbox: { src, alt: fileName } }); } }}
|
|
1580
|
+
/>
|
|
1581
|
+
<ConfirmRemoveButton
|
|
1582
|
+
title={t('ui.chatInput.confirmRemoveImage')}
|
|
1583
|
+
onConfirm={() => onRemovePendingImage?.(i)}
|
|
1584
|
+
className={styles.pendingImageRemove}
|
|
1585
|
+
ariaLabel={t('ui.chatInput.removeImage')}
|
|
1586
|
+
>×</ConfirmRemoveButton>
|
|
1587
|
+
</div>
|
|
1588
|
+
) : (
|
|
1589
|
+
<span key={img.path} className={styles.pendingFileTag}>
|
|
1590
|
+
<span className={styles.pendingFileName}>{fileName}</span>
|
|
1591
|
+
<ConfirmRemoveButton
|
|
1592
|
+
title={t('ui.chatInput.confirmRemoveFile')}
|
|
1593
|
+
onConfirm={() => onRemovePendingImage?.(i)}
|
|
1594
|
+
className={styles.pendingFileClose}
|
|
1595
|
+
ariaLabel={t('ui.chatInput.removeImage')}
|
|
1596
|
+
>×</ConfirmRemoveButton>
|
|
1597
|
+
</span>
|
|
1598
|
+
);
|
|
1599
|
+
})}
|
|
1600
|
+
</div>
|
|
1601
|
+
)}
|
|
1602
|
+
<input type="file" ref={this.fileInputRef} className={styles.hiddenFileInput} onChange={this.handleFileUpload} />
|
|
1603
|
+
{(!isMobile || isPad) && (
|
|
1604
|
+
<div className={styles.terminalToolbar}>
|
|
1605
|
+
<div className={styles.toolbarLeft}>
|
|
1606
|
+
<Popover
|
|
1607
|
+
trigger="click"
|
|
1608
|
+
placement="top"
|
|
1609
|
+
open={this.state.quickSettingsOpen}
|
|
1610
|
+
onOpenChange={(v) => {
|
|
1611
|
+
if (!v) this._qmHover.cancel();
|
|
1612
|
+
this.setState(v ? { quickSettingsOpen: true } : { quickSettingsOpen: false, quickSettingsExpanded: null });
|
|
1613
|
+
}}
|
|
1614
|
+
overlayInnerStyle={{ background: 'var(--bg-elevated)', border: '1px solid var(--border-hover)', borderRadius: 8, padding: 4, minWidth: 200 }}
|
|
1615
|
+
content={
|
|
1616
|
+
<div className={styles.presetMenu}>
|
|
1617
|
+
<QuickAutoApproveRows
|
|
1618
|
+
approvalsReviewer={this.props.approvalsReviewer}
|
|
1619
|
+
planAutoApproveSeconds={this.props.planAutoApproveSeconds}
|
|
1620
|
+
onApprovalsReviewerChange={this.props.onApprovalsReviewerChange}
|
|
1621
|
+
onPlanAutoApproveChange={this.props.onPlanAutoApproveChange}
|
|
1622
|
+
expandedKey={this.state.quickSettingsExpanded}
|
|
1623
|
+
onToggle={(k) => this.setState({ quickSettingsExpanded: k })}
|
|
1624
|
+
onHoverEnter={this._qmHover.enter}
|
|
1625
|
+
onHoverLeave={this._qmHover.leave}
|
|
1626
|
+
/>
|
|
1627
|
+
{/* AgentTeam 快捷指令(自工具栏独立按钮迁入,置于菜单底部)。 */}
|
|
1628
|
+
<div
|
|
1629
|
+
className={`${chrome.quickMenuGroup} ${this.state.quickSettingsExpanded === 'agentteam' ? chrome.quickMenuGroupOpen : ''}`}
|
|
1630
|
+
onMouseEnter={() => this._qmHover.enter('agentteam')}
|
|
1631
|
+
onMouseLeave={() => this._qmHover.leave('agentteam')}
|
|
1632
|
+
>
|
|
1633
|
+
<button
|
|
1634
|
+
className={chrome.quickMenuRow}
|
|
1635
|
+
onClick={() => this.setState(s => ({ quickSettingsExpanded: s.quickSettingsExpanded === 'agentteam' ? null : 'agentteam' }))}
|
|
1636
|
+
>
|
|
1637
|
+
<span className={chrome.quickMenuRowIcon}><AgentTeamIcon /></span>
|
|
1638
|
+
<span className={chrome.quickMenuLabel}>{t('ui.terminal.agentTeam')}</span>
|
|
1639
|
+
<span className={chrome.quickMenuCaret}>▸</span>
|
|
1640
|
+
</button>
|
|
1641
|
+
<div className={chrome.quickMenuSubWrap}>
|
|
1642
|
+
<div className={chrome.quickMenuSub}>
|
|
1643
|
+
<button className={`${styles.presetMenuItem} ${styles.presetMenuItemMuted}`} onClick={() => this.setState({ quickSettingsOpen: false, quickSettingsExpanded: null, presetModalVisible: true })}>
|
|
1644
|
+
{t('ui.terminal.customShortcuts')}
|
|
1645
|
+
</button>
|
|
1646
|
+
{this.state.presetItems.length === 0 ? (
|
|
1647
|
+
<div className={styles.popoverEmptyHint}>—</div>
|
|
1648
|
+
) : (
|
|
1649
|
+
this.state.presetItems.map(item => {
|
|
1650
|
+
const isBuiltinRaw = item.builtinId && !item.modified;
|
|
1651
|
+
const name = isBuiltinRaw ? t(item.teamName) : item.teamName;
|
|
1652
|
+
const desc = isBuiltinRaw ? t(item.description) : item.description;
|
|
1653
|
+
return (
|
|
1654
|
+
<button key={item.id} className={styles.presetMenuItem} onClick={() => this.handlePresetSend(desc)} title={desc}>
|
|
1655
|
+
{name || desc}
|
|
1656
|
+
</button>
|
|
1657
|
+
);
|
|
1658
|
+
})
|
|
1659
|
+
)}
|
|
1660
|
+
</div>
|
|
1661
|
+
</div>
|
|
1662
|
+
</div>
|
|
1663
|
+
</div>
|
|
1664
|
+
}
|
|
1665
|
+
>
|
|
1666
|
+
<button
|
|
1667
|
+
className={`${styles.toolbarBtn} ${styles.quickSettingsBtn} ${this.state.quickSettingsOpen ? styles.quickSettingsBtnOpen : ''}`}
|
|
1668
|
+
title={t('ui.terminal.quickSettings')}
|
|
1669
|
+
>
|
|
1670
|
+
<span className={styles.quickSettingsIcon}><span className={styles.quickSettingsGlyph} style={SPARKLE_MASK_STYLE} aria-hidden="true" /></span>
|
|
1671
|
+
</button>
|
|
1672
|
+
</Popover>
|
|
1673
|
+
<Popover
|
|
1674
|
+
trigger="click"
|
|
1675
|
+
placement="top"
|
|
1676
|
+
open={this.state.ultraplanOpen}
|
|
1677
|
+
onOpenChange={(v) => {
|
|
1678
|
+
// customUltraplanEditOpen 守卫:编辑器(Antd Modal)通过 portal 挂 body,它的 mask 对本
|
|
1679
|
+
// Popover 而言是"外部点击"。事件顺序保护住了我们:
|
|
1680
|
+
// rc-trigger 注册 capture-phase mousedown(useWinClick.js)→ 早于 rc-dialog 的 bubble
|
|
1681
|
+
// click → onOpenChange 触发时 mask click 还没冒泡到 editor.onCancel,
|
|
1682
|
+
// customUltraplanEditOpen 仍为 true,直接拦掉本次 Popover 关闭。
|
|
1683
|
+
// (这个机制与 React 是否批量化 setState 无关,对 React 19 / concurrent 仍稳。)
|
|
1684
|
+
// ultraplanManagerOpen(管理专家弹窗)同理:它也是 portal Modal,关闭它不应连带收起本 Popover。
|
|
1685
|
+
if (!v && (this.state.lightbox || this.state.ultraplanLightbox || this.state.ultraplanConfirming || this.state.customUltraplanEditOpen || this.state.ultraplanManagerOpen)) return;
|
|
1686
|
+
if (!v) this.setState({ ultraplanOpen: false });
|
|
1687
|
+
}}
|
|
1688
|
+
overlayClassName="cxv-ultraplan-popover"
|
|
1689
|
+
overlayInnerStyle={ultraplanOverlayInnerStyle(this.state.ultraplanPopoverSize)}
|
|
1690
|
+
content={
|
|
1691
|
+
<UltraplanPanel
|
|
1692
|
+
variant={this.state.ultraplanVariant}
|
|
1693
|
+
prompt={this.state.ultraplanPrompt}
|
|
1694
|
+
files={this.state.ultraplanFiles}
|
|
1695
|
+
customExperts={this.state.customUltraplanExperts}
|
|
1696
|
+
expertOrder={this.state.ultraplanExpertOrder}
|
|
1697
|
+
expertHidden={this.state.ultraplanExpertHidden}
|
|
1698
|
+
onVariantChange={(v) => this.setState({ ultraplanVariant: v })}
|
|
1699
|
+
onPromptChange={(p) => this.setState({ ultraplanPrompt: p })}
|
|
1700
|
+
onSend={this.handleUltraplanSend}
|
|
1701
|
+
onUpload={this.handleUltraplanUpload}
|
|
1702
|
+
onPaste={this.handleUltraplanPaste}
|
|
1703
|
+
onRemoveFile={this.handleUltraplanRemoveFile}
|
|
1704
|
+
onClose={() => this.setState({ ultraplanOpen: false })}
|
|
1705
|
+
onOpenManager={() => this.setState({ ultraplanManagerOpen: true })}
|
|
1706
|
+
onOpenCustomEditor={this.openCustomUltraplanEditor}
|
|
1707
|
+
onPreviewImage={(lb) => this.setState({ ultraplanLightbox: lb })}
|
|
1708
|
+
onConfirmingChange={(open) => this.setState({ ultraplanConfirming: open })}
|
|
1709
|
+
onSizeChange={(size) => this.setState({ ultraplanPopoverSize: size })}
|
|
1710
|
+
/>
|
|
1711
|
+
}
|
|
1712
|
+
>
|
|
1713
|
+
<button className={`${styles.toolbarBtn} ${this.state.ultraplanOpen ? styles.ultraToggleActive : ''}`} onClick={() => this.setState({ ultraplanOpen: true })} title={t('ui.ultraplan')}>
|
|
1714
|
+
<span className={styles.ultraToggleIcon} style={ULTRAPLAN_MASK_STYLE} aria-hidden="true" />
|
|
1715
|
+
<span className={styles.ultraToggleLabel}>UltraPlan</span>
|
|
1716
|
+
</button>
|
|
1717
|
+
</Popover>
|
|
1718
|
+
<button className={styles.toolbarBtn} onClick={() => this.fileInputRef.current?.click()} title={t('ui.terminal.upload')}>
|
|
1719
|
+
<UploadIcon />
|
|
1720
|
+
</button>
|
|
1721
|
+
{(() => {
|
|
1722
|
+
// i18n 是单句 "X?Y。" 结构,按 ? / ? 拆成 Popconfirm 的 title + description 以换行呈现
|
|
1723
|
+
const confirmFull = t('ui.chatInput.clearContextConfirm');
|
|
1724
|
+
const qIdx = Math.max(confirmFull.indexOf('?'), confirmFull.indexOf('?'));
|
|
1725
|
+
const confirmTitle = qIdx > 0 ? confirmFull.slice(0, qIdx + 1) : confirmFull;
|
|
1726
|
+
const confirmDesc = qIdx > 0 ? confirmFull.slice(qIdx + 1).trim() : null;
|
|
1727
|
+
return (
|
|
1728
|
+
<Popconfirm
|
|
1729
|
+
title={confirmTitle}
|
|
1730
|
+
description={confirmDesc}
|
|
1731
|
+
okText={t('ui.chatInput.clearContext')}
|
|
1732
|
+
cancelText={t('ui.common.confirmCancel')}
|
|
1733
|
+
okButtonProps={{ danger: true }}
|
|
1734
|
+
placement="top"
|
|
1735
|
+
onConfirm={this.handleClearContext}
|
|
1736
|
+
>
|
|
1737
|
+
<button className={styles.toolbarBtn} title={t('ui.chatInput.clearContext')}>
|
|
1738
|
+
<TrashIcon />
|
|
1739
|
+
</button>
|
|
1740
|
+
</Popconfirm>
|
|
1741
|
+
);
|
|
1742
|
+
})()}
|
|
1743
|
+
</div>
|
|
1744
|
+
{/* 中段:血条 portal slot;左/右两端控件分别由 toolbarLeft / toolbarRight 包裹,
|
|
1745
|
+
slot 在中间用 flex-basis 200 + flex-shrink 1 自适应左右控件之间的空间,封顶 200px */}
|
|
1746
|
+
<div className={styles.ctxBarSlot} ref={this.props.setContextBarSlot} />
|
|
1747
|
+
<div className={styles.toolbarRight}>
|
|
1748
|
+
<button
|
|
1749
|
+
className={`${styles.toolbarBtn}${this.state.scratchOpen ? ` ${styles.toolbarBtnActive}` : ''}`}
|
|
1750
|
+
onClick={this.toggleScratch}
|
|
1751
|
+
aria-pressed={this.state.scratchOpen}
|
|
1752
|
+
aria-label={this.state.scratchOpen ? t('ui.terminal.scratchTerminalClose') : t('ui.terminal.scratchTerminalOpen')}
|
|
1753
|
+
title={this.state.scratchOpen ? t('ui.terminal.scratchTerminalClose') : t('ui.terminal.scratchTerminalOpen')}
|
|
1754
|
+
>
|
|
1755
|
+
<ScratchTerminalIcon />
|
|
1756
|
+
</button>
|
|
1757
|
+
</div>
|
|
1758
|
+
</div>
|
|
1759
|
+
)}
|
|
1760
|
+
{(!isMobile || isPad) && this.state.scratchOpen && (
|
|
1761
|
+
<>
|
|
1762
|
+
<div
|
|
1763
|
+
className={`${styles.scratchResizer}${this.state.isDraggingScratch ? ` ${styles.scratchResizerDragging}` : ''}`}
|
|
1764
|
+
onPointerDown={this.handleScratchResizerPointerDown}
|
|
1765
|
+
onPointerMove={this.handleScratchResizerPointerMove}
|
|
1766
|
+
onPointerUp={this.handleScratchResizerPointerUp}
|
|
1767
|
+
onPointerCancel={this.handleScratchResizerPointerUp}
|
|
1768
|
+
role="separator"
|
|
1769
|
+
aria-orientation="horizontal"
|
|
1770
|
+
aria-label={t('ui.terminal.scratchResizer')}
|
|
1771
|
+
/>
|
|
1772
|
+
<div
|
|
1773
|
+
ref={this._scratchWrapRef}
|
|
1774
|
+
className={styles.scratchWrap}
|
|
1775
|
+
>
|
|
1776
|
+
<div className={styles.scratchTabs} role="tablist" aria-orientation="vertical">
|
|
1777
|
+
{this.state.scratchTabs.map((tab, idx) => {
|
|
1778
|
+
const isActive = tab.id === this.state.activeScratchTabId;
|
|
1779
|
+
const isLast = this.state.scratchTabs.length === 1;
|
|
1780
|
+
// 同名 shell 重复时追加序号区分
|
|
1781
|
+
// 占位 'zsh':老版本 server 不发 shellBasename 时也展示符合 macOS 默认 shell 的名字;
|
|
1782
|
+
// 新 server 的 WS state 消息携带真实 basename 后会覆盖(bash/fish 用户也对)
|
|
1783
|
+
const baseLabel = this.state.scratchShellBasename || 'zsh';
|
|
1784
|
+
const label = baseLabel + (this.state.scratchTabs.length > 1 ? ` ${idx + 1}` : '');
|
|
1785
|
+
return (
|
|
1786
|
+
<div
|
|
1787
|
+
key={tab.id}
|
|
1788
|
+
role="tab"
|
|
1789
|
+
aria-selected={isActive}
|
|
1790
|
+
tabIndex={isActive ? 0 : -1}
|
|
1791
|
+
className={`${styles.scratchTab}${isActive ? ` ${styles.scratchTabActive}` : ''}`}
|
|
1792
|
+
onClick={() => this.handleScratchTabClick(tab.id)}
|
|
1793
|
+
title={label}
|
|
1794
|
+
>
|
|
1795
|
+
<span className={styles.scratchTabIcon}><ScratchTerminalIcon /></span>
|
|
1796
|
+
<span className={styles.scratchTabLabel}>{label}</span>
|
|
1797
|
+
{!isLast && (
|
|
1798
|
+
<button
|
|
1799
|
+
className={styles.scratchTabClose}
|
|
1800
|
+
onClick={(e) => this.handleScratchTabClose(tab.id, e)}
|
|
1801
|
+
title={t('ui.terminal.scratchTabClose')}
|
|
1802
|
+
aria-label={t('ui.terminal.scratchTabClose')}
|
|
1803
|
+
>
|
|
1804
|
+
<CloseIcon />
|
|
1805
|
+
</button>
|
|
1806
|
+
)}
|
|
1807
|
+
</div>
|
|
1808
|
+
);
|
|
1809
|
+
})}
|
|
1810
|
+
<button
|
|
1811
|
+
className={styles.scratchTabAdd}
|
|
1812
|
+
onClick={this.handleScratchTabAdd}
|
|
1813
|
+
disabled={this.state.scratchTabs.length >= SCRATCH_TAB_MAX}
|
|
1814
|
+
title={t('ui.terminal.scratchTabAdd')}
|
|
1815
|
+
aria-label={t('ui.terminal.scratchTabAdd')}
|
|
1816
|
+
>
|
|
1817
|
+
<PlusIcon />
|
|
1818
|
+
</button>
|
|
1819
|
+
</div>
|
|
1820
|
+
<div className={`${styles.scratchPanes}${this.state.scratchFocused ? ` ${styles.scratchPanesFocused}` : ''}`}>
|
|
1821
|
+
{this.state.scratchTabs.map((tab) => {
|
|
1822
|
+
const isActive = tab.id === this.state.activeScratchTabId;
|
|
1823
|
+
return (
|
|
1824
|
+
<div
|
|
1825
|
+
key={tab.id}
|
|
1826
|
+
className={`${styles.scratchPane}${isActive ? ` ${styles.scratchPaneActive}` : ''}`}
|
|
1827
|
+
role="tabpanel"
|
|
1828
|
+
>
|
|
1829
|
+
<ScratchTerminal
|
|
1830
|
+
ref={this._getScratchRef(tab.id)}
|
|
1831
|
+
id={tab.id}
|
|
1832
|
+
onFocusChange={(f) => this.handleScratchTabFocusChange(tab.id, f)}
|
|
1833
|
+
onShellInfo={this.handleScratchShellInfo}
|
|
1834
|
+
/>
|
|
1835
|
+
</div>
|
|
1836
|
+
);
|
|
1837
|
+
})}
|
|
1838
|
+
</div>
|
|
1839
|
+
</div>
|
|
1840
|
+
</>
|
|
1841
|
+
)}
|
|
1842
|
+
{(isMobile && !isPad) && (
|
|
1843
|
+
<div className={styles.virtualKeybar}>
|
|
1844
|
+
{VIRTUAL_KEYS.map(k => (
|
|
1845
|
+
<button
|
|
1846
|
+
key={k.label}
|
|
1847
|
+
className={styles.virtualKey}
|
|
1848
|
+
onTouchStart={this._vkTouchStart}
|
|
1849
|
+
onTouchMove={this._vkTouchMove}
|
|
1850
|
+
onTouchEnd={(e) => this._vkTouchEnd(() => this.handleVirtualKey(k.seq), e)}
|
|
1851
|
+
>
|
|
1852
|
+
{k.label}
|
|
1853
|
+
</button>
|
|
1854
|
+
))}
|
|
1855
|
+
{/* TODO: 移动端文件上传 - 受限于浏览器安全策略,触摸事件链中 input.click() 无法触发文件选择器
|
|
1856
|
+
<span className={styles.vkSeparator} />
|
|
1857
|
+
<button
|
|
1858
|
+
className={`${styles.virtualKey} ${styles.vkAction}`}
|
|
1859
|
+
onClick={() => {
|
|
1860
|
+
this.fileInputRef.current?.click();
|
|
1861
|
+
const ta = this.containerRef.current?.querySelector('.xterm-helper-textarea');
|
|
1862
|
+
if (ta) ta.blur();
|
|
1863
|
+
}}
|
|
1864
|
+
title={t('ui.terminal.upload')}
|
|
1865
|
+
>
|
|
1866
|
+
<UploadIcon />
|
|
1867
|
+
</button>
|
|
1868
|
+
*/}
|
|
1869
|
+
{this.state.presetItems.length > 0 && (
|
|
1870
|
+
<>
|
|
1871
|
+
<span className={styles.vkSeparator} />
|
|
1872
|
+
{this.state.presetItems.map(item => {
|
|
1873
|
+
const isBuiltinRaw = item.builtinId && !item.modified;
|
|
1874
|
+
const name = isBuiltinRaw ? t(item.teamName) : item.teamName;
|
|
1875
|
+
const desc = isBuiltinRaw ? t(item.description) : item.description;
|
|
1876
|
+
return (
|
|
1877
|
+
<button
|
|
1878
|
+
key={item.id}
|
|
1879
|
+
className={`${styles.virtualKey} ${styles.vkAction} ${styles.vkTeamPreset}`}
|
|
1880
|
+
onTouchStart={this._vkTouchStart}
|
|
1881
|
+
onTouchMove={this._vkTouchMove}
|
|
1882
|
+
onTouchEnd={(e) => this._vkTouchEnd(() => this.handlePresetSend(desc), e)}
|
|
1883
|
+
title={desc}
|
|
1884
|
+
>
|
|
1885
|
+
<AgentTeamIcon /><span className={styles.vkTeamLabel}>{name || desc}</span>
|
|
1886
|
+
</button>
|
|
1887
|
+
);
|
|
1888
|
+
})}
|
|
1889
|
+
</>
|
|
1890
|
+
)}
|
|
1891
|
+
</div>
|
|
1892
|
+
)}
|
|
1893
|
+
{/* 预置快捷方式弹窗 */}
|
|
1894
|
+
<Modal
|
|
1895
|
+
title={t('ui.terminal.presetShortcuts')}
|
|
1896
|
+
open={this.state.presetModalVisible}
|
|
1897
|
+
onCancel={() => this.setState({ presetModalVisible: false, presetSelected: new Set() })}
|
|
1898
|
+
footer={null}
|
|
1899
|
+
width={800}
|
|
1900
|
+
styles={{ content: { background: 'var(--bg-elevated)', border: '1px solid var(--border-light)' }, header: { background: 'var(--bg-elevated)', borderBottom: 'none' } }}
|
|
1901
|
+
>
|
|
1902
|
+
<div className={styles.presetSectionHeader}>
|
|
1903
|
+
<span className={styles.presetSectionTitle}>{t('ui.terminal.agentTeamCustom')}</span>
|
|
1904
|
+
</div>
|
|
1905
|
+
<div className={styles.presetList} onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); }} onDrop={(e) => { e.preventDefault(); e.stopPropagation(); }}>
|
|
1906
|
+
{this.state.presetItems.length === 0 ? (
|
|
1907
|
+
<div className={styles.presetListEmptyHint}>—</div>
|
|
1908
|
+
) : (
|
|
1909
|
+
this.state.presetItems.map((item, idx) => {
|
|
1910
|
+
const isBuiltinRaw = item.builtinId && !item.modified;
|
|
1911
|
+
const name = isBuiltinRaw ? t(item.teamName) : item.teamName;
|
|
1912
|
+
const desc = isBuiltinRaw ? t(item.description) : item.description;
|
|
1913
|
+
const isDragging = this._dragIdx === idx;
|
|
1914
|
+
const isDragOver = this._dragOverIdx === idx && this._dragIdx !== idx;
|
|
1915
|
+
return (
|
|
1916
|
+
<div
|
|
1917
|
+
key={item.id}
|
|
1918
|
+
className={`${styles.presetRow} ${isDragging ? styles.presetRowDragging : ''} ${isDragOver ? styles.presetRowDragOver : ''}`}
|
|
1919
|
+
onDragOver={(e) => this.handleDragOver(idx, e)}
|
|
1920
|
+
onDragLeave={(e) => this.handleDragLeave(idx, e)}
|
|
1921
|
+
onDrop={(e) => this.handleDrop(idx, e)}
|
|
1922
|
+
onDragEnd={this.handleDragEnd}
|
|
1923
|
+
>
|
|
1924
|
+
<span
|
|
1925
|
+
className={styles.dragHandle}
|
|
1926
|
+
draggable
|
|
1927
|
+
onDragStart={(e) => this.handleDragStart(idx, e)}
|
|
1928
|
+
>
|
|
1929
|
+
<svg viewBox="0 0 10 16" width="10" height="16" fill="currentColor">
|
|
1930
|
+
<circle cx="3" cy="3" r="1.2"/><circle cx="7" cy="3" r="1.2"/>
|
|
1931
|
+
<circle cx="3" cy="8" r="1.2"/><circle cx="7" cy="8" r="1.2"/>
|
|
1932
|
+
<circle cx="3" cy="13" r="1.2"/><circle cx="7" cy="13" r="1.2"/>
|
|
1933
|
+
</svg>
|
|
1934
|
+
</span>
|
|
1935
|
+
<Checkbox
|
|
1936
|
+
checked={this.state.presetSelected.has(item.id)}
|
|
1937
|
+
onChange={() => this.handlePresetToggle(item.id)}
|
|
1938
|
+
/>
|
|
1939
|
+
<span className={styles.presetName} title={name}>{name || '—'}</span>
|
|
1940
|
+
<span className={styles.presetText} title={desc}>{desc}</span>
|
|
1941
|
+
<Button size="small" type="link" onClick={() => this.setState({ presetAddVisible: true, presetAddName: isBuiltinRaw ? t(item.teamName) : item.teamName, presetAddText: isBuiltinRaw ? t(item.description) : item.description, presetEditId: item.id })}>{t('ui.terminal.editItem')}</Button>
|
|
1942
|
+
</div>
|
|
1943
|
+
);
|
|
1944
|
+
})
|
|
1945
|
+
)}
|
|
1946
|
+
</div>
|
|
1947
|
+
<div className={styles.presetActions}>
|
|
1948
|
+
<Button size="small" danger disabled={this.state.presetSelected.size === 0} onClick={this.handlePresetDelete}>{t('ui.terminal.deleteSelected')}</Button>
|
|
1949
|
+
<Button size="small" onClick={() => this.setState({ presetAddVisible: true, presetAddName: '', presetAddText: '', presetEditId: null })}>{t('ui.terminal.addItem')}</Button>
|
|
1950
|
+
</div>
|
|
1951
|
+
</Modal>
|
|
1952
|
+
|
|
1953
|
+
{/* 添加快捷方式弹窗 */}
|
|
1954
|
+
<Modal
|
|
1955
|
+
title={this.state.presetEditId ? t('ui.terminal.editItem') : t('ui.terminal.addItem')}
|
|
1956
|
+
open={this.state.presetAddVisible}
|
|
1957
|
+
onCancel={() => this.setState({ presetAddVisible: false, presetAddName: '', presetAddText: '', presetEditId: null })}
|
|
1958
|
+
onOk={this.handlePresetAdd}
|
|
1959
|
+
okText={this.state.presetEditId ? t('ui.ok') : t('ui.terminal.addItem')}
|
|
1960
|
+
cancelText={t('ui.cancel')}
|
|
1961
|
+
okButtonProps={{ disabled: !this.state.presetAddText.trim() && !this.state.presetAddName.trim() }}
|
|
1962
|
+
width="fit-content"
|
|
1963
|
+
styles={{ content: { background: 'var(--bg-elevated)', border: '1px solid var(--border-light)' }, header: { background: 'var(--bg-elevated)', borderBottom: 'none' } }}
|
|
1964
|
+
>
|
|
1965
|
+
<div className={styles.presetFormField}>
|
|
1966
|
+
<label className={styles.presetFormLabel}>Team {t('ui.terminal.teamName')}</label>
|
|
1967
|
+
<input
|
|
1968
|
+
className={styles.presetInput}
|
|
1969
|
+
placeholder={t('ui.terminal.teamNamePlaceholder')}
|
|
1970
|
+
value={this.state.presetAddName}
|
|
1971
|
+
onChange={(e) => this.setState({ presetAddName: e.target.value })}
|
|
1972
|
+
/>
|
|
1973
|
+
</div>
|
|
1974
|
+
<div>
|
|
1975
|
+
<label className={styles.presetFormLabel}>Team {t('ui.terminal.teamDesc')}</label>
|
|
1976
|
+
<textarea
|
|
1977
|
+
className={styles.presetTextarea}
|
|
1978
|
+
rows={15}
|
|
1979
|
+
placeholder={t('ui.terminal.presetInputPlaceholder')}
|
|
1980
|
+
value={this.state.presetAddText}
|
|
1981
|
+
onChange={(e) => this.setState({ presetAddText: e.target.value })}
|
|
1982
|
+
onKeyDown={(e) => { if (e.key === 'Enter') e.stopPropagation(); }}
|
|
1983
|
+
/>
|
|
1984
|
+
</div>
|
|
1985
|
+
</Modal>
|
|
1986
|
+
<CustomUltraplanEditModal
|
|
1987
|
+
open={this.state.customUltraplanEditOpen}
|
|
1988
|
+
initial={this.state.customUltraplanEditing}
|
|
1989
|
+
onSave={this.saveCustomUltraplanExpert}
|
|
1990
|
+
onDelete={this.deleteCustomUltraplanExpert}
|
|
1991
|
+
onClose={this.closeCustomUltraplanEditor}
|
|
1992
|
+
/>
|
|
1993
|
+
<UltraplanExpertManagerModal
|
|
1994
|
+
open={this.state.ultraplanManagerOpen}
|
|
1995
|
+
customExperts={this.state.customUltraplanExperts}
|
|
1996
|
+
order={this.state.ultraplanExpertOrder}
|
|
1997
|
+
hidden={this.state.ultraplanExpertHidden}
|
|
1998
|
+
onPersist={({ order, hidden }) => this._ultraplan.persistExpertLayout({ order, hidden })}
|
|
1999
|
+
onClose={() => this.setState({ ultraplanManagerOpen: false })}
|
|
2000
|
+
/>
|
|
2001
|
+
{this.state.lightbox && (
|
|
2002
|
+
<ImageLightbox
|
|
2003
|
+
src={this.state.lightbox.src}
|
|
2004
|
+
alt={this.state.lightbox.alt}
|
|
2005
|
+
onClose={() => this.setState({ lightbox: null })}
|
|
2006
|
+
/>
|
|
2007
|
+
)}
|
|
2008
|
+
{this.state.ultraplanLightbox && (
|
|
2009
|
+
<ImageLightbox
|
|
2010
|
+
src={this.state.ultraplanLightbox.src}
|
|
2011
|
+
alt={this.state.ultraplanLightbox.alt}
|
|
2012
|
+
zIndex={1200}
|
|
2013
|
+
onClose={() => this.setState({ ultraplanLightbox: null })}
|
|
2014
|
+
/>
|
|
2015
|
+
)}
|
|
2016
|
+
</div>
|
|
2017
|
+
);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
export default TerminalPanel;
|