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,2259 @@
1
+ <script setup lang="ts">
2
+ import { computed, onUnmounted, reactive, ref } from 'vue'
3
+ import { useRoute } from 'vue-router'
4
+ import { DCW_DRIVERS, type DcwParamLedger, type DcwTemplateIcon, type LineQueryResult, type RecipeRunData } from '#shared/dcw-protocol'
5
+ import type { DriverConfigField } from '#shared/daq-protocol'
6
+ import { useDcwStream } from '~/composables/workshop/useDcwStream'
7
+ import { useDaqStream } from '~/composables/workshop/useDaqStream'
8
+ import { useDeviceTwins } from '~/composables/workshop/useDeviceTwins'
9
+
10
+ const { t } = useI18n()
11
+
12
+ const route = useRoute()
13
+ const dcw = useDcwStream()
14
+ const daq = useDaqStream()
15
+ const deviceTwins = useDeviceTwins()
16
+ void daq.load()
17
+
18
+ /** 路由产线 id;总览页「产线管理」进入 */
19
+ const lineId = computed(() => String(route.params.id ?? ''))
20
+ const line = computed(() => dcw.lines.find(l => l.id === lineId.value))
21
+ const ls = computed(() => dcw.lineStateOf(lineId.value))
22
+
23
+ void dcw.load()
24
+ /** WS 喂帧:节点变更(dcw.node.changed)/写 ACK 实时收敛 —— 本页状态同步的通道 */
25
+ const unsubDcw = dcw.ensureWsFeed()
26
+ onUnmounted(() => unsubDcw())
27
+
28
+ const deviceName = (id: string | null): string =>
29
+ id ? (deviceTwins.twins.find(t => t.id === id)?.name ?? id) : t('dcwDetail.k3own4q121')
30
+
31
+ const stateLabel: Record<string, string> = {
32
+ idle: t('dcwDetail.k3zgkk122'), writing: t('dcwDetail.k3l3h80123'), ok: '已 ACK', error: t('dcwDetail.k40reu124'), offline: t('dcwDetail.k44c2n125'),
33
+ }
34
+
35
+ function dcwTemplateRefCh(templateRef?: string): string {
36
+ const ref = templateRef ?? ''
37
+ const tpl = dcw.templates.find(t => t.key === (ref.startsWith('dcw-') ? ref.slice(4) : ref))
38
+ return tpl?.ch ?? ref
39
+ }
40
+
41
+ /** 配方参数窗口提示:目标节点的全局工艺量程 */
42
+ function nodeMin(nodeId: string): number | undefined {
43
+ return dcw.nodeById(nodeId)?.min
44
+ }
45
+ function nodeMax(nodeId: string): number | undefined {
46
+ return dcw.nodeById(nodeId)?.max
47
+ }
48
+
49
+ /** 本产线数采节点(配方监控窗口的可选目标) */
50
+ const lineDaqNodes = computed(() => daq.nodes.filter(n => n.lineId === lineId.value))
51
+
52
+ // ---------- 参数台账(调控闭环:三值对照 + 设定历史 + 优化记录) ----------
53
+ const ledgerNodeId = ref('')
54
+ const ledger = ref<DcwParamLedger | null>(null)
55
+ const ledgerLoading = ref(false)
56
+ async function loadLedger(): Promise<void> {
57
+ if (!ledgerNodeId.value) return
58
+ ledgerLoading.value = true
59
+ try {
60
+ ledger.value = await dcw.fetchLedger(ledgerNodeId.value)
61
+ }
62
+ finally {
63
+ ledgerLoading.value = false
64
+ }
65
+ }
66
+ /** 单步回退(经 write() 门控;回退后刷新台账) */
67
+ async function rollbackLedgerNode(): Promise<void> {
68
+ if (!ledgerNodeId.value) return
69
+ await dcw.rollbackNode(ledgerNodeId.value)
70
+ await loadLedger()
71
+ }
72
+
73
+ /** 监控窗口 chip 显示:数采节点参数语义 */
74
+ function daqNodeCh(nodeId: string): string {
75
+ return daq.nodeById(nodeId)?.name ?? nodeId
76
+ }
77
+
78
+ // ---------- 产线作用域数据(仅本产线 + 未分配收编) ----------
79
+ /** 本产线节点(直写表) */
80
+ const lineNodes = computed(() => dcw.nodes.filter(n => n.lineId === lineId.value))
81
+ /** 本产线产品 */
82
+ const lineProducts = computed(() => dcw.products.filter(p => p.lineId === lineId.value))
83
+ /** 本产线配方 */
84
+ const lineRecipesAll = computed(() => dcw.recipes.filter(r => r.lineId === lineId.value))
85
+ /** 本产线批次 */
86
+ const lineRuns = computed(() => dcw.runs.filter(r => r.lineId === lineId.value))
87
+ /** 本产线写历史(按本产线节点过滤) */
88
+ const lineHistory = computed(() => {
89
+ const ids = new Set(lineNodes.value.map(n => n.id))
90
+ return dcw.history.filter(h => ids.has(h.nodeId))
91
+ })
92
+ /** 未分配节点/产品(可收编进本产线) */
93
+ const unassignedNodes = computed(() => dcw.nodes.filter(n => !n.lineId))
94
+ const unassignedProducts = computed(() => dcw.products.filter(p => !p.lineId))
95
+
96
+ async function adoptNode(id: string): Promise<void> {
97
+ await dcw.patchNode(id, { lineId: lineId.value })
98
+ const n = dcw.nodeById(id)
99
+ if (n) n.lineId = lineId.value
100
+ }
101
+ async function adoptProduct(id: string): Promise<void> {
102
+ await dcw.updateProduct(id, { lineId: lineId.value })
103
+ await dcw.load()
104
+ }
105
+
106
+ // ---------- 直写 ----------
107
+ const setInputs = reactive<Record<string, number | ''>>({})
108
+ const writingId = ref('')
109
+ const writeError = ref('')
110
+ const writeOk = ref('')
111
+
112
+ async function doWrite(nodeId: string, value: number): Promise<void> {
113
+ writingId.value = nodeId
114
+ writeError.value = ''
115
+ writeOk.value = ''
116
+ try {
117
+ const outcome = await dcw.write(nodeId, value)
118
+ if (outcome.ok) {
119
+ writeOk.value = t('dcwDetail.k1alcbyu182', { p0: dcw.nodeById(nodeId)?.name ?? nodeId, p1: outcome.message })
120
+ setInputs[nodeId] = ''
121
+ }
122
+ else {
123
+ writeError.value = t('dcwDetail.kl1e9x9183', { p0: dcw.nodeById(nodeId)?.name ?? nodeId, p1: outcome.message })
124
+ }
125
+ }
126
+ catch (err) {
127
+ writeError.value = err instanceof Error ? err.message : String(err)
128
+ }
129
+ finally {
130
+ writingId.value = ''
131
+ }
132
+ }
133
+
134
+ // ---------- 逐节点 控制开启/暂停 ----------
135
+ const togglingId = ref('')
136
+ /** 开启/暂停单个节点的控制:暂停后服务端拒绝一切下发(409 当前节点暂停),本地即时收敛状态 */
137
+ async function toggleControl(nodeId: string, enabled: boolean): Promise<void> {
138
+ togglingId.value = nodeId
139
+ writeError.value = ''
140
+ try {
141
+ await dcw.patchNode(nodeId, { enabled })
142
+ const n = dcw.nodeById(nodeId)
143
+ if (n) {
144
+ n.enabled = enabled
145
+ if (!enabled) n.state = 'offline'
146
+ else if (n.state === 'offline') n.state = 'idle'
147
+ }
148
+ }
149
+ catch (err) {
150
+ writeError.value = err instanceof Error ? err.message : String(err)
151
+ }
152
+ finally {
153
+ togglingId.value = ''
154
+ }
155
+ }
156
+
157
+ // ---------- 添加控制节点向导 ----------
158
+ const addOpen = ref(false)
159
+ const addScenario = ref<'mock' | 'real'>('mock')
160
+ const addTemplate = ref(dcw.templates[0]?.key ?? '')
161
+ const addDriver = ref<'mock' | 'modbus-tcp' | 'opcua'>('mock')
162
+ const addName = ref('')
163
+ const addHold = ref<number | null>(null)
164
+ const addCfg = ref<Record<string, string | number>>({})
165
+ const addTransform = reactive({ kind: 'none' as 'none' | 'linear', scale: 1, offset: 0 })
166
+ const addSemantics = ref('')
167
+ const addTesting = ref(false)
168
+ const addTest = ref<{ ok: boolean, message: string } | null>(null)
169
+ const addSaving = ref(false)
170
+ const addError = ref('')
171
+
172
+ const addDriverMeta = computed(() => DCW_DRIVERS.find(d => d.kind === addDriver.value))
173
+ const addFields = computed<DriverConfigField[]>(() => addDriverMeta.value?.configFields ?? [])
174
+
175
+ function resetAddCfg(): void {
176
+ const cfg: Record<string, string | number> = {}
177
+ for (const f of addFields.value) {
178
+ if (f.default !== undefined) cfg[f.key] = f.default
179
+ }
180
+ addCfg.value = cfg
181
+ addTest.value = null
182
+ }
183
+ void resetAddCfg()
184
+
185
+ async function doTestConnection(): Promise<void> {
186
+ addTesting.value = true
187
+ addTest.value = null
188
+ try {
189
+ addTest.value = await dcw.testDriver(addDriver.value, addCfg.value)
190
+ }
191
+ catch (err) {
192
+ addTest.value = { ok: false, message: err instanceof Error ? err.message : String(err) }
193
+ }
194
+ finally {
195
+ addTesting.value = false
196
+ }
197
+ }
198
+
199
+ async function doAddNode(): Promise<void> {
200
+ addSaving.value = true
201
+ addError.value = ''
202
+ try {
203
+ for (const f of addFields.value) {
204
+ if (f.required && (addCfg.value[f.key] === undefined || addCfg.value[f.key] === '')) {
205
+ throw new Error(t('dcwDetail.k8x1un1184', { p0: f.label }))
206
+ }
207
+ }
208
+ const transform = addTransform.kind === 'linear'
209
+ ? { kind: 'linear' as const, scale: Number(addTransform.scale), offset: Number(addTransform.offset) }
210
+ : undefined
211
+ await dcw.createFromTemplate(`dcw-${addTemplate.value}`, {
212
+ name: addName.value.trim() || undefined,
213
+ driver: addDriver.value,
214
+ driverConfig: { ...addCfg.value },
215
+ transform,
216
+ holdIntervalMs: addHold.value,
217
+ lineId: lineId.value,
218
+ semantics: addSemantics.value.trim() || undefined,
219
+ })
220
+ addOpen.value = false
221
+ addName.value = ''
222
+ addSemantics.value = ''
223
+ }
224
+ catch (err) {
225
+ addError.value = err instanceof Error ? err.message : String(err)
226
+ }
227
+ finally {
228
+ addSaving.value = false
229
+ }
230
+ }
231
+
232
+ // ---------- 添加控制节点模板(自定义创建) ----------
233
+ const tplOpen = ref(false)
234
+ const tplSaving = ref(false)
235
+ const tplError = ref('')
236
+ const tplOk = ref('')
237
+
238
+ function openTplModal(): void {
239
+ tplOpen.value = true
240
+ tplError.value = ''
241
+ tplOk.value = ''
242
+ }
243
+
244
+ const tplForm = reactive({
245
+ name: '', ch: '', code: '', unit: '', min: '' as number | '', max: '' as number | '',
246
+ decimals: 1, icon: 'gateway' as DcwTemplateIcon, semantics: '',
247
+ })
248
+ const tplIcons: Array<{ key: DcwTemplateIcon, label: string }> = [
249
+ { key: 'thermo', label: t('dcwDetail.k422b8126') },
250
+ { key: 'pressure', label: t('dcwDetail.k3x6ff127') },
251
+ { key: 'tension', label: t('dcwDetail.k3z9xc128') },
252
+ { key: 'encoder', label: t('dcwDetail.kjb3vhs129') },
253
+ { key: 'camera', label: t('dcwDetail.k47atw130') },
254
+ { key: 'gateway', label: t('dcwDetail.k48c07131') },
255
+ ]
256
+
257
+ async function doCreateTemplate(): Promise<void> {
258
+ tplSaving.value = true
259
+ tplError.value = ''
260
+ tplOk.value = ''
261
+ try {
262
+ if (!tplForm.name.trim()) throw new Error(t('dcwDetail.k6ugbw2132'))
263
+ if (tplForm.unit.trim() === '') throw new Error(t('dcwDetail.kx3pg59133'))
264
+ if (tplForm.min === '' || tplForm.max === '') throw new Error(t('dcwDetail.k13awowo134'))
265
+ const tpl = await dcw.createTemplate({
266
+ name: tplForm.name.trim(),
267
+ ch: tplForm.ch.trim() || tplForm.name.trim(),
268
+ code: tplForm.code.trim() || 'CUSTOM',
269
+ unit: tplForm.unit.trim(),
270
+ min: Number(tplForm.min),
271
+ max: Number(tplForm.max),
272
+ decimals: Number(tplForm.decimals) || 0,
273
+ icon: tplForm.icon,
274
+ semantics: tplForm.semantics.trim() || undefined,
275
+ })
276
+ // 新模板即刻可选:添加控制节点向导自动选中它,下拉随 store 响应式更新
277
+ addTemplate.value = tpl.key
278
+ tplOk.value = t('dcwDetail.ky4e1tr185', { p0: tpl.name })
279
+ tplForm.name = ''
280
+ tplForm.ch = ''
281
+ tplForm.code = ''
282
+ tplForm.unit = ''
283
+ tplForm.min = ''
284
+ tplForm.max = ''
285
+ tplForm.semantics = ''
286
+ }
287
+ catch (err) {
288
+ tplError.value = err instanceof Error ? err.message : String(err)
289
+ }
290
+ finally {
291
+ tplSaving.value = false
292
+ }
293
+ }
294
+
295
+ // ---------- 产线开跑/停止(逐产线) ----------
296
+ const lineProductId = ref('')
297
+ const lineRecipeId = ref('')
298
+ const lineBusy = ref(false)
299
+ const lineMsg = ref('')
300
+ const lineErr = ref('')
301
+
302
+ const lineRecipes = computed(() => lineRecipesAll.value.filter(r => r.productId === lineProductId.value))
303
+
304
+ async function doLineStart(): Promise<void> {
305
+ lineBusy.value = true
306
+ lineMsg.value = ''
307
+ lineErr.value = ''
308
+ try {
309
+ if (!lineRecipeId.value) {
310
+ throw new Error(t('dcwDetail.k1b6qe0r135'))
311
+ }
312
+ await dcw.startLine(lineId.value, lineRecipeId.value)
313
+ lineMsg.value = t('dcwDetail.k17jteb9186', { p0: ls.value.productName, p1: ls.value.recipeName, p2: ls.value.runId })
314
+ }
315
+ catch (err) {
316
+ lineErr.value = err instanceof Error ? err.message : String(err)
317
+ }
318
+ finally {
319
+ lineBusy.value = false
320
+ }
321
+ }
322
+
323
+ async function doLineStop(): Promise<void> {
324
+ lineBusy.value = true
325
+ lineMsg.value = ''
326
+ lineErr.value = ''
327
+ try {
328
+ const was = `${ls.value.productName ?? ''} · ${ls.value.recipeName ?? ''}`
329
+ await dcw.stopLine(lineId.value)
330
+ lineMsg.value = t('dcwDetail.kzl49pd187', { p0: was })
331
+ }
332
+ catch (err) {
333
+ lineErr.value = err instanceof Error ? err.message : String(err)
334
+ }
335
+ finally {
336
+ lineBusy.value = false
337
+ }
338
+ }
339
+
340
+ // ---------- 产品管理 ----------
341
+ const productOpen = ref(false)
342
+ const productSaving = ref(false)
343
+ const productError = ref('')
344
+ const productForm = reactive({ name: '', description: '' })
345
+ const filterProductId = ref('')
346
+
347
+ async function doCreateProduct(): Promise<void> {
348
+ productSaving.value = true
349
+ productError.value = ''
350
+ try {
351
+ const p = await dcw.createProduct({ name: productForm.name.trim(), description: productForm.description.trim(), lineId: lineId.value })
352
+ lineProductId.value = p.id
353
+ filterProductId.value = p.id
354
+ productOpen.value = false
355
+ productForm.name = ''
356
+ productForm.description = ''
357
+ }
358
+ catch (err) {
359
+ productError.value = err instanceof Error ? err.message : String(err)
360
+ }
361
+ finally {
362
+ productSaving.value = false
363
+ }
364
+ }
365
+
366
+ const visibleRecipes = computed(() =>
367
+ filterProductId.value
368
+ ? lineRecipesAll.value.filter(r => r.productId === filterProductId.value)
369
+ : lineRecipesAll.value,
370
+ )
371
+ const productName = (id: string): string => dcw.products.find(p => p.id === id)?.name ?? id
372
+
373
+ // ---------- 产线数据查询(产品/配方/参数/时间/间隔;限定本产线通道) ----------
374
+ const query = reactive({
375
+ productId: '',
376
+ recipeId: '',
377
+ paramKey: '',
378
+ nodeId: '',
379
+ lastMin: 30,
380
+ bucketMs: 0,
381
+ })
382
+ const queryResult = ref<LineQueryResult | null>(null)
383
+ const queryBusy = ref(false)
384
+ const queryError = ref('')
385
+
386
+ async function doQuery(): Promise<void> {
387
+ queryBusy.value = true
388
+ queryError.value = ''
389
+ queryResult.value = null
390
+ try {
391
+ queryResult.value = await dcw.queryLine({
392
+ lineId: lineId.value,
393
+ productId: query.productId || undefined,
394
+ recipeId: query.recipeId || undefined,
395
+ paramKey: query.paramKey || undefined,
396
+ nodeId: query.nodeId || undefined,
397
+ fromMs: Date.now() - query.lastMin * 60_000,
398
+ toMs: Date.now(),
399
+ bucketMs: query.bucketMs > 0 ? query.bucketMs : undefined,
400
+ limit: 2000,
401
+ })
402
+ if (queryResult.value.channels.length === 0) queryError.value = t('dcwDetail.kszv5sq136')
403
+ }
404
+ catch (err) {
405
+ queryError.value = err instanceof Error ? err.message : String(err)
406
+ }
407
+ finally {
408
+ queryBusy.value = false
409
+ }
410
+ }
411
+
412
+ /** 查询参数选项(DAQ 模板 key,含自定义) */
413
+ const daqParamKeys = computed(() => dcw.templates.map(t => t.key))
414
+
415
+ // ---------- Recipe 配方管理(参数节点级绑定) ----------
416
+ const recipeOpen = ref(false)
417
+ const recipeEditing = ref<string | null>(null)
418
+ const recipeSaving = ref(false)
419
+ const recipeError = ref('')
420
+ const recipeForm = reactive({
421
+ productId: '',
422
+ name: '',
423
+ description: '',
424
+ params: [] as Array<{ nodeId: string, value: number | '', min: number | '', max: number | '' }>,
425
+ daqWindows: [] as Array<{ nodeId: string, min: number | '', max: number | '' }>,
426
+ })
427
+ const applyResult = ref<{ runId: string, ok: number, total: number } | null>(null)
428
+ const runDataView = ref<{ runId: string, data: RecipeRunData } | null>(null)
429
+ const runDataLoading = ref(false)
430
+
431
+ function openRecipeCreate(): void {
432
+ recipeEditing.value = null
433
+ recipeError.value = ''
434
+ recipeForm.productId = filterProductId.value || lineProductId.value || lineProducts.value[0]?.id || ''
435
+ recipeForm.name = ''
436
+ recipeForm.description = ''
437
+ recipeForm.params = [{ nodeId: lineNodes.value[0]?.id ?? '', value: '', min: '', max: '' }]
438
+ recipeForm.daqWindows = []
439
+ recipeOpen.value = true
440
+ }
441
+
442
+ function openRecipeEdit(id: string): void {
443
+ const r = dcw.recipes.find(x => x.id === id)
444
+ if (!r) return
445
+ recipeEditing.value = id
446
+ recipeError.value = ''
447
+ recipeForm.productId = r.productId
448
+ recipeForm.name = r.name
449
+ recipeForm.description = r.description
450
+ recipeForm.params = r.params.map(p => ({
451
+ nodeId: p.nodeId,
452
+ value: p.value,
453
+ min: p.min ?? '',
454
+ max: p.max ?? '',
455
+ }))
456
+ recipeForm.daqWindows = (r.daqWindows ?? []).map(w => ({
457
+ nodeId: w.nodeId,
458
+ min: w.min ?? '',
459
+ max: w.max ?? '',
460
+ }))
461
+ recipeOpen.value = true
462
+ }
463
+
464
+ async function saveRecipe(): Promise<void> {
465
+ recipeSaving.value = true
466
+ recipeError.value = ''
467
+ try {
468
+ const input = {
469
+ productId: recipeForm.productId,
470
+ name: recipeForm.name.trim(),
471
+ description: recipeForm.description.trim(),
472
+ params: recipeForm.params
473
+ .filter(p => p.nodeId && p.value !== '' && Number.isFinite(Number(p.value)))
474
+ .map(p => ({
475
+ nodeId: p.nodeId,
476
+ value: Number(p.value),
477
+ ...(p.min !== '' && Number.isFinite(Number(p.min)) ? { min: Number(p.min) } : {}),
478
+ ...(p.max !== '' && Number.isFinite(Number(p.max)) ? { max: Number(p.max) } : {}),
479
+ })),
480
+ daqWindows: recipeForm.daqWindows
481
+ .filter(w => w.nodeId && (w.min !== '' || w.max !== ''))
482
+ .map(w => ({
483
+ nodeId: w.nodeId,
484
+ ...(w.min !== '' && Number.isFinite(Number(w.min)) ? { min: Number(w.min) } : {}),
485
+ ...(w.max !== '' && Number.isFinite(Number(w.max)) ? { max: Number(w.max) } : {}),
486
+ })),
487
+ }
488
+ if (recipeEditing.value) await dcw.updateRecipe(recipeEditing.value, input)
489
+ else await dcw.createRecipe(input)
490
+ recipeOpen.value = false
491
+ }
492
+ catch (err) {
493
+ recipeError.value = err instanceof Error ? err.message : String(err)
494
+ }
495
+ finally {
496
+ recipeSaving.value = false
497
+ }
498
+ }
499
+
500
+ async function doApplyRecipe(id: string): Promise<void> {
501
+ applyResult.value = null
502
+ writeError.value = ''
503
+ try {
504
+ const run = await dcw.applyRecipe(id)
505
+ const ok = run.results.filter(r => r.ok).length
506
+ applyResult.value = { runId: run.id, ok, total: run.results.length }
507
+ if (ok < run.results.length) writeError.value = t('dcwDetail.ks7szkt188', { p0: run.results.filter(r => !r.ok).map(r => r.message).join(';') })
508
+ }
509
+ catch (err) {
510
+ writeError.value = err instanceof Error ? err.message : String(err)
511
+ }
512
+ }
513
+
514
+ async function doViewRun(id: string): Promise<void> {
515
+ runDataLoading.value = true
516
+ try {
517
+ const data = await dcw.runData(id)
518
+ runDataView.value = { runId: id, data }
519
+ }
520
+ catch (err) {
521
+ writeError.value = err instanceof Error ? err.message : String(err)
522
+ }
523
+ finally {
524
+ runDataLoading.value = false
525
+ }
526
+ }
527
+
528
+ /** 查询点位展示(原始值或桶均值) */
529
+ function fmtPoint(p: { value?: number, avg?: number } | undefined): string {
530
+ const v = p?.value ?? p?.avg
531
+ return v == null ? '--' : String(Math.round(v * 1000) / 1000)
532
+ }
533
+ </script>
534
+
535
+ <template>
536
+ <div
537
+ v-if="dcw.loaded && !line"
538
+ class="page"
539
+ >
540
+ <p class="banner bad">
541
+ {{ $t('dcwDetail.k19337yp018') }}
542
+ </p>
543
+ <NuxtLink
544
+ class="pill-btn"
545
+ to="/dcw"
546
+ >
547
+ {{ $t('dcwDetail.kllwu1c019') }}
548
+ </NuxtLink>
549
+ </div>
550
+ <div
551
+ v-else
552
+ class="page"
553
+ >
554
+ <div class="aw-page-head">
555
+ <div>
556
+ <p class="aw-kicker">
557
+ <span
558
+ class="line-chip"
559
+ :style="{ background: line?.color ?? '#3aa0ff' }"
560
+ />AGENTWORKSHOP / LINE / {{ line?.name ?? lineId }}
561
+ </p>
562
+ <h1>{{ line?.name ?? $t('dcwDetail.k1b2snna151') }} · {{ $t('dcwDetail.k1l0iow1137') }}</h1>
563
+ <p class="sub">
564
+ {{ line?.description || $t('dcwDetail.kzcbfki152') }}
565
+ </p>
566
+ </div>
567
+ <div class="badges mono">
568
+ <span class="badge">{{ $t('dcwDetail.k45uio082') }} {{ lineNodes.length }}</span>
569
+ <span class="badge">{{ $t('dcwDetail.k3waz1025') }} {{ lineProducts.length }}</span>
570
+ <span class="badge">{{ $t('dcwDetail.k48grv027') }} {{ lineRecipesAll.length }}</span>
571
+ </div>
572
+ </div>
573
+
574
+ <!-- 网关总控条 -->
575
+ <section class="aw-tile ctrl-card">
576
+ <div class="ctrl-left">
577
+ <button
578
+ class="aw-pill"
579
+ :class="{ running: dcw.controller.running }"
580
+ :title="$t('dcwDetail.k1snap154')"
581
+ @click="dcw.startStop(dcw.controller.running ? 'pause' : 'resume')"
582
+ >
583
+ <span :class="dcw.controller.running ? 'i-tabler-player-pause' : 'i-tabler-player-play'" />
584
+ {{ dcw.controller.running ? $t('dcwDetail.kplrsg153') : $t('dcwDetail.knxgni9169') }}
585
+ </button>
586
+ </div>
587
+ <div class="ctrl-right">
588
+ <span class="ctrl-metrics mono">
589
+ <span>{{ $t('dcwDetail.k45uio082') }} {{ dcw.controller.nodesOnline }}/{{ dcw.controller.nodesTotal }}</span>
590
+ <span class="sep">·</span>
591
+ <span :title="$t('dcwDetail.kd3ygmn001')">{{ $t('dcwDetail.k3wtib138') }} {{ dcw.controller.writesTotal }}</span>
592
+ <span class="sep">·</span>
593
+ <span
594
+ :class="{ warn: dcw.controller.writesFailed > 0 }"
595
+ :title="$t('dcwDetail.kd3znl0002')"
596
+ >{{ $t('dcwDetail.k3yit7139') }} {{ dcw.controller.writesFailed }}</span>
597
+ </span>
598
+ <button
599
+ class="aw-pill outline"
600
+ :title="$t('dcwDetail.kc4ixly003')"
601
+ @click="openTplModal"
602
+ >
603
+ <span class="i-tabler-template" />
604
+ {{ $t('dcwDetail.k1972dx9020') }}
605
+ </button>
606
+ <button
607
+ class="aw-pill add-btn"
608
+ @click="addOpen = true"
609
+ >
610
+ <span class="i-tabler-plus" />
611
+ {{ $t('dcwDetail.k1976uns021') }}
612
+ </button>
613
+ </div>
614
+ </section>
615
+
616
+ <!-- 写错误/成功横幅 -->
617
+ <p
618
+ v-if="writeError || writeOk"
619
+ class="banner"
620
+ :class="writeError ? 'bad' : 'good'"
621
+ >
622
+ {{ writeError || writeOk }}
623
+ </p>
624
+
625
+ <!-- 未分配资产收编(历史节点/产品迁移到本产线) -->
626
+ <section
627
+ v-if="unassignedNodes.length || unassignedProducts.length"
628
+ class="aw-tile adopt-card"
629
+ >
630
+ <b class="adopt-title">{{ $t('dcwDetail.kaebkky022') }}</b>
631
+ <div class="adopt-list">
632
+ <span
633
+ v-for="n in unassignedNodes"
634
+ :key="n.id"
635
+ class="adopt-chip"
636
+ >
637
+ {{ n.name }}
638
+ <button
639
+ class="mini-btn"
640
+ @click="adoptNode(n.id)"
641
+ >
642
+ {{ $t('dcwDetail.kyjp9fg023') }}
643
+ </button>
644
+ </span>
645
+ <span
646
+ v-for="p in unassignedProducts"
647
+ :key="p.id"
648
+ class="adopt-chip"
649
+ >
650
+ {{ p.name }}({{ $t('dcwDetail.k3km252140') }}<button
651
+ class="mini-btn"
652
+ @click="adoptProduct(p.id)"
653
+ >
654
+ {{ $t('dcwDetail.kyjp9fg023') }}
655
+ </button>
656
+ </span>
657
+ </div>
658
+ </section>
659
+
660
+ <!-- 产线运行控制:开跑必设配方;窗口内数采逐样本打标 -->
661
+ <section class="aw-tile line-card">
662
+ <div class="line-status">
663
+ <span
664
+ class="line-dot"
665
+ :class="{ on: ls.active }"
666
+ />
667
+ <div class="line-info">
668
+ <b>{{ ls.active ? $t('dcwDetail.k1pxrhx0154') : $t('dcwDetail.k1b2hxmx170') }}</b>
669
+ <small
670
+ v-if="ls.active"
671
+ class="mono dim"
672
+ >{{ ls.productName }} · {{ ls.recipeName }} · {{ $t('dcwDetail.k400lb075') }} {{ ls.runId }} · {{ $t('dcwDetail.k3zkt2166') }} {{ ls.startedAt?.slice(11, 19) }} {{ $t('dcwDetail.k69vag8168') }} {{ ls.taggedSamples }} {{ $t('dcwDetail.k4118o085') }}</small>
673
+ <small
674
+ v-else
675
+ class="dim"
676
+ >{{ $t('dcwDetail.k19pof14024') }}</small>
677
+ </div>
678
+ </div>
679
+ <div class="line-ctl">
680
+ <label class="ctl-sel">
681
+ <span>{{ $t('dcwDetail.k3waz1025') }}</span>
682
+ <select
683
+ v-model="lineProductId"
684
+ class="inp"
685
+ @change="lineRecipeId = ''"
686
+ >
687
+ <option value="">
688
+ {{ $t('dcwDetail.kuisodh026') }}
689
+ </option>
690
+ <option
691
+ v-for="p in lineProducts"
692
+ :key="p.id"
693
+ :value="p.id"
694
+ >
695
+ {{ p.name }}
696
+ </option>
697
+ </select>
698
+ </label>
699
+ <label class="ctl-sel">
700
+ <span>{{ $t('dcwDetail.k48grv027') }}</span>
701
+ <select
702
+ v-model="lineRecipeId"
703
+ class="inp"
704
+ :disabled="!lineProductId"
705
+ >
706
+ <option value="">
707
+ {{ $t('dcwDetail.kutxzsz028') }}
708
+ </option>
709
+ <option
710
+ v-for="r in lineRecipes"
711
+ :key="r.id"
712
+ :value="r.id"
713
+ :disabled="r.params.length === 0"
714
+ >
715
+ {{ r.name }}{{ r.params.length === 0 ? $t('dcwDetail.k1hicmch155') : $t('dcwDetail.k1lzfi4g180', { p0: r.params.length }) }}
716
+ </option>
717
+ </select>
718
+ </label>
719
+ <button
720
+ v-if="!ls.active"
721
+ class="pill-btn"
722
+ :disabled="lineBusy || !lineRecipeId"
723
+ :title="!lineRecipeId ? '开跑前必须先设定配方' : '下发配方参数并开始打标数据采集'"
724
+ @click="doLineStart"
725
+ >
726
+ {{ $t('dcwDetail.kfb8vml029') }}
727
+ </button>
728
+ <button
729
+ v-else
730
+ class="pill-btn stop"
731
+ :disabled="lineBusy"
732
+ @click="doLineStop"
733
+ >
734
+ {{ $t('dcwDetail.k1xyhd2y030') }}
735
+ </button>
736
+ </div>
737
+ <p
738
+ v-if="lineErr"
739
+ class="banner bad"
740
+ style="margin-top: 10px;"
741
+ >
742
+ {{ lineErr }}
743
+ </p>
744
+ <p
745
+ v-if="lineMsg"
746
+ class="banner good"
747
+ style="margin-top: 10px;"
748
+ >
749
+ {{ lineMsg }}
750
+ </p>
751
+ </section>
752
+
753
+ <!-- 添加控制节点模板(自定义创建) -->
754
+ <div
755
+ v-if="tplOpen"
756
+ class="modal-mask"
757
+ @click.self="tplOpen = false"
758
+ >
759
+ <div class="modal">
760
+ <h3 class="m-title">
761
+ {{ $t('dcwDetail.k1x4vam0031') }} <small class="dim mono">{{ $t('dcwDetail.k3x23c141') }} {{ dcw.templates.filter(t => t.builtin).length }} · {{ $t('dcwDetail.k3t616a142') }} {{ dcw.templates.filter(t => !t.builtin).length }}</small>
762
+ </h3>
763
+ <p class="dim tpl-hint">
764
+ {{ $t('dcwDetail.ke1iyud032') }}
765
+ </p>
766
+
767
+ <div class="tpl-chips">
768
+ <span
769
+ v-for="t in dcw.templates"
770
+ :key="t.key"
771
+ class="tpl-chip"
772
+ :title="`${t.code} · ${t.min}~${t.max} ${t.unit}${t.semantics ? ` · ${t.semantics}` : ''}`"
773
+ >
774
+ {{ t.name }} <small class="mono">{{ t.min }}~{{ t.max }}{{ t.unit }}</small>
775
+ <em
776
+ class="tpl-tag"
777
+ :class="{ builtin: t.builtin }"
778
+ >{{ t.builtin ? $t('dcwDetail.k3x23c141') : $t('dcwDetail.k3t616a142') }}</em>
779
+ </span>
780
+ </div>
781
+
782
+ <p class="sec-label">
783
+ {{ $t('dcwDetail.k169eb4s033') }}
784
+ </p>
785
+ <div class="f-grid tpl-form">
786
+ <label class="f">
787
+ <span>{{ $t('dcwDetail.k1f55q76034') }}<em>*</em></span>
788
+ <input
789
+ v-model="tplForm.name"
790
+ class="inp"
791
+ :placeholder="$t('dcwDetail.k1tooi3o004')"
792
+ >
793
+ </label>
794
+ <label class="f">
795
+ <span>{{ $t('dcwDetail.k1bqk219035') }}</span>
796
+ <input
797
+ v-model="tplForm.ch"
798
+ class="inp"
799
+ :placeholder="$t('dcwDetail.k698qz0005')"
800
+ >
801
+ </label>
802
+ <label class="f">
803
+ <span>{{ $t('dcwDetail.k1ayxrqb036') }}</span>
804
+ <input
805
+ v-model="tplForm.code"
806
+ class="inp"
807
+ placeholder="如 MOTOR · I"
808
+ >
809
+ </label>
810
+ <label class="f">
811
+ <span>{{ $t('dcwDetail.k3x4ef037') }}<em>*</em></span>
812
+ <input
813
+ v-model="tplForm.unit"
814
+ class="inp"
815
+ placeholder="如 A"
816
+ >
817
+ </label>
818
+ <label class="f">
819
+ <span>{{ $t('dcwDetail.k1l9jv5m038') }}<em>*</em></span>
820
+ <input
821
+ v-model.number="tplForm.min"
822
+ type="number"
823
+ class="inp"
824
+ :placeholder="$t('dcwDetail.kxz9174006')"
825
+ >
826
+ </label>
827
+ <label class="f">
828
+ <span>{{ $t('dcwDetail.k1l9jv4p039') }}<em>*</em></span>
829
+ <input
830
+ v-model.number="tplForm.max"
831
+ type="number"
832
+ class="inp"
833
+ :placeholder="$t('dcwDetail.kxz9167007')"
834
+ >
835
+ </label>
836
+ <label class="f">
837
+ <span>{{ $t('dcwDetail.k3mxmcx040') }}</span>
838
+ <input
839
+ v-model.number="tplForm.decimals"
840
+ type="number"
841
+ min="0"
842
+ max="4"
843
+ class="inp"
844
+ >
845
+ </label>
846
+ <label class="f">
847
+ <span>{{ $t('dcwDetail.k3xx56041') }}</span>
848
+ <select
849
+ v-model="tplForm.icon"
850
+ class="inp"
851
+ >
852
+ <option
853
+ v-for="ic in tplIcons"
854
+ :key="ic.key"
855
+ :value="ic.key"
856
+ >
857
+ {{ ic.label }}
858
+ </option>
859
+ </select>
860
+ </label>
861
+ </div>
862
+ <label class="f">
863
+ <span>{{ $t('dcwDetail.knjmyfr042') }}</span>
864
+ <textarea
865
+ v-model="tplForm.semantics"
866
+ class="inp"
867
+ rows="2"
868
+ :placeholder="$t('dcwDetail.k1qdnfzc008')"
869
+ />
870
+ </label>
871
+
872
+ <p
873
+ v-if="tplOk"
874
+ class="banner good"
875
+ style="margin-top: 10px;"
876
+ >
877
+ {{ tplOk }}
878
+ </p>
879
+ <p
880
+ v-if="tplError"
881
+ class="m-err"
882
+ >
883
+ {{ tplError }}
884
+ </p>
885
+ <div class="m-actions">
886
+ <button
887
+ class="aw-pill outline"
888
+ @click="tplOpen = false"
889
+ >
890
+ {{ $t('dcwDetail.k3x62t043') }}
891
+ </button>
892
+ <button
893
+ class="pill-btn"
894
+ :disabled="tplSaving || !tplForm.name.trim() || tplForm.unit.trim() === '' || tplForm.min === '' || tplForm.max === ''"
895
+ @click="doCreateTemplate"
896
+ >
897
+ {{ tplSaving ? $t('dcwDetail.k1bg4759156') : $t('dcwDetail.k1bg9nga171') }}
898
+ </button>
899
+ </div>
900
+ </div>
901
+ </div>
902
+
903
+ <!-- 添加控制节点向导 -->
904
+ <div
905
+ v-if="addOpen"
906
+ class="modal-mask"
907
+ @click.self="addOpen = false"
908
+ >
909
+ <div class="modal">
910
+ <h3 class="m-title">
911
+ {{ $t('dcwDetail.k1976uns021') }}
912
+ </h3>
913
+
914
+ <div class="seg-row">
915
+ <button
916
+ class="seg"
917
+ :class="{ on: addScenario === 'mock' }"
918
+ @click="addScenario = 'mock'"
919
+ >
920
+ Mock 模拟 PLC
921
+ </button>
922
+ <button
923
+ class="seg"
924
+ :class="{ on: addScenario === 'real' }"
925
+ @click="addScenario = 'real'"
926
+ >
927
+ {{ $t('dcwDetail.kyj8fen044') }}
928
+ </button>
929
+ </div>
930
+
931
+ <div class="f-grid">
932
+ <label class="f">
933
+ <span>{{ $t('dcwDetail.k1ejzwqp045') }}</span>
934
+ <select
935
+ v-model="addTemplate"
936
+ class="inp"
937
+ >
938
+ <option
939
+ v-for="t in dcw.templates"
940
+ :key="t.key"
941
+ :value="t.key"
942
+ >
943
+ {{ t.name }} · {{ t.ch }}({{ t.min }}~{{ t.max }} {{ t.unit }}){{ t.builtin ? '' : $t('dcwDetail.kr45rk9157') }}
944
+ </option>
945
+ </select>
946
+ </label>
947
+ <label class="f">
948
+ <span>{{ $t('dcwDetail.k1ce2k1y046') }}</span>
949
+ <input
950
+ v-model="addName"
951
+ class="inp"
952
+ :placeholder="$t('dcwDetail.kgpxzy3009')"
953
+ >
954
+ </label>
955
+ <label class="f">
956
+ <span>{{ $t('dcwDetail.k1vyb8im047') }}</span>
957
+ <input
958
+ v-model.number="addHold"
959
+ type="number"
960
+ min="0"
961
+ max="3600000"
962
+ step="500"
963
+ class="inp"
964
+ :placeholder="$t('dcwDetail.kb1srz0010')"
965
+ >
966
+ </label>
967
+ </div>
968
+
969
+ <!-- 数据语义标定 encode(物理设定值 → PLC 设定值;mock/真实均可用) -->
970
+ <div class="f-grid cal-form">
971
+ <label class="f">
972
+ <span>{{ $t('dcwDetail.kiune1f048') }}</span>
973
+ <select
974
+ v-model="addTransform.kind"
975
+ class="inp"
976
+ >
977
+ <option value="none">
978
+ {{ $t('dcwDetail.kkzy0k049') }}
979
+ </option>
980
+ <option value="linear">
981
+ 线性标定:PLC值 = (物理值 - offset) / scale
982
+ </option>
983
+ </select>
984
+ </label>
985
+ <label class="f">
986
+ <span>scale / offset</span>
987
+ <div style="display: flex; gap: 6px;">
988
+ <input
989
+ v-model.number="addTransform.scale"
990
+ type="number"
991
+ step="0.1"
992
+ class="inp"
993
+ :disabled="addTransform.kind !== 'linear'"
994
+ title="decoder 斜率(≠0):物理值 = scale × PLC值 + offset;下发时自动取逆"
995
+ >
996
+ <input
997
+ v-model.number="addTransform.offset"
998
+ type="number"
999
+ step="0.1"
1000
+ class="inp"
1001
+ :disabled="addTransform.kind !== 'linear'"
1002
+ title="decoder 截距"
1003
+ >
1004
+ </div>
1005
+ </label>
1006
+ </div>
1007
+
1008
+ <template v-if="addScenario === 'real'">
1009
+ <div class="f-grid">
1010
+ <label class="f">
1011
+ <span>{{ $t('dcwDetail.k1kt87rx050') }}</span>
1012
+ <select
1013
+ v-model="addDriver"
1014
+ class="inp"
1015
+ >
1016
+ <option
1017
+ v-for="d in DCW_DRIVERS.filter(x => x.status !== 'builtin')"
1018
+ :key="d.kind"
1019
+ :value="d.kind"
1020
+ >
1021
+ {{ d.label }}
1022
+ </option>
1023
+ </select>
1024
+ </label>
1025
+ </div>
1026
+ <div
1027
+ v-if="addFields.length"
1028
+ class="f-grid driver-form"
1029
+ >
1030
+ <label
1031
+ v-for="f in addFields"
1032
+ :key="f.key"
1033
+ class="f"
1034
+ >
1035
+ <span>{{ f.label }}<em
1036
+ v-if="f.required"
1037
+ style="color: var(--tone-danger-dot); font-style: normal;"
1038
+ >*</em></span>
1039
+ <select
1040
+ v-if="f.type === 'select'"
1041
+ v-model="addCfg[f.key]"
1042
+ class="inp"
1043
+ >
1044
+ <option
1045
+ v-for="o in f.options"
1046
+ :key="o.value"
1047
+ :value="o.value"
1048
+ >
1049
+ {{ o.label }}
1050
+ </option>
1051
+ </select>
1052
+ <input
1053
+ v-else
1054
+ v-model="addCfg[f.key]"
1055
+ :type="f.type === 'number' ? 'number' : 'text'"
1056
+ :placeholder="f.placeholder"
1057
+ class="inp"
1058
+ >
1059
+ <small
1060
+ v-if="f.hint"
1061
+ class="hint"
1062
+ >{{ f.hint }}</small>
1063
+ </label>
1064
+ </div>
1065
+ <div class="test-row">
1066
+ <button
1067
+ class="pill-btn outline"
1068
+ :disabled="addTesting"
1069
+ @click="doTestConnection"
1070
+ >
1071
+ {{ addTesting ? $t('dcwDetail.k1fsh720158') : $t('dcwDetail.k1fstglk172') }}
1072
+ </button>
1073
+ <span
1074
+ v-if="addTest"
1075
+ class="test-result"
1076
+ :class="addTest.ok ? 'good' : 'bad'"
1077
+ >{{ addTest.ok ? '✓' : '✗' }} {{ addTest.message }}</span>
1078
+ </div>
1079
+ </template>
1080
+
1081
+ <label class="f">
1082
+ <span>{{ $t('dcwDetail.k1hgizn7051') }}</span>
1083
+ <textarea
1084
+ v-model="addSemantics"
1085
+ class="inp"
1086
+ rows="2"
1087
+ :placeholder="$t('dcwDetail.k12bnyt9011')"
1088
+ />
1089
+ </label>
1090
+
1091
+ <p
1092
+ v-if="addError"
1093
+ class="m-err"
1094
+ >
1095
+ {{ addError }}
1096
+ </p>
1097
+
1098
+ <div class="m-actions">
1099
+ <button
1100
+ class="aw-pill outline"
1101
+ @click="addOpen = false"
1102
+ >
1103
+ {{ $t('dcwDetail.k3xdnn052') }}
1104
+ </button>
1105
+ <button
1106
+ class="aw-pill"
1107
+ :disabled="addSaving"
1108
+ @click="doAddNode"
1109
+ >
1110
+ {{ addSaving ? $t('dcwDetail.k1bg4759156') : $t('dcwDetail.k1bge46t173') }}
1111
+ </button>
1112
+ </div>
1113
+ </div>
1114
+ </div>
1115
+
1116
+ <!-- 控制节点清单 -->
1117
+ <a-spin :spinning="!dcw.loaded && !dcw.error">
1118
+ <section class="aw-tile table-card">
1119
+ <table class="nodes-table">
1120
+ <thead>
1121
+ <tr>
1122
+ <th>{{ $t('dcwDetail.k1e2dtkt053') }}</th>
1123
+ <th>{{ $t('dcwDetail.k403cy054') }}</th>
1124
+ <th>{{ $t('dcwDetail.k42w8s055') }}</th>
1125
+ <th>{{ $t('dcwDetail.k1deqh0d056') }}</th>
1126
+ <th>{{ $t('dcwDetail.k1k79ec9057') }}</th>
1127
+ <th>{{ $t('dcwDetail.k1dexou6058') }}</th>
1128
+ <th>{{ $t('dcwDetail.k1b1nnaa059') }}</th>
1129
+ <th>{{ $t('dcwDetail.k1i8rtqt060') }}</th>
1130
+ <th class="right">
1131
+ {{ $t('dcwDetail.k40aa6061') }}
1132
+ </th>
1133
+ </tr>
1134
+ </thead>
1135
+ <tbody>
1136
+ <tr
1137
+ v-for="n in lineNodes"
1138
+ :key="n.id"
1139
+ :class="{ 'node-paused': !n.enabled }"
1140
+ >
1141
+ <td>
1142
+ <span class="mono dim">{{ n.id.slice(0, 8) }}</span>
1143
+ <b>{{ n.name }}</b>
1144
+ <small class="mono ch">{{ dcwTemplateRefCh(n.templateRef) }}</small>
1145
+ </td>
1146
+ <td>
1147
+ <button
1148
+ class="ctrl-toggle"
1149
+ :class="{ on: n.enabled }"
1150
+ :disabled="togglingId === n.id"
1151
+ :title="n.enabled ? '暂停该节点控制:下发将被拒绝' : '开启该节点控制:恢复可下发'"
1152
+ @click="toggleControl(n.id, !n.enabled)"
1153
+ >
1154
+ <span class="ct-dot" />
1155
+ {{ n.enabled ? $t('dcwDetail.k3o3ib3159') : $t('dcwDetail.k3n93ed062') }}
1156
+ </button>
1157
+ </td>
1158
+ <td>
1159
+ <span
1160
+ v-if="!n.enabled"
1161
+ class="st-pill paused"
1162
+ >{{ $t('dcwDetail.k3n93ed062') }}</span>
1163
+ <span
1164
+ v-else
1165
+ class="st-pill"
1166
+ :class="n.state"
1167
+ >{{ stateLabel[n.state] ?? n.state }}</span>
1168
+ </td>
1169
+ <td class="mono val">
1170
+ {{ n.value != null ? n.value.toFixed(n.decimals) : '--' }}
1171
+ <small>{{ n.unit }}</small>
1172
+ <small
1173
+ v-if="n.lastAckAt"
1174
+ class="dim"
1175
+ > · {{ n.lastAckAt.slice(11, 19) }}</small>
1176
+ </td>
1177
+ <td>
1178
+ <div class="write-row">
1179
+ <input
1180
+ v-model.number="setInputs[n.id]"
1181
+ type="number"
1182
+ class="inp write-inp mono"
1183
+ :placeholder="`${n.min}~${n.max}`"
1184
+ :min="n.min"
1185
+ :max="n.max"
1186
+ :step="10 ** -n.decimals"
1187
+ :disabled="!n.enabled"
1188
+ :title="!n.enabled ? '当前节点暂停:开启控制后方可设定' : ''"
1189
+ >
1190
+ <button
1191
+ class="mini-btn write-btn"
1192
+ :disabled="!n.enabled || writingId === n.id || setInputs[n.id] == null || setInputs[n.id] === ''"
1193
+ :title="!n.enabled ? '当前节点暂停:开启控制后方可设定' : '下发设定值'"
1194
+ @click="doWrite(n.id, Number(setInputs[n.id]))"
1195
+ >
1196
+ {{ writingId === n.id ? $t('dcwDetail.k3l3h80123') : $t('dcwDetail.k3w6td174') }}
1197
+ </button>
1198
+ </div>
1199
+ </td>
1200
+ <td class="mono dim">
1201
+ {{ n.min }} ~ {{ n.max }} {{ n.unit }}
1202
+ </td>
1203
+ <td class="mono">
1204
+ {{ n.holdIntervalMs == null ? $t('dcwDetail.k3zul4160') : `${n.holdIntervalMs}ms` }}
1205
+ </td>
1206
+ <td>{{ deviceName(n.deviceBindingId) }}</td>
1207
+ <td class="right">
1208
+ <button
1209
+ class="mini-btn danger"
1210
+ @click="dcw.removeNode(n.id)"
1211
+ >
1212
+ {{ $t('dcwDetail.k3xakp063') }}
1213
+ </button>
1214
+ </td>
1215
+ </tr>
1216
+ <tr v-if="dcw.loaded && lineNodes.length === 0">
1217
+ <td colspan="9">
1218
+ <div
1219
+ class="pane-empty"
1220
+ style="min-height: 120px;"
1221
+ >
1222
+ <p class="pe-sub">
1223
+ {{ $t('dcwDetail.k1wtecyw064') }}
1224
+ </p>
1225
+ </div>
1226
+ </td>
1227
+ </tr>
1228
+ </tbody>
1229
+ </table>
1230
+ </section>
1231
+ </a-spin>
1232
+
1233
+ <!-- 产品与配方管理 -->
1234
+ <section class="aw-tile recipe-card">
1235
+ <div class="recipe-hd">
1236
+ <h3>{{ $t('dcwDetail.k1j8w3hd065') }}</h3>
1237
+ <span class="panel-tag mono">{{ lineProducts.length }} {{ $t('dcwDetail.k1av3wgs143') }} {{ visibleRecipes.length }} {{ $t('dcwDetail.k48grv027') }}</span>
1238
+ <button
1239
+ class="pill-btn"
1240
+ style="margin-left: auto;"
1241
+ @click="openRecipeCreate"
1242
+ >
1243
+ {{ $t('dcwDetail.k1akm6dc066') }}
1244
+ </button>
1245
+ <button
1246
+ class="mini-btn"
1247
+ @click="productOpen = true"
1248
+ >
1249
+ {{ $t('dcwDetail.k1aka0ki067') }}
1250
+ </button>
1251
+ </div>
1252
+ <p class="recipe-sub">
1253
+ {{ $t('dcwDetail.k6bl2q8068') }}
1254
+ </p>
1255
+
1256
+ <!-- 产品管理行 -->
1257
+ <div
1258
+ v-if="lineProducts.length"
1259
+ class="product-row"
1260
+ >
1261
+ <button
1262
+ class="prod-chip"
1263
+ :class="{ on: filterProductId === '' }"
1264
+ @click="filterProductId = ''"
1265
+ >
1266
+ {{ $t('dcwDetail.k3x4t1069') }}
1267
+ </button>
1268
+ <button
1269
+ v-for="p in lineProducts"
1270
+ :key="p.id"
1271
+ class="prod-chip"
1272
+ :class="{ on: filterProductId === p.id }"
1273
+ :title="p.description"
1274
+ @click="filterProductId = p.id"
1275
+ >
1276
+ {{ p.name }}
1277
+ <span
1278
+ class="prod-del"
1279
+ :title="$t('dcwDetail.k1bpfjgx012')"
1280
+ @click.stop="dcw.removeProduct(p.id)"
1281
+ >×</span>
1282
+ </button>
1283
+ </div>
1284
+ <p
1285
+ v-else
1286
+ class="dim"
1287
+ style="margin: 6px 0 12px; font-size: 12px;"
1288
+ >
1289
+ {{ $t('dcwDetail.knov3zg070') }}
1290
+ </p>
1291
+
1292
+ <div class="recipe-grid">
1293
+ <div
1294
+ v-for="r in visibleRecipes"
1295
+ :key="r.id"
1296
+ class="recipe-item"
1297
+ >
1298
+ <div class="recipe-name">
1299
+ <b>{{ r.name }}</b>
1300
+ <small class="dim">{{ productName(r.productId) }}</small>
1301
+ <small class="mono dim">{{ r.id }}</small>
1302
+ </div>
1303
+ <p
1304
+ v-if="r.description"
1305
+ class="dim desc"
1306
+ >
1307
+ {{ r.description }}
1308
+ </p>
1309
+ <div class="recipe-params mono">
1310
+ <span
1311
+ v-for="(p, i) in r.params"
1312
+ :key="i"
1313
+ class="param-chip"
1314
+ >
1315
+ {{ (dcw.templates.find(t => t.key === (p.templateRef ?? '').replace('dcw-', ''))?.ch ?? p.templateRef) }} = {{ p.value }}
1316
+ </span>
1317
+ <span
1318
+ v-for="(w, i) in r.daqWindows"
1319
+ :key="`w-${i}`"
1320
+ class="param-chip daqwin"
1321
+ :title="$t('dcwDetail.k1l11api013')"
1322
+ >
1323
+ ◎ {{ daqNodeCh(w.nodeId) }} ∈ [{{ w.min ?? '-∞' }}, {{ w.max ?? '+∞' }}]
1324
+ </span>
1325
+ </div>
1326
+ <div class="recipe-actions">
1327
+ <button
1328
+ class="pill-btn"
1329
+ @click="doApplyRecipe(r.id)"
1330
+ >
1331
+ {{ $t('dcwDetail.k1497qvr071') }}
1332
+ </button>
1333
+ <button
1334
+ class="mini-btn"
1335
+ @click="openRecipeEdit(r.id)"
1336
+ >
1337
+ {{ $t('dcwDetail.k45eb0072') }}
1338
+ </button>
1339
+ <button
1340
+ class="mini-btn danger"
1341
+ @click="dcw.removeRecipe(r.id)"
1342
+ >
1343
+ {{ $t('dcwDetail.k3xakp063') }}
1344
+ </button>
1345
+ </div>
1346
+ </div>
1347
+ <div
1348
+ v-if="visibleRecipes.length === 0"
1349
+ class="pane-empty"
1350
+ style="grid-column: 1 / -1;"
1351
+ >
1352
+ {{ $t('dcwDetail.keko1xy073') }}
1353
+ </div>
1354
+ </div>
1355
+
1356
+ <div
1357
+ v-if="applyResult"
1358
+ class="banner good"
1359
+ >
1360
+ {{ $t('dcwDetail.k1l3hrvp144') }} {{ applyResult.runId }} {{ $t('dcwDetail.k1g3lh2v167') }}{{ applyResult.ok }}/{{ applyResult.total }} {{ $t('dcwDetail.k1bqci06149') }}
1361
+ <button
1362
+ class="mini-btn"
1363
+ style="margin-left: 10px;"
1364
+ :disabled="runDataLoading"
1365
+ @click="doViewRun(applyResult.runId)"
1366
+ >
1367
+ {{ runDataLoading ? $t('dcwDetail.k1br0ij9161') : $t('dcwDetail.kxwei5175') }}
1368
+ </button>
1369
+ </div>
1370
+
1371
+ <!-- 批次列表 -->
1372
+ <div
1373
+ v-if="lineRuns.length"
1374
+ class="runs"
1375
+ >
1376
+ <p class="sec-label">
1377
+ {{ $t('dcwDetail.k1rjer8d074') }}
1378
+ </p>
1379
+ <table class="nodes-table">
1380
+ <thead>
1381
+ <tr>
1382
+ <th>{{ $t('dcwDetail.k400lb075') }}</th>
1383
+ <th>{{ $t('dcwDetail.k48grv027') }}</th>
1384
+ <th>{{ $t('dcwDetail.k4497j076') }}</th>
1385
+ <th>{{ $t('dcwDetail.k1bqhtmu077') }}</th>
1386
+ <th class="right">
1387
+ {{ $t('dcwDetail.k40aa6061') }}
1388
+ </th>
1389
+ </tr>
1390
+ </thead>
1391
+ <tbody>
1392
+ <tr
1393
+ v-for="run in [...lineRuns].reverse().slice(0, 8)"
1394
+ :key="run.id"
1395
+ >
1396
+ <td class="mono">
1397
+ {{ run.id }}
1398
+ </td>
1399
+ <td>{{ run.recipeName }}</td>
1400
+ <td class="mono dim">
1401
+ {{ run.startedAt.slice(5, 19) }} ~ {{ run.endedAt ? run.endedAt.slice(5, 19) : $t('dcwDetail.k3vpfg9162') }}
1402
+ </td>
1403
+ <td>
1404
+ <span
1405
+ class="st-pill"
1406
+ :class="run.results.every(r => r.ok) ? 'ok' : 'warn'"
1407
+ >{{ run.results.filter(r => r.ok).length }}/{{ run.results.length }}</span>
1408
+ </td>
1409
+ <td class="right">
1410
+ <button
1411
+ class="mini-btn"
1412
+ :disabled="runDataLoading"
1413
+ @click="doViewRun(run.id)"
1414
+ >
1415
+ {{ $t('dcwDetail.k1dzwrdp078') }}
1416
+ </button>
1417
+ <button
1418
+ v-if="!run.endedAt"
1419
+ class="mini-btn"
1420
+ @click="dcw.closeRun(run.id)"
1421
+ >
1422
+ {{ $t('dcwDetail.k1blr7y7079') }}
1423
+ </button>
1424
+ </td>
1425
+ </tr>
1426
+ </tbody>
1427
+ </table>
1428
+ </div>
1429
+
1430
+ <!-- 批次数据视图 -->
1431
+ <div
1432
+ v-if="runDataView"
1433
+ class="modal-mask"
1434
+ @click.self="runDataView = null"
1435
+ >
1436
+ <div class="modal">
1437
+ <h3 class="m-title">
1438
+ {{ $t('dcwDetail.k11lq94k145') }} {{ runDataView.data.run.recipeName }}({{ runDataView.runId }})
1439
+ </h3>
1440
+ <p class="sec-label">
1441
+ {{ $t('dcwDetail.k1me41w8080') }}
1442
+ </p>
1443
+ <table class="nodes-table">
1444
+ <thead>
1445
+ <tr>
1446
+ <th>{{ $t('dcwDetail.k48hde081') }}</th>
1447
+ <th>{{ $t('dcwDetail.k45uio082') }}</th>
1448
+ <th>{{ $t('dcwDetail.k40t11083') }}</th>
1449
+ <th>{{ $t('dcwDetail.k3xuaw084') }}</th>
1450
+ <th>min ~ max</th>
1451
+ <th>{{ $t('dcwDetail.k4118o085') }}</th>
1452
+ </tr>
1453
+ </thead>
1454
+ <tbody>
1455
+ <tr
1456
+ v-for="d in runDataView.data.daq"
1457
+ :key="d.nodeId"
1458
+ >
1459
+ <td>{{ d.ch }}<small class="mono dim"> {{ d.unit }}</small></td>
1460
+ <td>{{ d.nodeName }}</td>
1461
+ <td class="mono">
1462
+ {{ d.latest }}
1463
+ </td>
1464
+ <td class="mono">
1465
+ {{ d.avg }}
1466
+ </td>
1467
+ <td class="mono dim">
1468
+ {{ d.min }} ~ {{ d.max }}
1469
+ </td>
1470
+ <td class="mono dim">
1471
+ {{ d.cnt }}
1472
+ </td>
1473
+ </tr>
1474
+ <tr v-if="runDataView.data.daq.length === 0">
1475
+ <td
1476
+ colspan="6"
1477
+ class="dim"
1478
+ style="text-align: center; padding: 12px;"
1479
+ >
1480
+ {{ $t('dcwDetail.kd457ry086') }}
1481
+ </td>
1482
+ </tr>
1483
+ </tbody>
1484
+ </table>
1485
+ <p class="sec-label">
1486
+ {{ $t('dcwDetail.k12b7cxs087') }}
1487
+ </p>
1488
+ <table class="nodes-table">
1489
+ <thead>
1490
+ <tr>
1491
+ <th>{{ $t('dcwDetail.k3xbjr088') }}</th>
1492
+ <th>{{ $t('dcwDetail.k45uio082') }}</th>
1493
+ <th>{{ $t('dcwDetail.k3ncbsh089') }}</th>
1494
+ <th>{{ $t('dcwDetail.k3lh3mz090') }}</th>
1495
+ <th>{{ $t('dcwDetail.k454pg091') }}</th>
1496
+ <th>{{ $t('dcwDetail.k40ieu092') }}</th>
1497
+ </tr>
1498
+ </thead>
1499
+ <tbody>
1500
+ <tr
1501
+ v-for="(w, i) in runDataView.data.writes"
1502
+ :key="i"
1503
+ >
1504
+ <td>{{ w.param }}</td>
1505
+ <td>{{ w.nodeName }}</td>
1506
+ <td class="mono">
1507
+ {{ w.eng }}
1508
+ </td>
1509
+ <td class="mono dim">
1510
+ {{ w.raw }}
1511
+ </td>
1512
+ <td>
1513
+ <span
1514
+ class="st-pill"
1515
+ :class="w.ok ? 'ok' : 'alarm'"
1516
+ >{{ w.ok ? 'ACK' : $t('dcwDetail.k3yit7139') }}</span>
1517
+ </td>
1518
+ <td class="mono dim">
1519
+ {{ w.at.slice(5, 19) }}
1520
+ </td>
1521
+ </tr>
1522
+ <tr v-if="runDataView.data.writes.length === 0">
1523
+ <td
1524
+ colspan="6"
1525
+ class="dim"
1526
+ style="text-align: center; padding: 12px;"
1527
+ >
1528
+ {{ $t('dcwDetail.knzcp8i093') }}
1529
+ </td>
1530
+ </tr>
1531
+ </tbody>
1532
+ </table>
1533
+ <div class="m-actions">
1534
+ <button
1535
+ class="aw-pill outline"
1536
+ @click="runDataView = null"
1537
+ >
1538
+ {{ $t('dcwDetail.k3x62t043') }}
1539
+ </button>
1540
+ </div>
1541
+ </div>
1542
+ </div>
1543
+ </section>
1544
+
1545
+ <!-- 配方编辑弹窗 -->
1546
+ <div
1547
+ v-if="recipeOpen"
1548
+ class="modal-mask"
1549
+ @click.self="recipeOpen = false"
1550
+ >
1551
+ <div class="modal">
1552
+ <h3 class="m-title">
1553
+ {{ recipeEditing ? $t('dcwDetail.k1iiwk0i163') : $t('dcwDetail.k1efq0r9176') }}
1554
+ </h3>
1555
+ <div class="f-grid">
1556
+ <label class="f">
1557
+ <span>{{ $t('dcwDetail.k1dw4fzf094') }}<em>*</em></span>
1558
+ <select
1559
+ v-model="recipeForm.productId"
1560
+ class="inp"
1561
+ >
1562
+ <option
1563
+ v-for="p in lineProducts"
1564
+ :key="p.id"
1565
+ :value="p.id"
1566
+ >
1567
+ {{ p.name }}
1568
+ </option>
1569
+ </select>
1570
+ </label>
1571
+ <label class="f">
1572
+ <span>{{ $t('dcwDetail.k1l3f8so095') }}<em>*</em></span>
1573
+ <input
1574
+ v-model="recipeForm.name"
1575
+ class="inp"
1576
+ :placeholder="$t('dcwDetail.k12c11vm014')"
1577
+ >
1578
+ </label>
1579
+ <label class="f">
1580
+ <span>{{ $t('dcwDetail.k40gkk096') }}</span>
1581
+ <input
1582
+ v-model="recipeForm.description"
1583
+ class="inp"
1584
+ :placeholder="$t('dcwDetail.k1hxx8hy015')"
1585
+ >
1586
+ </label>
1587
+ </div>
1588
+ <p class="sec-label">
1589
+ {{ $t('dcwDetail.ka1kfgh097') }}
1590
+ </p>
1591
+ <div
1592
+ v-for="(p, i) in recipeForm.params"
1593
+ :key="i"
1594
+ class="param-row"
1595
+ >
1596
+ <label class="f">
1597
+ <span>{{ $t('dcwDetail.k1e2dtkt053') }}</span>
1598
+ <select
1599
+ v-model="p.nodeId"
1600
+ class="inp"
1601
+ >
1602
+ <option
1603
+ v-for="n in lineNodes"
1604
+ :key="n.id"
1605
+ :value="n.id"
1606
+ >
1607
+ {{ n.name }}({{ dcwTemplateRefCh(n.templateRef) }} · {{ n.min }}~{{ n.max }} {{ n.unit }})
1608
+ </option>
1609
+ </select>
1610
+ </label>
1611
+ <label class="f">
1612
+ <span>{{ $t('dcwDetail.k3renp2098') }}<em>*</em></span>
1613
+ <input
1614
+ v-model.number="p.value"
1615
+ type="number"
1616
+ class="inp"
1617
+ :step="0.1"
1618
+ >
1619
+ </label>
1620
+ <label class="f">
1621
+ <span>{{ $t('dcwDetail.k134rw3b146') }}{{ nodeMin(p.nodeId) ?? '-' }})</span>
1622
+ <input
1623
+ v-model.number="p.min"
1624
+ type="number"
1625
+ class="inp"
1626
+ :step="0.1"
1627
+ :placeholder="$t('dcwDetail.k5vew9z016')"
1628
+ >
1629
+ </label>
1630
+ <label class="f">
1631
+ <span>{{ $t('dcwDetail.khrv911147') }}{{ nodeMax(p.nodeId) ?? '-' }})</span>
1632
+ <input
1633
+ v-model.number="p.max"
1634
+ type="number"
1635
+ class="inp"
1636
+ :step="0.1"
1637
+ :placeholder="$t('dcwDetail.kzoh5pr017')"
1638
+ >
1639
+ </label>
1640
+ <button
1641
+ class="mini-btn danger param-del"
1642
+ @click="recipeForm.params.splice(i, 1)"
1643
+ >
1644
+ {{ $t('dcwDetail.k44idg099') }}
1645
+ </button>
1646
+ </div>
1647
+ <button
1648
+ class="mini-btn"
1649
+ @click="recipeForm.params.push({ nodeId: lineNodes[0]?.id ?? '', value: '', min: '', max: '' })"
1650
+ >
1651
+ {{ $t('dcwDetail.k1broh7h100') }}
1652
+ </button>
1653
+ <p class="sec-label">
1654
+ {{ $t('dcwDetail.k1h5gues101') }}
1655
+ </p>
1656
+ <div
1657
+ v-for="(w, i) in recipeForm.daqWindows"
1658
+ :key="`dw-${i}`"
1659
+ class="param-row"
1660
+ >
1661
+ <label class="f">
1662
+ <span>{{ $t('dcwDetail.k1empnnb102') }}</span>
1663
+ <select
1664
+ v-model="w.nodeId"
1665
+ class="inp"
1666
+ >
1667
+ <option
1668
+ v-for="n in lineDaqNodes"
1669
+ :key="n.id"
1670
+ :value="n.id"
1671
+ >
1672
+ {{ n.name }}({{ n.min }}~{{ n.max }} {{ n.unit }})
1673
+ </option>
1674
+ </select>
1675
+ </label>
1676
+ <label class="f">
1677
+ <span>{{ $t('dcwDetail.kqm08ap103') }}</span>
1678
+ <input
1679
+ v-model.number="w.min"
1680
+ type="number"
1681
+ class="inp"
1682
+ :step="0.1"
1683
+ >
1684
+ </label>
1685
+ <label class="f">
1686
+ <span>{{ $t('dcwDetail.kpypf8g104') }}</span>
1687
+ <input
1688
+ v-model.number="w.max"
1689
+ type="number"
1690
+ class="inp"
1691
+ :step="0.1"
1692
+ >
1693
+ </label>
1694
+ <button
1695
+ class="mini-btn danger param-del"
1696
+ @click="recipeForm.daqWindows.splice(i, 1)"
1697
+ >
1698
+ {{ $t('dcwDetail.k44idg099') }}
1699
+ </button>
1700
+ </div>
1701
+ <button
1702
+ class="mini-btn"
1703
+ :disabled="lineDaqNodes.length === 0"
1704
+ :title="lineDaqNodes.length === 0 ? '本产线暂无数采节点' : ''"
1705
+ @click="recipeForm.daqWindows.push({ nodeId: lineDaqNodes[0]?.id ?? '', min: '', max: '' })"
1706
+ >
1707
+ {{ $t('dcwDetail.kv1de1p105') }}
1708
+ </button>
1709
+ <p
1710
+ v-if="recipeError"
1711
+ class="m-err"
1712
+ >
1713
+ {{ recipeError }}
1714
+ </p>
1715
+ <div class="m-actions">
1716
+ <button
1717
+ class="aw-pill outline"
1718
+ @click="recipeOpen = false"
1719
+ >
1720
+ {{ $t('dcwDetail.k3xdnn052') }}
1721
+ </button>
1722
+ <button
1723
+ class="pill-btn"
1724
+ :disabled="recipeSaving"
1725
+ @click="saveRecipe"
1726
+ >
1727
+ {{ recipeSaving ? $t('dcwDetail.k1b38d59164') : $t('dcwDetail.k1b3kwg0177') }}
1728
+ </button>
1729
+ </div>
1730
+ </div>
1731
+ </div>
1732
+
1733
+ <!-- 产线数据查询(产品/配方/参数/时间/间隔) -->
1734
+ <section class="aw-tile query-card">
1735
+ <p class="sec-label">
1736
+ {{ $t('dcwDetail.k1us3jse106') }}
1737
+ </p>
1738
+ <div class="q-grid">
1739
+ <label class="f">
1740
+ <span>{{ $t('dcwDetail.k3waz1025') }}</span>
1741
+ <select
1742
+ v-model="query.productId"
1743
+ class="inp"
1744
+ @change="query.recipeId = ''"
1745
+ >
1746
+ <option value="">
1747
+ {{ $t('dcwDetail.k1bkl1jx107') }}
1748
+ </option>
1749
+ <option
1750
+ v-for="p in lineProducts"
1751
+ :key="p.id"
1752
+ :value="p.id"
1753
+ >
1754
+ {{ p.name }}
1755
+ </option>
1756
+ </select>
1757
+ </label>
1758
+ <label class="f">
1759
+ <span>{{ $t('dcwDetail.k48grv027') }}</span>
1760
+ <select
1761
+ v-model="query.recipeId"
1762
+ class="inp"
1763
+ :disabled="!query.productId"
1764
+ >
1765
+ <option value="">
1766
+ {{ $t('dcwDetail.k1bkx7cr108') }}
1767
+ </option>
1768
+ <option
1769
+ v-for="r in visibleRecipes"
1770
+ :key="r.id"
1771
+ :value="r.id"
1772
+ >
1773
+ {{ r.name }}
1774
+ </option>
1775
+ </select>
1776
+ </label>
1777
+ <label class="f">
1778
+ <span>{{ $t('dcwDetail.k1demcae109') }}</span>
1779
+ <select
1780
+ v-model="query.paramKey"
1781
+ class="inp"
1782
+ >
1783
+ <option value="">
1784
+ {{ $t('dcwDetail.k1bkx7ya110') }}
1785
+ </option>
1786
+ <option
1787
+ v-for="k in daqParamKeys"
1788
+ :key="k"
1789
+ :value="k"
1790
+ >
1791
+ {{ k }}
1792
+ </option>
1793
+ </select>
1794
+ </label>
1795
+ <label class="f">
1796
+ <span>{{ $t('dcwDetail.k45uio082') }}</span>
1797
+ <select
1798
+ v-model="query.nodeId"
1799
+ class="inp"
1800
+ >
1801
+ <option value="">
1802
+ {{ $t('dcwDetail.k1bkul3k111') }}
1803
+ </option>
1804
+ <option
1805
+ v-for="n in lineDaqNodes"
1806
+ :key="n.id"
1807
+ :value="n.id"
1808
+ >
1809
+ {{ n.name }}
1810
+ </option>
1811
+ </select>
1812
+ </label>
1813
+ <label class="f">
1814
+ <span>{{ $t('dcwDetail.kx2ojn0112') }}</span>
1815
+ <input
1816
+ v-model.number="query.lastMin"
1817
+ type="number"
1818
+ min="1"
1819
+ max="10080"
1820
+ class="inp"
1821
+ >
1822
+ </label>
1823
+ <label class="f">
1824
+ <span>{{ $t('dcwDetail.kjvnvn4113') }}</span>
1825
+ <input
1826
+ v-model.number="query.bucketMs"
1827
+ type="number"
1828
+ min="0"
1829
+ step="100"
1830
+ class="inp"
1831
+ >
1832
+ </label>
1833
+ <div class="f q-actions">
1834
+ <button
1835
+ class="pill-btn"
1836
+ :disabled="queryBusy"
1837
+ @click="doQuery"
1838
+ >
1839
+ {{ queryBusy ? $t('dcwDetail.k1eyx09r165') : $t('dcwDetail.k416ek178') }}
1840
+ </button>
1841
+ </div>
1842
+ </div>
1843
+ <p
1844
+ v-if="queryError"
1845
+ class="banner bad"
1846
+ style="margin-top: 8px;"
1847
+ >
1848
+ {{ queryError }}
1849
+ </p>
1850
+ <div
1851
+ v-if="queryResult"
1852
+ class="q-result"
1853
+ >
1854
+ <table class="nodes-table">
1855
+ <thead>
1856
+ <tr>
1857
+ <th>{{ $t('dcwDetail.k48hde081') }}</th>
1858
+ <th>{{ $t('dcwDetail.k45uio082') }}</th>
1859
+ <th>{{ $t('dcwDetail.k42kcu114') }}</th>
1860
+ <th>{{ $t('dcwDetail.k49ujb115') }}</th>
1861
+ <th>{{ $t('dcwDetail.k40pvw116') }}</th>
1862
+ <th>{{ $t('dcwDetail.k3xho3117') }}</th>
1863
+ </tr>
1864
+ </thead>
1865
+ <tbody>
1866
+ <tr
1867
+ v-for="c in queryResult.channels"
1868
+ :key="c.nodeId"
1869
+ >
1870
+ <td>{{ c.ch }}<small class="mono dim"> {{ c.unit }}</small></td>
1871
+ <td>{{ c.nodeName }}</td>
1872
+ <td class="mono">
1873
+ {{ c.points.length }}
1874
+ </td>
1875
+ <td class="mono">
1876
+ {{ fmtPoint(c.points[0]) }}
1877
+ </td>
1878
+ <td class="mono">
1879
+ {{ fmtPoint(c.points[c.points.length - 1]) }}
1880
+ </td>
1881
+ <td class="mono dim">
1882
+ {{ new Date(c.points[0]?.at ?? 0).toLocaleTimeString() }} ~ {{ new Date(c.points[c.points.length - 1]?.at ?? 0).toLocaleTimeString() }}
1883
+ </td>
1884
+ </tr>
1885
+ </tbody>
1886
+ </table>
1887
+ </div>
1888
+ </section>
1889
+
1890
+ <!-- 写历史 -->
1891
+ <section
1892
+ v-if="lineHistory.length"
1893
+ class="aw-tile table-card"
1894
+ >
1895
+ <p class="sec-label">
1896
+ {{ $t('dcwDetail.kw7ym9b148') }} {{ lineHistory.length }} {{ $t('dcwDetail.k40bfz150') }}
1897
+ </p>
1898
+ <table class="nodes-table">
1899
+ <tbody>
1900
+ <tr
1901
+ v-for="h in lineHistory.slice(0, 12)"
1902
+ :key="h.id"
1903
+ >
1904
+ <td class="mono dim">
1905
+ {{ h.at.slice(5, 19) }}
1906
+ </td>
1907
+ <td>{{ h.param }}</td>
1908
+ <td>{{ h.nodeName }}</td>
1909
+ <td class="mono">
1910
+ {{ h.eng }}
1911
+ </td>
1912
+ <td class="mono dim">
1913
+ raw {{ h.raw ?? '-' }}
1914
+ </td>
1915
+ <td>
1916
+ <span
1917
+ class="st-pill"
1918
+ :class="h.ok ? 'ok' : 'alarm'"
1919
+ >{{ h.ok ? 'ACK' : $t('dcwDetail.k3yit7139') }}</span>
1920
+ </td>
1921
+ <td class="dim">
1922
+ {{ h.recipeRunId ? $t('dcwDetail.k6vgks7181', { p0: h.recipeRunId }) : '手动' }}
1923
+ </td>
1924
+ </tr>
1925
+ </tbody>
1926
+ </table>
1927
+ </section>
1928
+
1929
+ <!-- 新建产品弹窗 -->
1930
+ <div
1931
+ v-if="productOpen"
1932
+ class="modal-mask"
1933
+ @click.self="productOpen = false"
1934
+ >
1935
+ <div class="modal">
1936
+ <h3 class="m-title">
1937
+ {{ $t('dcwDetail.k1efduyf118') }}
1938
+ </h3>
1939
+ <div class="f-grid">
1940
+ <label class="f">
1941
+ <span>{{ $t('dcwDetail.k1avjrl6119') }}<em>*</em></span>
1942
+ <input
1943
+ v-model="productForm.name"
1944
+ class="inp"
1945
+ :placeholder="$t('dcwDetail.k12c11vm014')"
1946
+ >
1947
+ </label>
1948
+ <label class="f">
1949
+ <span>{{ $t('dcwDetail.k40gkk096') }}</span>
1950
+ <input
1951
+ v-model="productForm.description"
1952
+ class="inp"
1953
+ :placeholder="$t('dcwDetail.k1hxx8hy015')"
1954
+ >
1955
+ </label>
1956
+ </div>
1957
+ <p
1958
+ v-if="productError"
1959
+ class="m-err"
1960
+ >
1961
+ {{ productError }}
1962
+ </p>
1963
+ <div class="m-actions">
1964
+ <button
1965
+ class="aw-pill outline"
1966
+ @click="productOpen = false"
1967
+ >
1968
+ {{ $t('dcwDetail.k3xdnn052') }}
1969
+ </button>
1970
+ <button
1971
+ class="pill-btn"
1972
+ :disabled="productSaving"
1973
+ @click="doCreateProduct"
1974
+ >
1975
+ {{ productSaving ? $t('dcwDetail.k1bg4759156') : $t('dcwDetail.k1bg4kn6179') }}
1976
+ </button>
1977
+ </div>
1978
+ </div>
1979
+ </div>
1980
+
1981
+ <p
1982
+ v-if="dcw.error"
1983
+ class="err"
1984
+ >
1985
+ {{ dcw.error }}(<NuxtLink to="/workshop">{{ $t('dcwDetail.k1bhhheq120') }}</NuxtLink>)
1986
+ </p>
1987
+
1988
+ <!-- 参数台账(调控闭环:当前值/配方目标/lastGood 三值对照 + 设定历史 + 优化记录) -->
1989
+ <section class="aw-tile ledger-card">
1990
+ <div class="ledger-head">
1991
+ <b>{{ $t('dcwDetail.k1ledgt001') }}</b>
1992
+ <select
1993
+ v-model="ledgerNodeId"
1994
+ class="inp"
1995
+ @change="loadLedger"
1996
+ >
1997
+ <option value="">
1998
+ {{ $t('dcwDetail.k1ledgs001') }}
1999
+ </option>
2000
+ <option
2001
+ v-for="n in lineNodes"
2002
+ :key="n.id"
2003
+ :value="n.id"
2004
+ >
2005
+ {{ n.name }}
2006
+ </option>
2007
+ </select>
2008
+ </div>
2009
+ <template v-if="ledger">
2010
+ <div class="ledger-tri">
2011
+ <div class="tri-cell">
2012
+ <small>{{ $t('dcwDetail.k1ledgv001') }}</small>
2013
+ <b class="mono">{{ ledger.current ?? '--' }}</b>
2014
+ </div>
2015
+ <div class="tri-cell">
2016
+ <small>{{ $t('dcwDetail.k1ledgv002') }}</small>
2017
+ <b class="mono">{{ ledger.recipeTarget ?? '--' }}</b>
2018
+ </div>
2019
+ <div class="tri-cell">
2020
+ <small>{{ $t('dcwDetail.k1ledgv003') }}</small>
2021
+ <b class="mono">{{ ledger.lastGood ?? '--' }}</b>
2022
+ </div>
2023
+ <button
2024
+ class="mini-btn"
2025
+ @click="loadLedger"
2026
+ >
2027
+ {{ $t('dcwDetail.k1ledga001') }}
2028
+ </button>
2029
+ <button
2030
+ class="mini-btn danger"
2031
+ @click="rollbackLedgerNode"
2032
+ >
2033
+ {{ $t('dcwDetail.k1ledga002') }}
2034
+ </button>
2035
+ </div>
2036
+ <div class="ledger-cols">
2037
+ <div class="ledger-col">
2038
+ <small class="sec-t">{{ $t('dcwDetail.k1ledgh001') }}</small>
2039
+ <ul class="ledger-list mono">
2040
+ <li
2041
+ v-for="a in ledger.journal"
2042
+ :key="a.id"
2043
+ >
2044
+ {{ a.at.slice(5, 16).replace('T', ' ') }} [{{ a.source }}] {{ a.prevValue ?? '?' }} → {{ a.newValue }}
2045
+ </li>
2046
+ </ul>
2047
+ </div>
2048
+ <div class="ledger-col">
2049
+ <small class="sec-t">{{ $t('dcwDetail.k1ledgr001') }}</small>
2050
+ <ul class="ledger-list">
2051
+ <li
2052
+ v-for="r in ledger.records"
2053
+ :key="r.id"
2054
+ class="ledger-rec"
2055
+ >
2056
+ <span
2057
+ class="rec-st"
2058
+ :class="r.status"
2059
+ >{{ r.status }}</span>
2060
+ <span class="mono">{{ r.params[0]?.from ?? '?' }} → {{ r.params[0]?.to }}</span>
2061
+ <small>{{ r.judge ? `${r.judge.verdict}(${r.judge.by})` : (r.status === 'open' ? 'open' : r.closedBy ?? '') }}</small>
2062
+ </li>
2063
+ </ul>
2064
+ </div>
2065
+ </div>
2066
+ </template>
2067
+ </section>
2068
+ </div>
2069
+ </template>
2070
+
2071
+ <style scoped>
2072
+ .line-chip {
2073
+ display: inline-block;
2074
+ width: 10px;
2075
+ height: 10px;
2076
+ margin-right: 8px;
2077
+ vertical-align: -1px;
2078
+ border-radius: 3px;
2079
+ box-shadow: 0 0 10px currentColor;
2080
+ }
2081
+ .param-chip.daqwin { color: #41c8f4; border-color: rgba(65, 200, 244, 0.4); }
2082
+ .adopt-card { display: flex; gap: 12px; align-items: flex-start; flex-wrap: wrap; }
2083
+ .adopt-title { font-size: 12px; color: var(--aw-dim, #8fa0b5); flex: none; padding-top: 4px; }
2084
+ .adopt-list { display: flex; gap: 8px; flex-wrap: wrap; }
2085
+ .adopt-chip {
2086
+ display: inline-flex;
2087
+ gap: 6px;
2088
+ align-items: center;
2089
+ padding: 4px 8px;
2090
+ font-size: 11px;
2091
+ color: #e8eef8;
2092
+ background: rgba(13, 20, 32, 0.7);
2093
+ border: 1px solid rgba(60, 80, 110, 0.5);
2094
+ border-radius: 8px;
2095
+ }
2096
+ .page { padding: 4px; }
2097
+ h1 { margin: 2px 0 4px; font-size: 30px; font-weight: 400; letter-spacing: -0.015em; }
2098
+
2099
+ /* ── 参数台账(调控闭环) ── */
2100
+ .ledger-card { padding: 14px 18px; margin-bottom: 14px; }
2101
+ .ledger-head { display: flex; gap: 12px; align-items: center; }
2102
+ .ledger-head b { font-size: 13.5px; }
2103
+ .ledger-tri { display: flex; gap: 12px; align-items: center; margin-top: 12px; }
2104
+ .tri-cell {
2105
+ display: flex;
2106
+ flex: 0 0 auto;
2107
+ flex-direction: column;
2108
+ gap: 2px;
2109
+ padding: 8px 16px;
2110
+ background: var(--paper-deep);
2111
+ border: 1px solid var(--line-strong);
2112
+ border-radius: var(--radius-chip);
2113
+ }
2114
+ .tri-cell small { font-size: 10px; color: var(--ink-faint); }
2115
+ .tri-cell b { font-size: 17px; color: var(--ink); }
2116
+ .ledger-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 12px; }
2117
+ .sec-t { display: block; margin-bottom: 6px; font-size: 10px; font-weight: 700; letter-spacing: 0.14em; color: var(--ink-faint); }
2118
+ .ledger-list { display: flex; flex-direction: column; gap: 3px; max-height: 200px; margin: 0; padding: 0; overflow: auto; font-size: 11px; color: var(--ink-soft); list-style: none; }
2119
+ .ledger-rec { display: flex; gap: 8px; align-items: center; }
2120
+ .rec-st {
2121
+ flex: none;
2122
+ padding: 0 6px;
2123
+ font-family: var(--font-mono);
2124
+ font-size: 9px;
2125
+ color: var(--ink-faint);
2126
+ border: 1px solid var(--line-strong);
2127
+ border-radius: 4px;
2128
+ }
2129
+ .rec-st.open { color: var(--tone-warning-dot); border-color: var(--tone-warning-dot); }
2130
+ .rec-st.judged { color: var(--tone-warning-dot); border-color: var(--tone-warning-dot); opacity: 0.85; }
2131
+ .rec-st.rolled-back, .rec-st.judged-keep { color: var(--tone-success-dot); border-color: var(--tone-success-dot); }
2132
+ .sub { margin: 0; font-size: 12.5px; opacity: 0.6; }
2133
+ .mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
2134
+ .dim { opacity: 0.55; }
2135
+ .badges { display: flex; gap: 8px; }
2136
+ .badge { padding: 3px 10px; font-size: 11px; letter-spacing: 0.05em; color: var(--ink-soft); border: 1px solid var(--line-strong); border-radius: var(--radius-chip); }
2137
+
2138
+ .ctrl-card { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; justify-content: space-between; padding: 14px 18px; margin-bottom: 14px; }
2139
+ .ctrl-left { display: flex; gap: 14px; align-items: center; }
2140
+ .aw-pill.running { background: var(--accent); }
2141
+ .ctrl-right { display: flex; gap: 12px; align-items: center; }
2142
+ .ctrl-metrics { display: flex; gap: 8px; align-items: center; font-size: 11.5px; color: var(--ink-soft); }
2143
+ .ctrl-metrics .sep { opacity: 0.4; }
2144
+ .ctrl-metrics .warn { color: var(--tone-warning-dot); }
2145
+ .add-btn { padding: 8px 16px; font-size: 13px; }
2146
+
2147
+ .banner { padding: 9px 14px; margin: 0 0 12px; font-size: 12.5px; border-radius: var(--radius-chip); }
2148
+ .banner.bad { color: var(--tone-danger-dot); background: var(--tone-danger-bg); border: 1px solid color-mix(in srgb, var(--tone-danger-dot) 40%, transparent); }
2149
+ .banner.good { color: var(--tone-success-dot); background: var(--tone-success-bg); border: 1px solid color-mix(in srgb, var(--tone-success-dot) 40%, transparent); }
2150
+
2151
+ .table-card { overflow-x: auto; margin-bottom: 14px; }
2152
+ .nodes-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
2153
+ .nodes-table th, .nodes-table td { padding: 9px 12px; text-align: left; border-bottom: 1px solid var(--divider-hair); }
2154
+ .nodes-table th { font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-faint); border-bottom: 1px solid var(--line-strong); }
2155
+ .nodes-table td b { margin-left: 8px; }
2156
+ .ch { display: block; margin-top: 2px; font-size: 10px; color: var(--ink-faint); }
2157
+ .right { text-align: right; }
2158
+ .val { font-size: 13px; }
2159
+ .val small { margin-left: 3px; color: var(--ink-faint); }
2160
+
2161
+ .st-pill { display: inline-block; padding: 2px 9px; font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.04em; border-radius: var(--radius-pill); }
2162
+ .st-pill.ok { color: var(--tone-success-dot); background: var(--tone-success-bg); }
2163
+ .st-pill.writing { color: var(--ink); background: var(--hover-tint); }
2164
+ .st-pill.error, .st-pill.alarm { color: var(--tone-danger-dot); background: var(--tone-danger-bg); }
2165
+ .st-pill.warn { color: var(--tone-warning-dot); background: var(--tone-warning-bg); }
2166
+ .st-pill.offline, .st-pill.idle { color: var(--tone-neutral-dot); background: var(--tone-neutral-bg); }
2167
+ .st-pill.paused { color: var(--tone-warning-dot); background: var(--tone-warning-bg); }
2168
+
2169
+ /* 逐节点 控制开关 */
2170
+ .ctrl-toggle { display: inline-flex; gap: 6px; align-items: center; padding: 3px 10px; font-size: 11.5px; cursor: pointer; color: var(--ink-faint); background: var(--paper-deep); border: 1px solid var(--line-strong); border-radius: var(--radius-pill); }
2171
+ .ctrl-toggle .ct-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--tone-neutral-dot); }
2172
+ .ctrl-toggle.on { color: var(--tone-success-dot); border-color: color-mix(in srgb, var(--tone-success-dot) 45%, transparent); }
2173
+ .ctrl-toggle.on .ct-dot { background: var(--tone-success-dot); box-shadow: 0 0 6px var(--tone-success-dot); }
2174
+ .ctrl-toggle:disabled { opacity: 0.55; cursor: wait; }
2175
+ .node-paused td { opacity: 0.6; }
2176
+ .node-paused .write-row { opacity: 0.5; }
2177
+
2178
+ /* 模板弹窗 */
2179
+ .tpl-hint { margin: 0 0 10px; font-size: 12px; }
2180
+ .tpl-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 4px; }
2181
+ .tpl-chip { display: inline-flex; gap: 6px; align-items: center; padding: 3px 9px; font-size: 11.5px; color: var(--ink-soft); background: var(--paper-deep); border: 1px solid var(--line); border-radius: var(--radius-chip); }
2182
+ .tpl-chip small { color: var(--ink-faint); }
2183
+ .tpl-tag { padding: 1px 6px; font-size: 10px; font-style: normal; color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); border-radius: var(--radius-pill); }
2184
+ .tpl-tag.builtin { color: var(--ink-faint); border-color: var(--line-strong); }
2185
+
2186
+ .write-row { display: flex; gap: 6px; align-items: center; }
2187
+ .write-inp { width: 92px; padding: 4px 8px; }
2188
+ .write-btn { padding: 5px 12px; }
2189
+ .mini-btn { padding: 4px 10px; font-size: 11.5px; cursor: pointer; color: var(--ink-soft); background: var(--paper-deep); border: 1px solid var(--line-strong); border-radius: var(--radius-chip); margin-right: 4px; }
2190
+ .mini-btn.danger { color: var(--tone-danger-dot); }
2191
+ .mini-btn:hover { border-color: var(--accent); color: var(--accent); }
2192
+ .inp { padding: 5px 9px; font-size: 12.5px; color: var(--ink); background: var(--paper-deep); border: 1px solid var(--line-strong); border-radius: var(--radius-chip); }
2193
+
2194
+ .line-card { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; justify-content: space-between; padding: 14px 18px; margin-bottom: 14px; }
2195
+ .line-status { display: flex; gap: 12px; align-items: center; min-width: 260px; }
2196
+ .line-dot { width: 12px; height: 12px; border-radius: 50%; background: var(--tone-neutral-dot); box-shadow: 0 0 0 4px color-mix(in srgb, var(--tone-neutral-dot) 20%, transparent); }
2197
+ .line-dot.on { background: var(--tone-success-dot); box-shadow: 0 0 0 4px color-mix(in srgb, var(--tone-success-dot) 22%, transparent); animation: linePulse 1.6s infinite; }
2198
+ @keyframes linePulse { 0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--tone-success-dot) 25%, transparent); } 50% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--tone-success-dot) 8%, transparent); } }
2199
+ .line-info b { font-size: 14px; }
2200
+ .line-info small { display: block; margin-top: 2px; font-size: 11px; }
2201
+ .line-ctl { display: flex; gap: 10px; align-items: flex-end; }
2202
+ .ctl-sel { display: flex; flex-direction: column; gap: 4px; font-size: 11px; color: var(--ink-faint); }
2203
+ .ctl-sel .inp { min-width: 150px; }
2204
+ .pill-btn.stop { background: var(--tone-danger-dot); border-color: var(--tone-danger-dot); }
2205
+ .product-row { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 12px; }
2206
+ .prod-chip { display: inline-flex; gap: 6px; align-items: center; padding: 4px 12px; font-size: 12px; cursor: pointer; color: var(--ink-soft); background: var(--paper-deep); border: 1px solid var(--line-strong); border-radius: var(--radius-pill); }
2207
+ .prod-chip.on { color: var(--on-accent); background: var(--accent); border-color: var(--accent); }
2208
+ .prod-del { color: var(--tone-danger-dot); font-weight: 700; }
2209
+ .query-card { padding: 14px 18px; margin-bottom: 14px; }
2210
+ .q-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; align-items: end; }
2211
+ .q-grid .f { font-size: 11px; color: var(--ink-faint); }
2212
+ .q-actions { justify-content: flex-end; }
2213
+ .q-result { margin-top: 12px; overflow-x: auto; }
2214
+ .cal-form { grid-template-columns: 2fr 1fr; align-items: end; }
2215
+ .recipe-card { padding: 16px 18px; margin-bottom: 14px; }
2216
+ .recipe-hd { display: flex; gap: 10px; align-items: center; margin-bottom: 6px; }
2217
+ .recipe-hd h3 { margin: 0; font-size: 16px; }
2218
+ .panel-tag { padding: 2px 8px; font-size: 11px; color: var(--ink-soft); border: 1px solid var(--line-strong); border-radius: var(--radius-chip); }
2219
+ .recipe-sub { margin: 0 0 14px; font-size: 12px; color: var(--ink-faint); }
2220
+ .recipe-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 12px; }
2221
+ .recipe-item { padding: 12px 14px; background: var(--paper-deep); border: 1px solid var(--line); border-radius: var(--radius-chip); }
2222
+ .recipe-name b { font-size: 13.5px; }
2223
+ .recipe-name small { margin-left: 8px; }
2224
+ .desc { margin: 4px 0; font-size: 12px; }
2225
+ .recipe-params { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
2226
+ .param-chip { padding: 2px 8px; font-size: 11px; color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); border-radius: var(--radius-chip); }
2227
+ .recipe-actions { display: flex; gap: 6px; align-items: center; margin-top: 8px; }
2228
+ .sec-label { margin: 14px 0 6px; font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-faint); }
2229
+ .runs { margin-top: 6px; }
2230
+ .pill-btn { padding: 6px 14px; font-size: 12.5px; cursor: pointer; color: var(--paper-raised); background: var(--accent); border: 1px solid var(--accent); border-radius: var(--radius-pill); }
2231
+ .pill-btn.outline { color: var(--ink); background: var(--paper-raised); border-color: var(--line-strong); }
2232
+ .pill-btn:disabled { opacity: 0.5; cursor: default; }
2233
+
2234
+ .modal-mask { position: fixed; z-index: 50; inset: 0; display: flex; align-items: center; justify-content: center; background: var(--scrim); backdrop-filter: blur(2px); }
2235
+ .modal { width: 680px; max-width: 94vw; max-height: 88vh; overflow-y: auto; padding: 22px 24px; background: var(--surface-glass-strong); backdrop-filter: var(--aurora-blur); -webkit-backdrop-filter: var(--aurora-blur); border: 1px solid var(--glass-line); border-radius: var(--radius-panel); box-shadow: var(--glass-edge), var(--shadow-float); }
2236
+ .m-title { margin: 0 0 14px; font-size: 17px; }
2237
+ .seg-row { display: flex; gap: 8px; margin-bottom: 14px; }
2238
+ .seg { flex: 1; padding: 8px 0; font-size: 13px; cursor: pointer; color: var(--ink-faint); background: var(--paper-deep); border: 1px solid var(--line); border-radius: var(--radius-chip); }
2239
+ .seg.on { font-weight: 600; color: var(--on-accent); background: var(--accent); border-color: var(--accent); }
2240
+ .f-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px; }
2241
+ .driver-form { grid-template-columns: repeat(3, 1fr); }
2242
+ .param-row { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr auto; gap: 8px; align-items: end; margin-bottom: 8px; }
2243
+ .param-row .f { flex: 1; }
2244
+ .param-del { margin-bottom: 2px; }
2245
+ .f { display: flex; flex-direction: column; gap: 4px; font-size: 11.5px; color: var(--ink-faint); }
2246
+ .f em { margin-left: 3px; font-style: normal; }
2247
+ .hint { font-size: 10px; color: var(--ink-fainter); }
2248
+ .test-row { display: flex; gap: 10px; align-items: center; margin: 6px 0 4px; }
2249
+ .test-result { font-family: var(--font-mono); font-size: 11px; }
2250
+ .test-result.good { color: var(--tone-success-dot); }
2251
+ .test-result.bad { color: var(--tone-danger-dot); }
2252
+ .m-err { margin: 8px 0 0; font-size: 12px; color: var(--tone-danger-dot); }
2253
+ .m-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 16px; }
2254
+ .aw-pill.outline { color: var(--ink); background: var(--paper-raised); border-color: var(--line-strong); }
2255
+ .err { margin-top: 14px; font-size: 13px; color: var(--tone-danger-dot); }
2256
+ @media (prefers-reduced-motion: no-preference) {
2257
+ .nodes-table tbody tr:hover { background: var(--hover-tint); }
2258
+ }
2259
+ </style>