agentworkshop 0.7.40 → 0.7.44

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 (327) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/B8uy9iRW.js +1 -0
  3. package/.output/public/_nuxt/BGTYYQH-.js +1 -0
  4. package/.output/public/_nuxt/BUmHEJ77.js +2 -0
  5. package/.output/public/_nuxt/BY7YrV_D.js +1 -0
  6. package/.output/public/_nuxt/Bqcgz95s.js +18 -0
  7. package/.output/public/_nuxt/C7UlqPeD.js +1 -0
  8. package/.output/public/_nuxt/CTmDn-YP.js +1 -0
  9. package/.output/public/_nuxt/Cs8pVeVG.js +1 -0
  10. package/.output/public/_nuxt/{DcsCcxp4.js → D98NH8kG.js} +1 -1
  11. package/.output/public/_nuxt/DKySmNPI.js +14 -0
  12. package/.output/public/_nuxt/Di0NY_ht.js +1 -0
  13. package/.output/public/_nuxt/Du9v6qGB.js +1 -0
  14. package/.output/public/_nuxt/{C_iYD-EB.js → EHlEMRii.js} +1 -1
  15. package/.output/public/_nuxt/Eke1fs-U.js +1 -0
  16. package/.output/public/_nuxt/{DeJykBFf.js → H0BcqZtT.js} +1 -1
  17. package/.output/public/_nuxt/{C4oW1HsE.js → HSYEkkce.js} +1 -1
  18. package/.output/public/_nuxt/{DZshyNLa.js → IREZ1eC2.js} +1 -1
  19. package/.output/public/_nuxt/{D_OTvQza.js → J9oU00D1.js} +1 -1
  20. package/.output/public/_nuxt/OmpTerminalPanel.BxR5-lvi.css +1 -0
  21. package/.output/public/_nuxt/SSyAPaHc.js +1 -0
  22. package/.output/public/_nuxt/TownView.li4EMQxK.css +1 -0
  23. package/.output/public/_nuxt/VdLk4jLE2.js +1 -0
  24. package/.output/public/_nuxt/_id_.BhsXoEuk.css +1 -0
  25. package/.output/public/_nuxt/_id_.DQyj-WKN.css +1 -0
  26. package/.output/public/_nuxt/{_wsId_.B4WiqkIS.css → _wsId_.CTx-BlfF.css} +1 -1
  27. package/.output/public/_nuxt/aShsv_rG.js +19 -0
  28. package/.output/public/_nuxt/{yF0aYMF1.js → aesgXKSr.js} +3 -3
  29. package/.output/public/_nuxt/agents.B21nUEUX.css +1 -0
  30. package/.output/public/_nuxt/aml.ekTjxFkf.css +1 -0
  31. package/.output/public/_nuxt/builds/latest.json +1 -1
  32. package/.output/public/_nuxt/builds/meta/b5506ef0-f48e-4f14-a6c0-d65e505d0b09.json +1 -0
  33. package/.output/public/_nuxt/ca4TJ3Op.js +1 -0
  34. package/.output/public/_nuxt/channel-templates.DplM1pJ3.css +1 -0
  35. package/.output/public/_nuxt/daq.BdVmoIfe.css +1 -0
  36. package/.output/public/_nuxt/dcw.DCgyzpWV.css +1 -0
  37. package/.output/public/_nuxt/{default.J32SgqFY.css → default.Bh3L2HU8.css} +1 -1
  38. package/.output/public/_nuxt/lHAxdSsj.js +1 -0
  39. package/.output/public/_nuxt/pages.j9rzRocr.css +1 -0
  40. package/.output/public/_nuxt/plugins.DhiZbHU8.css +1 -0
  41. package/.output/public/_nuxt/settings.BRx9rIDg.css +1 -0
  42. package/.output/public/_nuxt/teams.BLlOPB_Z.css +1 -0
  43. package/.output/public/_nuxt/tokens.De8xzszn.css +1 -0
  44. package/.output/public/_nuxt/workshop.D1jFNkrn.css +1 -0
  45. package/.output/server/chunks/_/nitro.mjs +1058 -830
  46. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  47. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs → OmpTerminalPanel-CVvxpsF7.mjs} +15 -15
  48. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs.map → OmpTerminalPanel-CVvxpsF7.mjs.map} +1 -1
  49. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs → TownView-CJ6IsDCc.mjs} +229 -228
  50. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs.map → TownView-CJ6IsDCc.mjs.map} +1 -1
  51. package/.output/server/chunks/build/{_id_-BU1wfOID.mjs → _id_-CVD_H0ir.mjs} +149 -147
  52. package/.output/server/chunks/build/_id_-CVD_H0ir.mjs.map +1 -0
  53. package/.output/server/chunks/build/{_id_-BYsEua57.mjs → _id_-CtxMb3qp.mjs} +47 -46
  54. package/.output/server/chunks/build/_id_-CtxMb3qp.mjs.map +1 -0
  55. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs → _wsId_-DpY3T-md.mjs} +516 -446
  56. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs.map → _wsId_-DpY3T-md.mjs.map} +1 -1
  57. package/.output/server/chunks/build/{agents-CLQqJF2y.mjs → agents-DsCkuXDV.mjs} +49 -46
  58. package/.output/server/chunks/build/agents-DsCkuXDV.mjs.map +1 -0
  59. package/.output/server/chunks/build/{aml-_7FdFL2p.mjs → aml-CT0xKGKI.mjs} +122 -105
  60. package/.output/server/chunks/build/aml-CT0xKGKI.mjs.map +1 -0
  61. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs +117 -0
  62. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs.map +1 -0
  63. package/.output/server/chunks/build/{channel-templates-p6SZNjlM.mjs → channel-templates-Dla9FbYa.mjs} +49 -37
  64. package/.output/server/chunks/build/channel-templates-Dla9FbYa.mjs.map +1 -0
  65. package/.output/server/chunks/build/{daq-cgyjKmJu.mjs → daq-DWWSKGAV.mjs} +95 -93
  66. package/.output/server/chunks/build/daq-DWWSKGAV.mjs.map +1 -0
  67. package/.output/server/chunks/build/{dcw-CoTxBlAZ.mjs → dcw-CT8k17_E.mjs} +46 -45
  68. package/.output/server/chunks/build/dcw-CT8k17_E.mjs.map +1 -0
  69. package/.output/server/chunks/build/{default-BE4r0FZN.mjs → default-D3525UFX.mjs} +16 -15
  70. package/.output/server/chunks/build/{default-BE4r0FZN.mjs.map → default-D3525UFX.mjs.map} +1 -1
  71. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs → logs--dByNyhw.mjs} +2 -2
  72. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs.map → logs--dByNyhw.mjs.map} +1 -1
  73. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs → monitor-Dh2Kvaq5.mjs} +2 -2
  74. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs.map → monitor-Dh2Kvaq5.mjs.map} +1 -1
  75. package/.output/server/chunks/build/{pages-DFxjn9Xa.mjs → pages-BHcSBM2D.mjs} +26 -20
  76. package/.output/server/chunks/build/pages-BHcSBM2D.mjs.map +1 -0
  77. package/.output/server/chunks/build/{plugins-lGlUr1lj.mjs → plugins-BNgIi61W.mjs} +23 -17
  78. package/.output/server/chunks/build/plugins-BNgIi61W.mjs.map +1 -0
  79. package/.output/server/chunks/build/{settings-Ddd1qx2Z.mjs → settings-ipYBDWdn.mjs} +45 -44
  80. package/.output/server/chunks/build/settings-ipYBDWdn.mjs.map +1 -0
  81. package/.output/server/chunks/build/{teams-MEDLZdMn.mjs → teams-5lx-CwFa.mjs} +47 -44
  82. package/.output/server/chunks/build/teams-5lx-CwFa.mjs.map +1 -0
  83. package/.output/server/chunks/build/{tokens-C7fTM5C2.mjs → tokens-q-IDT8Io.mjs} +73 -53
  84. package/.output/server/chunks/build/tokens-q-IDT8Io.mjs.map +1 -0
  85. package/.output/server/chunks/build/{town-qMPql9Se.mjs → town-QJWB5wng.mjs} +5 -4
  86. package/.output/server/chunks/build/town-QJWB5wng.mjs.map +1 -0
  87. package/.output/server/chunks/build/{useDaqStream-CIBaiGAl.mjs → useDaqStream-DfCFpb3p.mjs} +9 -3
  88. package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs.map +1 -0
  89. package/.output/server/chunks/build/{useDcwStream-CBbiYnWe.mjs → useDcwStream-UPiX-3iO.mjs} +11 -2
  90. package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs.map +1 -0
  91. package/.output/server/chunks/build/{workshop-BL5bhfs0.mjs → workshop-BIlCj_ZP.mjs} +45 -45
  92. package/.output/server/chunks/build/workshop-BIlCj_ZP.mjs.map +1 -0
  93. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  94. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +3 -3
  95. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
  96. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  97. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  98. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
  99. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
  100. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  101. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  102. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  103. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  104. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  105. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  106. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  107. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +3 -1
  108. package/.output/server/chunks/routes/api/workshop/index5.get.mjs.map +1 -1
  109. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +3 -1
  110. package/.output/server/chunks/routes/api/workshop/index6.get.mjs.map +1 -1
  111. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  112. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  113. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  114. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  115. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  116. package/.output/server/chunks/routes/renderer.mjs +2 -2
  117. package/.output/server/chunks/virtual/entry.mjs +639 -88
  118. package/.output/server/chunks/virtual/entry.mjs.map +1 -1
  119. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  120. package/.output/server/package.json +1 -1
  121. package/README-zh.md +16 -6
  122. package/README.md +17 -7
  123. package/app/components/AppHeader.vue +961 -959
  124. package/app/components/workshop/A2aRpcDebugger.vue +237 -237
  125. package/app/components/workshop/AgentInspectorDrawer.vue +424 -424
  126. package/app/components/workshop/AgentLanesView.vue +998 -998
  127. package/app/components/workshop/ArtifactCard.vue +120 -120
  128. package/app/components/workshop/AssetLibrary.vue +452 -452
  129. package/app/components/workshop/ChannelSessionList.vue +812 -804
  130. package/app/components/workshop/CommandPalette.vue +269 -269
  131. package/app/components/workshop/Composer.vue +827 -827
  132. package/app/components/workshop/DeviceTwinPanel.vue +643 -643
  133. package/app/components/workshop/EventBlock.vue +602 -602
  134. package/app/components/workshop/FileSelectorModal.vue +188 -188
  135. package/app/components/workshop/InspectorPanel.vue +301 -301
  136. package/app/components/workshop/MemoryPanel.vue +364 -364
  137. package/app/components/workshop/MultiChannelView.vue +189 -189
  138. package/app/components/workshop/TaskBoardView.vue +686 -686
  139. package/app/components/workshop/TaskInspectorDrawer.vue +390 -390
  140. package/app/components/workshop/TranscriptTimeline.vue +601 -601
  141. package/app/components/workshop/blocks/ClusterArtifact.vue +61 -61
  142. package/app/components/workshop/blocks/ClusterError.vue +85 -85
  143. package/app/components/workshop/blocks/ClusterLife.vue +87 -87
  144. package/app/components/workshop/blocks/ClusterMember.vue +95 -93
  145. package/app/components/workshop/blocks/ClusterMemory.vue +84 -84
  146. package/app/components/workshop/blocks/ClusterOther.vue +67 -67
  147. package/app/components/workshop/blocks/ClusterStatus.vue +149 -147
  148. package/app/components/workshop/blocks/ClusterTool.vue +158 -158
  149. package/app/components/workshop/terminal/OmpTerminalPanel.vue +1011 -1011
  150. package/app/components/workshop/town/TownView.vue +56 -56
  151. package/app/composables/workshop/useDaqStream.ts +7 -4
  152. package/app/composables/workshop/useDcwStream.ts +8 -2
  153. package/app/middleware/locale-cookie.global.ts +22 -0
  154. package/app/pages/aml/index.vue +24 -2
  155. package/app/pages/daq/[id].vue +20 -16
  156. package/app/pages/daq/index.vue +2310 -2307
  157. package/app/pages/dcw/[id].vue +26 -23
  158. package/app/pages/dcw/index.vue +3 -3
  159. package/app/pages/index.vue +987 -981
  160. package/app/pages/plugins/index.vue +317 -311
  161. package/app/pages/settings.vue +1564 -1562
  162. package/app/pages/tokens.vue +15 -15
  163. package/app/pages/workshop/agents.vue +612 -609
  164. package/app/pages/workshop/channel-templates.vue +18 -11
  165. package/app/pages/workshop/index.vue +668 -668
  166. package/app/pages/workshop/teams.vue +743 -740
  167. package/app/pages/workshop/w/[wsId].vue +704 -704
  168. package/app/plugins/locale-restore.client.ts +16 -1
  169. package/app/utils/builtin-catalog.ts +104 -0
  170. package/config.yml +170 -170
  171. package/docs/cli.en.md +2 -2
  172. package/docs/cli.md +2 -2
  173. package/docs/full-test-plan.md +2 -3
  174. package/docs/plugins.en.md +33 -1
  175. package/docs/plugins.md +29 -1
  176. package/docs/sdk.en.md +1 -1
  177. package/docs/sdk.md +1 -1
  178. package/i18n/locales/en.ts +293 -35
  179. package/i18n/locales/zh-CN.ts +265 -7
  180. package/package.json +4 -3
  181. package/scripts/_dbg-perms-e2e.mjs +132 -109
  182. package/scripts/_demo-agentteam-capture.mjs +278 -0
  183. package/scripts/_e2e-ui-functional.mjs +371 -0
  184. package/scripts/_i18n-en-cjk-scan.mjs +28 -0
  185. package/scripts/_i18n-en-verify.mjs +107 -0
  186. package/scripts/_i18n-extra-checks.mjs +53 -0
  187. package/scripts/_i18n-scan-hardcoded.mjs +180 -0
  188. package/scripts/_i18n-verify-keys.mjs +50 -0
  189. package/scripts/_loop-biax-campaign.mjs +130 -0
  190. package/scripts/_sweep-button-contrast.mjs +87 -0
  191. package/scripts/test-serial-bridge.mjs +418 -0
  192. package/scripts/ui/e2e-interactive.mjs +328 -0
  193. package/scripts/ui/record-demo.mjs +557 -0
  194. package/scripts/ui/seed-industrial-demo.mjs +171 -0
  195. package/server/api/workshop/daq/index.get.ts +3 -1
  196. package/server/api/workshop/dcw/index.get.ts +3 -0
  197. package/server/plugins/aw-plugins.ts +9 -7
  198. package/server/plugins-builtin/diag-bridge/index.mjs +608 -661
  199. package/server/plugins-builtin/rag-bridge/index.mjs +57 -133
  200. package/server/plugins-builtin/serial-bridge/client.mjs +210 -0
  201. package/server/plugins-builtin/serial-bridge/drivers.mjs +409 -0
  202. package/server/plugins-builtin/serial-bridge/i18n.json +62 -0
  203. package/server/plugins-builtin/serial-bridge/index.mjs +247 -0
  204. package/server/plugins-builtin/serial-bridge/lib.mjs +208 -0
  205. package/server/plugins-builtin/serial-bridge/rtu.mjs +168 -0
  206. package/server/services/workshop/daq/daq-controller.ts +7 -2
  207. package/server/services/workshop/daq/drivers.ts +89 -3
  208. package/server/services/workshop/dcw/dcw-controller.ts +5 -1
  209. package/server/services/workshop/dcw/drivers.ts +85 -2
  210. package/server/services/workshop/dcw/plugin-bridge.ts +45 -0
  211. package/server/services/workshop/plugins/host.mjs +30 -0
  212. package/shared/daq-protocol.ts +15 -0
  213. package/.output/public/_nuxt/88GOowgO2.js +0 -1
  214. package/.output/public/_nuxt/BOt2SP4z.js +0 -1
  215. package/.output/public/_nuxt/BP-NyKVG.js +0 -18
  216. package/.output/public/_nuxt/BXxH0UQE.js +0 -1
  217. package/.output/public/_nuxt/BsROmY89.js +0 -1
  218. package/.output/public/_nuxt/ClILjvWH.js +0 -1
  219. package/.output/public/_nuxt/CzOCSGLe.js +0 -1
  220. package/.output/public/_nuxt/D8qvkfyY.js +0 -1
  221. package/.output/public/_nuxt/DCn7QZ5N.js +0 -2
  222. package/.output/public/_nuxt/DS8qj8EL.js +0 -1
  223. package/.output/public/_nuxt/DUjW5Yq5.js +0 -1
  224. package/.output/public/_nuxt/Dihnimlk.js +0 -19
  225. package/.output/public/_nuxt/Dqw1lpr-.js +0 -1
  226. package/.output/public/_nuxt/Dr9v6C0f.js +0 -14
  227. package/.output/public/_nuxt/Du9ZJtnj.js +0 -1
  228. package/.output/public/_nuxt/OmpTerminalPanel.B_Pl5Ynm.css +0 -1
  229. package/.output/public/_nuxt/TownView.f-YOdXRs.css +0 -1
  230. package/.output/public/_nuxt/_id_.CM4bIgKY.css +0 -1
  231. package/.output/public/_nuxt/_id_.bNV69Dtf.css +0 -1
  232. package/.output/public/_nuxt/agents.ZD0rVdHE.css +0 -1
  233. package/.output/public/_nuxt/aml.BN8ZEW4h.css +0 -1
  234. package/.output/public/_nuxt/builds/meta/f4707884-7cf4-4a31-80d2-e3d72b73745f.json +0 -1
  235. package/.output/public/_nuxt/channel-templates.BOtahzPi.css +0 -1
  236. package/.output/public/_nuxt/daq.B0D3dPAc.css +0 -1
  237. package/.output/public/_nuxt/dcw.8Y7FqCVN.css +0 -1
  238. package/.output/public/_nuxt/pages.D7aSDlUI.css +0 -1
  239. package/.output/public/_nuxt/plugins.sb4nDGqr.css +0 -1
  240. package/.output/public/_nuxt/qr6R6Ha1.js +0 -1
  241. package/.output/public/_nuxt/settings.BTgR-NiE.css +0 -1
  242. package/.output/public/_nuxt/teams.CxnxKVVs.css +0 -1
  243. package/.output/public/_nuxt/tokens.BFY7qv4f.css +0 -1
  244. package/.output/public/_nuxt/workshop.CR_QzeAN.css +0 -1
  245. package/.output/server/chunks/build/_id_-BU1wfOID.mjs.map +0 -1
  246. package/.output/server/chunks/build/_id_-BYsEua57.mjs.map +0 -1
  247. package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +0 -1
  248. package/.output/server/chunks/build/aml-_7FdFL2p.mjs.map +0 -1
  249. package/.output/server/chunks/build/channel-templates-p6SZNjlM.mjs.map +0 -1
  250. package/.output/server/chunks/build/daq-cgyjKmJu.mjs.map +0 -1
  251. package/.output/server/chunks/build/dcw-CoTxBlAZ.mjs.map +0 -1
  252. package/.output/server/chunks/build/pages-DFxjn9Xa.mjs.map +0 -1
  253. package/.output/server/chunks/build/plugins-lGlUr1lj.mjs.map +0 -1
  254. package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +0 -1
  255. package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +0 -1
  256. package/.output/server/chunks/build/tokens-C7fTM5C2.mjs.map +0 -1
  257. package/.output/server/chunks/build/town-qMPql9Se.mjs.map +0 -1
  258. package/.output/server/chunks/build/useDaqStream-CIBaiGAl.mjs.map +0 -1
  259. package/.output/server/chunks/build/useDcwStream-CBbiYnWe.mjs.map +0 -1
  260. package/.output/server/chunks/build/workshop-BL5bhfs0.mjs.map +0 -1
  261. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +0 -1
  262. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +0 -1
  263. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +0 -153
  264. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +0 -153
  265. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +0 -431
  266. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +0 -180
  267. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +0 -112
  268. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +0 -227
  269. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +0 -131
  270. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +0 -160
  271. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +0 -157
  272. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +0 -348
  273. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +0 -296
  274. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +0 -285
  275. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +0 -217
  276. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +0 -301
  277. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +0 -151
  278. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +0 -135
  279. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +0 -156
  280. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +0 -72
  281. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +0 -469
  282. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +0 -259
  283. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +0 -97
  284. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +0 -277
  285. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +0 -171
  286. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +0 -425
  287. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +0 -99
  288. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +0 -325
  289. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +0 -332
  290. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +0 -43
  291. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +0 -154
  292. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +0 -531
  293. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +0 -439
  294. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +0 -323
  295. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +0 -433
  296. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +0 -280
  297. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +0 -117
  298. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +0 -160
  299. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +0 -101
  300. package/scripts/.mimosa/hook-status/sess_aeaecb58-8833-4b83-b93c-050467d21e03-d944d71bb3.json +0 -14
  301. package/scripts/_dbg-audit-all-pages.mjs +0 -43
  302. package/scripts/_dbg-daq-frames-e2e.mjs +0 -118
  303. package/scripts/_dbg-daq-rowstyle-verify.mjs +0 -112
  304. package/scripts/_dbg-hitl-timeout-e2e.mjs +0 -93
  305. package/scripts/_dbg-live-line-e2e.mjs +0 -279
  306. package/scripts/_dbg-minimal-omp.mjs +0 -77
  307. package/scripts/_dbg-mode-nav.mjs +0 -173
  308. package/scripts/_dbg-real-scenario-timeline.log +0 -55
  309. package/scripts/_dbg-team-repro.mjs +0 -76
  310. package/scripts/_dbg-town-agents-probe.mjs +0 -60
  311. package/scripts/_dbg-visual-acceptance.mjs +0 -63
  312. package/scripts/_townview_style.css +0 -1185
  313. package/scripts/_townview_template.html +0 -1215
  314. package/server/data/agent-node-bindings.json +0 -554
  315. package/server/data/daq-templates.json +0 -16
  316. package/server/data/daqs.json +0 -7464
  317. package/server/data/dcw-lines.json +0 -1787
  318. package/server/data/dcw-products.json +0 -1626
  319. package/server/data/dcw-recipes.json +0 -4672
  320. package/server/data/dcw-rollback.json +0 -20754
  321. package/server/data/dcw-runs.json +0 -6354
  322. package/server/data/dcw-templates.json +0 -146
  323. package/server/data/dcw-writes.json +0 -15650
  324. package/server/data/dcws.json +0 -9033
  325. package/server/data/device-twins.json +0 -133
  326. package/server/data/line-runs.json +0 -322
  327. package/server/data/scene-layouts.json +0 -277
