agentworkshop 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (895) hide show
  1. package/.AgentWorkShop/prompts/host-tools.json +631 -0
  2. package/.AgentWorkShop/prompts/lead-supervise.md +42 -0
  3. package/.AgentWorkShop/prompts/mode-goal.md +26 -0
  4. package/.AgentWorkShop/prompts/mode-loop.md +10 -0
  5. package/.AgentWorkShop/prompts/mode-pipeline.md +16 -0
  6. package/.AgentWorkShop/prompts/peer-lead-roster.md +3 -0
  7. package/.AgentWorkShop/prompts/peer-message.md +14 -0
  8. package/.AgentWorkShop/prompts/peer-reply-optional.md +2 -0
  9. package/.AgentWorkShop/prompts/peer-reply-required.md +8 -0
  10. package/.AgentWorkShop/prompts/scenario-default.md +11 -0
  11. package/.AgentWorkShop/prompts/system-manual.md +18 -0
  12. package/.AgentWorkShop/prompts/team-roster.md +9 -0
  13. package/.AgentWorkShop/prompts/worker-workflow.md +19 -0
  14. package/.env.example +34 -0
  15. package/README-zh.md +424 -0
  16. package/README.md +424 -0
  17. package/app/.mimosa/hook-state/sess_05189059-e42d-4188-8911-4f7baabe009b.json +1 -0
  18. package/app/.mimosa/hook-state/sess_05189059-e42d-4188-8911-4f7baabe009b.mtg0vz68-49792-75b0709555.baseline/1459e6269990a5d229b9228c8b2f62ffaa7d350068380ae8b875fee0db45cca6.source +180 -0
  19. package/app/.mimosa/hook-state/sess_05189059-e42d-4188-8911-4f7baabe009b.mtg0vz68-49792-75b0709555.baseline/c2991e909c70c5c12b90a407c7adcf63384c51668a2b334e6e047fa675c268fe.source +305 -0
  20. package/app/.mimosa/hook-state/sess_05189059-e42d-4188-8911-4f7baabe009b.mtg0vz68-49792-75b0709555.baseline/e09129039d1e27e3fac24becc9bdafd2d1a7c8049bff24173df007be865f0165.source +264 -0
  21. package/app/.mimosa/hook-status/sess_05189059-e42d-4188-8911-4f7baabe009b-28ef7c75ea.json +14 -0
  22. package/app/app.vue +139 -0
  23. package/app/assets/css/main.css +1257 -0
  24. package/app/components/AppHeader.vue +675 -0
  25. package/app/components/AppSidebar.vue +286 -0
  26. package/app/components/AwChart.vue +44 -0
  27. package/app/components/AwLogo.vue +75 -0
  28. package/app/components/workshop/A2aRpcDebugger.vue +237 -0
  29. package/app/components/workshop/AgentInspectorDrawer.vue +424 -0
  30. package/app/components/workshop/AgentLanesView.vue +947 -0
  31. package/app/components/workshop/ArtifactCard.vue +120 -0
  32. package/app/components/workshop/AssetLibrary.vue +450 -0
  33. package/app/components/workshop/ChannelSessionList.vue +593 -0
  34. package/app/components/workshop/CommandPalette.vue +270 -0
  35. package/app/components/workshop/Composer.vue +731 -0
  36. package/app/components/workshop/DeviceTwinPanel.vue +607 -0
  37. package/app/components/workshop/EventBlock.vue +602 -0
  38. package/app/components/workshop/FileSelectorModal.vue +188 -0
  39. package/app/components/workshop/InspectorPanel.vue +265 -0
  40. package/app/components/workshop/LiveAgentBar.vue +362 -0
  41. package/app/components/workshop/MemoryPanel.vue +364 -0
  42. package/app/components/workshop/MentionHoverCard.vue +155 -0
  43. package/app/components/workshop/MultiChannelView.vue +189 -0
  44. package/app/components/workshop/PaneSplitter.vue +142 -0
  45. package/app/components/workshop/StatsPanel.vue +168 -0
  46. package/app/components/workshop/TaskBoardView.vue +636 -0
  47. package/app/components/workshop/TaskInspectorDrawer.vue +390 -0
  48. package/app/components/workshop/TranscriptTimeline.vue +545 -0
  49. package/app/components/workshop/blocks/ClusterArtifact.vue +77 -0
  50. package/app/components/workshop/blocks/ClusterError.vue +85 -0
  51. package/app/components/workshop/blocks/ClusterLife.vue +87 -0
  52. package/app/components/workshop/blocks/ClusterMember.vue +93 -0
  53. package/app/components/workshop/blocks/ClusterMemory.vue +84 -0
  54. package/app/components/workshop/blocks/ClusterOther.vue +67 -0
  55. package/app/components/workshop/blocks/ClusterRoute.vue +359 -0
  56. package/app/components/workshop/blocks/ClusterStatus.vue +147 -0
  57. package/app/components/workshop/blocks/ClusterStream.vue +325 -0
  58. package/app/components/workshop/blocks/ClusterTask.vue +151 -0
  59. package/app/components/workshop/blocks/ClusterTool.vue +158 -0
  60. package/app/components/workshop/lanes/LaneBlocks.vue +149 -0
  61. package/app/components/workshop/terminal/OmpTerminalPanel.vue +1010 -0
  62. package/app/components/workshop/town/ModelPreview3D.vue +276 -0
  63. package/app/components/workshop/town/TownScene.ts +1355 -0
  64. package/app/components/workshop/town/TownScene3D.ts +4533 -0
  65. package/app/components/workshop/town/TownView.vue +7071 -0
  66. package/app/composables/useCodeCopy.ts +30 -0
  67. package/app/composables/useHttp.ts +8 -0
  68. package/app/composables/useRouteMeta.ts +50 -0
  69. package/app/composables/useSiteConfig.ts +21 -0
  70. package/app/composables/workshop/apiClient.ts +95 -0
  71. package/app/composables/workshop/useCharacterAssets.ts +292 -0
  72. package/app/composables/workshop/useClusteredBlocks.ts +150 -0
  73. package/app/composables/workshop/useComposerBus.ts +16 -0
  74. package/app/composables/workshop/useDaqStream.ts +379 -0
  75. package/app/composables/workshop/useDcwStream.ts +300 -0
  76. package/app/composables/workshop/useDeviceTwins.ts +147 -0
  77. package/app/composables/workshop/useEventBlocks.ts +662 -0
  78. package/app/composables/workshop/useLocalTime.ts +33 -0
  79. package/app/composables/workshop/useOpsLog.ts +164 -0
  80. package/app/composables/workshop/useSceneLayouts.ts +166 -0
  81. package/app/composables/workshop/useTownBus.ts +47 -0
  82. package/app/composables/workshop/useVisibleInterval.ts +42 -0
  83. package/app/composables/workshop/useWorkshopApi.ts +224 -0
  84. package/app/composables/workshop/useWorkshopWs.ts +113 -0
  85. package/app/config/index.ts +66 -0
  86. package/app/config/schema.ts +68 -0
  87. package/app/error.vue +76 -0
  88. package/app/layouts/default.vue +100 -0
  89. package/app/layouts/town.vue +16 -0
  90. package/app/pages/daq/[id].vue +796 -0
  91. package/app/pages/daq/index.vue +2577 -0
  92. package/app/pages/dcw/[id].vue +2259 -0
  93. package/app/pages/dcw/index.vue +1079 -0
  94. package/app/pages/index.vue +830 -0
  95. package/app/pages/logs/index.vue +621 -0
  96. package/app/pages/monitor.vue +665 -0
  97. package/app/pages/settings.vue +757 -0
  98. package/app/pages/tokens.vue +610 -0
  99. package/app/pages/town.vue +145 -0
  100. package/app/pages/users.vue +257 -0
  101. package/app/pages/workshop/agents.vue +382 -0
  102. package/app/pages/workshop/channel-templates.vue +456 -0
  103. package/app/pages/workshop/index.vue +604 -0
  104. package/app/pages/workshop/teams.vue +448 -0
  105. package/app/pages/workshop/w/[wsId].vue +516 -0
  106. package/app/plugins/http.ts +71 -0
  107. package/app/plugins/locale-restore.client.ts +13 -0
  108. package/app/plugins/runtime-config.client.ts +65 -0
  109. package/app/plugins/session-restore.client.ts +24 -0
  110. package/app/plugins/theme.client.ts +15 -0
  111. package/app/stores/app.ts +32 -0
  112. package/app/stores/route-trail.ts +45 -0
  113. package/app/stores/runtime-config.ts +212 -0
  114. package/app/stores/workshop/connection.ts +184 -0
  115. package/app/stores/workshop/entities.ts +350 -0
  116. package/app/stores/workshop/events.ts +292 -0
  117. package/app/stores/workshop/user.ts +176 -0
  118. package/app/stores/workshop/workspaces.ts +133 -0
  119. package/bin/aw.mjs +13 -0
  120. package/cli/aw.mjs +184 -0
  121. package/cli/commands/build.mjs +33 -0
  122. package/cli/commands/config.mjs +157 -0
  123. package/cli/commands/dev.mjs +74 -0
  124. package/cli/commands/doctor.mjs +120 -0
  125. package/cli/commands/home.mjs +75 -0
  126. package/cli/commands/init.mjs +150 -0
  127. package/cli/commands/register.mjs +123 -0
  128. package/cli/commands/start.mjs +106 -0
  129. package/cli/commands/status.mjs +67 -0
  130. package/cli/commands/update.mjs +116 -0
  131. package/cli/commands/version.mjs +32 -0
  132. package/cli/core/args.mjs +95 -0
  133. package/cli/core/context.mjs +264 -0
  134. package/cli/core/errors.mjs +18 -0
  135. package/cli/core/help.mjs +68 -0
  136. package/cli/core/logger.mjs +40 -0
  137. package/cli/core/registry.mjs +111 -0
  138. package/config.yml +70 -0
  139. package/docker-compose.yml +34 -0
  140. package/docs/cli.md +220 -0
  141. package/i18n/dicts/_en.json +1097 -0
  142. package/i18n/dicts/_en2.json +300 -0
  143. package/i18n/dicts/_en3.json +102 -0
  144. package/i18n/dicts/_en4.json +115 -0
  145. package/i18n/dicts/_en5.json +33 -0
  146. package/i18n/dicts/a2aRpcDebugger.json +10 -0
  147. package/i18n/dicts/agentInspectorDrawer.json +25 -0
  148. package/i18n/dicts/agentLanesView.json +57 -0
  149. package/i18n/dicts/agents.json +27 -0
  150. package/i18n/dicts/appHeader.json +7 -0
  151. package/i18n/dicts/appSidebar.json +1 -0
  152. package/i18n/dicts/artifactCard.json +6 -0
  153. package/i18n/dicts/assetLibrary.json +18 -0
  154. package/i18n/dicts/awChart.json +1 -0
  155. package/i18n/dicts/awLogo.json +1 -0
  156. package/i18n/dicts/channelSessionList.json +45 -0
  157. package/i18n/dicts/clusterArtifact.json +4 -0
  158. package/i18n/dicts/clusterError.json +3 -0
  159. package/i18n/dicts/clusterLife.json +5 -0
  160. package/i18n/dicts/clusterMember.json +3 -0
  161. package/i18n/dicts/clusterMemory.json +3 -0
  162. package/i18n/dicts/clusterOther.json +3 -0
  163. package/i18n/dicts/clusterRoute.json +15 -0
  164. package/i18n/dicts/clusterStatus.json +3 -0
  165. package/i18n/dicts/clusterStream.json +3 -0
  166. package/i18n/dicts/clusterTask.json +10 -0
  167. package/i18n/dicts/clusterTool.json +3 -0
  168. package/i18n/dicts/commandPalette.json +16 -0
  169. package/i18n/dicts/composer.json +38 -0
  170. package/i18n/dicts/ctpl.json +32 -0
  171. package/i18n/dicts/daq.json +157 -0
  172. package/i18n/dicts/daqDetail.json +55 -0
  173. package/i18n/dicts/dcw.json +68 -0
  174. package/i18n/dicts/dcwDetail.json +199 -0
  175. package/i18n/dicts/deviceTwinPanel.json +12 -0
  176. package/i18n/dicts/eventBlock.json +8 -0
  177. package/i18n/dicts/fileSelectorModal.json +6 -0
  178. package/i18n/dicts/inspectorPanel.json +12 -0
  179. package/i18n/dicts/laneBlocks.json +6 -0
  180. package/i18n/dicts/liveAgentBar.json +8 -0
  181. package/i18n/dicts/logs.json +39 -0
  182. package/i18n/dicts/memoryPanel.json +20 -0
  183. package/i18n/dicts/mentionHoverCard.json +4 -0
  184. package/i18n/dicts/modelPreview3D.json +1 -0
  185. package/i18n/dicts/monitor.json +5 -0
  186. package/i18n/dicts/multiChannelView.json +13 -0
  187. package/i18n/dicts/ompTerminalPanel.json +7 -0
  188. package/i18n/dicts/paneSplitter.json +3 -0
  189. package/i18n/dicts/settings.json +1 -0
  190. package/i18n/dicts/statsPanel.json +6 -0
  191. package/i18n/dicts/taskBoardView.json +22 -0
  192. package/i18n/dicts/taskInspectorDrawer.json +19 -0
  193. package/i18n/dicts/teams.json +33 -0
  194. package/i18n/dicts/tokens.json +30 -0
  195. package/i18n/dicts/town.json +7 -0
  196. package/i18n/dicts/townView.json +209 -0
  197. package/i18n/dicts/transcriptTimeline.json +31 -0
  198. package/i18n/dicts/users.json +15 -0
  199. package/i18n/dicts/wsHome.json +33 -0
  200. package/i18n/dicts/wsView.json +15 -0
  201. package/i18n/locales/en.ts +1737 -0
  202. package/i18n/locales/zh-CN.ts +1736 -0
  203. package/nuxt.config.ts +209 -0
  204. package/package.json +123 -0
  205. package/public/assets/game/character/bot.png +0 -0
  206. package/public/assets/game/character/device-3d.glb +0 -0
  207. package/public/assets/game/character/hero-3d.glb +0 -0
  208. package/public/assets/game/character/hero-anime-1.glb +0 -0
  209. package/public/assets/game/character/hero-anime-2.glb +0 -0
  210. package/public/assets/game/character/hero-anime-3.glb +0 -0
  211. package/public/assets/game/character/hero-anime-4.glb +0 -0
  212. package/public/assets/game/character/knight.png +0 -0
  213. package/public/assets/game/character/mage.png +0 -0
  214. package/public/assets/game/coin.png +0 -0
  215. package/public/assets/game/devices/agv.glb +0 -0
  216. package/public/assets/game/devices/caster.glb +0 -0
  217. package/public/assets/game/devices/device-console.glb +0 -0
  218. package/public/assets/game/devices/device-robot-arm.glb +0 -0
  219. package/public/assets/game/devices/device-scanner.glb +0 -0
  220. package/public/assets/game/devices/extruder.glb +0 -0
  221. package/public/assets/game/devices/mdo.glb +0 -0
  222. package/public/assets/game/devices/power-cabinet.glb +0 -0
  223. package/public/assets/game/devices/pump.glb +0 -0
  224. package/public/assets/game/devices/tdo.glb +0 -0
  225. package/public/assets/game/devices/thickness-scanner.glb +0 -0
  226. package/public/assets/game/devices/winder.glb +0 -0
  227. package/public/assets/game/npc/boltnu-sheet.png +0 -0
  228. package/public/assets/game/npc/fluttaflap-sheet.png +0 -0
  229. package/public/assets/game/npc/rockitten-sheet.png +0 -0
  230. package/public/assets/game/npc/tux-sheet.png +0 -0
  231. package/public/assets/game/player/misa-back-walk.000.png +0 -0
  232. package/public/assets/game/player/misa-back-walk.001.png +0 -0
  233. package/public/assets/game/player/misa-back-walk.002.png +0 -0
  234. package/public/assets/game/player/misa-back-walk.003.png +0 -0
  235. package/public/assets/game/player/misa-back.png +0 -0
  236. package/public/assets/game/player/misa-front-walk.000.png +0 -0
  237. package/public/assets/game/player/misa-front-walk.001.png +0 -0
  238. package/public/assets/game/player/misa-front-walk.002.png +0 -0
  239. package/public/assets/game/player/misa-front-walk.003.png +0 -0
  240. package/public/assets/game/player/misa-front.png +0 -0
  241. package/public/assets/game/player/misa-left-walk.000.png +0 -0
  242. package/public/assets/game/player/misa-left-walk.001.png +0 -0
  243. package/public/assets/game/player/misa-left-walk.002.png +0 -0
  244. package/public/assets/game/player/misa-left-walk.003.png +0 -0
  245. package/public/assets/game/player/misa-left.png +0 -0
  246. package/public/assets/game/player/misa-right-walk.000.png +0 -0
  247. package/public/assets/game/player/misa-right-walk.001.png +0 -0
  248. package/public/assets/game/player/misa-right-walk.002.png +0 -0
  249. package/public/assets/game/player/misa-right-walk.003.png +0 -0
  250. package/public/assets/game/player/misa-right.png +0 -0
  251. package/public/assets/game/player-sheet.png +0 -0
  252. package/public/assets/game/tuxemon-town.json +1 -0
  253. package/public/assets/game/tuxmon-sample-32px-extruded.png +0 -0
  254. package/public/assets/game/wuwa/scene-map.png +0 -0
  255. package/public/assets/game/wuwa/world-far.png +0 -0
  256. package/public/assets/game/wuwa/world-middle.png +0 -0
  257. package/public/assets/game/wuwa/world-near.png +0 -0
  258. package/public/assets/game/wuwa/wu-aura.png +0 -0
  259. package/public/assets/game/wuwa/wu-lead.png +0 -0
  260. package/public/assets/game/wuwa/wu-ring.png +0 -0
  261. package/public/assets/game/wuwa/wu-slash.png +0 -0
  262. package/public/assets/game/wuwa/wu-worker-0.png +0 -0
  263. package/public/assets/game/wuwa/wu-worker-1.png +0 -0
  264. package/public/assets/game/wuwa/wu-worker-2.png +0 -0
  265. package/public/favicon.svg +14 -0
  266. package/public/scene/background/cyber-town-background.svg +110 -0
  267. package/scripts/_daq-realtime-probe.mjs +68 -0
  268. package/scripts/_daq-warn-probe.mjs +50 -0
  269. package/scripts/_dbg-agent-tools-audit.mjs +154 -0
  270. package/scripts/_dbg-agent-workflow-audit.mjs +177 -0
  271. package/scripts/_dbg-anim-verify.mjs +81 -0
  272. package/scripts/_dbg-audit-all-pages.mjs +43 -0
  273. package/scripts/_dbg-autoframe.mjs +103 -0
  274. package/scripts/_dbg-beacon-final.mjs +181 -0
  275. package/scripts/_dbg-bind-bidi-e2e.mjs +239 -0
  276. package/scripts/_dbg-callout-near.mjs +108 -0
  277. package/scripts/_dbg-chan-focus.mjs +49 -0
  278. package/scripts/_dbg-cleanup.mjs +31 -0
  279. package/scripts/_dbg-closedloop-ui-shot.mjs +61 -0
  280. package/scripts/_dbg-console-audit.mjs +61 -0
  281. package/scripts/_dbg-console-probe.mjs +23 -0
  282. package/scripts/_dbg-context-inject-audit.mjs +231 -0
  283. package/scripts/_dbg-crash-recovery.mjs +143 -0
  284. package/scripts/_dbg-crop-shot.mjs +16 -0
  285. package/scripts/_dbg-daq-acc-probe.mjs +24 -0
  286. package/scripts/_dbg-daq-attrib.mjs +34 -0
  287. package/scripts/_dbg-daq-bind-audit.mjs +119 -0
  288. package/scripts/_dbg-daq-cadence.mjs +104 -0
  289. package/scripts/_dbg-daq-debug.mjs +22 -0
  290. package/scripts/_dbg-daq-diag.mjs +33 -0
  291. package/scripts/_dbg-daq-e2e.mjs +171 -0
  292. package/scripts/_dbg-daq-film.mjs +136 -0
  293. package/scripts/_dbg-daq-filter-e2e.mjs +109 -0
  294. package/scripts/_dbg-daq-filter-e2e2.mjs +62 -0
  295. package/scripts/_dbg-daq-final-regression.mjs +78 -0
  296. package/scripts/_dbg-daq-full-check.mjs +179 -0
  297. package/scripts/_dbg-daq-line-note-verify.mjs +61 -0
  298. package/scripts/_dbg-daq-linebanner-audit.mjs +107 -0
  299. package/scripts/_dbg-daq-pages-shot.mjs +37 -0
  300. package/scripts/_dbg-daq-perf.mjs +61 -0
  301. package/scripts/_dbg-daq-probe.mjs +27 -0
  302. package/scripts/_dbg-daq-recipe-e2e.mjs +30 -0
  303. package/scripts/_dbg-daq-redesign-shot.mjs +88 -0
  304. package/scripts/_dbg-daq-rows-probe.mjs +26 -0
  305. package/scripts/_dbg-daq-rowstate-e2e.mjs +152 -0
  306. package/scripts/_dbg-daq-rowstate-shot.mjs +45 -0
  307. package/scripts/_dbg-daq-rowstate-shot2.mjs +38 -0
  308. package/scripts/_dbg-daq-tpl.mjs +144 -0
  309. package/scripts/_dbg-dash-console-check.mjs +49 -0
  310. package/scripts/_dbg-dash-deep-probe.mjs +61 -0
  311. package/scripts/_dbg-dash-hitpoint.mjs +44 -0
  312. package/scripts/_dbg-dash-live-shot.mjs +36 -0
  313. package/scripts/_dbg-dash-shot.mjs +21 -0
  314. package/scripts/_dbg-dashboard-shot.mjs +35 -0
  315. package/scripts/_dbg-dcw-audit.mjs +126 -0
  316. package/scripts/_dbg-dcw-card-e2e.mjs +108 -0
  317. package/scripts/_dbg-dcw-channel-ui.mjs +144 -0
  318. package/scripts/_dbg-dcw-examples.mjs +81 -0
  319. package/scripts/_dbg-dcw-light-shot.mjs +19 -0
  320. package/scripts/_dbg-dcw-line-audit.mjs +107 -0
  321. package/scripts/_dbg-dcw-pause-tpl-audit.mjs +161 -0
  322. package/scripts/_dbg-dcw-pause-ui.mjs +175 -0
  323. package/scripts/_dbg-dcw-scene.mjs +72 -0
  324. package/scripts/_dbg-dcw-shot2.mjs +14 -0
  325. package/scripts/_dbg-dcw-twin-e2e.mjs +192 -0
  326. package/scripts/_dbg-dt-interact.mjs +193 -0
  327. package/scripts/_dbg-error-probe.mjs +54 -0
  328. package/scripts/_dbg-filter-light-verify.mjs +54 -0
  329. package/scripts/_dbg-final-acceptance-2.mjs +72 -0
  330. package/scripts/_dbg-final-acceptance.mjs +192 -0
  331. package/scripts/_dbg-final-agent-e2e.mjs +151 -0
  332. package/scripts/_dbg-final-full-e2e.mjs +218 -0
  333. package/scripts/_dbg-final-line-e2e.mjs +173 -0
  334. package/scripts/_dbg-final-t6-hitl.mjs +38 -0
  335. package/scripts/_dbg-focus-range.mjs +115 -0
  336. package/scripts/_dbg-fresh-scene-e2e.mjs +125 -0
  337. package/scripts/_dbg-full-feature-e2e.mjs +219 -0
  338. package/scripts/_dbg-fullchain-e2e.mjs +178 -0
  339. package/scripts/_dbg-gate-debug.mjs +37 -0
  340. package/scripts/_dbg-gif-encode.mjs +111 -0
  341. package/scripts/_dbg-gizmo.mjs +95 -0
  342. package/scripts/_dbg-glass-probe.mjs +57 -0
  343. package/scripts/_dbg-glass-shot.mjs +50 -0
  344. package/scripts/_dbg-glass-sweep.mjs +25 -0
  345. package/scripts/_dbg-glb-probe.mjs +43 -0
  346. package/scripts/_dbg-hardening-probe.mjs +90 -0
  347. package/scripts/_dbg-hero-btn-probe.mjs +21 -0
  348. package/scripts/_dbg-hidden-check.mjs +21 -0
  349. package/scripts/_dbg-hitl-timeout-e2e.mjs +93 -0
  350. package/scripts/_dbg-hmi-audit.mjs +164 -0
  351. package/scripts/_dbg-home-repro.mjs +24 -0
  352. package/scripts/_dbg-home-stack.mjs +13 -0
  353. package/scripts/_dbg-huashu-survey.mjs +31 -0
  354. package/scripts/_dbg-huashu-survey2.mjs +35 -0
  355. package/scripts/_dbg-huashu-verify.mjs +25 -0
  356. package/scripts/_dbg-huashu-verify2.mjs +26 -0
  357. package/scripts/_dbg-hydration-detail.mjs +18 -0
  358. package/scripts/_dbg-i18n-debug.mjs +27 -0
  359. package/scripts/_dbg-i18n-debug2.mjs +26 -0
  360. package/scripts/_dbg-i18n-final.mjs +42 -0
  361. package/scripts/_dbg-i18n-scan.mjs +38 -0
  362. package/scripts/_dbg-infra-lifecycle.mjs +44 -0
  363. package/scripts/_dbg-line-aura-shots.mjs +54 -0
  364. package/scripts/_dbg-line-control-e2e.mjs +120 -0
  365. package/scripts/_dbg-line-gate-audit.mjs +108 -0
  366. package/scripts/_dbg-line-live-e2e.mjs +144 -0
  367. package/scripts/_dbg-line-scenario-e2e.mjs +256 -0
  368. package/scripts/_dbg-minimal-omp.mjs +77 -0
  369. package/scripts/_dbg-mode-nav.mjs +173 -0
  370. package/scripts/_dbg-mqtt-chain-e2e.mjs +90 -0
  371. package/scripts/_dbg-nav-rpg.mjs +84 -0
  372. package/scripts/_dbg-nav-shot.mjs +28 -0
  373. package/scripts/_dbg-nav.mjs +27 -0
  374. package/scripts/_dbg-opslog-e2e.mjs +121 -0
  375. package/scripts/_dbg-opslog-shots.mjs +62 -0
  376. package/scripts/_dbg-opt-team-e2e.mjs +133 -0
  377. package/scripts/_dbg-optimization-e2e.mjs +94 -0
  378. package/scripts/_dbg-orbit.mjs +58 -0
  379. package/scripts/_dbg-panesplitter-fix-check.mjs +62 -0
  380. package/scripts/_dbg-phase-e3-monitor.mjs +25 -0
  381. package/scripts/_dbg-pop-clamp-verify.mjs +77 -0
  382. package/scripts/_dbg-preset-e2e.mjs +116 -0
  383. package/scripts/_dbg-preview-console.mjs +28 -0
  384. package/scripts/_dbg-preview-minrepro.mjs +156 -0
  385. package/scripts/_dbg-preview-pixels.mjs +93 -0
  386. package/scripts/_dbg-quality-pages-e2e.mjs +84 -0
  387. package/scripts/_dbg-query-cancel.ts +12 -0
  388. package/scripts/_dbg-rail-tree-e2e.mjs +183 -0
  389. package/scripts/_dbg-real-driver-e2e.mjs +55 -0
  390. package/scripts/_dbg-redesign-shots.mjs +34 -0
  391. package/scripts/_dbg-restart-persist.mjs +41 -0
  392. package/scripts/_dbg-rollback-mgr-audit.mjs +169 -0
  393. package/scripts/_dbg-rpg.mjs +71 -0
  394. package/scripts/_dbg-sc001-dispatch.mjs +63 -0
  395. package/scripts/_dbg-scenario-final-shot.mjs +24 -0
  396. package/scripts/_dbg-scenario-phase-e2.mjs +75 -0
  397. package/scripts/_dbg-scenario-phase-e3.mjs +96 -0
  398. package/scripts/_dbg-scenario-phase1.mjs +90 -0
  399. package/scripts/_dbg-scenario-phase2.mjs +105 -0
  400. package/scripts/_dbg-scenario-phase3-monitor.mjs +48 -0
  401. package/scripts/_dbg-scenario-phase3.mjs +111 -0
  402. package/scripts/_dbg-scene-polish-shot.mjs +118 -0
  403. package/scripts/_dbg-selring-probe.mjs +47 -0
  404. package/scripts/_dbg-sideinfo.mjs +101 -0
  405. package/scripts/_dbg-splitter.mjs +88 -0
  406. package/scripts/_dbg-team-repro.mjs +76 -0
  407. package/scripts/_dbg-town-alarm-e2e.mjs +40 -0
  408. package/scripts/_dbg-town-beauty-shots.mjs +54 -0
  409. package/scripts/_dbg-town-daq-shot.mjs +26 -0
  410. package/scripts/_dbg-town-full-e2e.mjs +104 -0
  411. package/scripts/_dbg-town-gate.mjs +44 -0
  412. package/scripts/_dbg-town-ins-probe.mjs +37 -0
  413. package/scripts/_dbg-town-perf.mjs +50 -0
  414. package/scripts/_dbg-town-polish-shots.mjs +82 -0
  415. package/scripts/_dbg-town-realtime.mjs +42 -0
  416. package/scripts/_dbg-town-shot3.mjs +17 -0
  417. package/scripts/_dbg-town-truth.mjs +37 -0
  418. package/scripts/_dbg-town-truth2.mjs +27 -0
  419. package/scripts/_dbg-transform-audit.mjs +98 -0
  420. package/scripts/_dbg-trend-expand-test.mjs +38 -0
  421. package/scripts/_dbg-twindaq-focus.mjs +81 -0
  422. package/scripts/_dbg-ui-polish-shot.mjs +43 -0
  423. package/scripts/_dbg-ui-polish-verify1.mjs +92 -0
  424. package/scripts/_dbg-ui-polish-verify2.mjs +50 -0
  425. package/scripts/_dbg-ui-polish-verify3.mjs +51 -0
  426. package/scripts/_dbg-ui-polish-verify4.mjs +50 -0
  427. package/scripts/_dbg-ui-shot.mjs +26 -0
  428. package/scripts/_dbg-unify-shot.mjs +43 -0
  429. package/scripts/_dbg-uplink-probe.mjs +36 -0
  430. package/scripts/_dbg-wizard-preview-shot.mjs +58 -0
  431. package/scripts/_dbg-wizard-ui-e2e.mjs +93 -0
  432. package/scripts/_dbg-ws-probe.mjs +86 -0
  433. package/scripts/_dbg-ws-shot2.mjs +23 -0
  434. package/scripts/_dbg-wsdot-sanity.mjs +25 -0
  435. package/scripts/_dev-stack.mjs +46 -0
  436. package/scripts/_http-driver-probe.mjs +30 -0
  437. package/scripts/_lib-dcw-line.mjs +24 -0
  438. package/scripts/_mqtt-driver-probe.mjs +30 -0
  439. package/scripts/_newdriver-rest-probe.mjs +100 -0
  440. package/scripts/_node-state-probe.mjs +5 -0
  441. package/scripts/_opcua-driver-probe.mjs +31 -0
  442. package/scripts/_rtu-debug.mjs +41 -0
  443. package/scripts/_rtu-direct.mjs +6 -0
  444. package/scripts/_rtu-driver-probe.mjs +53 -0
  445. package/scripts/_rtu-mini-slave.mjs +63 -0
  446. package/scripts/_townview_style.css +1185 -0
  447. package/scripts/_townview_template.html +1215 -0
  448. package/scripts/_wizard-shots.mjs +90 -0
  449. package/scripts/acceptance-suite.py +207 -0
  450. package/scripts/api-live-e2e.mjs +467 -0
  451. package/scripts/build-anime-models.mjs +339 -0
  452. package/scripts/build-character-models.mjs +175 -0
  453. package/scripts/build-film-devices.mjs +227 -0
  454. package/scripts/build-model-3d.mjs +173 -0
  455. package/scripts/build-player-sheet.mjs +58 -0
  456. package/scripts/build-world-map.mjs +188 -0
  457. package/scripts/build-wuwa-assets.mjs +345 -0
  458. package/scripts/collab-live-test.py +177 -0
  459. package/scripts/debug-mcp-http.ts +64 -0
  460. package/scripts/demo-lead-worker.ts +256 -0
  461. package/scripts/demo-modes.ts +415 -0
  462. package/scripts/demo-omp-channel.ts +135 -0
  463. package/scripts/design-preflight.mjs +223 -0
  464. package/scripts/design-verify-templates.mjs +110 -0
  465. package/scripts/design-verify.mjs +170 -0
  466. package/scripts/dev-guard.mjs +74 -0
  467. package/scripts/dev-modbus-simulator.mjs +55 -0
  468. package/scripts/dev-opcua-simulator.mjs +57 -0
  469. package/scripts/dev-protocol-simulators.mjs +145 -0
  470. package/scripts/dev-window.cmd +6 -0
  471. package/scripts/device-line-restore.mjs +34 -0
  472. package/scripts/diag-lead-status.ts +101 -0
  473. package/scripts/e2e-a2a-steer-live.mjs +193 -0
  474. package/scripts/e2e-agent-channel-task.ts +151 -0
  475. package/scripts/e2e-agent-team.mjs +168 -0
  476. package/scripts/e2e-auth-matrix.mjs +119 -0
  477. package/scripts/e2e-channel-isolation.ts +325 -0
  478. package/scripts/e2e-dual-drive-comm.ts +259 -0
  479. package/scripts/e2e-lead-team-mgmt.ts +217 -0
  480. package/scripts/e2e-memory-persistence.ts +320 -0
  481. package/scripts/e2e-memory-system.ts +332 -0
  482. package/scripts/e2e-omp-modes.ts +274 -0
  483. package/scripts/e2e-omp-realtime.ts +258 -0
  484. package/scripts/e2e-omp-workspace.ts +180 -0
  485. package/scripts/e2e-parallel-execution.mjs +151 -0
  486. package/scripts/e2e-rest-memory.mjs +251 -0
  487. package/scripts/e2e-rest-robustness.mjs +177 -0
  488. package/scripts/e2e-resume-crash.mjs +254 -0
  489. package/scripts/e2e-tab-close.mjs +136 -0
  490. package/scripts/e2e-task-queue.ts +277 -0
  491. package/scripts/e2e-template-isolation.mjs +267 -0
  492. package/scripts/e2e-token-ui.mjs +149 -0
  493. package/scripts/final-soup-run.py +192 -0
  494. package/scripts/final-soup-watch.py +83 -0
  495. package/scripts/home-bootstrap.mjs +117 -0
  496. package/scripts/i18n-codemod.mjs +173 -0
  497. package/scripts/i18n-fix-en.py +28 -0
  498. package/scripts/i18n-fix-en2.py +31 -0
  499. package/scripts/i18n-merge.mjs +67 -0
  500. package/scripts/omp-real-test.py +328 -0
  501. package/scripts/smoke-optimization-verify.ts +72 -0
  502. package/scripts/start.mjs +60 -0
  503. package/scripts/test-a2a-relay.mjs +222 -0
  504. package/scripts/test-a2a-types.ts +296 -0
  505. package/scripts/test-agent-brain.ts +135 -0
  506. package/scripts/test-agent-multi-channel.ts +160 -0
  507. package/scripts/test-agent-runtime.ts +348 -0
  508. package/scripts/test-api-client.ts +108 -0
  509. package/scripts/test-api-suite.mjs +271 -0
  510. package/scripts/test-block-consumption.mjs +194 -0
  511. package/scripts/test-channel-routing.ts +301 -0
  512. package/scripts/test-cmd-render.mjs +218 -0
  513. package/scripts/test-collab-e2e.mjs +136 -0
  514. package/scripts/test-collab-omp.mjs +192 -0
  515. package/scripts/test-dedup-logic.ts +132 -0
  516. package/scripts/test-dual-drive.ts +227 -0
  517. package/scripts/test-e2e-collaboration.ts +262 -0
  518. package/scripts/test-e2e-monitor.ts +192 -0
  519. package/scripts/test-event-history.ts +112 -0
  520. package/scripts/test-event-persistence.mjs +169 -0
  521. package/scripts/test-exec-modes.ts +316 -0
  522. package/scripts/test-fifo-realtime.mjs +193 -0
  523. package/scripts/test-final-e2e.mjs +290 -0
  524. package/scripts/test-full-system.ts +439 -0
  525. package/scripts/test-game-session.ts +160 -0
  526. package/scripts/test-goal-summary.ts +202 -0
  527. package/scripts/test-harness-liveness.ts +109 -0
  528. package/scripts/test-harness-terminal.ts +164 -0
  529. package/scripts/test-header-trail.mjs +95 -0
  530. package/scripts/test-hitl.ts +360 -0
  531. package/scripts/test-koda-restyle.mjs +105 -0
  532. package/scripts/test-lane-history-live.mjs +103 -0
  533. package/scripts/test-lane-raw.ts +67 -0
  534. package/scripts/test-lanes-history-render.mjs +138 -0
  535. package/scripts/test-loop-omp.mjs +154 -0
  536. package/scripts/test-mcp-tools.ts +285 -0
  537. package/scripts/test-mdlite.ts +53 -0
  538. package/scripts/test-memory-dynamic.ts +99 -0
  539. package/scripts/test-memory-maintenance.ts +117 -0
  540. package/scripts/test-memory-vector.ts +154 -0
  541. package/scripts/test-memory.ts +431 -0
  542. package/scripts/test-mock-collab.mjs +226 -0
  543. package/scripts/test-modern-render.mjs +174 -0
  544. package/scripts/test-omp-terminal.mjs +187 -0
  545. package/scripts/test-orchestration.ts +425 -0
  546. package/scripts/test-persistence-lazy.ts +323 -0
  547. package/scripts/test-prompt-system.mjs +181 -0
  548. package/scripts/test-prompts-loader.ts +56 -0
  549. package/scripts/test-protocol.ts +156 -0
  550. package/scripts/test-real-scenario.mjs +290 -0
  551. package/scripts/test-route-reason.mjs +111 -0
  552. package/scripts/test-scheduler-loop.ts +469 -0
  553. package/scripts/test-stream-render.ts +37 -0
  554. package/scripts/test-task-drawer.mjs +204 -0
  555. package/scripts/test-task-engine.ts +433 -0
  556. package/scripts/test-task-invariants.mjs +169 -0
  557. package/scripts/test-terminal-e2e.mjs +264 -0
  558. package/scripts/test-town-3d.mjs +121 -0
  559. package/scripts/test-town-drag.mjs +218 -0
  560. package/scripts/test-town-layout.mjs +200 -0
  561. package/scripts/test-town-page.mjs +136 -0
  562. package/scripts/test-town-scene-math.ts +169 -0
  563. package/scripts/test-town-twin.mjs +121 -0
  564. package/scripts/test-town-upload.mjs +89 -0
  565. package/scripts/test-town-user-flow.mjs +619 -0
  566. package/scripts/test-town-world.mjs +76 -0
  567. package/scripts/test-town-wuwa.mjs +198 -0
  568. package/scripts/test-user-isolation.mjs +193 -0
  569. package/scripts/test-workshop-db.ts +280 -0
  570. package/scripts/test-workshop-entries.ts +370 -0
  571. package/scripts/test-workspace-api.ts +329 -0
  572. package/scripts/test-ws-aep.mjs +187 -0
  573. package/scripts/test-ws-protocol.ts +134 -0
  574. package/scripts/test-ws-stress.mjs +208 -0
  575. package/scripts/town-demo-seed.mjs +75 -0
  576. package/scripts/town-scene-clear.mjs +56 -0
  577. package/scripts/ui-screenshot.mjs +128 -0
  578. package/scripts/verify-a2a-live.mjs +124 -0
  579. package/scripts/verify-exec-modes-report.ts +248 -0
  580. package/scripts/verify-game.mjs +477 -0
  581. package/scripts/verify-lead-mail.ts +188 -0
  582. package/scripts/verify-page-smoke.mjs +87 -0
  583. package/scripts/verify-ws-events.mjs +123 -0
  584. package/scripts/ws-test.mjs +176 -0
  585. package/scripts/ws-test.py +4 -0
  586. package/server/api/health.get.ts +32 -0
  587. package/server/api/mcp/workshop.ts +78 -0
  588. package/server/api/metrics.ts +75 -0
  589. package/server/api/system/config.get.ts +12 -0
  590. package/server/api/system/monitor/terminal/ws.ts +177 -0
  591. package/server/api/system/monitor/terminate.post.ts +36 -0
  592. package/server/api/system/monitor.get.ts +13 -0
  593. package/server/api/system/settings/events.get.ts +28 -0
  594. package/server/api/system/settings/index.get.ts +14 -0
  595. package/server/api/system/settings/index.patch.ts +22 -0
  596. package/server/api/system/settings/reset.post.ts +12 -0
  597. package/server/api/users/[id].delete.ts +11 -0
  598. package/server/api/users/[id].get.ts +11 -0
  599. package/server/api/users/[id].put.ts +14 -0
  600. package/server/api/users/index.get.ts +13 -0
  601. package/server/api/users/index.post.ts +13 -0
  602. package/server/api/users/login.post.ts +11 -0
  603. package/server/api/users/logout.post.ts +9 -0
  604. package/server/api/users/me.get.ts +9 -0
  605. package/server/api/users/register.post.ts +11 -0
  606. package/server/api/users/tokens/[id]/token.get.ts +11 -0
  607. package/server/api/users/tokens/[id].delete.ts +11 -0
  608. package/server/api/users/tokens/[id].patch.ts +14 -0
  609. package/server/api/users/tokens/index.get.ts +9 -0
  610. package/server/api/users/tokens/index.post.ts +13 -0
  611. package/server/api/workshop/[...path].ts +13 -0
  612. package/server/api/workshop/a2a/[agentId]/card/index.get.ts +75 -0
  613. package/server/api/workshop/a2a/[agentId]/rpc/index.post.ts +429 -0
  614. package/server/api/workshop/a2a/send.post.ts +28 -0
  615. package/server/api/workshop/agent-tools/approvals/[id]/decide.post.ts +21 -0
  616. package/server/api/workshop/agent-tools/approvals/index.get.ts +18 -0
  617. package/server/api/workshop/agent-tools/bindings/[id].delete.ts +21 -0
  618. package/server/api/workshop/agent-tools/bindings/[id].patch.ts +14 -0
  619. package/server/api/workshop/agent-tools/bindings/index.get.ts +14 -0
  620. package/server/api/workshop/agent-tools/bindings/index.post.ts +20 -0
  621. package/server/api/workshop/agent-tools/invoke.post.ts +34 -0
  622. package/server/api/workshop/agents/[id].delete.ts +20 -0
  623. package/server/api/workshop/agents/[id].get.ts +19 -0
  624. package/server/api/workshop/agents/[id].patch.ts +30 -0
  625. package/server/api/workshop/agents/index.get.ts +13 -0
  626. package/server/api/workshop/agents/index.post.ts +29 -0
  627. package/server/api/workshop/agents/subscribe.post.ts +21 -0
  628. package/server/api/workshop/approvals/[id]/decide.post.ts +30 -0
  629. package/server/api/workshop/approvals/index.get.ts +22 -0
  630. package/server/api/workshop/assets/character/[id].delete.ts +33 -0
  631. package/server/api/workshop/assets/character/index.get.ts +17 -0
  632. package/server/api/workshop/assets/character/index.post.ts +87 -0
  633. package/server/api/workshop/assets/devices/[id].delete.ts +43 -0
  634. package/server/api/workshop/assets/devices/index.get.ts +103 -0
  635. package/server/api/workshop/assets/devices/index.post.ts +53 -0
  636. package/server/api/workshop/audit.get.ts +43 -0
  637. package/server/api/workshop/caller.ts +141 -0
  638. package/server/api/workshop/channel-templates/[id]/instantiate.post.ts +31 -0
  639. package/server/api/workshop/channel-templates/[id].delete.ts +20 -0
  640. package/server/api/workshop/channel-templates/[id].get.ts +19 -0
  641. package/server/api/workshop/channel-templates/[id].patch.ts +48 -0
  642. package/server/api/workshop/channel-templates/from-channel.post.ts +31 -0
  643. package/server/api/workshop/channel-templates/index.get.ts +13 -0
  644. package/server/api/workshop/channel-templates/index.post.ts +61 -0
  645. package/server/api/workshop/channels/[id]/activate.post.ts +19 -0
  646. package/server/api/workshop/channels/[id]/agents/[agentId]/memories/[memoryId].delete.ts +30 -0
  647. package/server/api/workshop/channels/[id]/agents/[agentId]/memories/index.get.ts +29 -0
  648. package/server/api/workshop/channels/[id]/agents/[agentId]/memories/index.post.ts +48 -0
  649. package/server/api/workshop/channels/[id]/agents/[agentId]/memories/search.post.ts +43 -0
  650. package/server/api/workshop/channels/[id]/agents/[agentId]/model.patch.ts +39 -0
  651. package/server/api/workshop/channels/[id]/agents/[agentId]/position.patch.ts +33 -0
  652. package/server/api/workshop/channels/[id]/agents/[agentId]/range.patch.ts +54 -0
  653. package/server/api/workshop/channels/[id]/agents/[agentId]/stop.post.ts +25 -0
  654. package/server/api/workshop/channels/[id]/agents/[agentId].delete.ts +23 -0
  655. package/server/api/workshop/channels/[id]/agents/[agentId].patch.ts +28 -0
  656. package/server/api/workshop/channels/[id]/agents/index.get.ts +27 -0
  657. package/server/api/workshop/channels/[id]/agents/index.post.ts +61 -0
  658. package/server/api/workshop/channels/[id]/events.get.ts +55 -0
  659. package/server/api/workshop/channels/[id]/index.get.ts +18 -0
  660. package/server/api/workshop/channels/[id]/index.patch.ts +30 -0
  661. package/server/api/workshop/channels/[id]/memories/[memoryId].delete.ts +29 -0
  662. package/server/api/workshop/channels/[id]/memories/index.get.ts +28 -0
  663. package/server/api/workshop/channels/[id]/memories/index.post.ts +42 -0
  664. package/server/api/workshop/channels/[id]/messages/index.get.ts +56 -0
  665. package/server/api/workshop/channels/[id]/messages/index.post.ts +75 -0
  666. package/server/api/workshop/channels/[id]/queue.get.ts +25 -0
  667. package/server/api/workshop/channels/[id]/tasks/index.get.ts +24 -0
  668. package/server/api/workshop/channels/[id]/tasks/index.post.ts +79 -0
  669. package/server/api/workshop/channels/[id]/terminals.get.ts +21 -0
  670. package/server/api/workshop/channels/[id].delete.ts +18 -0
  671. package/server/api/workshop/channels.get.ts +11 -0
  672. package/server/api/workshop/channels.post.ts +48 -0
  673. package/server/api/workshop/daq/[id]/bind.post.ts +19 -0
  674. package/server/api/workshop/daq/[id]/samples.get.ts +22 -0
  675. package/server/api/workshop/daq/[id]/test.post.ts +17 -0
  676. package/server/api/workshop/daq/[id].delete.ts +30 -0
  677. package/server/api/workshop/daq/[id].patch.ts +31 -0
  678. package/server/api/workshop/daq/alarms/[id]/ack.post.ts +15 -0
  679. package/server/api/workshop/daq/alarms.get.ts +16 -0
  680. package/server/api/workshop/daq/controller.post.ts +52 -0
  681. package/server/api/workshop/daq/index.get.ts +40 -0
  682. package/server/api/workshop/daq/index.post.ts +33 -0
  683. package/server/api/workshop/daq/infra/reconnect.post.ts +17 -0
  684. package/server/api/workshop/daq/infra.get.ts +11 -0
  685. package/server/api/workshop/daq/templates/[key].delete.ts +17 -0
  686. package/server/api/workshop/daq/templates/[key].patch.ts +18 -0
  687. package/server/api/workshop/daq/templates/index.post.ts +17 -0
  688. package/server/api/workshop/daq/test-driver.post.ts +23 -0
  689. package/server/api/workshop/dcw/[id]/bind.post.ts +17 -0
  690. package/server/api/workshop/dcw/[id]/param-ledger.get.ts +15 -0
  691. package/server/api/workshop/dcw/[id]/test.post.ts +15 -0
  692. package/server/api/workshop/dcw/[id]/write.post.ts +19 -0
  693. package/server/api/workshop/dcw/[id].delete.ts +30 -0
  694. package/server/api/workshop/dcw/[id].patch.ts +18 -0
  695. package/server/api/workshop/dcw/controller.post.ts +43 -0
  696. package/server/api/workshop/dcw/index.get.ts +25 -0
  697. package/server/api/workshop/dcw/index.post.ts +20 -0
  698. package/server/api/workshop/dcw/journal/index.get.ts +21 -0
  699. package/server/api/workshop/dcw/journal/node/[nodeId]/rollback.post.ts +22 -0
  700. package/server/api/workshop/dcw/line/query.get.ts +31 -0
  701. package/server/api/workshop/dcw/lines/[id]/start.post.ts +35 -0
  702. package/server/api/workshop/dcw/lines/[id]/stop.post.ts +34 -0
  703. package/server/api/workshop/dcw/lines/[id].delete.ts +35 -0
  704. package/server/api/workshop/dcw/lines/[id].patch.ts +15 -0
  705. package/server/api/workshop/dcw/lines/index.get.ts +14 -0
  706. package/server/api/workshop/dcw/lines/index.post.ts +31 -0
  707. package/server/api/workshop/dcw/optimizations/[id]/judge.post.ts +24 -0
  708. package/server/api/workshop/dcw/optimizations/[id]/rollback.post.ts +29 -0
  709. package/server/api/workshop/dcw/optimizations/[id]/series.get.ts +18 -0
  710. package/server/api/workshop/dcw/optimizations/[id].get.ts +17 -0
  711. package/server/api/workshop/dcw/optimizations/index.get.ts +23 -0
  712. package/server/api/workshop/dcw/products/[id].delete.ts +16 -0
  713. package/server/api/workshop/dcw/products/[id].patch.ts +17 -0
  714. package/server/api/workshop/dcw/products/index.post.ts +16 -0
  715. package/server/api/workshop/dcw/recipes/[id]/apply.post.ts +40 -0
  716. package/server/api/workshop/dcw/recipes/[id]/mark-good.post.ts +21 -0
  717. package/server/api/workshop/dcw/recipes/[id]/rollback-good.post.ts +19 -0
  718. package/server/api/workshop/dcw/recipes/[id].delete.ts +14 -0
  719. package/server/api/workshop/dcw/recipes/[id].patch.ts +16 -0
  720. package/server/api/workshop/dcw/recipes/index.get.ts +12 -0
  721. package/server/api/workshop/dcw/recipes/index.post.ts +15 -0
  722. package/server/api/workshop/dcw/runs/[id]/close.post.ts +15 -0
  723. package/server/api/workshop/dcw/runs/[id]/data.get.ts +15 -0
  724. package/server/api/workshop/dcw/runs/[id]/rollback.post.ts +20 -0
  725. package/server/api/workshop/dcw/runs/index.get.ts +11 -0
  726. package/server/api/workshop/dcw/templates/[key].delete.ts +14 -0
  727. package/server/api/workshop/dcw/templates/[key].patch.ts +16 -0
  728. package/server/api/workshop/dcw/templates/index.post.ts +15 -0
  729. package/server/api/workshop/dcw/test-driver.post.ts +16 -0
  730. package/server/api/workshop/device-twins/[id]/control.post.ts +21 -0
  731. package/server/api/workshop/device-twins/[id]/telemetry.post.ts +21 -0
  732. package/server/api/workshop/device-twins/[id].delete.ts +25 -0
  733. package/server/api/workshop/device-twins/[id].patch.ts +29 -0
  734. package/server/api/workshop/device-twins/index.get.ts +16 -0
  735. package/server/api/workshop/device-twins/index.post.ts +32 -0
  736. package/server/api/workshop/fs/dirs.get.ts +40 -0
  737. package/server/api/workshop/mailbox/all.get.ts +19 -0
  738. package/server/api/workshop/mailbox.get.ts +14 -0
  739. package/server/api/workshop/memories/maintenance.post.ts +15 -0
  740. package/server/api/workshop/ops-logs/index.get.ts +30 -0
  741. package/server/api/workshop/ops-logs/index.post.ts +39 -0
  742. package/server/api/workshop/runtime.get.ts +11 -0
  743. package/server/api/workshop/scene/layouts/[channelId].delete.ts +23 -0
  744. package/server/api/workshop/scene/layouts/[channelId].put.ts +31 -0
  745. package/server/api/workshop/scene/layouts.get.ts +13 -0
  746. package/server/api/workshop/tasks/[id]/cancel/index.post.ts +34 -0
  747. package/server/api/workshop/tasks/[id]/complete.post.ts +32 -0
  748. package/server/api/workshop/tasks/[id]/dispatch.post.ts +30 -0
  749. package/server/api/workshop/tasks/[id]/report.post.ts +38 -0
  750. package/server/api/workshop/tasks/[id]/retry.post.ts +33 -0
  751. package/server/api/workshop/tasks/[id].get.ts +32 -0
  752. package/server/api/workshop/tasks.get.ts +13 -0
  753. package/server/api/workshop/teams/[id]/deploy.post.ts +40 -0
  754. package/server/api/workshop/teams/[id]/members/[agentId].delete.ts +19 -0
  755. package/server/api/workshop/teams/[id]/members/index.get.ts +19 -0
  756. package/server/api/workshop/teams/[id]/members/index.post.ts +32 -0
  757. package/server/api/workshop/teams/[id].delete.ts +20 -0
  758. package/server/api/workshop/teams/[id].get.ts +19 -0
  759. package/server/api/workshop/teams/[id].patch.ts +32 -0
  760. package/server/api/workshop/teams/index.get.ts +13 -0
  761. package/server/api/workshop/teams/index.post.ts +27 -0
  762. package/server/api/workshop/users/me.get.ts +23 -0
  763. package/server/api/workshop/users/register.post.ts +33 -0
  764. package/server/api/workshop/workspaces/[id]/channel-templates/[tplId].post.ts +31 -0
  765. package/server/api/workshop/workspaces/[id]/channels/[channelId].delete.ts +15 -0
  766. package/server/api/workshop/workspaces/[id]/channels/[channelId].post.ts +15 -0
  767. package/server/api/workshop/workspaces/[id]/index.delete.ts +14 -0
  768. package/server/api/workshop/workspaces/index.get.ts +11 -0
  769. package/server/api/workshop/workspaces/index.post.ts +17 -0
  770. package/server/api/workshop/ws.ts +694 -0
  771. package/server/data/agent-node-bindings.json +554 -0
  772. package/server/data/daq-templates.json +16 -0
  773. package/server/data/daqs.json +2680 -0
  774. package/server/data/dcw-lines.json +919 -0
  775. package/server/data/dcw-products.json +835 -0
  776. package/server/data/dcw-recipes.json +1650 -0
  777. package/server/data/dcw-rollback.json +9339 -0
  778. package/server/data/dcw-runs.json +4633 -0
  779. package/server/data/dcw-templates.json +26 -0
  780. package/server/data/dcw-writes.json +9770 -0
  781. package/server/data/dcws.json +1736 -0
  782. package/server/data/device-twins.json +133 -0
  783. package/server/data/line-runs.json +152 -0
  784. package/server/data/scene-layouts.json +200 -0
  785. package/server/mcp/workshop-server.ts +510 -0
  786. package/server/middleware/request-log.ts +15 -0
  787. package/server/plugins/backup.ts +85 -0
  788. package/server/plugins/daq.ts +65 -0
  789. package/server/plugins/dev-stability-guard.ts +42 -0
  790. package/server/plugins/security.ts +29 -0
  791. package/server/plugins/system-config.ts +38 -0
  792. package/server/plugins/workshop.ts +145 -0
  793. package/server/repositories/user.repository.ts +391 -0
  794. package/server/schemas/user.schema.ts +67 -0
  795. package/server/services/system-config.ts +356 -0
  796. package/server/services/user.service.ts +186 -0
  797. package/server/services/workshop/agents/adapters/omp-rpc-client.ts +539 -0
  798. package/server/services/workshop/agents/adapters/to-a2a.ts +40 -0
  799. package/server/services/workshop/agents/agent-interface.ts +219 -0
  800. package/server/services/workshop/agents/claude-agent.ts +47 -0
  801. package/server/services/workshop/agents/factory.ts +24 -0
  802. package/server/services/workshop/agents/harness-process.ts +158 -0
  803. package/server/services/workshop/agents/harness-terminal.ts +545 -0
  804. package/server/services/workshop/agents/industrial-context.ts +222 -0
  805. package/server/services/workshop/agents/industrial-tools.ts +362 -0
  806. package/server/services/workshop/agents/mock-agent.ts +396 -0
  807. package/server/services/workshop/agents/node-bindings.repo.ts +165 -0
  808. package/server/services/workshop/agents/omp-agent.ts +1598 -0
  809. package/server/services/workshop/agents/tool-approvals.ts +174 -0
  810. package/server/services/workshop/assets/character-asset.repo.ts +120 -0
  811. package/server/services/workshop/assets/device-twin.repo.ts +236 -0
  812. package/server/services/workshop/daq/alarm-notify.ts +100 -0
  813. package/server/services/workshop/daq/bus/index.ts +46 -0
  814. package/server/services/workshop/daq/bus/inproc.adapter.ts +62 -0
  815. package/server/services/workshop/daq/bus/mqtt.adapter.ts +112 -0
  816. package/server/services/workshop/daq/bus/queue-port.ts +36 -0
  817. package/server/services/workshop/daq/daq-controller.ts +778 -0
  818. package/server/services/workshop/daq/daq-node.repo.ts +96 -0
  819. package/server/services/workshop/daq/daq-node.ts +247 -0
  820. package/server/services/workshop/daq/daq-runtime.ts +168 -0
  821. package/server/services/workshop/daq/daq-templates.ts +157 -0
  822. package/server/services/workshop/daq/drivers.ts +771 -0
  823. package/server/services/workshop/daq/host-bindings.ts +71 -0
  824. package/server/services/workshop/daq/host-ports.ts +64 -0
  825. package/server/services/workshop/daq/infra.ts +202 -0
  826. package/server/services/workshop/daq/storage/index.ts +96 -0
  827. package/server/services/workshop/daq/storage/sqlite.adapter.ts +205 -0
  828. package/server/services/workshop/daq/storage/timescale.adapter.ts +185 -0
  829. package/server/services/workshop/daq/storage/tsdb-port.ts +74 -0
  830. package/server/services/workshop/db/agent.repo.ts +105 -0
  831. package/server/services/workshop/db/channel-agent.repo.ts +119 -0
  832. package/server/services/workshop/db/channel-event.repo.ts +154 -0
  833. package/server/services/workshop/db/channel-template.repo.ts +113 -0
  834. package/server/services/workshop/db/channel.repo.ts +101 -0
  835. package/server/services/workshop/db/database.ts +934 -0
  836. package/server/services/workshop/db/memory.repo.ts +249 -0
  837. package/server/services/workshop/db/message.repo.ts +149 -0
  838. package/server/services/workshop/db/ops.repo.ts +323 -0
  839. package/server/services/workshop/db/schema.sql +143 -0
  840. package/server/services/workshop/db/subscription.repo.ts +62 -0
  841. package/server/services/workshop/db/task.repo.ts +175 -0
  842. package/server/services/workshop/db/team-member.repo.ts +70 -0
  843. package/server/services/workshop/db/team.repo.ts +109 -0
  844. package/server/services/workshop/db/user.repo.ts +100 -0
  845. package/server/services/workshop/dcw/dcw-controller.ts +841 -0
  846. package/server/services/workshop/dcw/dcw-line.repo.ts +86 -0
  847. package/server/services/workshop/dcw/dcw-node.repo.ts +89 -0
  848. package/server/services/workshop/dcw/dcw-node.ts +200 -0
  849. package/server/services/workshop/dcw/dcw-product.repo.ts +95 -0
  850. package/server/services/workshop/dcw/dcw-recipe.repo.ts +349 -0
  851. package/server/services/workshop/dcw/dcw-runtime.ts +82 -0
  852. package/server/services/workshop/dcw/dcw-templates.ts +138 -0
  853. package/server/services/workshop/dcw/drivers.ts +523 -0
  854. package/server/services/workshop/dcw/line-run.ts +101 -0
  855. package/server/services/workshop/dcw/recipe-rollback-manager.ts +702 -0
  856. package/server/services/workshop/dcw/recipe-rollback.repo.ts +197 -0
  857. package/server/services/workshop/logger.ts +58 -0
  858. package/server/services/workshop/ops/ops.ts +78 -0
  859. package/server/services/workshop/prompts/loader.ts +112 -0
  860. package/server/services/workshop/runtime/agent-runtime.ts +698 -0
  861. package/server/services/workshop/runtime/channel-runtime.ts +202 -0
  862. package/server/services/workshop/runtime/embedding-provider.ts +79 -0
  863. package/server/services/workshop/runtime/execution-mode.ts +215 -0
  864. package/server/services/workshop/runtime/mailbox.ts +148 -0
  865. package/server/services/workshop/runtime/manager.ts +2543 -0
  866. package/server/services/workshop/runtime/memory.ts +439 -0
  867. package/server/services/workshop/runtime/monitor.ts +194 -0
  868. package/server/services/workshop/runtime/scheduler-loop.ts +773 -0
  869. package/server/services/workshop/runtime/task-engine.ts +574 -0
  870. package/server/services/workshop/scene/scene-layout.repo.ts +112 -0
  871. package/server/services/workshop/scene-events.ts +63 -0
  872. package/server/services/workshop/types/a2a.ts +61 -0
  873. package/server/services/workshop/types/task.ts +85 -0
  874. package/server/types/user.ts +53 -0
  875. package/server/utils/approval-gate.ts +67 -0
  876. package/server/utils/auth.ts +13 -0
  877. package/server/utils/config.ts +40 -0
  878. package/server/utils/errors.ts +24 -0
  879. package/server/utils/response.ts +49 -0
  880. package/server/utils/validate.ts +19 -0
  881. package/shared/config/engine.d.mts +41 -0
  882. package/shared/config/engine.mjs +291 -0
  883. package/shared/config/home.d.mts +48 -0
  884. package/shared/config/home.mjs +146 -0
  885. package/shared/config/schema.json +30 -0
  886. package/shared/daq-protocol.ts +361 -0
  887. package/shared/dcw-protocol.ts +576 -0
  888. package/shared/terminal-protocol.ts +70 -0
  889. package/shared/town-anim.ts +56 -0
  890. package/shared/town-behavior.ts +136 -0
  891. package/shared/town-protocol.ts +110 -0
  892. package/shared/town-scene-math.ts +283 -0
  893. package/shared/workshop-protocol.ts +199 -0
  894. package/tsconfig.json +10 -0
  895. package/uno.config.ts +37 -0
