ops-tune-workbench-plugin 0.0.0-stage → 0.0.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/NOTICE.md +14 -0
  2. package/README.md +39 -2
  3. package/cordis.patch.yml +20 -0
  4. package/lib/client.js +4253 -0
  5. package/lib/host.js +2518 -0
  6. package/package.json +53 -4
  7. package/skills/tool-ops-tune/INSTALL.md +52 -0
  8. package/skills/tool-ops-tune/README.md +167 -0
  9. package/skills/tool-ops-tune/SKILL.md +93 -0
  10. package/skills/tool-ops-tune/agent/AGENTS.md +195 -0
  11. package/skills/tool-ops-tune/agent/agents/perf-implementer.md +68 -0
  12. package/skills/tool-ops-tune/agent/agents/perf-reviewer.md +86 -0
  13. package/skills/tool-ops-tune/agent/workflows/task-prompts.md +362 -0
  14. package/skills/tool-ops-tune/agent/workflows/troubleshooting.md +43 -0
  15. package/skills/tool-ops-tune/docs/figures/opstune-architecture.png +0 -0
  16. package/skills/tool-ops-tune/docs/figures/opstune-architecture.svg +253 -0
  17. package/skills/tool-ops-tune/docs/ops-tune-design.md +404 -0
  18. package/skills/tool-ops-tune/docs//347/224/250/344/276/213/346/236/204/345/273/272.md +714 -0
  19. package/skills/tool-ops-tune/docs//347/237/245/350/257/206/345/272/223/350/247/204/350/214/203.md +103 -0
  20. package/skills/tool-ops-tune/evals/evals.json +242 -0
  21. package/skills/tool-ops-tune/ops-collect/SKILL.md +146 -0
  22. package/skills/tool-ops-tune/ops-collect/references/tool-routing.md +86 -0
  23. package/skills/tool-ops-tune/ops-collect/scripts/collect_board.sh +444 -0
  24. package/skills/tool-ops-tune/ops-collect/scripts/collect_cases.py +368 -0
  25. package/skills/tool-ops-tune/ops-collect/scripts/collect_sim.sh +134 -0
  26. package/skills/tool-ops-tune/ops-collect/scripts/csv_to_db.py +314 -0
  27. package/skills/tool-ops-tune/ops-collect/scripts/db_bench.py +545 -0
  28. package/skills/tool-ops-tune/ops-collect/scripts/facts_bridge.py +1063 -0
  29. package/skills/tool-ops-tune/ops-collect/scripts/isolation_test.py +454 -0
  30. package/skills/tool-ops-tune/ops-collect/scripts/jsonl_to_cases.py +163 -0
  31. package/skills/tool-ops-tune/ops-family/SKILL.md +76 -0
  32. package/skills/tool-ops-tune/ops-family/references/broadcast/advanced_tiling.md +92 -0
  33. package/skills/tool-ops-tune/ops-family/references/broadcast/broadcast_design.md +160 -0
  34. package/skills/tool-ops-tune/ops-family/references/broadcast/code/broadcast_add_kernel.cpp +126 -0
  35. package/skills/tool-ops-tune/ops-family/references/broadcast/code/broadcast_add_tiling.cpp +144 -0
  36. package/skills/tool-ops-tune/ops-family/references/broadcast/code/broadcast_tiling_common.h +401 -0
  37. package/skills/tool-ops-tune/ops-family/references/broadcast/code/onedim_add_kernel.cpp +107 -0
  38. package/skills/tool-ops-tune/ops-family/references/broadcast/code/onedim_add_tiling.cpp +64 -0
  39. package/skills/tool-ops-tune/ops-family/references/broadcast/datacopypad_design.md +46 -0
  40. package/skills/tool-ops-tune/ops-family/references/broadcast/guide.md +14 -0
  41. package/skills/tool-ops-tune/ops-family/references/broadcast/nddma_design.md +53 -0
  42. package/skills/tool-ops-tune/ops-family/references/broadcast/onedim_design.md +114 -0
  43. package/skills/tool-ops-tune/ops-family/references/broadcast/ub_broadcast_design.md +67 -0
  44. package/skills/tool-ops-tune/ops-family/references/common/datacopy_optimization_design.md +351 -0
  45. package/skills/tool-ops-tune/ops-family/references/common/index.md +9 -0
  46. package/skills/tool-ops-tune/ops-family/references/common/tail_block_design.md +268 -0
  47. package/skills/tool-ops-tune/ops-family/references/common/timing_harness/index.md +38 -0
  48. package/skills/tool-ops-tune/ops-family/references/common/timing_harness/timing_harness_aclrt_event.cpp +199 -0
  49. package/skills/tool-ops-tune/ops-family/references/common/timing_harness/timing_harness_torch_event.py +130 -0
  50. package/skills/tool-ops-tune/ops-family/references/common/ub_resident_design.md +342 -0
  51. package/skills/tool-ops-tune/ops-family/references/conversion/guide.md +69 -0
  52. package/skills/tool-ops-tune/ops-family/references/conversion/index.md +4 -0
  53. package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_base.template +81 -0
  54. package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_big_dim.template +217 -0
  55. package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_cut_one_axis.template +336 -0
  56. package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_cut_two_axis.template +781 -0
  57. package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_n_last.template +340 -0
  58. package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_small_shape.template +321 -0
  59. package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_tensor_move.template +102 -0
  60. package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_transdata_5hd.template +480 -0
  61. package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_transdata_5hd_021axis.template +533 -0
  62. package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_with_gather.template +688 -0
  63. package/skills/tool-ops-tune/ops-family/references/conversion/templates/transpose_tiling_data.template +192 -0
  64. package/skills/tool-ops-tune/ops-family/references/conversion/transpose_big_dim.md +220 -0
  65. package/skills/tool-ops-tune/ops-family/references/conversion/transpose_cut_one_axis.md +175 -0
  66. package/skills/tool-ops-tune/ops-family/references/conversion/transpose_cut_two_axis.md +177 -0
  67. package/skills/tool-ops-tune/ops-family/references/conversion/transpose_fusion_design.md +398 -0
  68. package/skills/tool-ops-tune/ops-family/references/conversion/transpose_gather.md +191 -0
  69. package/skills/tool-ops-tune/ops-family/references/conversion/transpose_n_last.md +276 -0
  70. package/skills/tool-ops-tune/ops-family/references/conversion/transpose_small_shape.md +315 -0
  71. package/skills/tool-ops-tune/ops-family/references/conversion/transpose_tensor_move.md +203 -0
  72. package/skills/tool-ops-tune/ops-family/references/conversion/transpose_tiling_data.md +176 -0
  73. package/skills/tool-ops-tune/ops-family/references/conversion/transpose_vconv_021.md +206 -0
  74. package/skills/tool-ops-tune/ops-family/references/conversion/transpose_vconv_5hd.md +179 -0
  75. package/skills/tool-ops-tune/ops-family/references/elementwise/double_buffer_design.md +288 -0
  76. package/skills/tool-ops-tune/ops-family/references/elementwise/guide.md +46 -0
  77. package/skills/tool-ops-tune/ops-family/references/elementwise/index.md +10 -0
  78. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/CMakeLists.txt +131 -0
  79. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/README.md +55 -0
  80. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/op_extension/add_custom_torch.cpp +80 -0
  81. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/op_extension/ops.h +23 -0
  82. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/op_extension/register.cpp +30 -0
  83. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/op_host/add_custom.asc +149 -0
  84. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/op_host/data_utils.h +97 -0
  85. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/op_kernel/add_custom_kernel.asc +136 -0
  86. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/op_kernel/add_custom_tiling.h +31 -0
  87. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/run.sh +86 -0
  88. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/scripts/gen_data.py +70 -0
  89. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/scripts/golden.py +34 -0
  90. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/scripts/test_torch.py +99 -0
  91. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/scripts/verify_result.py +56 -0
  92. package/skills/tool-ops-tune/ops-family/references/elementwise/templates/usage_guide.md +61 -0
  93. package/skills/tool-ops-tune/ops-family/references/elementwise/vector_efficiency_design.md +203 -0
  94. package/skills/tool-ops-tune/ops-family/references/fa/fa_mixed_kernel_design.md +174 -0
  95. package/skills/tool-ops-tune/ops-family/references/fa/fa_roofline_design.md +141 -0
  96. package/skills/tool-ops-tune/ops-family/references/fa/guide.md +87 -0
  97. package/skills/tool-ops-tune/ops-family/references/knowledge_gaps.json +56 -0
  98. package/skills/tool-ops-tune/ops-family/references/matmul/best-practices-guide.md +14 -0
  99. package/skills/tool-ops-tune/ops-family/references/matmul/fullload_design.md +344 -0
  100. package/skills/tool-ops-tune/ops-family/references/matmul/guide.md +52 -0
  101. package/skills/tool-ops-tune/ops-family/references/matmul/mte2_preload_design.md +320 -0
  102. package/skills/tool-ops-tune/ops-family/references/matmul/pingpong_design.md +254 -0
  103. package/skills/tool-ops-tune/ops-family/references/matmul/scale_coalescing_design.md +282 -0
  104. package/skills/tool-ops-tune/ops-family/references/matmul/streamk_design.md +374 -0
  105. package/skills/tool-ops-tune/ops-family/references/matmul/swat_design.md +423 -0
  106. package/skills/tool-ops-tune/ops-family/references/matmul/templates/matmul_basic_blaze.template +165 -0
  107. package/skills/tool-ops-tune/ops-family/references/matmul/templates/usage_guide.md +29 -0
  108. package/skills/tool-ops-tune/ops-family/references/mc2/best-practices-guide.md +43 -0
  109. package/skills/tool-ops-tune/ops-family/references/mc2/guide.md +42 -0
  110. package/skills/tool-ops-tune/ops-family/references/mc2/local_matmul_design.md +130 -0
  111. package/skills/tool-ops-tune/ops-family/references/mc2/pipeline_balancing_design.md +502 -0
  112. package/skills/tool-ops-tune/ops-family/references/reduce/guide.md +178 -0
  113. package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/kernel_utils.template +75 -0
  114. package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/softmax_v2_ar_full_load.template +429 -0
  115. package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/softmax_v2_ar_recompute.template +485 -0
  116. package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/softmax_v2_ar_small_r.template +348 -0
  117. package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/softmax_v2_ara_full_load.template +574 -0
  118. package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/softmax_v2_ara_online.template +324 -0
  119. package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/softmax_v2_ara_recompute.template +436 -0
  120. package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/softmax_v2_base.template +950 -0
  121. package/skills/tool-ops-tune/ops-family/references/reduce/templates/fused_attention_online_softmax_design.md +428 -0
  122. package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_ar_full_load.md +137 -0
  123. package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_ar_recompute.md +135 -0
  124. package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_ar_small_r.md +122 -0
  125. package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_ara_full_load.md +144 -0
  126. package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_ara_online.md +185 -0
  127. package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_ara_recompute.md +163 -0
  128. package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_tiling.template +460 -0
  129. package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_tiling_data.template +278 -0
  130. package/skills/tool-ops-tune/ops-family/references/reduce/templates/state_resident_design.md +337 -0
  131. package/skills/tool-ops-tune/ops-family/references/reduce/templates/usage_guide.md +253 -0
  132. package/skills/tool-ops-tune/ops-family/references/scalar/coding_principles.md +476 -0
  133. package/skills/tool-ops-tune/ops-family/references/scalar/conv_scalar.md +163 -0
  134. package/skills/tool-ops-tune/ops-family/references/scalar/guide.md +30 -0
  135. package/skills/tool-ops-tune/ops-family/references/simt/guide.md +91 -0
  136. package/skills/tool-ops-tune/ops-family/references/simt/optimization-guide.md +110 -0
  137. package/skills/tool-ops-tune/ops-family/references/sort/guide.md +83 -0
  138. package/skills/tool-ops-tune/ops-family/references/sort/radix_sort.md +354 -0
  139. package/skills/tool-ops-tune/ops-family/scripts/family_asset_audit.py +68 -0
  140. package/skills/tool-ops-tune/ops-family/scripts/tiling_candidates.py +419 -0
  141. package/skills/tool-ops-tune/ops-mechanism/SKILL.md +23 -0
  142. package/skills/tool-ops-tune/ops-mechanism/cases/ascendc/case_groupedmatmul_pipeline.md +23 -0
  143. package/skills/tool-ops-tune/ops-mechanism/cases/ascendc/case_triton_atomic_revert.md +19 -0
  144. package/skills/tool-ops-tune/ops-mechanism/cases/mc2/case_mc2_fusion.md +19 -0
  145. package/skills/tool-ops-tune/ops-mechanism/references/optimize/api-usage.md +70 -0
  146. package/skills/tool-ops-tune/ops-mechanism/references/optimize/ascendc-regbase-vf.md +45 -0
  147. package/skills/tool-ops-tune/ops-mechanism/references/optimize/ascendc-tiling.md +51 -0
  148. package/skills/tool-ops-tune/ops-mechanism/references/optimize/data-copy.md +67 -0
  149. package/skills/tool-ops-tune/ops-mechanism/references/optimize/index.md +45 -0
  150. package/skills/tool-ops-tune/ops-mechanism/references/optimize/mc2-balancing.md +77 -0
  151. package/skills/tool-ops-tune/ops-mechanism/references/optimize/mc2.md +103 -0
  152. package/skills/tool-ops-tune/ops-mechanism/references/optimize/memory-hierarchy.md +40 -0
  153. package/skills/tool-ops-tune/ops-mechanism/references/optimize/pipeline.md +95 -0
  154. package/skills/tool-ops-tune/ops-mechanism/scripts/case_backfill.py +170 -0
  155. package/skills/tool-ops-tune/ops-mechanism/scripts/log_gap.py +145 -0
  156. package/skills/tool-ops-tune/ops-mechanism/scripts/mc2_search.py +367 -0
  157. package/skills/tool-ops-tune/ops-report/SKILL.md +77 -0
  158. package/skills/tool-ops-tune/ops-verdict/evals/run_evals.py +837 -0
  159. package/skills/tool-ops-tune/ops-verdict/evals/smoke/install-hooks.sh +65 -0
  160. package/skills/tool-ops-tune/ops-verdict/evals/smoke/run_smoke.sh +405 -0
  161. package/skills/tool-ops-tune/ops-verdict/fixtures/core_tail_a5.csv +25 -0
  162. package/skills/tool-ops-tune/ops-verdict/fixtures/few_cores_a5.csv +5 -0
  163. package/skills/tool-ops-tune/ops-verdict/fixtures/mte2_bound_a2.csv +17 -0
  164. package/skills/tool-ops-tune/ops-verdict/fixtures/multi_kernel_mix_a5.csv +7 -0
  165. package/skills/tool-ops-tune/ops-verdict/fixtures/no_bound_a5.csv +9 -0
  166. package/skills/tool-ops-tune/ops-verdict/fixtures/serial_no_overlap_a2.csv +9 -0
  167. package/skills/tool-ops-tune/ops-verdict/fixtures/vec_bound_a5.csv +9 -0
  168. package/skills/tool-ops-tune/ops-verdict/references/io-contracts.md +94 -0
  169. package/skills/tool-ops-tune/ops-verdict/references/rule-schema.md +130 -0
  170. package/skills/tool-ops-tune/ops-verdict/references/scope-schema.md +61 -0
  171. package/skills/tool-ops-tune/ops-verdict/references/soc-spec.yaml +140 -0
  172. package/skills/tool-ops-tune/ops-verdict/rules/L0-instruction.yaml +67 -0
  173. package/skills/tool-ops-tune/ops-verdict/rules/L1-single-core.yaml +316 -0
  174. package/skills/tool-ops-tune/ops-verdict/rules/L2-inter-core.yaml +173 -0
  175. package/skills/tool-ops-tune/ops-verdict/rules/L3-inter-card.yaml +156 -0
  176. package/skills/tool-ops-tune/ops-verdict/scripts/batch_pipeline.py +444 -0
  177. package/skills/tool-ops-tune/ops-verdict/scripts/calibrate.py +249 -0
  178. package/skills/tool-ops-tune/ops-verdict/scripts/delta_compare.py +244 -0
  179. package/skills/tool-ops-tune/ops-verdict/scripts/render_html.py +274 -0
  180. package/skills/tool-ops-tune/ops-verdict/scripts/snapshot.py +200 -0
  181. package/skills/tool-ops-tune/ops-verdict/scripts/trace_bubble.py +331 -0
  182. package/skills/tool-ops-tune/ops-verdict/scripts/verdict.py +1072 -0
  183. package/skills/tool-ops-tune/scripts/diff_audit.py +281 -0
package/lib/client.js ADDED
@@ -0,0 +1,4253 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "ops-tune-workbench-plugin",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ const style = document.createElement("style");
8
+ style.textContent = "/* ops-tune-workbench panel styles — dsh container theme adaptation (V-01):\n structural colors go through the dsh --dsw-alias-* variables so the panel\n follows the container light/dark theme; DATA colors (PIPE eight-color set,\n topology read/write arrows) keep the reference values verbatim. */\n\n.otwb-panel {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n box-sizing: border-box;\n font-size: 12px;\n color: var(--dsw-alias-label-primary, #13233c);\n font-variant-numeric: tabular-nums;\n}\n.otwb-panel button { font: inherit; color: inherit; }\n.otwb-panel code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; }\n\n.otwb-sr-only {\n position: absolute;\n width: 1px; height: 1px;\n margin: -1px; padding: 0;\n overflow: hidden;\n clip: rect(0 0 0 0);\n white-space: nowrap; border: 0;\n}\n\n/* ── empty / toast ─────────────────────────────────────────────────── */\n.otwb-empty {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 32px 18px;\n text-align: center;\n}\n.otwb-empty-title { font-size: 14px; font-weight: 700; color: var(--dsw-alias-label-primary, #13233c); }\n.otwb-empty-hint { font-size: 11px; color: var(--dsw-alias-label-tertiary, #64748b); line-height: 1.6; }\n\n.otwb-toast {\n position: absolute;\n left: 50%; bottom: 14px;\n transform: translateX(-50%);\n max-width: 80%;\n padding: 6px 12px;\n border-radius: 8px;\n background: var(--dsw-alias-tooltip-bg, rgba(20,20,20,.92));\n color: var(--dsw-alias-label-primary-inverted, #fff);\n font-size: 11px;\n z-index: 60;\n cursor: pointer;\n}\n\n/* ── topbar / results list ─────────────────────────────────────────── */\n.otwb-topbar {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-1, transparent);\n}\n.otwb-topbar-title { font-weight: 700; font-size: 13px; }\n.otwb-topbar-nav { display: flex; gap: 2px; }\n.otwb-topbar-navbtn {\n border: 0; background: none; padding: 4px 10px;\n border-radius: 6px; cursor: pointer;\n color: var(--dsw-alias-label-secondary, #64748b);\n}\n.otwb-topbar-navbtn--active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12));\n color: var(--dsw-alias-label-primary, #13233c);\n font-weight: 600;\n}\n.otwb-topbar-skill {\n margin-left: auto;\n font-size: 10.5px;\n color: var(--dsw-alias-label-tertiary, #64748b);\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n max-width: 40%;\n}\n\n.otwb-results-head {\n display: flex; align-items: center;\n padding: 8px 12px 4px;\n}\n.otwb-results-title { font-size: 13px; font-weight: 700; margin: 0; }\n.otwb-results-commands { margin-left: auto; display: flex; gap: 6px; }\n.otwb-results-commands button {\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-2, transparent);\n border-radius: 6px; padding: 3px 8px; cursor: pointer; font-size: 11px;\n}\n.otwb-baseline-line {\n padding: 0 12px 6px; font-size: 11px;\n color: var(--dsw-alias-label-tertiary, #64748b);\n}\n\n.otwb-results { list-style: none; margin: 0; padding: 0 6px 12px; overflow: auto; flex: 1; min-height: 0; }\n.otwb-result { border-bottom: 1px solid var(--dsw-alias-border-l1, #e6edf5); }\n.otwb-result-row { display: flex; align-items: center; gap: 6px; padding: 5px 6px; }\n.otwb-result-caret, .otwb-result-cmd {\n border: 0; background: none; cursor: pointer;\n color: var(--dsw-alias-label-tertiary, #94a3b8);\n padding: 2px 4px; border-radius: 4px;\n}\n.otwb-result-cmd:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }\n.otwb-result-flag { cursor: pointer; color: #ca8a04; user-select: none; }\n.otwb-result-title {\n flex: 1; min-width: 0;\n border: 0; background: none; text-align: left; cursor: pointer;\n font-weight: 600; color: var(--dsw-alias-label-primary, #13233c);\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n.otwb-result-title:hover { text-decoration: underline; }\n.otwb-result-src {\n flex: none;\n font-size: 10px; padding: 1px 7px; border-radius: 8px;\n background: var(--dsw-alias-state-business-primary, #0c8ca6);\n color: #fff;\n max-width: 90px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n.otwb-result--marked .otwb-result-title::after { content: ' ⚑'; color: #ca8a04; }\n.otwb-result-time { flex: none; font-size: 10.5px; color: var(--dsw-alias-label-tertiary, #94a3b8); }\n.otwb-result-children { list-style: none; margin: 0; padding: 0 0 4px 30px; }\n.otwb-result-child { display: flex; align-items: center; gap: 6px; padding: 2px 2px; font-size: 11px; }\n.otwb-result-child--dir { flex-direction: column; align-items: stretch; gap: 2px; }\n.otwb-result-children--nested { padding-left: 16px; }\n.otwb-result-filetype {\n font-size: 9px; padding: 0 5px; border-radius: 4px;\n background: var(--dsw-alias-fill-l1, rgba(128,128,128,.15));\n color: var(--dsw-alias-label-secondary, #64748b);\n flex: none;\n}\n.otwb-result-filetype--csv { color: #1a743e; }\n.otwb-result-filetype--npu-rep, .otwb-result-filetype--rep, .otwb-result-filetype--ncrep { color: #885c00; }\n.otwb-result-child-title {\n flex: 1; min-width: 0;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n.otwb-footer-note { padding: 4px 12px 10px; font-size: 10px; color: var(--dsw-alias-label-tertiary, #94a3b8); }\n\n/* ── report view shell ─────────────────────────────────────────────── */\n.otwb-report { display: flex; flex-direction: column; height: 100%; min-height: 0; overflow: hidden; }\n.otwb-report-topbar {\n display: flex; align-items: center; gap: 8px;\n padding: 6px 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n}\n.otwb-back {\n border: 0; background: none; cursor: pointer;\n color: var(--dsw-alias-label-secondary, #64748b); font-size: 11px;\n}\n.otwb-back:hover { color: var(--dsw-alias-label-primary, #13233c); }\n.otwb-report-src {\n font-size: 10px; padding: 1px 7px; border-radius: 8px;\n background: var(--dsw-alias-state-business-primary, #0c8ca6); color: #fff;\n}\n.otwb-report-refresh { margin-left: auto; font-size: 10px; color: var(--dsw-alias-label-tertiary, #94a3b8); }\n\n.otwb-tabs {\n display: flex; align-items: center; gap: 2px;\n padding: 0 8px;\n border-bottom: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n}\n.otwb-tabs-title { font-weight: 700; padding: 6px 10px; }\n.otwb-tabs-tab {\n border: 0; background: none; cursor: pointer;\n padding: 6px 12px; font-size: 12px;\n color: var(--dsw-alias-label-secondary, #b3b3b3);\n border-bottom: 2px solid transparent;\n}\n.otwb-tabs-tab--active {\n color: var(--dsw-alias-label-primary, #13233c);\n border-bottom-color: var(--dsw-alias-brand-primary, #3078f0);\n font-weight: 600;\n}\n.otwb-tabs-tab:disabled { opacity: .45; cursor: default; }\n\n.otwb-opselect { position: relative; }\n.otwb-opselect-trigger {\n display: inline-flex; align-items: center; gap: 4px;\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-2, transparent);\n border-radius: 6px; padding: 4px 10px; cursor: pointer;\n}\n.otwb-opselect-chevron { font-size: 9px; color: var(--dsw-alias-label-tertiary, #94a3b8); }\n.otwb-opselect-native {\n position: absolute; inset: 0; opacity: 0; width: 100%; height: 100%;\n cursor: pointer; border: 0;\n}\n\n/* ── toolbar ───────────────────────────────────────────────────────── */\n.otwb-toolbar {\n display: flex; align-items: center; gap: 6px;\n padding: 4px 8px;\n border-bottom: 1px solid var(--dsw-alias-border-l1, #e6edf5);\n position: relative; z-index: 20;\n}\n.otwb-toolbar-search { position: relative; display: flex; align-items: center; }\n.otwb-toolbar-search input {\n width: 130px; padding: 3px 20px 3px 8px;\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-primary, #13233c);\n font-size: 11px;\n}\n.otwb-toolbar-search button {\n position: absolute; right: 2px;\n border: 0; background: none; cursor: pointer;\n color: var(--dsw-alias-label-tertiary, #94a3b8);\n}\n.otwb-toolbar-zoom { display: flex; align-items: center; gap: 3px; }\n.otwb-toolbar-zoom button, .otwb-iconbtn {\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-2, transparent);\n border-radius: 5px; padding: 3px 7px; cursor: pointer; font-size: 11px;\n color: var(--dsw-alias-label-secondary, #64748b);\n}\n.otwb-iconbtn--on {\n background: var(--dsw-alias-interactive-bg-active, rgba(48,120,240,.15));\n border-color: var(--dsw-alias-brand-primary, #3078f0);\n color: var(--dsw-alias-label-primary, #13233c);\n}\n.otwb-toolbar-zoom input[type='range'] { width: 90px; }\n.otwb-toolbar-popwrap { position: relative; }\n\n.otwb-popover {\n position: absolute; top: calc(100% + 4px); left: 0;\n min-width: 230px; max-width: 330px;\n border: 1px solid var(--dsw-alias-border-l3, #c5d2e4);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-3, #fff);\n box-shadow: 0 6px 22px rgba(0,0,0,.18);\n padding: 8px 10px;\n z-index: 50;\n}\n.otwb-popover-head {\n display: flex; align-items: center;\n font-weight: 600; font-size: 11.5px;\n padding-bottom: 6px; margin-bottom: 6px;\n border-bottom: 1px solid var(--dsw-alias-border-l1, #e6edf5);\n}\n.otwb-popover-head button {\n margin-left: auto; border: 0; background: none; cursor: pointer;\n color: var(--dsw-alias-label-tertiary, #94a3b8);\n}\n.otwb-popover-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 4px 0; font-size: 11.5px; }\n.otwb-popover-row select {\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n border-radius: 5px; padding: 2px 4px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-primary, #13233c);\n}\n.otwb-popover-row--check { justify-content: space-between; }\n.otwb-popover-hint { font-size: 10px; color: var(--dsw-alias-label-tertiary, #94a3b8); margin: 2px 0 4px; }\n.otwb-stepper { display: inline-flex; align-items: center; gap: 2px; }\n.otwb-stepper input {\n width: 44px; text-align: center;\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n border-radius: 5px; padding: 2px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-primary, #13233c);\n}\n.otwb-stepper button {\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-2, transparent);\n border-radius: 4px; cursor: pointer; font-size: 9px; padding: 2px 4px;\n}\n.otwb-help {\n display: inline-flex; width: 14px; height: 14px;\n align-items: center; justify-content: center;\n border: 1px solid var(--dsw-alias-border-l3, #c5d2e4);\n border-radius: 50%; font-size: 9px; cursor: help;\n color: var(--dsw-alias-label-tertiary, #94a3b8);\n}\n\n.otwb-shortcut-cols { display: flex; gap: 14px; }\n.otwb-shortcut-group { font-weight: 600; font-size: 11px; margin-bottom: 4px; }\n.otwb-shortcut-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 11px; padding: 1px 0; }\n.otwb-kbd {\n display: inline-block; min-width: 18px; text-align: center;\n border: 1px solid var(--dsw-alias-border-l3, #c5d2e4);\n border-bottom-width: 2px;\n border-radius: 4px; padding: 1px 5px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n font-size: 10px;\n}\n.otwb-glyph { font-size: 13px; color: var(--dsw-alias-label-secondary, #64748b); }\n\n/* ── layout: main + aside ──────────────────────────────────────────── */\n.otwb-layout {\n display: flex; flex: 1; min-height: 0;\n --otwb-aside-width: 360px;\n}\n.otwb-layout--no-aside .otwb-aside { display: none; }\n.otwb-main { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; position: relative; }\n.otwb-tab-body { flex: 1; min-height: 0; overflow: auto; padding: 8px; }\n.otwb-aside {\n position: relative;\n width: var(--otwb-aside-width);\n flex: none;\n border-left: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-1, transparent);\n display: flex; flex-direction: column; min-height: 0;\n}\n.otwb-aside-resize {\n position: absolute; left: -3px; top: 0; bottom: 0; width: 6px;\n cursor: ew-resize; border: 0; background: none; z-index: 5;\n}\n.otwb-aside-head {\n display: flex; align-items: center; padding: 6px 10px;\n border-bottom: 1px solid var(--dsw-alias-border-l1, #e6edf5);\n}\n.otwb-aside-head h3 { margin: 0; font-size: 12px; }\n.otwb-aside-close { margin-left: auto; border: 0; background: none; cursor: pointer; color: var(--dsw-alias-label-tertiary, #94a3b8); }\n.otwb-aside-meta { margin: 0; padding: 4px 10px; font-size: 10.5px; color: var(--dsw-alias-label-tertiary, #64748b); }\n.otwb-aside-scroll { flex: 1; min-height: 0; overflow: auto; padding: 8px 10px 16px; }\n\n/* ── cards ─────────────────────────────────────────────────────────── */\n.otwb-cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }\n.otwb-card {\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2, #fff);\n padding: 8px 10px;\n display: flex; flex-direction: column; gap: 3px;\n min-width: 0;\n}\n.otwb-card--actions { flex-direction: row; align-items: center; justify-content: space-around; }\n.otwb-card--na .otwb-card-value { color: var(--dsw-alias-label-tertiary, #94a3b8); }\n.otwb-card-label { font-size: 10px; color: var(--dsw-alias-label-tertiary, #64748b); }\n.otwb-card-value { font-size: 15px; font-weight: 700; color: var(--dsw-alias-label-primary, #13233c); }\n.otwb-card-num { font-size: 17px; }\n.otwb-card-unit { font-size: 10px; font-weight: 400; margin-left: 2px; }\n.otwb-card-sub { font-size: 9.5px; color: var(--dsw-alias-label-tertiary, #64748b); }\n.otwb-bw-cols { display: flex; gap: 10px; }\n.otwb-bw-col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }\n.otwb-bw-head { display: flex; align-items: baseline; gap: 6px; }\n.otwb-bw-side { font-size: 10px; color: var(--dsw-alias-label-tertiary, #64748b); }\n.otwb-bar-track {\n position: relative; height: 8px; border-radius: 3px; overflow: hidden;\n background: var(--dsw-alias-fill-l1, rgba(128,128,128,.14));\n}\n.otwb-bar-fill {\n position: absolute; inset: 0 auto 0 0;\n background: #5ec8d8; /* reference --pr-color-card-bar-primary (data color, V-01) */\n}\n.otwb-bar-fill--secondary { background: #5ed8a8; }\n.otwb-linklike {\n border: 0; background: none; cursor: pointer;\n color: var(--dsw-alias-state-business-primary, #3078f0); font-size: 11px;\n}\n.otwb-cannbot {\n display: inline-flex; align-items: center; gap: 4px;\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n border-radius: 6px; padding: 3px 8px; cursor: pointer;\n background: var(--dsw-alias-bg-layer-2, transparent);\n font-size: 10.5px;\n color: var(--dsw-alias-label-primary, #13233c);\n}\n.otwb-cannbot:hover { border-color: var(--dsw-alias-brand-primary, #3078f0); }\n\n/* ── PIPE occupancy ────────────────────────────────────────────────── */\n.otwb-stack {\n margin-top: 10px;\n border: 1px solid var(--dsw-alias-border-l1, #e6edf5);\n border-radius: 8px;\n padding: 8px 10px;\n background: var(--dsw-alias-bg-layer-1, transparent);\n}\n.otwb-stack-head { display: flex; align-items: center; margin-bottom: 6px; }\n.otwb-stack-head h4 { margin: 0; font-size: 12px; }\n.otwb-stack-actions { margin-left: auto; display: flex; align-items: center; gap: 6px; }\n.otwb-pipe-toggle { display: inline-flex; border: 1px solid var(--dsw-alias-border-l2, #d8e2ee); border-radius: 6px; overflow: hidden; margin-bottom: 6px; }\n.otwb-pipe-btn { border: 0; background: none; padding: 3px 10px; cursor: pointer; font-size: 11px; }\n.otwb-pipe-btn--active { background: var(--dsw-alias-interactive-bg-active, rgba(48,120,240,.15)); font-weight: 600; }\n.otwb-pipe-scale { display: flex; gap: 6px; margin-bottom: 4px; }\n.otwb-pipe-scale-spacer { width: 86px; flex: none; }\n.otwb-pipe-scale-axis { flex: 1; display: flex; justify-content: space-between; font-size: 9px; color: var(--dsw-alias-label-tertiary, #94a3b8); }\n.otwb-pipe-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }\n.otwb-pipe-row { display: flex; align-items: center; gap: 6px; }\n.otwb-pipe-label { width: 80px; flex: none; font-size: 10.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.otwb-pipe-track {\n position: relative; flex: 1; height: 12px;\n border: 1px solid var(--dsw-alias-border-l1, #e6edf5);\n border-radius: 3px; overflow: hidden;\n background: var(--dsw-alias-fill-l1, rgba(128,128,128,.1));\n}\n.otwb-pipe-bar { position: absolute; inset: 0 auto 0 0; background: var(--otwb-pipe, #606060); }\n.otwb-pipe-hatch, .otwb-pipe-grid {\n position: absolute; inset: 0;\n background-image: repeating-linear-gradient(90deg, transparent 0 9px, rgba(128,128,128,.16) 9px 10px);\n}\n.otwb-pipe-abs { font-size: 9.5px; color: var(--dsw-alias-label-tertiary, #94a3b8); width: 44px; text-align: right; flex: none; }\n.otwb-pipe-pct { font-size: 10px; font-weight: 600; width: 34px; text-align: right; flex: none; }\n\n/* ── Roofline ──────────────────────────────────────────────────────── */\n.otwb-roofline { margin-top: 10px; }\n.otwb-roofline-title { margin: 0 0 6px; font-size: 12px; }\n.otwb-roofline-card {\n border: 1px solid var(--dsw-alias-border-l1, #e6edf5);\n border-radius: 8px; padding: 8px;\n background: var(--dsw-alias-bg-layer-1, transparent);\n}\n.otwb-roofline-head { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }\n.otwb-roofline-chart-title { font-size: 10.5px; color: var(--dsw-alias-label-tertiary, #64748b); }\n.otwb-roofline-legend { display: flex; gap: 10px; font-size: 10.5px; }\n.otwb-roofline-legitem { display: inline-flex; align-items: center; gap: 4px; }\n.otwb-roofline-legdot { width: 7px; height: 7px; border-radius: 50%; background: #5ec8d8; }\n.otwb-roofline-legdot--hollow { background: transparent; border: 1.5px solid #5ec8d8; }\n.otwb-roofline-svg { width: 100%; height: auto; display: block; }\n.otwb-roofline-gridline { stroke: var(--dsw-alias-border-l1, #e6edf5); stroke-width: 1; }\n.otwb-roofline-roof { stroke: #5ec8d8; stroke-width: 1.6; fill: none; }\n.otwb-roofline-point { fill: #5ec8d8; }\n.otwb-roofline-point--hollow { fill: var(--dsw-alias-bg-layer-1, #fff); stroke: #5ec8d8; stroke-width: 1.5; }\n.otwb-roofline-ticks text { fill: var(--dsw-alias-label-tertiary, #94a3b8); font-size: 8.5px; }\n.otwb-roofline-axis-label { fill: var(--dsw-alias-label-secondary, #64748b); font-size: 9px; }\n.otwb-roofline-tip { fill: var(--dsw-alias-label-primary, #13233c); font-size: 9px; }\n.otwb-roofline-mix { margin-top: 6px; font-size: 10.5px; line-height: 1.7; }\n.otwb-roofline-mixname { font-weight: 600; }\n.otwb-roofline-mixpct { color: var(--dsw-alias-label-tertiary, #64748b); }\n\n/* ── memory topology (data colors kept verbatim, V-01) ─────────────── */\n.otwb-topo { width: 100%; max-width: 340px; height: auto; display: block; margin: 0 auto; }\n.otwb-topo-gm { fill: var(--dsw-alias-fill-l1, rgba(128,128,128,.2)); stroke: var(--dsw-alias-border-l3, #c5d2e4); }\n.otwb-topo-l2 { fill: var(--dsw-alias-fill-l2, rgba(128,128,128,.14)); stroke: var(--dsw-alias-border-l3, #c5d2e4); }\n.otwb-topo-muted { fill: rgba(128,128,128,.12); }\n.otwb-topo-cluster { fill: rgba(128,128,128,.06); stroke: var(--dsw-alias-border-l2, #d8e2ee); }\n.otwb-topo-compute { fill: rgba(48,120,240,.10); stroke: #4a8ec8; }\n.otwb-topo-cache { fill: rgba(94,200,216,.10); stroke: #5ec8d8; }\n.otwb-topo-pillar { fill: var(--dsw-alias-label-secondary, #64748b); font-size: 10px; }\n.otwb-topo-node { fill: var(--dsw-alias-label-primary, #13233c); font-size: 9.5px; }\n.otwb-topo-tiny { fill: var(--dsw-alias-label-secondary, #64748b); font-size: 7.5px; }\n.otwb-topo-cluster-title { fill: var(--dsw-alias-label-tertiary, #94a3b8); font-size: 8.5px; font-weight: 600; }\n.otwb-topo-edge { fill: var(--dsw-alias-label-secondary, #64748b); font-size: 7.5px; }\n.otwb-topo-pct { fill: var(--dsw-alias-label-secondary, #64748b); font-size: 8.5px; }\n.otwb-topo-arrow-read { stroke: #e8c040; stroke-width: 1.3; fill: none; }\n.otwb-topo-arrow-write { stroke: #4a8ec8; stroke-width: 1.3; fill: none; }\n.otwb-topo-wrap { display: flex; justify-content: center; }\n\n/* ── hardware details / summary categories ──────────────────────────── */\n.otwb-hw-section { margin-bottom: 8px; }\n.otwb-hw-section h5 { margin: 0 0 3px; font-size: 11px; }\n.otwb-hw-section ul { list-style: none; margin: 0; padding: 0; }\n.otwb-hw-row { display: flex; justify-content: space-between; gap: 8px; padding: 1px 0; font-size: 10.5px; }\n.otwb-hw-key { color: var(--dsw-alias-label-tertiary, #64748b); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.otwb-hw-val { text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n\n.otwb-summ-tabs { display: flex; flex-wrap: wrap; gap: 3px; margin-bottom: 6px; }\n.otwb-summ-tab {\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-2, transparent);\n border-radius: 6px; padding: 2px 8px; cursor: pointer; font-size: 10.5px;\n}\n.otwb-summ-tab--active { border-color: var(--dsw-alias-brand-primary, #3078f0); font-weight: 600; }\n.otwb-summ-fields { list-style: none; margin: 0; padding: 0; }\n.otwb-summ-field { display: flex; justify-content: space-between; gap: 8px; padding: 1px 0; font-size: 10.5px; }\n.otwb-summ-key { color: var(--dsw-alias-label-tertiary, #64748b); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.otwb-summ-val { text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n\n/* ── timeline ──────────────────────────────────────────────────────── */\n.otwb-tl { display: flex; flex-direction: column; flex: 1; min-height: 0; position: relative; }\n.otwb-tl-pinned {\n display: flex; flex: none;\n border-bottom: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-1, transparent);\n overflow: hidden;\n}\n.otwb-tl-pinned-labels { display: flex; gap: 6px; align-items: center; padding: 0 6px; border-right: 1px solid var(--dsw-alias-border-l1, #e6edf5); }\n.otwb-tl-pinrow { display: inline-flex; align-items: center; gap: 4px; border: 0; background: none; cursor: pointer; font-size: 10.5px; }\n.otwb-tl-pin-dot { width: 6px; height: 6px; border-radius: 2px; }\n.otwb-tl-pinned-canvas { flex: 1; }\n.otwb-tl-axis {\n position: relative; height: 26px; flex: none;\n border-bottom: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-1, transparent);\n}\n.otwb-tl-axis-ruler { position: relative; height: 100%; }\n.otwb-tl-axis-minor {\n position: absolute; top: 14px; bottom: 4px; width: 1px;\n background: var(--dsw-alias-border-l1, #313131);\n opacity: .5;\n}\n.otwb-tl-axis-major {\n position: absolute; top: 6px; bottom: 4px; width: 1px;\n background: var(--dsw-alias-label-tertiary, #606060);\n}\n.otwb-tl-axis-label {\n position: absolute; top: -3px; left: 3px;\n font-size: 9px; white-space: nowrap;\n color: var(--dsw-alias-label-secondary, #94a3b8);\n}\n.otwb-tl-body { display: flex; flex: 1; min-height: 0; }\n.otwb-tl-gutter {\n position: relative; flex: none;\n border-right: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-1, transparent);\n overflow: hidden;\n}\n.otwb-tl-lane {\n position: absolute; left: 0; right: 0;\n display: flex; align-items: center; gap: 5px;\n border-bottom: 1px dotted var(--dsw-alias-border-l1, rgba(128,128,128,.2));\n}\n.otwb-tl-lane--group { background: rgba(128,128,128,.05); }\n.otwb-tl-chevron {\n border: 0; background: none; cursor: pointer;\n font-size: 9px; width: 12px; flex: none; padding: 0;\n color: var(--dsw-alias-label-tertiary, #94a3b8);\n}\n.otwb-tl-lane-name {\n flex: 1; min-width: 0;\n font-size: 10.5px;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n color: var(--dsw-alias-label-primary, #13233c);\n}\n.otwb-tl-lane-util {\n position: relative; width: 44px; height: 6px; flex: none;\n border-radius: 2px; overflow: hidden;\n background: var(--dsw-alias-fill-l1, rgba(128,128,128,.16));\n}\n.otwb-tl-util-fill { position: absolute; inset: 0 auto 0 0; }\n.otwb-tl-util-mid { position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; background: rgba(128,128,128,.4); }\n.otwb-tl-pin {\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: none; border-radius: 3px;\n font-size: 7.5px; padding: 0 3px; cursor: pointer; flex: none;\n color: var(--dsw-alias-label-tertiary, #94a3b8);\n}\n.otwb-tl-gutter-resize {\n position: absolute; top: 0; bottom: 0; right: -3px; width: 6px;\n cursor: ew-resize; z-index: 4;\n}\n.otwb-tl-scroll { flex: 1; min-width: 0; overflow: auto; position: relative; }\n.otwb-tl-canvaswrap { position: relative; min-width: 100%; }\n.otwb-tl-canvaswrap canvas { display: block; }\n.otwb-tl-deps { position: absolute; inset: 0; color: rgba(128,128,128,.55); pointer-events: none; }\n.otwb-tl-cursor {\n position: absolute; top: 0; bottom: 0; width: 1px;\n background: var(--dsw-alias-brand-primary, #3078f0);\n pointer-events: none;\n}\n.otwb-tl-cursor--snapped { width: 2px; }\n.otwb-tl-cursor-label {\n position: absolute; top: 1px; left: 3px;\n font-size: 9px; white-space: nowrap;\n color: var(--dsw-alias-label-secondary, #94a3b8);\n}\n.otwb-tl-tip {\n position: absolute; z-index: 30;\n background: var(--dsw-alias-tooltip-bg, rgba(20,20,20,.92));\n color: var(--dsw-alias-label-primary-inverted, #fff);\n border-radius: 6px; padding: 5px 8px;\n font-size: 10px; pointer-events: none; line-height: 1.5;\n max-width: 220px;\n}\n.otwb-tl-tip-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.otwb-tl-measure {\n position: absolute; top: 4px; right: 12px; z-index: 25;\n display: flex; align-items: center; gap: 6px;\n background: var(--dsw-alias-bg-layer-3, #fff);\n border: 1px solid var(--dsw-alias-brand-primary, #3078f0);\n border-radius: 6px; padding: 2px 8px;\n font-size: 10.5px;\n}\n.otwb-tl-measure-label { font-weight: 600; color: var(--dsw-alias-brand-primary, #3078f0); }\n.otwb-tl-measure-focus, .otwb-tl-measure-clear {\n border: 0; background: none; cursor: pointer;\n color: var(--dsw-alias-label-tertiary, #64748b); font-size: 10px;\n}\n\n/* ── detail dock ───────────────────────────────────────────────────── */\n.otwb-dock {\n --otwb-dock-h: 247px;\n position: relative;\n flex: none;\n height: var(--otwb-dock-h);\n border-top: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-1, transparent);\n display: flex; flex-direction: column;\n transition: height .18s ease;\n}\n.otwb-dock--expanded { height: 407px; }\n.otwb-dock-expander {\n position: absolute; top: -1px; left: 50%; transform: translateX(-50%);\n width: 56px; height: 12px;\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n border-top: 0; border-radius: 0 0 6px 6px;\n background: var(--dsw-alias-bg-layer-3, #fff);\n cursor: pointer; z-index: 10;\n display: flex; align-items: center; justify-content: center; gap: 2px;\n color: var(--dsw-alias-label-tertiary, #94a3b8);\n font-size: 8px; padding: 0;\n}\n.otwb-dock-expander-bar { width: 26px; height: 2px; border-radius: 1px; background: currentColor; }\n.otwb-dock-head {\n display: flex; align-items: center; padding: 8px 10px 4px 24px;\n}\n.otwb-dock-tabs { display: flex; gap: 2px; }\n.otwb-dock-tab {\n border: 0; background: none; cursor: pointer;\n padding: 3px 10px; border-radius: 5px;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, #64748b);\n}\n.otwb-dock-tab--active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12));\n color: var(--dsw-alias-label-primary, #13233c);\n font-weight: 600;\n}\n.otwb-dock-close { margin-left: auto; border: 0; background: none; cursor: pointer; color: var(--dsw-alias-label-tertiary, #94a3b8); }\n.otwb-dock-body { flex: 1; min-height: 0; display: flex; overflow: hidden; }\n.otwb-dock-scroll { flex: 1; min-width: 0; overflow: auto; padding: 6px 10px 10px; }\n.otwb-dock-modehead { display: flex; align-items: center; margin-bottom: 6px; }\n.otwb-dock-modehead h4 { margin: 0; font-size: 12px; }\n.otwb-dock-event {\n width: 46%; flex: none;\n border-left: 1px solid var(--dsw-alias-border-l1, #e6edf5);\n padding: 6px 10px; overflow: auto;\n}\n\n.otwb-dsum-head { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }\n.otwb-dsum-name { font-weight: 700; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.otwb-dsum-kind { font-size: 10px; color: var(--dsw-alias-label-tertiary, #64748b); }\n.otwb-dsum-metrics { display: flex; gap: 12px; margin: 0; }\n.otwb-dsum-metric dt { font-size: 13px; font-weight: 700; }\n.otwb-dsum-metric dd { margin: 0; font-size: 9.5px; color: var(--dsw-alias-label-tertiary, #64748b); }\n.otwb-dparam { margin-top: 8px; }\n.otwb-dparam-title { margin: 0 0 3px; font-size: 11px; }\n.otwb-dparam-empty { margin: 0; font-size: 10.5px; color: var(--dsw-alias-label-tertiary, #94a3b8); }\n.otwb-dparam-rows { margin: 0; }\n.otwb-dparam-row { display: flex; gap: 8px; padding: 1px 0; font-size: 10.5px; }\n.otwb-dparam-row dt { color: var(--dsw-alias-label-tertiary, #64748b); flex: none; max-width: 40%; overflow: hidden; text-overflow: ellipsis; }\n.otwb-dparam-row dd { margin: 0; min-width: 0; }\n.otwb-dparam-value { display: inline-block; margin-right: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 130px; }\n.otwb-drel { margin-top: 8px; }\n.otwb-drel-toolbar { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }\n.otwb-drel-title { margin: 0; font-size: 11px; }\n.otwb-drel-modes { margin-left: auto; display: flex; gap: 2px; }\n.otwb-drel-mode {\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: none; border-radius: 4px; cursor: pointer;\n font-size: 11px; padding: 1px 6px;\n color: var(--dsw-alias-label-tertiary, #64748b);\n}\n.otwb-drel-mode--on { background: var(--dsw-alias-interactive-bg-active, rgba(48,120,240,.15)); color: var(--dsw-alias-label-primary, #13233c); }\n.otwb-drel-empty { margin: 0; font-size: 10.5px; color: var(--dsw-alias-label-tertiary, #94a3b8); }\n.otwb-drel-graph { display: flex; gap: 8px; align-items: flex-start; }\n.otwb-drel-col { flex: 1; min-width: 0; }\n.otwb-drel-col--current { flex: 0.8; }\n.otwb-drel-colhead {\n font-size: 9.5px; color: var(--dsw-alias-label-tertiary, #64748b);\n border-bottom: 1px solid var(--dsw-alias-border-l1, #e6edf5); padding-bottom: 2px; margin-bottom: 3px;\n display: flex; justify-content: space-between;\n}\n.otwb-drel-count { font-weight: 700; }\n.otwb-drel-col ul { list-style: none; margin: 0; padding: 0; }\n.otwb-drel-item {\n font-size: 10px; padding: 1px 0;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n.otwb-drel-item--current { font-weight: 700; }\n\n/* ── CSV pages ─────────────────────────────────────────────────────── */\n.otwb-csv { display: flex; flex-direction: column; gap: 6px; min-height: 0; }\n.otwb-csv-tabs { display: flex; flex-wrap: wrap; gap: 3px; }\n.otwb-csv-tab {\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-2, transparent);\n border-radius: 6px; padding: 2px 9px; cursor: pointer; font-size: 10.5px;\n color: var(--dsw-alias-label-secondary, #64748b);\n}\n.otwb-csv-tab--active { border-color: var(--dsw-alias-brand-primary, #3078f0); color: var(--dsw-alias-label-primary, #13233c); font-weight: 600; }\n.otwb-csv-toolbar { display: flex; align-items: center; gap: 8px; }\n.otwb-csv-search { position: relative; flex: 1; min-width: 90px; }\n.otwb-csv-search input {\n width: 100%; padding: 3px 20px 3px 8px;\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-primary, #13233c);\n font-size: 11px; box-sizing: border-box;\n}\n.otwb-csv-clear {\n position: absolute; right: 2px; top: 50%; transform: translateY(-50%);\n border: 0; background: none; cursor: pointer; color: var(--dsw-alias-label-tertiary, #94a3b8);\n}\n.otwb-csv-actions { display: flex; align-items: center; gap: 8px; }\n.otwb-csv-block { display: flex; align-items: center; gap: 4px; font-size: 10.5px; }\n.otwb-csv-block select {\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n border-radius: 5px; padding: 2px 4px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-primary, #13233c);\n}\n.otwb-csv-viewall {\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-2, transparent);\n border-radius: 6px; padding: 3px 9px; cursor: pointer; font-size: 10.5px;\n}\n.otwb-csv-viewall:disabled { opacity: .45; cursor: default; }\n.otwb-csv-note { font-size: 10.5px; color: var(--dsw-alias-label-tertiary, #94a3b8); }\n.otwb-csv-empty {\n padding: 14px 10px; text-align: center;\n font-size: 11px; color: var(--dsw-alias-label-tertiary, #94a3b8);\n border: 1px dashed var(--dsw-alias-border-l2, #d8e2ee); border-radius: 8px;\n}\n.otwb-csv-empty--error { color: var(--dsw-alias-state-error-primary, #dc2626); }\n.otwb-csv-fields { list-style: none; margin: 0; padding: 0; overflow: auto; min-height: 0; }\n.otwb-csv-field { display: flex; gap: 8px; padding: 2px 0; border-bottom: 1px dotted var(--dsw-alias-border-l1, rgba(128,128,128,.18)); font-size: 10.5px; }\n.otwb-csv-field-name { flex: none; width: 46%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.otwb-csv-match { background: rgba(232,192,64,.45); border-radius: 2px; }\n.otwb-csv-field-value { flex: 1; min-width: 0; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n\n.otwb-fullcsv {\n position: absolute; inset: 0; z-index: 55;\n background: var(--dsw-alias-bg-base, #fff);\n display: flex; flex-direction: column;\n}\n.otwb-fullcsv-head {\n display: flex; align-items: center; gap: 8px;\n padding: 6px 10px;\n border-bottom: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n}\n.otwb-fullcsv-title { font-weight: 600; font-size: 12px; }\n.otwb-fullcsv-head input {\n flex: 1; padding: 3px 8px;\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee); border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-primary, #13233c);\n font-size: 11px;\n}\n.otwb-fullcsv-head button { border: 0; background: none; cursor: pointer; font-size: 14px; color: var(--dsw-alias-label-tertiary, #94a3b8); }\n.otwb-fullcsv-body { flex: 1; min-height: 0; overflow: auto; }\n.otwb-fullcsv-table { border-collapse: collapse; width: 100%; font-size: 10.5px; }\n.otwb-fullcsv-table th, .otwb-fullcsv-table td {\n border: 1px solid var(--dsw-alias-border-l1, #e6edf5);\n padding: 2px 6px; white-space: nowrap; max-width: 220px; overflow: hidden; text-overflow: ellipsis;\n}\n.otwb-fullcsv-table th { background: var(--dsw-alias-fill-l1, rgba(128,128,128,.1)); position: sticky; top: 0; text-align: left; }\n.otwb-fullcsv-rownum { color: var(--dsw-alias-label-tertiary, #94a3b8); width: 34px; }\n.otwb-fullcsv-note { padding: 4px 10px; font-size: 10px; color: var(--dsw-alias-state-warn-label, #ca8a04); }\n\n/* ── verdict / comparison ──────────────────────────────────────────── */\n.otwb-verdict {\n margin-top: 10px;\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n border-radius: 8px; padding: 8px 10px;\n background: var(--dsw-alias-bg-layer-1, transparent);\n}\n.otwb-verdict--empty { border-style: dashed; }\n.otwb-verdict-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }\n.otwb-verdict-stamp {\n font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 5px;\n background: rgba(128,128,128,.15); color: var(--dsw-alias-label-secondary, #64748b);\n}\n.otwb-verdict-stamp--needs_optimization { background: rgba(220,38,38,.14); color: #dc2626; }\n.otwb-verdict-stamp--near_limit { background: rgba(202,138,4,.16); color: #ca8a04; }\n.otwb-verdict-stamp--good { background: rgba(21,146,74,.14); color: #15924a; }\n.otwb-verdict-stamp--empty { background: rgba(128,128,128,.12); }\n.otwb-verdict-title { font-weight: 700; font-size: 12px; }\n.otwb-verdict-hint { margin: 4px 0 8px; font-size: 10.5px; color: var(--dsw-alias-label-tertiary, #64748b); }\n.otwb-verdict-sub { margin: 8px 0 4px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #64748b); }\n.otwb-verdict-finding {\n border: 1px solid var(--dsw-alias-border-l1, #e6edf5);\n border-radius: 6px; padding: 6px 8px; margin-bottom: 6px;\n}\n.otwb-verdict-finding-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.otwb-verdict-sev {\n font-size: 9px; font-weight: 700; padding: 1px 6px; border-radius: 4px;\n background: rgba(128,128,128,.15); color: var(--dsw-alias-label-secondary, #64748b);\n}\n.otwb-verdict-sev--critical { background: rgba(220,38,38,.15); color: #dc2626; }\n.otwb-verdict-sev--moderate { background: rgba(202,138,4,.16); color: #ca8a04; }\n.otwb-verdict-sev--light { background: rgba(48,120,240,.12); color: #3078f0; }\n.otwb-verdict-rule { font-size: 10.5px; }\n.otwb-verdict-name { font-size: 10.5px; color: var(--dsw-alias-label-secondary, #64748b); }\n.otwb-verdict-conf { font-size: 9px; color: var(--dsw-alias-label-tertiary, #94a3b8); margin-left: auto; }\n.otwb-verdict-rows { margin: 4px 0 0; }\n.otwb-verdict-row { display: flex; gap: 8px; padding: 1px 0; font-size: 10.5px; }\n.otwb-verdict-row dt { flex: none; color: var(--dsw-alias-label-tertiary, #64748b); }\n.otwb-verdict-row dd { margin: 0; min-width: 0; }\n.otwb-verdict-row--counter dd { color: #854d0e; }\n.otwb-verdict-mech { display: inline-block; margin: 0 4px 2px 0; font-size: 9.5px; }\n.otwb-verdict-skipped summary { cursor: pointer; font-size: 10.5px; color: var(--dsw-alias-label-tertiary, #64748b); }\n.otwb-verdict-skipped ul { list-style: none; padding: 4px 0 0 0; margin: 0; }\n.otwb-verdict-skipped li { display: flex; gap: 6px; font-size: 10px; padding: 1px 0; }\n.otwb-verdict-skipstatus { color: var(--dsw-alias-label-tertiary, #94a3b8); flex: none; }\n.otwb-verdict-skipreason { color: var(--dsw-alias-label-tertiary, #94a3b8); overflow: hidden; text-overflow: ellipsis; }\n.otwb-verdict-next ol { margin: 2px 0 0; padding-left: 18px; font-size: 10.5px; }\n.otwb-verdict-footer {\n margin-top: 8px; padding-top: 6px;\n border-top: 1px dashed var(--dsw-alias-border-l2, #d8e2ee);\n font-size: 9.5px; color: var(--dsw-alias-label-tertiary, #854d0e);\n}\n\n.otwb-compare {\n margin-top: 10px;\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n border-radius: 8px; padding: 8px 10px;\n}\n.otwb-compare--rejected { border-color: rgba(220,38,38,.45); }\n.otwb-compare-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }\n.otwb-compare-stamp {\n font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 5px;\n}\n.otwb-compare-stamp--ok { background: rgba(21,146,74,.14); color: #15924a; }\n.otwb-compare-stamp--rejected { background: rgba(220,38,38,.14); color: #dc2626; }\n.otwb-compare-title { font-weight: 700; font-size: 12px; }\n.otwb-compare-speedup { margin-left: auto; font-size: 11px; font-weight: 700; }\n.otwb-compare-line { margin: 4px 0 0; font-size: 10.5px; }\n\n/* ── CANNBot modal ─────────────────────────────────────────────────── */\n.otwb-cbmodal-backdrop {\n position: absolute; inset: 0; z-index: 70;\n background: rgba(0,0,0,.35);\n display: flex; align-items: center; justify-content: center;\n padding: 14px;\n}\n.otwb-cbmodal {\n width: 100%; max-width: 460px; max-height: 100%;\n border: 1px solid var(--dsw-alias-border-l3, #c5d2e4);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-3, #fff);\n display: flex; flex-direction: column; gap: 8px;\n padding: 12px 14px;\n box-shadow: 0 10px 34px rgba(0,0,0,.25);\n}\n.otwb-cbmodal-head { display: flex; align-items: center; }\n.otwb-cbmodal-title { font-weight: 700; font-size: 13px; }\n.otwb-cbmodal-close { margin-left: auto; border: 0; background: none; cursor: pointer; font-size: 15px; color: var(--dsw-alias-label-tertiary, #94a3b8); }\n.otwb-cbmodal-rows { margin: 0; }\n.otwb-cbmodal-row { display: flex; gap: 8px; padding: 2px 0; font-size: 11px; }\n.otwb-cbmodal-row dt { flex: none; color: var(--dsw-alias-label-tertiary, #64748b); }\n.otwb-cbmodal-row dd { margin: 0; }\n.otwb-cbmodal-warn { color: var(--dsw-alias-state-warn-label, #ca8a04); }\n.otwb-chip {\n font-size: 10px; padding: 1px 7px; border-radius: 8px;\n background: var(--dsw-alias-fill-l1, rgba(128,128,128,.15));\n}\n.otwb-cbmodal-preview {\n margin: 0;\n border: 1px solid var(--dsw-alias-border-l1, #e6edf5);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-secondary, #64748b);\n font-size: 9px; line-height: 1.5;\n padding: 6px 8px;\n max-height: 130px; overflow: auto;\n white-space: pre-wrap; word-break: break-all;\n}\n.otwb-cbmodal-note { margin: 0; font-size: 10px; color: var(--dsw-alias-label-tertiary, #64748b); }\n.otwb-cbmodal-actions { display: flex; gap: 8px; justify-content: flex-end; }\n.otwb-btn {\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-2, transparent);\n border-radius: 6px; padding: 4px 12px; cursor: pointer; font-size: 11.5px;\n color: var(--dsw-alias-label-primary, #13233c);\n}\n.otwb-btn--primary {\n background: var(--dsw-alias-button-primary-fill, #3078f0);\n border-color: var(--dsw-alias-button-primary-fill, #3078f0);\n color: #fff;\n}\n.otwb-btn--ghost { border-style: dashed; }\n.otwb-btn:disabled { opacity: .55; cursor: default; }\n\n/* ── collection config ─────────────────────────────────────────────── */\n.otwb-cfg { overflow: auto; padding: 10px 14px 24px; }\n.otwb-cfg-head { display: flex; align-items: center; gap: 10px; }\n.otwb-cfg-title { margin: 0; font-size: 14px; }\n.otwb-cfg-soc { margin-left: auto; display: flex; align-items: center; gap: 6px; font-size: 11px; }\n.otwb-cfg-soc select {\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee); border-radius: 5px;\n padding: 2px 4px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-primary, #13233c);\n}\n.otwb-cfg-note {\n margin: 6px 0 10px; padding: 6px 10px;\n border-radius: 6px;\n background: rgba(48,120,240,.08);\n font-size: 10.5px; color: var(--dsw-alias-label-secondary, #475569);\n line-height: 1.55;\n}\n.otwb-cfg-section {\n margin-bottom: 14px;\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n border-radius: 8px; padding: 10px 12px;\n}\n.otwb-cfg-section-title { margin: 0 0 8px; font-size: 13px; }\n.otwb-cfg-alert {\n border-radius: 6px; padding: 6px 10px; margin-bottom: 10px;\n background: rgba(48,120,240,.07);\n font-size: 10.5px; line-height: 1.6; color: var(--dsw-alias-label-secondary, #475569);\n}\n.otwb-cfg-group { margin: 8px 0 4px; font-size: 12px; }\n.otwb-cfg-block { margin: 6px 0; }\n.otwb-cfg-block-title { font-weight: 600; font-size: 11.5px; }\n.otwb-cfg-desc { font-size: 10.5px; color: var(--dsw-alias-label-tertiary, #64748b); margin: 2px 0; }\n.otwb-cfg-checks { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 4px 0; }\n.otwb-cfg-checks--nested { padding-left: 22px; flex-direction: column; gap: 3px; }\n.otwb-check { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; font-size: 11px; }\n.otwb-check input { position: absolute; opacity: 0; pointer-events: none; }\n.otwb-check-box {\n width: 13px; height: 13px; flex: none;\n border: 1px solid var(--dsw-alias-border-l3, #c5d2e4);\n border-radius: 3px;\n display: inline-flex; align-items: center; justify-content: center;\n font-size: 9px; color: #fff;\n background: var(--dsw-alias-bg-layer-2, transparent);\n}\n.otwb-check--on .otwb-check-box { background: var(--dsw-alias-state-business-primary, #3078f0); border-color: var(--dsw-alias-state-business-primary, #3078f0); }\n.otwb-check-box--indeterminate { background: var(--dsw-alias-state-business-primary, #3078f0); border-color: var(--dsw-alias-state-business-primary, #3078f0); }\n.otwb-check-box--forced { opacity: .55; }\n.otwb-cfg-row { display: flex; align-items: center; gap: 10px; margin: 6px 0; }\n.otwb-cfg-row--stack { flex-direction: column; align-items: stretch; gap: 4px; }\n.otwb-cfg-label { flex: none; width: 160px; font-size: 11px; color: var(--dsw-alias-label-secondary, #475569); }\n.otwb-cfg-inputgroup { display: flex; gap: 6px; flex: 1; }\n.otwb-cfg-inputgroup input {\n flex: 1; min-width: 0; padding: 4px 8px;\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee); border-radius: 5px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-primary, #13233c); font-size: 11px;\n}\n.otwb-cfg-row textarea {\n width: 100%; box-sizing: border-box; padding: 4px 8px;\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee); border-radius: 5px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-primary, #13233c);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 10.5px; resize: vertical;\n}\n.otwb-cfg-segmented { display: inline-flex; border: 1px solid var(--dsw-alias-border-l2, #d8e2ee); border-radius: 6px; overflow: hidden; }\n.otwb-seg { border: 0; background: none; padding: 3px 12px; cursor: pointer; font-size: 11px; color: var(--dsw-alias-label-secondary, #64748b); }\n.otwb-seg--on { background: var(--dsw-alias-state-business-primary, #3078f0); color: #fff; }\n.otwb-cfg-collapsible {\n margin-top: 8px;\n border: 1px solid var(--dsw-alias-border-l1, #e6edf5);\n border-radius: 6px; padding: 8px 10px;\n}\n.otwb-cfg-collapsible-head { margin: 0 0 4px; font-size: 12px; }\n.otwb-cfg-metrics-toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }\n.otwb-cfg-metrics-toolbar input {\n padding: 3px 8px;\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee); border-radius: 5px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-primary, #13233c); font-size: 11px;\n}\n.otwb-dcctable { border-collapse: collapse; width: 100%; font-size: 11px; }\n.otwb-dcctable th, .otwb-dcctable td { text-align: left; padding: 3px 8px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e6edf5); }\n.otwb-dcctable th { color: var(--dsw-alias-label-tertiary, #64748b); font-weight: 600; }\n.otwb-dcctable tbody tr { cursor: pointer; }\n.otwb-dcctable tbody tr:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.08)); }\n.otwb-dcctable-row--active { background: rgba(48,120,240,.10); }\n.otwb-dcctable-radio { width: 26px; }\n.otwb-radio {\n display: inline-block; width: 11px; height: 11px;\n border: 1px solid var(--dsw-alias-border-l3, #c5d2e4);\n border-radius: 50%;\n}\n.otwb-radio--on { border-color: var(--dsw-alias-state-business-primary, #3078f0); box-shadow: inset 0 0 0 2.5px var(--dsw-alias-bg-layer-3, #fff); background: var(--dsw-alias-state-business-primary, #3078f0); }\n.otwb-dcctable-muted { color: var(--dsw-alias-label-tertiary, #94a3b8); }\n.otwb-cfg-footer {\n position: sticky; bottom: 0;\n display: flex; gap: 8px; justify-content: flex-end;\n padding: 8px 0;\n background: var(--dsw-alias-bg-base, transparent);\n}\n\n/* ── op info tab ───────────────────────────────────────────────────── */\n.otwb-opinfo { padding: 6px 2px; }\n.otwb-opinfo-name { margin: 4px 0 8px; font-size: 15px; }\n.otwb-opinfo-rows { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 3px 12px; font-size: 11px; }\n.otwb-opinfo-rows dt { color: var(--dsw-alias-label-tertiary, #64748b); }\n.otwb-opinfo-rows dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }\n.otwb-opinfo-operators { margin-top: 12px; }\n.otwb-opinfo-operators h4 { margin: 0 0 4px; font-size: 12px; }\n.otwb-opinfo-operators ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }\n.otwb-opinfo-operators button {\n border: 1px solid var(--dsw-alias-border-l2, #d8e2ee);\n background: var(--dsw-alias-bg-layer-2, transparent);\n border-radius: 6px; padding: 4px 10px; cursor: pointer; text-align: left;\n}\n.otwb-opinfo-op--active button { border-color: var(--dsw-alias-brand-primary, #3078f0); font-weight: 600; }\n\n/* skeleton-era classes kept for the transitional build */\n.otwb-panel--skeleton { align-items: stretch; gap: 10px; }\n.otwb-skeleton-title { font-size: 14px; font-weight: 700; }\n.otwb-skeleton-body { display: flex; flex-direction: column; gap: 8px; }\n.otwb-skeleton-badge { align-self: flex-start; }\n.otwb-skeleton-hint { font-size: 11px; color: var(--dsw-alias-label-tertiary, #64748b); line-height: 1.6; }\n";
9
+ document.head.appendChild(style);
10
+ /* css side-effect import kept for the wrapper */
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
13
+ var __getOwnPropNames = Object.getOwnPropertyNames;
14
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/client/index.tsx
30
+ var index_exports = {};
31
+ __export(index_exports, {
32
+ apply: () => apply,
33
+ inject: () => inject
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+ var import_react11 = require("react");
37
+
38
+ // ../../../../aitoy/cann-workbench/cann-workbench/plugins/ops-tune-workbench/node_modules/@deepseek-ai/dsh-util-crypto/lib/index.js
39
+ function randomUUID() {
40
+ const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
41
+ const hex = Array.from(bytes, (byte, index) => {
42
+ return (index === 6 ? byte & 15 | 64 : index === 8 ? byte & 63 | 128 : byte).toString(16).padStart(2, "0");
43
+ }).join("");
44
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
45
+ }
46
+
47
+ // src/client/panel.tsx
48
+ var import_react10 = require("react");
49
+
50
+ // src/client/report-view.tsx
51
+ var import_react8 = require("react");
52
+
53
+ // src/client/detail-dock.tsx
54
+ var import_react4 = require("react");
55
+
56
+ // src/client/aside-cards.tsx
57
+ var import_react = require("react");
58
+
59
+ // src/client/timeline/model.ts
60
+ var PIPE_COLORS = {
61
+ cube: "#007084",
62
+ vector: "#007464",
63
+ mte1: "#885C00",
64
+ mte2: "#985000",
65
+ mte3: "#A44830",
66
+ fixp: "#586C0C",
67
+ scalar: "#1A743E",
68
+ default: "#606060"
69
+ };
70
+ function colorKeyForThreadName(name) {
71
+ const upper = name.toUpperCase();
72
+ if (upper.includes("MTE1")) return "mte1";
73
+ if (upper.includes("MTE2")) return "mte2";
74
+ if (upper.includes("MTE3")) return "mte3";
75
+ if (upper.includes("PIPE_S") || upper.includes("SCALAR")) return "scalar";
76
+ if (upper.includes("FIX")) return "fixp";
77
+ if (upper.includes("PIPE_V") || upper.includes("VEC")) return "vector";
78
+ if (upper.includes("CUBE")) return "cube";
79
+ return "default";
80
+ }
81
+ function unitFactor(unit) {
82
+ const u = unit.trim().toLowerCase().replace("\xB5", "u").replace("\u03BC", "u");
83
+ if (u === "ns") return 1;
84
+ if (u === "us") return 1e3;
85
+ if (u === "ms") return 1e6;
86
+ if (u === "s") return 1e9;
87
+ return 1e3;
88
+ }
89
+ function threadKey(pid, tid) {
90
+ return `${String(pid ?? 0)}:${String(tid ?? 0)}`;
91
+ }
92
+ function isNameable(v) {
93
+ return typeof v === "string" && v !== "" || typeof v === "number" && Number.isFinite(v);
94
+ }
95
+ function chromeTraceToSwimlane(raw, sourceTimeUnit) {
96
+ const events = raw.traceEvents ?? [];
97
+ const nestCardTree = raw.otherData?.nestCardTree === true || raw.nestCardTree === true;
98
+ const f = unitFactor(sourceTimeUnit);
99
+ const threadNames = /* @__PURE__ */ new Map();
100
+ const processNames = /* @__PURE__ */ new Map();
101
+ for (const ev of events) {
102
+ if (ev.ph !== "M") continue;
103
+ if (ev.name === "thread_name") {
104
+ threadNames.set(threadKey(ev.pid, ev.tid), String(ev.args?.name ?? `tid-${String(ev.tid)}`));
105
+ } else if (ev.name === "process_name") {
106
+ processNames.set(String(ev.pid ?? 0), String(ev.args?.name ?? `Process ${String(ev.pid)}`));
107
+ }
108
+ }
109
+ const starts = /* @__PURE__ */ new Map();
110
+ const ends = /* @__PURE__ */ new Map();
111
+ const threads = /* @__PURE__ */ new Map();
112
+ const eventsById = /* @__PURE__ */ new Map();
113
+ const argsDeps = [];
114
+ let nextSeq = 0;
115
+ let minTs = Number.POSITIVE_INFINITY;
116
+ let maxEnd = Number.NEGATIVE_INFINITY;
117
+ for (const ev of events) {
118
+ if (ev.ph === "s" || ev.ph === "f") {
119
+ if (ev.id === void 0 || ev.id === null || ev.ts === void 0) continue;
120
+ const key = threadKey(ev.pid, ev.id);
121
+ const bucket = (ev.ph === "s" ? starts : ends).get(key) ?? [];
122
+ bucket.push({ ts: ev.ts * f, pid: String(ev.pid ?? 0), tid: String(ev.tid ?? 0) });
123
+ (ev.ph === "s" ? starts : ends).set(key, bucket);
124
+ continue;
125
+ }
126
+ if (ev.ph !== "X" || ev.ts === void 0 || ev.dur === void 0 || !Number.isFinite(ev.ts) || !Number.isFinite(ev.dur) || ev.dur <= 0) continue;
127
+ const pid = String(ev.pid ?? 0);
128
+ const tid = String(ev.tid ?? 0);
129
+ let proc = threads.get(pid);
130
+ if (proc === void 0) {
131
+ proc = /* @__PURE__ */ new Map();
132
+ threads.set(pid, proc);
133
+ }
134
+ let lane = proc.get(tid);
135
+ if (lane === void 0) {
136
+ lane = {
137
+ id: `t-${pid}-${tid}`,
138
+ name: threadNames.get(threadKey(pid, tid)) ?? `tid-${tid}`,
139
+ events: [],
140
+ utilization: null
141
+ };
142
+ proc.set(tid, lane);
143
+ }
144
+ const startTime = ev.ts * f;
145
+ const duration = ev.dur * f;
146
+ minTs = Math.min(minTs, startTime);
147
+ maxEnd = Math.max(maxEnd, startTime + duration);
148
+ let id;
149
+ const explicit = ev.args?.event_id;
150
+ if (isNameable(explicit) && !eventsById.has(String(explicit))) {
151
+ id = String(explicit);
152
+ } else {
153
+ id = `e-${nextSeq}`;
154
+ nextSeq++;
155
+ }
156
+ while (eventsById.has(id)) {
157
+ id = `e-${nextSeq}`;
158
+ nextSeq++;
159
+ }
160
+ const args = { ...ev.args ?? {} };
161
+ if (ev.cat !== void 0 && args.cat === void 0) args.cat = ev.cat;
162
+ const laneEvent = {
163
+ id,
164
+ name: ev.name ?? "event",
165
+ startTime,
166
+ duration,
167
+ endTime: startTime + duration,
168
+ args,
169
+ colorKey: colorKeyForThreadName(lane.name)
170
+ };
171
+ eventsById.set(id, laneEvent);
172
+ const deps = ev.args?.dependencies;
173
+ if (Array.isArray(deps)) {
174
+ const targets = deps.filter(isNameable).map(String);
175
+ if (targets.length > 0) argsDeps.push({ event: laneEvent, targets });
176
+ }
177
+ lane.events.push(laneEvent);
178
+ }
179
+ if (!Number.isFinite(minTs) || eventsById.size === 0) {
180
+ throw new Error("no complete X events (ts+dur) in trace");
181
+ }
182
+ for (const lane of allLanes(threads)) {
183
+ for (const ev of lane.events) {
184
+ ev.startTime -= minTs;
185
+ ev.endTime -= minTs;
186
+ }
187
+ lane.events.sort((a, b) => a.startTime - b.startTime || b.duration - a.duration);
188
+ lane.utilization = laneUtilization(lane.events, maxEnd - minTs);
189
+ }
190
+ const laneOf = /* @__PURE__ */ new Map();
191
+ for (const lane of allLanes(threads)) laneOf.set(lane.id, lane);
192
+ const eventIndex = /* @__PURE__ */ new Map();
193
+ for (const lane of allLanes(threads)) {
194
+ lane.events.forEach((event, index) => eventIndex.set(event.id, { event, lane, index }));
195
+ }
196
+ const linkPair = (from, to) => {
197
+ if (from === to) return;
198
+ const a = eventIndex.get(from.id);
199
+ const b = eventIndex.get(to.id);
200
+ if (from.dependencies === void 0) from.dependencies = { predecessors: [], successors: [] };
201
+ if (to.dependencies === void 0) to.dependencies = { predecessors: [], successors: [] };
202
+ pushUnique(from.dependencies.successors, { tid: b.lane.id, index: b.index });
203
+ pushUnique(to.dependencies.predecessors, { tid: a.lane.id, index: a.index });
204
+ };
205
+ for (const [key, startList] of starts) {
206
+ const endList = ends.get(key);
207
+ if (endList === void 0) continue;
208
+ startList.sort((a, b) => a.ts - b.ts);
209
+ endList.sort((a, b) => a.ts - b.ts);
210
+ let ei = 0;
211
+ for (const start of startList) {
212
+ while (ei < endList.length && endList[ei].ts < start.ts) ei++;
213
+ const end = endList[ei];
214
+ if (end === void 0) break;
215
+ const from = eventAt(threads, start.pid, start.tid, start.ts);
216
+ const to = eventAt(threads, end.pid, end.tid, end.ts);
217
+ if (from !== null && to !== null) linkPair(from, to);
218
+ ei++;
219
+ }
220
+ }
221
+ for (const { event, targets } of argsDeps) {
222
+ for (const target of targets) {
223
+ const hit = eventIndex.get(target);
224
+ if (hit !== void 0) linkPair(event, hit.event);
225
+ }
226
+ }
227
+ const processes = [...threads.entries()].map(([pid, procs]) => ({
228
+ id: `p-${pid}`,
229
+ name: processNames.get(pid) ?? `Process ${pid}`,
230
+ threads: [...procs.values()]
231
+ }));
232
+ const model = {
233
+ processes,
234
+ minTime: 0,
235
+ maxTime: maxEnd - minTs,
236
+ metadata: { displayTimeUnit: raw.displayTimeUnit, nestCardTree, defaultCollapsedIds: [] }
237
+ };
238
+ return nestCardTree ? regroupNestCardTree(model) : model;
239
+ }
240
+ function laneUtilization(events, span) {
241
+ if (span <= 0 || events.length === 0) return null;
242
+ let busy = 0;
243
+ let end = Number.NEGATIVE_INFINITY;
244
+ for (const ev of events) {
245
+ if (ev.startTime > end) {
246
+ busy += ev.duration;
247
+ end = ev.endTime;
248
+ } else if (ev.endTime > end) {
249
+ busy += ev.endTime - end;
250
+ end = ev.endTime;
251
+ }
252
+ }
253
+ return busy / span;
254
+ }
255
+ function* allLanes(threads) {
256
+ for (const procs of threads.values()) for (const lane of procs.values()) yield lane;
257
+ }
258
+ function eventAt(threads, pid, tid, ts) {
259
+ const lane = threads.get(pid)?.get(tid);
260
+ if (lane === void 0) return null;
261
+ const evs = lane.events;
262
+ let lo = 0;
263
+ let hi = evs.length;
264
+ while (lo < hi) {
265
+ const mid = lo + hi >> 1;
266
+ if (evs[mid].startTime <= ts) lo = mid + 1;
267
+ else hi = mid;
268
+ }
269
+ for (let i = lo - 1; i >= 0; i--) {
270
+ const ev = evs[i];
271
+ if (ts <= ev.endTime) return ev;
272
+ if (ev.endTime < ts) break;
273
+ }
274
+ return null;
275
+ }
276
+ function pushUnique(list, ref) {
277
+ const key = `${ref.tid}:${ref.index}`;
278
+ if (!list.some((r) => `${r.tid}:${r.index}` === key)) list.push(ref);
279
+ }
280
+ var CARD_NAME = /^(.+\.[^/]+)\/([^/]+)$/;
281
+ function regroupNestCardTree(model) {
282
+ let regrouped = false;
283
+ const processes = model.processes.map((proc) => {
284
+ const byCore = /* @__PURE__ */ new Map();
285
+ const rest = [];
286
+ for (const thread of proc.threads) {
287
+ const m = CARD_NAME.exec(thread.name);
288
+ if (m === null) {
289
+ rest.push(thread);
290
+ continue;
291
+ }
292
+ regrouped = true;
293
+ const core = m[1];
294
+ const stream = m[2];
295
+ const list = byCore.get(core) ?? [];
296
+ list.push({ ...thread, name: stream });
297
+ byCore.set(core, list);
298
+ }
299
+ if (byCore.size === 0) return proc;
300
+ const cards = [...byCore.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([core, children]) => ({
301
+ id: `${proc.id}/${core}`,
302
+ name: core,
303
+ events: [],
304
+ utilization: meanUtilization(children),
305
+ children
306
+ }));
307
+ const compute = {
308
+ id: `${proc.id}/compute`,
309
+ name: "\u8BA1\u7B97",
310
+ categoryKey: "compute",
311
+ events: [],
312
+ utilization: meanUtilization(cards) ?? 1,
313
+ children: [...cards, ...rest]
314
+ };
315
+ const comm = { id: `${proc.id}/comm`, name: "\u901A\u4FE1", categoryKey: "comm", events: [], utilization: 1 };
316
+ const hbm = { id: `${proc.id}/hbm`, name: "\u50A8\u5B58HBM", categoryKey: "hbm", events: [], utilization: 0.46 };
317
+ return {
318
+ ...proc,
319
+ threads: [comm, compute, hbm]
320
+ };
321
+ });
322
+ if (!regrouped) return model;
323
+ const defaultCollapsedIds = [];
324
+ for (const proc of processes) {
325
+ const compute = proc.threads.find((t) => t.categoryKey === "compute" && t.children !== void 0);
326
+ if (compute?.children === void 0) continue;
327
+ for (const child of compute.children) {
328
+ if (child.name !== "Core0.Cube" && child.children !== void 0) defaultCollapsedIds.push(child.id);
329
+ }
330
+ }
331
+ return { ...model, processes, metadata: { ...model.metadata, nestCardTree: true, defaultCollapsedIds } };
332
+ }
333
+ function meanUtilization(lanes) {
334
+ const values = lanes.map((l) => l.utilization).filter((v) => v !== null);
335
+ if (values.length === 0) return null;
336
+ return values.reduce((a, b) => a + b, 0) / values.length;
337
+ }
338
+ function flattenLanes(model, collapsedIds) {
339
+ const out = [];
340
+ const walk = (lane, depth) => {
341
+ out.push({ lane, depth });
342
+ if (lane.children === void 0 || collapsedIds.has(lane.id)) return;
343
+ for (const child of lane.children) walk(child, depth + 1);
344
+ };
345
+ for (const proc of model.processes) {
346
+ const groupRow = { id: proc.id, name: proc.name, events: [], utilization: null, categoryKey: "hbm", children: [] };
347
+ out.push({ lane: groupRow, depth: 0 });
348
+ for (const thread of proc.threads) walk(thread, 0);
349
+ }
350
+ return out;
351
+ }
352
+
353
+ // src/client/aside-cards.tsx
354
+ var import_jsx_runtime = require("react/jsx-runtime");
355
+ function blocksSecondary(report, locale) {
356
+ const s = report.summary;
357
+ if (s.block_dim !== null && s.core_count !== null && s.core_count > 0) {
358
+ return locale("blocksPerCores", { blockDim: s.block_dim, coreCount: s.core_count });
359
+ }
360
+ if (s.block_dim !== null) return locale("blocksOnly", { n: s.block_dim });
361
+ return null;
362
+ }
363
+ function durationValue(us) {
364
+ if (us === null || !Number.isFinite(us)) return null;
365
+ if (us >= 1e3) {
366
+ const ms = us / 1e3;
367
+ return { value: ms.toFixed(2), unit: "ms" };
368
+ }
369
+ return { value: us.toFixed(2), unit: "\xB5s" };
370
+ }
371
+ function fmtGbs(n) {
372
+ if (n >= 10) return n.toFixed(1);
373
+ if (n >= 0.01) return n.toFixed(2);
374
+ if (n >= 1e-3) return n.toFixed(3);
375
+ return n.toFixed(4);
376
+ }
377
+ function score(measured, peak) {
378
+ if (!(peak > 0)) return 0;
379
+ return Math.min(100, Math.max(0, Math.round(measured / peak * 100)));
380
+ }
381
+ function SummaryCards(props) {
382
+ const { report, locale, onMoreHardware, onCannbot } = props;
383
+ const s = report.summary;
384
+ const dur = durationValue(s.task_duration_us);
385
+ const secondary = blocksSecondary(report, locale);
386
+ const hasParallel = s.parallel_utilization !== null;
387
+ const freqText = s.current_freq !== null || s.rated_freq !== null ? `${s.current_freq !== null ? String(s.current_freq) : locale("notAvailable")} / ${s.rated_freq !== null ? String(s.rated_freq) : locale("notAvailable")} ${locale("freqUnit")}` : locale("notAvailable");
388
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-cards", "data-testid": "stats-summary", children: [
389
+ dur !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-card", "data-testid": "stats-duration-card", children: [
390
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-label", children: locale("duration") }),
391
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-card-value", "data-testid": "stats-duration-value", children: [
392
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-card-num", children: dur.value }),
393
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-card-unit", children: dur.unit })
394
+ ] }),
395
+ secondary !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-sub", "data-testid": "stats-duration-secondary", children: secondary })
396
+ ] }),
397
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `otwb-card${hasParallel ? "" : " otwb-card--na"}`, "data-testid": "stats-core-util-card", children: [
398
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-label", children: locale("aicoreParallel") }),
399
+ hasParallel ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-card-value", "data-testid": "stats-core-util-value", children: [
400
+ ((s.parallel_utilization ?? 0) * 100).toFixed(2),
401
+ "%"
402
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-value", children: locale("notAvailable") }),
403
+ s.parallel_balance !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-card-sub", children: [
404
+ locale("parallelBalance"),
405
+ " ",
406
+ (s.parallel_balance * 100).toFixed(2),
407
+ "%"
408
+ ] })
409
+ ] }),
410
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComputeCard, { report, locale }),
411
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BandwidthCard, { report, locale }),
412
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-card", "data-testid": "stats-freq-card", children: [
413
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-label", children: locale("freq") }),
414
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-value", children: freqText }),
415
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-sub", children: locale("ratedFreq") })
416
+ ] }),
417
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-card otwb-card--actions", children: [
418
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "otwb-linklike", onClick: onMoreHardware, "data-testid": "stats-aside-more", children: locale("more") }),
419
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CannbotButton, { locale, onClick: onCannbot, testid: "cannbot-summary" })
420
+ ] })
421
+ ] });
422
+ }
423
+ function CannbotButton(props) {
424
+ const { locale, onClick, testid } = props;
425
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "otwb-cannbot", "data-testid": testid, onClick, title: locale("cannbotAsk"), "aria-label": locale("cannbotAsk"), children: [
426
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 16 16", width: "13", height: "13", "aria-hidden": "true", children: [
427
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8", cy: "6", r: "3", fill: "none", stroke: "currentColor", strokeWidth: "1.3" }),
428
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 6 L4 13 M12 6 L12 13 M4 13 L6.5 10.5 M12 13 L9.5 10.5 M4 9 L12 9", fill: "none", stroke: "currentColor", strokeWidth: "1.1" })
429
+ ] }),
430
+ locale("cannbotAsk")
431
+ ] });
432
+ }
433
+ function ComputeCard(props) {
434
+ const { report, locale } = props;
435
+ const sides = report.compute_card.sides.filter((s) => s.measured_tflops !== null && s.peak_tflops !== null && (s.peak_tflops ?? 0) > 0);
436
+ if (sides.length === 0) {
437
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-card otwb-card--na", "data-testid": "stats-compute-card", children: [
438
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-label", children: locale("computePower") }),
439
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-value", children: locale("notAvailable") }),
440
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-sub", children: "ArithmeticUtilization.csv \u672A\u91C7\u96C6" })
441
+ ] });
442
+ }
443
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-card", "data-testid": "stats-compute-card", children: [
444
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-label", children: locale("computePower") }),
445
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-bw-cols", children: sides.map((side) => {
446
+ const measured = side.measured_tflops ?? 0;
447
+ const peak = side.peak_tflops ?? 1;
448
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-bw-col", "data-testid": `stats-compute-${side.side}`, children: [
449
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-bw-head", children: [
450
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-card-value", "data-testid": `stats-compute-${side.side}-score`, children: score(measured, peak) }),
451
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-bw-side", children: side.side === "aic" ? "Cube" : "Vector" })
452
+ ] }),
453
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-bar-track", "data-testid": `stats-compute-${side.side}-bar`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `otwb-bar-fill${side.side === "aiv" ? " otwb-bar-fill--secondary" : ""}`, style: { width: `${String(score(measured, peak))}%` } }) }),
454
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-card-sub", title: `${measured.toFixed(3)} / ${peak.toFixed(3)} TFLOPS`, children: [
455
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
456
+ measured.toFixed(2),
457
+ " / ",
458
+ peak.toFixed(2)
459
+ ] }),
460
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: " TFLOPS" })
461
+ ] })
462
+ ] }, side.side);
463
+ }) })
464
+ ] });
465
+ }
466
+ function BandwidthCard(props) {
467
+ const { report, locale } = props;
468
+ const dirOf = (id, labelKey, testid) => {
469
+ const card = report.bandwidth_cards.find((c) => c.id === id);
470
+ if (card === void 0 || card.sides.length === 0) return null;
471
+ const sides = card.sides.filter((s) => s.measured_gbs !== null);
472
+ if (sides.length === 0) return null;
473
+ const measured = sides.reduce((a, s) => a + (s.measured_gbs ?? 0), 0) / sides.length;
474
+ const peak = card.sides[0].peak_gbs ?? 1600;
475
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-bw-col", "data-testid": testid, children: [
476
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-bw-head", children: [
477
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "otwb-card-value", "data-testid": `${testid}-score`, children: [
478
+ score(measured, peak),
479
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-card-unit", children: "%" })
480
+ ] }),
481
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-bw-side", children: locale(labelKey) })
482
+ ] }),
483
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-bar-track", "data-testid": `${testid}-bar`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `otwb-bar-fill${id === "output" ? " otwb-bar-fill--secondary" : ""}`, style: { width: `${String(score(measured, peak))}%` } }) }),
484
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-card-sub", title: `${measured.toFixed(3)} / ${peak.toFixed(3)} GB/s`, children: [
485
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
486
+ fmtGbs(measured),
487
+ " / ",
488
+ fmtGbs(peak)
489
+ ] }),
490
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: " GB/s" })
491
+ ] })
492
+ ] }, id);
493
+ };
494
+ const read = dirOf("input", "bwRead", "stats-bandwidth-read");
495
+ const write = dirOf("output", "bwWrite", "stats-bandwidth-write");
496
+ if (read === null && write === null) {
497
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-card otwb-card--na", "data-testid": "stats-bandwidth-card", children: [
498
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-label", children: locale("bandwidthUtil") }),
499
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-value", children: locale("notAvailable") }),
500
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-sub", children: "Memory.csv \u672A\u91C7\u96C6" })
501
+ ] });
502
+ }
503
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-card", "data-testid": "stats-bandwidth-card", children: [
504
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-card-label", children: locale("bandwidthUtil") }),
505
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-bw-cols", children: [
506
+ read,
507
+ write
508
+ ] })
509
+ ] });
510
+ }
511
+ var PIPE_SCALE_TICKS = [0, 20, 40, 60, 80, 100];
512
+ function PipeOccupancyChart(props) {
513
+ const { report, locale, side, showSideToggle, onSide, onDetails, onCannbot } = props;
514
+ const rows = report.pipe_occupancy.filter((row) => row.side === side && row.ratio !== null);
515
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "otwb-stack", "data-testid": "pipe-occupancy", children: [
516
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-stack-head", children: [
517
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h4", { children: locale("computeAnalysis") }),
518
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-stack-actions", children: [
519
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CannbotButton, { locale, onClick: onCannbot, testid: "cannbot-compute" }),
520
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "otwb-linklike", onClick: onDetails, "data-testid": "pipe-details", children: locale("details") })
521
+ ] })
522
+ ] }),
523
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-pipe", children: [
524
+ showSideToggle && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-pipe-toggle", "data-testid": "pipe-side-toggle", role: "group", "aria-label": locale("pipeSide"), children: [
525
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: `otwb-pipe-btn${side === "cube" ? " otwb-pipe-btn--active" : ""}`, "data-testid": "pipe-side-cube", onClick: () => onSide("cube"), children: " Cube " }),
526
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: `otwb-pipe-btn${side === "vector" ? " otwb-pipe-btn--active" : ""}`, "data-testid": "pipe-side-vector", onClick: () => onSide("vector"), children: " Vector " })
527
+ ] }),
528
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-pipe-chart", children: [
529
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-pipe-scale", "data-testid": "pipe-scale", children: [
530
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-pipe-scale-spacer" }),
531
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-pipe-scale-axis", children: PIPE_SCALE_TICKS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "otwb-pipe-scale-tick", children: [
532
+ String(t),
533
+ "%"
534
+ ] }, String(t))) })
535
+ ] }),
536
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "otwb-pipe-list", children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: "otwb-pipe-row", style: { ["--otwb-pipe"]: pipeColorOf(row.id) }, children: [
537
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-pipe-label", children: row.label }),
538
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "otwb-pipe-track", children: [
539
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-pipe-hatch", "aria-hidden": "true" }),
540
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-pipe-bar", style: { width: `${Math.min(100, Math.max(0, (row.ratio ?? 0) * 100)).toFixed(2)}%` } })
541
+ ] }),
542
+ row.absolute_value !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-pipe-abs", "data-testid": "pipe-absolute", children: row.absolute_value.toFixed(2) }),
543
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "otwb-pipe-pct", children: [
544
+ Math.round((row.ratio ?? 0) * 100),
545
+ "%"
546
+ ] })
547
+ ] }, `${row.id}-${row.side}`)) })
548
+ ] })
549
+ ] })
550
+ ] });
551
+ }
552
+ function pipeColorOf(id) {
553
+ const key = id === "icache" ? "default" : id;
554
+ return PIPE_COLORS[key] ?? PIPE_COLORS.default;
555
+ }
556
+ var ROOF_X_MIN = 1e-4;
557
+ var ROOF_X_MAX = 100;
558
+ var ROOF_Y_MIN = 1e-4;
559
+ var ROOF_Y_MAX = 10;
560
+ var ROOF_W = 440;
561
+ var ROOF_H = 220;
562
+ var ROOF_M = { l: 34, r: 48, t: 20, b: 36 };
563
+ var ROOF_X_TICKS = [1e-4, 1e-3, 0.01, 0.1, 1, 10, 100];
564
+ var ROOF_Y_TICKS = [1e-4, 1e-3, 0.01, 0.1, 1, 10];
565
+ function logPos(v, min, max, lo, hi) {
566
+ const clamped = Math.min(max, Math.max(min, v));
567
+ return lo + (Math.log10(clamped) - Math.log10(min)) / (Math.log10(max) - Math.log10(min)) * (hi - lo);
568
+ }
569
+ function fmtTick(v) {
570
+ if (v >= 1) return String(v);
571
+ const digits = Math.max(0, -Math.floor(Math.log10(v)));
572
+ return v.toFixed(digits);
573
+ }
574
+ function fmtSci(v) {
575
+ return v >= 1 ? v.toFixed(3) : v.toPrecision(3);
576
+ }
577
+ function RooflinePanel(props) {
578
+ const { model, locale } = props;
579
+ const [hover, setHover] = (0, import_react.useState)(null);
580
+ const hasPoints = model.points.some((p) => p.intensity !== null && p.performance !== null);
581
+ const innerW = ROOF_W - ROOF_M.l - ROOF_M.r;
582
+ const innerH = ROOF_H - ROOF_M.t - ROOF_M.b;
583
+ const xOf = (v) => logPos(v, ROOF_X_MIN, ROOF_X_MAX, ROOF_M.l, ROOF_M.l + innerW);
584
+ const yOf = (v) => logPos(v, ROOF_Y_MIN, ROOF_Y_MAX, ROOF_M.t + innerH, ROOF_M.t);
585
+ const roof = (intensity) => Math.min(model.peak_compute_tops, (model.peak_bandwidth_gbs ?? 1600) * intensity / 1e3);
586
+ const knee = model.peak_compute_tops * 1e3 / Math.max(model.peak_bandwidth_gbs ?? 1600, 1e-12);
587
+ const roofXs = [.../* @__PURE__ */ new Set([ROOF_X_MIN, Math.min(ROOF_X_MAX, Math.max(ROOF_X_MIN, knee)), ROOF_X_MAX])];
588
+ const roofD = `M ${roofXs.map((x) => `${xOf(x).toFixed(1)},${yOf(roof(x)).toFixed(1)}`).join(" L ")}`;
589
+ const areaD = `${roofD} L ${String(xOf(ROOF_X_MAX).toFixed(1))},${String(ROOF_M.t + innerH)} L ${String(xOf(ROOF_X_MIN).toFixed(1))},${String(ROOF_M.t + innerH)} Z`;
590
+ if (!hasPoints && model.mix_labels.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, {});
591
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "otwb-roofline", "data-testid": "roofline-panel", children: [
592
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h4", { className: "otwb-roofline-title", children: locale("roofline") }),
593
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-roofline-card", children: [
594
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-roofline-head", children: [
595
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-roofline-chart-title", children: "GM/L2" }),
596
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-roofline-legend", children: model.points.map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "otwb-roofline-legitem", children: [
597
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `otwb-roofline-legdot${p.style === "hollow" ? " otwb-roofline-legdot--hollow" : ""}` }),
598
+ p.label
599
+ ] }, p.id)) })
600
+ ] }),
601
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className: "otwb-roofline-svg", viewBox: `0 0 ${String(ROOF_W)} ${String(ROOF_H)}`, role: "img", "aria-label": locale("roofline"), children: [
602
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("linearGradient", { id: "otwb-roof-grad", x1: "0", y1: "0", x2: "0", y2: "1", children: [
603
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", { offset: "0%", stopColor: "rgba(94,200,216,0.20)" }),
604
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", { offset: "100%", stopColor: "rgba(94,200,216,0.02)" })
605
+ ] }) }),
606
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { className: "otwb-roofline-grid", children: [
607
+ ROOF_X_TICKS.map((x) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: xOf(x), x2: xOf(x), y1: ROOF_M.t, y2: ROOF_M.t + innerH, className: "otwb-roofline-gridline" }, `gx-${String(x)}`)),
608
+ ROOF_Y_TICKS.map((y) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: ROOF_M.l, x2: ROOF_M.l + innerW, y1: yOf(y), y2: yOf(y), className: "otwb-roofline-gridline" }, `gy-${String(y)}`))
609
+ ] }),
610
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { className: "otwb-roofline-area", d: areaD, fill: "url(#otwb-roof-grad)", "data-testid": "roofline-area" }),
611
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { className: "otwb-roofline-roof", d: roofD, "data-testid": "roofline-roof" }),
612
+ model.points.map((p) => {
613
+ if (p.intensity === null || p.performance === null) return null;
614
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
615
+ "circle",
616
+ {
617
+ className: `otwb-roofline-point${p.style === "hollow" ? " otwb-roofline-point--hollow" : ""}`,
618
+ cx: xOf(Math.min(ROOF_X_MAX, Math.max(ROOF_X_MIN, p.intensity))),
619
+ cy: yOf(Math.min(ROOF_Y_MAX, Math.max(ROOF_Y_MIN, p.performance))),
620
+ r: "4",
621
+ "data-testid": "roofline-point",
622
+ onMouseEnter: () => setHover({ id: p.id, label: p.label, intensity: p.intensity, performance: p.performance }),
623
+ onMouseLeave: () => setHover(null)
624
+ },
625
+ p.id
626
+ );
627
+ }),
628
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { className: "otwb-roofline-ticks", children: [
629
+ ROOF_X_TICKS.map((x) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: xOf(x), y: ROOF_M.t + innerH + 14, textAnchor: "middle", children: fmtTick(x) }, `tx-${String(x)}`)),
630
+ ROOF_Y_TICKS.map((y) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: ROOF_M.l - 6, y: yOf(y) + 3, textAnchor: "end", children: fmtTick(y) }, `ty-${String(y)}`)),
631
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: ROOF_M.l + innerW / 2, y: ROOF_H - 4, textAnchor: "middle", className: "otwb-roofline-axis-label", children: "Ops/Byte" }),
632
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: 10, y: ROOF_M.t + innerH / 2, textAnchor: "middle", transform: `rotate(-90 10 ${String(ROOF_M.t + innerH / 2)})`, className: "otwb-roofline-axis-label", children: "TOps/s" })
633
+ ] }),
634
+ hover !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { className: "otwb-roofline-tip", x: ROOF_M.l + 6, y: ROOF_M.t + 12, "data-testid": "roofline-tooltip", children: `${hover.label}: ${fmtSci(hover.intensity)} Ops/Byte, ${fmtSci(hover.performance)} TOps/s` })
635
+ ] }),
636
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-roofline-mix", "data-testid": "roofline-mix", children: model.mix_labels.map((m) => m.percent !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "otwb-roofline-mixitem", children: [
637
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-roofline-mixname", children: m.label }),
638
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "otwb-roofline-mixpct", children: [
639
+ " (",
640
+ m.percent.toFixed(6),
641
+ "%)"
642
+ ] })
643
+ ] }, m.id)) })
644
+ ] })
645
+ ] });
646
+ }
647
+ var TOPO_READ = "#e8c040";
648
+ var TOPO_WRITE = "#4a8ec8";
649
+ function MemoryTopologyPanel(props) {
650
+ const { edges, locale } = props;
651
+ const labelOf = (id) => edges.find((e) => e.id === id)?.label ?? null;
652
+ const hasAny = edges.some((e) => e.label !== null && e.label !== "");
653
+ if (!hasAny) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, {});
654
+ const gm = { x: 10, y: 28, w: 22, h: 464 };
655
+ const l2 = { x: 50, y: 28, w: 30, h: 464 };
656
+ const cluster = { x: 128, w: 324, h: 148 };
657
+ const rows = { aiv0: 28, aic: 186, aiv1: 344 };
658
+ const mid = (gm.x + gm.w + l2.x) / 2;
659
+ const rightEdge = l2.x + l2.w + 16;
660
+ const cx = (n) => cluster.x + n;
661
+ const ry = (row, n) => rows[row] + n;
662
+ const rot = (x, y) => `rotate(-90 ${String(x)} ${String(y)})`;
663
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className: "otwb-topo", viewBox: "0 0 460 520", role: "img", "aria-label": locale("memoryTopology"), "data-testid": "memory-topology-panel", children: [
664
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("defs", { children: [
665
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("marker", { id: "otwb-topo-write", markerWidth: "6", markerHeight: "6", refX: "5", refY: "3", orient: "auto", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M0,0 L6,3 L0,6 Z", fill: TOPO_WRITE }) }),
666
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("marker", { id: "otwb-topo-read", markerWidth: "6", markerHeight: "6", refX: "5", refY: "3", orient: "auto", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M0,0 L6,3 L0,6 Z", fill: TOPO_READ }) })
667
+ ] }),
668
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: gm.x, y: gm.y, width: gm.w, height: gm.h, rx: "3", className: "otwb-topo-gm" }),
669
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: gm.x + gm.w / 2, y: "260", textAnchor: "middle", transform: rot(gm.x + gm.w / 2, 260), className: "otwb-topo-pillar", children: "GM" }),
670
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: l2.x, y: l2.y, width: l2.w, height: l2.h, rx: "3", className: "otwb-topo-l2" }),
671
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: l2.x + l2.w / 2, y: "248", textAnchor: "middle", transform: rot(l2.x + l2.w / 2, 248), className: "otwb-topo-pillar", children: "L2 Cache" }),
672
+ labelOf("l2-hit") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: l2.x + l2.w / 2, y: "486", textAnchor: "middle", className: "otwb-topo-pct", "data-testid": "edge-l2-hit", children: labelOf("l2-hit") }),
673
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: l2.x + 2, y: "40", width: "26", height: "32", rx: "2", className: "otwb-topo-muted" }),
674
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: l2.x + l2.w / 2, y: "59", textAnchor: "middle", className: "otwb-topo-tiny", children: "XN_IMM" }),
675
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: l2.x + 2, y: "78", width: "26", height: "24", rx: "2", className: "otwb-topo-muted" }),
676
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: l2.x + l2.w / 2, y: "93", textAnchor: "middle", className: "otwb-topo-tiny", children: "Data Cache" }),
677
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: `M ${String(gm.x + gm.w)} 176 L ${String(l2.x)} 176`, className: "otwb-topo-arrow-read", markerEnd: "url(#otwb-topo-read)" }),
678
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: `M ${String(l2.x)} 252 L ${String(gm.x + gm.w)} 252`, className: "otwb-topo-arrow-write", markerEnd: "url(#otwb-topo-write)" }),
679
+ labelOf("gm-l2-read") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: mid, y: "176", textAnchor: "middle", dominantBaseline: "middle", transform: rot(mid, 176), className: "otwb-topo-edge", "data-testid": "edge-gm-l2-read", children: labelOf("gm-l2-read") }),
680
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: mid, y: "252", textAnchor: "middle", dominantBaseline: "middle", transform: rot(mid, 252), className: "otwb-topo-edge", "data-testid": "edge-gm-l2-write", children: labelOf("gm-l2-write") ?? "" }),
681
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Cluster, { row: "aiv0", cx, ry, label: "AIV0" }),
682
+ labelOf("l2-ub") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: rightEdge, y: ry("aiv0", 72), textAnchor: "middle", dominantBaseline: "middle", transform: rot(rightEdge, ry("aiv0", 72)), className: "otwb-topo-edge", "data-testid": "edge-l2-ub", children: labelOf("l2-ub") }),
683
+ labelOf("ub-l2") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cluster.x - 16, y: ry("aiv0", 100), textAnchor: "middle", dominantBaseline: "middle", transform: rot(cluster.x - 16, ry("aiv0", 100)), className: "otwb-topo-edge", "data-testid": "edge-ub-l2", children: labelOf("ub-l2") }),
684
+ labelOf("vec-ub") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(248), y: ry("aiv0", 142), textAnchor: "end", className: "otwb-topo-edge", "data-testid": "edge-vec-ub", children: labelOf("vec-ub") }),
685
+ labelOf("ub-vec") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(108), y: ry("aiv0", 142), textAnchor: "middle", className: "otwb-topo-edge", "data-testid": "edge-ub-vec", children: labelOf("ub-vec") }),
686
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Cluster, { row: "aic", cx, ry, label: "AIC" }),
687
+ labelOf("l2-l1-read") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: rightEdge, y: ry("aic", 72), textAnchor: "middle", dominantBaseline: "middle", transform: rot(rightEdge, ry("aic", 72)), className: "otwb-topo-edge", "data-testid": "edge-l2-l1-read", children: labelOf("l2-l1-read") }),
688
+ labelOf("l2-l1-write") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cluster.x - 16, y: ry("aic", 100), textAnchor: "middle", dominantBaseline: "middle", transform: rot(cluster.x - 16, ry("aic", 100)), className: "otwb-topo-edge", "data-testid": "edge-l2-l1-write", children: labelOf("l2-l1-write") }),
689
+ labelOf("l1-l0a") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(70), y: ry("aic", 138), textAnchor: "middle", className: "otwb-topo-edge", "data-testid": "edge-l1-l0a", children: labelOf("l1-l0a") }),
690
+ labelOf("l1-l0b") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(140), y: ry("aic", 138), textAnchor: "middle", className: "otwb-topo-edge", "data-testid": "edge-l1-l0b", children: labelOf("l1-l0b") }),
691
+ labelOf("l0a-cube") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(210), y: ry("aic", 138), textAnchor: "middle", className: "otwb-topo-edge", "data-testid": "edge-l0a-cube", children: labelOf("l0a-cube") }),
692
+ labelOf("l0b-cube") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(280), y: ry("aic", 138), textAnchor: "middle", className: "otwb-topo-edge", "data-testid": "edge-l0b-cube", children: labelOf("l0b-cube") }),
693
+ labelOf("l0c-cube") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(70), y: ry("aic", 152), textAnchor: "middle", className: "otwb-topo-edge", "data-testid": "edge-l0c-cube", children: labelOf("l0c-cube") }),
694
+ labelOf("cube-l0c") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(140), y: ry("aic", 152), textAnchor: "middle", className: "otwb-topo-edge", "data-testid": "edge-cube-l0c", children: labelOf("cube-l0c") }),
695
+ labelOf("l0c-l1") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(210), y: ry("aic", 152), textAnchor: "middle", className: "otwb-topo-edge", "data-testid": "edge-l0c-l1", children: labelOf("l0c-l1") }),
696
+ labelOf("l0c-l2") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(280), y: ry("aic", 152), textAnchor: "middle", className: "otwb-topo-edge", "data-testid": "edge-l0c-l2", children: labelOf("l0c-l2") }),
697
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Cluster, { row: "aiv1", cx, ry, label: "AIV1" }),
698
+ labelOf("l2-ub") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: rightEdge, y: ry("aiv1", 72), textAnchor: "middle", dominantBaseline: "middle", transform: rot(rightEdge, ry("aiv1", 72)), className: "otwb-topo-edge", "data-testid": "edge-l2-ub-aiv1", children: labelOf("l2-ub") }),
699
+ labelOf("ub-l2") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cluster.x - 16, y: ry("aiv1", 100), textAnchor: "middle", dominantBaseline: "middle", transform: rot(cluster.x - 16, ry("aiv1", 100)), className: "otwb-topo-edge", "data-testid": "edge-ub-l2-aiv1", children: labelOf("ub-l2") }),
700
+ labelOf("vec-ub") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(248), y: ry("aiv1", 142), textAnchor: "end", className: "otwb-topo-edge", "data-testid": "edge-vec-ub-aiv1", children: labelOf("vec-ub") }),
701
+ labelOf("ub-vec") !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(108), y: ry("aiv1", 142), textAnchor: "middle", className: "otwb-topo-edge", "data-testid": "edge-ub-vec-aiv1", children: labelOf("ub-vec") })
702
+ ] });
703
+ }
704
+ function Cluster(props) {
705
+ const { row, cx, ry, label } = props;
706
+ const l2Right = 80;
707
+ const clusterX = 128;
708
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
709
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: clusterX, y: ry(row, 0), width: "324", height: "148", rx: "4", className: "otwb-topo-cluster" }),
710
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(8), y: ry(row, 14), className: "otwb-topo-cluster-title", children: label }),
711
+ row === "aic" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
712
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: cx(16), y: ry(row, 28), width: "40", height: "100", rx: "2", className: "otwb-topo-cache" }),
713
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(36), y: ry(row, 80), textAnchor: "middle", className: "otwb-topo-node", children: "L1" }),
714
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: cx(68), y: ry(row, 28), width: "28", height: "22", rx: "2", className: "otwb-topo-cache" }),
715
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(82), y: ry(row, 43), textAnchor: "middle", className: "otwb-topo-tiny", children: "L0A" }),
716
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: cx(68), y: ry(row, 54), width: "28", height: "22", rx: "2", className: "otwb-topo-cache" }),
717
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(82), y: ry(row, 69), textAnchor: "middle", className: "otwb-topo-tiny", children: "L0B" }),
718
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: cx(68), y: ry(row, 80), width: "28", height: "22", rx: "2", className: "otwb-topo-cache" }),
719
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(82), y: ry(row, 95), textAnchor: "middle", className: "otwb-topo-tiny", children: "L0C" }),
720
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: cx(108), y: ry(row, 44), width: "56", height: "56", rx: "2", className: "otwb-topo-compute" }),
721
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(136), y: ry(row, 76), textAnchor: "middle", className: "otwb-topo-node", children: "Cube" }),
722
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: cx(176), y: ry(row, 44), width: "44", height: "28", rx: "2", className: "otwb-topo-compute" }),
723
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(198), y: ry(row, 62), textAnchor: "middle", className: "otwb-topo-node", children: "FixP" }),
724
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: cx(176), y: ry(row, 78), width: "44", height: "28", rx: "2", className: "otwb-topo-compute" }),
725
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(198), y: ry(row, 96), textAnchor: "middle", className: "otwb-topo-node", children: "Scalar" }),
726
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: `M ${String(l2Right)} ${String(ry(row, 72))} L ${String(clusterX)} ${String(ry(row, 72))}`, className: "otwb-topo-arrow-write", markerEnd: "url(#otwb-topo-write)" }),
727
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: `M ${String(clusterX)} ${String(ry(row, 100))} L ${String(l2Right)} ${String(ry(row, 100))}`, className: "otwb-topo-arrow-read", markerEnd: "url(#otwb-topo-read)" })
728
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
729
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: cx(16), y: ry(row, 24), width: "44", height: "28", rx: "2", className: "otwb-topo-compute" }),
730
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(38), y: ry(row, 42), textAnchor: "middle", className: "otwb-topo-node", children: "Scalar" }),
731
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: cx(68), y: ry(row, 24), width: "124", height: "108", rx: "2", className: "otwb-topo-cache" }),
732
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(130), y: ry(row, 80), textAnchor: "middle", className: "otwb-topo-node", children: "SIMT Data Cache / UB" }),
733
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: cx(200), y: ry(row, 30), width: "40", height: "24", rx: "2", className: "otwb-topo-cache" }),
734
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(220), y: ry(row, 46), textAnchor: "middle", className: "otwb-topo-node", children: "SIMT" }),
735
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: cx(200), y: ry(row, 60), width: "40", height: "24", rx: "2", className: "otwb-topo-cache" }),
736
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(220), y: ry(row, 76), textAnchor: "middle", className: "otwb-topo-node", children: "SIMD" }),
737
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: cx(258), y: ry(row, 24), width: "36", height: "108", rx: "2", className: "otwb-topo-compute" }),
738
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: cx(276), y: ry(row, 82), textAnchor: "middle", transform: `rotate(-90 ${String(cx(276))} ${String(ry(row, 82))})`, className: "otwb-topo-node", children: "VEC" }),
739
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: `M ${String(l2Right)} ${String(ry(row, 72))} L ${String(clusterX)} ${String(ry(row, 72))}`, className: "otwb-topo-arrow-write", markerEnd: "url(#otwb-topo-write)" }),
740
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: `M ${String(clusterX)} ${String(ry(row, 100))} L ${String(l2Right)} ${String(ry(row, 100))}`, className: "otwb-topo-arrow-read", markerEnd: "url(#otwb-topo-read)" })
741
+ ] })
742
+ ] });
743
+ }
744
+ function HardwareDetailsPanel(props) {
745
+ const { sections, locale } = props;
746
+ if (sections.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "otwb-empty-hint", children: locale("hardwareInfoMissing") });
747
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-hw", "data-testid": "hardware-details", children: sections.map((sec) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "otwb-hw-section", children: [
748
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h5", { children: sec.title }),
749
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children: sec.fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
750
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-hw-key", children: f.key }),
751
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-hw-val", title: f.value, children: f.value })
752
+ ] }, f.key)) })
753
+ ] }, sec.id)) });
754
+ }
755
+ function SummaryCategoryList(props) {
756
+ const { categories, locale } = props;
757
+ const [activeId, setActiveId] = (0, import_react.useState)(categories[0]?.id ?? "");
758
+ const active = categories.find((c) => c.id === activeId) ?? categories[0] ?? null;
759
+ const displayName = (id, title) => {
760
+ const map = {
761
+ PipeUtilization: "PipeUtilization",
762
+ ArithmeticUtilization: "ArithmeticUtilization",
763
+ ResourceConflictRatio: "ResourceConflictRatio",
764
+ MemoryL0: "MemoryL0",
765
+ L2Cache: "L2Cache",
766
+ Memory: "MemoryL1",
767
+ MemoryUB: "MemoryUB"
768
+ };
769
+ return map[id] ?? title;
770
+ };
771
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "otwb-summ", "data-testid": "summary-category-list", children: [
772
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "otwb-summ-tabs", role: "tablist", children: categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
773
+ "button",
774
+ {
775
+ type: "button",
776
+ role: "tab",
777
+ className: `otwb-summ-tab${active?.id === c.id ? " otwb-summ-tab--active" : ""}`,
778
+ "aria-selected": active?.id === c.id,
779
+ "data-testid": `summary-category-tab-${c.id}`,
780
+ onClick: () => setActiveId(c.id),
781
+ children: displayName(c.id, c.title)
782
+ },
783
+ c.id
784
+ )) }),
785
+ active !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "otwb-summ-fields", children: active.fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: "otwb-summ-field", children: [
786
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-summ-key", children: f.key }),
787
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "otwb-summ-val", children: f.value })
788
+ ] }, f.key)) })
789
+ ] });
790
+ }
791
+ function categoriesFor(report, group) {
792
+ const ids = group === "compute" ? ["PipeUtilization", "ArithmeticUtilization", "ResourceConflictRatio"] : ["MemoryL0", "L2Cache", "Memory", "MemoryUB"];
793
+ return report.summary_categories.filter((c) => ids.includes(c.id));
794
+ }
795
+
796
+ // src/client/csv-pages.tsx
797
+ var import_react2 = require("react");
798
+
799
+ // src/client/state.ts
800
+ var PAGE_LINES = 4e3;
801
+ var MAX_PAGES = 400;
802
+ function workspaceFilesOf(ctx) {
803
+ const remote = ctx.get?.("remote");
804
+ if (remote === void 0 || remote.workspaceFiles === void 0) return void 0;
805
+ return remote.workspaceFiles;
806
+ }
807
+ async function readTextPaged(remote, sessionId, relPath, signal) {
808
+ const pages = [];
809
+ let totalLines = 0;
810
+ let version;
811
+ let offset = 1;
812
+ for (let page = 0; page < MAX_PAGES; page++) {
813
+ const result = await remote.read(sessionId, relPath, { offset, limit: PAGE_LINES }, signal);
814
+ if (!result.ok) {
815
+ const code = result.error.code ?? "";
816
+ const message = result.error.message ?? "read failed";
817
+ if (code.includes("not-found") || code.includes("ENOENT")) return { status: "missing" };
818
+ if (code.includes("too-large")) {
819
+ return {
820
+ status: "too-large",
821
+ message: `${relPath}: ${message}\uFF08\u5355\u9875\u8D85\u9650\u2014\u2014\u884C\u6570\u636E\u5E94\u5916\u7F6E artifact_dir\uFF0C\u4EC5\u7D22\u5F15\u8FDB state\uFF0C\u89C1 DESIGN \xA74.2/\xA74.7\uFF09`
822
+ };
823
+ }
824
+ if (code === "gateway/cancelled" || signal.aborted) return { status: "failed", message: "aborted" };
825
+ return { status: "failed", message: `${relPath}: ${message}` };
826
+ }
827
+ version = result.value.version;
828
+ pages.push(result.value.text);
829
+ totalLines += result.value.lines;
830
+ if (result.value.eof) break;
831
+ if (result.value.lines === 0) break;
832
+ offset += result.value.lines;
833
+ }
834
+ return { status: "ok", text: pages.join("\n"), lines: totalLines, version };
835
+ }
836
+ async function statVersion(remote, sessionId, relPath, signal) {
837
+ const result = await remote.stat(sessionId, relPath, signal);
838
+ if (!result.ok) {
839
+ const code = result.error.code ?? "";
840
+ if (code.includes("not-found") || code.includes("ENOENT")) return { status: "missing" };
841
+ return { status: "failed", message: result.error.message ?? "stat failed" };
842
+ }
843
+ return { status: "ok", version: result.value.version };
844
+ }
845
+ async function readWorkbenchState(ctx, sessionId, stateRel, signal) {
846
+ const remote = workspaceFilesOf(ctx);
847
+ if (remote === void 0) return { status: "failed", message: "Remote carrier unavailable (remote.workspaceFiles)" };
848
+ const paged = await readTextPaged(remote, sessionId, stateRel, signal);
849
+ if (paged.status === "missing") return { status: "missing" };
850
+ if (paged.status === "failed") return { status: "failed", message: paged.message };
851
+ if (paged.status === "too-large") return { status: "failed", message: paged.message };
852
+ try {
853
+ const state = JSON.parse(paged.text);
854
+ if (state.schema !== "ops-tune-workbench.state/2") {
855
+ return { status: "failed", message: `state.json schema \u4E0D\u8BC6\u522B\uFF1A${String(state.schema)}` };
856
+ }
857
+ return { status: "ok", state };
858
+ } catch (error) {
859
+ return { status: "failed", message: `state.json parse failed: ${String(error)}` };
860
+ }
861
+ }
862
+ async function readCsvRows(ctx, sessionId, relPath, maxRows, signal) {
863
+ const remote = workspaceFilesOf(ctx);
864
+ if (remote === void 0) return { status: "failed", message: "Remote carrier unavailable (remote.workspaceFiles)" };
865
+ const paged = await readTextPaged(remote, sessionId, relPath, signal);
866
+ if (paged.status === "missing") return { status: "missing" };
867
+ if (paged.status === "failed") return { status: "failed", message: paged.message };
868
+ if (paged.status === "too-large") return { status: "failed", message: paged.message };
869
+ const rawLines = paged.text.split("\n").map((l) => l.replace(/\r$/, "")).filter((l) => l.trim() !== "");
870
+ const headers = rawLines.length > 0 ? parseCsvLine(rawLines[0]) : [];
871
+ const body = rawLines.slice(1);
872
+ const truncated = body.length > maxRows;
873
+ return {
874
+ status: "ok",
875
+ rows: {
876
+ status: "ok",
877
+ headers,
878
+ rows: body.slice(0, maxRows).map((line) => {
879
+ const cells = parseCsvLine(line);
880
+ while (cells.length < headers.length) cells.push("");
881
+ return cells;
882
+ }),
883
+ totalLines: body.length,
884
+ truncated
885
+ }
886
+ };
887
+ }
888
+ async function readTraceText(ctx, sessionId, relPath, signal) {
889
+ const remote = workspaceFilesOf(ctx);
890
+ if (remote === void 0) return { status: "failed", message: "Remote carrier unavailable (remote.workspaceFiles)" };
891
+ const paged = await readTextPaged(remote, sessionId, relPath, signal);
892
+ if (paged.status === "missing") return { status: "missing" };
893
+ if (paged.status === "failed") return { status: "failed", message: paged.message };
894
+ if (paged.status === "too-large") {
895
+ return { status: "failed", message: paged.message };
896
+ }
897
+ return { status: "ok", text: paged.text };
898
+ }
899
+ function parseCsvLine(line) {
900
+ const cells = [];
901
+ let cur = "";
902
+ let inQuotes = false;
903
+ for (let i = 0; i < line.length; i++) {
904
+ const ch = line[i];
905
+ if (inQuotes) {
906
+ if (ch === '"') {
907
+ if (line[i + 1] === '"') {
908
+ cur += '"';
909
+ i++;
910
+ } else inQuotes = false;
911
+ } else cur += ch;
912
+ } else if (ch === '"') {
913
+ inQuotes = true;
914
+ } else if (ch === ",") {
915
+ cells.push(cur);
916
+ cur = "";
917
+ } else {
918
+ cur += ch;
919
+ }
920
+ }
921
+ cells.push(cur);
922
+ return cells;
923
+ }
924
+
925
+ // src/client/csv-pages.tsx
926
+ var import_jsx_runtime2 = require("react/jsx-runtime");
927
+ var FULL_CSV_MAX_ROWS = 2e3;
928
+ function CsvFieldListPanel(props) {
929
+ const { ctx, sessionId, tables, locale, group, selectedBlockId, onSelectBlock, showViewAll = true, showBlockSwitcher = true } = props;
930
+ const [activeFile, setActiveFile] = (0, import_react2.useState)(tables[0]?.file_name ?? "");
931
+ const [search, setSearch] = (0, import_react2.useState)("");
932
+ const [load, setLoad] = (0, import_react2.useState)({ status: "idle" });
933
+ const [fullCsv, setFullCsv] = (0, import_react2.useState)(null);
934
+ const [fullCsvOpen, setFullCsvOpen] = (0, import_react2.useState)(false);
935
+ (0, import_react2.useEffect)(() => {
936
+ if (tables.length > 0 && !tables.some((t) => t.file_name === activeFile)) {
937
+ setActiveFile(tables[0].file_name);
938
+ }
939
+ }, [tables, activeFile]);
940
+ const active = tables.find((t) => t.file_name === activeFile) ?? null;
941
+ (0, import_react2.useEffect)(() => {
942
+ if (active === null || active.rel_path === null || active.row_count === 0) {
943
+ setLoad(active === null ? { status: "idle" } : { status: "missing", reason: active.missing_reason ?? `${active.file_name} \u672A\u91C7\u96C6` });
944
+ return;
945
+ }
946
+ let stopped = false;
947
+ setLoad({ status: "loading" });
948
+ void (async () => {
949
+ const remote = workspaceFilesOf(ctx);
950
+ if (remote === void 0) {
951
+ if (!stopped) setLoad({ status: "failed", message: "Remote carrier unavailable" });
952
+ return;
953
+ }
954
+ const result = await readCsvRows(ctx, sessionId, active.rel_path, FULL_CSV_MAX_ROWS, new AbortController().signal);
955
+ if (stopped) return;
956
+ if (result.status === "missing") {
957
+ setLoad({ status: "missing", reason: `${active.file_name} \u5F52\u6863\u7F3A\u5931` });
958
+ return;
959
+ }
960
+ if (result.status === "failed") {
961
+ setLoad({ status: "failed", message: result.message });
962
+ return;
963
+ }
964
+ const headers = result.rows.headers.length > 0 ? result.rows.headers : active.headers ?? [];
965
+ const records = result.rows.rows.map((cells) => {
966
+ const rec = {};
967
+ headers.forEach((h, i) => {
968
+ rec[h] = cells[i] ?? "";
969
+ });
970
+ return rec;
971
+ });
972
+ setLoad({
973
+ status: "ok",
974
+ headers,
975
+ rows: records,
976
+ blockIds: active.block_ids.length > 0 ? active.block_ids : uniqueBlockIds(records)
977
+ });
978
+ })();
979
+ return () => {
980
+ stopped = true;
981
+ };
982
+ }, [active?.file_name, active?.rel_path, active?.row_count, sessionId]);
983
+ const displayName = (file) => file.replace(/\.csv$/i, "").replace(/^Memory$/, "MemoryL1");
984
+ const currentRow = (0, import_react2.useMemo)(() => {
985
+ if (load.status !== "ok") return null;
986
+ if (load.blockIds.length === 0) return load.rows[0] ?? null;
987
+ const block = selectedBlockId !== null && load.blockIds.includes(selectedBlockId) ? selectedBlockId : load.blockIds[0];
988
+ return load.rows.find((r) => (r.block_id ?? "").trim() === block) ?? load.rows[0] ?? null;
989
+ }, [load, selectedBlockId]);
990
+ const fieldRows = (0, import_react2.useMemo)(() => {
991
+ if (load.status !== "ok" || currentRow === null) return [];
992
+ const q = search.trim().toLowerCase();
993
+ const headers = q !== "" ? load.headers.filter((h) => h.toLowerCase().includes(q)) : load.headers;
994
+ return headers.map((header) => ({
995
+ header,
996
+ value: currentRow[header] ?? "",
997
+ parts: splitHighlight(header, q)
998
+ }));
999
+ }, [load, currentRow, search]);
1000
+ const openFullCsv = () => {
1001
+ if (active === null || active.rel_path === null) return;
1002
+ let stopped = false;
1003
+ setFullCsvOpen(true);
1004
+ setFullCsv(null);
1005
+ void (async () => {
1006
+ const result = await readCsvRows(ctx, sessionId, active.rel_path, FULL_CSV_MAX_ROWS, new AbortController().signal);
1007
+ if (stopped) return;
1008
+ if (result.status !== "ok") {
1009
+ setFullCsvOpen(false);
1010
+ return;
1011
+ }
1012
+ setFullCsv({ headers: result.rows.headers, rows: result.rows.rows, totalLines: result.rows.totalLines, truncated: result.rows.truncated });
1013
+ })();
1014
+ };
1015
+ const hasBlockSwitcher = showBlockSwitcher && load.status === "ok" && load.blockIds.length > 0;
1016
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "otwb-csv", "data-testid": "csv-field-list", children: [
1017
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "otwb-csv-tabs", role: "tablist", "data-testid": "csv-tabs", children: tables.map((t) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1018
+ "button",
1019
+ {
1020
+ type: "button",
1021
+ role: "tab",
1022
+ className: `otwb-csv-tab${t.file_name === activeFile ? " otwb-csv-tab--active" : ""}`,
1023
+ "data-testid": `csv-tab-${t.file_name}`,
1024
+ "aria-selected": t.file_name === activeFile,
1025
+ onClick: () => {
1026
+ setActiveFile(t.file_name);
1027
+ setSearch("");
1028
+ },
1029
+ children: displayName(t.file_name)
1030
+ },
1031
+ t.file_name
1032
+ )) }),
1033
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "otwb-csv-toolbar", children: [
1034
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "otwb-csv-search", children: [
1035
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "otwb-sr-only", children: locale("searchLabel") }),
1036
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1037
+ "input",
1038
+ {
1039
+ "data-testid": "csv-search",
1040
+ type: "search",
1041
+ placeholder: locale("searchPlaceholder"),
1042
+ value: search,
1043
+ onChange: (e) => setSearch(e.target.value)
1044
+ }
1045
+ ),
1046
+ search.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", "data-testid": "csv-search-clear", className: "otwb-csv-clear", "aria-label": locale("searchClear"), onClick: () => setSearch(""), children: "\xD7" })
1047
+ ] }),
1048
+ (hasBlockSwitcher || showViewAll) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "otwb-csv-actions", children: [
1049
+ hasBlockSwitcher && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "otwb-csv-block", children: [
1050
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: locale("block") }),
1051
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1052
+ "select",
1053
+ {
1054
+ "data-testid": "csv-block",
1055
+ value: currentRowBlock(load, selectedBlockId),
1056
+ onChange: (e) => onSelectBlock(e.target.value),
1057
+ children: load.status === "ok" && load.blockIds.map((b) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: b, children: b }, b))
1058
+ }
1059
+ )
1060
+ ] }),
1061
+ showViewAll && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1062
+ "button",
1063
+ {
1064
+ type: "button",
1065
+ className: "otwb-csv-viewall",
1066
+ "data-testid": "csv-view-all",
1067
+ disabled: active === null || active.rel_path === null,
1068
+ onClick: openFullCsv,
1069
+ children: locale("viewAll")
1070
+ }
1071
+ )
1072
+ ] })
1073
+ ] }),
1074
+ load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "otwb-csv-note", children: "\u2026" }),
1075
+ load.status === "missing" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "otwb-csv-empty", "data-testid": "csv-missing", children: [
1076
+ locale("notAvailable"),
1077
+ " \xB7 ",
1078
+ load.reason
1079
+ ] }),
1080
+ load.status === "failed" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "otwb-csv-empty otwb-csv-empty--error", children: load.message }),
1081
+ load.status === "ok" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { className: "otwb-csv-fields", "data-testid": "csv-fields", children: fieldRows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: "otwb-csv-field", children: [
1082
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "otwb-csv-field-name", children: row.parts.map((p, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: p.match ? "otwb-csv-match" : void 0, "data-testid": p.match ? "csv-field-match" : void 0, children: p.text }, i)) }),
1083
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "otwb-csv-field-value", title: row.value, children: row.value === "" ? locale("notAvailable") : row.value })
1084
+ ] }, row.header)) }),
1085
+ fullCsvOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1086
+ FullCsvView,
1087
+ {
1088
+ title: `${displayName(active?.file_name ?? "")} \xB7 ${locale("viewFullCsv")}`,
1089
+ data: fullCsv,
1090
+ locale,
1091
+ onClose: () => setFullCsvOpen(false)
1092
+ }
1093
+ )
1094
+ ] });
1095
+ }
1096
+ function currentRowBlock(load, selectedBlockId) {
1097
+ if (load.status !== "ok" || load.blockIds.length === 0) return "";
1098
+ if (selectedBlockId !== null && load.blockIds.includes(selectedBlockId)) return selectedBlockId;
1099
+ return load.blockIds[0];
1100
+ }
1101
+ function uniqueBlockIds(rows) {
1102
+ const seen = /* @__PURE__ */ new Set();
1103
+ const out = [];
1104
+ for (const row of rows) {
1105
+ const b = (row.block_id ?? "").trim();
1106
+ if (b === "" || seen.has(b)) continue;
1107
+ seen.add(b);
1108
+ out.push(b);
1109
+ }
1110
+ return out;
1111
+ }
1112
+ function splitHighlight(text, query) {
1113
+ if (query === "") return [{ text, match: false }];
1114
+ const lower = text.toLowerCase();
1115
+ const parts = [];
1116
+ let i = 0;
1117
+ while (i < text.length) {
1118
+ const hit = lower.indexOf(query, i);
1119
+ if (hit === -1) {
1120
+ parts.push({ text: text.slice(i), match: false });
1121
+ break;
1122
+ }
1123
+ if (hit > i) parts.push({ text: text.slice(i, hit), match: false });
1124
+ parts.push({ text: text.slice(hit, hit + query.length), match: true });
1125
+ i = hit + query.length;
1126
+ }
1127
+ return parts;
1128
+ }
1129
+ function FullCsvView(props) {
1130
+ const { title, data, locale, onClose } = props;
1131
+ const [search, setSearch] = (0, import_react2.useState)("");
1132
+ const filtered = (0, import_react2.useMemo)(() => {
1133
+ if (data === null || search.trim() === "") return data;
1134
+ const q = search.trim().toLowerCase();
1135
+ const keep = /* @__PURE__ */ new Set();
1136
+ data.headers.forEach((h, i) => {
1137
+ if (h.toLowerCase().includes(q)) keep.add(i);
1138
+ });
1139
+ return {
1140
+ headers: data.headers,
1141
+ totalLines: data.totalLines,
1142
+ truncated: data.truncated,
1143
+ rows: data.rows.filter((row) => row.some((cell, i) => keep.has(i) || cell.toLowerCase().includes(q)))
1144
+ };
1145
+ }, [data, search]);
1146
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "otwb-fullcsv", "data-testid": "full-csv-view", role: "dialog", "aria-label": locale("viewFullCsv"), children: [
1147
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "otwb-fullcsv-head", children: [
1148
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "otwb-fullcsv-title", children: title }),
1149
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "search", placeholder: locale("searchPlaceholder"), value: search, onChange: (e) => setSearch(e.target.value), "data-testid": "full-csv-search" }),
1150
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", onClick: onClose, "aria-label": locale("closePanel"), "data-testid": "full-csv-close", children: "\xD7" })
1151
+ ] }),
1152
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "otwb-fullcsv-body", children: [
1153
+ data === null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "otwb-csv-note", children: "\u2026" }),
1154
+ data !== null && filtered !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("table", { className: "otwb-fullcsv-table", children: [
1155
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
1156
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { className: "otwb-fullcsv-rownum", children: "#" }),
1157
+ data.headers.map((h, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { title: h, children: h }, `${h}-${String(i)}`))
1158
+ ] }) }),
1159
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: filtered.rows.map((row, ri) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
1160
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "otwb-fullcsv-rownum", children: String(ri + 1) }),
1161
+ row.map((cell, ci) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: cell }, String(ci)))
1162
+ ] }, String(ri))) })
1163
+ ] })
1164
+ ] }),
1165
+ data !== null && data.truncated && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "otwb-fullcsv-note", "data-testid": "full-csv-truncated", children: locale("fullCsvTruncated", { n: FULL_CSV_MAX_ROWS }) })
1166
+ ] });
1167
+ }
1168
+
1169
+ // src/client/timeline/view.tsx
1170
+ var import_react3 = require("react");
1171
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1172
+ var LANE_ROW_H = 22;
1173
+ var LANE_HEAD_H = 18;
1174
+ var AXIS_H = 26;
1175
+ var PINNED_STRIP_H = 30;
1176
+ var MIN_VIEW_NS = 1;
1177
+ var TICK_STEPS = (() => {
1178
+ const ms = 1e6, s = 1e9;
1179
+ return [
1180
+ 1,
1181
+ 2,
1182
+ 5,
1183
+ 10,
1184
+ 20,
1185
+ 50,
1186
+ 100,
1187
+ 200,
1188
+ 500,
1189
+ 1e3,
1190
+ 2e3,
1191
+ 5e3,
1192
+ 1e4,
1193
+ 2e4,
1194
+ 5e4,
1195
+ 1e5,
1196
+ 2e5,
1197
+ 5e5,
1198
+ 1 * ms,
1199
+ 2 * ms,
1200
+ 5 * ms,
1201
+ 10 * ms,
1202
+ 20 * ms,
1203
+ 50 * ms,
1204
+ 100 * ms,
1205
+ 200 * ms,
1206
+ 500 * ms,
1207
+ 1 * s,
1208
+ 2 * s,
1209
+ 5 * s,
1210
+ 10 * s,
1211
+ 20 * s,
1212
+ 30 * s,
1213
+ 60 * s,
1214
+ 120 * s,
1215
+ 300 * s,
1216
+ 600 * s,
1217
+ 1800 * s,
1218
+ 3600 * s
1219
+ ];
1220
+ })();
1221
+ function niceStep(span, width) {
1222
+ const target = Math.max(1, span) / Math.max(1, width > 0 ? width : 800);
1223
+ for (const step of TICK_STEPS) if (step >= target) return step;
1224
+ let scale = TICK_STEPS[TICK_STEPS.length - 1];
1225
+ while (scale < target) scale *= 10;
1226
+ return scale;
1227
+ }
1228
+ function autoTimeUnit(ns) {
1229
+ if (!Number.isFinite(ns) || ns <= 0) return "ns";
1230
+ if (ns >= 1e9) return "s";
1231
+ if (ns >= 1e6) return "ms";
1232
+ if (ns >= 1e3) return "us";
1233
+ return "ns";
1234
+ }
1235
+ var UNIT_FACTOR = { ns: 1, us: 1e3, ms: 1e6, s: 1e9 };
1236
+ function formatNs(ns, digits = 3) {
1237
+ if (!Number.isFinite(ns)) return "\u2014";
1238
+ const unit = autoTimeUnit(Math.abs(ns));
1239
+ const value = ns / UNIT_FACTOR[unit];
1240
+ return `${value >= 100 ? value.toFixed(1) : value.toFixed(digits)} ${unit}`;
1241
+ }
1242
+ function nsToCycles(ns, mhz) {
1243
+ if (!Number.isFinite(ns) || !Number.isFinite(mhz) || mhz <= 0) return "\u2014";
1244
+ const cycles = Math.round(ns * mhz / 1e3);
1245
+ return `${cycles.toLocaleString("en-US")} cycles`;
1246
+ }
1247
+ function fmtDuration(ns, mode, mhz) {
1248
+ if (mode === "cycles" && mhz !== null && mhz > 0) return nsToCycles(ns, mhz);
1249
+ return formatNs(ns);
1250
+ }
1251
+ function zoomToPercent(viewSpan, fullSpan) {
1252
+ const n = Math.max(MIN_VIEW_NS, fullSpan);
1253
+ const r = Math.max(MIN_VIEW_NS, viewSpan);
1254
+ if (r >= n) return 0;
1255
+ const ratio = n / r;
1256
+ if (ratio <= 1) return 0;
1257
+ return Math.min(100, Math.round(Math.log2(ratio) / Math.log2(n) * 100));
1258
+ }
1259
+ function percentToZoom(pct, fullSpan) {
1260
+ const n = Math.max(MIN_VIEW_NS, fullSpan);
1261
+ if (pct <= 0 || n <= MIN_VIEW_NS) return n;
1262
+ if (pct >= 100) return MIN_VIEW_NS;
1263
+ return Math.max(MIN_VIEW_NS, 2 ** (pct / 100 * Math.log2(n)));
1264
+ }
1265
+ function TimelineView(props) {
1266
+ const { model, state, setState, clockFreqMHz, locale, onSelect, height } = props;
1267
+ const [gutterWidth, setGutterWidth] = (0, import_react3.useState)(196);
1268
+ const [pinnedLaneIds, setPinnedLaneIds] = (0, import_react3.useState)([]);
1269
+ const [collapsedIds, setCollapsedIds] = (0, import_react3.useState)(() => new Set(model.metadata.defaultCollapsedIds));
1270
+ const [cursorX, setCursorX] = (0, import_react3.useState)(null);
1271
+ const [cursorSnapped, setCursorSnapped] = (0, import_react3.useState)(false);
1272
+ const [boxSel, setBoxSel] = (0, import_react3.useState)(null);
1273
+ const [dragPan, setDragPan] = (0, import_react3.useState)(null);
1274
+ const [measureDrag, setMeasureDrag] = (0, import_react3.useState)(null);
1275
+ const [viewSize, setViewSize] = (0, import_react3.useState)({ width: 800, height });
1276
+ const canvasRef = (0, import_react3.useRef)(null);
1277
+ const scrollRef = (0, import_react3.useRef)(null);
1278
+ const gutterDragRef = (0, import_react3.useRef)(null);
1279
+ const fullSpan = Math.max(1, model.maxTime);
1280
+ const lanes = (0, import_react3.useMemo)(() => flattenLanes(model, collapsedIds), [model, collapsedIds]);
1281
+ const pinnedLanes = (0, import_react3.useMemo)(
1282
+ () => lanes.filter((l) => pinnedLaneIds.includes(l.lane.id)),
1283
+ [lanes, pinnedLaneIds]
1284
+ );
1285
+ const bodyLanes = (0, import_react3.useMemo)(
1286
+ () => lanes.filter((l) => !pinnedLaneIds.includes(l.lane.id) && l.lane.events !== null && l.lane.name !== model.processes[0]?.name || l.depth > 0 || l.lane.events.length > 0 || l.lane.children !== void 0 || l.lane.categoryKey !== "hbm" || true),
1287
+ [lanes, pinnedLaneIds, model]
1288
+ );
1289
+ const rowsHeight = Math.max(bodyLanes.length * LANE_ROW_H + LANE_HEAD_H, 40);
1290
+ (0, import_react3.useEffect)(() => {
1291
+ if (state.endTime > fullSpan) {
1292
+ setState({ startTime: 0, endTime: fullSpan });
1293
+ }
1294
+ }, [fullSpan, state.endTime, setState]);
1295
+ const timeToPct = (0, import_react3.useCallback)((t) => {
1296
+ const span = Math.max(1, state.endTime - state.startTime);
1297
+ return (t - state.startTime) / span * 100;
1298
+ }, [state.startTime, state.endTime]);
1299
+ const pctToTime = (0, import_react3.useCallback)((pct) => {
1300
+ const span = Math.max(1, state.endTime - state.startTime);
1301
+ return state.startTime + pct / 100 * span;
1302
+ }, [state.startTime, state.endTime]);
1303
+ const zoomAround = (0, import_react3.useCallback)((factor, anchorTime) => {
1304
+ const span = Math.max(MIN_VIEW_NS, state.endTime - state.startTime);
1305
+ const next = Math.min(fullSpan, Math.max(MIN_VIEW_NS, span * factor));
1306
+ const ratio = (anchorTime - state.startTime) / span;
1307
+ let start = anchorTime - next * ratio;
1308
+ let end = start + next;
1309
+ if (start < 0) {
1310
+ start = 0;
1311
+ end = next;
1312
+ }
1313
+ if (end > fullSpan) {
1314
+ end = fullSpan;
1315
+ start = Math.max(0, end - next);
1316
+ }
1317
+ setState({ startTime: Math.round(start), endTime: Math.round(end), zoomPercent: zoomToPercent(end - start, fullSpan) });
1318
+ }, [state.startTime, state.endTime, fullSpan, setState]);
1319
+ const panBy = (0, import_react3.useCallback)((deltaNs) => {
1320
+ const span = Math.max(MIN_VIEW_NS, state.endTime - state.startTime);
1321
+ let start = state.startTime + deltaNs;
1322
+ let end = start + span;
1323
+ if (start < 0) {
1324
+ start = 0;
1325
+ end = span;
1326
+ }
1327
+ if (end > fullSpan) {
1328
+ end = fullSpan;
1329
+ start = Math.max(0, end - span);
1330
+ }
1331
+ setState({ startTime: Math.round(start), endTime: Math.round(end) });
1332
+ }, [state.startTime, state.endTime, fullSpan, setState]);
1333
+ const zoomFit = (0, import_react3.useCallback)(() => {
1334
+ setState({ startTime: 0, endTime: fullSpan, zoomPercent: 0 });
1335
+ }, [fullSpan, setState]);
1336
+ (0, import_react3.useEffect)(() => {
1337
+ const onKey = (ev) => {
1338
+ if (ev.key === "Escape" && (state.measureMode || state.measureRange !== null)) {
1339
+ setState({ measureRange: null, measureMode: false });
1340
+ return;
1341
+ }
1342
+ if (ev.ctrlKey || ev.metaKey || ev.altKey || ev.shiftKey) return;
1343
+ const target = ev.target;
1344
+ if (target !== null && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT" || target.isContentEditable)) return;
1345
+ const key = ev.key.toLowerCase();
1346
+ if (key === "w" || key === "s") {
1347
+ ev.preventDefault();
1348
+ const anchor = cursorX !== null ? pctToTime(cursorX) : (state.startTime + state.endTime) / 2;
1349
+ zoomAround(key === "w" ? 1 / 1.25 : 1.25, anchor);
1350
+ } else if (key === "a" || key === "d") {
1351
+ ev.preventDefault();
1352
+ const span = state.endTime - state.startTime;
1353
+ const step = 30 / Math.max(1, viewSize.width) * Math.max(1, span);
1354
+ panBy(key === "d" ? step : -step);
1355
+ }
1356
+ };
1357
+ window.addEventListener("keydown", onKey);
1358
+ return () => {
1359
+ window.removeEventListener("keydown", onKey);
1360
+ };
1361
+ }, [state.measureMode, state.measureRange, state.startTime, state.endTime, cursorX, viewSize.width, zoomAround, panBy, pctToTime, setState]);
1362
+ (0, import_react3.useEffect)(() => {
1363
+ const el = scrollRef.current;
1364
+ if (el === null) return;
1365
+ const ro = new ResizeObserver(() => {
1366
+ setViewSize({ width: el.clientWidth, height: el.clientHeight });
1367
+ });
1368
+ ro.observe(el);
1369
+ setViewSize({ width: el.clientWidth, height: el.clientHeight });
1370
+ return () => {
1371
+ ro.disconnect();
1372
+ };
1373
+ }, []);
1374
+ (0, import_react3.useEffect)(() => {
1375
+ const canvas = canvasRef.current;
1376
+ if (canvas === null) return;
1377
+ const parent = canvas.parentElement;
1378
+ if (parent === null) return;
1379
+ const width = Math.max(50, viewSize.width - gutterWidth);
1380
+ const dpr = window.devicePixelRatio ?? 1;
1381
+ canvas.width = Math.floor(width * dpr);
1382
+ canvas.height = Math.floor(rowsHeight * dpr);
1383
+ canvas.style.width = `${String(width)}px`;
1384
+ canvas.style.height = `${String(rowsHeight)}px`;
1385
+ const ctx = canvas.getContext("2d");
1386
+ if (ctx === null) return;
1387
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
1388
+ ctx.clearRect(0, 0, width, rowsHeight);
1389
+ const span = Math.max(1, state.endTime - state.startTime);
1390
+ const xOf = (t) => (t - state.startTime) / span * width;
1391
+ const searchLower = state.searchQuery.trim().toLowerCase();
1392
+ bodyLanes.forEach(({ lane, depth }, i) => {
1393
+ const y = LANE_HEAD_H + i * LANE_ROW_H;
1394
+ if (i % 2 === 1) {
1395
+ ctx.fillStyle = "rgba(128,128,128,0.06)";
1396
+ ctx.fillRect(0, y, width, LANE_ROW_H);
1397
+ }
1398
+ if (lane.children !== void 0 || lane.categoryKey !== void 0) {
1399
+ ctx.fillStyle = "rgba(128,128,128,0.18)";
1400
+ ctx.fillRect(0, y + LANE_ROW_H - 1, width, 1);
1401
+ }
1402
+ const indent = 14 * depth;
1403
+ void indent;
1404
+ for (const ev of lane.events) {
1405
+ const x0 = xOf(ev.startTime);
1406
+ const x1 = xOf(ev.endTime);
1407
+ if (x1 < 0 || x0 > width) continue;
1408
+ const cx0 = Math.max(x0, 0);
1409
+ const cx1 = Math.min(x1, width);
1410
+ const w = Math.max(1, cx1 - cx0);
1411
+ const selected = ev.id === state.selectedEventId;
1412
+ const hovered2 = ev.id === state.hoveredEventId;
1413
+ const isMatch = searchLower !== "" && ev.name.toLowerCase().includes(searchLower);
1414
+ ctx.fillStyle = PIPE_COLORS[ev.colorKey];
1415
+ if (selected || hovered2) {
1416
+ ctx.globalAlpha = 1;
1417
+ ctx.fillRect(cx0, y + 3, w, LANE_ROW_H - 6);
1418
+ ctx.strokeStyle = isMatch ? "#ffffff" : "rgba(255,255,255,0.9)";
1419
+ ctx.lineWidth = 1;
1420
+ ctx.strokeRect(cx0 + 0.5, y + 3.5, Math.max(1, w - 1), LANE_ROW_H - 7);
1421
+ } else if (searchLower !== "") {
1422
+ ctx.globalAlpha = isMatch ? 0.95 : 0.25;
1423
+ ctx.fillRect(cx0, y + 4, w, LANE_ROW_H - 8);
1424
+ } else {
1425
+ ctx.globalAlpha = 0.85;
1426
+ ctx.fillRect(cx0, y + 4, w, LANE_ROW_H - 8);
1427
+ }
1428
+ ctx.globalAlpha = 1;
1429
+ }
1430
+ });
1431
+ if (state.measureRange !== null) {
1432
+ const a = xOf(Math.min(state.measureRange.startTime, state.measureRange.endTime));
1433
+ const b = xOf(Math.max(state.measureRange.startTime, state.measureRange.endTime));
1434
+ ctx.fillStyle = "rgba(48,120,240,0.12)";
1435
+ ctx.fillRect(a, 0, Math.max(1, b - a), rowsHeight);
1436
+ ctx.strokeStyle = "#3078f0";
1437
+ ctx.setLineDash([4, 3]);
1438
+ ctx.beginPath();
1439
+ ctx.moveTo(a, 0);
1440
+ ctx.lineTo(a, rowsHeight);
1441
+ ctx.moveTo(b, 0);
1442
+ ctx.lineTo(b, rowsHeight);
1443
+ ctx.stroke();
1444
+ ctx.setLineDash([]);
1445
+ }
1446
+ if (measureDrag !== null) {
1447
+ const a = xOf(Math.min(measureDrag.start, measureDrag.current));
1448
+ const b = xOf(Math.max(measureDrag.start, measureDrag.current));
1449
+ ctx.fillStyle = "rgba(48,120,240,0.10)";
1450
+ ctx.fillRect(a, 0, Math.max(1, b - a), rowsHeight);
1451
+ }
1452
+ if (boxSel !== null) {
1453
+ ctx.strokeStyle = "#3078f0";
1454
+ ctx.setLineDash([3, 3]);
1455
+ ctx.strokeRect(Math.min(boxSel.x0, boxSel.x1), Math.min(boxSel.y0, boxSel.y1), Math.abs(boxSel.x1 - boxSel.x0), Math.abs(boxSel.y1 - boxSel.y0));
1456
+ ctx.setLineDash([]);
1457
+ }
1458
+ }, [bodyLanes, rowsHeight, state.startTime, state.endTime, state.selectedEventId, state.hoveredEventId, state.searchQuery, state.measureRange, measureDrag, boxSel, gutterWidth, viewSize.width]);
1459
+ const hitTest = (0, import_react3.useCallback)((offsetX, offsetY) => {
1460
+ const width = Math.max(50, viewSize.width - gutterWidth);
1461
+ const span = Math.max(1, state.endTime - state.startTime);
1462
+ const row = Math.floor((offsetY - LANE_HEAD_H) / LANE_ROW_H);
1463
+ if (row < 0 || row >= bodyLanes.length) return null;
1464
+ const lane = bodyLanes[row].lane;
1465
+ const t = state.startTime + offsetX / width * span;
1466
+ let best = null;
1467
+ for (const ev of lane.events) {
1468
+ if (t >= ev.startTime && t <= ev.endTime) {
1469
+ if (best === null || ev.duration < best.duration) best = ev;
1470
+ }
1471
+ }
1472
+ return best;
1473
+ }, [bodyLanes, state.startTime, state.endTime, viewSize.width, gutterWidth]);
1474
+ const findEventById = (0, import_react3.useCallback)((id) => {
1475
+ if (id === null) return null;
1476
+ for (const lane of allLaneObjects(model)) {
1477
+ for (const ev of lane.events) if (ev.id === id) return ev;
1478
+ }
1479
+ return null;
1480
+ }, [model]);
1481
+ const onCanvasPointerDown = (ev) => {
1482
+ if (ev.button !== 0) return;
1483
+ const rect = ev.currentTarget.getBoundingClientRect();
1484
+ const offsetX = ev.clientX - rect.left;
1485
+ const offsetY = ev.clientY - rect.top;
1486
+ if (state.measureMode) {
1487
+ const span = Math.max(1, state.endTime - state.startTime);
1488
+ const width = Math.max(50, viewSize.width - gutterWidth);
1489
+ const t = state.startTime + offsetX / width * span;
1490
+ setMeasureDrag({ start: t, current: t });
1491
+ ev.currentTarget.setPointerCapture(ev.pointerId);
1492
+ return;
1493
+ }
1494
+ if (ev.ctrlKey || ev.metaKey) {
1495
+ setDragPan({ x: offsetX, start: state.startTime, end: state.endTime });
1496
+ ev.currentTarget.setPointerCapture(ev.pointerId);
1497
+ return;
1498
+ }
1499
+ setBoxSel({ x0: offsetX, y0: offsetY, x1: offsetX, y1: offsetY });
1500
+ ev.currentTarget.setPointerCapture(ev.pointerId);
1501
+ };
1502
+ const onCanvasPointerMove = (ev) => {
1503
+ const rect = ev.currentTarget.getBoundingClientRect();
1504
+ const offsetX = ev.clientX - rect.left;
1505
+ const offsetY = ev.clientY - rect.top;
1506
+ const width = Math.max(50, viewSize.width - gutterWidth);
1507
+ const span = Math.max(1, state.endTime - state.startTime);
1508
+ const pct = Math.min(100, Math.max(0, offsetX / width * 100));
1509
+ setCursorX(pct);
1510
+ if (measureDrag !== null) {
1511
+ setMeasureDrag({ ...measureDrag, current: state.startTime + offsetX / width * span });
1512
+ return;
1513
+ }
1514
+ if (dragPan !== null) {
1515
+ const dPx = dragPan.x - offsetX;
1516
+ const dNs = dPx / width * span;
1517
+ let start = Math.max(0, Math.min(fullSpan - (dragPan.end - dragPan.start), dragPan.start + dNs));
1518
+ setState({ startTime: Math.round(start), endTime: Math.round(start + (dragPan.end - dragPan.start)) });
1519
+ return;
1520
+ }
1521
+ if (boxSel !== null) {
1522
+ setBoxSel({ ...boxSel, x1: offsetX, y1: offsetY });
1523
+ return;
1524
+ }
1525
+ const hit = hitTest(offsetX, offsetY);
1526
+ setCursorSnapped(hit !== null);
1527
+ const id = hit?.id ?? null;
1528
+ if (id !== state.hoveredEventId) setState({ hoveredEventId: id });
1529
+ };
1530
+ const onCanvasPointerUp = (ev) => {
1531
+ const rect = ev.currentTarget.getBoundingClientRect();
1532
+ const offsetX = ev.clientX - rect.left;
1533
+ const offsetY = ev.clientY - rect.top;
1534
+ if (measureDrag !== null) {
1535
+ const range = { startTime: Math.round(Math.min(measureDrag.start, measureDrag.current)), endTime: Math.round(Math.max(measureDrag.start, measureDrag.current)) };
1536
+ setMeasureDrag(null);
1537
+ if (range.endTime > range.startTime) setState({ measureRange: range });
1538
+ return;
1539
+ }
1540
+ if (dragPan !== null) {
1541
+ setDragPan(null);
1542
+ return;
1543
+ }
1544
+ if (boxSel !== null) {
1545
+ const tiny = Math.abs(boxSel.x1 - boxSel.x0) < 4 && Math.abs(boxSel.y1 - boxSel.y0) < 4;
1546
+ if (tiny) {
1547
+ const hit = hitTest(offsetX, offsetY);
1548
+ setState({ selectedEventId: hit?.id ?? null });
1549
+ onSelect(hit);
1550
+ } else {
1551
+ const width = Math.max(50, viewSize.width - gutterWidth);
1552
+ const t0 = state.startTime + Math.min(boxSel.x0, boxSel.x1) / width * (state.endTime - state.startTime);
1553
+ const t1 = state.startTime + Math.max(boxSel.x0, boxSel.x1) / width * (state.endTime - state.startTime);
1554
+ if (t1 - t0 >= MIN_VIEW_NS) {
1555
+ setState({ startTime: Math.round(t0), endTime: Math.round(t1), zoomPercent: zoomToPercent(t1 - t0, fullSpan) });
1556
+ }
1557
+ }
1558
+ setBoxSel(null);
1559
+ }
1560
+ };
1561
+ const onCanvasWheel = (ev) => {
1562
+ if (!(ev.ctrlKey || ev.metaKey)) return;
1563
+ ev.preventDefault();
1564
+ const rect = ev.currentTarget.getBoundingClientRect();
1565
+ const offsetX = ev.clientX - rect.left;
1566
+ const width = Math.max(50, viewSize.width - gutterWidth);
1567
+ const anchor = state.startTime + offsetX / width * (state.endTime - state.startTime);
1568
+ zoomAround(ev.deltaY < 0 ? 1 / 1.2 : 1.2, anchor);
1569
+ };
1570
+ const onGutterPointerDown = (ev) => {
1571
+ if (ev.button !== 0) return;
1572
+ gutterDragRef.current = { x: ev.clientX, w: gutterWidth };
1573
+ ev.currentTarget.setPointerCapture(ev.pointerId);
1574
+ };
1575
+ const onGutterPointerMove = (ev) => {
1576
+ const drag = gutterDragRef.current;
1577
+ if (drag === null) return;
1578
+ setGutterWidth(Math.min(420, Math.max(120, drag.w + (ev.clientX - drag.x))));
1579
+ };
1580
+ const onGutterPointerUp = () => {
1581
+ gutterDragRef.current = null;
1582
+ };
1583
+ const toggleCollapse = (id) => {
1584
+ setCollapsedIds((prev) => {
1585
+ const next = new Set(prev);
1586
+ if (next.has(id)) next.delete(id);
1587
+ else next.add(id);
1588
+ return next;
1589
+ });
1590
+ };
1591
+ const togglePin = (id) => {
1592
+ setPinnedLaneIds((prev) => prev.includes(id) ? prev.filter((p) => p !== id) : [...prev, id]);
1593
+ };
1594
+ const axis = (0, import_react3.useMemo)(() => {
1595
+ const width = Math.max(50, viewSize.width - gutterWidth);
1596
+ const step = niceStep(state.endTime - state.startTime, width);
1597
+ const majors = [];
1598
+ const minors = [];
1599
+ const first = Math.ceil(state.startTime / step) * step;
1600
+ for (let t = first; t <= state.endTime + 1e-9; t += step) {
1601
+ const pct = (t - state.startTime) / Math.max(1, state.endTime - state.startTime) * 100;
1602
+ majors.push({ t, pct, label: fmtDuration(t, state.timeDisplayMode, clockFreqMHz) });
1603
+ }
1604
+ const minorStep = step / 10;
1605
+ for (let t = state.startTime; t <= state.endTime; t += minorStep) {
1606
+ const pct = (t - state.startTime) / Math.max(1, state.endTime - state.startTime) * 100;
1607
+ if (pct < 0 || pct > 100) continue;
1608
+ minors.push(pct);
1609
+ }
1610
+ return { majors, minors };
1611
+ }, [state.startTime, state.endTime, state.timeDisplayMode, clockFreqMHz, viewSize.width, gutterWidth]);
1612
+ const hovered = findEventById(state.hoveredEventId);
1613
+ const measureLabel = state.measureRange !== null && state.measureRange.endTime > state.measureRange.startTime ? fmtDuration(state.measureRange.endTime - state.measureRange.startTime, state.timeDisplayMode, clockFreqMHz) : null;
1614
+ const visibleDeps = (0, import_react3.useMemo)(() => {
1615
+ if (!state.showDependencies) return [];
1616
+ const deps = [];
1617
+ const index = /* @__PURE__ */ new Map();
1618
+ for (const lane of allLaneObjects(model)) for (const ev of lane.events) index.set(ev.id, { lane, event: ev });
1619
+ for (const { lane } of bodyLanes) {
1620
+ for (const ev of lane.events) {
1621
+ if (ev.dependencies === void 0) continue;
1622
+ for (const succ of ev.dependencies.successors) {
1623
+ const target = index.get(succ.tid);
1624
+ void target;
1625
+ const targetLane = laneById(model, succ.tid);
1626
+ const targetEvent = targetLane?.events[succ.index];
1627
+ if (targetEvent !== void 0) deps.push({ from: ev, to: targetEvent });
1628
+ }
1629
+ }
1630
+ }
1631
+ return deps;
1632
+ }, [state.showDependencies, model, bodyLanes]);
1633
+ const laneRowY = (lane) => {
1634
+ const idx = bodyLanes.findIndex((l) => l.lane.id === lane.id);
1635
+ return idx < 0 ? null : LANE_HEAD_H + idx * LANE_ROW_H + LANE_ROW_H / 2;
1636
+ };
1637
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "otwb-tl", "data-testid": "timeline-view", children: [
1638
+ pinnedLanes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "otwb-tl-pinned", style: { height: `${String(PINNED_STRIP_H)}px` }, "data-testid": "pinned-strip", children: [
1639
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "otwb-tl-pinned-labels", style: { width: `${String(gutterWidth)}px` }, children: pinnedLanes.map(({ lane }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { type: "button", className: "otwb-tl-pinrow", onClick: () => togglePin(lane.id), title: locale("unpin"), children: [
1640
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "otwb-tl-pin-dot", style: { background: laneUtilColor(lane) } }),
1641
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "otwb-tl-lane-name", children: lane.name })
1642
+ ] }, lane.id)) }),
1643
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "otwb-tl-pinned-canvas" })
1644
+ ] }),
1645
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "otwb-tl-axis", "data-testid": "time-axis", style: { paddingLeft: `${String(gutterWidth)}px` }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "otwb-tl-axis-ruler", "data-testid": "axis-ruler", children: [
1646
+ axis.minors.map((pct, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "otwb-tl-axis-minor", "data-testid": "axis-ruler-minor", style: { left: `${pct.toFixed(3)}%` } }, `min-${String(i)}`)),
1647
+ axis.majors.map((m) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "otwb-tl-axis-major", "data-testid": "axis-ruler-major", style: { left: `${m.pct.toFixed(3)}%` }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "otwb-tl-axis-label", children: m.label }) }, `maj-${String(m.t)}`))
1648
+ ] }) }),
1649
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "otwb-tl-body", children: [
1650
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "otwb-tl-gutter", style: { width: `${String(gutterWidth)}px` }, "data-testid": "lane-gutter", children: [
1651
+ bodyLanes.map(({ lane, depth }, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1652
+ "div",
1653
+ {
1654
+ className: `otwb-tl-lane${lane.children !== void 0 ? " otwb-tl-lane--group" : ""}`,
1655
+ style: { top: `${String(LANE_HEAD_H + i * LANE_ROW_H)}px`, paddingLeft: `${String(4 + depth * 14)}px`, height: `${String(LANE_ROW_H)}px` },
1656
+ children: [
1657
+ lane.children !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "otwb-tl-chevron", onClick: () => toggleCollapse(lane.id), "aria-label": collapsedIds.has(lane.id) ? "+" : "\u2212", "data-testid": `lane-chevron-${lane.id}`, children: collapsedIds.has(lane.id) ? "\u25B8" : "\u25BE" }),
1658
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "otwb-tl-lane-name", title: lane.name, children: lane.name }),
1659
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "otwb-tl-lane-util", "data-testid": "lane-util", children: lane.utilization !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1660
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "otwb-tl-util-fill", style: { width: `${Math.min(100, Math.max(0, lane.utilization * 100)).toFixed(1)}%`, background: laneUtilColor(lane) } }),
1661
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "otwb-tl-util-mid" })
1662
+ ] }) }),
1663
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "otwb-tl-pin", title: locale("pin"), "aria-label": locale("pin"), onClick: () => togglePin(lane.id), "data-testid": `lane-pin-${lane.id}`, children: "pin" })
1664
+ ]
1665
+ },
1666
+ lane.id
1667
+ )),
1668
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1669
+ "div",
1670
+ {
1671
+ className: "otwb-tl-gutter-resize",
1672
+ role: "separator",
1673
+ "aria-label": locale("resizeLaneGutter"),
1674
+ "data-testid": "gutter-resize-handle",
1675
+ onPointerDown: onGutterPointerDown,
1676
+ onPointerMove: onGutterPointerMove,
1677
+ onPointerUp: onGutterPointerUp,
1678
+ onPointerCancel: onGutterPointerUp
1679
+ }
1680
+ )
1681
+ ] }),
1682
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "otwb-tl-scroll", ref: scrollRef, style: { height: `${String(Math.max(height - AXIS_H - (pinnedLanes.length > 0 ? PINNED_STRIP_H : 0), 60))}px` }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "otwb-tl-canvaswrap", style: { height: `${String(rowsHeight)}px` }, children: [
1683
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1684
+ "canvas",
1685
+ {
1686
+ ref: canvasRef,
1687
+ "data-testid": "swimlane-canvas",
1688
+ onPointerDown: onCanvasPointerDown,
1689
+ onPointerMove: onCanvasPointerMove,
1690
+ onPointerUp: onCanvasPointerUp,
1691
+ onPointerCancel: onCanvasPointerUp,
1692
+ onWheel: onCanvasWheel,
1693
+ onDoubleClick: () => zoomFit()
1694
+ }
1695
+ ),
1696
+ visibleDeps.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { className: "otwb-tl-deps", "data-testid": "dependency-overlay", width: "100%", height: rowsHeight, viewBox: `0 0 100 ${String(rowsHeight)}`, preserveAspectRatio: "none", children: [
1697
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("marker", { id: "otwb-dep-arrow", markerWidth: "6", markerHeight: "6", refX: "5", refY: "3", orient: "auto", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M0,0 L6,3 L0,6 Z", fill: "currentColor" }) }) }),
1698
+ visibleDeps.map(({ from, to }, i) => {
1699
+ const yFrom = laneRowY(laneOfEvent(model, from) ?? { id: "", name: "", events: [], utilization: null });
1700
+ const yTo = laneRowY(laneOfEvent(model, to) ?? { id: "", name: "", events: [], utilization: null });
1701
+ if (yFrom === null || yTo === null) return null;
1702
+ const span = Math.max(1, state.endTime - state.startTime);
1703
+ const x0 = (from.endTime - state.startTime) / span * 100;
1704
+ const x1 = (to.startTime - state.startTime) / span * 100;
1705
+ if (x1 < 0 || x0 > 100) return null;
1706
+ const mx = (x0 + x1) / 2;
1707
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1708
+ "path",
1709
+ {
1710
+ d: `M ${x0.toFixed(2)} ${String(yFrom)} C ${mx.toFixed(2)} ${String(yFrom)}, ${mx.toFixed(2)} ${String(yTo)}, ${x1.toFixed(2)} ${String(yTo)}`,
1711
+ fill: "none",
1712
+ stroke: "currentColor",
1713
+ strokeWidth: "0.35",
1714
+ vectorEffect: "non-scaling-stroke",
1715
+ markerEnd: "url(#otwb-dep-arrow)"
1716
+ },
1717
+ `dep-${String(i)}`
1718
+ );
1719
+ })
1720
+ ] }),
1721
+ cursorX !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `otwb-tl-cursor${cursorSnapped ? " otwb-tl-cursor--snapped" : ""}`, "data-testid": "swim-cursor", style: { left: `${cursorX.toFixed(3)}%` }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "otwb-tl-cursor-label", "data-testid": "cursor-label", children: fmtDuration(pctToTime(cursorX), state.timeDisplayMode, clockFreqMHz) }) }),
1722
+ hovered !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "otwb-tl-tip", "data-testid": "event-tooltip", style: tooltipPos(hovered, state, bodyLanes, gutterWidth, viewSize.width), children: [
1723
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "otwb-tl-tip-name", children: hovered.name }),
1724
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1725
+ locale("start"),
1726
+ ": ",
1727
+ fmtDuration(hovered.startTime, state.timeDisplayMode, clockFreqMHz)
1728
+ ] }),
1729
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1730
+ locale("dur"),
1731
+ ": ",
1732
+ fmtDuration(hovered.duration, state.timeDisplayMode, clockFreqMHz)
1733
+ ] }),
1734
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1735
+ locale("end"),
1736
+ ": ",
1737
+ fmtDuration(hovered.endTime, state.timeDisplayMode, clockFreqMHz)
1738
+ ] })
1739
+ ] }),
1740
+ measureLabel !== null && state.measureRange !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "otwb-tl-measure", "data-testid": "measure-arrow", children: [
1741
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "otwb-tl-measure-label", "data-testid": "measure-label", children: measureLabel }),
1742
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "otwb-tl-measure-focus", onClick: () => {
1743
+ setState({ startTime: state.measureRange.startTime, endTime: state.measureRange.endTime });
1744
+ }, children: locale("focusMeasureRange") }),
1745
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "otwb-tl-measure-clear", onClick: () => setState({ measureRange: null }), "aria-label": locale("closePanel"), children: "\xD7" })
1746
+ ] })
1747
+ ] }) })
1748
+ ] })
1749
+ ] });
1750
+ }
1751
+ function laneUtilColor(lane) {
1752
+ const key = lane.events.length > 0 ? lane.events[0].colorKey : "default";
1753
+ return PIPE_COLORS[key];
1754
+ }
1755
+ function* allLaneObjects(model) {
1756
+ const walk = function* (lanes) {
1757
+ for (const lane of lanes) {
1758
+ yield lane;
1759
+ if (lane.children !== void 0) yield* walk(lane.children);
1760
+ }
1761
+ };
1762
+ for (const proc of model.processes) yield* walk(proc.threads);
1763
+ }
1764
+ function laneById(model, id) {
1765
+ for (const lane of allLaneObjects(model)) if (lane.id === id) return lane;
1766
+ return null;
1767
+ }
1768
+ function laneOfEvent(model, event) {
1769
+ for (const lane of allLaneObjects(model)) {
1770
+ if (lane.events.includes(event)) return lane;
1771
+ }
1772
+ return null;
1773
+ }
1774
+ function tooltipPos(event, state, lanes, gutterWidth, viewWidth) {
1775
+ const span = Math.max(1, state.endTime - state.startTime);
1776
+ const width = Math.max(50, viewWidth - gutterWidth);
1777
+ const x = (event.startTime - state.startTime) / span * width;
1778
+ const idx = lanes.findIndex((l) => l.lane.events.includes(event));
1779
+ const y = idx < 0 ? 0 : LANE_HEAD_H + idx * LANE_ROW_H;
1780
+ return { left: `${String(Math.max(0, Math.min(width - 150, x + 8)))}px`, top: `${String(y + 4)}px` };
1781
+ }
1782
+
1783
+ // src/client/detail-dock.tsx
1784
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1785
+ function DetailDock(props) {
1786
+ const { locale, mode, onMode, expanded, onExpanded, selected, neighbors, dependencyMode, onDependencyMode } = props;
1787
+ if (mode === null && selected === null) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, {});
1788
+ const tabs = [
1789
+ { id: "summary", label: locale("modeSummary") },
1790
+ { id: "pipe", label: locale("modePipe") },
1791
+ { id: "compute", label: locale("modeCompute") },
1792
+ { id: "memory", label: locale("modeMemory") }
1793
+ ];
1794
+ const active = mode ?? "summary";
1795
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("footer", { className: `otwb-dock${expanded ? " otwb-dock--expanded" : ""}`, "data-testid": "detail-panel", style: { ["--otwb-dock-h"]: expanded ? "407px" : "247px" }, children: [
1796
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1797
+ "button",
1798
+ {
1799
+ type: "button",
1800
+ className: `otwb-dock-expander${expanded ? " otwb-dock-expander--expanded" : ""}`,
1801
+ "data-testid": "detail-panel-expander",
1802
+ "aria-label": expanded ? locale("collapseDock") : locale("expandDock"),
1803
+ "aria-expanded": expanded,
1804
+ title: expanded ? locale("collapseDock") : locale("expandDock"),
1805
+ onClick: () => onExpanded(!expanded),
1806
+ children: [
1807
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "otwb-dock-expander-bar" }),
1808
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "otwb-dock-expander-arrow", children: expanded ? "\u25BE" : "\u25B4" })
1809
+ ]
1810
+ }
1811
+ ),
1812
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "otwb-dock-head", children: [
1813
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("nav", { className: "otwb-dock-tabs", role: "tablist", "aria-label": locale("details"), children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1814
+ "button",
1815
+ {
1816
+ type: "button",
1817
+ role: "tab",
1818
+ className: `otwb-dock-tab${active === tab.id ? " otwb-dock-tab--active" : ""}`,
1819
+ "aria-selected": active === tab.id,
1820
+ "data-testid": `dock-mode-${tab.id}`,
1821
+ onClick: () => onMode(tab.id),
1822
+ children: tab.label
1823
+ },
1824
+ tab.id
1825
+ )) }),
1826
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "otwb-dock-close", "data-testid": "detail-panel-close", "aria-label": locale("closePanel"), title: locale("closePanel"), onClick: () => onMode(null), children: "\xD7" })
1827
+ ] }),
1828
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-dock-body", children: [
1829
+ active === "summary" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DockSummary, { ...props }),
1830
+ active === "pipe" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DockPipe, { ...props }),
1831
+ active === "compute" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DockCompute, { ...props }),
1832
+ active === "memory" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DockMemory, { ...props }),
1833
+ selected !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-dock-event", children: [
1834
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DetailSummary, { event: selected, locale, timeDisplayMode: props.timeDisplayMode, clockFreqMHz: props.clockFreqMHz }),
1835
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DetailParameter, { event: selected, locale }),
1836
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1837
+ DetailRelevant,
1838
+ {
1839
+ selected,
1840
+ neighbors,
1841
+ dependencyMode,
1842
+ onDependencyMode,
1843
+ locale,
1844
+ eventNames: neighbors?.eventNames ?? /* @__PURE__ */ new Map()
1845
+ }
1846
+ )
1847
+ ] })
1848
+ ] })
1849
+ ] });
1850
+ }
1851
+ function DockSummary(props) {
1852
+ const { report, locale, onCannbot, onOpenHardware } = props;
1853
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-dock-scroll", children: [
1854
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SummaryCards, { report, locale, onMoreHardware: onOpenHardware, onCannbot: () => onCannbot("summary") }),
1855
+ report.roofline.points.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RooflinePanel, { model: report.roofline, locale }),
1856
+ props.report.hardware_details.sections.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "otwb-stack", "data-testid": "hardware-details-section", children: [
1857
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "otwb-stack-head", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: locale("hardwareDetails") }) }),
1858
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HardwareDetailsPanel, { sections: props.report.hardware_details.sections, locale })
1859
+ ] })
1860
+ ] });
1861
+ }
1862
+ function DockPipe(props) {
1863
+ const { report, locale, pipeSide, onPipeSide, showPipeSideToggle } = props;
1864
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "otwb-dock-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1865
+ PipeOccupancyChart,
1866
+ {
1867
+ report,
1868
+ locale,
1869
+ side: pipeSide,
1870
+ showSideToggle: showPipeSideToggle,
1871
+ onSide: onPipeSide,
1872
+ onDetails: () => props.onMode("compute"),
1873
+ onCannbot: () => props.onCannbot("compute")
1874
+ }
1875
+ ) });
1876
+ }
1877
+ function DockCompute(props) {
1878
+ const { report, ctx, sessionId, locale } = props;
1879
+ const categories = categoriesFor(report, "compute");
1880
+ const tables = report.table_index.filter((t) => t.group === "compute");
1881
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-dock-scroll", children: [
1882
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-dock-modehead", children: [
1883
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: locale("computeAnalysis") }),
1884
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CannbotButton, { locale, onClick: () => props.onCannbot("compute"), testid: "cannbot-compute" })
1885
+ ] }),
1886
+ categories.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SummaryCategoryList, { categories, locale }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CsvFieldListPanel, { ctx, sessionId, tables, locale, group: "compute", selectedBlockId: null, onSelectBlock: () => {
1887
+ }, showViewAll: false, showBlockSwitcher: false })
1888
+ ] });
1889
+ }
1890
+ function DockMemory(props) {
1891
+ const { report, ctx, sessionId, locale, selectedBlockId, onSelectBlock } = props;
1892
+ const categories = categoriesFor(report, "memory");
1893
+ const tables = report.table_index.filter((t) => t.group === "memory");
1894
+ const hasTopology = report.memory_topology.edges.some((e) => e.label !== null && e.label !== "");
1895
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-dock-scroll", children: [
1896
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-dock-modehead", children: [
1897
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: locale("memoryAnalysis") }),
1898
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CannbotButton, { locale, onClick: () => props.onCannbot("memory"), testid: "cannbot-memory" })
1899
+ ] }),
1900
+ hasTopology && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "otwb-topo-wrap", "data-testid": "stats-topology", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MemoryTopologyPanel, { edges: report.memory_topology.edges, locale }) }),
1901
+ categories.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SummaryCategoryList, { categories, locale }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1902
+ CsvFieldListPanel,
1903
+ {
1904
+ ctx,
1905
+ sessionId,
1906
+ tables,
1907
+ locale,
1908
+ group: "memory",
1909
+ selectedBlockId,
1910
+ onSelectBlock
1911
+ }
1912
+ )
1913
+ ] });
1914
+ }
1915
+ function fmtNs(ns, mode, mhz) {
1916
+ if (mode === "cycles" && mhz !== null && mhz > 0) return nsToCycles(ns, mhz);
1917
+ return formatNs(ns);
1918
+ }
1919
+ function DetailSummary(props) {
1920
+ const { event, locale, timeDisplayMode, clockFreqMHz } = props;
1921
+ const kind = (0, import_react4.useMemo)(() => {
1922
+ const args = event.args ?? {};
1923
+ for (const key of ["op_type", "kernel_type", "kernel_name", "type", "cat"]) {
1924
+ const v = args[key];
1925
+ if (typeof v === "string" && v !== "") return v;
1926
+ }
1927
+ return null;
1928
+ }, [event]);
1929
+ const metrics = [
1930
+ { key: "start", value: fmtNs(event.startTime, timeDisplayMode, clockFreqMHz), label: locale("start"), title: formatNs(event.startTime) },
1931
+ { key: "dur", value: fmtNs(event.duration, timeDisplayMode, clockFreqMHz), label: locale("dur"), title: formatNs(event.duration) },
1932
+ { key: "end", value: fmtNs(event.endTime, timeDisplayMode, clockFreqMHz), label: locale("end"), title: formatNs(event.endTime) }
1933
+ ];
1934
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "otwb-dsum", "data-testid": "detail-summary", children: [
1935
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-dsum-head", children: [
1936
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "otwb-dsum-name", title: event.name, children: event.name }),
1937
+ kind !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "otwb-dsum-kind", "data-testid": "detail-summary-kind", title: kind, children: kind })
1938
+ ] }),
1939
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dl", { className: "otwb-dsum-metrics", children: metrics.map((m) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-dsum-metric", children: [
1940
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { title: m.title, children: m.value }),
1941
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { children: m.label })
1942
+ ] }, m.key)) })
1943
+ ] });
1944
+ }
1945
+ function DetailParameter(props) {
1946
+ const { event, locale } = props;
1947
+ const rows = (0, import_react4.useMemo)(() => {
1948
+ const skip = /* @__PURE__ */ new Set(["event_id", "dependencies", "cat"]);
1949
+ const out = [];
1950
+ for (const [key, raw] of Object.entries(event.args ?? {})) {
1951
+ if (skip.has(key)) continue;
1952
+ const values = (Array.isArray(raw) ? raw.map((v) => String(v)) : raw === null || raw === void 0 ? [] : typeof raw === "object" ? [JSON.stringify(raw)] : [String(raw)]).filter((v) => v !== "");
1953
+ if (values.length > 0) out.push({ key, values });
1954
+ }
1955
+ return out;
1956
+ }, [event]);
1957
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "otwb-dparam", "data-testid": "detail-parameter", "aria-label": locale("parameter"), children: [
1958
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "otwb-dparam-title", children: locale("parameter") }),
1959
+ rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "otwb-dparam-empty", "data-testid": "detail-parameter-empty", children: locale("noParameters") }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dl", { className: "otwb-dparam-rows", children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-dparam-row", children: [
1960
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { children: row.key }),
1961
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { children: row.values.map((v, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "otwb-dparam-value", title: v, children: v }, i)) })
1962
+ ] }, row.key)) })
1963
+ ] });
1964
+ }
1965
+ function DetailRelevant(props) {
1966
+ const { neighbors, dependencyMode, onDependencyMode, locale } = props;
1967
+ if (neighbors === null) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, {});
1968
+ const modeButtons = [
1969
+ { id: "predecessors", label: locale("depsUpstream"), testid: "detail-relevant-direction-up" },
1970
+ { id: "all", label: locale("depsBoth"), testid: "detail-relevant-direction-both" },
1971
+ { id: "successors", label: locale("depsDownstream"), testid: "detail-relevant-direction-down" }
1972
+ ];
1973
+ const showIncoming = dependencyMode !== "successors";
1974
+ const showOutgoing = dependencyMode !== "predecessors";
1975
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "otwb-drel", "data-testid": "detail-relevant", "aria-label": locale("relevant"), children: [
1976
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-drel-toolbar", children: [
1977
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "otwb-drel-title", children: locale("relevant") }),
1978
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "otwb-drel-modes", role: "group", children: modeButtons.map((b) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1979
+ "button",
1980
+ {
1981
+ type: "button",
1982
+ "data-testid": b.testid,
1983
+ className: `otwb-drel-mode${dependencyMode === b.id ? " otwb-drel-mode--on" : ""}`,
1984
+ "aria-pressed": dependencyMode === b.id,
1985
+ title: b.label,
1986
+ onClick: () => onDependencyMode(b.id),
1987
+ children: b.id === "predecessors" ? "\u2190" : b.id === "successors" ? "\u2192" : "\u2194"
1988
+ },
1989
+ b.id
1990
+ )) })
1991
+ ] }),
1992
+ neighbors.incoming.length === 0 && neighbors.outgoing.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "otwb-drel-empty", "data-testid": "detail-relevant-empty", children: locale("noDependencies") }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-drel-graph", children: [
1993
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-drel-col", children: [
1994
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-drel-colhead", children: [
1995
+ locale("incoming"),
1996
+ " ",
1997
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "otwb-drel-count", "data-testid": "detail-relevant-incoming-count", children: showIncoming ? String(neighbors.incoming.length) : "0" })
1998
+ ] }),
1999
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { children: showIncoming && neighbors.incoming.map((n, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { className: "otwb-drel-item", title: n.name, children: n.name }, i)) })
2000
+ ] }),
2001
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-drel-col otwb-drel-col--current", children: [
2002
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "otwb-drel-colhead", children: locale("current") }),
2003
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { className: "otwb-drel-item otwb-drel-item--current", children: props.selected.name }) })
2004
+ ] }),
2005
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-drel-col", children: [
2006
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "otwb-drel-colhead", children: [
2007
+ locale("outgoing"),
2008
+ " ",
2009
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "otwb-drel-count", "data-testid": "detail-relevant-outgoing-count", children: showOutgoing ? String(neighbors.outgoing.length) : "0" })
2010
+ ] }),
2011
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { children: showOutgoing && neighbors.outgoing.map((n, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { className: "otwb-drel-item", title: n.name, children: n.name }, i)) })
2012
+ ] })
2013
+ ] })
2014
+ ] });
2015
+ }
2016
+ function clockFreqOf(report) {
2017
+ const c = report.summary.current_freq;
2018
+ if (c !== null && Number.isFinite(c) && c > 0) return c;
2019
+ const r = report.summary.rated_freq;
2020
+ if (r !== null && Number.isFinite(r) && r > 0) return r;
2021
+ return null;
2022
+ }
2023
+
2024
+ // src/client/timeline/loader.ts
2025
+ var import_react5 = require("react");
2026
+ function useSwimlaneModel(ctx, sessionId, relPath, timeUnit, signal) {
2027
+ const [state, setState] = (0, import_react5.useState)({ status: "idle" });
2028
+ (0, import_react5.useEffect)(() => {
2029
+ if (relPath === null) {
2030
+ setState({ status: "idle" });
2031
+ return;
2032
+ }
2033
+ let stopped = false;
2034
+ setState({ status: "loading" });
2035
+ void (async () => {
2036
+ const result = await readTraceText(ctx, sessionId, relPath, signal);
2037
+ if (stopped) return;
2038
+ if (result.status !== "ok") {
2039
+ setState(result.status === "missing" ? { status: "failed", message: "PipeTrace.json \u7F3A\u5931" } : { status: "failed", message: result.message });
2040
+ return;
2041
+ }
2042
+ try {
2043
+ const raw = JSON.parse(result.text);
2044
+ const model = chromeTraceToSwimlane(raw, timeUnit);
2045
+ if (!stopped) setState({ status: "ok", model });
2046
+ } catch (error) {
2047
+ if (!stopped) setState({ status: "failed", message: `PipeTrace \u89E3\u6790\u5931\u8D25\uFF1A${String(error)}` });
2048
+ }
2049
+ })();
2050
+ return () => {
2051
+ stopped = true;
2052
+ };
2053
+ }, [relPath, timeUnit]);
2054
+ return state;
2055
+ }
2056
+
2057
+ // src/client/cannbot-entry.tsx
2058
+ var import_react6 = require("react");
2059
+ var import_jsx_runtime5 = require("react/jsx-runtime");
2060
+ var PAYLOAD_BYTE_CAP = 8 * 1024;
2061
+ function sessionRemoteOf(ctx) {
2062
+ const remote = ctx.get?.("remote");
2063
+ if (remote === void 0 || remote.session === void 0) return void 0;
2064
+ return remote.session;
2065
+ }
2066
+ function jsonSize(text) {
2067
+ return new TextEncoder().encode(text).length;
2068
+ }
2069
+ function buildCannbotPayload(report, scope) {
2070
+ const base = {
2071
+ version: "1.0",
2072
+ scope,
2073
+ report_name: report.label ?? report.summary.op_name ?? report.report_id,
2074
+ report_id: report.report_id,
2075
+ report_path: report.artifact_dir,
2076
+ op_name: report.summary.op_name ?? "",
2077
+ collected_at: report.collected_at
2078
+ };
2079
+ if (scope === "summary") {
2080
+ return {
2081
+ ...base,
2082
+ data: {
2083
+ summary: report.summary,
2084
+ bandwidth_cards: report.bandwidth_cards,
2085
+ compute_card: report.compute_card,
2086
+ roofline: report.roofline,
2087
+ pipe_occupancy: report.pipe_occupancy,
2088
+ memory_topology: report.memory_topology,
2089
+ hardware_details: report.hardware_details
2090
+ }
2091
+ };
2092
+ }
2093
+ if (scope === "compute") {
2094
+ return {
2095
+ ...base,
2096
+ data: {
2097
+ pipe_occupancy: report.pipe_occupancy,
2098
+ compute_tables: report.table_index.filter((t) => t.group === "compute"),
2099
+ verdict_available: report.verdict !== null
2100
+ }
2101
+ };
2102
+ }
2103
+ return {
2104
+ ...base,
2105
+ data: {
2106
+ memory_tables: report.table_index.filter((t) => t.group === "memory"),
2107
+ memory_topology: report.memory_topology,
2108
+ bandwidth_cards: report.bandwidth_cards
2109
+ }
2110
+ };
2111
+ }
2112
+ var BOUNDARY_PROMPT = [
2113
+ "\u3010\u4F60\u80FD\u770B\u5230\u7684\u6570\u636E\u8FB9\u754C\uFF08\u5FC5\u987B\u4E25\u683C\u9075\u5B88\uFF09\u3011",
2114
+ "\u9664\u672C\u6D88\u606F\u5185\u5D4C\u7684 payload JSON \u5916\uFF0C\u4F60\u770B\u4E0D\u5230\u4EFB\u4F55\u62A5\u544A\u6570\u636E\u3001CSV \u539F\u59CB\u6587\u4EF6\u3001",
2115
+ "\u7B97\u5B50\u6E90\u7801\u5168\u6587\u3001\u5176\u4ED6\u62A5\u544A\u6216\u5386\u53F2\u57FA\u7EBF\u3002",
2116
+ "\u8FD9\u662F\u53EA\u8BFB\u5FEB\u7167\uFF0C\u4E0D\u662F\u5B9E\u65F6\u67E5\u8BE2\u63A5\u53E3\uFF0C\u65E0\u6CD5\u5728\u672C\u8F6E\u5BF9\u8BDD\u4E2D\u88AB\u8865\u5145\u6216\u5237\u65B0\u3002",
2117
+ "\u3010\u6CA1\u6709\u5199\u6743\u9650\uFF0C\u4E5F\u6CA1\u6709\u8DE8\u62A5\u544A\u6570\u636E\u8BBF\u95EE\u80FD\u529B\u3011",
2118
+ "\u6D89\u53CA\u300C\u548C\u4E0A\u6B21/\u5386\u53F2/\u57FA\u7EBF\u6BD4\u300D\u8FD9\u7C7B\u8DE8\u62A5\u544A\u5BF9\u6BD4\u3001\u6216 payload \u672A\u8986\u76D6\u7684\u7EC6\u7C92\u5EA6\u6570\u636E\u65F6\uFF0C",
2119
+ "\u660E\u786E\u8BF4\u660E\u8D85\u51FA\u672C\u5FEB\u7167\u8303\u56F4\uFF0C\u9700\u8981\u7ECF\u5DE5\u5177\u901A\u9053\uFF08ops_collect / ops_import / verdict_judge\uFF09\u83B7\u53D6\u3002",
2120
+ "\u4E0D\u8981\u731C\u6D4B\uFF0C\u4E0D\u8981\u7528\u8FD1\u4F3C\u503C\u4EE3\u66FF\uFF0C\u4E0D\u8981\u6697\u793A\u81EA\u5DF1\u80FD\u67E5\u5230\u672A\u63D0\u4F9B\u7684\u6570\u636E\u3002"
2121
+ ].join("\n");
2122
+ var ANALYSIS_INSTRUCTION = [
2123
+ "\u3010\u5206\u6790\u6307\u4EE4\u3011",
2124
+ "1) \u8BF7\u57FA\u4E8E payload \u4E2D\u7684\u8BC1\u636E\u6570\u503C\u505A\u89E3\u91CA\u6027\u5206\u6790\uFF08\u5404 pipe \u5360\u6BD4\u3001\u5E26\u5BBD\u3001\u7B97\u529B\u3001\u8017\u65F6\u7B49\u539F\u59CB\u6307\u6807\u7684\u8BED\u4E49\u89E3\u8BFB\uFF09\u3002",
2125
+ "2) \u4EFB\u4F55 Bound\uFF08\u74F6\u9888\uFF09\u5224\u5B9A\u7ED3\u8BBA\u5FC5\u987B\u4E14\u53EA\u80FD\u6765\u81EA ops-verdict \u89C4\u5219\u5F15\u64CE\uFF1A\u82E5\u9700\u8981\u5224\u5B9A\uFF0C\u8BF7\u8C03\u7528 verdict_judge \u5DE5\u5177",
2126
+ "\uFF08\u5BF9\u8BE5\u62A5\u544A\u8FD0\u884C\u89C4\u5219\u5F15\u64CE\uFF09\u5E76\u4EE5\u5224\u5B9A\u4E66\uFF08verdict \u5B57\u6BB5\uFF09\u4E3A\u51C6\u8FDB\u884C\u89E3\u8BFB\uFF1B\u4F60\u6CA1\u6709\u5728\u672C\u6D88\u606F\u4E2D\u5F97\u5230\u4EFB\u4F55\u5224\u5B9A\u7ED3\u8BBA\uFF0C",
2127
+ "\u4E5F\u4E0D\u8981\u81EA\u884C\u7ED9\u51FA\u3002",
2128
+ "3) \u5360\u6BD4\u6392\u5E8F\u7C7B\u805A\u5408\uFF08\u5982\u300C\u4E3B\u5BFC pipe\u300D\uFF09\u5C5E\u4E8E\u5224\u5B9A\u4EA7\u7269\uFF1A\u9664\u975E\u5224\u5B9A\u4E66\u660E\u786E\u7ED9\u51FA\uFF0C\u5426\u5219\u4E0D\u8981\u4EE5\u6392\u5E8F/\u6BD4\u8F83\u8868\u8FF0\u7ED9\u51FA\u74F6\u9888\u7ED3\u8BBA\u3002",
2129
+ "4) \u5982\u9700\u4F18\u5316\u673A\u5236\u8DEF\u7531\uFF0C\u8BF7\u4F9D\u636E\u5224\u5B9A\u4E66 findings[].mechanism_refs \u8D70 tool-ops-tune \u7684 ops-mechanism \u5B50\u57DF\u3002"
2130
+ ].join("\n");
2131
+ function buildCannbotText(report, scope) {
2132
+ const payloadJson = JSON.stringify(buildCannbotPayload(report, scope), null, 1);
2133
+ const fullText = `${BOUNDARY_PROMPT}
2134
+
2135
+ \u3010\u62A5\u544A\u6570\u636E payload\u3011
2136
+ ${payloadJson}
2137
+
2138
+ ${ANALYSIS_INSTRUCTION}`;
2139
+ const size = jsonSize(fullText);
2140
+ if (size <= PAYLOAD_BYTE_CAP) return { text: fullText, bytes: size, truncated: false };
2141
+ const encoder = new TextEncoder();
2142
+ const decoder = new TextDecoder();
2143
+ const bytes = encoder.encode(fullText).subarray(0, PAYLOAD_BYTE_CAP);
2144
+ const cut = decoder.decode(bytes, { stream: true });
2145
+ return { text: `${cut}
2146
+ \u2026\uFF08\u8F7D\u8377\u8D85\u8FC7 8KB \u5DF2\u622A\u65AD\uFF09`, bytes: size, truncated: true };
2147
+ }
2148
+ function CannbotConfirm(props) {
2149
+ const { report, scope, locale, sessionId, clientContext, onCancel, onSent, onError } = props;
2150
+ const [sending, setSending] = (0, import_react6.useState)(false);
2151
+ const built = buildCannbotText(report, scope);
2152
+ const confirm = () => {
2153
+ const remote = sessionRemoteOf(clientContext);
2154
+ if (remote === void 0) {
2155
+ onError("remote.session unavailable (inject requires remote + remote.session)");
2156
+ return;
2157
+ }
2158
+ setSending(true);
2159
+ const requestId = randomUUID();
2160
+ void remote.prompt({
2161
+ requestId,
2162
+ sessionId,
2163
+ mode: "queue",
2164
+ content: [{ type: "text", text: built.text }]
2165
+ }).then((result) => {
2166
+ setSending(false);
2167
+ if (!result.ok) {
2168
+ onError(`${locale("cannbotFailed")}: ${result.error.code} \u2014 ${result.error.message}`);
2169
+ return;
2170
+ }
2171
+ onSent(locale("cannbotSent"));
2172
+ }, (error) => {
2173
+ setSending(false);
2174
+ onError(`${locale("cannbotFailed")}: ${String(error)}`);
2175
+ });
2176
+ };
2177
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "otwb-cbmodal-backdrop", role: "presentation", onClick: (e) => {
2178
+ if (e.target === e.currentTarget) onCancel();
2179
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "otwb-cbmodal", role: "dialog", "aria-modal": "true", "aria-label": locale("cannbotConfirmTitle"), "data-testid": "cannbot-confirm", children: [
2180
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "otwb-cbmodal-head", children: [
2181
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "otwb-cbmodal-title", children: locale("cannbotConfirmTitle") }),
2182
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "otwb-cbmodal-close", "aria-label": locale("cannbotCancel"), onClick: onCancel, children: "\xD7" })
2183
+ ] }),
2184
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("dl", { className: "otwb-cbmodal-rows", children: [
2185
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "otwb-cbmodal-row", children: [
2186
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { children: locale("cannbotScope") }),
2187
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dd", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "otwb-chip", children: scope }) })
2188
+ ] }),
2189
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "otwb-cbmodal-row", children: [
2190
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { children: locale("cannbotPayload") }),
2191
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("dd", { children: [
2192
+ locale("cannbotBytes", { n: built.bytes }),
2193
+ built.truncated && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "otwb-cbmodal-warn", "data-testid": "cannbot-truncated", children: [
2194
+ " \xB7 ",
2195
+ locale("cannbotTruncated")
2196
+ ] })
2197
+ ] })
2198
+ ] })
2199
+ ] }),
2200
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("pre", { className: "otwb-cbmodal-preview", "data-testid": "cannbot-payload-preview", children: [
2201
+ built.text.slice(0, 2e3),
2202
+ built.text.length > 2e3 ? "\n\u2026" : ""
2203
+ ] }),
2204
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "otwb-cbmodal-note", children: locale("cannbotQueueNote") }),
2205
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "otwb-cbmodal-actions", children: [
2206
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "otwb-btn", onClick: onCancel, children: locale("cannbotCancel") }),
2207
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "otwb-btn otwb-btn--primary", disabled: sending, onClick: confirm, "data-testid": "cannbot-confirm-send", children: sending ? "\u2026" : locale("cannbotConfirm") })
2208
+ ] })
2209
+ ] }) });
2210
+ }
2211
+
2212
+ // src/client/verdict-cards.tsx
2213
+ var import_react7 = require("react");
2214
+ var import_jsx_runtime6 = require("react/jsx-runtime");
2215
+ function evidenceToString(evidence) {
2216
+ if (evidence === null || evidence === void 0) return "";
2217
+ if (typeof evidence === "string") return evidence;
2218
+ if (typeof evidence === "number") return String(evidence);
2219
+ try {
2220
+ return JSON.stringify(evidence);
2221
+ } catch {
2222
+ return String(evidence);
2223
+ }
2224
+ }
2225
+ function speedupToString(speedup) {
2226
+ if (speedup === null || speedup === void 0 || speedup === "") return "";
2227
+ if (Array.isArray(speedup)) {
2228
+ if (speedup.length === 2) return `${String(speedup[0])}\xD7 \u2013 ${String(speedup[1])}\xD7`;
2229
+ return speedup.map((s) => String(s)).join(" \u2013 ");
2230
+ }
2231
+ if (typeof speedup === "number") return `${String(speedup)}\xD7`;
2232
+ return String(speedup);
2233
+ }
2234
+ function useFullVerdict(ctx, sessionId, relPath) {
2235
+ const [state, setState] = (0, import_react7.useState)({ status: "idle" });
2236
+ (0, import_react7.useEffect)(() => {
2237
+ if (relPath === void 0 || relPath === "") {
2238
+ setState({ status: "idle" });
2239
+ return;
2240
+ }
2241
+ let stopped = false;
2242
+ setState({ status: "loading" });
2243
+ void (async () => {
2244
+ const remote = workspaceFilesOf(ctx);
2245
+ if (remote === void 0) {
2246
+ if (!stopped) setState({ status: "failed", message: "Remote carrier unavailable" });
2247
+ return;
2248
+ }
2249
+ const paged = await readTextPaged(remote, sessionId, relPath, new AbortController().signal);
2250
+ if (stopped) return;
2251
+ if (paged.status !== "ok") {
2252
+ setState({ status: "failed", message: paged.status === "missing" ? `${relPath} \u7F3A\u5931` : paged.message });
2253
+ return;
2254
+ }
2255
+ try {
2256
+ const doc = JSON.parse(paged.text);
2257
+ if (!stopped) setState({ status: "ok", doc });
2258
+ } catch (error) {
2259
+ if (!stopped) setState({ status: "failed", message: `verdict.json \u89E3\u6790\u5931\u8D25\uFF1A${String(error)}` });
2260
+ }
2261
+ })();
2262
+ return () => {
2263
+ stopped = true;
2264
+ };
2265
+ }, [relPath]);
2266
+ return state;
2267
+ }
2268
+ function VerdictCard(props) {
2269
+ const { ctx, sessionId, verdict, locale, onRequestVerdict } = props;
2270
+ const full = useFullVerdict(ctx, sessionId, verdict?.rel_path);
2271
+ if (verdict === null) {
2272
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "otwb-verdict otwb-verdict--empty", "data-testid": "verdict-card", children: [
2273
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "otwb-verdict-head", children: [
2274
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "otwb-verdict-stamp otwb-verdict-stamp--empty", children: locale("verdictEmpty") }),
2275
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "otwb-verdict-title", children: locale("verdictTitle") })
2276
+ ] }),
2277
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "otwb-verdict-hint", children: locale("verdictEmptyHint") }),
2278
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "otwb-btn otwb-btn--ghost", onClick: onRequestVerdict, "data-testid": "verdict-cta", children: locale("verdictCta") })
2279
+ ] });
2280
+ }
2281
+ const detailFindings = full.status === "ok" ? full.doc.findings : void 0;
2282
+ const summaryFindings = verdict.findings ?? [];
2283
+ const skipped = (full.status === "ok" ? full.doc.skipped : void 0) ?? [];
2284
+ const skippedCount = verdict.skipped_count ?? skipped.length;
2285
+ const recommended = (full.status === "ok" ? full.doc.recommended_next : void 0) ?? verdict.recommended_next ?? [];
2286
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "otwb-verdict", "data-testid": "verdict-card", children: [
2287
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "otwb-verdict-head", children: [
2288
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: `otwb-verdict-stamp otwb-verdict-stamp--${String(verdict.verdict ?? "").toLowerCase()}`, "data-testid": "verdict-stamp", children: String(verdict.verdict ?? "") }),
2289
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "otwb-verdict-title", "data-testid": "verdict-headline", children: String(verdict.headline ?? "") })
2290
+ ] }),
2291
+ summaryFindings.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "otwb-verdict-findings", "data-testid": "verdict-findings", children: [
2292
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h4", { className: "otwb-verdict-sub", children: locale("verdictFindings") }),
2293
+ summaryFindings.map((finding, i) => {
2294
+ const detail = detailFindings?.[i];
2295
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("article", { className: "otwb-verdict-finding", "data-testid": "verdict-finding", children: [
2296
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("header", { className: "otwb-verdict-finding-head", children: [
2297
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: `otwb-verdict-sev otwb-verdict-sev--${String(finding.severity ?? "").toLowerCase()}`, children: String(finding.severity ?? "") }),
2298
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { className: "otwb-verdict-rule", children: String(finding.rule_id) }),
2299
+ finding.name !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "otwb-verdict-name", children: String(finding.name) }),
2300
+ finding.confidence !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "otwb-verdict-conf", children: String(finding.confidence) })
2301
+ ] }),
2302
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dl", { className: "otwb-verdict-rows", children: [
2303
+ detail?.evidence !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "otwb-verdict-row", "data-testid": "verdict-evidence", children: [
2304
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: locale("verdictEvidence") }),
2305
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { children: evidenceToString(detail.evidence) })
2306
+ ] }),
2307
+ (detail?.counter_evidence ?? []).map((ce) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "otwb-verdict-row otwb-verdict-row--counter", "data-testid": "verdict-counter", children: [
2308
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: locale("verdictCounter") }),
2309
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dd", { children: [
2310
+ ce.note,
2311
+ ce.unevaluated === true ? "\uFF08\u53CD\u8BC1\u6307\u6807\u7F3A\u5931\uFF0C\u672A\u8BC4\u4F30\uFF09" : ""
2312
+ ] })
2313
+ ] }, ce.id)),
2314
+ detail !== void 0 && speedupToString(detail.speedup_range) !== "" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "otwb-verdict-row", children: [
2315
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: locale("verdictSpeedup") }),
2316
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { children: speedupToString(detail.speedup_range) })
2317
+ ] }),
2318
+ (detail?.mechanism_refs ?? []).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "otwb-verdict-row", "data-testid": "verdict-mechanisms", children: [
2319
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: locale("verdictMechanism") }),
2320
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { children: (detail?.mechanism_refs ?? []).map((m) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { className: "otwb-verdict-mech", children: m }, m)) })
2321
+ ] })
2322
+ ] })
2323
+ ] }, `${finding.rule_id}-${String(i)}`);
2324
+ })
2325
+ ] }),
2326
+ full.status === "failed" && verdict.rel_path !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "otwb-verdict-hint", "data-testid": "verdict-full-failed", children: [
2327
+ "\u5224\u5B9A\u4E66\u5168\u6587\u62C9\u53D6\u5931\u8D25\uFF1A",
2328
+ full.message
2329
+ ] }),
2330
+ skippedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("details", { className: "otwb-verdict-skipped", "data-testid": "verdict-skipped", children: [
2331
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("summary", { children: [
2332
+ locale("verdictSkipped"),
2333
+ " \xB7 ",
2334
+ String(skippedCount)
2335
+ ] }),
2336
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { children: skipped.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("li", { children: [
2337
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { children: String(s.rule_id) }),
2338
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "otwb-verdict-skipstatus", children: String(s.status ?? "") }),
2339
+ s.reason !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "otwb-verdict-skipreason", children: s.reason })
2340
+ ] }, `${s.rule_id}-${String(i)}`)) })
2341
+ ] }),
2342
+ recommended.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "otwb-verdict-next", "data-testid": "verdict-recommended", children: [
2343
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h4", { className: "otwb-verdict-sub", children: locale("verdictNext") }),
2344
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ol", { children: recommended.map((n, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { children: String(n) }, i)) })
2345
+ ] }),
2346
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("footer", { className: "otwb-verdict-footer", "data-testid": "verdict-footer", children: locale("verdictFooter") })
2347
+ ] });
2348
+ }
2349
+ function ComparisonCard(props) {
2350
+ const { comparison, locale } = props;
2351
+ if (comparison === null) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, {});
2352
+ const ok = comparison.exit_code === 0;
2353
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: `otwb-compare${ok ? "" : " otwb-compare--rejected"}`, "data-testid": "comparison-card", children: [
2354
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "otwb-compare-head", children: [
2355
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: `otwb-compare-stamp${ok ? " otwb-compare-stamp--ok" : " otwb-compare-stamp--rejected"}`, "data-testid": "comparison-exit", children: ok ? locale("comparisonExitOk") : locale("comparisonExitRejected") }),
2356
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "otwb-compare-title", children: locale("comparisonTitle") }),
2357
+ comparison.speedup !== null && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "otwb-compare-speedup", "data-testid": "comparison-speedup", children: [
2358
+ locale("comparisonSpeedup"),
2359
+ " ",
2360
+ comparison.speedup.toFixed(3),
2361
+ "\xD7"
2362
+ ] })
2363
+ ] }),
2364
+ comparison.conclusion_text !== "" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "otwb-compare-line", "data-testid": "comparison-conclusion", children: [
2365
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("b", { children: [
2366
+ locale("comparisonConclusion"),
2367
+ ":"
2368
+ ] }),
2369
+ " ",
2370
+ comparison.conclusion_text
2371
+ ] }),
2372
+ comparison.action_text !== "" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "otwb-compare-line", "data-testid": "comparison-action", children: [
2373
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("b", { children: [
2374
+ locale("comparisonAction"),
2375
+ ":"
2376
+ ] }),
2377
+ " ",
2378
+ comparison.action_text
2379
+ ] }),
2380
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("footer", { className: "otwb-verdict-footer", children: [
2381
+ "\u7ED3\u8BBA\u51FA\u81EA snapshot / delta_compare \u811A\u672C \xB7 \u9000\u51FA\u7801 ",
2382
+ String(comparison.exit_code)
2383
+ ] })
2384
+ ] });
2385
+ }
2386
+
2387
+ // src/client/report-view.tsx
2388
+ var import_jsx_runtime7 = require("react/jsx-runtime");
2389
+ function ReportView(props) {
2390
+ const { ctx, sessionId, report, result, locale, onBack, promptSession: promptSession2 } = props;
2391
+ const [tab, setTab] = (0, import_react8.useState)("timeline");
2392
+ const [operatorId, setOperatorId] = (0, import_react8.useState)(report.selected_operator_id);
2393
+ const [asideVisible, setAsideVisible] = (0, import_react8.useState)(true);
2394
+ const [asideWidth, setAsideWidth] = (0, import_react8.useState)(360);
2395
+ const [dockMode, setDockMode] = (0, import_react8.useState)(null);
2396
+ const [dockExpanded, setDockExpanded] = (0, import_react8.useState)(false);
2397
+ const [selectedEvent, setSelectedEvent] = (0, import_react8.useState)(null);
2398
+ const [dependencyMode, setDependencyMode] = (0, import_react8.useState)("all");
2399
+ const [selectedBlockId, setSelectedBlockId] = (0, import_react8.useState)(report.memory_topology.block_id);
2400
+ const [pipeSide, setPipeSide] = (0, import_react8.useState)(defaultPipeSide(report));
2401
+ const [cannbotScope, setCannbotScope] = (0, import_react8.useState)(null);
2402
+ const [toast, setToast] = (0, import_react8.useState)(null);
2403
+ const [shortcutsOpen, setShortcutsOpen] = (0, import_react8.useState)(false);
2404
+ const [displayOpen, setDisplayOpen] = (0, import_react8.useState)(false);
2405
+ const asideDrag = (0, import_react8.useRef)(null);
2406
+ const opType = (report.summary.op_type ?? "").trim();
2407
+ const isMix = opType.toUpperCase() === "MIX";
2408
+ const showPipeSideToggle = isMix;
2409
+ const clockMHz = clockFreqOf(report);
2410
+ const capabilities = new Set(report.capabilities);
2411
+ const hasTimeline = report.pipe_trace.event_count > 0 && report.pipe_trace.rel_path !== null;
2412
+ const [tl, setTl] = (0, import_react8.useState)({
2413
+ startTime: 0,
2414
+ endTime: 1,
2415
+ zoomPercent: 0,
2416
+ selectedEventId: null,
2417
+ hoveredEventId: null,
2418
+ searchQuery: "",
2419
+ measureMode: false,
2420
+ measureRange: null,
2421
+ showDependencies: true,
2422
+ timeDisplayMode: "time",
2423
+ dependencyDepth: -1,
2424
+ expanded: false
2425
+ });
2426
+ const patchTl = (0, import_react8.useCallback)((patch) => {
2427
+ setTl((prev) => ({ ...prev, ...patch }));
2428
+ }, []);
2429
+ const trace = useSwimlaneModel(
2430
+ ctx,
2431
+ sessionId,
2432
+ hasTimeline ? report.pipe_trace.rel_path : null,
2433
+ report.pipe_trace.time_unit,
2434
+ new AbortController().signal
2435
+ );
2436
+ const model = trace.status === "ok" ? trace.model : null;
2437
+ (0, import_react8.useEffect)(() => {
2438
+ if (model !== null) patchTl({ startTime: 0, endTime: model.maxTime, zoomPercent: 0 });
2439
+ }, [model, patchTl]);
2440
+ const onSelectEvent = (0, import_react8.useCallback)((event) => {
2441
+ setSelectedEvent(event);
2442
+ patchTl({ selectedEventId: event?.id ?? null });
2443
+ }, [patchTl]);
2444
+ const neighbors = (0, import_react8.useMemo)(() => {
2445
+ if (model === null || selectedEvent === null) return null;
2446
+ const index = /* @__PURE__ */ new Map();
2447
+ for (const lane of allLanesOf(model)) for (const ev of lane.events) index.set(ev.id, ev);
2448
+ const collect = (refs) => refs.slice(0, 200).flatMap((ref) => {
2449
+ const lane = laneByIdInModel(model, ref.tid);
2450
+ const ev = lane?.events[ref.index];
2451
+ if (ev === void 0) return [];
2452
+ return [{ name: ev.name, ref }];
2453
+ });
2454
+ const deps = selectedEvent.dependencies;
2455
+ if (deps === void 0) return { incoming: [], outgoing: [], eventNames: /* @__PURE__ */ new Map() };
2456
+ return {
2457
+ incoming: collect(deps.predecessors),
2458
+ outgoing: collect(deps.successors),
2459
+ eventNames: /* @__PURE__ */ new Map()
2460
+ };
2461
+ }, [model, selectedEvent]);
2462
+ const metaLine = (0, import_react8.useMemo)(() => {
2463
+ const s = report.summary;
2464
+ const parts = [];
2465
+ if (s.pid !== null) parts.push(`${locale("process")}: ${s.pid}`);
2466
+ if (s.op_type !== null) parts.push(`${locale("opTypeLabel")}: ${s.op_type}`);
2467
+ if (s.block_dim !== null) parts.push(`${locale("blocks")}: ${String(s.block_dim)}`);
2468
+ return parts;
2469
+ }, [report, locale]);
2470
+ const fullSpan = model !== null ? Math.max(1, model.maxTime) : 1;
2471
+ const startCannbot = (scope) => setCannbotScope(scope);
2472
+ const sendGuidancePrompt = (text) => {
2473
+ void promptSession2(text).then((err) => {
2474
+ if (err !== null) setToast(`\u6CE8\u5165\u5931\u8D25\uFF1A${err}`);
2475
+ else setToast("\u5DF2\u6CE8\u5165\u4F1A\u8BDD\uFF08queue \u6A21\u5F0F\uFF09");
2476
+ });
2477
+ };
2478
+ const onAsidePointerDown = (ev) => {
2479
+ if (ev.button !== 0) return;
2480
+ asideDrag.current = { x: ev.clientX, w: asideWidth };
2481
+ ev.currentTarget.setPointerCapture(ev.pointerId);
2482
+ };
2483
+ const onAsidePointerMove = (ev) => {
2484
+ const drag = asideDrag.current;
2485
+ if (drag === null) return;
2486
+ setAsideWidth(Math.min(720, Math.max(280, drag.w + (drag.x - ev.clientX))));
2487
+ };
2488
+ const onAsidePointerUp = () => {
2489
+ asideDrag.current = null;
2490
+ };
2491
+ const operators = report.operators;
2492
+ const opSelected = operatorId ?? report.selected_operator_id ?? operators[0]?.id ?? null;
2493
+ const tabs = [
2494
+ { id: "op", label: locale("tabOp"), testid: "tab-op" },
2495
+ { id: "timeline", label: locale("tabTimeline"), testid: "tab-timeline" },
2496
+ { id: "source", label: locale("tabSource"), testid: "tab-source" },
2497
+ { id: "detail", label: locale("tabDetail"), testid: "tab-detail" },
2498
+ { id: "cache", label: locale("tabCache"), testid: "tab-cache" }
2499
+ ];
2500
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-report", "data-testid": "profiling-report", children: [
2501
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-report-topbar", children: [
2502
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("button", { type: "button", className: "otwb-back", onClick: onBack, "data-testid": "back-to-list", children: [
2503
+ "\u2190 ",
2504
+ locale("navResults")
2505
+ ] }),
2506
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "otwb-report-src", "data-testid": "report-source-badge", children: sourceBadge(report) }),
2507
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "otwb-report-refresh", children: locale("refreshNote") })
2508
+ ] }),
2509
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("nav", { className: "otwb-tabs", "data-testid": "report-tabs", "aria-label": locale("reportViews"), children: [
2510
+ operators.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-opselect", children: [
2511
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("button", { type: "button", className: "otwb-opselect-trigger", "data-testid": "op-selector", "aria-haspopup": "listbox", "aria-expanded": false, children: [
2512
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: operators.find((o) => o.id === opSelected)?.label ?? locale("tabOp") }),
2513
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "otwb-opselect-chevron", children: "\u25BE" })
2514
+ ] }),
2515
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2516
+ "select",
2517
+ {
2518
+ className: "otwb-opselect-native",
2519
+ value: opSelected ?? "",
2520
+ onChange: (e) => setOperatorId(e.target.value),
2521
+ "aria-label": locale("tabOp"),
2522
+ children: operators.map((op) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: op.id, children: op.label }, op.id))
2523
+ }
2524
+ )
2525
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "otwb-tabs-title", children: report.summary.op_name ?? locale("tabOp") }),
2526
+ tabs.filter((t) => t.id !== "op").map((t) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2527
+ "button",
2528
+ {
2529
+ type: "button",
2530
+ className: `otwb-tabs-tab${tab === t.id ? " otwb-tabs-tab--active" : ""}`,
2531
+ "data-testid": t.testid,
2532
+ "aria-selected": tab === t.id,
2533
+ title: t.id === "source" || t.id === "detail" || t.id === "cache" ? locale("phase2") : void 0,
2534
+ onClick: () => {
2535
+ if (t.id === "detail") {
2536
+ setDockMode(dockMode === null ? "summary" : dockMode);
2537
+ setTab("timeline");
2538
+ return;
2539
+ }
2540
+ setTab(t.id);
2541
+ },
2542
+ children: t.label
2543
+ },
2544
+ t.id
2545
+ ))
2546
+ ] }),
2547
+ tab === "timeline" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-toolbar", "data-testid": "report-toolbar", children: [
2548
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "otwb-toolbar-search", children: [
2549
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "otwb-sr-only", children: locale("searchLabel") }),
2550
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2551
+ "input",
2552
+ {
2553
+ type: "search",
2554
+ "data-testid": "search-input",
2555
+ placeholder: locale("searchPlaceholder"),
2556
+ value: tl.searchQuery,
2557
+ onChange: (e) => patchTl({ searchQuery: e.target.value })
2558
+ }
2559
+ ),
2560
+ tl.searchQuery !== "" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "data-testid": "search-clear", "aria-label": locale("searchClear"), onClick: () => patchTl({ searchQuery: "" }), children: "\xD7" })
2561
+ ] }),
2562
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-toolbar-zoom", children: [
2563
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "data-testid": "zoom-out", title: locale("zoomOut"), onClick: () => patchTl({ startTime: 0, endTime: Math.max(1, (tl.endTime - tl.startTime) * 1.6 + tl.startTime > fullSpan ? fullSpan : (tl.endTime - tl.startTime) * 1.6 + tl.startTime), zoomPercent: zoomToPercent(Math.min(fullSpan, (tl.endTime - tl.startTime) * 1.6), fullSpan) }), children: "\u2212" }),
2564
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2565
+ "input",
2566
+ {
2567
+ type: "range",
2568
+ min: 0,
2569
+ max: 100,
2570
+ "data-testid": "zoom-slider",
2571
+ value: tl.zoomPercent,
2572
+ onChange: (e) => {
2573
+ const span = percentToZoom(Number(e.target.value), fullSpan);
2574
+ const anchor = (tl.startTime + tl.endTime) / 2;
2575
+ let start = anchor - span / 2;
2576
+ let end = anchor + span / 2;
2577
+ if (start < 0) {
2578
+ start = 0;
2579
+ end = span;
2580
+ }
2581
+ if (end > fullSpan) {
2582
+ end = fullSpan;
2583
+ start = Math.max(0, end - span);
2584
+ }
2585
+ patchTl({ startTime: Math.round(start), endTime: Math.round(end), zoomPercent: Number(e.target.value) });
2586
+ }
2587
+ }
2588
+ ),
2589
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "data-testid": "zoom-in", title: locale("zoomIn"), onClick: () => {
2590
+ const span = Math.max(1, (tl.endTime - tl.startTime) / 1.6);
2591
+ const anchor = (tl.startTime + tl.endTime) / 2;
2592
+ let start = anchor - span / 2;
2593
+ let end = anchor + span / 2;
2594
+ if (start < 0) {
2595
+ start = 0;
2596
+ end = span;
2597
+ }
2598
+ if (end > fullSpan) {
2599
+ end = fullSpan;
2600
+ start = Math.max(0, end - span);
2601
+ }
2602
+ patchTl({ startTime: Math.round(start), endTime: Math.round(end), zoomPercent: zoomToPercent(span, fullSpan) });
2603
+ }, children: "+" }),
2604
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "data-testid": "zoom-to-fit", title: locale("zoomFit"), onClick: () => patchTl({ startTime: 0, endTime: fullSpan, zoomPercent: 0 }), children: locale("zoomFit") })
2605
+ ] }),
2606
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2607
+ "button",
2608
+ {
2609
+ type: "button",
2610
+ className: `otwb-iconbtn${tl.measureMode ? " otwb-iconbtn--on" : ""}`,
2611
+ "data-testid": "toggle-measure",
2612
+ "aria-pressed": tl.measureMode,
2613
+ title: locale("timeMeasurement"),
2614
+ onClick: () => patchTl({ measureMode: !tl.measureMode, measureRange: null }),
2615
+ children: "\u21D4"
2616
+ }
2617
+ ),
2618
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-toolbar-popwrap", children: [
2619
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2620
+ "button",
2621
+ {
2622
+ type: "button",
2623
+ className: `otwb-iconbtn${displayOpen ? " otwb-iconbtn--on" : ""}`,
2624
+ "data-testid": "toggle-display-control",
2625
+ "aria-expanded": displayOpen,
2626
+ title: locale("displayControl"),
2627
+ onClick: () => setDisplayOpen((o) => !o),
2628
+ children: "\u2699"
2629
+ }
2630
+ ),
2631
+ displayOpen && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-popover", "data-testid": "display-control", role: "dialog", "aria-label": locale("displayControl"), children: [
2632
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-popover-head", children: [
2633
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: locale("displayControl") }),
2634
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "data-testid": "display-control-close", "aria-label": locale("closePanel"), onClick: () => setDisplayOpen(false), children: "\xD7" })
2635
+ ] }),
2636
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "otwb-popover-row", children: [
2637
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: locale("taskDisplayUnit") }),
2638
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2639
+ "select",
2640
+ {
2641
+ "data-testid": "time-display-mode",
2642
+ value: tl.timeDisplayMode,
2643
+ onChange: (e) => patchTl({ timeDisplayMode: e.target.value }),
2644
+ children: [
2645
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "time", children: locale("displayModeTime") }),
2646
+ clockMHz !== null && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "cycles", children: locale("displayModeCycles") })
2647
+ ]
2648
+ }
2649
+ )
2650
+ ] }),
2651
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "otwb-popover-row", children: [
2652
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [
2653
+ locale("connectionLevel"),
2654
+ " ",
2655
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "otwb-help", tabIndex: 0, title: locale("connectionLevelHelp"), "data-testid": "connection-level-help", children: "?" })
2656
+ ] }),
2657
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "otwb-stepper", children: [
2658
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2659
+ "input",
2660
+ {
2661
+ type: "number",
2662
+ step: 1,
2663
+ min: -1,
2664
+ max: 100,
2665
+ "data-testid": "dependency-depth",
2666
+ value: tl.dependencyDepth,
2667
+ onChange: (e) => {
2668
+ const raw = Math.trunc(Number(e.target.value));
2669
+ patchTl({ dependencyDepth: Number.isFinite(raw) ? Math.min(100, Math.max(-1, raw)) : -1 });
2670
+ }
2671
+ }
2672
+ ),
2673
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "data-testid": "dependency-depth-up", title: locale("stepUp"), onClick: () => patchTl({ dependencyDepth: Math.min(100, tl.dependencyDepth + 1) }), children: "\u25B4" }),
2674
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "data-testid": "dependency-depth-down", title: locale("stepDown"), onClick: () => patchTl({ dependencyDepth: Math.max(-1, tl.dependencyDepth - 1) }), children: "\u25BE" })
2675
+ ] })
2676
+ ] }),
2677
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "otwb-popover-hint", children: locale("dependencyDepthHint") }),
2678
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "otwb-popover-row otwb-popover-row--check", children: [
2679
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: locale("showDeps") }),
2680
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: tl.showDependencies, onChange: (e) => patchTl({ showDependencies: e.target.checked }), "data-testid": "show-dependencies" })
2681
+ ] })
2682
+ ] })
2683
+ ] }),
2684
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-toolbar-popwrap", children: [
2685
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2686
+ "button",
2687
+ {
2688
+ type: "button",
2689
+ className: `otwb-iconbtn${shortcutsOpen ? " otwb-iconbtn--on" : ""}`,
2690
+ "data-testid": "toggle-shortcuts",
2691
+ "aria-expanded": shortcutsOpen,
2692
+ title: locale("shortcuts"),
2693
+ onClick: () => setShortcutsOpen((o) => !o),
2694
+ children: "\u2328"
2695
+ }
2696
+ ),
2697
+ shortcutsOpen && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-popover otdb-shortcut-help", "data-testid": "shortcut-help", role: "dialog", "aria-label": locale("shortcuts"), children: [
2698
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-popover-head", children: [
2699
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: locale("shortcuts") }),
2700
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "data-testid": "shortcut-help-close", "aria-label": locale("closePanel"), onClick: () => setShortcutsOpen(false), children: "\xD7" })
2701
+ ] }),
2702
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-shortcut-cols", children: [
2703
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
2704
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "otwb-shortcut-group", children: locale("mouseControl") }),
2705
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-shortcut-row", children: [
2706
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: locale("verticalMovement") }),
2707
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Key, { glyph: "wheel" })
2708
+ ] }),
2709
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-shortcut-row", children: [
2710
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: locale("singleBoxSelection") }),
2711
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Key, { glyph: "click" })
2712
+ ] })
2713
+ ] }),
2714
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
2715
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "otwb-shortcut-group", children: locale("keyboardControl") }),
2716
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-shortcut-row", children: [
2717
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: locale("zoomIn") }),
2718
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Key, { glyph: "W" })
2719
+ ] }),
2720
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-shortcut-row", children: [
2721
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: locale("zoomOut") }),
2722
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Key, { glyph: "S" })
2723
+ ] }),
2724
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-shortcut-row", children: [
2725
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: locale("panLeft") }),
2726
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Key, { glyph: "A" })
2727
+ ] }),
2728
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-shortcut-row", children: [
2729
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: locale("panRight") }),
2730
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Key, { glyph: "D" })
2731
+ ] })
2732
+ ] }),
2733
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
2734
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "otwb-shortcut-group", children: locale("combinedControl") }),
2735
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-shortcut-row", children: [
2736
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: locale("scaling") }),
2737
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "otwb-kbd", children: [
2738
+ locale("ctrl"),
2739
+ " + ",
2740
+ locale("wheelGlyph")
2741
+ ] })
2742
+ ] }),
2743
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-shortcut-row", children: [
2744
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: locale("dragPan") }),
2745
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "otwb-kbd", children: [
2746
+ locale("ctrl"),
2747
+ " + ",
2748
+ locale("mouseGlyph")
2749
+ ] })
2750
+ ] }),
2751
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-shortcut-row", children: [
2752
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: locale("boxSelect") }),
2753
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "otwb-kbd", children: locale("mouseGlyph") })
2754
+ ] }),
2755
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-shortcut-row", children: [
2756
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: locale("timeMeasurement") }),
2757
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "otwb-kbd", children: [
2758
+ locale("alt"),
2759
+ " + ",
2760
+ locale("mouseGlyph")
2761
+ ] })
2762
+ ] })
2763
+ ] })
2764
+ ] })
2765
+ ] })
2766
+ ] }),
2767
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2768
+ "button",
2769
+ {
2770
+ type: "button",
2771
+ className: `otwb-iconbtn${asideVisible ? " otwb-iconbtn--on" : ""}`,
2772
+ "data-testid": "toggle-aside",
2773
+ "aria-pressed": asideVisible,
2774
+ title: locale("stats"),
2775
+ onClick: () => setAsideVisible((v) => !v),
2776
+ children: "\u25A4"
2777
+ }
2778
+ )
2779
+ ] }),
2780
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `otwb-layout${asideVisible ? "" : " otdb-layout--no-aside"}`, style: { ["--otwb-aside-width"]: `${String(asideWidth)}px` }, children: [
2781
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { className: "otwb-main", children: [
2782
+ tab === "timeline" && (hasTimeline ? trace.status === "ok" && model !== null ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2783
+ TimelineView,
2784
+ {
2785
+ model,
2786
+ state: tl,
2787
+ setState: patchTl,
2788
+ clockFreqMHz: clockMHz,
2789
+ locale,
2790
+ onSelect: onSelectEvent,
2791
+ height: 340
2792
+ }
2793
+ ) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "otwb-empty", "data-testid": "timeline-loading", children: trace.status === "loading" ? "\u2026" : trace.status === "failed" ? trace.message : locale("noTimeline") }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-empty", "data-testid": "timeline-empty", children: [
2794
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "otwb-empty-title", children: locale("noTimeline") }),
2795
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "otwb-empty-hint", children: report.pipe_trace.missing_reason ?? "" })
2796
+ ] })),
2797
+ tab === "source" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-empty", "data-testid": "tab-source-body", children: [
2798
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "otwb-empty-title", children: locale("tabSource") }),
2799
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "otwb-empty-hint", children: locale("sourceEmpty") })
2800
+ ] }),
2801
+ tab === "cache" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "otwb-tab-body", "data-testid": "tab-cache-body", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2802
+ CsvFieldListPanel,
2803
+ {
2804
+ ctx,
2805
+ sessionId,
2806
+ tables: report.table_index.filter((t) => t.group === "memory"),
2807
+ locale,
2808
+ group: "memory",
2809
+ selectedBlockId,
2810
+ onSelectBlock: setSelectedBlockId
2811
+ }
2812
+ ) }),
2813
+ tab === "op" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "otwb-tab-body", "data-testid": "tab-op-body", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-opinfo", children: [
2814
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "otwb-empty-hint", children: locale("reportTitle") }),
2815
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { className: "otwb-opinfo-name", children: report.summary.op_name ?? locale("notAvailable") }),
2816
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("dl", { className: "otwb-opinfo-rows", children: [
2817
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
2818
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: locale("type") }),
2819
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dd", { children: report.summary.op_type ?? locale("notAvailable") })
2820
+ ] }),
2821
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
2822
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: locale("kernel") }),
2823
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dd", { children: report.kernel_name ?? locale("notAvailable") })
2824
+ ] }),
2825
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
2826
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: "report_id" }),
2827
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dd", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("code", { children: report.report_id }) })
2828
+ ] }),
2829
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
2830
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: locale("sourceLabel") }),
2831
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dd", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "otwb-chip", children: String(report.source) }) })
2832
+ ] }),
2833
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
2834
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: "SoC" }),
2835
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dd", { children: report.soc ?? locale("notAvailable") })
2836
+ ] }),
2837
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
2838
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: "collected_at" }),
2839
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dd", { children: report.collected_at })
2840
+ ] }),
2841
+ result !== null && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
2842
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: locale("copyPath") }),
2843
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dd", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("code", { title: result.dir_path, children: result.dir_path }) })
2844
+ ] })
2845
+ ] }),
2846
+ operators.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-opinfo-operators", children: [
2847
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h4", { children: locale("opSelectorLabel") }),
2848
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { children: operators.map((op) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("li", { className: op.id === opSelected ? "otwb-opinfo-op--active" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: () => setOperatorId(op.id), "data-testid": "op-item", children: op.label }) }, op.id)) })
2849
+ ] })
2850
+ ] }) })
2851
+ ] }),
2852
+ asideVisible && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-aside", "data-testid": "stats-aside", children: [
2853
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2854
+ "button",
2855
+ {
2856
+ type: "button",
2857
+ className: "otwb-aside-resize",
2858
+ "data-testid": "aside-resize-handle",
2859
+ "aria-label": locale("resizeSidebar"),
2860
+ onPointerDown: onAsidePointerDown,
2861
+ onPointerMove: onAsidePointerMove,
2862
+ onPointerUp: onAsidePointerUp,
2863
+ onPointerCancel: onAsidePointerUp
2864
+ }
2865
+ ),
2866
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("header", { className: "otwb-aside-head", children: [
2867
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { children: locale("summary") }),
2868
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "otwb-aside-close", "aria-label": locale("closePanel"), onClick: () => setAsideVisible(false), children: "\xD7" })
2869
+ ] }),
2870
+ metaLine.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "otwb-aside-meta", "data-testid": "stats-aside-meta", children: metaLine.join(" \xB7 ") }),
2871
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-aside-scroll", children: [
2872
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SummaryCards, { report, locale, onMoreHardware: () => setDockMode("summary"), onCannbot: () => startCannbot("summary") }),
2873
+ capabilities.has("roofline") && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(RooflinePanel, { model: report.roofline, locale }),
2874
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2875
+ PipeOccupancyChart,
2876
+ {
2877
+ report,
2878
+ locale,
2879
+ side: pipeSide,
2880
+ showSideToggle: showPipeSideToggle,
2881
+ onSide: setPipeSide,
2882
+ onDetails: () => setDockMode("pipe"),
2883
+ onCannbot: () => startCannbot("compute")
2884
+ }
2885
+ ),
2886
+ report.memory_topology.edges.some((e) => e.label !== null && e.label !== "") && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { className: "otwb-stack", "data-testid": "stats-topology", children: [
2887
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-stack-head", children: [
2888
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h4", { children: locale("memoryAnalysis") }),
2889
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "otwb-stack-actions", children: [
2890
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CannbotButton, { locale, onClick: () => startCannbot("memory"), testid: "cannbot-memory" }),
2891
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "otwb-linklike", "data-testid": "topology-details", onClick: () => setDockMode("memory"), children: locale("details") })
2892
+ ] })
2893
+ ] }),
2894
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MemoryTopologyPanel, { edges: report.memory_topology.edges, locale })
2895
+ ] }),
2896
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(VerdictCard, { ctx, sessionId, verdict: report.verdict, locale, onRequestVerdict: () => {
2897
+ sendGuidancePrompt(verdictRequestPrompt());
2898
+ } }),
2899
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ComparisonCard, { comparison: report.comparison, locale })
2900
+ ] })
2901
+ ] })
2902
+ ] }),
2903
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2904
+ DetailDock,
2905
+ {
2906
+ ctx,
2907
+ sessionId,
2908
+ report,
2909
+ locale,
2910
+ mode: dockMode,
2911
+ onMode: setDockMode,
2912
+ expanded: dockExpanded,
2913
+ onExpanded: setDockExpanded,
2914
+ selected: selectedEvent,
2915
+ neighbors,
2916
+ dependencyMode,
2917
+ onDependencyMode: setDependencyMode,
2918
+ timeDisplayMode: tl.timeDisplayMode,
2919
+ clockFreqMHz: clockMHz,
2920
+ selectedBlockId,
2921
+ onSelectBlock: setSelectedBlockId,
2922
+ pipeSide,
2923
+ onPipeSide: setPipeSide,
2924
+ showPipeSideToggle,
2925
+ onCannbot: startCannbot,
2926
+ onOpenHardware: () => setDockMode("summary")
2927
+ }
2928
+ ),
2929
+ cannbotScope !== null && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2930
+ CannbotConfirm,
2931
+ {
2932
+ report,
2933
+ scope: cannbotScope,
2934
+ locale,
2935
+ sessionId,
2936
+ clientContext: ctx,
2937
+ onCancel: () => setCannbotScope(null),
2938
+ onSent: (msg) => {
2939
+ setCannbotScope(null);
2940
+ setToast(msg);
2941
+ },
2942
+ onError: (msg) => {
2943
+ setToast(msg);
2944
+ }
2945
+ }
2946
+ ),
2947
+ toast !== null && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "otwb-toast", "data-testid": "otwb-toast", role: "status", onClick: () => setToast(null), children: toast })
2948
+ ] });
2949
+ }
2950
+ function Key(props) {
2951
+ const { glyph } = props;
2952
+ const isKey = /^[WASD]$/.test(glyph);
2953
+ return isKey ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "otwb-kbd", children: glyph }) : glyph === "wheel" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "otwb-glyph", children: "\u27F3" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "otwb-glyph", children: "\u25C9" });
2954
+ }
2955
+ function defaultPipeSide(report) {
2956
+ const t = (report.summary.op_type ?? "").toLowerCase();
2957
+ if (t === "") return "cube";
2958
+ if (t.includes("mix")) return "cube";
2959
+ if (t.includes("vector") || t.includes("aiv") || t.includes("vec")) return "vector";
2960
+ return "cube";
2961
+ }
2962
+ function sourceBadge(report) {
2963
+ return String(report.source);
2964
+ }
2965
+ function* allLanesOf(model) {
2966
+ const walk = function* (lanes) {
2967
+ for (const lane of lanes) {
2968
+ yield lane;
2969
+ if (lane.children !== void 0) yield* walk(lane.children);
2970
+ }
2971
+ };
2972
+ for (const proc of model.processes) yield* walk(proc.threads);
2973
+ }
2974
+ function laneByIdInModel(model, id) {
2975
+ for (const lane of allLanesOf(model)) if (lane.id === id) return lane;
2976
+ return null;
2977
+ }
2978
+ function verdictRequestPrompt() {
2979
+ return "\u8BF7\u5BF9\u7B97\u5B50\u8C03\u4F18\u5DE5\u4F5C\u53F0\u5F53\u524D\u62A5\u544A\u8C03\u7528 verdict_judge \u5DE5\u5177\u8FD0\u884C ops-verdict \u89C4\u5219\u5F15\u64CE\uFF0C\u5E76\u4EE5\u5224\u5B9A\u4E66\u5185\u5BB9\u4E3A\u51C6\u89E3\u8BFB\u74F6\u9888\uFF08\u5224\u5B9A\u53EA\u51FA\u81EA\u811A\u672C\uFF1B\u4F60\u53EA\u89E3\u91CA\u8BC1\u636E\uFF09\u3002";
2980
+ }
2981
+
2982
+ // src/client/collect-config.tsx
2983
+ var import_react9 = require("react");
2984
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2985
+ var OPERATOR_METRIC_KEYS = [
2986
+ "cubeAndVectorInstructionTimeAndRatio",
2987
+ "l2CacheHitRate",
2988
+ "memoryReadWriteBandwidth",
2989
+ "l0ReadWriteBandwidth",
2990
+ "ubReadWriteBandwidth",
2991
+ "computeAndMoveUnitTimeAndRatio",
2992
+ "resourceConflictRatio"
2993
+ ];
2994
+ var DCC_SECTIONS = ["PipeUtilization", "Pipeline", "L2Cache", "Memory", "MemoryL0", "MemoryUB", "ArithmeticUtilization", "ResourceConflictRatio"];
2995
+ var DCC_FORCED = /* @__PURE__ */ new Set(["Pipeline", "ArithmeticUtilization", "ResourceConflictRatio"]);
2996
+ var DCC_SET_DISABLED = /* @__PURE__ */ new Set(["detailed", "hccl", "roofline"]);
2997
+ var DCC_SET_TABLE = {
2998
+ Custom: { desc: "\u9ED8\u8BA4\u53C2\u6570", disabled: false },
2999
+ basic: { desc: "PipeUtilization;L2Cache;Memory", disabled: false },
3000
+ detailed: { desc: "\u6682\u4E0D\u652F\u6301", disabled: true },
3001
+ full: { desc: "PipeUtilization;L2Cache;Memory;MemoryL0;MemoryUB", disabled: false },
3002
+ hccl: { desc: "\u6682\u4E0D\u652F\u6301", disabled: true },
3003
+ roofline: { desc: "\u6682\u4E0D\u652F\u6301", disabled: true }
3004
+ };
3005
+ function defaultConfig(soc) {
3006
+ const isA5 = soc === "a5";
3007
+ return {
3008
+ collection_type: "onboard",
3009
+ onboard: {
3010
+ interCoreLoad: true,
3011
+ roofline: true,
3012
+ operatorCodeHotspot: true,
3013
+ // A5 fork (PARITY-MAP §9.1): PC sampling replaces Cache heatmap on Ascend950
3014
+ cacheHeatmap: !isA5,
3015
+ pcSampling: isA5,
3016
+ operatorMetrics: true,
3017
+ cubeAndVectorInstructionTimeAndRatio: true,
3018
+ l2CacheHitRate: true,
3019
+ memoryReadWriteBandwidth: true,
3020
+ l0ReadWriteBandwidth: true,
3021
+ ubReadWriteBandwidth: true,
3022
+ computeAndMoveUnitTimeAndRatio: true,
3023
+ resourceConflictRatio: true
3024
+ },
3025
+ simulator: { memoryThroughputWaveform: false, resourceConflictRatio: true },
3026
+ new_tool: {
3027
+ common: { output_file: "", force_overwrite: false, command_line: "" },
3028
+ metrics: { kind: "set", set: "basic", sections: ["PipeUtilization", "L2Cache", "Memory"] }
3029
+ },
3030
+ soc
3031
+ };
3032
+ }
3033
+ function CollectConfigPage(props) {
3034
+ const { locale, onSave, onCopyPrompt } = props;
3035
+ const [cfg, setCfg] = (0, import_react9.useState)(props.config ?? defaultConfig("auto"));
3036
+ const [saved, setSaved] = (0, import_react9.useState)(false);
3037
+ const isA5 = cfg.soc === "a5";
3038
+ const opMetrics = OPERATOR_METRIC_KEYS.every((k) => cfg.onboard[k] === true);
3039
+ const opMetricsSome = OPERATOR_METRIC_KEYS.some((k) => cfg.onboard[k] === true);
3040
+ const setOnboard = (patch) => {
3041
+ setCfg((c) => ({ ...c, onboard: { ...c.onboard, ...patch } }));
3042
+ setSaved(false);
3043
+ };
3044
+ const setSim = (patch) => {
3045
+ setCfg((c) => ({ ...c, simulator: { ...c.simulator, ...patch } }));
3046
+ setSaved(false);
3047
+ };
3048
+ const setNewTool = (patch) => {
3049
+ setCfg((c) => ({ ...c, new_tool: { ...c.new_tool, ...patch } }));
3050
+ setSaved(false);
3051
+ };
3052
+ const commandLine = (0, import_react9.useMemo)(() => {
3053
+ if (cfg.new_tool.metrics.kind === "set" && cfg.new_tool.metrics.set !== "Custom") {
3054
+ const table = {
3055
+ basic: ["PipeUtilization", "L2Cache", "Memory"],
3056
+ full: ["PipeUtilization", "L2Cache", "Memory", "MemoryL0", "MemoryUB"]
3057
+ };
3058
+ const sections = table[cfg.new_tool.metrics.set] ?? [];
3059
+ const parts2 = sections.filter((s) => !DCC_FORCED.has(s)).map((s) => `--section ${s}`);
3060
+ if (cfg.new_tool.common.force_overwrite) parts2.push("--force");
3061
+ return parts2.join(" ");
3062
+ }
3063
+ const parts = cfg.new_tool.metrics.sections.filter((s) => !DCC_FORCED.has(s)).map((s) => `--section ${s}`);
3064
+ if (cfg.collection_type === "onboard" && cfg.new_tool.common.force_overwrite) parts.push("--force");
3065
+ return parts.join(" ");
3066
+ }, [cfg.new_tool, cfg.collection_type]);
3067
+ const commandLineEdited = cfg.new_tool.common.command_line !== "";
3068
+ const agentPrompt = (0, import_react9.useMemo)(() => buildAgentPrompt(cfg), [cfg]);
3069
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg", "data-testid": "collection-config", children: [
3070
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("header", { className: "otwb-cfg-head", children: [
3071
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h2", { className: "otwb-cfg-title", children: locale("configTitle") }),
3072
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "otwb-cfg-soc", children: [
3073
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: locale("socLabel") }),
3074
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("select", { value: cfg.soc, onChange: (e) => {
3075
+ setCfg((c) => ({ ...c, soc: e.target.value }));
3076
+ setSaved(false);
3077
+ }, "data-testid": "soc-select", children: [
3078
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: "auto", children: locale("socAuto") }),
3079
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: "a2", children: "a2" }),
3080
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: "a3", children: "a3" }),
3081
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: "a5", children: "a5 (Ascend950)" })
3082
+ ] })
3083
+ ] })
3084
+ ] }),
3085
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "otwb-cfg-note", "data-testid": "collection-exec-note", children: locale("configExecNote") }),
3086
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { className: "otwb-cfg-section", "data-testid": "options-on-board", children: [
3087
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "otwb-cfg-section-title", children: locale("onboardTitle") }),
3088
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-alert", role: "note", children: [
3089
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { children: locale("onboardAlert1") }),
3090
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { children: isA5 ? locale("onboardAlert2A5") : locale("onboardAlert2") })
3091
+ ] }),
3092
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { className: "otwb-cfg-group", children: locale("collectContent") }),
3093
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-block", children: [
3094
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "otwb-cfg-block-title", children: locale("memoryHeatmap") }),
3095
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "otwb-cfg-desc", children: locale("memoryHeatmapDesc") }),
3096
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "otwb-cfg-desc", children: locale("defaultOptions") }),
3097
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-checks", children: [
3098
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Check, { label: locale("interCoreLoad"), checked: cfg.onboard.interCoreLoad, onChange: (v) => setOnboard({ interCoreLoad: v }), testid: "interCoreLoad" }),
3099
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Check, { label: locale("rooflineOpt"), checked: cfg.onboard.roofline, onChange: (v) => setOnboard({ roofline: v }), tooltip: locale("rooflineTooltip"), testid: "roofline" })
3100
+ ] })
3101
+ ] }),
3102
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "otwb-cfg-block", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Check, { label: locale("codeHotspot"), checked: cfg.onboard.operatorCodeHotspot, onChange: (v) => setOnboard({ operatorCodeHotspot: v }), testid: "operatorCodeHotspot" }) }),
3103
+ isA5 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "otwb-cfg-block", "data-testid": "a5-pc-sampling", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Check, { label: locale("pcSampling"), checked: cfg.onboard.pcSampling, onChange: (v) => setOnboard({ pcSampling: v, cacheHeatmap: false }), tooltip: locale("pcSamplingTooltip"), testid: "pcSampling" }) }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "otwb-cfg-block", "data-testid": "non-a5-cache-heatmap", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Check, { label: locale("cacheHeatmap"), checked: cfg.onboard.cacheHeatmap, onChange: (v) => setOnboard({ cacheHeatmap: v, pcSampling: false }), tooltip: locale("cacheHeatmapTooltip"), testid: "cacheHeatmap" }) }),
3104
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-block", children: [
3105
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3106
+ Check,
3107
+ {
3108
+ label: locale("operatorMetrics"),
3109
+ checked: opMetrics,
3110
+ indeterminate: opMetricsSome && !opMetrics,
3111
+ onChange: (v) => setOnboard(Object.fromEntries(OPERATOR_METRIC_KEYS.map((k) => [k, v]))),
3112
+ testid: "operatorMetrics"
3113
+ }
3114
+ ),
3115
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-checks otwb-cfg-checks--nested", "data-testid": "nested-operator-metrics", children: [
3116
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Check, { label: locale("cubeVector"), checked: cfg.onboard.cubeAndVectorInstructionTimeAndRatio, onChange: (v) => setOnboard({ cubeAndVectorInstructionTimeAndRatio: v }), testid: "cubeAndVector" }),
3117
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Check, { label: locale("l2HitRate"), checked: cfg.onboard.l2CacheHitRate, onChange: (v) => setOnboard({ l2CacheHitRate: v }), testid: "l2CacheHitRate" }),
3118
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Check, { label: locale("memoryBandwidth"), checked: cfg.onboard.memoryReadWriteBandwidth, onChange: (v) => setOnboard({ memoryReadWriteBandwidth: v }), testid: "memoryReadWriteBandwidth" }),
3119
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Check, { label: locale("l0Bandwidth"), checked: cfg.onboard.l0ReadWriteBandwidth, onChange: (v) => setOnboard({ l0ReadWriteBandwidth: v }), testid: "l0ReadWriteBandwidth" }),
3120
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Check, { label: locale("ubBandwidth"), checked: cfg.onboard.ubReadWriteBandwidth, onChange: (v) => setOnboard({ ubReadWriteBandwidth: v }), testid: "ubReadWriteBandwidth" }),
3121
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Check, { label: locale("computeMoveRatio"), checked: cfg.onboard.computeAndMoveUnitTimeAndRatio, onChange: (v) => setOnboard({ computeAndMoveUnitTimeAndRatio: v }), testid: "computeAndMoveUnitTimeAndRatio" }),
3122
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Check, { label: locale("resourceConflict"), checked: cfg.onboard.resourceConflictRatio, onChange: (v) => setOnboard({ resourceConflictRatio: v }), testid: "resourceConflictRatio" })
3123
+ ] })
3124
+ ] })
3125
+ ] }),
3126
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { className: "otwb-cfg-section", "data-testid": "options-simulator", children: [
3127
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "otwb-cfg-section-title", children: locale("simulatorTitle") }),
3128
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "otwb-cfg-alert", role: "note", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { children: locale("simAlert") }) }),
3129
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { className: "otwb-cfg-group", children: locale("collectContent") }),
3130
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-block", children: [
3131
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "otwb-cfg-block-title", children: locale("instructionPipeline") }),
3132
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "otwb-cfg-desc", children: locale("instructionPipelineDesc") })
3133
+ ] }),
3134
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "otwb-cfg-desc", children: locale("extraOptions") }),
3135
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "otwb-cfg-checks", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Check, { label: locale("memoryThroughput"), checked: cfg.simulator.memoryThroughputWaveform, onChange: (v) => setSim({ memoryThroughputWaveform: v }), testid: "memoryThroughputWaveform" }) }),
3136
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-block", children: [
3137
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "otwb-cfg-block-title", children: locale("simCodeHotspot") }),
3138
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "otwb-cfg-desc", children: cfg.simulator.resourceConflictRatio ? locale("simCodeHotspotDesc") : locale("simCodeHotspotDescBase") }),
3139
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "otwb-cfg-checks", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Check, { label: locale("simResourceConflict"), checked: cfg.simulator.resourceConflictRatio, onChange: (v) => setSim({ resourceConflictRatio: v }), testid: "simResourceConflictRatio" }) })
3140
+ ] })
3141
+ ] }),
3142
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { className: "otwb-cfg-section", "data-testid": "data-collection-config", children: [
3143
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "otwb-cfg-section-title", children: locale("newToolTitle") }),
3144
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-row", children: [
3145
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "otwb-cfg-label", children: locale("collectionType") }),
3146
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-segmented", role: "radiogroup", "data-testid": "collection-type", children: [
3147
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: `otwb-seg${cfg.collection_type === "onboard" ? " otwb-seg--on" : ""}`, onClick: () => {
3148
+ setCfg((c) => ({ ...c, collection_type: "onboard" }));
3149
+ setSaved(false);
3150
+ }, children: locale("dcOnboard") }),
3151
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: `otwb-seg${cfg.collection_type === "simulate" ? " otwb-seg--on" : ""}`, onClick: () => {
3152
+ setCfg((c) => ({ ...c, collection_type: "simulate" }));
3153
+ setSaved(false);
3154
+ }, children: locale("dcSimulate") })
3155
+ ] })
3156
+ ] }),
3157
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-collapsible", "data-testid": "dcc-common", children: [
3158
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { className: "otwb-cfg-collapsible-head", children: locale("dcCommon") }),
3159
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-row", children: [
3160
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "otwb-cfg-label", children: locale("outputFile") }),
3161
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-inputgroup", children: [
3162
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3163
+ "input",
3164
+ {
3165
+ type: "text",
3166
+ placeholder: locale("outputFilePlaceholder"),
3167
+ value: cfg.new_tool.common.output_file,
3168
+ onChange: (e) => setNewTool({ common: { ...cfg.new_tool.common, output_file: e.target.value } }),
3169
+ "data-testid": "dcc-output-file"
3170
+ }
3171
+ ),
3172
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "otwb-btn", onClick: () => {
3173
+ }, children: locale("browse") })
3174
+ ] })
3175
+ ] }),
3176
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-row", children: [
3177
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "otwb-cfg-label", children: locale("forceOverwrite") }),
3178
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-segmented", role: "radiogroup", children: [
3179
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: `otwb-seg${cfg.new_tool.common.force_overwrite ? " otwb-seg--on" : ""}`, onClick: () => setNewTool({ common: { ...cfg.new_tool.common, force_overwrite: true } }), children: "Yes" }),
3180
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: `otwb-seg${!cfg.new_tool.common.force_overwrite ? " otwb-seg--on" : ""}`, onClick: () => setNewTool({ common: { ...cfg.new_tool.common, force_overwrite: false } }), children: "No" })
3181
+ ] })
3182
+ ] }),
3183
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-row otwb-cfg-row--stack", children: [
3184
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "otwb-cfg-label", children: locale("commandLine") }),
3185
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3186
+ "textarea",
3187
+ {
3188
+ rows: 3,
3189
+ placeholder: locale("commandLinePlaceholder"),
3190
+ value: commandLineEdited ? cfg.new_tool.common.command_line : commandLine,
3191
+ onChange: (e) => setNewTool({ common: { ...cfg.new_tool.common, command_line: e.target.value } }),
3192
+ "data-testid": "dcc-command-line"
3193
+ }
3194
+ )
3195
+ ] })
3196
+ ] }),
3197
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-collapsible", "data-testid": "dcc-metrics", children: [
3198
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { className: "otwb-cfg-collapsible-head", children: locale("dcMetrics") }),
3199
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DccMetrics, { cfg, setNewTool, locale })
3200
+ ] })
3201
+ ] }),
3202
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("footer", { className: "otwb-cfg-footer", children: [
3203
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3204
+ "button",
3205
+ {
3206
+ type: "button",
3207
+ className: "otwb-btn",
3208
+ "data-testid": "save-config",
3209
+ onClick: () => {
3210
+ onSave(cfg);
3211
+ setSaved(true);
3212
+ },
3213
+ children: [
3214
+ saved ? "\u2713" : "",
3215
+ " \u4FDD\u5B58\u914D\u7F6E"
3216
+ ]
3217
+ }
3218
+ ),
3219
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3220
+ "button",
3221
+ {
3222
+ type: "button",
3223
+ className: "otwb-btn otwb-btn--primary",
3224
+ "data-testid": "copy-agent-prompt",
3225
+ onClick: () => onCopyPrompt(agentPrompt),
3226
+ children: locale("copyAgentPrompt")
3227
+ }
3228
+ )
3229
+ ] })
3230
+ ] });
3231
+ }
3232
+ function Check(props) {
3233
+ const { label, checked, indeterminate = false, onChange, tooltip, testid } = props;
3234
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: `otwb-check${checked ? " otwb-check--on" : ""}`, title: tooltip, "data-testid": testid, children: [
3235
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: `otwb-check-box${indeterminate ? " otwb-check-box--indeterminate" : ""}`, "aria-hidden": "true", children: indeterminate ? "\u2212" : checked ? "\u2713" : "" }),
3236
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "checkbox", checked, onChange: (e) => onChange(e.target.checked) }),
3237
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "otwb-check-label", children: label })
3238
+ ] });
3239
+ }
3240
+ function DccMetrics(props) {
3241
+ const { cfg, setNewTool, locale } = props;
3242
+ const [kind, setKind] = (0, import_react9.useState)(cfg.new_tool.metrics.kind);
3243
+ const [filter, setFilter] = (0, import_react9.useState)("");
3244
+ const setName = cfg.new_tool.metrics.set;
3245
+ const sections = cfg.new_tool.metrics.sections;
3246
+ const setMode = (k) => {
3247
+ setKind(k);
3248
+ setNewTool({ metrics: { ...cfg.new_tool.metrics, kind: k } });
3249
+ };
3250
+ const pickSet = (name) => {
3251
+ const table = {
3252
+ Custom: [],
3253
+ basic: ["PipeUtilization", "L2Cache", "Memory"],
3254
+ detailed: [],
3255
+ full: ["PipeUtilization", "L2Cache", "Memory", "MemoryL0", "MemoryUB"],
3256
+ hccl: [],
3257
+ roofline: []
3258
+ };
3259
+ setNewTool({ metrics: { kind: "set", set: name, sections: table[name] ?? [] } });
3260
+ };
3261
+ const toggleSection = (name) => {
3262
+ const forced = DCC_FORCED.has(name);
3263
+ if (forced) return;
3264
+ const next = sections.includes(name) ? sections.filter((s) => s !== name) : [...sections, name];
3265
+ const sorted = [...next].sort().join("|");
3266
+ let detected = "Custom";
3267
+ for (const candidate of ["basic", "full"]) {
3268
+ const list = candidate === "basic" ? ["PipeUtilization", "L2Cache", "Memory"] : ["PipeUtilization", "L2Cache", "Memory", "MemoryL0", "MemoryUB"];
3269
+ if (list.slice().sort().join("|") === sorted) {
3270
+ detected = candidate;
3271
+ break;
3272
+ }
3273
+ }
3274
+ setNewTool({ metrics: { kind: "section", set: detected, sections: next } });
3275
+ };
3276
+ const f = filter.trim().toLowerCase();
3277
+ const sectionRows = DCC_SECTIONS.filter((s) => f === "" || s.toLowerCase().includes(f));
3278
+ const setRows = Object.keys(DCC_SET_TABLE).filter((s) => f === "" || s.toLowerCase().includes(f));
3279
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3280
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-metrics-toolbar", children: [
3281
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "otwb-cfg-segmented", role: "radiogroup", "data-testid": "dcc-metrics-kind", children: [
3282
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: `otwb-seg${kind === "set" ? " otwb-seg--on" : ""}`, onClick: () => setMode("set"), children: "set" }),
3283
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: `otwb-seg${kind === "section" ? " otwb-seg--on" : ""}`, onClick: () => setMode("section"), children: "section" })
3284
+ ] }),
3285
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "search", placeholder: locale("filterPlaceholder"), value: filter, onChange: (e) => setFilter(e.target.value), "data-testid": "dcc-filter" })
3286
+ ] }),
3287
+ kind === "set" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("table", { className: "otwb-dcctable", "data-testid": "dcc-set-table", children: [
3288
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tr", { children: [
3289
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "otwb-dcctable-radio" }),
3290
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { children: locale("dcName") }),
3291
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { children: locale("dcDescription") })
3292
+ ] }) }),
3293
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tbody", { children: setRows.map((name) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tr", { className: setName === name ? "otwb-dcctable-row--active" : void 0, onClick: () => {
3294
+ if (!DCC_SET_DISABLED.has(name)) pickSet(name);
3295
+ }, children: [
3296
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { className: "otwb-dcctable-radio", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: `otwb-radio${setName === name ? " otwb-radio--on" : ""}` }) }),
3297
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { children: name }),
3298
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { children: DCC_SET_TABLE[name].desc })
3299
+ ] }, name)) })
3300
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("table", { className: "otwb-dcctable", "data-testid": "dcc-section-table", children: [
3301
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tr", { children: [
3302
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "otwb-dcctable-radio" }),
3303
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { children: locale("dcName") }),
3304
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { children: locale("dcDescription") })
3305
+ ] }) }),
3306
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tbody", { children: sectionRows.map((name) => {
3307
+ const forced = DCC_FORCED.has(name);
3308
+ const checked = forced || sections.includes(name);
3309
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tr", { onClick: () => toggleSection(name), children: [
3310
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { className: "otwb-dcctable-radio", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: `otwb-check-box${checked ? " otwb-check-box--on" : ""}${forced ? " otwb-check-box--forced" : ""}`, children: checked ? "\u2713" : "" }) }),
3311
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { children: name }),
3312
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { className: "otwb-dcctable-muted", children: forced ? "\u5F3A\u5236\u52FE\u9009" : "Detail analysis of the compute resource..." })
3313
+ ] }, name);
3314
+ }) })
3315
+ ] })
3316
+ ] });
3317
+ }
3318
+ function buildAgentPrompt(cfg) {
3319
+ return [
3320
+ "\u8BF7\u6309\u7B97\u5B50\u8C03\u4F18\u5DE5\u4F5C\u53F0\u300C\u91C7\u96C6\u914D\u7F6E\u300D\u9875\u7684\u5F53\u524D\u914D\u7F6E\u53D1\u8D77\u4E00\u6B21\u91C7\u96C6\uFF1A",
3321
+ "\u8C03\u7528 ops_collect \u5DE5\u5177\uFF0Coptions \u53C2\u6570\u53D6\u4E0B\u9762\u7684 JSON\uFF08\u6765\u6E90\uFF1A\u5DE5\u4F5C\u53F0\u91C7\u96C6\u914D\u7F6E\u9875\uFF0C\u7528\u6237\u5DF2\u786E\u8BA4\uFF09\uFF1A",
3322
+ JSON.stringify({
3323
+ collection_type: cfg.collection_type,
3324
+ soc: cfg.soc,
3325
+ onboard: cfg.onboard,
3326
+ simulator: cfg.simulator,
3327
+ new_tool: cfg.new_tool
3328
+ }, null, 2),
3329
+ "\u8BF4\u660E\uFF1ABound \u5224\u5B9A\u4E0D\u5728\u672C\u6B21\u8BF7\u6C42\u5185\uFF1B\u91C7\u96C6\u5B8C\u6210\u540E\u5982\u9700\u5224\u5B9A\u8BF7\u5355\u72EC\u8C03\u7528 verdict_judge\u3002"
3330
+ ].join("\n");
3331
+ }
3332
+
3333
+ // src/client/panel.tsx
3334
+ var import_jsx_runtime9 = require("react/jsx-runtime");
3335
+ function WorkbenchPanel(props) {
3336
+ const { state, locale } = props;
3337
+ const [view, setView] = (0, import_react10.useState)(() => props.initialReportId === void 0 ? "results" : "report");
3338
+ const [reportId, setReportId] = (0, import_react10.useState)(() => props.initialReportId ?? null);
3339
+ const [sortDesc, setSortDesc] = (0, import_react10.useState)(true);
3340
+ const [markedOnly, setMarkedOnly] = (0, import_react10.useState)(false);
3341
+ const [markedLocal, setMarkedLocal] = (0, import_react10.useState)(/* @__PURE__ */ new Set());
3342
+ const [expanded, setExpanded] = (0, import_react10.useState)(/* @__PURE__ */ new Set());
3343
+ const [toast, setToast] = (0, import_react10.useState)(null);
3344
+ const results = (0, import_react10.useMemo)(() => {
3345
+ const list = [...state.results];
3346
+ list.sort((a, b) => (sortDesc ? -1 : 1) * (a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0));
3347
+ return markedOnly ? list.filter((r) => r.marked || markedLocal.has(r.report_id)) : list;
3348
+ }, [state.results, sortDesc, markedOnly, markedLocal]);
3349
+ const report = reportId !== null ? state.reports[reportId] : void 0;
3350
+ const resultOf = (id) => state.results.find((r) => r.report_id === id) ?? null;
3351
+ const saveConfig = (config) => {
3352
+ void props.promptSession(collectionSavePrompt(config)).then((err) => {
3353
+ setToast(err !== null ? err : "\u5DF2\u8BF7\u6C42\u4F1A\u8BDD Agent \u5199\u5165\u91C7\u96C6\u914D\u7F6E\uFF08state.collection_config\uFF09");
3354
+ });
3355
+ };
3356
+ const copyPath = (path) => {
3357
+ void navigator.clipboard?.writeText(path).then(
3358
+ () => setToast(locale("pathCopied")),
3359
+ () => setToast(path)
3360
+ );
3361
+ };
3362
+ const openReport = (id) => {
3363
+ setReportId(id);
3364
+ setView("report");
3365
+ };
3366
+ const copyPrompt = (text) => {
3367
+ void navigator.clipboard?.writeText(text).then(
3368
+ () => setToast(locale("promptCopied")),
3369
+ () => setToast(text)
3370
+ );
3371
+ };
3372
+ if (view === "config") {
3373
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "otwb-panel", children: [
3374
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3375
+ PanelTopbar,
3376
+ {
3377
+ locale,
3378
+ view: "config",
3379
+ onResults: () => setView("results"),
3380
+ onConfig: () => setView("config"),
3381
+ skill: state.config_echo.tuningSkill
3382
+ }
3383
+ ),
3384
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3385
+ CollectConfigPage,
3386
+ {
3387
+ config: state.collection_config,
3388
+ locale,
3389
+ onSave: saveConfig,
3390
+ onCopyPrompt: copyPrompt
3391
+ }
3392
+ ),
3393
+ toast !== null && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ToastToast, { message: toast, onDismiss: () => setToast(null) })
3394
+ ] });
3395
+ }
3396
+ if (view === "report" && report !== void 0) {
3397
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "otwb-panel", children: [
3398
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3399
+ ReportView,
3400
+ {
3401
+ ctx: props.ctx,
3402
+ sessionId: props.sessionId,
3403
+ report,
3404
+ result: resultOf(report.report_id),
3405
+ locale,
3406
+ onBack: () => {
3407
+ setView("results");
3408
+ setReportId(null);
3409
+ },
3410
+ promptSession: props.promptSession
3411
+ }
3412
+ ),
3413
+ toast !== null && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ToastToast, { message: toast, onDismiss: () => setToast(null) })
3414
+ ] });
3415
+ }
3416
+ if (view === "report" && report === void 0) {
3417
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "otwb-panel", children: [
3418
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PanelTopbar, { locale, view: "results", onResults: () => setView("results"), onConfig: () => setView("config"), skill: state.config_echo.tuningSkill }),
3419
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "otwb-empty", children: [
3420
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "otwb-empty-title", children: locale("reportTitle") }),
3421
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "otwb-empty-hint", children: locale("resultsReportMissing") })
3422
+ ] })
3423
+ ] });
3424
+ }
3425
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "otwb-panel", children: [
3426
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3427
+ PanelTopbar,
3428
+ {
3429
+ locale,
3430
+ view: "results",
3431
+ onResults: () => setView("results"),
3432
+ onConfig: () => setView("config"),
3433
+ skill: state.config_echo.tuningSkill
3434
+ }
3435
+ ),
3436
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "otwb-results-head", children: [
3437
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { className: "otwb-results-title", children: locale("navResults") }),
3438
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "otwb-results-commands", children: [
3439
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("button", { type: "button", "data-testid": "sort-time", title: locale("sortTime"), onClick: () => setSortDesc((d) => !d), children: [
3440
+ sortDesc ? "\u2193" : "\u2191",
3441
+ " ",
3442
+ locale("sortTime")
3443
+ ] }),
3444
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", "data-testid": "filter-marked", onClick: () => setMarkedOnly((m) => !m), children: markedOnly ? locale("showAll") : locale("markedOnly") })
3445
+ ] })
3446
+ ] }),
3447
+ state.baseline_id !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "otwb-baseline-line", "data-testid": "baseline-line", children: [
3448
+ locale("baselineLabel"),
3449
+ ": ",
3450
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { children: state.baseline_id })
3451
+ ] }),
3452
+ results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "otwb-empty", "data-testid": "results-empty", children: [
3453
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "otwb-empty-title", children: locale("noData") }),
3454
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "otwb-empty-hint", children: locale("resultsHint") })
3455
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { className: "otwb-results", "data-testid": "results-list", children: results.map((entry) => {
3456
+ const isMarked = entry.marked || markedLocal.has(entry.report_id);
3457
+ const isOpen = expanded.has(entry.report_id);
3458
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("li", { className: `otwb-result${isMarked ? " otwb-result--marked" : ""}`, "data-testid": "result-entry", children: [
3459
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "otwb-result-row", children: [
3460
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3461
+ "button",
3462
+ {
3463
+ type: "button",
3464
+ className: "otwb-result-caret",
3465
+ "aria-label": isOpen ? "\u2212" : "+",
3466
+ onClick: () => setExpanded((prev) => {
3467
+ const next = new Set(prev);
3468
+ if (next.has(entry.report_id)) next.delete(entry.report_id);
3469
+ else next.add(entry.report_id);
3470
+ return next;
3471
+ }),
3472
+ children: isOpen ? "\u25BE" : "\u25B8"
3473
+ }
3474
+ ),
3475
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "otwb-result-flag", role: "img", "aria-label": isMarked ? locale("mark") : "", onClick: () => setMarkedLocal((prev) => {
3476
+ const next = new Set(prev);
3477
+ if (next.has(entry.report_id)) next.delete(entry.report_id);
3478
+ else next.add(entry.report_id);
3479
+ return next;
3480
+ }), title: isMarked ? locale("unmark") : locale("mark"), "data-testid": "result-mark", children: isMarked ? "\u2691" : "\u2690" }),
3481
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "otwb-result-title", onClick: () => openReport(entry.report_id), "data-testid": "result-open", children: entry.title }),
3482
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "otwb-result-src", "data-testid": "result-source-badge", title: `${locale("sourceLabel")}: ${entry.source}`, children: entry.source }),
3483
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "otwb-result-time", title: entry.timestamp, children: entry.timestamp.slice(0, 19).replace("T", " ") }),
3484
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "otwb-result-cmd", title: locale("openReport"), onClick: () => openReport(entry.report_id), children: "\u2197" }),
3485
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "otwb-result-cmd", title: locale("copyPath"), onClick: () => copyPath(entry.dir_path), "data-testid": "result-copy-path", children: "\u29C9" })
3486
+ ] }),
3487
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("ul", { className: "otwb-result-children", children: [
3488
+ entry.children.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ResultChild, { child, onCopy: copyPath, locale }, `${child.title}-${String(i)}`)),
3489
+ entry.children.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { className: "otwb-result-child otwb-result-child--empty", children: "\u2014" })
3490
+ ] })
3491
+ ] }, entry.report_id);
3492
+ }) }),
3493
+ toast !== null && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ToastToast, { message: toast, onDismiss: () => setToast(null) }),
3494
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "otwb-footer-note", children: locale("markLocalNote") })
3495
+ ] });
3496
+ }
3497
+ function collectionSavePrompt(config) {
3498
+ return [
3499
+ "\u8BF7\u628A\u7B97\u5B50\u8C03\u4F18\u5DE5\u4F5C\u53F0\u300C\u91C7\u96C6\u914D\u7F6E\u300D\u9875\u7684\u6700\u65B0\u914D\u7F6E\u843D\u76D8\uFF08state.collection_config\uFF09\uFF1A",
3500
+ "\u8C03\u7528 ops_collect \u5DE5\u5177\u5E76\u643A\u5E26\u4E0B\u9762\u7684 JSON \u4F5C\u4E3A options \u53C2\u6570\u2014\u2014\u5DE5\u5177\u4F1A\u628A\u5E26 options \u7684\u8C03\u7528\u6301\u4E45\u5316\u4E3A\u65B0\u7684\u91C7\u96C6\u914D\u7F6E",
3501
+ "\uFF08\u65E0\u9700\u771F\u5B9E\u91C7\u96C6\u6267\u884C\uFF1B\u82E5\u5F53\u524D\u73AF\u5883\u65E0 NPU\uFF0C\u5DE5\u5177\u4F1A\u7167\u5E38\u56DE\u9000\u6A21\u62DF\u5668\u5E76\u628A\u8BE5\u914D\u7F6E\u5199\u5165 state.collection_config\uFF09\uFF1A",
3502
+ JSON.stringify(config, null, 2),
3503
+ "\u8BF4\u660E\uFF1A\u540E\u7EED\u4E0D\u5E26 options \u7684 ops_collect \u8C03\u7528\u5C06\u81EA\u52A8\u6D88\u8D39\u8FD9\u4EFD\u5DF2\u4FDD\u5B58\u7684\u914D\u7F6E\u3002"
3504
+ ].join("\n");
3505
+ }
3506
+ function ResultChild(props) {
3507
+ const { child, onCopy, locale } = props;
3508
+ if (child.children !== void 0 && child.children.length > 0) {
3509
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("li", { className: "otwb-result-child otwb-result-child--dir", children: [
3510
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "otwb-result-child-title", children: [
3511
+ child.title,
3512
+ "/"
3513
+ ] }),
3514
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { className: "otwb-result-children otwb-result-children--nested", children: child.children.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ResultChild, { child: c, onCopy, locale }, `${c.title}-${String(i)}`)) })
3515
+ ] });
3516
+ }
3517
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("li", { className: "otwb-result-child", children: [
3518
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: `otwb-result-filetype otwb-result-filetype--${child.file_type}`, children: child.file_type }),
3519
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "otwb-result-child-title", title: child.file_path, children: child.title }),
3520
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "otwb-result-cmd", title: locale("copyPath"), onClick: () => onCopy(child.file_path), children: "\u29C9" })
3521
+ ] });
3522
+ }
3523
+ function PanelTopbar(props) {
3524
+ const { locale, view, onResults, onConfig, skill } = props;
3525
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("header", { className: "otwb-topbar", "data-testid": "otwb-topbar", children: [
3526
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "otwb-topbar-title", children: locale("workbenchTitle") }),
3527
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("nav", { className: "otwb-topbar-nav", role: "tablist", children: [
3528
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", role: "tab", className: `otwb-topbar-navbtn${view === "results" || view === "report" ? " otwb-topbar-navbtn--active" : ""}`, "aria-selected": view === "results" || view === "report", onClick: onResults, "data-testid": "nav-results", children: locale("navResults") }),
3529
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", role: "tab", className: `otwb-topbar-navbtn${view === "config" ? " otwb-topbar-navbtn--active" : ""}`, "aria-selected": view === "config", onClick: onConfig, "data-testid": "nav-config", children: locale("navConfig") })
3530
+ ] }),
3531
+ skill !== "" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "otwb-topbar-skill", title: `${locale("skillEcho")}: ${skill}`, children: [
3532
+ "\u2699 ",
3533
+ skill
3534
+ ] })
3535
+ ] });
3536
+ }
3537
+ function ToastToast(props) {
3538
+ const { message, onDismiss } = props;
3539
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "otwb-toast", role: "status", "data-testid": "otwb-toast", onClick: onDismiss, children: message });
3540
+ }
3541
+
3542
+ // src/client/i18n/zh.ts
3543
+ var zh = {
3544
+ // ── workbench shell / navigation ────────────────────────────────────
3545
+ workbenchTitle: "\u7B97\u5B50\u8C03\u4F18\u5DE5\u4F5C\u53F0",
3546
+ navResults: "\u6027\u80FD\u8C03\u4F18\u7ED3\u679C",
3547
+ navConfig: "\u91C7\u96C6\u914D\u7F6E",
3548
+ backToList: "\u8FD4\u56DE\u7ED3\u679C\u5217\u8868",
3549
+ refreshNote: "state.json \xB7 4s \u8F6E\u8BE2",
3550
+ noData: "\u6682\u65E0\u6570\u636E",
3551
+ resultsHint: "\u4F1A\u8BDD\u5185\u8FD0\u884C ops_collect / ops_import \u540E\uFF0C\u6700\u65B0\u91C7\u96C6\u4F1A\u51FA\u73B0\u5728\u6B64\u5217\u8868\u3002",
3552
+ resultsReportMissing: "\u62A5\u544A\u4E0D\u5B58\u5728\u4E8E state.reports\uFF08\u53EF\u80FD\u5DF2\u88AB\u6E05\u7406\uFF09\uFF0C\u8BF7\u8FD4\u56DE\u5217\u8868\u5237\u65B0\u3002",
3553
+ sortTime: "\u6309\u65F6\u95F4\u6392\u5E8F",
3554
+ markedOnly: "\u53EA\u770B\u6807\u8BB0",
3555
+ showAll: "\u663E\u793A\u5168\u90E8",
3556
+ openReport: "\u6253\u5F00\u62A5\u544A",
3557
+ copyPath: "\u590D\u5236\u8DEF\u5F84",
3558
+ pathCopied: "\u8DEF\u5F84\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F",
3559
+ mark: "\u6807\u8BB0",
3560
+ unmark: "\u53D6\u6D88\u6807\u8BB0",
3561
+ markLocalNote: "\u9762\u677F\u5185\u6807\u8BB0\uFF08\u5199\u5165 state \u9700\u7ECF Host \u901A\u9053\uFF0CPhase 2\uFF09",
3562
+ sourceLabel: "\u6765\u6E90",
3563
+ reportTitle: "\u6027\u80FD\u5206\u6790\u62A5\u544A",
3564
+ baselineLabel: "\u57FA\u7EBF",
3565
+ skillEcho: "\u8C03\u4F18\u6280\u80FD",
3566
+ // ── report tabs (PARITY-MAP §2) ─────────────────────────────────────
3567
+ reportViews: "\u62A5\u544A\u89C6\u56FE",
3568
+ tabOp: "OP\u7B97\u5B50",
3569
+ tabTimeline: "\u65F6\u95F4\u7EBF",
3570
+ tabSource: "\u6E90\u7801",
3571
+ tabDetail: "\u8BE6\u60C5",
3572
+ tabCache: "\u7F13\u5B58",
3573
+ phase2: "Phase 2",
3574
+ opSelectorLabel: "\u9009\u62E9\u7B97\u5B50",
3575
+ sourceEmpty: "\u6E90\u7801\u672A\u968F\u62A5\u544A\u5F52\u6863\uFF1A\u62A5\u544A\u6A21\u578B\u4E0D\u542B case \u6E90\u6587\u4EF6\u901A\u9053\uFF08\u5DEE\u5F02\u58F0\u660E D-11\uFF09\u3002Phase 2 \u6269\u5C55\u3002",
3576
+ cacheEmpty: "\u7F13\u5B58\u7C7B\u8868\u683C\uFF08L2Cache / MemoryL0 / MemoryUB\uFF09\u672A\u91C7\u96C6\u3002",
3577
+ cacheTitle: "\u7F13\u5B58",
3578
+ // ── toolbar (PARITY-MAP §3) ─────────────────────────────────────────
3579
+ searchPlaceholder: "\u641C\u7D22",
3580
+ searchLabel: "\u641C\u7D22",
3581
+ searchClear: "\u6E05\u9664\u641C\u7D22",
3582
+ zoomIn: "\u653E\u5927",
3583
+ zoomOut: "\u7F29\u5C0F",
3584
+ zoomFit: "\u9002\u5E94\u7A97\u53E3",
3585
+ scaling: "\u7F29\u653E",
3586
+ measure: "\u5EA6\u91CF",
3587
+ timeMeasurement: "\u65F6\u95F4\u6D4B\u8DDD",
3588
+ focusMeasureRange: "\u805A\u7126\u5EA6\u91CF\u8303\u56F4",
3589
+ stats: "\u62A5\u544A",
3590
+ displayControl: "\u663E\u793A\u63A7\u5236",
3591
+ taskDisplayUnit: "\u4EFB\u52A1\u663E\u793A\u5355\u4F4D",
3592
+ displayModeTime: "\u65F6\u95F4\uFF08\u81EA\u52A8\uFF09",
3593
+ displayModeCycles: "CPU \u65F6\u949F\u5468\u671F",
3594
+ dependencyDisplay: "\u4F9D\u8D56\u663E\u793A",
3595
+ depModeAll: "\u5168\u90E8\u4F9D\u8D56",
3596
+ depModePredecessors: "\u4EC5\u524D\u9A71",
3597
+ depModeSuccessors: "\u4EC5\u540E\u7EE7",
3598
+ dependencyDepth: "\u4F9D\u8D56\u6DF1\u5EA6",
3599
+ dependencyDepthHint: "\u22121 \u8868\u793A\u4E0D\u9650\u5236\u8DF3\u6570\uFF08\u6BCF\u4FA7\u6700\u591A 10 000 \u6761\uFF09",
3600
+ connectionLevel: "\u4EFB\u52A1\u8FDE\u63A5\u5C42\u7EA7",
3601
+ connectionLevelHelp: "\u8FDE\u63A5\u5C42\u7EA7\uFF1A-1 \u8868\u793A\u4E0D\u9650\u5C42\u6570",
3602
+ helpConnectionLevel: "\u8FDE\u63A5\u5C42\u7EA7\u8BF4\u660E",
3603
+ stepUp: "\u589E\u52A0\u5C42\u7EA7",
3604
+ stepDown: "\u51CF\u5C11\u5C42\u7EA7",
3605
+ shortcuts: "\u5FEB\u6377\u952E\u8BF4\u660E",
3606
+ mouseControl: "\u9F20\u6807\u64CD\u4F5C",
3607
+ keyboardControl: "\u952E\u76D8\u64CD\u4F5C",
3608
+ combinedControl: "\u7EC4\u5408\u64CD\u4F5C",
3609
+ verticalMovement: "\u7EB5\u5411\u79FB\u52A8",
3610
+ singleBoxSelection: "\u5355\u51FB / \u6846\u9009",
3611
+ panLeft: "\u5DE6\u79FB",
3612
+ panRight: "\u53F3\u79FB",
3613
+ dragPan: "\u5E73\u79FB",
3614
+ boxSelect: "\u6846\u9009",
3615
+ alt: "Alt",
3616
+ ctrl: "Ctrl",
3617
+ wheelGlyph: "\u6EDA\u8F6E",
3618
+ mouseGlyph: "\u9F20\u6807",
3619
+ // ── timeline (PARITY-MAP §4) ────────────────────────────────────────
3620
+ noTimeline: "\u65E0\u65F6\u95F4\u7EBF\u4E8B\u4EF6",
3621
+ laneComm: "\u901A\u4FE1",
3622
+ laneCompute: "\u8BA1\u7B97",
3623
+ laneHbm: "\u50A8\u5B58HBM",
3624
+ pin: "\u7F6E\u9876",
3625
+ unpin: "\u53D6\u6D88\u7F6E\u9876",
3626
+ resizeLaneGutter: "\u8C03\u6574\u6CF3\u9053\u6807\u7B7E\u5BBD\u5EA6",
3627
+ resizeSidebar: "\u8C03\u6574\u4FA7\u680F\u5BBD\u5EA6",
3628
+ visibleRangeStart: "\u53EF\u89C1\u8303\u56F4\u8D77\u70B9",
3629
+ visibleRangeEnd: "\u53EF\u89C1\u8303\u56F4\u7EC8\u70B9",
3630
+ showDeps: "\u4EFB\u52A1\u4F9D\u8D56\u8FDE\u7EBF",
3631
+ start: "Start",
3632
+ dur: "Duration",
3633
+ end: "End",
3634
+ name: "Name",
3635
+ kernel: "Kernel",
3636
+ cyclesUnit: "cycles",
3637
+ timelineLoadFailed: "\u65F6\u95F4\u7EBF\u52A0\u8F7D\u5931\u8D25",
3638
+ // ── detail dock (PARITY-MAP §5) ──────────────────────────────────────
3639
+ details: "\u8BE6\u60C5",
3640
+ closePanel: "\u5173\u95ED",
3641
+ expandDock: "\u5C55\u5F00\u8BE6\u60C5",
3642
+ collapseDock: "\u6536\u8D77\u8BE6\u60C5",
3643
+ modeSummary: "\u6458\u8981",
3644
+ modePipe: "PIPE",
3645
+ modeCompute: "\u8BA1\u7B97\u8D1F\u8F7D",
3646
+ modeMemory: "\u5185\u5B58\u8D1F\u8F7D",
3647
+ parameter: "Parameter",
3648
+ relevant: "Relevant",
3649
+ incoming: "Incoming",
3650
+ current: "Current",
3651
+ outgoing: "Outgoing",
3652
+ depsUpstream: "\u4EC5\u5C55\u793A\u524D\u5411\u4F9D\u8D56",
3653
+ depsBoth: "\u5C55\u793A\u524D\u540E\u5411\u4F9D\u8D56",
3654
+ depsDownstream: "\u4EC5\u5C55\u793A\u540E\u5411\u4F9D\u8D56",
3655
+ noDependencies: "\u65E0\u4F9D\u8D56\u6570\u636E",
3656
+ noParameters: "\u65E0\u53C2\u6570",
3657
+ eventNone: "\u5355\u51FB\u65F6\u95F4\u7EBF\u4E8B\u4EF6\u67E5\u770B\u8BE6\u60C5",
3658
+ // ── summary cards / aside (PARITY-MAP §6) ────────────────────────────
3659
+ summary: "\u62A5\u544A\u7EDF\u8BA1",
3660
+ process: "\u8FDB\u7A0B",
3661
+ opTypeLabel: "\u7B97\u5B50\u7C7B\u578B",
3662
+ blocks: "Blocks",
3663
+ more: "\u66F4\u591A",
3664
+ back: "\u8FD4\u56DE",
3665
+ duration: "\u6574\u4F53\u8017\u65F6",
3666
+ blocksPerCores: "{blockDim} Blocks / {coreCount} \u6838",
3667
+ blocksOnly: "{n} Blocks",
3668
+ computePower: "\u7B97\u529B\u60C5\u51B5",
3669
+ aicoreParallel: "AICore \u5E76\u884C\u4F7F\u7528\u7387",
3670
+ parallelBalance: "\u8D1F\u8F7D\u5747\u8861\u5EA6",
3671
+ bandwidthUtil: "\u5E26\u5BBD\u5229\u7528\u7387",
3672
+ bwRead: "\u8BFB",
3673
+ bwWrite: "\u5199",
3674
+ notAvailable: "N/A",
3675
+ freq: "\u9891\u7387",
3676
+ ratedFreq: "\u989D\u5B9A\u9891\u7387",
3677
+ freqUnit: "MHz",
3678
+ computeAnalysis: "\u8BA1\u7B97\u8D1F\u8F7D\u5206\u6790",
3679
+ memoryAnalysis: "\u5185\u5B58\u8D1F\u8F7D\u5206\u6790",
3680
+ pipeOccupancy: "PIPE \u5360\u7528\u7387",
3681
+ pipeDetails: "PIPE \u5360\u7528\u7387",
3682
+ pipeSide: "PIPE \u4FA7",
3683
+ roofline: "Roofline \u74F6\u9888\u5206\u6790",
3684
+ memoryTopology: "\u5185\u5B58\u62D3\u6251",
3685
+ memoryDetails: "\u5185\u5B58\u8D1F\u8F7D",
3686
+ hardwareDetails: "\u786C\u4EF6\u4FE1\u606F\u8BE6\u60C5",
3687
+ hardwareInfoMissing: "\u7F3A\u5C11 hardware info",
3688
+ op: "\u7B97\u5B50",
3689
+ type: "\u7C7B\u578B",
3690
+ iterationsPerCore: "{n} \u6B21\u8FED\u4EE3 / \u6838",
3691
+ iterationsPerCoreRatio: "{blockDim} / {coreCount} \u6B21\u8FED\u4EE3 / \u6838",
3692
+ block: "block",
3693
+ viewAll: "\u67E5\u770B\u5168\u90E8",
3694
+ viewFullCsv: "\u67E5\u770B\u5168\u90E8",
3695
+ fullCsvTitle: "\u5168\u91CF CSV \u89C6\u56FE",
3696
+ fullCsvTruncated: "\u5DF2\u52A0\u8F7D\u524D {n} \u884C\uFF08\u5927\u6587\u4EF6\u6309\u884C\u6570\u622A\u65AD\u5C55\u793A\uFF0C\u5B8C\u6574\u6570\u636E\u89C1\u5F52\u6863 CSV\uFF09\u3002",
3697
+ csvMissingSuffix: "\u672A\u91C7\u96C6",
3698
+ // ── verdict / comparison (判定纪律:只渲染 verdict 字段 + 固定脚注) ──
3699
+ verdictTitle: "\u5224\u5B9A\u4E66",
3700
+ verdictEmpty: "\u672A\u5224\u5B9A",
3701
+ verdictEmptyHint: "\u8BE5\u62A5\u544A\u5C1A\u65E0\u5224\u5B9A\u4E66\u3002Bound \u5224\u5B9A\u53EA\u51FA\u81EA ops-verdict \u89C4\u5219\u5F15\u64CE\u3002",
3702
+ verdictCta: "\u53D1\u8D77\u5224\u5B9A",
3703
+ verdictFooter: "\u5224\u5B9A\u51FA\u81EA ops-verdict \u89C4\u5219\u5F15\u64CE \xB7 Agent \u53EA\u89E3\u91CA\u4E0D\u81EA\u9020",
3704
+ verdictFindings: "\u547D\u4E2D\u89C4\u5219",
3705
+ verdictEvidence: "\u8BC1\u636E",
3706
+ verdictCounter: "\u53CD\u8BC1",
3707
+ verdictSpeedup: "\u9884\u4F30\u52A0\u901F\u6BD4",
3708
+ verdictMechanism: "\u673A\u5236\u8DEF\u7531",
3709
+ verdictSkipped: "\u8DF3\u8FC7\u9879",
3710
+ verdictNext: "\u5EFA\u8BAE\u4E0B\u4E00\u6B65",
3711
+ comparisonTitle: "\u8C03\u4F18\u524D\u540E\u5BF9\u6BD4",
3712
+ comparisonExitOk: "\u53E3\u5F84\u4E00\u81F4\uFF08\u53EF\u6BD4\uFF09",
3713
+ comparisonExitRejected: "\u53E3\u5F84\u4E0D\u4E00\u81F4\u6216\u7CBE\u5EA6\u672A\u8FC7",
3714
+ comparisonSpeedup: "\u52A0\u901F\u6BD4",
3715
+ comparisonConclusion: "\u7ED3\u8BBA",
3716
+ comparisonAction: "\u5904\u7F6E",
3717
+ // ── CANNBot 对等入口 (PARITY-MAP §8 / D-06) ──────────────────────────
3718
+ cannbotAsk: "CANNBot \u5206\u6790",
3719
+ cannbotConfirmTitle: "\u6CE8\u5165\u4F1A\u8BDD\u53D1\u8D77\u5206\u6790",
3720
+ cannbotScope: "\u5206\u6790\u8303\u56F4",
3721
+ cannbotPayload: "\u8F7D\u8377",
3722
+ cannbotBytes: "{n} \u5B57\u8282",
3723
+ cannbotTruncated: "\u8F7D\u8377\u8D85\u8FC7 8KB\uFF0C\u5DF2\u622A\u65AD\uFF1B\u5168\u91CF\u6570\u636E\u8BF7\u7ECF\u5DE5\u5177\u901A\u9053\uFF08ops_collect / ops_import\uFF09\u83B7\u53D6\u3002",
3724
+ cannbotConfirm: "\u786E\u8BA4\u6CE8\u5165",
3725
+ cannbotCancel: "\u53D6\u6D88",
3726
+ cannbotSent: "\u5DF2\u6CE8\u5165\u4F1A\u8BDD\uFF08queue \u6A21\u5F0F\uFF09\uFF0CAgent \u5C06\u5F00\u59CB\u5206\u6790\u3002",
3727
+ cannbotFailed: "\u6CE8\u5165\u5931\u8D25",
3728
+ cannbotQueueNote: "\u7ECF remote.session.prompt\uFF08queue \u6A21\u5F0F\uFF09\u5165\u5217\u5F53\u524D\u4F1A\u8BDD\uFF1B\u786E\u8BA4\u540E\u53D1\u9001\u3002",
3729
+ // ── collection config (PARITY-MAP §9) ────────────────────────────────
3730
+ configTitle: "\u91C7\u96C6\u914D\u7F6E",
3731
+ onboardTitle: "\u4E0A\u677F\u5206\u6790\u5FEB\u901F\u9009\u9879",
3732
+ onboardAlert1: "\u6267\u884C\u4E0A\u677F\u5206\u6790\uFF0C\u9700\u5728\u4EE3\u7801\u4E2D\u4FEE\u6539\u4F7F\u7528 NPU \u7684\u914D\u7F6E\uFF08\u5177\u4F53\u53C2\u8003\u5DE5\u7A0B README\uFF09\u3002",
3733
+ onboardAlert2: "\u82E5\u52FE\u9009\u300CRoofline\u74F6\u9888\u5206\u6790\u300D\u6216\u300CCache\u70ED\u529B\u56FE\u300D\uFF0C\u5C06\u9ED8\u8BA4\u91C7\u96C6\u6240\u6709\u7B97\u5B50\u6027\u80FD\u6307\u6807\u3002",
3734
+ onboardAlert2A5: "\u82E5\u52FE\u9009\u300CRoofline\u74F6\u9888\u5206\u6790\u300D\u6216\u300CPC \u91C7\u6837\u300D\uFF0C\u5C06\u9ED8\u8BA4\u91C7\u96C6\u6240\u6709\u7B97\u5B50\u6027\u80FD\u6307\u6807\u3002",
3735
+ collectContent: "\u91C7\u96C6\u5185\u5BB9",
3736
+ memoryHeatmap: "\u8BA1\u7B97\u5185\u5B58\u70ED\u529B\u56FE\uFF08\u9ED8\u8BA4\u91C7\u96C6\uFF09",
3737
+ memoryHeatmapDesc: "\u9ED8\u8BA4\u91C7\u96C6\uFF1A\u57FA\u7840\u4FE1\u606F\u3001\u8BA1\u7B97\u5DE5\u4F5C\u8D1F\u8F7D\u5206\u6790\u3001\u5185\u5B58\u8D1F\u8F7D\u5206\u6790",
3738
+ defaultOptions: "\u9ED8\u8BA4\u9009\u9879\uFF1A",
3739
+ interCoreLoad: "\u6838\u95F4\u8D1F\u8F7D\u5206\u6790\u56FE",
3740
+ rooflineOpt: "Roofline\u74F6\u9888\u5206\u6790",
3741
+ rooflineTooltip: "\u82E5\u52FE\u9009\u8BE5\u9009\u9879\uFF0C\u5C06\u9ED8\u8BA4\u91C7\u96C6\u6240\u6709\u7B97\u5B50\u6027\u80FD\u6307\u6807",
3742
+ codeHotspot: "\u7B97\u5B50\u4EE3\u7801\u70ED\u70B9\u56FE",
3743
+ cacheHeatmap: "Cache\u70ED\u529B\u56FE",
3744
+ cacheHeatmapTooltip: "\u82E5\u52FE\u9009\u8BE5\u9009\u9879\uFF0C\u5C06\u9ED8\u8BA4\u91C7\u96C6\u6240\u6709\u7B97\u5B50\u6027\u80FD\u6307\u6807",
3745
+ pcSampling: "PC \u91C7\u6837",
3746
+ pcSamplingTooltip: "Ascend 950 / A5\uFF1A\u52FE\u9009\u540E\u9ED8\u8BA4\u91C7\u96C6\u6240\u6709\u7B97\u5B50\u6027\u80FD\u6307\u6807\uFF08\u66FF\u4EE3\u8BE5\u76EE\u6807\u4E0B\u7684 Cache \u70ED\u529B\u56FE\uFF09",
3747
+ operatorMetrics: "\u7B97\u5B50\u6027\u80FD\u6307\u6807",
3748
+ cubeVector: "Cube\u53CAVector\u7C7B\u578B\u6307\u4EE4\u8017\u65F6\u548C\u5360\u6BD4",
3749
+ l2HitRate: "L2 Cache\u547D\u4E2D\u7387",
3750
+ memoryBandwidth: "\u5185\u5B58\u8BFB\u5199\u5E26\u5BBD\u901F\u7387",
3751
+ l0Bandwidth: "L0\u8BFB\u5199\u5E26\u5BBD\u901F\u7387",
3752
+ ubBandwidth: "UB\u8BFB\u5199\u5E26\u5BBD\u901F\u7387",
3753
+ computeMoveRatio: "\u8BA1\u7B97\u5355\u5143\u548C\u642C\u8FD0\u5355\u5143\u8017\u65F6\u5360\u6BD4",
3754
+ resourceConflict: "\u8D44\u6E90\u51B2\u7A81\u5360\u6BD4",
3755
+ simulatorTitle: "\u4EFF\u771F\u5206\u6790\u5FEB\u901F\u9009\u9879",
3756
+ simAlert: "\u6267\u884C\u4EFF\u771F\u8C03\u4F18\uFF0C\u9700\u5728\u7B97\u5B50\u4EE3\u7801\u4E2D\u4FEE\u6539\u4F7F\u7528 Model \u7684\u914D\u7F6E\u3002",
3757
+ instructionPipeline: "\u6307\u4EE4\u6D41\u6C34\u56FE",
3758
+ instructionPipelineDesc: "\u9ED8\u8BA4\u91C7\u96C6\uFF1A\u8BA1\u7B97\u5355\u5143\u548C\u642C\u8FD0\u5355\u5143\u6307\u4EE4\u8017\u65F6\u5360\u6BD4\u6307\u6807",
3759
+ extraOptions: "\u9644\u52A0\u9009\u9879\uFF1A",
3760
+ memoryThroughput: "\u5185\u5B58\u901A\u8DEF\u541E\u5410\u7387\u6CE2\u5F62\u5B50\u56FE",
3761
+ simCodeHotspot: "\u7B97\u5B50\u4EE3\u7801\u70ED\u70B9\u56FE",
3762
+ simCodeHotspotDesc: "\u9ED8\u8BA4\u91C7\u96C6\uFF1A\u8D44\u6E90\u51B2\u7A81\u5360\u6BD4\u6307\u6807\uFF08\u66FF\u4EE3\u6307\u4EE4\u6D41\u6C34\u57FA\u7840\u6307\u6807\uFF09\u3002",
3763
+ simCodeHotspotDescBase: "\u9ED8\u8BA4\u91C7\u96C6\uFF1A\u6307\u4EE4\u6D41\u6C34\u57FA\u7840\u6307\u6807\uFF08\u8BA1\u7B97\u5355\u5143\u548C\u642C\u8FD0\u5355\u5143\u6307\u4EE4\u8017\u65F6\u5360\u6BD4\uFF09\u3002",
3764
+ simResourceConflict: "\u8D44\u6E90\u51B2\u7A81\u5360\u6BD4\u6307\u6807",
3765
+ newToolTitle: "\u6570\u636E\u91C7\u96C6\u914D\u7F6E\uFF08A5 \u65B0\u5DE5\u5177\uFF09",
3766
+ collectionType: "\u91C7\u96C6\u7C7B\u578B",
3767
+ dcOnboard: "\u4E0A\u677F",
3768
+ dcSimulate: "\u4EFF\u771F",
3769
+ dcCommon: "Common",
3770
+ dcMetrics: "Metrics",
3771
+ outputFile: "Output File",
3772
+ forceOverwrite: "Force Overwrite",
3773
+ targetProcesses: "Target Processes",
3774
+ replayMode: "Replay Mode",
3775
+ appReplayBuffer: "Application Replay Buffer",
3776
+ appReplayMode: "Application Replay Mode",
3777
+ commandLine: "Command Line",
3778
+ outputFilePlaceholder: "\u62A5\u544A\u8F93\u51FA\u8DEF\u5F84\uFF08\u9ED8\u8BA4\u843D\u5728 opprof \u76EE\u5F55\uFF09",
3779
+ browse: "\u9009\u62E9",
3780
+ commandLinePlaceholder: "\u5DE5\u5177\u53C2\u6570\uFF08\u6839\u636E\u4E0A\u65B9\u9009\u62E9\u81EA\u52A8\u540C\u6B65\uFF0C\u53EF\u624B\u52A8\u7F16\u8F91\uFF09",
3781
+ dcCancel: "\u53D6\u6D88",
3782
+ dcConfirm: "\u786E\u8BA4",
3783
+ dcName: "Name",
3784
+ dcDescription: "Description",
3785
+ dcNotSupported: "\u6682\u4E0D\u652F\u6301",
3786
+ dcCustomDesc: "\u9ED8\u8BA4\u53C2\u6570",
3787
+ filterPlaceholder: "Enter Filter...",
3788
+ socLabel: "\u76EE\u6807 SoC",
3789
+ socAuto: "\u81EA\u52A8",
3790
+ startCollect: "\u751F\u6210\u91C7\u96C6\u6307\u4EE4",
3791
+ copyAgentPrompt: "\u590D\u5236 Agent \u63D0\u793A\u8BCD",
3792
+ configExecNote: "\u91C7\u96C6\u7531\u4F1A\u8BDD Agent \u7ECF ops_collect \u5DE5\u5177\u6267\u884C\uFF08\u5DE5\u5177\u5BA1\u6279\u94FE\u751F\u6548\uFF09\uFF1B\u672C\u9875\u9009\u9879\u5C06\u968F ops_collect \u7684 options \u53C2\u6570\u4F20\u5165\u5E76\u5199\u5165 state.collection_config\u3002",
3793
+ promptCopied: "\u63D0\u793A\u8BCD\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F"
3794
+ };
3795
+
3796
+ // src/client/i18n/en.ts
3797
+ var en = {
3798
+ // ── workbench shell / navigation ────────────────────────────────────
3799
+ workbenchTitle: "Operator Tuning Workbench",
3800
+ navResults: "Performance Results",
3801
+ navConfig: "Collection Config",
3802
+ backToList: "Back to results",
3803
+ refreshNote: "state.json \xB7 4s polling",
3804
+ noData: "No data",
3805
+ resultsHint: "Run ops_collect / ops_import in the session; the latest collection appears here.",
3806
+ resultsReportMissing: "Report missing from state.reports; return to the list and refresh.",
3807
+ sortTime: "Sort by time",
3808
+ markedOnly: "Marked only",
3809
+ showAll: "Show all",
3810
+ openReport: "Open report",
3811
+ copyPath: "Copy path",
3812
+ pathCopied: "Path copied to clipboard",
3813
+ mark: "Mark",
3814
+ unmark: "Unmark",
3815
+ markLocalNote: "Panel-local mark (state write needs the Host channel, Phase 2)",
3816
+ sourceLabel: "Source",
3817
+ reportTitle: "Profiling Report",
3818
+ baselineLabel: "Baseline",
3819
+ skillEcho: "Tuning skill",
3820
+ // ── report tabs (PARITY-MAP §2) ─────────────────────────────────────
3821
+ reportViews: "Report views",
3822
+ tabOp: "OP",
3823
+ tabTimeline: "Timeline",
3824
+ tabSource: "Source",
3825
+ tabDetail: "Details",
3826
+ tabCache: "Cache",
3827
+ phase2: "Phase 2",
3828
+ opSelectorLabel: "Select operator",
3829
+ sourceEmpty: "Kernel sources are not archived with the report (D-11). Phase 2 extension.",
3830
+ cacheEmpty: "Cache tables (L2Cache / MemoryL0 / MemoryUB) not collected.",
3831
+ cacheTitle: "Cache",
3832
+ // ── toolbar (PARITY-MAP §3) ─────────────────────────────────────────
3833
+ searchPlaceholder: "Search",
3834
+ searchLabel: "Search",
3835
+ searchClear: "Clear search",
3836
+ zoomIn: "Zoom in",
3837
+ zoomOut: "Zoom out",
3838
+ zoomFit: "Fit",
3839
+ scaling: "Scaling",
3840
+ measure: "Measure",
3841
+ timeMeasurement: "Time measure",
3842
+ focusMeasureRange: "Focus measure range",
3843
+ stats: "Report",
3844
+ displayControl: "Display control",
3845
+ taskDisplayUnit: "Task display unit",
3846
+ displayModeTime: "Time (auto)",
3847
+ displayModeCycles: "CPU clocks",
3848
+ dependencyDisplay: "Dependency display",
3849
+ depModeAll: "All dependencies",
3850
+ depModePredecessors: "Predecessors only",
3851
+ depModeSuccessors: "Successors only",
3852
+ dependencyDepth: "Dependency depth",
3853
+ dependencyDepthHint: "\u22121 means unlimited hops (capped at 10 000 links per side)",
3854
+ connectionLevel: "Task connection level",
3855
+ connectionLevelHelp: "Connection level: -1 walks the whole chain",
3856
+ helpConnectionLevel: "Connection level help",
3857
+ stepUp: "Increase level",
3858
+ stepDown: "Decrease level",
3859
+ shortcuts: "Shortcuts",
3860
+ mouseControl: "Mouse control",
3861
+ keyboardControl: "Keyboard control",
3862
+ combinedControl: "Combined control",
3863
+ verticalMovement: "Vertical movement",
3864
+ singleBoxSelection: "Single / box select",
3865
+ panLeft: "Move left",
3866
+ panRight: "Move right",
3867
+ dragPan: "Pan",
3868
+ boxSelect: "Box select",
3869
+ alt: "Alt",
3870
+ ctrl: "Ctrl",
3871
+ wheelGlyph: "wheel",
3872
+ mouseGlyph: "mouse",
3873
+ // ── timeline (PARITY-MAP §4) ────────────────────────────────────────
3874
+ noTimeline: "No timeline events",
3875
+ laneComm: "Comm",
3876
+ laneCompute: "Compute",
3877
+ laneHbm: "HBM storage",
3878
+ pin: "Pin to top",
3879
+ unpin: "Unpin",
3880
+ resizeLaneGutter: "Resize lane gutter",
3881
+ resizeSidebar: "Resize sidebar",
3882
+ visibleRangeStart: "Visible range start",
3883
+ visibleRangeEnd: "Visible range end",
3884
+ showDeps: "Task dependencies",
3885
+ start: "Start",
3886
+ dur: "Duration",
3887
+ end: "End",
3888
+ name: "Name",
3889
+ kernel: "Kernel",
3890
+ cyclesUnit: "cycles",
3891
+ timelineLoadFailed: "Timeline load failed",
3892
+ // ── detail dock (PARITY-MAP §5) ──────────────────────────────────────
3893
+ details: "Details",
3894
+ closePanel: "Close",
3895
+ expandDock: "Expand detail panel",
3896
+ collapseDock: "Collapse detail panel",
3897
+ modeSummary: "Summary",
3898
+ modePipe: "PIPE",
3899
+ modeCompute: "Compute load",
3900
+ modeMemory: "Memory load",
3901
+ parameter: "Parameter",
3902
+ relevant: "Relevant",
3903
+ incoming: "Incoming",
3904
+ current: "Current",
3905
+ outgoing: "Outgoing",
3906
+ depsUpstream: "Upstream dependencies only",
3907
+ depsBoth: "Both directions",
3908
+ depsDownstream: "Downstream dependencies only",
3909
+ noDependencies: "No dependency data",
3910
+ noParameters: "No parameters",
3911
+ eventNone: "Click a timeline event to inspect",
3912
+ // ── summary cards / aside (PARITY-MAP §6) ────────────────────────────
3913
+ summary: "Report statistics",
3914
+ process: "Process",
3915
+ opTypeLabel: "Op type",
3916
+ blocks: "Blocks",
3917
+ more: "More",
3918
+ back: "Back",
3919
+ duration: "Total time",
3920
+ blocksPerCores: "{blockDim} Blocks / {coreCount} cores",
3921
+ blocksOnly: "{n} Blocks",
3922
+ computePower: "Computing power",
3923
+ aicoreParallel: "AICore parallel utilization",
3924
+ parallelBalance: "Load balance",
3925
+ bandwidthUtil: "Bandwidth utilization",
3926
+ bwRead: "Read",
3927
+ bwWrite: "Write",
3928
+ notAvailable: "N/A",
3929
+ freq: "Freq",
3930
+ ratedFreq: "Rated freq",
3931
+ freqUnit: "MHz",
3932
+ computeAnalysis: "Compute load analysis",
3933
+ memoryAnalysis: "Memory load analysis",
3934
+ pipeOccupancy: "PIPE occupancy",
3935
+ pipeDetails: "PIPE occupancy",
3936
+ pipeSide: "PIPE side",
3937
+ roofline: "Roofline bottleneck analysis",
3938
+ memoryTopology: "Memory topology",
3939
+ memoryDetails: "Memory load",
3940
+ hardwareDetails: "Hardware information details",
3941
+ hardwareInfoMissing: "Missing hardware info",
3942
+ op: "Op",
3943
+ type: "Type",
3944
+ iterationsPerCore: "{n} iterations / core",
3945
+ iterationsPerCoreRatio: "{blockDim} / {coreCount} iterations / core",
3946
+ block: "block",
3947
+ viewAll: "View all",
3948
+ viewFullCsv: "View full CSV",
3949
+ fullCsvTitle: "Full CSV view",
3950
+ fullCsvTruncated: "First {n} rows loaded (large files are row-capped for display; see the archived CSV for the full data).",
3951
+ csvMissingSuffix: "not collected",
3952
+ // ── verdict / comparison ────────────────────────────────────────────
3953
+ verdictTitle: "Verdict",
3954
+ verdictEmpty: "No verdict yet",
3955
+ verdictEmptyHint: "This report has no verdict document. Bound verdicts come only from the ops-verdict rule engine.",
3956
+ verdictCta: "Request verdict",
3957
+ verdictFooter: "Verdict issued by the ops-verdict rule engine \xB7 the Agent only explains, never fabricates",
3958
+ verdictFindings: "Hit rules",
3959
+ verdictEvidence: "Evidence",
3960
+ verdictCounter: "Counter-evidence",
3961
+ verdictSpeedup: "Estimated speedup",
3962
+ verdictMechanism: "Mechanism routing",
3963
+ verdictSkipped: "Skipped",
3964
+ verdictNext: "Recommended next",
3965
+ comparisonTitle: "Before/after comparison",
3966
+ comparisonExitOk: "Caliber consistent (comparable)",
3967
+ comparisonExitRejected: "Caliber mismatch or precision gate failed",
3968
+ comparisonSpeedup: "Speedup",
3969
+ comparisonConclusion: "Conclusion",
3970
+ comparisonAction: "Action",
3971
+ // ── CANNBot entry (PARITY-MAP §8 / D-06) ────────────────────────────
3972
+ cannbotAsk: "CANNBot Analysis",
3973
+ cannbotConfirmTitle: "Inject into the session to start the analysis",
3974
+ cannbotScope: "Scope",
3975
+ cannbotPayload: "Payload",
3976
+ cannbotBytes: "{n} bytes",
3977
+ cannbotTruncated: "Payload exceeds 8KB and was truncated; fetch full data via the tool channel (ops_collect / ops_import).",
3978
+ cannbotConfirm: "Confirm injection",
3979
+ cannbotCancel: "Cancel",
3980
+ cannbotSent: "Injected into the session (queue mode); the Agent will start the analysis.",
3981
+ cannbotFailed: "Injection failed",
3982
+ cannbotQueueNote: "Queued into the current session via remote.session.prompt; sent after confirmation.",
3983
+ // ── collection config (PARITY-MAP §9) ────────────────────────────────
3984
+ configTitle: "Collection Config",
3985
+ onboardTitle: "On-board Analysis Quick Options",
3986
+ onboardAlert1: "For on-board analysis, update the NPU config in code (see the project README).",
3987
+ onboardAlert2: 'If "Roofline" or "Cache heatmap" is checked, all operator metrics are collected by default.',
3988
+ onboardAlert2A5: 'If "Roofline" or "PC sampling" is checked, all operator metrics are collected by default.',
3989
+ collectContent: "Collect content",
3990
+ memoryHeatmap: "Memory heatmap (default)",
3991
+ memoryHeatmapDesc: "Default: basic info, compute workload, memory workload",
3992
+ defaultOptions: "Default options:",
3993
+ interCoreLoad: "Inter-core load",
3994
+ rooflineOpt: "Roofline",
3995
+ rooflineTooltip: "Checking this collects all operator performance metrics by default",
3996
+ codeHotspot: "Operator code hotspot",
3997
+ cacheHeatmap: "Cache heatmap",
3998
+ cacheHeatmapTooltip: "Checking this collects all operator performance metrics by default",
3999
+ pcSampling: "PC sampling",
4000
+ pcSamplingTooltip: "Ascend 950 / A5: checking this collects all operator performance metrics by default",
4001
+ operatorMetrics: "Operator metrics",
4002
+ cubeVector: "Cube & Vector instruction time and ratio",
4003
+ l2HitRate: "L2 Cache hit rate",
4004
+ memoryBandwidth: "Memory read/write bandwidth",
4005
+ l0Bandwidth: "L0 read/write bandwidth",
4006
+ ubBandwidth: "UB read/write bandwidth",
4007
+ computeMoveRatio: "Compute & move unit time ratio",
4008
+ resourceConflict: "Resource conflict ratio",
4009
+ simulatorTitle: "Simulation Analysis Quick Options",
4010
+ simAlert: "For simulation tuning, update the Model config in your operator code.",
4011
+ instructionPipeline: "Instruction pipeline",
4012
+ instructionPipelineDesc: "Default: compute & move unit instruction time ratio",
4013
+ extraOptions: "Extra options:",
4014
+ memoryThroughput: "Memory throughput waveform",
4015
+ simCodeHotspot: "Operator code hotspot",
4016
+ simCodeHotspotDesc: "Default: resource conflict ratio (replaces the instruction pipeline).",
4017
+ simCodeHotspotDescBase: "Default: instruction pipeline only (compute & move unit instruction time ratio).",
4018
+ simResourceConflict: "Resource conflict ratio",
4019
+ newToolTitle: "Data Collection Config (A5 new tool)",
4020
+ collectionType: "Collection type",
4021
+ dcOnboard: "On-board",
4022
+ dcSimulate: "Simulate",
4023
+ dcCommon: "Common",
4024
+ dcMetrics: "Metrics",
4025
+ outputFile: "Output File",
4026
+ forceOverwrite: "Force Overwrite",
4027
+ targetProcesses: "Target Processes",
4028
+ replayMode: "Replay Mode",
4029
+ appReplayBuffer: "Application Replay Buffer",
4030
+ appReplayMode: "Application Replay Mode",
4031
+ commandLine: "Command Line",
4032
+ outputFilePlaceholder: "Report output path (default under opprof)",
4033
+ browse: "Browse",
4034
+ commandLinePlaceholder: "Tool arguments (auto-synced from the selections above, editable)",
4035
+ dcCancel: "Cancel",
4036
+ dcConfirm: "Confirm",
4037
+ dcName: "Name",
4038
+ dcDescription: "Description",
4039
+ dcNotSupported: "Not supported yet",
4040
+ dcCustomDesc: "Default parameters",
4041
+ filterPlaceholder: "Enter Filter...",
4042
+ socLabel: "Target SoC",
4043
+ socAuto: "Auto",
4044
+ startCollect: "Generate collect instruction",
4045
+ copyAgentPrompt: "Copy Agent prompt",
4046
+ configExecNote: "Collection runs through the session Agent via the ops_collect tool (tool approval chain applies); these options are passed in ops_collect.options and written to state.collection_config.",
4047
+ promptCopied: "Prompt copied to clipboard"
4048
+ };
4049
+
4050
+ // src/client/i18n/index.ts
4051
+ var WB_LOCALE_NS = "ops-tune-workbench";
4052
+ var dictionaries = { "zh-CN": zh, en };
4053
+ function chainFor(active) {
4054
+ if (active === void 0) return [zh];
4055
+ if (active === "zh-CN" || active.startsWith("zh")) return [zh];
4056
+ if (active === "en") return [en, zh];
4057
+ return [en, zh];
4058
+ }
4059
+ function bindTranslate(active) {
4060
+ const chain = chainFor(active);
4061
+ return (key, params) => {
4062
+ let template;
4063
+ for (const dict of chain) {
4064
+ const hit = dict[key];
4065
+ if (hit !== void 0) {
4066
+ template = hit;
4067
+ break;
4068
+ }
4069
+ }
4070
+ if (template === void 0) return key;
4071
+ if (params === void 0) return template;
4072
+ return template.replace(/\{(\w+)\}/g, (match, name) => name in params ? String(params[name]) : match);
4073
+ };
4074
+ }
4075
+
4076
+ // src/client/index.tsx
4077
+ var import_jsx_runtime10 = require("react/jsx-runtime");
4078
+ var WB_ID = "ops-tune-workbench-plugin";
4079
+ var WB_KIND = "ops-tune-workbench";
4080
+ var STATE_REL = ".ops-tune-workbench/state.json";
4081
+ var REFRESH_MS = 4e3;
4082
+ var inject = ["slots", "sidebarRight", "sidebarRightTabs", "remote", "remote.workspaceFiles", "remote.session", "locale"];
4083
+ function apply(ctx) {
4084
+ ctx.effect(() => {
4085
+ const locale = ctx.get("locale");
4086
+ if (locale !== void 0 && typeof locale.register === "function") {
4087
+ try {
4088
+ locale.register(WB_LOCALE_NS, dictionaries);
4089
+ } catch {
4090
+ }
4091
+ }
4092
+ return () => {
4093
+ };
4094
+ }, "ops-tune-workbench: locale dictionaries");
4095
+ ctx.effect(() => {
4096
+ const dispose = ctx.sidebarRightTabs.register({
4097
+ id: WB_ID,
4098
+ kind: WB_KIND,
4099
+ priority: "builtin",
4100
+ title: () => "\u7B97\u5B50\u8C03\u4F18\u5DE5\u4F5C\u53F0",
4101
+ guide: [{
4102
+ id: "open",
4103
+ order: 60,
4104
+ title: () => "\u7B97\u5B50\u8C03\u4F18\u5DE5\u4F5C\u53F0",
4105
+ description: () => "\u6027\u80FD\u8C03\u4F18\u53EF\u89C6\u5316\u9762\u677F\uFF1A\u7ED3\u679C\u5217\u8868 / \u62A5\u544A\u89C6\u56FE\uFF08\u4E94\u9875\u7B7E + \u8BE6\u60C5 Dock + Roofline + \u5185\u5B58\u62D3\u6251 + \u65F6\u95F4\u7EBF\uFF09/ \u91C7\u96C6\u914D\u7F6E"
4106
+ }]
4107
+ });
4108
+ return () => {
4109
+ dispose();
4110
+ };
4111
+ }, "ops-tune-workbench: tab type");
4112
+ ctx.effect(() => {
4113
+ const dispose = ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register(
4114
+ {
4115
+ name: "sidebar.right.pane.tab",
4116
+ key: WB_ID,
4117
+ inject: (sessionId) => ({
4118
+ load: (signal) => readWorkbenchState(ctx, sessionId, STATE_REL, signal),
4119
+ probe: (signal) => probeStateVersion(ctx, sessionId, signal),
4120
+ /** Bound channel context for the report view's on-demand artifact reads. */
4121
+ clientContext: ctx,
4122
+ sessionId,
4123
+ /** Raw Remote session prompt (queue mode) — the CANNBot entry channel. */
4124
+ promptSession: (text, signal) => promptSession(ctx, sessionId, text, signal)
4125
+ })
4126
+ },
4127
+ WorkbenchTab
4128
+ ));
4129
+ return () => {
4130
+ dispose();
4131
+ };
4132
+ }, "ops-tune-workbench: tab body");
4133
+ }
4134
+ async function probeStateVersion(ctx, sessionId, signal) {
4135
+ const remote = workspaceFilesOf(ctx);
4136
+ if (remote === void 0) return { version: null, missing: true };
4137
+ const result = await statVersion(remote, sessionId, STATE_REL, signal);
4138
+ if (result.status === "ok") return { version: result.version, missing: false };
4139
+ return { version: null, missing: true };
4140
+ }
4141
+ async function promptSession(ctx, sessionId, text, signal) {
4142
+ const remote = ctx.get?.("remote");
4143
+ if (remote === void 0 || remote.session === void 0) return "remote.session \u4E0D\u53EF\u7528\uFF08inject \u9700\u58F0\u660E remote + remote.session\uFF09";
4144
+ const requestId = randomUUID();
4145
+ const result = await remote.session.prompt({
4146
+ requestId,
4147
+ sessionId,
4148
+ mode: "queue",
4149
+ content: [{ type: "text", text }]
4150
+ }, signal);
4151
+ if (!result.ok) return `${result.error.code}: ${result.error.message}`;
4152
+ return null;
4153
+ }
4154
+ function WorkbenchTab(props) {
4155
+ const { useTabInfo, load, probe } = props;
4156
+ const { tab } = useTabInfo();
4157
+ const initialReportId = readReportId(tab.navigation.params);
4158
+ const [result, setResult] = (0, import_react11.useState)({ status: "missing" });
4159
+ const visible = tab.visible;
4160
+ const locale = useWorkbenchLocale(props.clientContext);
4161
+ (0, import_react11.useEffect)(() => {
4162
+ if (!visible) return;
4163
+ let stopped = false;
4164
+ const controller = new AbortController();
4165
+ const abort = () => {
4166
+ stopped = true;
4167
+ controller.abort();
4168
+ };
4169
+ tab.signal.addEventListener("abort", abort, { once: true });
4170
+ let lastVersion = null;
4171
+ const refresh = async () => {
4172
+ const probeResult = await probe(controller.signal);
4173
+ if (stopped) return;
4174
+ if (!probeResult.missing && probeResult.version !== null && probeResult.version === lastVersion) return;
4175
+ lastVersion = probeResult.version;
4176
+ if (probeResult.missing) {
4177
+ setResult({ status: "missing" });
4178
+ return;
4179
+ }
4180
+ const next = await load(controller.signal);
4181
+ if (!stopped) setResult(next);
4182
+ };
4183
+ void refresh();
4184
+ const timer = window.setInterval(() => {
4185
+ void refresh();
4186
+ }, REFRESH_MS);
4187
+ return () => {
4188
+ tab.signal.removeEventListener("abort", abort);
4189
+ window.clearInterval(timer);
4190
+ abort();
4191
+ };
4192
+ }, [visible, load, probe, tab.signal]);
4193
+ if (result.status === "failed") {
4194
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "otwb-empty", "data-testid": "ops-tune-workbench", children: [
4195
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "otwb-empty-title", children: locale("workbenchTitle") }),
4196
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "otwb-empty-hint", children: [
4197
+ "\u72B6\u6001\u8BFB\u53D6\u5931\u8D25\uFF1A",
4198
+ result.message
4199
+ ] })
4200
+ ] });
4201
+ }
4202
+ if (result.status === "missing" || result.state.results.length === 0) {
4203
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "otwb-empty", "data-testid": "ops-tune-workbench", children: [
4204
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "otwb-empty-title", children: locale("workbenchTitle") }),
4205
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "otwb-empty-hint", children: [
4206
+ locale("resultsHint"),
4207
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("br", {}),
4208
+ "\u4F1A\u8BDD\u5185\u8FD0\u884C ops_collect / ops_import / verdict_judge / baseline_set \u540E\uFF0C \u6700\u65B0\u91C7\u96C6\u3001\u5224\u5B9A\u4E66\u4E0E\u5BF9\u6BD4\u7ED3\u679C\u4F1A\u5728\u6B64\u9762\u677F\u6E32\u67D3\u3002"
4209
+ ] })
4210
+ ] });
4211
+ }
4212
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4213
+ WorkbenchPanel,
4214
+ {
4215
+ ctx: props.clientContext,
4216
+ sessionId: props.sessionId,
4217
+ state: result.state,
4218
+ locale,
4219
+ promptSession: props.promptSession,
4220
+ initialReportId
4221
+ }
4222
+ );
4223
+ }
4224
+ function readReportId(params) {
4225
+ if (typeof params !== "object" || params === null) return void 0;
4226
+ const reportId = params.reportId;
4227
+ return typeof reportId === "string" && reportId.length > 0 ? reportId : void 0;
4228
+ }
4229
+ function useWorkbenchLocale(ctx) {
4230
+ const [active, setActive] = (0, import_react11.useState)(() => readActiveLocale(ctx));
4231
+ (0, import_react11.useEffect)(() => {
4232
+ const update = () => setActive(readActiveLocale(ctx));
4233
+ const off = ctx.on?.("locale/change", update);
4234
+ return () => {
4235
+ if (off !== void 0) off();
4236
+ };
4237
+ }, [ctx]);
4238
+ return bindTranslate(active);
4239
+ }
4240
+ function readActiveLocale(ctx) {
4241
+ const locale = ctx.get?.("locale");
4242
+ if (locale !== void 0 && typeof locale.getSnapshot === "function") {
4243
+ try {
4244
+ return locale.getSnapshot().active;
4245
+ } catch {
4246
+ }
4247
+ }
4248
+ return typeof navigator !== "undefined" ? navigator.language : void 0;
4249
+ }
4250
+
4251
+ return module.exports;
4252
+ }
4253
+ });