@@ -1,668 +1,668 @@
1
- <script setup lang="ts">
2
- /**
3
- * Workspace 总览(用户级隔离):
4
- * - 未登录 → 注册/登录门(用户 token = 管理 API 凭证)
5
- * - 已登录 → workspace 卡片墙(服务端持久化;按用户隔离)+ 实时状态徽标
6
- */
7
- import { message } from 'ant-design-vue'
8
- import { useWorkspacesStore } from '../../stores/workshop/workspaces'
9
- import { useEntitiesStore } from '../../stores/workshop/entities'
10
- import { useUserStore } from '../../stores/workshop/user'
11
- import { useWorkshopWs } from '../../composables/workshop/useWorkshopWs'
12
-
13
- const { t } = useI18n()
14
-
15
- definePageMeta({ layout: 'default' })
16
-
17
- const userStore = useUserStore()
18
- const wsStore = useWorkspacesStore()
19
- const entities = useEntitiesStore()
20
- const { subscribe } = useWorkshopWs()
21
-
22
- // ===== 登录门(全局用户系统)=====
23
- const authTab = ref<'register' | 'login' | 'token'>('login')
24
- const authName = ref('')
25
- const authEmail = ref('')
26
- const authPassword = ref('')
27
- const authTokenInput = ref('')
28
- const authLoading = ref(false)
29
-
30
- // 首启初始化:系统尚无管理员 → 登录门切换为"注册管理员"模式(首个注册账号自动成为 admin)
31
- const needsSetup = ref(false)
32
- onMounted(async () => {
33
- try {
34
- const res = await $fetch<{ code: number, data?: { needsSetup: boolean } }>('/api/users/setup-status')
35
- if (res.code === 0 && res.data?.needsSetup) {
36
- needsSetup.value = true
37
- authTab.value = 'register'
38
- }
39
- }
40
- catch { /* 探测失败按常规登录门呈现 */ }
41
- })
42
-
43
- const doRegister = async (): Promise<void> => {
44
- if (!authName.value.trim()) {
45
- message.warning(t('wsHome.k1vnhyks019'))
46
- return
47
- }
48
- if (!authEmail.value.trim()) {
49
- message.warning(t('wsHome.k8ieqzj020'))
50
- return
51
- }
52
- if (authPassword.value.length < 6) {
53
- message.warning(t('wsHome.ksx73ra021'))
54
- return
55
- }
56
- authLoading.value = true
57
- try {
58
- const user = await userStore.register(authName.value, authEmail.value, authPassword.value)
59
- message.success(needsSetup.value ? `管理员账号创建成功:${user.name} 已进入系统` : t('wsHome.k1mbatbh030', { p0: user.name }))
60
- needsSetup.value = false
61
- authName.value = ''
62
- authEmail.value = ''
63
- authPassword.value = ''
64
- }
65
- catch (e) {
66
- message.error(apiErrorMessage(e))
67
- }
68
- finally {
69
- authLoading.value = false
70
- }
71
- }
72
- const doLogin = async (): Promise<void> => {
73
- if (!authEmail.value.trim()) {
74
- message.warning(t('wsHome.k8ieqzj020'))
75
- return
76
- }
77
- authLoading.value = true
78
- try {
79
- const user = await userStore.login(authEmail.value, authPassword.value)
80
- message.success(t('wsHome.kwixbdl031', { p0: user.name }))
81
- needsSetup.value = false
82
- authPassword.value = ''
83
- }
84
- catch (e) {
85
- message.error(apiErrorMessage(e))
86
- }
87
- finally {
88
- authLoading.value = false
89
- }
90
- }
91
- const doLoginWithToken = async (): Promise<void> => {
92
- authLoading.value = true
93
- try {
94
- const user = await userStore.loginWithToken(authTokenInput.value)
95
- message.success(t('wsHome.kwixbdl031', { p0: user.name }))
96
- needsSetup.value = false
97
- authTokenInput.value = ''
98
- }
99
- catch (e) {
100
- message.error(apiErrorMessage(e))
101
- }
102
- finally {
103
- authLoading.value = false
104
- }
105
- }
106
- const doLogout = (): void => {
107
- userStore.logout()
108
- message.success(t('wsHome.k3ngm6p022'))
109
- }
110
-
111
- // ===== 登录后加载 workspace(服务端持久化)=====
112
- const ready = ref(false)
113
- watch(() => userStore.isLoggedIn, async (ok) => {
114
- if (!ok) {
115
- ready.value = false
116
- return
117
- }
118
- await userStore.refresh()
119
- if (!userStore.isLoggedIn) return
120
- try {
121
- await wsStore.load()
122
- ready.value = true
123
- }
124
- catch (e) {
125
- // SSR 安全:ant-design-vue message 依赖 DOM,服务端静默(客户端进入页面后可重试)
126
- if (import.meta.client) message.error(e instanceof Error ? e.message : t('wsHome.k1br33vc023'))
127
- }
128
- }, { immediate: true })
129
-
130
- // 已有 workspace 的 channel 订阅(总览页也保持事件流活跃,状态徽标实时)
131
- watch(
132
- () => ready.value && wsStore.workspaces.map(w => w.channelIds.join(',')).join('|'),
133
- () => {
134
- if (!ready.value) return
135
- for (const ws of wsStore.workspaces) {
136
- for (const id of ws.channelIds) subscribe(id)
137
- }
138
- },
139
- { immediate: true },
140
- )
141
-
142
- const createOpen = ref(false)
143
- const createName = ref('')
144
- const createLoading = ref(false)
145
- const create = async (): Promise<void> => {
146
- const name = createName.value.trim()
147
- if (!name) {
148
- message.warning('Workspace 名称必填')
149
- return
150
- }
151
- createLoading.value = true
152
- try {
153
- const ws = await wsStore.create(name)
154
- createOpen.value = false
155
- createName.value = ''
156
- wsStore.setActiveWorkspaceId(ws.id)
157
- navigateTo(`/workshop/w/${ws.id}`)
158
- }
159
- catch (e) {
160
- message.error(apiErrorMessage(e))
161
- }
162
- finally {
163
- createLoading.value = false
164
- }
165
- }
166
-
167
- const remove = async (id: string): Promise<void> => {
168
- await wsStore.remove(id)
169
- message.success(t('wsHome.k3n5sd7024'))
170
- }
171
-
172
- const channelSummary = (channelIds: string[]) => channelIds.map((id) => {
173
- const meta = entities.channels[id]
174
- const agents = entities.agents[id] ?? []
175
- return {
176
- id,
177
- name: meta?.name ?? id.slice(0, 8),
178
- /** 实体基线(WS 快照)是否已到达:未到时计数不可信,展示"同步中"而非误导性的 0 */
179
- synced: meta !== undefined,
180
- agents: agents.length,
181
- busy: agents.filter(a => a.state === 'busy').length,
182
- activeTasks: (entities.tasks[id] ?? []).filter(t => !['COMPLETED', 'CANCELED', 'FAILED'].includes(t.state)).length,
183
- }
184
- })
185
-
186
- useHead({ title: () => t('titles.workshop') })
187
- </script>
188
-
189
- <template>
190
- <div class="page">
191
- <!-- 登录门 -->
192
- <div
193
- v-if="!userStore.isLoggedIn"
194
- class="auth-gate aw-orbs"
195
- >
196
- <a-card class="auth-card">
197
- <p class="aw-kicker">
198
- agentworkshop / sign in
199
- </p>
200
- <h2>{{ needsSetup ? '创建管理员账号' : $t('wsHome.kr0vzqu008') }}</h2>
201
- <p class="sub">
202
- {{ needsSetup
203
- ? '系统尚无任何用户:首个注册的账号将成为管理员,创建后自动进入系统。'
204
- : '全局用户系统统管身份;每个用户可管理多个 API Token,管理 API 需用户 token(Authorization: Bearer)。' }}
205
- </p>
206
- <a-tabs v-model:active-key="authTab">
207
- <a-tab-pane
208
- v-if="!needsSetup"
209
- key="login"
210
- tab="账号登录"
211
- >
212
- <a-space
213
- direction="vertical"
214
- style="width: 100%"
215
- >
216
- <a-input
217
- v-model:value="authEmail"
218
- type="email"
219
- :placeholder="$t('wsHome.k48h2c001')"
220
- @keydown.enter="doLogin"
221
- />
222
- <a-input-password
223
- v-model:value="authPassword"
224
- :placeholder="$t('wsHome.k3yvgs002')"
225
- @keydown.enter="doLogin"
226
- />
227
- <a-button
228
- type="primary"
229
- block
230
- :loading="authLoading"
231
- @click="doLogin"
232
- >
233
- {{ $t('wsHome.k43kol009') }}
234
- </a-button>
235
- <p class="hint">
236
- {{ $t('wsHome.k17x74cj010') }}
237
- </p>
238
- </a-space>
239
- </a-tab-pane>
240
- <a-tab-pane
241
- key="register"
242
- :tab="needsSetup ? '注册管理员' : '注册新用户'"
243
- >
244
- <a-space
245
- direction="vertical"
246
- style="width: 100%"
247
- >
248
- <a-input
249
- v-model:value="authName"
250
- :placeholder="$t('wsHome.kwgbai9003')"
251
- />
252
- <a-input
253
- v-model:value="authEmail"
254
- type="email"
255
- :placeholder="$t('wsHome.kizjkbo004')"
256
- />
257
- <a-input-password
258
- v-model:value="authPassword"
259
- :placeholder="$t('wsHome.kh3cqfn005')"
260
- @keydown.enter="doRegister"
261
- />
262
- <a-button
263
- type="primary"
264
- block
265
- :loading="authLoading"
266
- @click="doRegister"
267
- >
268
- {{ needsSetup ? '创建管理员并进入' : $t('wsHome.k1so6a0v011') }}
269
- </a-button>
270
- <p class="hint">
271
- {{ $t('wsHome.k1r0a4u3012') }}
272
- </p>
273
- </a-space>
274
- </a-tab-pane>
275
- <a-tab-pane
276
- v-if="!needsSetup"
277
- key="token"
278
- tab="Token 登录"
279
- >
280
- <a-space
281
- direction="vertical"
282
- style="width: 100%"
283
- >
284
- <a-input-password
285
- v-model:value="authTokenInput"
286
- :placeholder="$t('wsHome.k15qunld006')"
287
- @keydown.enter="doLoginWithToken"
288
- />
289
- <a-button
290
- type="primary"
291
- block
292
- :loading="authLoading"
293
- @click="doLoginWithToken"
294
- >
295
- {{ $t('wsHome.k43kol009') }}
296
- </a-button>
297
- </a-space>
298
- </a-tab-pane>
299
- </a-tabs>
300
- </a-card>
301
- </div>
302
-
303
- <!-- 工作区(已登录) -->
304
- <template v-else>
305
- <div class="aw-page-head">
306
- <div>
307
- <p class="aw-kicker">
308
- workshop / overview
309
- </p>
310
- <h1>Workshop {{ $t('wsHome.k3n4m5c025') }}</h1>
311
- <p class="sub">
312
- {{ userStore.user?.name }} {{ $t('wsHome.ke16e53026') }}
313
- </p>
314
- </div>
315
- <div class="head-acts">
316
- <div class="lib-links">
317
- <button
318
- type="button"
319
- class="lib-link"
320
- @click="navigateTo('/workshop/agents')"
321
- >
322
- {{ $t('wsHome.k3pa5h4013') }}
323
- </button>
324
- <button
325
- type="button"
326
- class="lib-link"
327
- @click="navigateTo('/workshop/teams')"
328
- >
329
- {{ $t('wsHome.k3svl8y014') }}
330
- </button>
331
- <button
332
- type="button"
333
- class="lib-link"
334
- @click="navigateTo('/workshop/channel-templates')"
335
- >
336
- Channel {{ $t('wsHome.k41ds5027') }}
337
- </button>
338
- <button
339
- type="button"
340
- class="lib-link"
341
- @click="navigateTo('/tokens')"
342
- >
343
- API Token
344
- </button>
345
- <button
346
- type="button"
347
- class="lib-link"
348
- @click="doLogout"
349
- >
350
- {{ $t('wsHome.k484e7015') }}
351
- </button>
352
- </div>
353
- <button
354
- class="aw-pill im"
355
- @click="createOpen = true"
356
- >
357
- <span class="i-tabler-plus im-pop" />
358
- {{ $t('wsHome.newWs') }}
359
- </button>
360
- </div>
361
- </div>
362
-
363
- <a-spin :spinning="!ready">
364
- <div class="grid">
365
- <div
366
- v-for="ws in wsStore.workspaces"
367
- :key="ws.id"
368
- class="card"
369
- >
370
- <div class="card-head">
371
- <span class="card-mark"><span class="i-tabler-box" /></span>
372
- <span class="name">{{ ws.name }}</span>
373
- </div>
374
- <div class="card-body">
375
- <div
376
- v-for="ch in channelSummary(ws.channelIds)"
377
- :key="ch.id"
378
- class="ch-row"
379
- @click="navigateTo(`/workshop/w/${ws.id}`)"
380
- >
381
- <span
382
- class="dot"
383
- :class="{ live: ch.activeTasks > 0 }"
384
- />
385
- <span class="ch-name">{{ ch.name }}</span>
386
- <span class="ch-meta">
387
- <template v-if="ch.synced">{{ ch.agents }} {{ $t('wsHome.k1ggoa45028') }} {{ ch.busy }} / {{ $t('wsHome.k3wcox029') }} {{ ch.activeTasks }}</template>
388
- <template v-else>{{ $t('wsHome.k1bst7s9016') }}</template>
389
- </span>
390
- </div>
391
- <div
392
- v-if="ws.channelIds.length === 0"
393
- class="empty"
394
- >
395
- {{ $t('wsHome.k1ylgrbc017') }}
396
- </div>
397
- </div>
398
- <div class="card-foot">
399
- <button
400
- class="aw-pill outline im"
401
- @click="navigateTo(`/workshop/w/${ws.id}`)"
402
- >
403
- <span class="i-tabler-arrow-right im-pop" />
404
- {{ $t('wsHome.kr1uwwi018') }}
405
- </button>
406
- <button
407
- class="aw-ghost im"
408
- title="删除 Workspace"
409
- @click.stop="remove(ws.id)"
410
- >
411
- <span class="i-tabler-trash im-shake" />
412
- </button>
413
- </div>
414
- </div>
415
-
416
- <button
417
- v-if="wsStore.workspaces.length === 0 && ready"
418
- class="ws-empty"
419
- @click="createOpen = true"
420
- >
421
- <span class="i-tabler-layout-2 ws-empty-ico" />
422
- <span class="aw-empty-title">从一条产线、一个 Agent 开始</span>
423
- <span class="aw-empty-sub">Workspace 是频道与任务的容器 —— 建立第一个工作区后,你的 Agent 团队即可上线作业。</span>
424
- <span class="pill-btn ws-empty-cta">
425
- <span class="i-tabler-plus" />
426
- 新建第一个 Workspace
427
- </span>
428
- </button>
429
- </div>
430
- </a-spin>
431
-
432
- <a-modal
433
- v-model:open="createOpen"
434
- :title="$t('wsHome.newWs')"
435
- :confirm-loading="createLoading"
436
- ok-text="创建并进入"
437
- cancel-text="取消"
438
- @ok="create"
439
- >
440
- <a-input
441
- v-model:value="createName"
442
- :placeholder="$t('wsHome.ksmpk8l007')"
443
- @keydown.enter="create"
444
- />
445
- </a-modal>
446
- </template>
447
- </div>
448
- </template>
449
-
450
- <style scoped>
451
- .page { padding: 4px; }
452
-
453
- .auth-gate {
454
- display: flex;
455
- justify-content: center;
456
- padding-top: 8vh;
457
- }
458
-
459
- /* 登录卡:open-tag auth 声部(hairline-strong + 柔投影 + serif 标题) */
460
- .auth-card {
461
- position: relative;
462
- width: 420px;
463
- max-width: 92vw;
464
- padding: 26px 26px 18px;
465
- border: 1px solid var(--line-strong) !important;
466
- border-radius: var(--radius-panel);
467
- box-shadow: var(--shadow-float);
468
- }
469
-
470
- .auth-card :deep(h2) {
471
- margin: 0 0 6px;
472
- font-family: var(--font-display);
473
- font-size: 26px;
474
- }
475
-
476
- .sub { margin: 0 0 14px; font-size: 12.5px; color: var(--ink-faint); }
477
- .hint { margin: 8px 0 0; font-size: 11px; color: var(--ink-faint); }
478
-
479
- .head-acts {
480
- display: flex;
481
- flex-direction: column;
482
- gap: 10px;
483
- align-items: flex-end;
484
- }
485
-
486
- /* 窄屏:页头已经纵向堆叠(见 main.css v5),这一组也必须改成"左对齐 + 按钮满行"。
487
- * 否则会出现"文字链居中、主按钮靠右"的错位 —— 两行不同对齐轴,读起来像两组东西(实测 /workshop)。 */
488
- @media (max-width: 900px) {
489
- .head-acts {
490
- align-items: stretch;
491
- gap: 12px;
492
- }
493
-
494
- .lib-links {
495
- justify-content: flex-start;
496
- gap: 8px 16px;
497
- }
498
- }
499
-
500
- /* 库链接行:安静文字链(降噪,主 CTA 只剩一个) */
501
- .lib-links { display: inline-flex; flex-wrap: wrap; gap: 2px 14px; justify-content: flex-end; }
502
-
503
- .lib-link {
504
- padding: 2px 0;
505
- font-family: var(--font-body);
506
- font-size: 12.5px;
507
- color: var(--ink-faint);
508
- cursor: pointer;
509
- background: transparent;
510
- border: 0;
511
- transition: color var(--transition-fast);
512
- }
513
-
514
- .lib-link:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
515
-
516
- /* 卡片头:软方块 mark + serif 名称 */
517
- .card-mark {
518
- display: inline-flex;
519
- align-items: center;
520
- justify-content: center;
521
- flex: none;
522
- width: 30px;
523
- height: 30px;
524
- font-size: 15px;
525
- color: var(--ink-soft);
526
- background: var(--paper-deep);
527
- border-radius: var(--radius-panel-sm);
528
- }
529
-
530
- .card-head .name {
531
- font-family: var(--font-display);
532
- font-size: 17px;
533
- letter-spacing: -0.01em;
534
- color: var(--ink);
535
- }
536
-
537
- .card-foot {
538
- display: flex;
539
- gap: 8px;
540
- align-items: center;
541
- justify-content: space-between;
542
- margin-top: auto;
543
- }
544
-
545
- .grid {
546
- display: grid;
547
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
548
- gap: 16px;
549
- }
550
-
551
- /* 工作台卡片:图纸面板 + 硬边投影,悬停时"浮起" */
552
- .card {
553
- position: relative;
554
- display: flex;
555
- flex-direction: column;
556
- gap: 10px;
557
- padding: 16px;
558
- background: var(--paper-raised);
559
- border: 1px solid var(--line);
560
- border-radius: var(--radius-panel);
561
- box-shadow: var(--shadow-card);
562
- transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
563
- }
564
-
565
- .card:hover {
566
- border-color: var(--line-strong);
567
- transform: translateY(-1px);
568
- }
569
-
570
- .card.placeholder {
571
- align-items: center;
572
- justify-content: center;
573
- min-height: 160px;
574
- font-size: 13px;
575
- opacity: 0.55;
576
- cursor: pointer;
577
- border-style: dashed;
578
- }
579
-
580
- /* 空态:整幅居中构图(跨全部网格列,代替左上角一张小幽灵卡) */
581
- .ws-empty {
582
- grid-column: 1 / -1;
583
- display: flex;
584
- flex-direction: column;
585
- gap: 12px;
586
- align-items: center;
587
- justify-content: center;
588
- min-height: min(480px, calc(100vh - 320px));
589
- padding: 40px 24px;
590
- text-align: center;
591
- cursor: pointer;
592
- background: transparent;
593
- border: 1px dashed var(--line-strong);
594
- border-radius: var(--radius-panel);
595
- transition: background var(--transition-fast), border-color var(--transition-fast);
596
- }
597
- .ws-empty:hover {
598
- background: var(--hover-tint);
599
- border-color: var(--ink-fainter);
600
- }
601
- .ws-empty-ico { font-size: 34px; color: var(--ink-faint); }
602
- .aw-empty-title { font-size: 16px; font-weight: 600; color: var(--ink); }
603
- .aw-empty-sub { max-width: 420px; font-size: 12.5px; line-height: 1.7; color: var(--ink-faint); }
604
- .ws-empty-cta { margin-top: 6px; pointer-events: none; }
605
-
606
- .big { font-size: 28px; }
607
-
608
- .card-head {
609
- display: flex;
610
- gap: 8px;
611
- align-items: center;
612
- font-size: 15px;
613
- }
614
-
615
- .card-head > :first-child { color: var(--accent-cobalt); }
616
-
617
- .name {
618
- flex: 1 1 auto;
619
- font-family: var(--font-display);
620
- font-size: 17px;
621
- font-weight: 600;
622
- }
623
-
624
- .op { cursor: pointer; opacity: 0.4; }
625
- .op:hover { opacity: 1; }
626
-
627
- .card-body { flex: 1 1 auto; min-height: 40px; }
628
-
629
- .ch-row {
630
- display: flex;
631
- gap: 8px;
632
- align-items: center;
633
- padding: 5px 7px;
634
- margin: 2px 0;
635
- font-size: 12px;
636
- cursor: pointer;
637
- border-radius: var(--radius-panel);
638
- transition: background 0.15s ease, transform 0.15s ease;
639
- }
640
-
641
- .ch-row:hover {
642
- background: color-mix(in srgb, var(--accent-cobalt) 7%, transparent);
643
- transform: translateX(2px);
644
- }
645
-
646
- .dot {
647
- flex: 0 0 auto;
648
- width: 7px;
649
- height: 7px;
650
- background: var(--line-strong);
651
- border-radius: 50%;
652
- }
653
-
654
- .dot.live { background: var(--accent-moss); box-shadow: 0 0 6px var(--accent-moss); }
655
-
656
- .ch-name { flex: 0 0 auto; font-weight: 600; }
657
- .ch-meta {
658
- flex: 1 1 auto;
659
- overflow: hidden;
660
- font-family: var(--font-mono);
661
- font-size: 11px;
662
- opacity: 0.5;
663
- text-overflow: ellipsis;
664
- white-space: nowrap;
665
- }
666
-
667
- .empty { padding: 12px 6px; font-size: 12px; opacity: 0.4; }
668
- </style>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Workspace 总览(用户级隔离):
4
+ * - 未登录 → 注册/登录门(用户 token = 管理 API 凭证)
5
+ * - 已登录 → workspace 卡片墙(服务端持久化;按用户隔离)+ 实时状态徽标
6
+ */
7
+ import { message } from 'ant-design-vue'
8
+ import { useWorkspacesStore } from '../../stores/workshop/workspaces'
9
+ import { useEntitiesStore } from '../../stores/workshop/entities'
10
+ import { useUserStore } from '../../stores/workshop/user'
11
+ import { useWorkshopWs } from '../../composables/workshop/useWorkshopWs'
12
+
13
+ const { t } = useI18n()
14
+
15
+ definePageMeta({ layout: 'default' })
16
+
17
+ const userStore = useUserStore()
18
+ const wsStore = useWorkspacesStore()
19
+ const entities = useEntitiesStore()
20
+ const { subscribe } = useWorkshopWs()
21
+
22
+ // ===== 登录门(全局用户系统)=====
23
+ const authTab = ref<'register' | 'login' | 'token'>('login')
24
+ const authName = ref('')
25
+ const authEmail = ref('')
26
+ const authPassword = ref('')
27
+ const authTokenInput = ref('')
28
+ const authLoading = ref(false)
29
+
30
+ // 首启初始化:系统尚无管理员 → 登录门切换为"注册管理员"模式(首个注册账号自动成为 admin)
31
+ const needsSetup = ref(false)
32
+ onMounted(async () => {
33
+ try {
34
+ const res = await $fetch<{ code: number, data?: { needsSetup: boolean } }>('/api/users/setup-status')
35
+ if (res.code === 0 && res.data?.needsSetup) {
36
+ needsSetup.value = true
37
+ authTab.value = 'register'
38
+ }
39
+ }
40
+ catch { /* 探测失败按常规登录门呈现 */ }
41
+ })
42
+
43
+ const doRegister = async (): Promise<void> => {
44
+ if (!authName.value.trim()) {
45
+ message.warning(t('wsHome.k1vnhyks019'))
46
+ return
47
+ }
48
+ if (!authEmail.value.trim()) {
49
+ message.warning(t('wsHome.k8ieqzj020'))
50
+ return
51
+ }
52
+ if (authPassword.value.length < 6) {
53
+ message.warning(t('wsHome.ksx73ra021'))
54
+ return
55
+ }
56
+ authLoading.value = true
57
+ try {
58
+ const user = await userStore.register(authName.value, authEmail.value, authPassword.value)
59
+ message.success(needsSetup.value ? t('wsHome.adminCreated', { p0: user.name }) : t('wsHome.k1mbatbh030', { p0: user.name }))
60
+ needsSetup.value = false
61
+ authName.value = ''
62
+ authEmail.value = ''
63
+ authPassword.value = ''
64
+ }
65
+ catch (e) {
66
+ message.error(apiErrorMessage(e))
67
+ }
68
+ finally {
69
+ authLoading.value = false
70
+ }
71
+ }
72
+ const doLogin = async (): Promise<void> => {
73
+ if (!authEmail.value.trim()) {
74
+ message.warning(t('wsHome.k8ieqzj020'))
75
+ return
76
+ }
77
+ authLoading.value = true
78
+ try {
79
+ const user = await userStore.login(authEmail.value, authPassword.value)
80
+ message.success(t('wsHome.kwixbdl031', { p0: user.name }))
81
+ needsSetup.value = false
82
+ authPassword.value = ''
83
+ }
84
+ catch (e) {
85
+ message.error(apiErrorMessage(e))
86
+ }
87
+ finally {
88
+ authLoading.value = false
89
+ }
90
+ }
91
+ const doLoginWithToken = async (): Promise<void> => {
92
+ authLoading.value = true
93
+ try {
94
+ const user = await userStore.loginWithToken(authTokenInput.value)
95
+ message.success(t('wsHome.kwixbdl031', { p0: user.name }))
96
+ needsSetup.value = false
97
+ authTokenInput.value = ''
98
+ }
99
+ catch (e) {
100
+ message.error(apiErrorMessage(e))
101
+ }
102
+ finally {
103
+ authLoading.value = false
104
+ }
105
+ }
106
+ const doLogout = (): void => {
107
+ userStore.logout()
108
+ message.success(t('wsHome.k3ngm6p022'))
109
+ }
110
+
111
+ // ===== 登录后加载 workspace(服务端持久化)=====
112
+ const ready = ref(false)
113
+ watch(() => userStore.isLoggedIn, async (ok) => {
114
+ if (!ok) {
115
+ ready.value = false
116
+ return
117
+ }
118
+ await userStore.refresh()
119
+ if (!userStore.isLoggedIn) return
120
+ try {
121
+ await wsStore.load()
122
+ ready.value = true
123
+ }
124
+ catch (e) {
125
+ // SSR 安全:ant-design-vue message 依赖 DOM,服务端静默(客户端进入页面后可重试)
126
+ if (import.meta.client) message.error(e instanceof Error ? e.message : t('wsHome.k1br33vc023'))
127
+ }
128
+ }, { immediate: true })
129
+
130
+ // 已有 workspace 的 channel 订阅(总览页也保持事件流活跃,状态徽标实时)
131
+ watch(
132
+ () => ready.value && wsStore.workspaces.map(w => w.channelIds.join(',')).join('|'),
133
+ () => {
134
+ if (!ready.value) return
135
+ for (const ws of wsStore.workspaces) {
136
+ for (const id of ws.channelIds) subscribe(id)
137
+ }
138
+ },
139
+ { immediate: true },
140
+ )
141
+
142
+ const createOpen = ref(false)
143
+ const createName = ref('')
144
+ const createLoading = ref(false)
145
+ const create = async (): Promise<void> => {
146
+ const name = createName.value.trim()
147
+ if (!name) {
148
+ message.warning(t('wsHome.nameRequired'))
149
+ return
150
+ }
151
+ createLoading.value = true
152
+ try {
153
+ const ws = await wsStore.create(name)
154
+ createOpen.value = false
155
+ createName.value = ''
156
+ wsStore.setActiveWorkspaceId(ws.id)
157
+ navigateTo(`/workshop/w/${ws.id}`)
158
+ }
159
+ catch (e) {
160
+ message.error(apiErrorMessage(e))
161
+ }
162
+ finally {
163
+ createLoading.value = false
164
+ }
165
+ }
166
+
167
+ const remove = async (id: string): Promise<void> => {
168
+ await wsStore.remove(id)
169
+ message.success(t('wsHome.k3n5sd7024'))
170
+ }
171
+
172
+ const channelSummary = (channelIds: string[]) => channelIds.map((id) => {
173
+ const meta = entities.channels[id]
174
+ const agents = entities.agents[id] ?? []
175
+ return {
176
+ id,
177
+ name: meta?.name ?? id.slice(0, 8),
178
+ /** 实体基线(WS 快照)是否已到达:未到时计数不可信,展示"同步中"而非误导性的 0 */
179
+ synced: meta !== undefined,
180
+ agents: agents.length,
181
+ busy: agents.filter(a => a.state === 'busy').length,
182
+ activeTasks: (entities.tasks[id] ?? []).filter(t => !['COMPLETED', 'CANCELED', 'FAILED'].includes(t.state)).length,
183
+ }
184
+ })
185
+
186
+ useHead({ title: () => t('titles.workshop') })
187
+ </script>
188
+
189
+ <template>
190
+ <div class="page">
191
+ <!-- 登录门 -->
192
+ <div
193
+ v-if="!userStore.isLoggedIn"
194
+ class="auth-gate aw-orbs"
195
+ >
196
+ <a-card class="auth-card">
197
+ <p class="aw-kicker">
198
+ agentworkshop / sign in
199
+ </p>
200
+ <h2>{{ needsSetup ? $t('wsHome.setupTitle') : $t('wsHome.kr0vzqu008') }}</h2>
201
+ <p class="sub">
202
+ {{ needsSetup
203
+ ? $t('wsHome.setupSub')
204
+ : $t('wsHome.normalSub') }}
205
+ </p>
206
+ <a-tabs v-model:active-key="authTab">
207
+ <a-tab-pane
208
+ v-if="!needsSetup"
209
+ key="login"
210
+ :tab="$t('wsHome.tabLogin')"
211
+ >
212
+ <a-space
213
+ direction="vertical"
214
+ style="width: 100%"
215
+ >
216
+ <a-input
217
+ v-model:value="authEmail"
218
+ type="email"
219
+ :placeholder="$t('wsHome.k48h2c001')"
220
+ @keydown.enter="doLogin"
221
+ />
222
+ <a-input-password
223
+ v-model:value="authPassword"
224
+ :placeholder="$t('wsHome.k3yvgs002')"
225
+ @keydown.enter="doLogin"
226
+ />
227
+ <a-button
228
+ type="primary"
229
+ block
230
+ :loading="authLoading"
231
+ @click="doLogin"
232
+ >
233
+ {{ $t('wsHome.k43kol009') }}
234
+ </a-button>
235
+ <p class="hint">
236
+ {{ $t('wsHome.k17x74cj010') }}
237
+ </p>
238
+ </a-space>
239
+ </a-tab-pane>
240
+ <a-tab-pane
241
+ key="register"
242
+ :tab="needsSetup ? $t('wsHome.tabRegAdmin') : $t('wsHome.tabRegUser')"
243
+ >
244
+ <a-space
245
+ direction="vertical"
246
+ style="width: 100%"
247
+ >
248
+ <a-input
249
+ v-model:value="authName"
250
+ :placeholder="$t('wsHome.kwgbai9003')"
251
+ />
252
+ <a-input
253
+ v-model:value="authEmail"
254
+ type="email"
255
+ :placeholder="$t('wsHome.kizjkbo004')"
256
+ />
257
+ <a-input-password
258
+ v-model:value="authPassword"
259
+ :placeholder="$t('wsHome.kh3cqfn005')"
260
+ @keydown.enter="doRegister"
261
+ />
262
+ <a-button
263
+ type="primary"
264
+ block
265
+ :loading="authLoading"
266
+ @click="doRegister"
267
+ >
268
+ {{ needsSetup ? $t('wsHome.setupCta') : $t('wsHome.k1so6a0v011') }}
269
+ </a-button>
270
+ <p class="hint">
271
+ {{ $t('wsHome.k1r0a4u3012') }}
272
+ </p>
273
+ </a-space>
274
+ </a-tab-pane>
275
+ <a-tab-pane
276
+ v-if="!needsSetup"
277
+ key="token"
278
+ :tab="$t('wsHome.tabToken')"
279
+ >
280
+ <a-space
281
+ direction="vertical"
282
+ style="width: 100%"
283
+ >
284
+ <a-input-password
285
+ v-model:value="authTokenInput"
286
+ :placeholder="$t('wsHome.k15qunld006')"
287
+ @keydown.enter="doLoginWithToken"
288
+ />
289
+ <a-button
290
+ type="primary"
291
+ block
292
+ :loading="authLoading"
293
+ @click="doLoginWithToken"
294
+ >
295
+ {{ $t('wsHome.k43kol009') }}
296
+ </a-button>
297
+ </a-space>
298
+ </a-tab-pane>
299
+ </a-tabs>
300
+ </a-card>
301
+ </div>
302
+
303
+ <!-- 工作区(已登录) -->
304
+ <template v-else>
305
+ <div class="aw-page-head">
306
+ <div>
307
+ <p class="aw-kicker">
308
+ workshop / overview
309
+ </p>
310
+ <h1>Workshop {{ $t('wsHome.k3n4m5c025') }}</h1>
311
+ <p class="sub">
312
+ {{ userStore.user?.name }} {{ $t('wsHome.ke16e53026') }}
313
+ </p>
314
+ </div>
315
+ <div class="head-acts">
316
+ <div class="lib-links">
317
+ <button
318
+ type="button"
319
+ class="lib-link"
320
+ @click="navigateTo('/workshop/agents')"
321
+ >
322
+ {{ $t('wsHome.k3pa5h4013') }}
323
+ </button>
324
+ <button
325
+ type="button"
326
+ class="lib-link"
327
+ @click="navigateTo('/workshop/teams')"
328
+ >
329
+ {{ $t('wsHome.k3svl8y014') }}
330
+ </button>
331
+ <button
332
+ type="button"
333
+ class="lib-link"
334
+ @click="navigateTo('/workshop/channel-templates')"
335
+ >
336
+ Channel {{ $t('wsHome.k41ds5027') }}
337
+ </button>
338
+ <button
339
+ type="button"
340
+ class="lib-link"
341
+ @click="navigateTo('/tokens')"
342
+ >
343
+ API Token
344
+ </button>
345
+ <button
346
+ type="button"
347
+ class="lib-link"
348
+ @click="doLogout"
349
+ >
350
+ {{ $t('wsHome.k484e7015') }}
351
+ </button>
352
+ </div>
353
+ <button
354
+ class="aw-pill im"
355
+ @click="createOpen = true"
356
+ >
357
+ <span class="i-tabler-plus im-pop" />
358
+ {{ $t('wsHome.newWs') }}
359
+ </button>
360
+ </div>
361
+ </div>
362
+
363
+ <a-spin :spinning="!ready">
364
+ <div class="grid">
365
+ <div
366
+ v-for="ws in wsStore.workspaces"
367
+ :key="ws.id"
368
+ class="card"
369
+ >
370
+ <div class="card-head">
371
+ <span class="card-mark"><span class="i-tabler-box" /></span>
372
+ <span class="name">{{ ws.name }}</span>
373
+ </div>
374
+ <div class="card-body">
375
+ <div
376
+ v-for="ch in channelSummary(ws.channelIds)"
377
+ :key="ch.id"
378
+ class="ch-row"
379
+ @click="navigateTo(`/workshop/w/${ws.id}`)"
380
+ >
381
+ <span
382
+ class="dot"
383
+ :class="{ live: ch.activeTasks > 0 }"
384
+ />
385
+ <span class="ch-name">{{ ch.name }}</span>
386
+ <span class="ch-meta">
387
+ <template v-if="ch.synced">{{ ch.agents }} {{ $t('wsHome.k1ggoa45028') }} {{ ch.busy }} / {{ $t('wsHome.k3wcox029') }} {{ ch.activeTasks }}</template>
388
+ <template v-else>{{ $t('wsHome.k1bst7s9016') }}</template>
389
+ </span>
390
+ </div>
391
+ <div
392
+ v-if="ws.channelIds.length === 0"
393
+ class="empty"
394
+ >
395
+ {{ $t('wsHome.k1ylgrbc017') }}
396
+ </div>
397
+ </div>
398
+ <div class="card-foot">
399
+ <button
400
+ class="aw-pill outline im"
401
+ @click="navigateTo(`/workshop/w/${ws.id}`)"
402
+ >
403
+ <span class="i-tabler-arrow-right im-pop" />
404
+ {{ $t('wsHome.kr1uwwi018') }}
405
+ </button>
406
+ <button
407
+ class="aw-ghost im"
408
+ :title="$t('wsHome.delWs')"
409
+ @click.stop="remove(ws.id)"
410
+ >
411
+ <span class="i-tabler-trash im-shake" />
412
+ </button>
413
+ </div>
414
+ </div>
415
+
416
+ <button
417
+ v-if="wsStore.workspaces.length === 0 && ready"
418
+ class="ws-empty"
419
+ @click="createOpen = true"
420
+ >
421
+ <span class="i-tabler-layout-2 ws-empty-ico" />
422
+ <span class="aw-empty-title">{{ $t('wsHome.emptyTitle') }}</span>
423
+ <span class="aw-empty-sub">{{ $t('wsHome.emptySub') }}</span>
424
+ <span class="pill-btn ws-empty-cta">
425
+ <span class="i-tabler-plus" />
426
+ {{ $t('wsHome.emptyCta') }}
427
+ </span>
428
+ </button>
429
+ </div>
430
+ </a-spin>
431
+
432
+ <a-modal
433
+ v-model:open="createOpen"
434
+ :title="$t('wsHome.newWs')"
435
+ :confirm-loading="createLoading"
436
+ :ok-text="$t('wsHome.createEnterOk')"
437
+ :cancel-text="$t('common.cancel')"
438
+ @ok="create"
439
+ >
440
+ <a-input
441
+ v-model:value="createName"
442
+ :placeholder="$t('wsHome.ksmpk8l007')"
443
+ @keydown.enter="create"
444
+ />
445
+ </a-modal>
446
+ </template>
447
+ </div>
448
+ </template>
449
+
450
+ <style scoped>
451
+ .page { padding: 4px; }
452
+
453
+ .auth-gate {
454
+ display: flex;
455
+ justify-content: center;
456
+ padding-top: 8vh;
457
+ }
458
+
459
+ /* 登录卡:open-tag auth 声部(hairline-strong + 柔投影 + serif 标题) */
460
+ .auth-card {
461
+ position: relative;
462
+ width: 420px;
463
+ max-width: 92vw;
464
+ padding: 26px 26px 18px;
465
+ border: 1px solid var(--line-strong) !important;
466
+ border-radius: var(--radius-panel);
467
+ box-shadow: var(--shadow-float);
468
+ }
469
+
470
+ .auth-card :deep(h2) {
471
+ margin: 0 0 6px;
472
+ font-family: var(--font-display);
473
+ font-size: 26px;
474
+ }
475
+
476
+ .sub { margin: 0 0 14px; font-size: 12.5px; color: var(--ink-faint); }
477
+ .hint { margin: 8px 0 0; font-size: 11px; color: var(--ink-faint); }
478
+
479
+ .head-acts {
480
+ display: flex;
481
+ flex-direction: column;
482
+ gap: 10px;
483
+ align-items: flex-end;
484
+ }
485
+
486
+ /* 窄屏:页头已经纵向堆叠(见 main.css v5),这一组也必须改成"左对齐 + 按钮满行"。
487
+ * 否则会出现"文字链居中、主按钮靠右"的错位 —— 两行不同对齐轴,读起来像两组东西(实测 /workshop)。 */
488
+ @media (max-width: 900px) {
489
+ .head-acts {
490
+ align-items: stretch;
491
+ gap: 12px;
492
+ }
493
+
494
+ .lib-links {
495
+ justify-content: flex-start;
496
+ gap: 8px 16px;
497
+ }
498
+ }
499
+
500
+ /* 库链接行:安静文字链(降噪,主 CTA 只剩一个) */
501
+ .lib-links { display: inline-flex; flex-wrap: wrap; gap: 2px 14px; justify-content: flex-end; }
502
+
503
+ .lib-link {
504
+ padding: 2px 0;
505
+ font-family: var(--font-body);
506
+ font-size: 12.5px;
507
+ color: var(--ink-faint);
508
+ cursor: pointer;
509
+ background: transparent;
510
+ border: 0;
511
+ transition: color var(--transition-fast);
512
+ }
513
+
514
+ .lib-link:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
515
+
516
+ /* 卡片头:软方块 mark + serif 名称 */
517
+ .card-mark {
518
+ display: inline-flex;
519
+ align-items: center;
520
+ justify-content: center;
521
+ flex: none;
522
+ width: 30px;
523
+ height: 30px;
524
+ font-size: 15px;
525
+ color: var(--ink-soft);
526
+ background: var(--paper-deep);
527
+ border-radius: var(--radius-panel-sm);
528
+ }
529
+
530
+ .card-head .name {
531
+ font-family: var(--font-display);
532
+ font-size: 17px;
533
+ letter-spacing: -0.01em;
534
+ color: var(--ink);
535
+ }
536
+
537
+ .card-foot {
538
+ display: flex;
539
+ gap: 8px;
540
+ align-items: center;
541
+ justify-content: space-between;
542
+ margin-top: auto;
543
+ }
544
+
545
+ .grid {
546
+ display: grid;
547
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
548
+ gap: 16px;
549
+ }
550
+
551
+ /* 工作台卡片:图纸面板 + 硬边投影,悬停时"浮起" */
552
+ .card {
553
+ position: relative;
554
+ display: flex;
555
+ flex-direction: column;
556
+ gap: 10px;
557
+ padding: 16px;
558
+ background: var(--paper-raised);
559
+ border: 1px solid var(--line);
560
+ border-radius: var(--radius-panel);
561
+ box-shadow: var(--shadow-card);
562
+ transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
563
+ }
564
+
565
+ .card:hover {
566
+ border-color: var(--line-strong);
567
+ transform: translateY(-1px);
568
+ }
569
+
570
+ .card.placeholder {
571
+ align-items: center;
572
+ justify-content: center;
573
+ min-height: 160px;
574
+ font-size: 13px;
575
+ opacity: 0.55;
576
+ cursor: pointer;
577
+ border-style: dashed;
578
+ }
579
+
580
+ /* 空态:整幅居中构图(跨全部网格列,代替左上角一张小幽灵卡) */
581
+ .ws-empty {
582
+ grid-column: 1 / -1;
583
+ display: flex;
584
+ flex-direction: column;
585
+ gap: 12px;
586
+ align-items: center;
587
+ justify-content: center;
588
+ min-height: min(480px, calc(100vh - 320px));
589
+ padding: 40px 24px;
590
+ text-align: center;
591
+ cursor: pointer;
592
+ background: transparent;
593
+ border: 1px dashed var(--line-strong);
594
+ border-radius: var(--radius-panel);
595
+ transition: background var(--transition-fast), border-color var(--transition-fast);
596
+ }
597
+ .ws-empty:hover {
598
+ background: var(--hover-tint);
599
+ border-color: var(--ink-fainter);
600
+ }
601
+ .ws-empty-ico { font-size: 34px; color: var(--ink-faint); }
602
+ .aw-empty-title { font-size: 16px; font-weight: 600; color: var(--ink); }
603
+ .aw-empty-sub { max-width: 420px; font-size: 12.5px; line-height: 1.7; color: var(--ink-faint); }
604
+ .ws-empty-cta { margin-top: 6px; pointer-events: none; }
605
+
606
+ .big { font-size: 28px; }
607
+
608
+ .card-head {
609
+ display: flex;
610
+ gap: 8px;
611
+ align-items: center;
612
+ font-size: 15px;
613
+ }
614
+
615
+ .card-head > :first-child { color: var(--accent-cobalt); }
616
+
617
+ .name {
618
+ flex: 1 1 auto;
619
+ font-family: var(--font-display);
620
+ font-size: 17px;
621
+ font-weight: 600;
622
+ }
623
+
624
+ .op { cursor: pointer; opacity: 0.4; }
625
+ .op:hover { opacity: 1; }
626
+
627
+ .card-body { flex: 1 1 auto; min-height: 40px; }
628
+
629
+ .ch-row {
630
+ display: flex;
631
+ gap: 8px;
632
+ align-items: center;
633
+ padding: 5px 7px;
634
+ margin: 2px 0;
635
+ font-size: 12px;
636
+ cursor: pointer;
637
+ border-radius: var(--radius-panel);
638
+ transition: background 0.15s ease, transform 0.15s ease;
639
+ }
640
+
641
+ .ch-row:hover {
642
+ background: color-mix(in srgb, var(--accent-cobalt) 7%, transparent);
643
+ transform: translateX(2px);
644
+ }
645
+
646
+ .dot {
647
+ flex: 0 0 auto;
648
+ width: 7px;
649
+ height: 7px;
650
+ background: var(--line-strong);
651
+ border-radius: 50%;
652
+ }
653
+
654
+ .dot.live { background: var(--accent-moss); box-shadow: 0 0 6px var(--accent-moss); }
655
+
656
+ .ch-name { flex: 0 0 auto; font-weight: 600; }
657
+ .ch-meta {
658
+ flex: 1 1 auto;
659
+ overflow: hidden;
660
+ font-family: var(--font-mono);
661
+ font-size: 11px;
662
+ opacity: 0.5;
663
+ text-overflow: ellipsis;
664
+ white-space: nowrap;
665
+ }
666
+
667
+ .empty { padding: 12px 6px; font-size: 12px; opacity: 0.4; }
668
+ </style>