@@ -0,0 +1,2577 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * 数采中心(DAQ Console)—— server 驱动数采的总控面。
4
+ * 后端能力自描述(tsdb/queue/驱动族 + 管线指标)、控制器全局启停/周期、
5
+ * 节点清单(状态/实时值/周期/绑定/驱动),点进 /daq/[id] 进入单节点专业控制台。
6
+ */
7
+ import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
8
+ import { message } from 'ant-design-vue'
9
+ import { useDaqStream } from '@/app/composables/workshop/useDaqStream'
10
+ import { useDcwStream } from '@/app/composables/workshop/useDcwStream'
11
+ import { useWorkshopWs } from '@/app/composables/workshop/useWorkshopWs'
12
+ import { DAQ_TEMPLATES, DAQ_DRIVERS, DAQ_TEMPLATE_ICONS, daqKeyFromRef, type DaqNodeView, type DaqNodeState, type DriverConfigField, type DriverTestResult as DaqDriverTestResult, type DaqTemplateDef, type DaqTemplateIcon } from '#shared/daq-protocol'
13
+ import { useDeviceTwins } from '@/app/composables/workshop/useDeviceTwins'
14
+ import { useOpsLog } from '@/app/composables/workshop/useOpsLog'
15
+ import { useVisibleInterval } from '@/app/composables/workshop/useVisibleInterval'
16
+
17
+ const { t: tt } = useI18n()
18
+
19
+ definePageMeta({ layout: 'default' })
20
+ useHead({ title: () => tt('titles.daq') })
21
+
22
+ const daq = useDaqStream()
23
+ const dcw = useDcwStream()
24
+ /** WS 会话:连接层即注册 scene peer —— daq.reading 实时帧直达本页(不再等 5s 轮询) */
25
+ const ws = useWorkshopWs()
26
+
27
+ /** 是否存在任意运行中的产线(横幅判定) */
28
+ const anyLineActive = computed(() => dcw.lines.some(l => dcw.lineStateOf(l.id).active))
29
+
30
+ /** 节点产线归属变更(挂载到产线/移出;失败 toast 后端可读原因) */
31
+ async function setNodeLine(id: string, e: Event): Promise<void> {
32
+ const lineId = (e.target as HTMLSelectElement).value
33
+ try {
34
+ await daq.patchNode(id, { lineId })
35
+ const n = daq.nodes.find(x => x.id === id)
36
+ if (n) n.lineId = lineId
37
+ }
38
+ catch (err) {
39
+ message.error(err instanceof Error ? err.message : String(err))
40
+ }
41
+ }
42
+
43
+ /** 节点绑定设备(bind REST server 落库;与数字孪生/节点检查器同一条 deviceBindingId 链路) */
44
+ async function setNodeDevice(id: string, e: Event): Promise<void> {
45
+ const deviceId = (e.target as HTMLSelectElement).value
46
+ try {
47
+ await daq.bindNode(id, deviceId || null)
48
+ }
49
+ catch (err) {
50
+ message.error(err instanceof Error ? err.message : String(err))
51
+ }
52
+ }
53
+
54
+ /** 单节点独立 启动/停止采集(PATCH enabled;server 权威,本地乐观翻转 + WS/轮询收敛) */
55
+ async function toggleNodeEnabled(n: { id: string, enabled: boolean, state: string }): Promise<void> {
56
+ const next = !n.enabled
57
+ try {
58
+ await daq.patchNode(n.id, { enabled: next })
59
+ n.enabled = next
60
+ if (!next) n.state = 'offline'
61
+ message.success(next ? tt('daq.k1nodestart147') : tt('daq.k1nodestop146'))
62
+ }
63
+ catch (err) {
64
+ message.error(err instanceof Error ? err.message : String(err))
65
+ }
66
+ }
67
+
68
+ /** 活动配方对该节点的数采监控窗口(本线活动批次;不同 Recipe 不同窗口) */
69
+ function recipeWinOf(n: { lineId: string, id: string }): { min?: number, max?: number } | null {
70
+ if (!n.lineId) return null
71
+ const run = dcw.lineStateOf(n.lineId)
72
+ if (!run.active || !run.recipeId) return null
73
+ const r = dcw.recipes.find(x => x.id === run.recipeId)
74
+ return r?.daqWindows?.find(w => w.nodeId === n.id) ?? null
75
+ }
76
+
77
+ /** 配方窗口越限(与服务端 alarm 判定同源;行标红) */
78
+ function recipeAlarm(n: DaqNodeView): boolean {
79
+ const w = recipeWinOf(n)
80
+ if (!w || n.value == null) return false
81
+ return (w.min != null && n.value < w.min) || (w.max != null && n.value > w.max)
82
+ }
83
+
84
+ // ---------- 运维日志(全操作统一记录):实时事件轨渲染摘要,详情在 /logs 日志管理 ----------
85
+ const opsLog = useOpsLog()
86
+
87
+ function opsKindLabel(kind: string): string {
88
+ const m: Record<string, string> = { write: tt('logs.kind.write'), manual: tt('logs.kind.manual'), alarm: tt('logs.kind.alarm'), line: tt('logs.kind.line'), recipe: tt('logs.kind.recipe'), rollback: tt('logs.kind.rollback'), daq: tt('logs.kind.daq'), system: tt('logs.kind.system') }
89
+ return m[kind] ?? (kind || tt('logs.kind.system'))
90
+ }
91
+
92
+ function opsTime(at: string): string {
93
+ const d = new Date(at)
94
+ return Number.isFinite(d.getTime())
95
+ ? `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`
96
+ : ''
97
+ }
98
+
99
+ // ---------- 实时趋势(行内 sparkline;hist 为 WS 读数流客户端缓冲,每帧推进) ----------
100
+ const TREND_W = 120
101
+ const TREND_H = 26
102
+
103
+ function trendPath(hist: number[]): string {
104
+ const vals = hist.filter(Number.isFinite)
105
+ if (vals.length < 2) return ''
106
+ const min = Math.min(...vals)
107
+ const max = Math.max(...vals)
108
+ const span = max - min || 1
109
+ return vals.map((v, i) => `${((i / (vals.length - 1)) * TREND_W).toFixed(1)},${(TREND_H - ((v - min) / span) * (TREND_H - 2) - 1).toFixed(1)}`).join(' ')
110
+ }
111
+
112
+ /** 上下限参考线在趋势图内的 y 坐标(量程不可知或无数据时返回 null 不画) */
113
+ function limitY(hist: number[], limit: number | undefined | null): number | null {
114
+ if (limit == null || !Number.isFinite(limit)) return null
115
+ const vals = hist.filter(Number.isFinite)
116
+ if (vals.length < 2) return null
117
+ const min = Math.min(...vals, limit)
118
+ const max = Math.max(...vals, limit)
119
+ const span = max - min || 1
120
+ return Number((TREND_H - ((limit - min) / span) * (TREND_H - 2) - 1).toFixed(1))
121
+ }
122
+
123
+ /** 行内趋势的有效限值:活动配方窗口优先,节点预警带兜底,皆无 → 不画限值线 */
124
+ function limitOf(n: DaqNodeView): { min: number | null | undefined, max: number | null | undefined, source: 'recipe' | 'warn' } {
125
+ const w = recipeWinOf(n)
126
+ if (w && (w.min != null || w.max != null)) return { min: w.min, max: w.max, source: 'recipe' }
127
+ if (n.warnLow != null || n.warnHigh != null) return { min: n.warnLow, max: n.warnHigh, source: 'warn' }
128
+ return { min: null, max: null, source: 'warn' }
129
+ }
130
+
131
+ /** 未确认报警确认(失败 toast 后端可读原因;成功后 fetchAlarms 已由 store 内部刷新) */
132
+ async function ackOne(id: string): Promise<void> {
133
+ try {
134
+ await daq.ackAlarm(id)
135
+ message.success(tt('daq.k1acked144'))
136
+ }
137
+ catch (err) {
138
+ message.error(err instanceof Error ? err.message : String(err))
139
+ }
140
+ }
141
+
142
+ let unsub: (() => void) | null = null
143
+ onMounted(() => {
144
+ // WS 会话建连(连接层即注册 scene peer):daq.reading 实时帧直达,不再等 5s 轮询
145
+ ws.ensureConnected()
146
+ unsub = daq.ensureWsFeed()
147
+ // 运维日志实时事件轨(ops.log 帧;越限告警亦入轨 —— server 权威,详情在 /logs)
148
+ opsLog.ensureLive()
149
+ void daq.load()
150
+ // 未确认报警首轮拉取(S5;WS daq.alarm 直推 + 此后随低频刷新拍轮询兜底)
151
+ void daq.fetchAlarms()
152
+ // 产线门控状态(无活动配方不采集;状态仅展示,控制在产线运营页)
153
+ void dcw.load()
154
+ // 设备注册表(绑定设备列:显示名 + 可编辑换绑;与数字孪生同源 bind REST)
155
+ void deviceTwins.load()
156
+ // Agent 优化记录首屏预取(面板展开即有数据;展开时仍会重拉一次保新鲜)
157
+ void dcw.loadOptimizations()
158
+ // meta 指标随读数帧落库节奏低频刷新(诚实可见的管线运行数据);
159
+ // 产线运行态(横幅/产线列注记)同拍刷新 —— 开跑/停线后本页 ≤5s 收敛;
160
+ // dcw 全量快照(95+ 线/配方/历史)是重载荷且行控状态已有 WS 帧收敛,降至 15s 兜底;
161
+ // 轮询走可见性调度:后台自动降频(5s→30s / 15s→60s),回前台立即补拍,主线程不被占满
162
+ useVisibleInterval(() => {
163
+ void daq.load()
164
+ void daq.fetchAlarms()
165
+ }, 5000, { bgMs: 30000 })
166
+ useVisibleInterval(() => {
167
+ void dcw.load()
168
+ }, 15000, { bgMs: 60000 })
169
+ })
170
+ onBeforeUnmount(() => {
171
+ unsub?.()
172
+ })
173
+
174
+ // ---------- 节点筛选(产线/设备绑定/产线运行态/节点状态 + 模板/驱动/数据时间/名称搜索;全部 AND 复合) ----------
175
+ const filters = reactive({ lineId: '', deviceId: '', template: '', driver: '', lineRun: '', state: '', time: '', search: '' })
176
+ const hasFilters = computed(() => !!(filters.lineId || filters.deviceId || filters.template || filters.driver || filters.lineRun || filters.state || filters.time || filters.search.trim()))
177
+ function clearFilters(): void {
178
+ filters.lineId = ''
179
+ filters.deviceId = ''
180
+ filters.template = ''
181
+ filters.driver = ''
182
+ filters.lineRun = ''
183
+ filters.state = ''
184
+ filters.time = ''
185
+ filters.search = ''
186
+ }
187
+
188
+ /** 模板筛选选项:节点中出现过的 templateRef(计数),名称走 server 模板目录降级原文 */
189
+ const templateOptions = computed(() => {
190
+ const used = new Map<string, number>()
191
+ for (const n of daq.nodes) used.set(n.templateRef, (used.get(n.templateRef) ?? 0) + 1)
192
+ return [...used.entries()].map(([ref, count]) => ({ ref, count, label: daqTemplateRefCh(ref) }))
193
+ })
194
+
195
+ /** 驱动筛选选项:节点中出现过的驱动(计数) */
196
+ const driverOptions = computed(() => {
197
+ const used = new Map<string, number>()
198
+ for (const n of daq.nodes) used.set(n.driver, (used.get(n.driver) ?? 0) + 1)
199
+ return [...used.entries()].map(([kind, count]) => ({
200
+ kind,
201
+ count,
202
+ label: daq.meta.drivers.find(d => d.kind === kind)?.label ?? kind,
203
+ }))
204
+ })
205
+
206
+ /** 数据时间语义桶:最后采样距 now 的Fresh度(与行级 stale 判定同源的诚实时间观) */
207
+ function timeBucketOf(n: DaqNodeView): 'live' | 'hour' | 'day' | 'old' | 'never' {
208
+ if (!n.lastAt) return 'never'
209
+ const age = Date.now() - Date.parse(n.lastAt)
210
+ if (age <= 5 * 60_000) return 'live'
211
+ if (age <= 3_600_000) return 'hour'
212
+ if (age <= 86_400_000) return 'day'
213
+ return 'old'
214
+ }
215
+ function timeMatch(n: DaqNodeView): boolean {
216
+ return !filters.time || timeBucketOf(n) === filters.time
217
+ }
218
+ function searchMatch(n: DaqNodeView): boolean {
219
+ const q = filters.search.trim().toLowerCase()
220
+ if (!q) return true
221
+ return n.name.toLowerCase().includes(q) || n.id.toLowerCase().includes(q)
222
+ }
223
+
224
+ // ---------- Agent 优化记录(调控闭环;数采中心视角) ----------
225
+ const optOpen = ref(false)
226
+ /** 首次展开后保持内容挂载(手风琴动画需要);收起态内容不再重渲染 */
227
+ const optMounted = ref(false)
228
+ const optFilterLine = ref('')
229
+ const optFilterRecipe = ref('')
230
+ const optSeriesOf = ref<Record<string, { channels: Array<{ nodeId: string, nodeName: string, ch: string, unit: string, points: Array<{ at: number, value?: number, avg?: number }> }> }>>({})
231
+ const optSeriesLoading = ref('')
232
+
233
+ /** Recipe 选项:选定产线时只列该线配方,否则全量(与产线筛选级联) */
234
+ const optRecipeOptions = computed(() =>
235
+ dcw.recipes.filter(r => !optFilterLine.value || r.lineId === optFilterLine.value))
236
+
237
+ function optStatusKey(s: string): string {
238
+ const map: Record<string, string> = { 'open': 'daq.k1optst001', 'judged': 'daq.k1optst008', 'judged-keep': 'daq.k1optst002', 'rolled-back': 'daq.k1optst003', 'superseded': 'daq.k1optst004', 'superseded-manual': 'daq.k1optst005', 'closed-line-stop': 'daq.k1optst006', 'failed': 'daq.k1optst007' }
239
+ return map[s] ?? 'daq.k1optst001'
240
+ }
241
+
242
+ async function toggleOptPanel(): Promise<void> {
243
+ optOpen.value = !optOpen.value
244
+ if (optOpen.value) {
245
+ optMounted.value = true
246
+ await filterOpts()
247
+ }
248
+ }
249
+
250
+ async function filterOpts(): Promise<void> {
251
+ await dcw.loadOptimizations({
252
+ lineId: optFilterLine.value || undefined,
253
+ recipeId: optFilterRecipe.value || undefined,
254
+ })
255
+ }
256
+
257
+ /** 产线筛选变化:Recipe 级联重置(所选 Recipe 不在新产线内时) */
258
+ function onOptLineChange(): void {
259
+ if (optFilterRecipe.value && !optRecipeOptions.value.some(r => r.id === optFilterRecipe.value))
260
+ optFilterRecipe.value = ''
261
+ void filterOpts()
262
+ }
263
+
264
+ async function showOptSeries(id: string): Promise<void> {
265
+ optSeriesLoading.value = id
266
+ try {
267
+ const s = await dcw.optimizationSeries(id)
268
+ optSeriesOf.value = { ...optSeriesOf.value, [id]: s }
269
+ }
270
+ finally {
271
+ optSeriesLoading.value = ''
272
+ }
273
+ }
274
+
275
+ function seriesPath(points: Array<{ at: number, value?: number, avg?: number }>, w = 240, h = 36): string {
276
+ const vals = points.map(p => p.value ?? p.avg ?? 0).filter(Number.isFinite)
277
+ if (vals.length < 2) return ''
278
+ const min = Math.min(...vals)
279
+ const max = Math.max(...vals)
280
+ const span = max - min || 1
281
+ return vals.map((v, i) => `${(i / (vals.length - 1)) * w},${h - ((v - min) / span) * h}`).join(' ')
282
+ }
283
+
284
+ async function rollbackRecord(id: string): Promise<void> {
285
+ await dcw.rollbackOptimization(id)
286
+ await filterOpts()
287
+ }
288
+
289
+ /** 序列点换算的小数位(避免 sparkline 抖动) */
290
+ function fmtPoint(v: number | null | undefined): string {
291
+ return v == null ? '--' : String(Number(v.toFixed(2)))
292
+ }
293
+
294
+ /** 筛选下拉的设备选项:仅列出至少绑定了一个节点的设备(空设备筛选无意义) */
295
+ const boundDevices = computed<Array<{ id: string, name: string, count: number }>>(() => {
296
+ const used = new Map<string, number>()
297
+ for (const n of daq.nodes) {
298
+ if (n.deviceBindingId) used.set(n.deviceBindingId, (used.get(n.deviceBindingId) ?? 0) + 1)
299
+ }
300
+ return deviceTwins.twins
301
+ .filter(t => used.has(t.id))
302
+ .map(t => ({ id: t.id, name: nodeDevices.get(t.id) ?? t.name, count: used.get(t.id)! }))
303
+ })
304
+
305
+ /** 产线运行态筛选语义:on = 节点所属产线开跑中;off = 待机或未分配(均未采集) */
306
+ function lineRunMatch(n: DaqNodeView): boolean {
307
+ if (!filters.lineRun) return true
308
+ const active = !!n.lineId && dcw.lineStateOf(n.lineId).active
309
+ return filters.lineRun === 'on' ? active : !active
310
+ }
311
+
312
+ // ---------- 筛选产线的上下文横幅(运行态/产品/Recipe;未筛选到具体产线时回退全局门控提示) ----------
313
+ const filteredLine = computed(() => dcw.lines.find(l => l.id === filters.lineId) ?? null)
314
+ const filteredLineState = computed(() =>
315
+ filteredLine.value ? dcw.lineStateOf(filteredLine.value.id) : null)
316
+
317
+ // ---------- 行级展示状态(真实场景语义):人为停用/未分配/产线未运行 与 故障离线 分离 ----------
318
+ // 状态机:disabled(人为停用) → unassigned(未挂产线,永不采集) → idle(产线未开跑,采集门控)
319
+ // → offline(网关暂停,或产线运行中却采不到新数据) → ok/warn/alarm(新鲜数据按量程/预警带/配方窗口派生)
320
+ type RowState = DaqNodeState | 'idle' | 'disabled' | 'unassigned'
321
+
322
+ /** 数据新鲜度:超过 max(4×有效周期, 12s) 无新样本视为「采不到数据」(容忍 5s REST 刷新拍与时钟偏差) */
323
+ function staleOf(n: DaqNodeView): boolean {
324
+ if (!n.lastAt) return true
325
+ const iv = n.intervalMs ?? daq.controller.defaultIntervalMs
326
+ return Date.now() - Date.parse(n.lastAt) > Math.max(iv * 4, 12_000)
327
+ }
328
+
329
+ function rowStateOf(n: DaqNodeView): RowState {
330
+ if (!n.enabled) return 'disabled'
331
+ if (!n.lineId) return 'unassigned'
332
+ if (!dcw.lineStateOf(n.lineId).active) return 'idle'
333
+ if (!daq.controller.running || staleOf(n)) return 'offline'
334
+ return n.state
335
+ }
336
+
337
+ /** 数据是否在线(实时值列据此区分「实时值」与「最后值」) */
338
+ function isLive(n: DaqNodeView): boolean {
339
+ const s = rowStateOf(n)
340
+ return s === 'ok' || s === 'warn' || s === 'alarm'
341
+ }
342
+
343
+ const rowLabel: Record<RowState, string> = {
344
+ ok: tt('daq.k41k5c062'),
345
+ warn: tt('daq.k49z8v063'),
346
+ alarm: tt('daq.k3xmid064'),
347
+ offline: tt('daq.k44c2n065'),
348
+ idle: tt('daq.k3ozyqz117'),
349
+ disabled: tt('daq.k1disabl130'),
350
+ unassigned: tt('daq.k3ootr6053'),
351
+ }
352
+
353
+ /** 状态 pill(含诚实的原因提示;配方越限标签仅在数据新鲜时展示) */
354
+ function statePillOf(n: DaqNodeView): { key: RowState, label: string, tip: string } {
355
+ const s = rowStateOf(n)
356
+ if (s === 'offline') {
357
+ const tip = !daq.controller.running
358
+ ? tt('daq.k1gateoff140')
359
+ : tt('daq.k1staled129', { p0: Math.round(Math.max((n.intervalMs ?? daq.controller.defaultIntervalMs) * 4, 12_000) / 1000) })
360
+ return { key: s, label: rowLabel.offline, tip }
361
+ }
362
+ if (s === 'idle') return { key: s, label: rowLabel.idle, tip: tt('daq.k1idlehin139') }
363
+ if ((s === 'ok' || s === 'warn' || s === 'alarm') && recipeAlarm(n)) {
364
+ const w = recipeWinOf(n)
365
+ return { key: 'alarm', label: tt('daq.k1l3pt51104'), tip: tt('daq.k1hrrqa1122', { p0: w?.min ?? '-∞', p1: w?.max ?? '+∞', p2: '' }) }
366
+ }
367
+ return { key: s, label: rowLabel[s], tip: '' }
368
+ }
369
+
370
+ const filteredNodes = computed<DaqNodeView[]>(() => daq.nodes.filter((n) => {
371
+ if (filters.lineId && (filters.lineId === 'none' ? !!n.lineId : (n.lineId ?? '') !== filters.lineId)) return false
372
+ if (filters.deviceId && (filters.deviceId === 'none' ? !!n.deviceBindingId : n.deviceBindingId !== filters.deviceId)) return false
373
+ if (filters.template && n.templateRef !== filters.template) return false
374
+ if (filters.driver && n.driver !== filters.driver) return false
375
+ if (!lineRunMatch(n)) return false
376
+ if (filters.state && rowStateOf(n) !== filters.state) return false
377
+ if (!timeMatch(n)) return false
378
+ if (!searchMatch(n)) return false
379
+ return true
380
+ }))
381
+
382
+ /** 行渲染上下文预计算:状态 pill/配方越限/在离线/模板名 每行只算一次,
383
+ * 模板里 11 次函数调用/行(102 行 = 1100+ 次/渲染,含多次 .find)收敛为 1 次查表 */
384
+ const rowCtx = computed(() => {
385
+ const m = new Map<string, { pill: { key: RowState, label: string, tip: string }, alarm: boolean, live: boolean, tpl: string }>()
386
+ for (const n of filteredNodes.value) {
387
+ m.set(n.id, {
388
+ pill: statePillOf(n),
389
+ alarm: recipeAlarm(n),
390
+ live: isLive(n),
391
+ tpl: daqTemplateRefCh(n.templateRef),
392
+ })
393
+ }
394
+ return m
395
+ })
396
+ const EMPTY_CTX = { pill: { key: 'offline' as RowState, label: '', tip: '' }, alarm: false, live: false, tpl: '-' }
397
+ /** 行渲染视图:节点 + 预计算上下文成对交付,模板零函数调用 */
398
+ const filteredRows = computed(() =>
399
+ filteredNodes.value.map(n => ({ n, c: rowCtx.value.get(n.id) ?? EMPTY_CTX })))
400
+
401
+ // 设备名映射(device-twins 注册表;绑定列展示用)
402
+ const deviceTwins = useDeviceTwins()
403
+ const nodeDevices = new Map<string, string>()
404
+ watch(() => deviceTwins.twins, (list) => {
405
+ for (const t of list) nodeDevices.set(t.id, t.name)
406
+ }, { immediate: true, deep: true })
407
+
408
+ /** 绑定设备下拉选项:全部真实设备孪生(剔除 daq/dcw 伪孪生) */
409
+ const bindableDeviceTwins = computed(() =>
410
+ deviceTwins.twins.filter(t => t.kind !== 'daq' && !(t.modelRef ?? '').startsWith('daq-') && !(t.modelRef ?? '').startsWith('dcw-')))
411
+
412
+ const intervalOf = (intervalMs: number | null): string => {
413
+ if (intervalMs == null) return tt('daq.k9vnp9h124', { p0: daq.controller.defaultIntervalMs })
414
+ return `${intervalMs}ms`
415
+ }
416
+
417
+ /** WS 下发节拍展示(null=跟随全局;0=每帧;>0 独立间隔) */
418
+ const publishOf = (v: number | null): string => {
419
+ if (v == null) return tt('daq.k9vnp9h124', { p0: daq.controller.defaultPublishIntervalMs })
420
+ if (v === 0) return tt('daq.k41mvv078')
421
+ return `${v}ms`
422
+ }
423
+
424
+ // ---------- 添加节点向导(mock / 真实场景 + 动态参数表单 + 测试连接) ----------
425
+ const addOpen = ref(false)
426
+ const addScenario = ref<'mock' | 'real'>('mock')
427
+ const addTemplate = ref(DAQ_TEMPLATES[0]?.key ?? 'temp-tc')
428
+ const addDriver = ref('modbus-tcp')
429
+ const addName = ref('')
430
+ const addInterval = ref<number | null>(null)
431
+ const addCfg = ref<Record<string, string | number>>({})
432
+ const addTransform = reactive({ kind: 'none' as 'none' | 'linear', scale: 1, offset: 0 })
433
+ const addTesting = ref(false)
434
+ const addTest = ref<DaqDriverTestResult | null>(null)
435
+ const addSaving = ref(false)
436
+ const addError = ref('')
437
+
438
+ const addDriverMeta = computed(() => DAQ_DRIVERS.find(d => d.kind === addDriver.value))
439
+ const addFields = computed<DriverConfigField[]>(() => addDriverMeta.value?.configFields ?? [])
440
+ const driverReady = (kind: string): boolean =>
441
+ daq.meta.drivers.find(d => d.kind === kind)?.status !== 'planned' && (daq.meta.driverAvailable?.[kind] !== false)
442
+
443
+ watch(addDriver, () => {
444
+ // 切协议:表单重置为 schema 缺省值
445
+ const cfg: Record<string, string | number> = {}
446
+ for (const f of addFields.value) {
447
+ if (f.default !== undefined) cfg[f.key] = f.default as string | number
448
+ }
449
+ addCfg.value = cfg
450
+ addTest.value = null
451
+ }, { immediate: true })
452
+
453
+ async function doTestConnection(): Promise<void> {
454
+ addTesting.value = true
455
+ addTest.value = null
456
+ try {
457
+ addTest.value = await daq.testDriver(addDriver.value, addCfg.value)
458
+ }
459
+ catch (err) {
460
+ addTest.value = { ok: false, message: err instanceof Error ? err.message : String(err) }
461
+ }
462
+ finally {
463
+ addTesting.value = false
464
+ }
465
+ }
466
+
467
+ async function doAddNode(): Promise<void> {
468
+ addSaving.value = true
469
+ addError.value = ''
470
+ try {
471
+ const transform = addTransform.kind === 'linear'
472
+ ? { kind: 'linear' as const, scale: Number(addTransform.scale), offset: Number(addTransform.offset) }
473
+ : undefined
474
+ if (addScenario.value === 'mock') {
475
+ await daq.createFromTemplate(`daq-${addTemplate.value}`, {
476
+ name: addName.value ? { name: addName.value }.name : undefined,
477
+ transform,
478
+ })
479
+ }
480
+ else {
481
+ // 校验必填
482
+ for (const f of addFields.value) {
483
+ if (f.required && !addCfg.value[f.key] && addCfg.value[f.key] !== 0) {
484
+ throw new Error(tt('daq.kyuadl0125', { p0: f.label }))
485
+ }
486
+ }
487
+ const tpl = daq.templates.find(t => t.key === addTemplate.value)
488
+ await daq.createFromTemplate(`daq-${addTemplate.value}`, {
489
+ name: addName.value || undefined,
490
+ driver: addDriver.value as never,
491
+ driverConfig: { ...addCfg.value },
492
+ transform,
493
+ intervalMs: addInterval.value,
494
+ unit: tpl?.unit,
495
+ min: tpl?.min,
496
+ max: tpl?.max,
497
+ decimals: tpl?.decimals,
498
+ } as never)
499
+ }
500
+ addOpen.value = false
501
+ addName.value = ''
502
+ addTest.value = null
503
+ }
504
+ catch (err) {
505
+ addError.value = err instanceof Error ? err.message : String(err)
506
+ }
507
+ finally {
508
+ addSaving.value = false
509
+ }
510
+ }
511
+
512
+ /** 模板通道语义(server 目录为唯一事实源;模板已删除 → 显示 templateRef 原文降级) */
513
+ function daqTemplateRefCh(templateRef: string): string {
514
+ const tpl = daq.templates.find(t => t.key === daqKeyFromRef(templateRef))
515
+ return tpl ? `${tpl.name} · ${tpl.ch}` : templateRef || '-'
516
+ }
517
+
518
+ // ---------- 自定义信号模板管理(server 权威 CRUD;内置只读可复制) ----------
519
+ const tplOpen = ref(false)
520
+ const tplEditing = ref<string | null>(null)
521
+ const tplSaving = ref(false)
522
+ const tplError = ref('')
523
+ const confirmingDel = ref('')
524
+ const tplForm = reactive({
525
+ name: '', ch: '', code: '', unit: '',
526
+ min: 0, max: 100, base: 50, amp: 2,
527
+ decimals: 2, icon: 'thermo' as DaqTemplateIcon,
528
+ })
529
+ const ICON_LABEL: Record<DaqTemplateIcon, string> = {
530
+ thermo: tt('daq.k422b8079'), pressure: tt('daq.k3x6ff080'), tension: tt('daq.k3z9xc081'), encoder: tt('daq.k3subk4082'), camera: tt('daq.k47atw083'), gateway: tt('daq.kz7yhbj084'),
531
+ }
532
+
533
+ const customTpls = computed<DaqTemplateDef[]>(() => daq.templates.filter(t => !t.builtin))
534
+ const builtinTpls = computed<DaqTemplateDef[]>(() => daq.templates.filter(t => t.builtin))
535
+
536
+ function fillTplForm(t: DaqTemplateDef, asCopy = false): void {
537
+ tplForm.name = asCopy ? tt('daq.k2hbo3c126', { p0: t.name }) : t.name
538
+ tplForm.ch = t.ch
539
+ tplForm.code = t.code
540
+ tplForm.unit = t.unit
541
+ tplForm.min = t.min
542
+ tplForm.max = t.max
543
+ tplForm.base = t.base
544
+ tplForm.amp = t.amp
545
+ tplForm.decimals = t.decimals
546
+ tplForm.icon = t.icon
547
+ }
548
+
549
+ function resetTplForm(): void {
550
+ tplEditing.value = null
551
+ tplError.value = ''
552
+ confirmingDel.value = ''
553
+ fillTplForm({ key: '', name: '', code: '', ch: '', unit: '', base: 50, amp: 2, min: 0, max: 100, decimals: 2, icon: 'thermo' })
554
+ }
555
+
556
+ function editTpl(t: DaqTemplateDef): void {
557
+ tplEditing.value = t.key
558
+ tplError.value = ''
559
+ fillTplForm(t)
560
+ }
561
+
562
+ function copyTpl(t: DaqTemplateDef): void {
563
+ tplEditing.value = null
564
+ tplError.value = ''
565
+ fillTplForm(t, true)
566
+ }
567
+
568
+ async function saveTpl(): Promise<void> {
569
+ tplSaving.value = true
570
+ tplError.value = ''
571
+ try {
572
+ const num = (v: number | string): number | undefined => {
573
+ const n = Number(v)
574
+ return v === '' || v == null || !Number.isFinite(n) ? undefined : n
575
+ }
576
+ const input = {
577
+ name: tplForm.name.trim(),
578
+ ch: tplForm.ch.trim() || undefined,
579
+ code: tplForm.code.trim() || undefined,
580
+ unit: tplForm.unit.trim(),
581
+ min: num(tplForm.min)!,
582
+ max: num(tplForm.max)!,
583
+ base: num(tplForm.base),
584
+ amp: num(tplForm.amp),
585
+ decimals: num(tplForm.decimals),
586
+ icon: tplForm.icon,
587
+ }
588
+ if (tplEditing.value) await daq.updateTemplate(tplEditing.value, input)
589
+ else await daq.createTemplate(input)
590
+ resetTplForm()
591
+ }
592
+ catch (err) {
593
+ tplError.value = err instanceof Error ? err.message : String(err)
594
+ }
595
+ finally {
596
+ tplSaving.value = false
597
+ }
598
+ }
599
+
600
+ /** 两段式删除确认(避免误删;空目录风格与节点表一致) */
601
+ async function askDelTpl(t: DaqTemplateDef): Promise<void> {
602
+ if (confirmingDel.value !== t.key) {
603
+ confirmingDel.value = t.key
604
+ return
605
+ }
606
+ try {
607
+ await daq.removeTemplate(t.key)
608
+ }
609
+ catch (err) {
610
+ tplError.value = err instanceof Error ? err.message : String(err)
611
+ }
612
+ finally {
613
+ confirmingDel.value = ''
614
+ }
615
+ }
616
+
617
+ /** 驱动是否为预留协议(meta status=planned) */
618
+ const driverPlanned = (kind: string): boolean =>
619
+ daq.meta.drivers.find(d => d.kind === kind)?.status === 'planned'
620
+
621
+ const reconnecting = ref(false)
622
+ async function doReconnect(): Promise<void> {
623
+ reconnecting.value = true
624
+ try {
625
+ await daq.reconnectInfra()
626
+ }
627
+ catch { /* 横幅仍在,30s 后台也会自动重试 */ }
628
+ finally {
629
+ reconnecting.value = false
630
+ }
631
+ }
632
+ </script>
633
+
634
+ <template>
635
+ <div class="page">
636
+ <div class="aw-page-head">
637
+ <div>
638
+ <p class="aw-kicker">
639
+ AGENTWORKSHOP / DAQ CONSOLE
640
+ </p>
641
+ <h1>{{ $t('daq.k1emg364011') }}</h1>
642
+ <p class="sub">
643
+ {{ $t('daq.k1wmg2bz012') }}
644
+ </p>
645
+ </div>
646
+ <div class="badges mono">
647
+ <span
648
+ class="badge"
649
+ :title="$t('daq.k1tsdbtip133')"
650
+ >TSDB · {{ daq.meta.tsdb }}</span>
651
+ <span
652
+ class="badge"
653
+ :title="$t('daq.k1queuet134')"
654
+ >QUEUE · {{ daq.meta.queue }}</span>
655
+ </div>
656
+ </div>
657
+
658
+ <!-- 产线门控横幅:筛选到具体产线 → 展示该产线运行态/产品/Recipe(待机可直达产线管理);
659
+ 未筛选具体产线 → 全局门控提示 -->
660
+ <div
661
+ v-if="daq.loaded && filteredLine && filteredLineState"
662
+ class="infra-banner"
663
+ :class="{ good: filteredLineState.active }"
664
+ >
665
+ <span :class="filteredLineState.active ? 'i-tabler-circle-check' : 'i-tabler-info-circle'" />
666
+ <span class="txt">
667
+ <template v-if="filteredLineState.active">
668
+ {{ $t('daq.k3ktxbr085') }}{{ filteredLine.name }}」{{ $t('daq.k15lvw0o105') }}<b>{{ filteredLineState.productName }}</b> · Recipe:<b>{{ filteredLineState.recipeName }}</b>
669
+ <small class="mono">{{ $t('daq.k400lb086') }} {{ filteredLineState.runId?.slice(0, 8) }} · {{ $t('daq.k3zkt2106') }} {{ filteredLineState.startedAt?.slice(11, 19) }} {{ $t('daq.k69vag8108') }} {{ filteredLineState.taggedSamples }} {{ $t('daq.k4118o092') }}</small>
670
+ </template>
671
+ <template v-else>
672
+ {{ $t('daq.k3ktxbr085') }}{{ filteredLine.name }}」{{ $t('daq.k12uge2o107') }}</template>
673
+ </span>
674
+ <NuxtLink
675
+ class="pill-btn"
676
+ :to="`/dcw/${filteredLine.id}`"
677
+ >
678
+ {{ filteredLineState.active ? $t('daq.k1ukoy0v093') : $t('daq.k1cg78i8109') }}
679
+ </NuxtLink>
680
+ </div>
681
+ <div
682
+ v-else-if="daq.loaded && !anyLineActive"
683
+ class="infra-banner"
684
+ >
685
+ <span class="i-tabler-info-circle" />
686
+ <span class="txt">{{ $t('daq.k19onw5w013') }}<NuxtLink to="/dcw">{{ $t('daq.k1b2tk5c014') }}</NuxtLink>{{ $t('daq.k1u9wnlg015') }}</span>
687
+ </div>
688
+
689
+ <!-- 基础设施降级横幅(MQTT/Timescale 不可达:在线采集停用 + 一键重连) -->
690
+ <div
691
+ v-if="daq.meta.infra?.degraded"
692
+ class="infra-banner"
693
+ >
694
+ <span class="i-tabler-alert-triangle" />
695
+ <span class="txt">{{ daq.meta.infra.warning }}</span>
696
+ <button
697
+ class="pill-btn"
698
+ :disabled="reconnecting"
699
+ @click="doReconnect"
700
+ >
701
+ {{ reconnecting ? $t('daq.k1ld43ur094') : $t('daq.kzg9805110') }}
702
+ </button>
703
+ </div>
704
+
705
+ <!-- 未确认报警条(S5):边缘触发产生 / 轮询收敛,确认后立即消失 -->
706
+ <div
707
+ v-if="daq.alarms.length"
708
+ class="alarm-bar"
709
+ >
710
+ <span class="bar-label">
711
+ <span class="i-tabler-bell-ringing bell" />
712
+ {{ $t('daq.k1alarm141') }}
713
+ <b class="mono">{{ daq.alarms.length }}</b>
714
+ </span>
715
+ <div class="alarm-scroll">
716
+ <span
717
+ v-for="a in daq.alarms"
718
+ :key="a.id"
719
+ class="alarm-item"
720
+ :title="$t('daq.k1esc143', { p0: a.escalation })"
721
+ >
722
+ <b>{{ a.nodeName }}</b>
723
+ <span class="mono dim">{{ a.metric }}</span>
724
+ <span class="mono">{{ a.value }} {{ a.rule === 'lt-min' ? '<' : '>' }} {{ a.threshold }}</span>
725
+ <small
726
+ v-if="a.escalation > 0"
727
+ class="esc mono"
728
+ >+{{ a.escalation }}</small>
729
+ <button
730
+ class="mini-btn ack"
731
+ @click="ackOne(a.id)"
732
+ >
733
+ {{ $t('daq.k1ack142') }}
734
+ </button>
735
+ </span>
736
+ </div>
737
+ </div>
738
+
739
+ <!-- 控制器总控条:第一行 控制/指标/操作,第二行 产线状态带 -->
740
+ <section class="aw-tile ctrl-card">
741
+ <div class="ctrl-row">
742
+ <div class="ctrl-left">
743
+ <button
744
+ class="aw-pill"
745
+ :class="{ running: daq.controller.running }"
746
+ :title="$t('daq.k1snap145')"
747
+ @click="daq.controllerAction(daq.controller.running ? 'pause' : 'resume')"
748
+ >
749
+ <span :class="daq.controller.running ? 'i-tabler-player-pause' : 'i-tabler-player-play'" />
750
+ {{ daq.controller.running ? $t('daq.kpui00095') : $t('daq.knxpdpt111') }}
751
+ </button>
752
+ <label class="cycle mono">
753
+ {{ $t('daq.k1ifahxj016') }}
754
+ <input
755
+ v-model.number="daq.controller.defaultIntervalMs"
756
+ type="number"
757
+ min="200"
758
+ max="60000"
759
+ step="100"
760
+ @change="daq.controllerAction('config', daq.controller.defaultIntervalMs)"
761
+ >ms
762
+ </label>
763
+ <label
764
+ class="cycle mono"
765
+ :title="$t('daq.k1e8lcoo001')"
766
+ >
767
+ {{ $t('daq.k1if98n0017') }}
768
+ <input
769
+ v-model.number="daq.controller.defaultPublishIntervalMs"
770
+ type="number"
771
+ min="0"
772
+ max="60000"
773
+ step="100"
774
+ @change="daq.controllerAction('config', daq.controller.defaultIntervalMs, daq.controller.defaultPublishIntervalMs)"
775
+ >ms
776
+ </label>
777
+ </div>
778
+ <div class="ctrl-right">
779
+ <span class="ctrl-metrics mono">
780
+ <span>{{ $t('daq.k45uio067') }} {{ daq.controller.nodesOnline }}/{{ daq.controller.nodesTotal }}</span>
781
+ <span class="sep">·</span>
782
+ <span :title="$t('daq.k1yztsu4002')">{{ $t('daq.k3xagp087') }} {{ daq.meta.produced }}</span>
783
+ <span class="sep">·</span>
784
+ <span :title="$t('daq.ko09iha003')">{{ $t('daq.k427eu088') }} {{ daq.meta.consumed }}</span>
785
+ <span class="sep">·</span>
786
+ <span
787
+ :class="{ warn: (daq.meta.dropped ?? 0) > 0 }"
788
+ :title="$t('daq.k1droptip135')"
789
+ >{{ $t('daq.k3w8go089') }} {{ daq.meta.dropped }}</span>
790
+ <span class="sep">·</span>
791
+ <span :title="$t('daq.kj1jbmw004')">{{ $t('daq.k3wusd090') }} {{ daq.meta.samplesStored }}</span>
792
+ </span>
793
+ <button
794
+ class="aw-pill outline add-btn"
795
+ @click="tplOpen = true; resetTplForm()"
796
+ >
797
+ <span class="i-tabler-adjustments-horizontal" />
798
+ {{ $t('daq.k1f5cv0s018') }}
799
+ </button>
800
+ <button
801
+ class="aw-pill add-btn"
802
+ @click="addOpen = true"
803
+ >
804
+ <span class="i-tabler-plus" />
805
+ {{ $t('daq.k1fn0ukb019') }}
806
+ </button>
807
+ </div>
808
+ </div>
809
+ <!-- 产线状态带:单行横向滚动,每产线一枚 pill(空心点=待机 / 实心呼吸点=运行中) -->
810
+ <div class="line-strip">
811
+ <span class="strip-label">
812
+ <span class="i-tabler-route" />
813
+ {{ $t('daq.k1b2o3b6020') }}<small class="mono">{{ dcw.lines.length }}</small>
814
+ </span>
815
+ <div class="strip-scroll">
816
+ <NuxtLink
817
+ v-for="l in dcw.lines"
818
+ :key="l.id"
819
+ class="line-pill"
820
+ :class="{ on: dcw.lineStateOf(l.id).active }"
821
+ :style="{ '--lc': l.color }"
822
+ :to="`/dcw/${l.id}`"
823
+ :title="dcw.lineStateOf(l.id).active
824
+ ? $t('daq.k3v957u120', { p0: l.name, p1: dcw.lineStateOf(l.id).productName, p2: dcw.lineStateOf(l.id).recipeName })
825
+ : $t('daq.k1x7jnru121', { p0: l.name })"
826
+ >
827
+ <span class="lp-dot" />
828
+ <b>{{ l.name }}</b>
829
+ <small
830
+ v-if="dcw.lineStateOf(l.id).active"
831
+ class="lp-run"
832
+ >{{ dcw.lineStateOf(l.id).recipeName ?? $t('daq.k3vp67i096') }}</small>
833
+ <small
834
+ v-else
835
+ class="lp-idle"
836
+ >{{ $t('daq.k3zgkk021') }}</small>
837
+ </NuxtLink>
838
+ <NuxtLink
839
+ v-if="dcw.lines.length === 0"
840
+ class="line-pill"
841
+ to="/dcw"
842
+ >
843
+ <span class="lp-dot" />
844
+ <b>{{ $t('daq.k1el12b1022') }}</b>
845
+ <small class="lp-idle">{{ $t('daq.k8jxxe8023') }}</small>
846
+ </NuxtLink>
847
+ </div>
848
+ </div>
849
+ </section>
850
+
851
+ <!-- 添加节点向导:mock 模拟 / 真实设备(协议参数 + 测试连接) -->
852
+ <div
853
+ v-if="addOpen"
854
+ class="modal-mask"
855
+ @click.self="addOpen = false"
856
+ >
857
+ <div class="modal">
858
+ <h3 class="m-title">
859
+ {{ $t('daq.k19rioqa024') }}
860
+ </h3>
861
+
862
+ <div class="seg-row">
863
+ <button
864
+ class="seg"
865
+ :class="{ on: addScenario === 'mock' }"
866
+ @click="addScenario = 'mock'"
867
+ >
868
+ {{ $t('daq.k1p5c1un025') }}
869
+ </button>
870
+ <button
871
+ class="seg"
872
+ :class="{ on: addScenario === 'real' }"
873
+ @click="addScenario = 'real'"
874
+ >
875
+ {{ $t('daq.k1cbcp3o026') }}
876
+ </button>
877
+ </div>
878
+
879
+ <div class="f-grid">
880
+ <label class="f">
881
+ <span>{{ $t('daq.k1fsgerc027') }}</span>
882
+ <select
883
+ v-model="addTemplate"
884
+ class="inp"
885
+ >
886
+ <option
887
+ v-for="t in daq.templates"
888
+ :key="t.key"
889
+ :value="t.key"
890
+ >
891
+ {{ t.name }} · {{ t.ch }}({{ t.min }}~{{ t.max }} {{ t.unit }}){{ t.builtin ? '' : $t('daq.kr45rk9097') }}
892
+ </option>
893
+ </select>
894
+ </label>
895
+ <label class="f">
896
+ <span>{{ $t('daq.k1ce2k1y028') }}</span>
897
+ <input
898
+ v-model="addName"
899
+ class="inp"
900
+ :placeholder="$t('daq.kgpxzy3005')"
901
+ >
902
+ </label>
903
+ <label
904
+ v-if="addScenario === 'real'"
905
+ class="f"
906
+ >
907
+ <span>{{ $t('daq.k13mfsfc029') }}</span>
908
+ <input
909
+ v-model.number="addInterval"
910
+ type="number"
911
+ min="200"
912
+ max="60000"
913
+ step="100"
914
+ class="inp"
915
+ :placeholder="$t('daq.k1eg1000a136')"
916
+ >
917
+ </label>
918
+ </div>
919
+
920
+ <!-- 真实场景:协议选择 + 动态参数表单 + 测试连接 -->
921
+ <template v-if="addScenario === 'real'">
922
+ <div class="f-grid">
923
+ <label class="f">
924
+ <span>{{ $t('daq.k1kt87rx030') }}</span>
925
+ <select
926
+ v-model="addDriver"
927
+ class="inp"
928
+ >
929
+ <option
930
+ v-for="d in DAQ_DRIVERS.filter(x => x.status !== 'planned')"
931
+ :key="d.kind"
932
+ :value="d.kind"
933
+ >
934
+ {{ d.label }}{{ driverReady(d.kind) ? '' : $t('daq.kjbqphp098') }}
935
+ </option>
936
+ </select>
937
+ </label>
938
+ </div>
939
+
940
+ <div
941
+ v-if="addFields.length"
942
+ class="f-grid driver-form"
943
+ >
944
+ <label
945
+ v-for="f in addFields"
946
+ :key="f.key"
947
+ class="f"
948
+ >
949
+ <span>{{ f.label }}<em v-if="f.required">*</em></span>
950
+ <select
951
+ v-if="f.type === 'select'"
952
+ v-model="addCfg[f.key]"
953
+ class="inp"
954
+ >
955
+ <option
956
+ v-for="o in f.options"
957
+ :key="o.value"
958
+ :value="o.value"
959
+ >
960
+ {{ o.label }}
961
+ </option>
962
+ </select>
963
+ <input
964
+ v-else
965
+ v-model="addCfg[f.key]"
966
+ :type="f.type === 'number' ? 'number' : 'text'"
967
+ :placeholder="f.placeholder"
968
+ class="inp"
969
+ >
970
+ <small
971
+ v-if="f.hint"
972
+ class="hint"
973
+ >{{ f.hint }}</small>
974
+ </label>
975
+ </div>
976
+
977
+ <div class="test-row">
978
+ <button
979
+ class="pill-btn"
980
+ :disabled="addTesting"
981
+ @click="doTestConnection"
982
+ >
983
+ {{ addTesting ? $t('daq.k1fsh720099') : $t('daq.k1fstglk112') }}
984
+ </button>
985
+ <span
986
+ v-if="addTest"
987
+ class="test-result"
988
+ :class="addTest.ok ? 'ok' : 'bad'"
989
+ >{{ addTest.ok ? '✓' : '✗' }} {{ addTest.message }}<template v-if="addTest.latencyMs != null">({{ addTest.latencyMs }}ms)</template></span>
990
+ </div>
991
+ </template>
992
+
993
+ <p
994
+ v-if="addError"
995
+ class="m-err"
996
+ >
997
+ {{ addError }}
998
+ </p>
999
+
1000
+ <div class="m-actions">
1001
+ <button
1002
+ class="aw-pill outline"
1003
+ @click="addOpen = false"
1004
+ >
1005
+ {{ $t('daq.k3xdnn031') }}
1006
+ </button>
1007
+ <button
1008
+ class="aw-pill"
1009
+ :disabled="addSaving || (addScenario === 'real' && addTest != null && !addTest.ok)"
1010
+ :title="addScenario === 'real' && !(addTest && addTest.ok) ? $t('daq.k1needtst137') : ''"
1011
+ @click="doAddNode"
1012
+ >
1013
+ {{ addSaving ? $t('daq.k1bg4759100') : (addScenario === 'real' ? $t('daq.k7pxjxo113') : $t('daq.k1bge46t118')) }}
1014
+ </button>
1015
+ </div>
1016
+ </div>
1017
+ </div>
1018
+
1019
+ <!-- 模板管理:自定义模板增删改 + 内置模板复制 -->
1020
+ <div
1021
+ v-if="tplOpen"
1022
+ class="modal-mask"
1023
+ @click.self="tplOpen = false"
1024
+ >
1025
+ <div class="modal">
1026
+ <h3 class="m-title">
1027
+ {{ $t('daq.k11mv96s032') }}
1028
+ </h3>
1029
+
1030
+ <p class="sec-label">
1031
+ {{ $t('daq.ktji4ky033') }}
1032
+ </p>
1033
+ <table class="tpl-table">
1034
+ <tbody>
1035
+ <tr
1036
+ v-for="t in customTpls"
1037
+ :key="t.key"
1038
+ >
1039
+ <td>
1040
+ <b>{{ t.name }}</b>
1041
+ <small class="mono dim">{{ t.code }}</small>
1042
+ </td>
1043
+ <td class="mono range">
1044
+ {{ t.min }}~{{ t.max }} {{ t.unit }} · {{ t.decimals }} {{ $t('daq.k48oi091') }}
1045
+ </td>
1046
+ <td class="right actions">
1047
+ <button
1048
+ class="mini-btn"
1049
+ @click="editTpl(t)"
1050
+ >
1051
+ {{ $t('daq.k45eb0034') }}
1052
+ </button>
1053
+ <button
1054
+ class="mini-btn danger"
1055
+ @click="askDelTpl(t)"
1056
+ >
1057
+ {{ confirmingDel === t.key ? $t('daq.k1hhheu3101') : $t('daq.k3xakp114') }}
1058
+ </button>
1059
+ </td>
1060
+ </tr>
1061
+ <tr v-if="!customTpls.length">
1062
+ <td
1063
+ colspan="3"
1064
+ class="empty"
1065
+ >
1066
+ {{ $t('daq.kztvzbo035') }}
1067
+ </td>
1068
+ </tr>
1069
+ </tbody>
1070
+ </table>
1071
+
1072
+ <p class="sec-label">
1073
+ {{ tplEditing ? $t('daq.k1iiph0s102') : $t('daq.k1efixrj115') }}
1074
+ </p>
1075
+ <div class="f-grid">
1076
+ <label class="f">
1077
+ <span>{{ $t('daq.k3xhia036') }}<em>*</em></span>
1078
+ <input
1079
+ v-model="tplForm.name"
1080
+ class="inp"
1081
+ :placeholder="$t('daq.k5opd2t006')"
1082
+ >
1083
+ </label>
1084
+ <label class="f">
1085
+ <span>{{ $t('daq.k3x4ef037') }}<em>*</em></span>
1086
+ <input
1087
+ v-model="tplForm.unit"
1088
+ class="inp"
1089
+ placeholder="如 %RH"
1090
+ >
1091
+ </label>
1092
+ <label class="f">
1093
+ <span>{{ $t('daq.k1l477m0038') }}</span>
1094
+ <input
1095
+ v-model="tplForm.ch"
1096
+ class="inp"
1097
+ :placeholder="$t('daq.kk8o4tl007')"
1098
+ >
1099
+ </label>
1100
+ <label class="f">
1101
+ <span>{{ $t('daq.k1ayxrqb039') }}</span>
1102
+ <input
1103
+ v-model="tplForm.code"
1104
+ class="inp"
1105
+ :placeholder="$t('daq.kzn3l7l008')"
1106
+ >
1107
+ </label>
1108
+ <label class="f">
1109
+ <span>{{ $t('daq.k1l9jv5m040') }}<em>*</em></span>
1110
+ <input
1111
+ v-model.number="tplForm.min"
1112
+ type="number"
1113
+ class="inp"
1114
+ >
1115
+ </label>
1116
+ <label class="f">
1117
+ <span>{{ $t('daq.k1l9jv4p041') }}<em>*</em></span>
1118
+ <input
1119
+ v-model.number="tplForm.max"
1120
+ type="number"
1121
+ class="inp"
1122
+ >
1123
+ </label>
1124
+ <label class="f">
1125
+ <span>{{ $t('daq.k3mxmcx042') }}</span>
1126
+ <input
1127
+ v-model.number="tplForm.decimals"
1128
+ type="number"
1129
+ min="0"
1130
+ max="4"
1131
+ class="inp"
1132
+ >
1133
+ </label>
1134
+ <label class="f">
1135
+ <span>{{ $t('daq.k3xx56043') }}</span>
1136
+ <select
1137
+ v-model="tplForm.icon"
1138
+ class="inp"
1139
+ >
1140
+ <option
1141
+ v-for="ic in DAQ_TEMPLATE_ICONS"
1142
+ :key="ic"
1143
+ :value="ic"
1144
+ >
1145
+ {{ ICON_LABEL[ic] }}
1146
+ </option>
1147
+ </select>
1148
+ </label>
1149
+ <label class="f">
1150
+ <span>{{ $t('daq.k1f4eknv044') }}</span>
1151
+ <input
1152
+ v-model.number="tplForm.base"
1153
+ type="number"
1154
+ class="inp"
1155
+ >
1156
+ <small class="hint">{{ $t('daq.kytd15s045') }}</small>
1157
+ </label>
1158
+ <label class="f">
1159
+ <span>{{ $t('daq.k1f4ifpo046') }}</span>
1160
+ <input
1161
+ v-model.number="tplForm.amp"
1162
+ type="number"
1163
+ class="inp"
1164
+ >
1165
+ <small class="hint">{{ $t('daq.ksp2aji047') }}</small>
1166
+ </label>
1167
+ </div>
1168
+ <p
1169
+ v-if="tplError"
1170
+ class="m-err"
1171
+ >
1172
+ {{ tplError }}
1173
+ </p>
1174
+ <div class="m-actions">
1175
+ <button
1176
+ class="aw-pill outline"
1177
+ @click="resetTplForm"
1178
+ >
1179
+ {{ $t('daq.k48p40048') }}
1180
+ </button>
1181
+ <button
1182
+ class="pill-btn"
1183
+ :disabled="tplSaving"
1184
+ @click="saveTpl"
1185
+ >
1186
+ {{ tplSaving ? $t('daq.k1b38d59103') : (tplEditing ? $t('daq.k1b39281116') : $t('daq.k1b3dtga119')) }}
1187
+ </button>
1188
+ </div>
1189
+
1190
+ <p class="sec-label">
1191
+ {{ $t('daq.k1w8s84s049') }}
1192
+ </p>
1193
+ <table class="tpl-table">
1194
+ <tbody>
1195
+ <tr
1196
+ v-for="t in builtinTpls"
1197
+ :key="t.key"
1198
+ >
1199
+ <td>
1200
+ <b>{{ t.name }}</b>
1201
+ <small class="mono dim">{{ t.code }}</small>
1202
+ </td>
1203
+ <td class="mono range">
1204
+ {{ t.min }}~{{ t.max }} {{ t.unit }} · {{ t.decimals }} {{ $t('daq.k48oi091') }}
1205
+ </td>
1206
+ <td class="right actions">
1207
+ <button
1208
+ class="mini-btn"
1209
+ @click="copyTpl(t)"
1210
+ >
1211
+ {{ $t('daq.k3y694050') }}
1212
+ </button>
1213
+ </td>
1214
+ </tr>
1215
+ </tbody>
1216
+ </table>
1217
+ </div>
1218
+ </div>
1219
+
1220
+ <!-- Agent 优化记录(调控闭环:设定历史 / 判定 / 窗口数采;序列为客户端渲染) -->
1221
+ <section class="aw-tile opt-card">
1222
+ <button
1223
+ class="opt-head"
1224
+ @click="toggleOptPanel"
1225
+ >
1226
+ <span class="i-tabler-flask" />
1227
+ <b>{{ $t('daq.k1optt001') }}</b>
1228
+ <span class="opt-count mono">{{ dcw.optimizations.length }}</span>
1229
+ <span
1230
+ class="opt-chevron"
1231
+ :class="{ open: optOpen }"
1232
+ />
1233
+ </button>
1234
+ <div
1235
+ class="opt-body"
1236
+ :class="{ open: optOpen }"
1237
+ >
1238
+ <template v-if="optMounted">
1239
+ <div class="opt-filters">
1240
+ <select
1241
+ v-model="optFilterLine"
1242
+ class="inp-sel"
1243
+ @change="onOptLineChange"
1244
+ >
1245
+ <option value="">
1246
+ {{ $t('daq.k1optf001') }}
1247
+ </option>
1248
+ <option
1249
+ v-for="l in dcw.lines"
1250
+ :key="l.id"
1251
+ :value="l.id"
1252
+ >
1253
+ {{ l.name }}
1254
+ </option>
1255
+ </select>
1256
+ <select
1257
+ v-model="optFilterRecipe"
1258
+ class="inp-sel"
1259
+ @change="filterOpts"
1260
+ >
1261
+ <option value="">
1262
+ {{ $t('daq.k1optf004') }}
1263
+ </option>
1264
+ <option
1265
+ v-for="r in optRecipeOptions"
1266
+ :key="r.id"
1267
+ :value="r.id"
1268
+ >
1269
+ {{ r.name }}
1270
+ </option>
1271
+ </select>
1272
+ <button
1273
+ class="mini-act"
1274
+ @click="filterOpts"
1275
+ >
1276
+ {{ $t('daq.k1optf002') }}
1277
+ </button>
1278
+ </div>
1279
+ <p
1280
+ v-if="dcw.optimizations.length === 0"
1281
+ class="opt-empty"
1282
+ >
1283
+ {{ $t('daq.k1opte001') }}
1284
+ </p>
1285
+ <ul class="opt-list">
1286
+ <li
1287
+ v-for="r in dcw.optimizations.slice(0, 30)"
1288
+ :key="r.id"
1289
+ class="opt-row"
1290
+ :class="r.status"
1291
+ >
1292
+ <div class="opt-line1">
1293
+ <span
1294
+ class="opt-status"
1295
+ :class="r.status"
1296
+ >{{ $t(optStatusKey(r.status)) }}</span>
1297
+ <b class="opt-node">{{ r.nodeName }}</b>
1298
+ <span class="mono opt-params">{{ r.params[0]?.from ?? '?' }} → {{ r.params[0]?.to }}</span>
1299
+ <span class="mono opt-time">{{ r.setAt.slice(5, 16).replace('T', ' ') }}</span>
1300
+ <button
1301
+ v-if="!optSeriesOf[r.id]"
1302
+ class="mini-act"
1303
+ :disabled="optSeriesLoading === r.id"
1304
+ @click="showOptSeries(r.id)"
1305
+ >
1306
+ {{ $t('daq.k1opta001') }}
1307
+ </button>
1308
+ <button
1309
+ v-if="r.status === 'open'"
1310
+ class="mini-act danger"
1311
+ @click="rollbackRecord(r.id)"
1312
+ >
1313
+ {{ $t('daq.k1opta002') }}
1314
+ </button>
1315
+ </div>
1316
+ <div class="opt-line2 mono">
1317
+ <span
1318
+ v-if="r.judge"
1319
+ class="opt-judge"
1320
+ :class="r.judge.verdict"
1321
+ >{{ r.judge.verdict }}({{ r.judge.by }}): {{ r.judge.reason.slice(0, 70) }}</span>
1322
+ <span
1323
+ v-else-if="r.status === 'open'"
1324
+ class="opt-open-hint"
1325
+ >{{ $t('daq.k1opto001') }}</span>
1326
+ <span
1327
+ v-if="r.aggPending"
1328
+ class="opt-agg"
1329
+ >{{ $t('daq.k1optw001') }}</span>
1330
+ <span
1331
+ v-else-if="r.windowAgg && r.windowAgg.channels.length"
1332
+ class="opt-agg"
1333
+ >
1334
+ <template
1335
+ v-for="c in r.windowAgg.channels"
1336
+ :key="c.daqNodeId"
1337
+ >{{ c.ch }} {{ fmtPoint(c.min) }}~{{ fmtPoint(c.max) }}({{ $t('daq.k1optw002') }}{{ c.breaches < 0 ? '-' : c.breaches }}) </template>
1338
+ </span>
1339
+ </div>
1340
+ <div
1341
+ v-if="optSeriesOf[r.id]"
1342
+ class="opt-series"
1343
+ >
1344
+ <div
1345
+ v-for="ch in optSeriesOf[r.id].channels"
1346
+ :key="ch.nodeId"
1347
+ class="opt-ch"
1348
+ >
1349
+ <svg
1350
+ class="opt-spark"
1351
+ viewBox="0 0 240 36"
1352
+ preserveAspectRatio="none"
1353
+ >
1354
+ <polyline :points="seriesPath(ch.points)" />
1355
+ </svg>
1356
+ <span class="mono">{{ ch.ch }} {{ fmtPoint(ch.points.at(-1)?.value ?? ch.points.at(-1)?.avg) }}{{ ch.unit }}</span>
1357
+ </div>
1358
+ <p
1359
+ v-if="optSeriesOf[r.id].channels.length === 0"
1360
+ class="opt-empty"
1361
+ >
1362
+ {{ $t('daq.k1opte002') }}
1363
+ </p>
1364
+ </div>
1365
+ </li>
1366
+ </ul>
1367
+ </template>
1368
+ </div>
1369
+ </section>
1370
+
1371
+ <!-- 节点清单:筛选工具条(产线/设备/产线运行/状态) + 节点表 -->
1372
+ <a-spin :spinning="!daq.loaded && !daq.error">
1373
+ <section class="aw-tile table-card">
1374
+ <div class="tbl-toolbar">
1375
+ <div class="filters">
1376
+ <label class="flt">
1377
+ <span>{{ $t('daq.k3wj9n051') }}</span>
1378
+ <select
1379
+ v-model="filters.lineId"
1380
+ class="inp-sel"
1381
+ >
1382
+ <option value="">
1383
+ {{ $t('daq.k1bkl9uj052') }}
1384
+ </option>
1385
+ <option
1386
+ v-for="l in dcw.lines"
1387
+ :key="l.id"
1388
+ :value="l.id"
1389
+ >
1390
+ {{ l.name }}
1391
+ </option>
1392
+ <option value="none">
1393
+ {{ $t('daq.k3ootr6053') }}
1394
+ </option>
1395
+ </select>
1396
+ </label>
1397
+ <label class="flt">
1398
+ <span>{{ $t('daq.k1i8rtqt054') }}</span>
1399
+ <select
1400
+ v-model="filters.deviceId"
1401
+ class="inp-sel"
1402
+ >
1403
+ <option value="">
1404
+ {{ $t('daq.k1bkw4m2055') }}
1405
+ </option>
1406
+ <option
1407
+ v-for="d in boundDevices"
1408
+ :key="d.id"
1409
+ :value="d.id"
1410
+ >
1411
+ {{ d.name }}({{ d.count }})
1412
+ </option>
1413
+ <option value="none">
1414
+ {{ $t('daq.k3own4q056') }}
1415
+ </option>
1416
+ </select>
1417
+ </label>
1418
+ <label
1419
+ class="flt"
1420
+ :title="$t('daq.khb8mru009')"
1421
+ >
1422
+ <span>{{ $t('daq.k1b2tkyv057') }}</span>
1423
+ <select
1424
+ v-model="filters.lineRun"
1425
+ class="inp-sel"
1426
+ >
1427
+ <option value="">
1428
+ {{ $t('daq.k3x4t1058') }}
1429
+ </option>
1430
+ <option value="on">
1431
+ {{ $t('daq.k1nxm3p7059') }}
1432
+ </option>
1433
+ <option value="off">
1434
+ {{ $t('daq.k1ulx34e060') }}
1435
+ </option>
1436
+ </select>
1437
+ </label>
1438
+ <label class="flt">
1439
+ <span>{{ $t('daq.k1iwdfef061') }}</span>
1440
+ <select
1441
+ v-model="filters.state"
1442
+ class="inp-sel"
1443
+ >
1444
+ <option value="">
1445
+ {{ $t('daq.k3x4t1058') }}
1446
+ </option>
1447
+ <option value="ok">
1448
+ {{ $t('daq.k41k5c062') }}
1449
+ </option>
1450
+ <option value="warn">
1451
+ {{ $t('daq.k49z8v063') }}
1452
+ </option>
1453
+ <option value="alarm">
1454
+ {{ $t('daq.k3xmid064') }}
1455
+ </option>
1456
+ <option value="offline">
1457
+ {{ $t('daq.k44c2n065') }}
1458
+ </option>
1459
+ </select>
1460
+ </label>
1461
+ <label class="flt">
1462
+ <span>{{ $t('daq.k1flttpl010') }}</span>
1463
+ <select
1464
+ v-model="filters.template"
1465
+ class="inp-sel"
1466
+ >
1467
+ <option value="">
1468
+ {{ $t('daq.k1flttpl011') }}
1469
+ </option>
1470
+ <option
1471
+ v-for="t in templateOptions"
1472
+ :key="t.ref"
1473
+ :value="t.ref"
1474
+ >
1475
+ {{ t.label }}({{ t.count }})
1476
+ </option>
1477
+ </select>
1478
+ </label>
1479
+ <label class="flt">
1480
+ <span>{{ $t('daq.k1fltdrv012') }}</span>
1481
+ <select
1482
+ v-model="filters.driver"
1483
+ class="inp-sel"
1484
+ >
1485
+ <option value="">
1486
+ {{ $t('daq.k1fltdrv013') }}
1487
+ </option>
1488
+ <option
1489
+ v-for="d in driverOptions"
1490
+ :key="d.kind"
1491
+ :value="d.kind"
1492
+ >
1493
+ {{ d.label }}({{ d.count }})
1494
+ </option>
1495
+ </select>
1496
+ </label>
1497
+ <label
1498
+ class="flt"
1499
+ :title="$t('daq.k1flttim014')"
1500
+ >
1501
+ <span>{{ $t('daq.k1flttim015') }}</span>
1502
+ <select
1503
+ v-model="filters.time"
1504
+ class="inp-sel"
1505
+ >
1506
+ <option value="">
1507
+ {{ $t('daq.k1flttim016') }}
1508
+ </option>
1509
+ <option value="live">
1510
+ {{ $t('daq.k1timlv017') }}
1511
+ </option>
1512
+ <option value="hour">
1513
+ {{ $t('daq.k1timhr018') }}
1514
+ </option>
1515
+ <option value="day">
1516
+ {{ $t('daq.k1timdy019') }}
1517
+ </option>
1518
+ <option value="old">
1519
+ {{ $t('daq.k1timld020') }}
1520
+ </option>
1521
+ <option value="never">
1522
+ {{ $t('daq.k1timnv021') }}
1523
+ </option>
1524
+ </select>
1525
+ </label>
1526
+ <label class="flt">
1527
+ <span>{{ $t('daq.k1fltsrc022') }}</span>
1528
+ <input
1529
+ v-model="filters.search"
1530
+ class="inp-sel flt-search"
1531
+ type="search"
1532
+ :placeholder="$t('daq.k1fltsph023')"
1533
+ >
1534
+ </label>
1535
+ <button
1536
+ v-if="hasFilters"
1537
+ class="mini-btn clear-btn"
1538
+ @click="clearFilters"
1539
+ >
1540
+ {{ $t('daq.k1fygck2066') }}
1541
+ </button>
1542
+ </div>
1543
+ <span class="count mono">{{ filteredNodes.length }} / {{ daq.nodes.length }} {{ $t('daq.k45uio067') }}</span>
1544
+ </div>
1545
+ <!-- 实时告警 + 实时事件:告警 = server 权威未确认报警(WS 直推/可确认);事件 = 全操作摘要轨(详情在日志管理) -->
1546
+ <div class="ops-panels">
1547
+ <div
1548
+ class="ops-panel alarm-panel"
1549
+ :class="{ live: daq.alarms.length > 0 }"
1550
+ >
1551
+ <div class="panel-head">
1552
+ <span class="i-tabler-bell-ringing bell" />
1553
+ <b>{{ $t('daq.k1alarm141') }}</b>
1554
+ <span
1555
+ class="mono cnt"
1556
+ :class="{ hot: daq.alarms.length > 0 }"
1557
+ >{{ daq.alarms.length }}</span>
1558
+ </div>
1559
+ <div class="panel-body">
1560
+ <p
1561
+ v-if="daq.alarms.length === 0"
1562
+ class="panel-empty"
1563
+ >
1564
+ {{ $t('daq.k1evt001') }}
1565
+ </p>
1566
+ <div
1567
+ v-for="a in daq.alarms"
1568
+ v-else
1569
+ :key="a.id"
1570
+ class="alarm-row"
1571
+ >
1572
+ <span class="i-tabler-alert-triangle tri" />
1573
+ <b>{{ a.nodeName }}</b>
1574
+ <span class="mono dim">{{ a.metric }}</span>
1575
+ <span class="mono">{{ a.value }} {{ a.rule === 'lt-min' ? '<' : '>' }} {{ a.threshold }}</span>
1576
+ <small
1577
+ v-if="a.escalation > 0"
1578
+ class="esc mono"
1579
+ >+{{ a.escalation }}</small>
1580
+ <button
1581
+ class="mini-btn ack"
1582
+ @click="ackOne(a.id)"
1583
+ >
1584
+ {{ $t('daq.k1ack142') }}
1585
+ </button>
1586
+ </div>
1587
+ </div>
1588
+ </div>
1589
+ <div class="ops-panel event-panel">
1590
+ <div class="panel-head">
1591
+ <span class="i-tabler-activity" />
1592
+ <b>{{ $t('daq.k1evt002') }}</b>
1593
+ <NuxtLink
1594
+ class="panel-link"
1595
+ to="/logs"
1596
+ >
1597
+ {{ $t('daq.k1evt003') }}<span class="i-tabler-arrow-right" />
1598
+ </NuxtLink>
1599
+ </div>
1600
+ <div class="panel-body">
1601
+ <p
1602
+ v-if="opsLog.recent.length === 0"
1603
+ class="panel-empty"
1604
+ >
1605
+ {{ $t('daq.k1evt004') }}
1606
+ </p>
1607
+ <div
1608
+ v-for="(r, i) in opsLog.recent"
1609
+ v-else
1610
+ :key="`${r.at}:${r.actor}:${r.action}:${i}`"
1611
+ class="evt-row"
1612
+ >
1613
+ <span class="mono dim t">{{ opsTime(r.at) }}</span>
1614
+ <span
1615
+ class="src-badge"
1616
+ :class="r.actorKind"
1617
+ >{{ r.actorKind === 'agent' ? 'Agent' : r.actorKind === 'user' ? $t('logs.src.user') : $t('logs.src.system') }}</span>
1618
+ <span
1619
+ class="kind-chip"
1620
+ :class="r.kind"
1621
+ >{{ opsKindLabel(r.kind) }}</span>
1622
+ <span
1623
+ class="txt"
1624
+ :title="r.summary"
1625
+ >{{ r.summary }}</span>
1626
+ </div>
1627
+ </div>
1628
+ </div>
1629
+ </div>
1630
+ <table class="nodes-table">
1631
+ <thead>
1632
+ <tr>
1633
+ <th>{{ $t('daq.k45uio067') }}</th>
1634
+ <th>{{ $t('daq.k42w8s068') }}</th>
1635
+ <th>{{ $t('daq.k3mv305069') }}</th>
1636
+ <th>{{ $t('daq.k1trnd001') }}</th>
1637
+ <th>{{ $t('daq.k1l6g2ga070') }}</th>
1638
+ <th>{{ $t('daq.k3zi0nf071') }}</th>
1639
+ <th>{{ $t('daq.k4a0la072') }}</th>
1640
+ <th>{{ $t('daq.k3wj9n051') }}</th>
1641
+ <th :title="$t('daq.k1runtipc131')">
1642
+ {{ $t('daq.k1b2tkyv057') }}
1643
+ </th>
1644
+ <th :title="$t('daq.k1prdrtip132')">
1645
+ {{ $t('daq.k1prodrc128') }}
1646
+ </th>
1647
+ <th>{{ $t('daq.k1i8rtqt054') }}</th>
1648
+ <th class="right">
1649
+ {{ $t('daq.k40aa6073') }}
1650
+ </th>
1651
+ </tr>
1652
+ </thead>
1653
+ <tbody>
1654
+ <tr
1655
+ v-for="{ n, c } in filteredRows"
1656
+ :key="n.id"
1657
+ :class="{ 'row-recipe-alarm': c.alarm }"
1658
+ >
1659
+ <td>
1660
+ <span class="mono dim">{{ n.id.slice(0, 8) }}</span>
1661
+ <b>{{ n.name }}</b>
1662
+ <small class="mono ch">{{ c.tpl }}</small>
1663
+ </td>
1664
+ <td>
1665
+ <span
1666
+ class="st-pill"
1667
+ :class="[c.pill.key]"
1668
+ :title="c.pill.tip"
1669
+ >{{ c.pill.label }}</span>
1670
+ </td>
1671
+ <td
1672
+ class="mono val"
1673
+ :class="{ 'stale': !c.live, 'val-alarm': n.state === 'alarm' || c.alarm }"
1674
+ :title="c.live ? undefined : c.pill.tip"
1675
+ >
1676
+ {{ n.value != null ? n.value.toFixed(n.decimals) : '--' }}
1677
+ <small>{{ n.unit }}</small>
1678
+ </td>
1679
+ <td class="trend-cell">
1680
+ <svg
1681
+ class="trend"
1682
+ viewBox="0 0 120 26"
1683
+ preserveAspectRatio="none"
1684
+ :title="$t('daq.k1trnd002')"
1685
+ >
1686
+ <line
1687
+ v-if="limitY(n.hist, limitOf(n).min) != null"
1688
+ class="lim"
1689
+ x1="0"
1690
+ :y1="limitY(n.hist, limitOf(n).min)"
1691
+ x2="120"
1692
+ :y2="limitY(n.hist, limitOf(n).min)"
1693
+ />
1694
+ <line
1695
+ v-if="limitY(n.hist, limitOf(n).max) != null"
1696
+ class="lim max"
1697
+ x1="0"
1698
+ :y1="limitY(n.hist, limitOf(n).max)"
1699
+ x2="120"
1700
+ :y2="limitY(n.hist, limitOf(n).max)"
1701
+ />
1702
+ <polyline
1703
+ class="trace"
1704
+ :class="{ alarm: n.state === 'alarm' || c.alarm }"
1705
+ :points="trendPath(n.hist)"
1706
+ />
1707
+ </svg>
1708
+ </td>
1709
+ <td class="mono">
1710
+ {{ intervalOf(n.intervalMs) }}
1711
+ </td>
1712
+ <td class="mono">
1713
+ {{ publishOf(n.publishIntervalMs) }}
1714
+ </td>
1715
+ <td>
1716
+ <span
1717
+ class="drv-tag"
1718
+ :class="{ planned: driverPlanned(n.driver) }"
1719
+ :title="driverPlanned(n.driver) ? $t('daq.k1plndrv138') : ''"
1720
+ >{{ n.driver }}</span>
1721
+ </td>
1722
+ <td>
1723
+ <select
1724
+ class="line-sel"
1725
+ :value="n.lineId"
1726
+ :title="$t('daq.k3q23v3010')"
1727
+ @change="setNodeLine(n.id, $event)"
1728
+ >
1729
+ <option value="">
1730
+ {{ $t('daq.k3ootr6053') }}
1731
+ </option>
1732
+ <option
1733
+ v-for="l in dcw.lines"
1734
+ :key="l.id"
1735
+ :value="l.id"
1736
+ >
1737
+ {{ l.name }}
1738
+ </option>
1739
+ </select>
1740
+ </td>
1741
+ <!-- 产线运行:呼吸绿点=运行中 / 空心点=未运行(与产线状态带同一套点语义) -->
1742
+ <td>
1743
+ <span
1744
+ v-if="n.lineId"
1745
+ class="run-pill"
1746
+ :class="{ on: dcw.lineStateOf(n.lineId).active }"
1747
+ :title="$t('daq.k1runtipc131')"
1748
+ >
1749
+ <span class="rp-dot" />
1750
+ {{ dcw.lineStateOf(n.lineId).active ? $t('daq.k3vp67i096') : $t('daq.k3ozyqz117') }}
1751
+ </span>
1752
+ <span
1753
+ v-else
1754
+ class="run-pill na"
1755
+ >{{ $t('daq.k3ootr6053') }}</span>
1756
+ </td>
1757
+ <!-- 产品 / Recipe:本线活动批次(运行中才携带;停线/未分配 → --) -->
1758
+ <td class="prod-cell">
1759
+ <template v-if="n.lineId && dcw.lineStateOf(n.lineId).active">
1760
+ <b :title="$t('daq.k1prdrtip132')">{{ dcw.lineStateOf(n.lineId).productName || '--' }}</b>
1761
+ <small
1762
+ class="mono"
1763
+ :title="$t('daq.k1prdrtip132')"
1764
+ >{{ dcw.lineStateOf(n.lineId).recipeName || '--' }}</small>
1765
+ </template>
1766
+ <span
1767
+ v-else
1768
+ class="dim"
1769
+ >--</span>
1770
+ </td>
1771
+ <td>
1772
+ <select
1773
+ class="line-sel"
1774
+ :value="n.deviceBindingId ?? ''"
1775
+ :title="$t('daq.k2bindtip137')"
1776
+ @change="setNodeDevice(n.id, $event)"
1777
+ >
1778
+ <option value="">
1779
+ {{ $t('daq.k3own4q056') }}
1780
+ </option>
1781
+ <option
1782
+ v-for="d in bindableDeviceTwins"
1783
+ :key="d.id"
1784
+ :value="d.id"
1785
+ >
1786
+ {{ nodeDevices.get(d.id) ?? d.name }}
1787
+ </option>
1788
+ </select>
1789
+ </td>
1790
+ <td class="right">
1791
+ <button
1792
+ class="node-toggle"
1793
+ :class="{ off: !n.enabled }"
1794
+ :title="n.enabled ? $t('daq.k1nodestop146') : $t('daq.k1nodestart147')"
1795
+ @click="toggleNodeEnabled(n)"
1796
+ >
1797
+ <span :class="n.enabled ? 'i-tabler-player-pause' : 'i-tabler-player-play'" />
1798
+ {{ n.enabled ? $t('daq.k1nodestop146') : $t('daq.k1nodestart147') }}
1799
+ </button>
1800
+ <NuxtLink
1801
+ class="console-link"
1802
+ :to="`/daq/${n.id}`"
1803
+ >
1804
+ <span class="i-tabler-dashboard" />
1805
+ {{ $t('daq.k3o3jg2074') }}
1806
+ </NuxtLink>
1807
+ </td>
1808
+ </tr>
1809
+ <tr v-if="daq.loaded && daq.nodes.length === 0">
1810
+ <td colspan="11">
1811
+ <div
1812
+ class="pane-empty"
1813
+ style="min-height: 120px;"
1814
+ >
1815
+ <p class="pe-sub">
1816
+ {{ $t('daq.kvhjyc3075') }}
1817
+ </p>
1818
+ </div>
1819
+ </td>
1820
+ </tr>
1821
+ <tr v-else-if="daq.loaded && filteredNodes.length === 0">
1822
+ <td colspan="11">
1823
+ <div
1824
+ class="pane-empty"
1825
+ style="min-height: 90px;"
1826
+ >
1827
+ <p class="pe-sub">
1828
+ {{ $t('daq.k1w20qew076') }}
1829
+ </p>
1830
+ </div>
1831
+ </td>
1832
+ </tr>
1833
+ </tbody>
1834
+ </table>
1835
+ </section>
1836
+ </a-spin>
1837
+
1838
+ <p
1839
+ v-if="daq.error"
1840
+ class="err"
1841
+ >
1842
+ {{ daq.error }}(<NuxtLink to="/workshop">{{ $t('daq.k1bhhheq077') }}</NuxtLink>)
1843
+ </p>
1844
+ </div>
1845
+ </template>
1846
+
1847
+ <style scoped>
1848
+ .page { padding: 4px; }
1849
+ h1 { margin: 2px 0 4px; font-size: 30px; font-weight: 400; letter-spacing: -0.015em; }
1850
+ .sub { margin: 0; font-size: 12.5px; opacity: 0.6; }
1851
+ .mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
1852
+ .dim { opacity: 0.55; }
1853
+
1854
+ .badges { display: flex; gap: 8px; }
1855
+ .badge {
1856
+ padding: 3px 10px;
1857
+ font-size: 11px;
1858
+ letter-spacing: 0.05em;
1859
+ color: var(--ink-soft);
1860
+ border: 1px solid var(--line-strong);
1861
+ border-radius: var(--radius-chip);
1862
+ }
1863
+
1864
+ .ctrl-card {
1865
+ display: flex;
1866
+ flex-direction: column;
1867
+ gap: 11px;
1868
+ padding: 12px 18px 11px;
1869
+ margin-bottom: 14px;
1870
+ }
1871
+ .ctrl-row {
1872
+ display: flex;
1873
+ flex-wrap: wrap;
1874
+ gap: 14px;
1875
+ align-items: center;
1876
+ justify-content: space-between;
1877
+ }
1878
+ .ctrl-left { display: flex; gap: 14px; align-items: center; }
1879
+ .aw-pill.running { background: var(--accent); }
1880
+ .cycle { display: inline-flex; gap: 6px; align-items: center; font-size: 12px; color: var(--ink-faint); }
1881
+ .cycle input {
1882
+ width: 84px;
1883
+ padding: 4px 8px;
1884
+ color: var(--ink);
1885
+ background: var(--paper-deep);
1886
+ border: 1px solid var(--line-strong);
1887
+ border-radius: var(--radius-chip);
1888
+ }
1889
+ .ctrl-metrics { display: flex; gap: 8px; align-items: center; font-size: 11.5px; color: var(--ink-soft); }
1890
+ .ctrl-metrics .sep { opacity: 0.4; }
1891
+ .ctrl-metrics .warn { color: var(--tone-warning-dot); }
1892
+
1893
+ /* 产线状态带:label 固定 + 单行横向滚动(任意产线数量恒定一行高) */
1894
+ .line-strip {
1895
+ display: flex;
1896
+ gap: 12px;
1897
+ align-items: center;
1898
+ padding-top: 10px;
1899
+ border-top: 1px solid var(--divider-hair);
1900
+ }
1901
+ .strip-label {
1902
+ display: inline-flex;
1903
+ flex: 0 0 auto;
1904
+ gap: 6px;
1905
+ align-items: center;
1906
+ font-size: 10.5px;
1907
+ letter-spacing: 0.08em;
1908
+ text-transform: uppercase;
1909
+ color: var(--ink-fainter);
1910
+ }
1911
+ .strip-label small { font-size: 10px; color: var(--ink-faint); }
1912
+ .strip-scroll {
1913
+ display: flex;
1914
+ gap: 8px;
1915
+ flex: 1 1 auto;
1916
+ min-width: 0;
1917
+ overflow-x: auto;
1918
+ padding-bottom: 2px;
1919
+ scrollbar-width: thin;
1920
+ scrollbar-color: var(--line-strong) transparent;
1921
+ }
1922
+ .strip-scroll::-webkit-scrollbar { height: 4px; }
1923
+ .strip-scroll::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 2px; }
1924
+ .strip-scroll::-webkit-scrollbar-track { background: transparent; }
1925
+ .line-pill {
1926
+ display: inline-flex;
1927
+ flex: 0 0 auto;
1928
+ gap: 7px;
1929
+ align-items: center;
1930
+ padding: 4px 12px;
1931
+ font-size: 11.5px;
1932
+ color: var(--ink-soft);
1933
+ background: var(--paper-deep);
1934
+ border: 1px solid var(--line-strong);
1935
+ border-radius: var(--radius-pill);
1936
+ transition: color 0.15s, border-color 0.15s, background 0.15s;
1937
+ }
1938
+ /* 空心点 = 待机;实心呼吸点 = 运行(状态语义承载体,非装饰) */
1939
+ .line-pill .lp-dot {
1940
+ width: 7px;
1941
+ height: 7px;
1942
+ border: 1.5px solid var(--lc, var(--ink-fainter));
1943
+ border-radius: 50%;
1944
+ opacity: 0.55;
1945
+ }
1946
+ .line-pill b { font-weight: 600; }
1947
+ .line-pill small { font-size: 10.5px; color: var(--ink-fainter); }
1948
+ .line-pill:hover { border-color: var(--lc, var(--accent)); color: var(--ink); }
1949
+ .line-pill.on {
1950
+ color: var(--ink);
1951
+ background: color-mix(in srgb, var(--lc) 10%, transparent);
1952
+ border-color: color-mix(in srgb, var(--lc) 50%, transparent);
1953
+ }
1954
+ .line-pill.on .lp-dot {
1955
+ border-color: transparent;
1956
+ background: var(--lc);
1957
+ opacity: 1;
1958
+ }
1959
+ .line-pill.on .lp-run { color: color-mix(in srgb, var(--lc) 75%, var(--ink)); }
1960
+ @media (prefers-reduced-motion: no-preference) {
1961
+ .line-pill.on .lp-dot { box-shadow: 0 0 7px color-mix(in srgb, var(--lc) 75%, transparent); animation: lpPulse 1.8s ease-in-out infinite; }
1962
+ }
1963
+ @keyframes lpPulse {
1964
+ 0%, 100% { box-shadow: 0 0 3px color-mix(in srgb, var(--lc) 55%, transparent); }
1965
+ 50% { box-shadow: 0 0 9px color-mix(in srgb, var(--lc) 85%, transparent); }
1966
+ }
1967
+
1968
+ .table-card { overflow-x: auto; }
1969
+ .nodes-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
1970
+ /* 离屏行跳过布局/绘制(102 行长表;contain-intrinsic-size 给出占位高度防滚动条跳动) */
1971
+ .nodes-table tbody tr { content-visibility: auto; contain-intrinsic-size: auto 42px; }
1972
+ .nodes-table th, .nodes-table td { padding: 9px 12px; text-align: left; border-bottom: 1px solid var(--divider-hair); }
1973
+ .nodes-table th {
1974
+ font-size: 11px;
1975
+ font-weight: 600;
1976
+ letter-spacing: 0.06em;
1977
+ text-transform: uppercase;
1978
+ color: var(--ink-faint);
1979
+ border-bottom: 1px solid var(--line-strong);
1980
+ }
1981
+ .nodes-table td b { margin-left: 8px; }
1982
+ .ch { display: block; margin-top: 2px; font-size: 10px; color: var(--ink-faint); }
1983
+ .right { text-align: right; }
1984
+ .val { font-size: 13px; }
1985
+ .val small { margin-left: 3px; color: var(--ink-faint); }
1986
+ /* 数据静默(停用/未运行/采不到数据):最后值置灰呈现,不再冒充实时值 */
1987
+ .val.stale { opacity: 0.45; }
1988
+ /* 越限实时值:红字(与行红底叠加仍可读) */
1989
+ .val.val-alarm { color: var(--tone-danger-dot); font-weight: 700; }
1990
+
1991
+ /* ── 实时告警 + 实时事件双面板(server 权威;玻璃面板语言) ── */
1992
+ .ops-panels {
1993
+ display: grid;
1994
+ grid-template-columns: minmax(320px, 5fr) minmax(320px, 7fr);
1995
+ gap: 10px;
1996
+ margin: 0 0 10px;
1997
+ }
1998
+ @media (max-width: 1100px) {
1999
+ .ops-panels { grid-template-columns: 1fr; }
2000
+ }
2001
+ .ops-panel {
2002
+ min-width: 0;
2003
+ background: var(--surface-glass);
2004
+ border: 1px solid var(--glass-line);
2005
+ border-radius: 10px;
2006
+ backdrop-filter: blur(var(--aurora-blur)) saturate(1.15);
2007
+ -webkit-backdrop-filter: blur(var(--aurora-blur)) saturate(1.15);
2008
+ }
2009
+ .ops-panel.alarm-panel.live { border-color: color-mix(in srgb, var(--tone-danger-dot) 40%, var(--glass-line)); }
2010
+ .panel-head {
2011
+ display: flex;
2012
+ gap: 7px;
2013
+ align-items: center;
2014
+ padding: 8px 12px;
2015
+ font-size: 12.5px;
2016
+ color: var(--ink-soft);
2017
+ border-bottom: 1px solid var(--glass-line);
2018
+ }
2019
+ .panel-head .bell { color: var(--tone-danger-dot); }
2020
+ .panel-head .cnt { margin-left: auto; font-size: 11px; color: var(--ink-faint); }
2021
+ .panel-head .cnt.hot { color: var(--tone-danger-dot); font-weight: 700; }
2022
+ .panel-link {
2023
+ display: inline-flex;
2024
+ gap: 3px;
2025
+ align-items: center;
2026
+ margin-left: auto;
2027
+ font-size: 11.5px;
2028
+ color: var(--tone-info-dot);
2029
+ text-decoration: none;
2030
+ }
2031
+ .panel-head .cnt + .panel-link { margin-left: 12px; }
2032
+ .panel-body {
2033
+ display: flex;
2034
+ flex-direction: column;
2035
+ gap: 5px;
2036
+ max-height: 168px;
2037
+ padding: 8px 12px;
2038
+ overflow-y: auto;
2039
+ }
2040
+ .panel-empty { margin: 4px 0; font-size: 12px; color: var(--ink-faint); }
2041
+ .alarm-row {
2042
+ display: flex;
2043
+ gap: 7px;
2044
+ align-items: center;
2045
+ padding: 5px 8px;
2046
+ font-size: 12px;
2047
+ color: var(--tone-danger-dot);
2048
+ background: var(--tone-danger-bg);
2049
+ border: 1px solid color-mix(in srgb, var(--tone-danger-dot) 35%, transparent);
2050
+ border-radius: 7px;
2051
+ }
2052
+ .alarm-row .tri { flex: none; }
2053
+ .alarm-row b { color: var(--ink); }
2054
+ .alarm-row .esc { color: var(--tone-danger-dot); }
2055
+ .alarm-row .ack { margin-left: auto; }
2056
+ .evt-row {
2057
+ display: flex;
2058
+ gap: 7px;
2059
+ align-items: center;
2060
+ padding: 4px 6px;
2061
+ font-size: 12px;
2062
+ border-radius: 6px;
2063
+ }
2064
+ .evt-row:hover { background: var(--frost-bg); }
2065
+ .evt-row .t { flex: none; font-size: 11px; }
2066
+ .evt-row .txt {
2067
+ min-width: 0;
2068
+ overflow: hidden;
2069
+ color: var(--ink-soft);
2070
+ text-overflow: ellipsis;
2071
+ white-space: nowrap;
2072
+ }
2073
+ .src-badge {
2074
+ flex: none;
2075
+ padding: 1px 6px;
2076
+ font-size: 10px;
2077
+ border: 1px solid var(--glass-line);
2078
+ border-radius: 99px;
2079
+ color: var(--ink-faint);
2080
+ }
2081
+ .src-badge.agent { color: var(--tone-info-dot); border-color: color-mix(in srgb, var(--tone-info-dot) 40%, transparent); }
2082
+ .src-badge.user { color: var(--tone-success-dot); border-color: color-mix(in srgb, var(--tone-success-dot) 40%, transparent); }
2083
+ .src-badge.system { color: var(--ink-faint); }
2084
+ .kind-chip {
2085
+ flex: none;
2086
+ padding: 1px 6px;
2087
+ font-size: 10px;
2088
+ color: var(--ink-soft);
2089
+ background: var(--frost-bg);
2090
+ border-radius: 5px;
2091
+ }
2092
+ .kind-chip.alarm { color: var(--tone-danger-dot); background: var(--tone-danger-bg); }
2093
+ .kind-chip.write { color: var(--tone-info-dot); }
2094
+ .kind-chip.rollback { color: var(--tone-warning-dot); }
2095
+
2096
+ /* ── 行内实时趋势(WS 读数流直驱;上下限虚线参考) ── */
2097
+ .trend-cell { width: 128px; }
2098
+ .trend { display: block; width: 120px; height: 26px; }
2099
+ .trend .trace {
2100
+ fill: none;
2101
+ stroke: var(--tone-info-dot);
2102
+ stroke-width: 1.4;
2103
+ }
2104
+ .trend .trace.alarm { stroke: var(--tone-danger-dot); }
2105
+ .trend .lim {
2106
+ stroke: color-mix(in srgb, var(--tone-warning-dot) 62%, transparent);
2107
+ stroke-width: 1;
2108
+ stroke-dasharray: 3 3;
2109
+ }
2110
+ .trend .lim.max { stroke: color-mix(in srgb, var(--tone-danger-dot) 55%, transparent); }
2111
+
2112
+ .st-pill {
2113
+ display: inline-block;
2114
+ padding: 2px 9px;
2115
+ font-family: var(--font-mono);
2116
+ font-size: 10.5px;
2117
+ letter-spacing: 0.04em;
2118
+ border-radius: var(--radius-pill);
2119
+ }
2120
+ .st-pill.ok { color: var(--tone-success-dot); background: var(--tone-success-bg); }
2121
+ .st-pill.warn { color: var(--tone-warning-dot); background: var(--tone-warning-bg); }
2122
+ .st-pill.alarm { color: var(--tone-danger-dot); background: var(--tone-danger-bg); }
2123
+ /* 配方越限行:整行淡红底 + 左缘警示条 */
2124
+ tr.row-recipe-alarm { background: color-mix(in srgb, var(--tone-danger-dot, #ff6b6b) 8%, transparent); }
2125
+ tr.row-recipe-alarm td:first-child { box-shadow: inset 3px 0 0 var(--tone-danger-dot, #ff6b6b); }
2126
+ .st-pill.offline { color: var(--tone-neutral-dot); background: var(--tone-neutral-bg); }
2127
+ /* 非故障的静止态(停用/未分配/未运行):同为中性灰,与故障离线区分靠文案与提示 */
2128
+ .st-pill.idle, .st-pill.disabled, .st-pill.unassigned { color: var(--tone-neutral-dot); background: var(--tone-neutral-bg); }
2129
+
2130
+ .drv-tag {
2131
+ font-family: var(--font-mono);
2132
+ font-size: 10.5px;
2133
+ padding: 2px 7px;
2134
+ border: 1px solid var(--line);
2135
+ border-radius: var(--radius-chip);
2136
+ color: var(--ink-soft);
2137
+ }
2138
+ .drv-tag.planned { opacity: 0.55; border-style: dashed; }
2139
+
2140
+ .console-link { display: inline-flex; gap: 5px; align-items: center; font-size: 12.5px; color: var(--accent); }
2141
+
2142
+ /* 单节点 启动/停止采集(R:独立节点控制) */
2143
+ .node-toggle {
2144
+ display: inline-flex;
2145
+ gap: 4px;
2146
+ align-items: center;
2147
+ margin-right: 12px;
2148
+ padding: 2px 9px;
2149
+ font-size: 11.5px;
2150
+ border: 1px solid var(--line);
2151
+ border-radius: var(--radius-chip);
2152
+ color: var(--ink);
2153
+ background: transparent;
2154
+ cursor: pointer;
2155
+ transition: border-color 0.15s ease, background 0.15s ease;
2156
+ }
2157
+ .node-toggle:hover { border-color: var(--accent); background: var(--hover-tint); }
2158
+ .node-toggle.off { color: var(--ink-soft); border-style: dashed; opacity: 0.8; }
2159
+
2160
+ .err { margin-top: 14px; font-size: 13px; color: var(--tone-danger-dot); }
2161
+ @media (prefers-reduced-motion: no-preference) {
2162
+ .nodes-table tbody tr:hover { background: var(--hover-tint); }
2163
+ }
2164
+
2165
+ .infra-banner {
2166
+ display: flex;
2167
+ gap: 10px;
2168
+ align-items: center;
2169
+ padding: 10px 14px;
2170
+ margin-bottom: 14px;
2171
+ color: var(--tone-warning-dot);
2172
+ background: var(--tone-warning-bg);
2173
+ border: 1px solid color-mix(in srgb, var(--tone-warning-dot) 40%, transparent);
2174
+ border-radius: var(--radius-chip);
2175
+ }
2176
+ /* 筛选产线运行中:绿色语义变体 */
2177
+ .infra-banner.good {
2178
+ color: var(--tone-success-dot);
2179
+ background: var(--tone-success-bg);
2180
+ border-color: color-mix(in srgb, var(--tone-success-dot) 40%, transparent);
2181
+ }
2182
+ .infra-banner .txt { flex: 1 1 auto; font-size: 12.5px; line-height: 1.5; }
2183
+ .infra-banner .txt a { color: var(--accent); text-decoration: underline; }
2184
+ .infra-banner .txt b { font-weight: 600; }
2185
+ .infra-banner .txt small { margin-left: 8px; font-size: 10.5px; opacity: 0.75; }
2186
+ .infra-banner .pill-btn { flex: 0 0 auto; color: var(--paper-raised); text-decoration: none; }
2187
+
2188
+ /* ---------- 未确认报警条(S5) ---------- */
2189
+ .alarm-bar {
2190
+ display: flex;
2191
+ gap: 12px;
2192
+ align-items: center;
2193
+ padding: 8px 14px;
2194
+ margin-bottom: 14px;
2195
+ color: var(--tone-danger-dot);
2196
+ background: var(--tone-danger-bg);
2197
+ border: 1px solid color-mix(in srgb, var(--tone-danger-dot) 40%, transparent);
2198
+ border-radius: var(--radius-chip);
2199
+ }
2200
+ .bar-label {
2201
+ display: inline-flex;
2202
+ flex: 0 0 auto;
2203
+ gap: 6px;
2204
+ align-items: center;
2205
+ font-size: 11px;
2206
+ font-weight: 600;
2207
+ letter-spacing: 0.05em;
2208
+ text-transform: uppercase;
2209
+ }
2210
+ .bar-label .bell { animation: bellShake 1.6s ease-in-out infinite; }
2211
+ @media (prefers-reduced-motion: reduce) {
2212
+ .bar-label .bell { animation: none; }
2213
+ }
2214
+ @keyframes bellShake {
2215
+ 0%, 80%, 100% { transform: rotate(0deg); }
2216
+ 85% { transform: rotate(12deg); }
2217
+ 90% { transform: rotate(-10deg); }
2218
+ 95% { transform: rotate(6deg); }
2219
+ }
2220
+ .alarm-scroll {
2221
+ display: flex;
2222
+ flex: 1 1 auto;
2223
+ gap: 8px;
2224
+ min-width: 0;
2225
+ overflow-x: auto;
2226
+ scrollbar-width: thin;
2227
+ scrollbar-color: var(--line-strong) transparent;
2228
+ }
2229
+ .alarm-item {
2230
+ display: inline-flex;
2231
+ flex: 0 0 auto;
2232
+ gap: 8px;
2233
+ align-items: center;
2234
+ padding: 4px 10px;
2235
+ font-size: 11.5px;
2236
+ color: var(--ink-soft);
2237
+ background: var(--paper-deep);
2238
+ border: 1px solid color-mix(in srgb, var(--tone-danger-dot) 30%, transparent);
2239
+ border-radius: var(--radius-pill);
2240
+ }
2241
+ .alarm-item b { font-weight: 600; }
2242
+ .alarm-item .dim { opacity: 0.6; }
2243
+ .alarm-item .esc { color: var(--tone-warning-dot); }
2244
+ .alarm-item .ack {
2245
+ padding: 2px 9px;
2246
+ color: var(--paper-raised);
2247
+ background: var(--tone-danger-dot);
2248
+ border-color: transparent;
2249
+ }
2250
+ .alarm-item .ack:hover { opacity: 0.85; border-color: transparent; color: var(--paper-raised); }
2251
+
2252
+ /* ---------- 添加节点向导 ---------- */
2253
+ .ctrl-right { display: flex; gap: 12px; align-items: center; }
2254
+ .add-btn { padding: 8px 16px; font-size: 13px; }
2255
+ .modal-mask {
2256
+ position: fixed;
2257
+ z-index: 50;
2258
+ inset: 0;
2259
+ display: flex;
2260
+ align-items: center;
2261
+ justify-content: center;
2262
+ background: var(--scrim);
2263
+ backdrop-filter: blur(2px);
2264
+ }
2265
+ .modal {
2266
+ width: 640px;
2267
+ max-width: 94vw;
2268
+ max-height: 88vh;
2269
+ overflow-y: auto;
2270
+ padding: 22px 24px;
2271
+ background: var(--surface-glass-strong);
2272
+ backdrop-filter: var(--aurora-blur);
2273
+ -webkit-backdrop-filter: var(--aurora-blur);
2274
+ border: 1px solid var(--glass-line);
2275
+ border-radius: var(--radius-panel);
2276
+ box-shadow: var(--glass-edge), var(--shadow-float);
2277
+ }
2278
+ .m-title { margin: 0 0 14px; font-size: 17px; }
2279
+ .seg-row { display: flex; gap: 8px; margin-bottom: 14px; }
2280
+ .seg {
2281
+ flex: 1;
2282
+ padding: 8px 0;
2283
+ font-size: 13px;
2284
+ cursor: pointer;
2285
+ color: var(--ink-faint);
2286
+ background: var(--paper-deep);
2287
+ border: 1px solid var(--line);
2288
+ border-radius: var(--radius-chip);
2289
+ }
2290
+ .seg.on {
2291
+ font-weight: 600;
2292
+ color: var(--on-accent);
2293
+ background: var(--accent);
2294
+ border-color: var(--accent);
2295
+ }
2296
+ .f-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px; }
2297
+ .driver-form { grid-template-columns: repeat(3, 1fr); }
2298
+ .cal-form { grid-template-columns: 2fr 1fr; align-items: end; }
2299
+ .f { display: flex; flex-direction: column; gap: 4px; font-size: 11.5px; color: var(--ink-faint); }
2300
+ .f em { margin-left: 3px; font-style: normal; color: var(--tone-danger-dot); }
2301
+ .inp {
2302
+ width: 100%;
2303
+ padding: 6px 9px;
2304
+ font-size: 12.5px;
2305
+ color: var(--ink);
2306
+ background: var(--paper-deep);
2307
+ border: 1px solid var(--line-strong);
2308
+ border-radius: var(--radius-chip);
2309
+ }
2310
+ .hint { font-size: 10px; color: var(--ink-fainter); }
2311
+ .test-row { display: flex; gap: 10px; align-items: center; margin: 6px 0 4px; }
2312
+ .pill-btn {
2313
+ padding: 7px 14px;
2314
+ font-size: 12.5px;
2315
+ cursor: pointer;
2316
+ color: var(--paper-raised);
2317
+ background: var(--accent);
2318
+ border: 1px solid var(--accent);
2319
+ border-radius: var(--radius-pill);
2320
+ }
2321
+ .pill-btn:disabled { opacity: 0.5; cursor: default; }
2322
+ .test-result { font-family: var(--font-mono); font-size: 11px; }
2323
+ .test-result.ok { color: var(--tone-success-dot); }
2324
+ .test-result.bad { color: var(--tone-danger-dot); }
2325
+ .m-err { margin: 8px 0 0; font-size: 12px; color: var(--tone-danger-dot); }
2326
+ .m-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 16px; }
2327
+ .aw-pill.outline { color: var(--ink); background: var(--paper-raised); border-color: var(--line-strong); }
2328
+
2329
+ /* ---------- 模板管理 ---------- */
2330
+ .sec-label {
2331
+ margin: 14px 0 6px;
2332
+ font-size: 11px;
2333
+ font-weight: 600;
2334
+ letter-spacing: 0.06em;
2335
+ text-transform: uppercase;
2336
+ color: var(--ink-faint);
2337
+ }
2338
+ .tpl-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
2339
+ .tpl-table td { padding: 7px 8px; border-bottom: 1px solid var(--divider-hair); }
2340
+ .tpl-table td b { margin-right: 8px; }
2341
+ .tpl-table .range { font-size: 11.5px; color: var(--ink-soft); }
2342
+ .tpl-table .empty { color: var(--ink-fainter); font-size: 12px; text-align: center; padding: 14px 0; }
2343
+ .tpl-table .actions { white-space: nowrap; }
2344
+ .mini-btn {
2345
+ padding: 3px 10px;
2346
+ font-size: 11.5px;
2347
+ cursor: pointer;
2348
+ color: var(--ink-soft);
2349
+ background: var(--paper-deep);
2350
+ border: 1px solid var(--line-strong);
2351
+ border-radius: var(--radius-chip);
2352
+ }
2353
+ .mini-btn.danger { color: var(--tone-danger-dot); }
2354
+ .mini-btn:hover { border-color: var(--accent); color: var(--accent); }
2355
+
2356
+ /* ---------- 节点筛选工具条 ---------- */
2357
+ .tbl-toolbar {
2358
+ display: flex;
2359
+ flex-wrap: wrap;
2360
+ gap: 10px;
2361
+ align-items: flex-end;
2362
+ justify-content: space-between;
2363
+ padding: 11px 14px;
2364
+ border-bottom: 1px solid var(--divider-hair);
2365
+ }
2366
+ .filters { display: flex; flex-wrap: wrap; gap: 9px; align-items: flex-end; }
2367
+ .flt {
2368
+ display: flex;
2369
+ flex-direction: column;
2370
+ gap: 3px;
2371
+ font-size: 10px;
2372
+ letter-spacing: 0.05em;
2373
+ text-transform: uppercase;
2374
+ color: var(--ink-fainter);
2375
+ }
2376
+ .inp-sel {
2377
+ min-width: 112px;
2378
+ padding: 4px 8px;
2379
+ font-size: 11.5px;
2380
+ color: var(--ink);
2381
+ background: var(--paper-deep);
2382
+ border: 1px solid var(--line-strong);
2383
+ border-radius: var(--radius-chip);
2384
+ transition: border-color 0.15s;
2385
+ }
2386
+ .inp-sel:focus { outline: none; border-color: var(--accent); }
2387
+ .clear-btn { margin-bottom: 1px; }
2388
+ /* 名称/ID 搜索:与下拉同族,宽度独立(复合筛选的最后一个自由维度) */
2389
+ .flt-search {
2390
+ width: 168px;
2391
+ min-width: 0;
2392
+ }
2393
+ .flt-search::placeholder { color: var(--ink-fainter); }
2394
+ .count { padding-bottom: 4px; font-size: 11px; color: var(--ink-faint); }
2395
+
2396
+ .line-sel {
2397
+ max-width: 120px;
2398
+ padding: 3px 6px;
2399
+ font-size: 10.5px;
2400
+ color: var(--ink-soft);
2401
+ background: var(--paper-deep);
2402
+ border: 1px solid var(--line-strong);
2403
+ border-radius: var(--radius-chip);
2404
+ transition: border-color 0.15s;
2405
+ }
2406
+ .line-sel:focus { outline: none; border-color: var(--accent); }
2407
+
2408
+ /* 产线运行列:空心点=未运行(中性) / 呼吸绿点=运行中 —— 与产线状态带同一套点语义 */
2409
+ .run-pill {
2410
+ display: inline-flex;
2411
+ gap: 6px;
2412
+ align-items: center;
2413
+ padding: 2px 9px;
2414
+ font-size: 10.5px;
2415
+ color: var(--ink-fainter);
2416
+ border: 1px solid var(--line);
2417
+ border-radius: var(--radius-pill);
2418
+ }
2419
+ .run-pill .rp-dot {
2420
+ width: 6px;
2421
+ height: 6px;
2422
+ border: 1.5px solid currentColor;
2423
+ border-radius: 50%;
2424
+ opacity: 0.7;
2425
+ }
2426
+ .run-pill.na {
2427
+ border-style: dashed;
2428
+ opacity: 0.75;
2429
+ }
2430
+ .run-pill.on {
2431
+ color: var(--tone-success-dot);
2432
+ border-color: color-mix(in srgb, var(--tone-success-dot) 40%, transparent);
2433
+ background: var(--tone-success-bg);
2434
+ }
2435
+ .run-pill.on .rp-dot {
2436
+ background: var(--tone-success-dot);
2437
+ border-color: transparent;
2438
+ opacity: 1;
2439
+ }
2440
+ @media (prefers-reduced-motion: no-preference) {
2441
+ .run-pill.on .rp-dot { animation: rpPulse 1.8s ease-in-out infinite; }
2442
+ }
2443
+ @keyframes rpPulse {
2444
+ 0%, 100% { box-shadow: 0 0 2px color-mix(in srgb, var(--tone-success-dot) 55%, transparent); }
2445
+ 50% { box-shadow: 0 0 7px color-mix(in srgb, var(--tone-success-dot) 85%, transparent); }
2446
+ }
2447
+
2448
+ /* 产品 / Recipe 列:产品主行 + Recipe 副行(两行紧凑,超长省略) */
2449
+ .prod-cell { max-width: 170px; }
2450
+ .prod-cell b {
2451
+ display: block;
2452
+ font-weight: 600;
2453
+ overflow: hidden;
2454
+ text-overflow: ellipsis;
2455
+ white-space: nowrap;
2456
+ }
2457
+ .prod-cell small {
2458
+ display: block;
2459
+ margin-top: 1px;
2460
+ font-size: 10px;
2461
+ color: var(--ink-faint);
2462
+ overflow: hidden;
2463
+ text-overflow: ellipsis;
2464
+ white-space: nowrap;
2465
+ }
2466
+
2467
+ /* ── Agent 优化记录面板(调控闭环;与全站玻璃语言同族) ── */
2468
+ .opt-card { padding: 0; }
2469
+ .opt-head {
2470
+ display: flex;
2471
+ gap: 9px;
2472
+ align-items: center;
2473
+ width: 100%;
2474
+ padding: 12px 16px;
2475
+ font-size: 13.5px;
2476
+ color: var(--ink);
2477
+ background: transparent;
2478
+ border: 0;
2479
+ cursor: pointer;
2480
+ }
2481
+ .opt-head > .i-tabler-flask { font-size: 16px; color: var(--tone-info-dot); }
2482
+ .opt-count {
2483
+ padding: 1px 8px;
2484
+ font-size: 10.5px;
2485
+ color: var(--tone-info-dot);
2486
+ background: var(--tone-info-bg);
2487
+ border-radius: 999px;
2488
+ }
2489
+ .opt-chevron {
2490
+ width: 7px;
2491
+ height: 7px;
2492
+ margin-left: auto;
2493
+ border-right: 1.5px solid var(--ink-faint);
2494
+ border-bottom: 1.5px solid var(--ink-faint);
2495
+ transform: rotate(45deg);
2496
+ transition: transform var(--transition-base);
2497
+ }
2498
+ .opt-chevron.open { transform: rotate(225deg); }
2499
+ /* 手风琴展开/收起(occasional 频次;状态指示;height 豁免 = 手风琴;进出对称 260ms;
2500
+ * 收起态 visibility 联动,内部链接/按钮不可聚焦) */
2501
+ .opt-body {
2502
+ display: flex;
2503
+ flex-direction: column;
2504
+ gap: 10px;
2505
+ max-height: 0;
2506
+ padding: 0 16px;
2507
+ overflow: hidden;
2508
+ visibility: hidden;
2509
+ opacity: 0;
2510
+ transition:
2511
+ max-height 260ms cubic-bezier(0.22, 0.68, 0.36, 1),
2512
+ opacity 200ms ease,
2513
+ visibility 260ms;
2514
+ }
2515
+ .opt-body.open {
2516
+ max-height: 64vh;
2517
+ padding-bottom: 14px;
2518
+ overflow-y: auto;
2519
+ visibility: visible;
2520
+ opacity: 1;
2521
+ }
2522
+ .opt-filters { display: flex; gap: 8px; align-items: center; }
2523
+ .opt-empty { font-size: 11.5px; color: var(--ink-fainter); }
2524
+ .opt-list { display: flex; flex-direction: column; gap: 7px; margin: 0; padding: 0; list-style: none; }
2525
+ .opt-row {
2526
+ display: flex;
2527
+ flex-direction: column;
2528
+ gap: 5px;
2529
+ padding: 9px 12px;
2530
+ background: var(--frost-bg);
2531
+ border: 1px solid var(--glass-line);
2532
+ border-radius: 10px;
2533
+ }
2534
+ .opt-row.open { border-color: color-mix(in srgb, var(--tone-warning-dot) 36%, var(--glass-line)); }
2535
+ .opt-row.rolled-back { border-color: color-mix(in srgb, var(--tone-danger-dot) 30%, var(--glass-line)); }
2536
+ .opt-line1 { display: flex; gap: 9px; align-items: center; min-width: 0; }
2537
+ .opt-status {
2538
+ flex: none;
2539
+ padding: 1px 8px;
2540
+ font-family: var(--font-mono);
2541
+ font-size: 9.5px;
2542
+ color: var(--ink-faint);
2543
+ border: 1px solid var(--line-strong);
2544
+ border-radius: 5px;
2545
+ }
2546
+ .opt-status.open { color: var(--tone-warning-dot); border-color: color-mix(in srgb, var(--tone-warning-dot) 45%, transparent); }
2547
+ .opt-status.judged-keep { color: var(--tone-success-dot); border-color: color-mix(in srgb, var(--tone-success-dot) 45%, transparent); }
2548
+ .opt-status.rolled-back { color: var(--tone-danger-dot); border-color: color-mix(in srgb, var(--tone-danger-dot) 45%, transparent); }
2549
+ .opt-node { overflow: hidden; font-size: 12.5px; color: var(--ink); text-overflow: ellipsis; white-space: nowrap; }
2550
+ .opt-params { flex: none; font-size: 11px; color: var(--tone-info-dot); }
2551
+ .opt-time { flex: none; margin-left: auto; font-size: 10px; color: var(--ink-fainter); }
2552
+ .mini-act {
2553
+ flex: none;
2554
+ padding: 2px 9px;
2555
+ font-size: 10.5px;
2556
+ color: var(--tone-info-dot);
2557
+ background: var(--tone-info-bg);
2558
+ border: 0;
2559
+ border-radius: 6px;
2560
+ cursor: pointer;
2561
+ }
2562
+ .mini-act.danger { color: var(--tone-danger-dot); background: var(--tone-danger-bg); }
2563
+ .mini-act:disabled { opacity: 0.5; cursor: default; }
2564
+ .opt-line2 { display: flex; flex-wrap: wrap; gap: 8px; font-size: 10.5px; color: var(--ink-faint); }
2565
+ .opt-judge.rollback, .opt-judge.keep, .opt-judge.uncertain { color: var(--ink-soft); }
2566
+ .opt-judge b { font-weight: 600; }
2567
+ .opt-open-hint { color: var(--tone-warning-dot); }
2568
+ .opt-agg { color: var(--ink-fainter); }
2569
+ .opt-series { display: flex; flex-wrap: wrap; gap: 10px; padding-top: 4px; }
2570
+ .opt-ch { display: flex; flex: 1 1 180px; gap: 8px; align-items: center; min-width: 160px; }
2571
+ .opt-spark { width: 120px; height: 30px; }
2572
+ .opt-spark polyline {
2573
+ fill: none;
2574
+ stroke: var(--tone-info-dot);
2575
+ stroke-width: 1.5;
2576
+ }
2577
+ </style>