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.
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/B8uy9iRW.js +1 -0
- package/.output/public/_nuxt/BGTYYQH-.js +1 -0
- package/.output/public/_nuxt/BUmHEJ77.js +2 -0
- package/.output/public/_nuxt/BY7YrV_D.js +1 -0
- package/.output/public/_nuxt/Bqcgz95s.js +18 -0
- package/.output/public/_nuxt/C7UlqPeD.js +1 -0
- package/.output/public/_nuxt/CTmDn-YP.js +1 -0
- package/.output/public/_nuxt/Cs8pVeVG.js +1 -0
- package/.output/public/_nuxt/{DcsCcxp4.js → D98NH8kG.js} +1 -1
- package/.output/public/_nuxt/DKySmNPI.js +14 -0
- package/.output/public/_nuxt/Di0NY_ht.js +1 -0
- package/.output/public/_nuxt/Du9v6qGB.js +1 -0
- package/.output/public/_nuxt/{C_iYD-EB.js → EHlEMRii.js} +1 -1
- package/.output/public/_nuxt/Eke1fs-U.js +1 -0
- package/.output/public/_nuxt/{DeJykBFf.js → H0BcqZtT.js} +1 -1
- package/.output/public/_nuxt/{C4oW1HsE.js → HSYEkkce.js} +1 -1
- package/.output/public/_nuxt/{DZshyNLa.js → IREZ1eC2.js} +1 -1
- package/.output/public/_nuxt/{D_OTvQza.js → J9oU00D1.js} +1 -1
- package/.output/public/_nuxt/OmpTerminalPanel.BxR5-lvi.css +1 -0
- package/.output/public/_nuxt/SSyAPaHc.js +1 -0
- package/.output/public/_nuxt/TownView.li4EMQxK.css +1 -0
- package/.output/public/_nuxt/VdLk4jLE2.js +1 -0
- package/.output/public/_nuxt/_id_.BhsXoEuk.css +1 -0
- package/.output/public/_nuxt/_id_.DQyj-WKN.css +1 -0
- package/.output/public/_nuxt/{_wsId_.B4WiqkIS.css → _wsId_.CTx-BlfF.css} +1 -1
- package/.output/public/_nuxt/aShsv_rG.js +19 -0
- package/.output/public/_nuxt/{yF0aYMF1.js → aesgXKSr.js} +3 -3
- package/.output/public/_nuxt/agents.B21nUEUX.css +1 -0
- package/.output/public/_nuxt/aml.ekTjxFkf.css +1 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/b5506ef0-f48e-4f14-a6c0-d65e505d0b09.json +1 -0
- package/.output/public/_nuxt/ca4TJ3Op.js +1 -0
- package/.output/public/_nuxt/channel-templates.DplM1pJ3.css +1 -0
- package/.output/public/_nuxt/daq.BdVmoIfe.css +1 -0
- package/.output/public/_nuxt/dcw.DCgyzpWV.css +1 -0
- package/.output/public/_nuxt/{default.J32SgqFY.css → default.Bh3L2HU8.css} +1 -1
- package/.output/public/_nuxt/lHAxdSsj.js +1 -0
- package/.output/public/_nuxt/pages.j9rzRocr.css +1 -0
- package/.output/public/_nuxt/plugins.DhiZbHU8.css +1 -0
- package/.output/public/_nuxt/settings.BRx9rIDg.css +1 -0
- package/.output/public/_nuxt/teams.BLlOPB_Z.css +1 -0
- package/.output/public/_nuxt/tokens.De8xzszn.css +1 -0
- package/.output/public/_nuxt/workshop.D1jFNkrn.css +1 -0
- package/.output/server/chunks/_/nitro.mjs +1058 -830
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs → OmpTerminalPanel-CVvxpsF7.mjs} +15 -15
- package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs.map → OmpTerminalPanel-CVvxpsF7.mjs.map} +1 -1
- package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs → TownView-CJ6IsDCc.mjs} +229 -228
- package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs.map → TownView-CJ6IsDCc.mjs.map} +1 -1
- package/.output/server/chunks/build/{_id_-BU1wfOID.mjs → _id_-CVD_H0ir.mjs} +149 -147
- package/.output/server/chunks/build/_id_-CVD_H0ir.mjs.map +1 -0
- package/.output/server/chunks/build/{_id_-BYsEua57.mjs → _id_-CtxMb3qp.mjs} +47 -46
- package/.output/server/chunks/build/_id_-CtxMb3qp.mjs.map +1 -0
- package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs → _wsId_-DpY3T-md.mjs} +516 -446
- package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs.map → _wsId_-DpY3T-md.mjs.map} +1 -1
- package/.output/server/chunks/build/{agents-CLQqJF2y.mjs → agents-DsCkuXDV.mjs} +49 -46
- package/.output/server/chunks/build/agents-DsCkuXDV.mjs.map +1 -0
- package/.output/server/chunks/build/{aml-_7FdFL2p.mjs → aml-CT0xKGKI.mjs} +122 -105
- package/.output/server/chunks/build/aml-CT0xKGKI.mjs.map +1 -0
- package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs +117 -0
- package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs.map +1 -0
- package/.output/server/chunks/build/{channel-templates-p6SZNjlM.mjs → channel-templates-Dla9FbYa.mjs} +49 -37
- package/.output/server/chunks/build/channel-templates-Dla9FbYa.mjs.map +1 -0
- package/.output/server/chunks/build/{daq-cgyjKmJu.mjs → daq-DWWSKGAV.mjs} +95 -93
- package/.output/server/chunks/build/daq-DWWSKGAV.mjs.map +1 -0
- package/.output/server/chunks/build/{dcw-CoTxBlAZ.mjs → dcw-CT8k17_E.mjs} +46 -45
- package/.output/server/chunks/build/dcw-CT8k17_E.mjs.map +1 -0
- package/.output/server/chunks/build/{default-BE4r0FZN.mjs → default-D3525UFX.mjs} +16 -15
- package/.output/server/chunks/build/{default-BE4r0FZN.mjs.map → default-D3525UFX.mjs.map} +1 -1
- package/.output/server/chunks/build/{logs-BSOeZwwI.mjs → logs--dByNyhw.mjs} +2 -2
- package/.output/server/chunks/build/{logs-BSOeZwwI.mjs.map → logs--dByNyhw.mjs.map} +1 -1
- package/.output/server/chunks/build/{monitor-C0okQaXz.mjs → monitor-Dh2Kvaq5.mjs} +2 -2
- package/.output/server/chunks/build/{monitor-C0okQaXz.mjs.map → monitor-Dh2Kvaq5.mjs.map} +1 -1
- package/.output/server/chunks/build/{pages-DFxjn9Xa.mjs → pages-BHcSBM2D.mjs} +26 -20
- package/.output/server/chunks/build/pages-BHcSBM2D.mjs.map +1 -0
- package/.output/server/chunks/build/{plugins-lGlUr1lj.mjs → plugins-BNgIi61W.mjs} +23 -17
- package/.output/server/chunks/build/plugins-BNgIi61W.mjs.map +1 -0
- package/.output/server/chunks/build/{settings-Ddd1qx2Z.mjs → settings-ipYBDWdn.mjs} +45 -44
- package/.output/server/chunks/build/settings-ipYBDWdn.mjs.map +1 -0
- package/.output/server/chunks/build/{teams-MEDLZdMn.mjs → teams-5lx-CwFa.mjs} +47 -44
- package/.output/server/chunks/build/teams-5lx-CwFa.mjs.map +1 -0
- package/.output/server/chunks/build/{tokens-C7fTM5C2.mjs → tokens-q-IDT8Io.mjs} +73 -53
- package/.output/server/chunks/build/tokens-q-IDT8Io.mjs.map +1 -0
- package/.output/server/chunks/build/{town-qMPql9Se.mjs → town-QJWB5wng.mjs} +5 -4
- package/.output/server/chunks/build/town-QJWB5wng.mjs.map +1 -0
- package/.output/server/chunks/build/{useDaqStream-CIBaiGAl.mjs → useDaqStream-DfCFpb3p.mjs} +9 -3
- package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs.map +1 -0
- package/.output/server/chunks/build/{useDcwStream-CBbiYnWe.mjs → useDcwStream-UPiX-3iO.mjs} +11 -2
- package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs.map +1 -0
- package/.output/server/chunks/build/{workshop-BL5bhfs0.mjs → workshop-BIlCj_ZP.mjs} +45 -45
- package/.output/server/chunks/build/workshop-BIlCj_ZP.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels.post.mjs +3 -3
- package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index5.get.mjs +3 -1
- package/.output/server/chunks/routes/api/workshop/index5.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index6.get.mjs +3 -1
- package/.output/server/chunks/routes/api/workshop/index6.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
- package/.output/server/chunks/routes/renderer.mjs +2 -2
- package/.output/server/chunks/virtual/entry.mjs +639 -88
- package/.output/server/chunks/virtual/entry.mjs.map +1 -1
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/README-zh.md +16 -6
- package/README.md +17 -7
- package/app/components/AppHeader.vue +961 -959
- package/app/components/workshop/A2aRpcDebugger.vue +237 -237
- package/app/components/workshop/AgentInspectorDrawer.vue +424 -424
- package/app/components/workshop/AgentLanesView.vue +998 -998
- package/app/components/workshop/ArtifactCard.vue +120 -120
- package/app/components/workshop/AssetLibrary.vue +452 -452
- package/app/components/workshop/ChannelSessionList.vue +812 -804
- package/app/components/workshop/CommandPalette.vue +269 -269
- package/app/components/workshop/Composer.vue +827 -827
- package/app/components/workshop/DeviceTwinPanel.vue +643 -643
- package/app/components/workshop/EventBlock.vue +602 -602
- package/app/components/workshop/FileSelectorModal.vue +188 -188
- package/app/components/workshop/InspectorPanel.vue +301 -301
- package/app/components/workshop/MemoryPanel.vue +364 -364
- package/app/components/workshop/MultiChannelView.vue +189 -189
- package/app/components/workshop/TaskBoardView.vue +686 -686
- package/app/components/workshop/TaskInspectorDrawer.vue +390 -390
- package/app/components/workshop/TranscriptTimeline.vue +601 -601
- package/app/components/workshop/blocks/ClusterArtifact.vue +61 -61
- package/app/components/workshop/blocks/ClusterError.vue +85 -85
- package/app/components/workshop/blocks/ClusterLife.vue +87 -87
- package/app/components/workshop/blocks/ClusterMember.vue +95 -93
- package/app/components/workshop/blocks/ClusterMemory.vue +84 -84
- package/app/components/workshop/blocks/ClusterOther.vue +67 -67
- package/app/components/workshop/blocks/ClusterStatus.vue +149 -147
- package/app/components/workshop/blocks/ClusterTool.vue +158 -158
- package/app/components/workshop/terminal/OmpTerminalPanel.vue +1011 -1011
- package/app/components/workshop/town/TownView.vue +56 -56
- package/app/composables/workshop/useDaqStream.ts +7 -4
- package/app/composables/workshop/useDcwStream.ts +8 -2
- package/app/middleware/locale-cookie.global.ts +22 -0
- package/app/pages/aml/index.vue +24 -2
- package/app/pages/daq/[id].vue +20 -16
- package/app/pages/daq/index.vue +2310 -2307
- package/app/pages/dcw/[id].vue +26 -23
- package/app/pages/dcw/index.vue +3 -3
- package/app/pages/index.vue +987 -981
- package/app/pages/plugins/index.vue +317 -311
- package/app/pages/settings.vue +1564 -1562
- package/app/pages/tokens.vue +15 -15
- package/app/pages/workshop/agents.vue +612 -609
- package/app/pages/workshop/channel-templates.vue +18 -11
- package/app/pages/workshop/index.vue +668 -668
- package/app/pages/workshop/teams.vue +743 -740
- package/app/pages/workshop/w/[wsId].vue +704 -704
- package/app/plugins/locale-restore.client.ts +16 -1
- package/app/utils/builtin-catalog.ts +104 -0
- package/config.yml +170 -170
- package/docs/cli.en.md +2 -2
- package/docs/cli.md +2 -2
- package/docs/full-test-plan.md +2 -3
- package/docs/plugins.en.md +33 -1
- package/docs/plugins.md +29 -1
- package/docs/sdk.en.md +1 -1
- package/docs/sdk.md +1 -1
- package/i18n/locales/en.ts +293 -35
- package/i18n/locales/zh-CN.ts +265 -7
- package/package.json +4 -3
- package/scripts/_dbg-perms-e2e.mjs +132 -109
- package/scripts/_demo-agentteam-capture.mjs +278 -0
- package/scripts/_e2e-ui-functional.mjs +371 -0
- package/scripts/_i18n-en-cjk-scan.mjs +28 -0
- package/scripts/_i18n-en-verify.mjs +107 -0
- package/scripts/_i18n-extra-checks.mjs +53 -0
- package/scripts/_i18n-scan-hardcoded.mjs +180 -0
- package/scripts/_i18n-verify-keys.mjs +50 -0
- package/scripts/_loop-biax-campaign.mjs +130 -0
- package/scripts/_sweep-button-contrast.mjs +87 -0
- package/scripts/test-serial-bridge.mjs +418 -0
- package/scripts/ui/e2e-interactive.mjs +328 -0
- package/scripts/ui/record-demo.mjs +557 -0
- package/scripts/ui/seed-industrial-demo.mjs +171 -0
- package/server/api/workshop/daq/index.get.ts +3 -1
- package/server/api/workshop/dcw/index.get.ts +3 -0
- package/server/plugins/aw-plugins.ts +9 -7
- package/server/plugins-builtin/diag-bridge/index.mjs +608 -661
- package/server/plugins-builtin/rag-bridge/index.mjs +57 -133
- package/server/plugins-builtin/serial-bridge/client.mjs +210 -0
- package/server/plugins-builtin/serial-bridge/drivers.mjs +409 -0
- package/server/plugins-builtin/serial-bridge/i18n.json +62 -0
- package/server/plugins-builtin/serial-bridge/index.mjs +247 -0
- package/server/plugins-builtin/serial-bridge/lib.mjs +208 -0
- package/server/plugins-builtin/serial-bridge/rtu.mjs +168 -0
- package/server/services/workshop/daq/daq-controller.ts +7 -2
- package/server/services/workshop/daq/drivers.ts +89 -3
- package/server/services/workshop/dcw/dcw-controller.ts +5 -1
- package/server/services/workshop/dcw/drivers.ts +85 -2
- package/server/services/workshop/dcw/plugin-bridge.ts +45 -0
- package/server/services/workshop/plugins/host.mjs +30 -0
- package/shared/daq-protocol.ts +15 -0
- package/.output/public/_nuxt/88GOowgO2.js +0 -1
- package/.output/public/_nuxt/BOt2SP4z.js +0 -1
- package/.output/public/_nuxt/BP-NyKVG.js +0 -18
- package/.output/public/_nuxt/BXxH0UQE.js +0 -1
- package/.output/public/_nuxt/BsROmY89.js +0 -1
- package/.output/public/_nuxt/ClILjvWH.js +0 -1
- package/.output/public/_nuxt/CzOCSGLe.js +0 -1
- package/.output/public/_nuxt/D8qvkfyY.js +0 -1
- package/.output/public/_nuxt/DCn7QZ5N.js +0 -2
- package/.output/public/_nuxt/DS8qj8EL.js +0 -1
- package/.output/public/_nuxt/DUjW5Yq5.js +0 -1
- package/.output/public/_nuxt/Dihnimlk.js +0 -19
- package/.output/public/_nuxt/Dqw1lpr-.js +0 -1
- package/.output/public/_nuxt/Dr9v6C0f.js +0 -14
- package/.output/public/_nuxt/Du9ZJtnj.js +0 -1
- package/.output/public/_nuxt/OmpTerminalPanel.B_Pl5Ynm.css +0 -1
- package/.output/public/_nuxt/TownView.f-YOdXRs.css +0 -1
- package/.output/public/_nuxt/_id_.CM4bIgKY.css +0 -1
- package/.output/public/_nuxt/_id_.bNV69Dtf.css +0 -1
- package/.output/public/_nuxt/agents.ZD0rVdHE.css +0 -1
- package/.output/public/_nuxt/aml.BN8ZEW4h.css +0 -1
- package/.output/public/_nuxt/builds/meta/f4707884-7cf4-4a31-80d2-e3d72b73745f.json +0 -1
- package/.output/public/_nuxt/channel-templates.BOtahzPi.css +0 -1
- package/.output/public/_nuxt/daq.B0D3dPAc.css +0 -1
- package/.output/public/_nuxt/dcw.8Y7FqCVN.css +0 -1
- package/.output/public/_nuxt/pages.D7aSDlUI.css +0 -1
- package/.output/public/_nuxt/plugins.sb4nDGqr.css +0 -1
- package/.output/public/_nuxt/qr6R6Ha1.js +0 -1
- package/.output/public/_nuxt/settings.BTgR-NiE.css +0 -1
- package/.output/public/_nuxt/teams.CxnxKVVs.css +0 -1
- package/.output/public/_nuxt/tokens.BFY7qv4f.css +0 -1
- package/.output/public/_nuxt/workshop.CR_QzeAN.css +0 -1
- package/.output/server/chunks/build/_id_-BU1wfOID.mjs.map +0 -1
- package/.output/server/chunks/build/_id_-BYsEua57.mjs.map +0 -1
- package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +0 -1
- package/.output/server/chunks/build/aml-_7FdFL2p.mjs.map +0 -1
- package/.output/server/chunks/build/channel-templates-p6SZNjlM.mjs.map +0 -1
- package/.output/server/chunks/build/daq-cgyjKmJu.mjs.map +0 -1
- package/.output/server/chunks/build/dcw-CoTxBlAZ.mjs.map +0 -1
- package/.output/server/chunks/build/pages-DFxjn9Xa.mjs.map +0 -1
- package/.output/server/chunks/build/plugins-lGlUr1lj.mjs.map +0 -1
- package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +0 -1
- package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +0 -1
- package/.output/server/chunks/build/tokens-C7fTM5C2.mjs.map +0 -1
- package/.output/server/chunks/build/town-qMPql9Se.mjs.map +0 -1
- package/.output/server/chunks/build/useDaqStream-CIBaiGAl.mjs.map +0 -1
- package/.output/server/chunks/build/useDcwStream-CBbiYnWe.mjs.map +0 -1
- package/.output/server/chunks/build/workshop-BL5bhfs0.mjs.map +0 -1
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +0 -1
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +0 -1
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +0 -153
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +0 -153
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +0 -431
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +0 -180
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +0 -112
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +0 -227
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +0 -131
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +0 -160
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +0 -157
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +0 -348
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +0 -296
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +0 -285
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +0 -217
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +0 -301
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +0 -151
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +0 -135
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +0 -156
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +0 -72
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +0 -469
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +0 -259
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +0 -97
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +0 -277
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +0 -171
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +0 -425
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +0 -99
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +0 -325
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +0 -332
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +0 -43
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +0 -154
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +0 -531
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +0 -439
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +0 -323
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +0 -433
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +0 -280
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +0 -117
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +0 -160
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +0 -101
- package/scripts/.mimosa/hook-status/sess_aeaecb58-8833-4b83-b93c-050467d21e03-d944d71bb3.json +0 -14
- package/scripts/_dbg-audit-all-pages.mjs +0 -43
- package/scripts/_dbg-daq-frames-e2e.mjs +0 -118
- package/scripts/_dbg-daq-rowstyle-verify.mjs +0 -112
- package/scripts/_dbg-hitl-timeout-e2e.mjs +0 -93
- package/scripts/_dbg-live-line-e2e.mjs +0 -279
- package/scripts/_dbg-minimal-omp.mjs +0 -77
- package/scripts/_dbg-mode-nav.mjs +0 -173
- package/scripts/_dbg-real-scenario-timeline.log +0 -55
- package/scripts/_dbg-team-repro.mjs +0 -76
- package/scripts/_dbg-town-agents-probe.mjs +0 -60
- package/scripts/_dbg-visual-acceptance.mjs +0 -63
- package/scripts/_townview_style.css +0 -1185
- package/scripts/_townview_template.html +0 -1215
- package/server/data/agent-node-bindings.json +0 -554
- package/server/data/daq-templates.json +0 -16
- package/server/data/daqs.json +0 -7464
- package/server/data/dcw-lines.json +0 -1787
- package/server/data/dcw-products.json +0 -1626
- package/server/data/dcw-recipes.json +0 -4672
- package/server/data/dcw-rollback.json +0 -20754
- package/server/data/dcw-runs.json +0 -6354
- package/server/data/dcw-templates.json +0 -146
- package/server/data/dcw-writes.json +0 -15650
- package/server/data/dcws.json +0 -9033
- package/server/data/device-twins.json +0 -133
- package/server/data/line-runs.json +0 -322
- package/server/data/scene-layouts.json +0 -277
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentWorkShop 实机演示录屏工具 v4.1 —— 英文界面 + Edge 神经语音 + 章节化合成。
|
|
3
|
+
*
|
|
4
|
+
* 产出:docs/site/public/demo/agentworkshop-demo.mp4(1600×900@30,含配音)
|
|
5
|
+
* docs/site/public/demo/poster.jpg
|
|
6
|
+
*
|
|
7
|
+
* 架构:录制期间 Node 零 Runtime.evaluate(场景脚本整体下推页面内单函数自跑,
|
|
8
|
+
* Node 只负责 screencast 启停、定长 sleep 与孪生鼠标运镜)。场景级重试:
|
|
9
|
+
* 帧不足(渲染进程被重页面拖挂等)自动 reload 后重录一次。
|
|
10
|
+
*
|
|
11
|
+
* 用法:
|
|
12
|
+
* node scripts/ui/record-demo.mjs tts # 生成英文配音(edge-tts,增量)
|
|
13
|
+
* node scripts/ui/record-demo.mjs record # 录制 + 配音对齐 + 合成(含 poster)
|
|
14
|
+
*/
|
|
15
|
+
import fs from 'node:fs'
|
|
16
|
+
import path from 'node:path'
|
|
17
|
+
import { execSync } from 'node:child_process'
|
|
18
|
+
import puppeteer from 'puppeteer-core'
|
|
19
|
+
|
|
20
|
+
const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
|
|
21
|
+
const CHROME = process.env.AW_CHROME ?? 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
|
|
22
|
+
const OUT_DIR = path.resolve('docs/site/public/demo')
|
|
23
|
+
const WORK = path.resolve('.tmp-demo')
|
|
24
|
+
const EMAIL = 'admin@awshop.local'
|
|
25
|
+
const PASSWORD = 'admin123'
|
|
26
|
+
const VOICE = 'en-US-AndrewNeural'
|
|
27
|
+
const RATE = '-4%'
|
|
28
|
+
const HEAD = 1.6
|
|
29
|
+
|
|
30
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
31
|
+
const sh = cmd => execSync(cmd, { stdio: ['ignore', 'ignore', 'pipe'] })
|
|
32
|
+
const probeDur = f => Number(execSync(`ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "${f}"`).toString().trim())
|
|
33
|
+
|
|
34
|
+
// ════ 解说词与场景规划(单一事实源) ═════════════════════════════════════════
|
|
35
|
+
const PLAN = [
|
|
36
|
+
{
|
|
37
|
+
id: 'title', chapter: null, url: 'card',
|
|
38
|
+
clips: [
|
|
39
|
+
{ cap: 'AgentWorkShop', sub: 'INDUSTRIAL AGENT RUNTIME · RECORDED LIVE', say: 'Agent Work Shop — an industrial agent runtime, recorded live. Real protocols, real control — nothing in this demo is scripted.' },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'platform', chapter: '01|THE CONTROL ROOM', url: '/',
|
|
44
|
+
clips: [
|
|
45
|
+
{ cap: 'The control room', sub: 'Every line, control node and sample in one view — live', say: 'The control room. Every production line, control node, and sample in one view — live.' },
|
|
46
|
+
{ cap: '14 engines · 4 entry points', sub: 'WebSocket · MCP · A2A · REST', say: 'Fourteen agent engines behind one contract, and four entry points: WebSocket, M C P, A to A, and REST.' },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'telemetry', chapter: '02|LIVE TELEMETRY', url: '/daq',
|
|
51
|
+
clips: [
|
|
52
|
+
{ cap: 'DAQ console — live telemetry', sub: '2,000+ channels across six field protocols', say: 'The data acquisition console. Over two thousand live channels across six field protocols: Modbus T C P and R T U, O P C U A, M Q T T, H T T P, and serial.' },
|
|
53
|
+
{ cap: 'driver → queue → storage → WebSocket', sub: 'Every node keeps its own cadence', say: 'Samples flow from driver, through the queue, into time-series storage, then fan out over WebSockets. Every node keeps its own cadence.' },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'control', chapter: '03|GOVERNED CONTROL', url: '/dcw/ln-bcc8ea44',
|
|
58
|
+
clips: [
|
|
59
|
+
{ cap: 'Writing to a real PLC', sub: 'Recipe interlock → safety limits → field', say: 'Now writing to a real PLC. Every setpoint passes the recipe interlock and the safety limits before anything reaches the field.' },
|
|
60
|
+
{ cap: 'SET 150 °C → Modbus → read-back', sub: 'Watch the write land, then read the register straight back', say: 'Watch the write land: the platform drives the Modbus holding registers, then reads the value straight back from the device.' },
|
|
61
|
+
{ cap: 'Read-back agrees — audit signed', sub: 'Who · when · why — every entry signed', say: 'One hundred and fifty degrees, confirmed by read-back, with a signed entry in the audit trail: who, when, and why.' },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'plugins', chapter: '04|PROTOCOLS ARE PLUGINS', url: '/plugins',
|
|
66
|
+
clips: [
|
|
67
|
+
{ cap: 'Protocols are plugins', sub: 'ctx.daq.registerDriver · ctx.dcw.registerWriteDriver', say: 'Protocols are plugins. A driver registered through the SDK appears in the interface with its own configuration form — zero front-end changes.' },
|
|
68
|
+
{ cap: 'serial-bridge — direct RS-232/485', sub: 'Modbus RTU + ASCII line, probe API included', say: 'The built-in serial bridge opens a real COM port and probes it. If nothing answers, it says so — honestly.' },
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'agents', chapter: '05|AGENT TEAMS', url: 'workshop',
|
|
73
|
+
clips: [
|
|
74
|
+
{ cap: 'The melt-optimization crew', sub: 'A lead agent · trend analyst · setpoint writer', say: 'This is the agent workshop, and this crew owns melt temperature optimization: a lead agent, a trend analyst, and a setpoint writer, supervised over A to A.' },
|
|
75
|
+
{ cap: 'Dispatch a goal', sub: 'Lead decomposes → workers execute with tools', say: 'Dispatch a goal. The lead decomposes it, workers stream progress as they execute with tools, and every message lands on the audit trail.' },
|
|
76
|
+
{ cap: 'Execution, live', sub: '7-state task machine · artifacts · real-time events', say: 'Task state, artifacts, and messages update in real time. The same seven-state machine backs every engine — from the scripted mock to a real L L M.' },
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'loop', chapter: '06|THE CLOSED LOOP', url: 'pvnode',
|
|
81
|
+
clips: [
|
|
82
|
+
{ cap: 'The closed loop', sub: 'Analyze → propose → approve → write → verify', say: 'The closed loop, end to end. Analyze the trend, propose a setpoint, take the human approval, write to the PLC, and verify — the process value follows.' },
|
|
83
|
+
{ cap: 'PV tracks the setpoint', sub: 'SET and ACT — one calibration, both directions', say: 'The process value tracks the setpoint through the real control chain. The same calibration reads back what was written: S E T and A C T, side by side.' },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'twin', chapter: '07|THE DIGITAL TWIN', url: '/town',
|
|
88
|
+
clips: [
|
|
89
|
+
{ cap: 'The digital twin', sub: 'Live tags · device health · alarms — one event bus', say: 'And everything streams into a three-D digital twin. Live tags, device health, and alarms — all rendered from the same event bus.' },
|
|
90
|
+
{ cap: '2,000+ channels · one scene', sub: 'Nothing mocked', say: 'Two thousand channels, one scene — nothing mocked.' },
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'outro', chapter: null, url: '/',
|
|
95
|
+
clips: [
|
|
96
|
+
{ cap: 'AgentWorkShop v0.7.41', sub: 'Open source · PolyForm NC · github.com/kingdol666/AgentWorkShop', say: 'Agent Work Shop, version zero point seven forty one. Open source under PolyForm N C. Find it on Git Hub — king dol sixty six, slash, Agent Work Shop.' },
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
// ════ 阶段 1:TTS(edge-tts 神经语音,增量) ═════════════════════════════════
|
|
102
|
+
async function ttsPhase() {
|
|
103
|
+
const narrDir = path.join(WORK, 'narr')
|
|
104
|
+
fs.mkdirSync(narrDir, { recursive: true })
|
|
105
|
+
for (const scene of PLAN) {
|
|
106
|
+
for (let k = 0; k < scene.clips.length; k++) {
|
|
107
|
+
const id = `${scene.id}_${k}`
|
|
108
|
+
const mp3 = path.join(narrDir, `${id}.mp3`)
|
|
109
|
+
const wav = path.join(narrDir, `${id}.wav`)
|
|
110
|
+
if (fs.existsSync(wav)) continue
|
|
111
|
+
const text = scene.clips[k].say.replace(/"/g, '\\"')
|
|
112
|
+
sh(`edge-tts --voice ${VOICE} --rate=${RATE} --text "${text}" --write-media "${mp3}"`)
|
|
113
|
+
sh(`ffmpeg -y -v error -i "${mp3}" -af "loudnorm=I=-16:TP=-1.5" -ar 44100 -ac 2 "${wav}"`)
|
|
114
|
+
console.log(` tts ${id}`)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const durations = {}
|
|
118
|
+
for (const scene of PLAN) {
|
|
119
|
+
for (let k = 0; k < scene.clips.length; k++) {
|
|
120
|
+
durations[`${scene.id}_${k}`] = probeDur(path.join(narrDir, `${scene.id}_${k}.wav`))
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
fs.writeFileSync(path.join(narrDir, 'durations.json'), JSON.stringify(durations, null, 2))
|
|
124
|
+
console.log('✅ TTS 完成:', JSON.stringify(durations))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ════ 页面内叠加层(序列化注入) ══════════════════════════════════════════════
|
|
128
|
+
const OVERLAY_FN = () => {
|
|
129
|
+
if (window.__demoInstalled) {
|
|
130
|
+
window.__demoRebuild()
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
window.__demoInstalled = true
|
|
134
|
+
const css = document.createElement('style')
|
|
135
|
+
css.textContent = `
|
|
136
|
+
#aw-demo-cap { position: fixed; left: 50%; bottom: 42px; transform: translateX(-50%);
|
|
137
|
+
max-width: 82vw; z-index: 2147483000; pointer-events: none; text-align: center;
|
|
138
|
+
background: rgba(7,11,19,.84); border: 1px solid rgba(65,200,244,.35);
|
|
139
|
+
border-radius: 12px; padding: 14px 26px;
|
|
140
|
+
font: 600 21px/1.45 'Segoe UI', system-ui, sans-serif; color: #eaf6ff;
|
|
141
|
+
box-shadow: 0 8px 32px rgba(0,0,0,.45); backdrop-filter: blur(6px);
|
|
142
|
+
transition: opacity .45s ease; opacity: 0; }
|
|
143
|
+
#aw-demo-cap .sub { display: block; margin-top: 4px; font: 400 15px/1.4 'Segoe UI', system-ui, sans-serif;
|
|
144
|
+
color: #7dd8c8; letter-spacing: .4px; }
|
|
145
|
+
#aw-demo-cap .tag { position: absolute; top: -11px; left: 18px; background: #35e0a0; color: #06251a;
|
|
146
|
+
font: 700 11px/1 'Segoe UI', sans-serif; letter-spacing: 1.5px; padding: 4px 9px; border-radius: 6px; }
|
|
147
|
+
#aw-demo-veil { position: fixed; inset: 0; background: #000; z-index: 2147483001;
|
|
148
|
+
pointer-events: none; opacity: 0; transition: opacity .4s ease; }
|
|
149
|
+
#aw-demo-card { position: fixed; inset: 0; background: #05080f; z-index: 2147482999;
|
|
150
|
+
display: none; align-items: center; justify-content: center; flex-direction: column; gap: 14px; }
|
|
151
|
+
#aw-demo-card .no { font: 700 15px/1 'Segoe UI', sans-serif; letter-spacing: 4px; color: #35e0a0; }
|
|
152
|
+
#aw-demo-card .tt { font: 600 54px/1.2 Georgia, 'Times New Roman', serif; color: #eaf2ff; }
|
|
153
|
+
#aw-demo-card .rule { width: 120px; height: 2px; background: #2c4568; animation: awpulse 1.4s ease-in-out infinite; }
|
|
154
|
+
@keyframes awpulse { 0%, 100% { width: 120px; opacity: .55 } 50% { width: 168px; opacity: 1 } }
|
|
155
|
+
#aw-demo-steps { position: fixed; left: 50%; top: 34px; transform: translateX(-50%);
|
|
156
|
+
display: none; gap: 10px; z-index: 2147483000; pointer-events: none; }
|
|
157
|
+
#aw-demo-steps span { font: 600 13px/1 'Segoe UI', sans-serif; letter-spacing: 1px;
|
|
158
|
+
padding: 8px 14px; border-radius: 8px; color: #8fa0b5;
|
|
159
|
+
background: rgba(7,11,19,.8); border: 1px solid rgba(45,69,104,.6); transition: all .3s ease; }
|
|
160
|
+
#aw-demo-steps span.on { color: #06251a; background: #35e0a0; border-color: #35e0a0; }
|
|
161
|
+
#aw-demo-beat { position: fixed; width: 2px; height: 2px; bottom: 0; left: 0;
|
|
162
|
+
opacity: 0; animation: awbeat .5s steps(1) infinite; }
|
|
163
|
+
@keyframes awbeat { 0% { opacity: 0 } 50% { opacity: .02 } 100% { opacity: 0 } }
|
|
164
|
+
`
|
|
165
|
+
document.head.append(css)
|
|
166
|
+
for (const id of ['aw-demo-cap', 'aw-demo-veil', 'aw-demo-card', 'aw-demo-steps', 'aw-demo-beat']) {
|
|
167
|
+
const el = document.createElement('div')
|
|
168
|
+
el.id = id
|
|
169
|
+
document.body.append(el)
|
|
170
|
+
}
|
|
171
|
+
window.__demoRebuild = () => {
|
|
172
|
+
for (const id of ['aw-demo-cap', 'aw-demo-veil', 'aw-demo-card', 'aw-demo-steps', 'aw-demo-beat']) {
|
|
173
|
+
let el = document.getElementById(id)
|
|
174
|
+
if (!el || !el.isConnected) {
|
|
175
|
+
el?.remove()
|
|
176
|
+
el = document.createElement('div')
|
|
177
|
+
el.id = id
|
|
178
|
+
document.body.append(el)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
window.__demoCap = (main, sub) => {
|
|
183
|
+
window.__demoRebuild()
|
|
184
|
+
const c = document.getElementById('aw-demo-cap')
|
|
185
|
+
if (!main) {
|
|
186
|
+
c.style.opacity = 0
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
c.innerHTML = `<span class="tag">AGENTWORKSHOP</span>${main}${sub ? `<span class="sub">${sub}</span>` : ''}`
|
|
190
|
+
c.style.opacity = 1
|
|
191
|
+
}
|
|
192
|
+
window.__demoVeil = async (on) => {
|
|
193
|
+
window.__demoRebuild()
|
|
194
|
+
document.getElementById('aw-demo-veil').style.opacity = on ? 1 : 0
|
|
195
|
+
await new Promise(r => setTimeout(r, 430))
|
|
196
|
+
}
|
|
197
|
+
window.__demoCard = (no) => {
|
|
198
|
+
window.__demoRebuild()
|
|
199
|
+
const c = document.getElementById('aw-demo-card')
|
|
200
|
+
if (!no) {
|
|
201
|
+
c.style.display = 'none'
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
const [noTxt, ...rest] = String(no).split('|')
|
|
205
|
+
c.innerHTML = `<span class="no">${noTxt}</span><span class="tt">${rest.join('|')}</span><span class="rule"></span>`
|
|
206
|
+
c.style.display = 'flex'
|
|
207
|
+
}
|
|
208
|
+
window.__demoSteps = (names, active) => {
|
|
209
|
+
window.__demoRebuild()
|
|
210
|
+
const s = document.getElementById('aw-demo-steps')
|
|
211
|
+
if (!names) {
|
|
212
|
+
s.style.display = 'none'
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
s.style.display = 'flex'
|
|
216
|
+
s.innerHTML = names.map((n, i) => `<span class="${i <= active ? 'on' : ''}">${n}</span>`).join('')
|
|
217
|
+
}
|
|
218
|
+
window.__demoScroll = async (toY, ms = 1000) => {
|
|
219
|
+
const from = window.scrollY
|
|
220
|
+
const t0 = performance.now()
|
|
221
|
+
await new Promise((done) => {
|
|
222
|
+
const step = (t) => {
|
|
223
|
+
const k = Math.min(1, (t - t0) / ms)
|
|
224
|
+
const e = k < 0.5 ? 2 * k * k : 1 - (-2 * k + 2) ** 2 / 2
|
|
225
|
+
window.scrollTo(0, from + (toY - from) * e)
|
|
226
|
+
if (k < 1) requestAnimationFrame(step)
|
|
227
|
+
else done()
|
|
228
|
+
}
|
|
229
|
+
requestAnimationFrame(step)
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 场景运行器(页面内自跑;录制期间 Node 零 evaluate)。返回实际耗时(秒)。
|
|
236
|
+
*/
|
|
237
|
+
const SCENE_RUNNER = async ({ clips, chapter, isTitle, actions, durs }) => {
|
|
238
|
+
const t0 = Date.now()
|
|
239
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
240
|
+
const cap = (m, s2) => window.__demoCap(m, s2)
|
|
241
|
+
const qa = sel => [...document.querySelectorAll(sel)]
|
|
242
|
+
const setVal = (inp, v) => {
|
|
243
|
+
const set = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set
|
|
244
|
+
set.call(inp, v)
|
|
245
|
+
inp.dispatchEvent(new Event('input', { bubbles: true }))
|
|
246
|
+
inp.dispatchEvent(new Event('change', { bubbles: true }))
|
|
247
|
+
}
|
|
248
|
+
await window.__demoVeil(false)
|
|
249
|
+
if (chapter) {
|
|
250
|
+
window.__demoCard(chapter)
|
|
251
|
+
await sleep(1700)
|
|
252
|
+
window.__demoCard(null)
|
|
253
|
+
await sleep(350)
|
|
254
|
+
}
|
|
255
|
+
if (isTitle) {
|
|
256
|
+
window.__demoCard('★|AgentWorkShop')
|
|
257
|
+
}
|
|
258
|
+
for (let k = 0; k < clips.length; k++) {
|
|
259
|
+
cap(clips[k].cap, clips[k].sub)
|
|
260
|
+
const act = actions[k]
|
|
261
|
+
if (act === 'scrollcard') {
|
|
262
|
+
await window.__demoScroll(document.body.scrollHeight, 1400)
|
|
263
|
+
await sleep(800)
|
|
264
|
+
}
|
|
265
|
+
if (act === 'scroll430') {
|
|
266
|
+
await window.__demoScroll(430, 1100)
|
|
267
|
+
await sleep(900)
|
|
268
|
+
}
|
|
269
|
+
if (act === 'scroll320') {
|
|
270
|
+
await window.__demoScroll(320, 1200)
|
|
271
|
+
await sleep(1100)
|
|
272
|
+
}
|
|
273
|
+
if (act === 'pause3400') await sleep(3400)
|
|
274
|
+
if (act === 'write150') {
|
|
275
|
+
const inp = qa('input[type=number]').find(i => /^\d+~\d+$/.test((i.placeholder || '').replace(/\s/g, '')))
|
|
276
|
+
if (inp) {
|
|
277
|
+
inp.closest('tr, [class*="row"]')?.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
|
278
|
+
await sleep(700)
|
|
279
|
+
setVal(inp, '150')
|
|
280
|
+
await sleep(250)
|
|
281
|
+
qa('button').find(b => b.textContent.trim() === '下发' || b.textContent.trim() === 'Dispatch')?.click()
|
|
282
|
+
}
|
|
283
|
+
await sleep(5200)
|
|
284
|
+
qa('button').find(b => ['读取', 'Read'].includes(b.textContent.trim()))?.click()
|
|
285
|
+
await sleep(3200)
|
|
286
|
+
}
|
|
287
|
+
if (act === 'history') {
|
|
288
|
+
await window.__demoScroll(document.body.scrollHeight, 1400)
|
|
289
|
+
await sleep(1500)
|
|
290
|
+
}
|
|
291
|
+
if (act === 'probe') {
|
|
292
|
+
const form = document.querySelector('.aw-serial-form')
|
|
293
|
+
if (form) {
|
|
294
|
+
const inputs = [...form.querySelectorAll('input')]
|
|
295
|
+
const nums = inputs.filter(i => i.type === 'number')
|
|
296
|
+
setVal(inputs[0], 'COM1')
|
|
297
|
+
if (nums[0]) setVal(nums[0], '9600')
|
|
298
|
+
if (nums[1]) setVal(nums[1], '1')
|
|
299
|
+
if (nums[2]) setVal(nums[2], '40001')
|
|
300
|
+
}
|
|
301
|
+
await sleep(400)
|
|
302
|
+
qa('.aw-serial-btn').find(b => (b.textContent || '').includes('测试连接') || (b.textContent || '').toLowerCase().includes('test'))?.click()
|
|
303
|
+
await sleep(4200)
|
|
304
|
+
}
|
|
305
|
+
if (act === 'sendtask') {
|
|
306
|
+
const any = qa('textarea')[0]
|
|
307
|
+
if (any) {
|
|
308
|
+
const st = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set
|
|
309
|
+
st.call(any, 'Demo run: optimize the melt temperature setpoint')
|
|
310
|
+
any.dispatchEvent(new Event('input', { bubbles: true }))
|
|
311
|
+
any.focus()
|
|
312
|
+
}
|
|
313
|
+
await sleep(700)
|
|
314
|
+
qa('button').find(b => /发送|Send/.test(b.title ?? ''))?.click()
|
|
315
|
+
await sleep(8500)
|
|
316
|
+
}
|
|
317
|
+
if (act === 'stages') {
|
|
318
|
+
const names = ['ANALYZE', 'PROPOSE', 'APPROVE', 'WRITE', 'VERIFY']
|
|
319
|
+
window.__demoSteps(names, -1)
|
|
320
|
+
for (let i = 0; i < names.length; i++) {
|
|
321
|
+
window.__demoSteps(names, i)
|
|
322
|
+
await sleep(1550)
|
|
323
|
+
}
|
|
324
|
+
await sleep(400)
|
|
325
|
+
window.__demoSteps(null)
|
|
326
|
+
}
|
|
327
|
+
await sleep(Math.max(1200, ((durs[k] ?? 6) + 0.7) * 1000))
|
|
328
|
+
}
|
|
329
|
+
cap(null)
|
|
330
|
+
await window.__demoVeil(true)
|
|
331
|
+
await sleep(700)
|
|
332
|
+
return (Date.now() - t0) / 1000
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ════ 阶段 2:录制(场景级重试) ══════════════════════════════════════════════
|
|
336
|
+
async function recordPhase() {
|
|
337
|
+
const narrDir = path.join(WORK, 'narr')
|
|
338
|
+
const durations = JSON.parse(fs.readFileSync(path.join(narrDir, 'durations.json'), 'utf8'))
|
|
339
|
+
fs.rmSync(path.join(WORK, 'frames'), { recursive: true, force: true })
|
|
340
|
+
fs.mkdirSync(path.join(WORK, 'frames'), { recursive: true })
|
|
341
|
+
const browser = await puppeteer.launch({
|
|
342
|
+
executablePath: CHROME,
|
|
343
|
+
headless: false,
|
|
344
|
+
defaultViewport: { width: 1600, height: 900, deviceScaleFactor: 1 },
|
|
345
|
+
args: ['--no-first-run', '--no-default-browser-check', '--window-size=1620,960', '--use-gl=angle'],
|
|
346
|
+
protocolTimeout: 600000,
|
|
347
|
+
})
|
|
348
|
+
const page = await browser.newPage()
|
|
349
|
+
await page.evaluateOnNewDocument(() => {
|
|
350
|
+
try {
|
|
351
|
+
localStorage.setItem('aw.locale', 'en')
|
|
352
|
+
}
|
|
353
|
+
catch { /* about:blank */ }
|
|
354
|
+
})
|
|
355
|
+
await page.goto(BASE + '/', { waitUntil: 'domcontentloaded', timeout: 60000 })
|
|
356
|
+
await sleep(1500)
|
|
357
|
+
const token = await page.evaluate(async ({ email, password }) => {
|
|
358
|
+
const res = await fetch('/api/users/login', {
|
|
359
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
360
|
+
body: JSON.stringify({ email, password }),
|
|
361
|
+
})
|
|
362
|
+
const body = await res.json()
|
|
363
|
+
if (body?.code !== 0) return null
|
|
364
|
+
document.cookie = `token=${body.data.token}; path=/; max-age=86400`
|
|
365
|
+
return body.data.token
|
|
366
|
+
}, { email: EMAIL, password: PASSWORD })
|
|
367
|
+
if (!token) throw new Error('登录失败')
|
|
368
|
+
|
|
369
|
+
const sceneFiles = []
|
|
370
|
+
for (const scene of PLAN) {
|
|
371
|
+
let recorded = null
|
|
372
|
+
for (let attempt = 0; attempt < 2 && !recorded; attempt++) {
|
|
373
|
+
const durs = scene.clips.map((_, k) => durations[`${scene.id}_${k}`] ?? 6)
|
|
374
|
+
const narrTotal = durs.reduce((a, b) => a + b, 0) + 0.7 * scene.clips.length
|
|
375
|
+
console.log(`● 场景 ${scene.id}${attempt ? '(重试)' : ''}(解说 ${narrTotal.toFixed(1)}s)`)
|
|
376
|
+
|
|
377
|
+
let navTo = scene.url.startsWith('/') ? BASE + scene.url : null
|
|
378
|
+
if (scene.url === 'workshop') {
|
|
379
|
+
const wsId = await page.evaluate(async (t) => {
|
|
380
|
+
const r = await fetch('/api/workshop/workspaces', { headers: { authorization: `Bearer ${t}` } }).then(x => x.json())
|
|
381
|
+
return r?.data?.[0]?.id ?? null
|
|
382
|
+
}, token)
|
|
383
|
+
navTo = wsId ? `${BASE}/workshop/w/${wsId}` : `${BASE}/workshop`
|
|
384
|
+
}
|
|
385
|
+
if (scene.url === 'pvnode') {
|
|
386
|
+
const nid = await page.evaluate(async (t) => {
|
|
387
|
+
const r = await fetch('/api/workshop/daq', { headers: { authorization: `Bearer ${t}` } }).then(x => x.json())
|
|
388
|
+
const nodes = r.data?.nodes ?? []
|
|
389
|
+
return (nodes.find(n => n.name.includes('Temp PV')) ?? nodes[0])?.id ?? null
|
|
390
|
+
}, token)
|
|
391
|
+
navTo = nid ? `${BASE}/daq/${nid}` : `${BASE}/daq`
|
|
392
|
+
}
|
|
393
|
+
if (scene.url === 'card') {
|
|
394
|
+
await page.goto('about:blank').catch(() => {})
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
await page.goto(navTo, { waitUntil: 'domcontentloaded', timeout: 60000 }).catch(() => {})
|
|
398
|
+
}
|
|
399
|
+
await sleep(800)
|
|
400
|
+
await page.evaluate(OVERLAY_FN)
|
|
401
|
+
await sleep(2200)
|
|
402
|
+
|
|
403
|
+
const actions = {
|
|
404
|
+
telemetry: { 1: 'scroll430' },
|
|
405
|
+
control: { 1: 'write150', 2: 'history' },
|
|
406
|
+
plugins: { 1: 'probe' },
|
|
407
|
+
agents: { 1: 'sendtask' },
|
|
408
|
+
loop: { 1: 'stages' },
|
|
409
|
+
twin: { 0: 'pause3400' },
|
|
410
|
+
}
|
|
411
|
+
const payload = {
|
|
412
|
+
clips: scene.clips.map(c => ({ cap: c.cap, sub: c.sub })),
|
|
413
|
+
chapter: scene.chapter,
|
|
414
|
+
isTitle: scene.id === 'title',
|
|
415
|
+
actions: actions[scene.id] ?? {},
|
|
416
|
+
durs,
|
|
417
|
+
}
|
|
418
|
+
const attemptSuffix = attempt ? `-r${attempt}` : ''
|
|
419
|
+
const sceneDir = path.join(WORK, 'frames', `${scene.id}${attemptSuffix}`)
|
|
420
|
+
fs.mkdirSync(sceneDir, { recursive: true })
|
|
421
|
+
let frameNo = 0
|
|
422
|
+
let pending = 0
|
|
423
|
+
const stamps = []
|
|
424
|
+
const client = await page.createCDPSession()
|
|
425
|
+
client.on('Page.screencastFrame', async (ev) => {
|
|
426
|
+
const at = ev.metadata.timestamp ?? Date.now() / 1000
|
|
427
|
+
if (pending < 24) {
|
|
428
|
+
pending++
|
|
429
|
+
const f = path.join(sceneDir, `f${String(frameNo++).padStart(5, '0')}.jpg`)
|
|
430
|
+
stamps.push({ file: f, at })
|
|
431
|
+
fs.promises.writeFile(f, Buffer.from(ev.data, 'base64')).catch(() => {}).finally(() => pending--)
|
|
432
|
+
}
|
|
433
|
+
await client.send('Page.screencastFrameAck', { sessionId: ev.sessionId }).catch(() => {})
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
// 场景脚本下推(不等待;录制期间零 evaluate)。超时/异常 → 由 attempt 重试兜底。
|
|
437
|
+
const runnerPromise = page.evaluate(SCENE_RUNNER, payload).catch(err => console.warn(' runner:', String(err).slice(0, 70)))
|
|
438
|
+
await sleep(600)
|
|
439
|
+
await client.send('Page.startScreencast', { format: 'jpeg', quality: 74, maxWidth: 1600, maxHeight: 900, everyNthFrame: 3 })
|
|
440
|
+
|
|
441
|
+
if (scene.id === 'twin') {
|
|
442
|
+
await sleep(4200)
|
|
443
|
+
const orbit = async (dx, steps = 24) => {
|
|
444
|
+
const box = await page.evaluate(() => {
|
|
445
|
+
const c = document.querySelector('canvas')
|
|
446
|
+
const r = c?.getBoundingClientRect()
|
|
447
|
+
return r ? { x: r.x + r.width / 2, y: r.y + r.height / 2 } : null
|
|
448
|
+
})
|
|
449
|
+
if (!box) return
|
|
450
|
+
await page.mouse.move(box.x, box.y)
|
|
451
|
+
await page.mouse.down()
|
|
452
|
+
for (let i = 0; i < steps; i++) {
|
|
453
|
+
const k = (i + 1) / steps
|
|
454
|
+
const e = 1 - (1 - k) ** 2
|
|
455
|
+
await page.mouse.move(box.x + dx * e, box.y + Math.sin(k * Math.PI) * 30)
|
|
456
|
+
await sleep(46)
|
|
457
|
+
}
|
|
458
|
+
await page.mouse.up()
|
|
459
|
+
}
|
|
460
|
+
await orbit(300)
|
|
461
|
+
await sleep(600)
|
|
462
|
+
await orbit(-240)
|
|
463
|
+
await sleep(500)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
await runnerPromise.catch(() => {})
|
|
467
|
+
await sleep(600)
|
|
468
|
+
await client.send('Page.stopScreencast').catch(() => {})
|
|
469
|
+
await sleep(300)
|
|
470
|
+
console.log(` 帧 ${stamps.length}`)
|
|
471
|
+
|
|
472
|
+
if (stamps.length >= 8) {
|
|
473
|
+
const lines = []
|
|
474
|
+
for (let i = 0; i < stamps.length; i++) {
|
|
475
|
+
const next = stamps[i + 1]?.at
|
|
476
|
+
const d = next ? Math.max(0.02, Math.min(3, next - stamps[i].at)) : 0.12
|
|
477
|
+
lines.push(`file '${stamps[i].file.replace(/\\/g, '/')}'`)
|
|
478
|
+
lines.push(`duration ${d.toFixed(3)}`)
|
|
479
|
+
}
|
|
480
|
+
lines.push(`file '${stamps[stamps.length - 1].file.replace(/\\/g, '/')}'`)
|
|
481
|
+
const durFile = path.join(sceneDir, 'durations.txt')
|
|
482
|
+
fs.writeFileSync(durFile, lines.join('\n'))
|
|
483
|
+
const out = path.join(WORK, 'frames', `${scene.id}${attemptSuffix}.mp4`)
|
|
484
|
+
execSync(`ffmpeg -y -v error -f concat -safe 0 -i "${durFile}" -vf "fps=30,format=yuv420p" -c:v libx264 -preset medium -crf 24 "${out}"`)
|
|
485
|
+
recorded = { id: scene.id, video: out }
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
console.warn(` ⚠ ${scene.id} 帧不足(attempt=${attempt})`)
|
|
489
|
+
// 渲染器可能被拖挂:整页 reload 复位后再试
|
|
490
|
+
await page.reload({ waitUntil: 'domcontentloaded', timeout: 60000 }).catch(() => {})
|
|
491
|
+
await sleep(2500)
|
|
492
|
+
}
|
|
493
|
+
await client.detach().catch(() => {})
|
|
494
|
+
}
|
|
495
|
+
if (recorded) sceneFiles.push(recorded)
|
|
496
|
+
else console.warn(` ✖ ${scene.id} 两次尝试均失败,跳过该场景`)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ── 配音对齐 + 混流(clip 边缘 afade)+ 拼接 + 封面 ──
|
|
500
|
+
console.log('● 配音对齐与混流 …')
|
|
501
|
+
const finalOut = path.join(OUT_DIR, 'agentworkshop-demo.mp4')
|
|
502
|
+
const parts = []
|
|
503
|
+
for (const scene of PLAN) {
|
|
504
|
+
const sf = sceneFiles.find(x => x.id === scene.id)
|
|
505
|
+
if (!sf) continue
|
|
506
|
+
const vdur = probeDur(sf.video)
|
|
507
|
+
const inputs = []
|
|
508
|
+
const filters = []
|
|
509
|
+
const mixIns = []
|
|
510
|
+
let t = HEAD
|
|
511
|
+
scene.clips.forEach((c, k) => {
|
|
512
|
+
const d = durations[`${scene.id}_${k}`] ?? 0
|
|
513
|
+
const narr = path.join(narrDir, `${scene.id}_${k}.wav`)
|
|
514
|
+
if (fs.existsSync(narr)) {
|
|
515
|
+
inputs.push('-i', narr)
|
|
516
|
+
const ms = Math.round(t * 1000)
|
|
517
|
+
filters.push(`[${k}:a]adelay=${ms}|${ms},afade=t=in:d=0.025,afade=t=out:st=${Math.max(0, d - 0.06).toFixed(3)}:d=0.06[a${k}]`)
|
|
518
|
+
mixIns.push(`[a${k}]`)
|
|
519
|
+
}
|
|
520
|
+
t += d + 0.7
|
|
521
|
+
})
|
|
522
|
+
const sceneWav = path.join(WORK, 'frames', `${scene.id}.wav`)
|
|
523
|
+
if (mixIns.length) {
|
|
524
|
+
execSync(`ffmpeg -y -v error ${inputs.join(' ')} -filter_complex "${filters.join(';')};${mixIns.join('')}amix=inputs=${mixIns.length}:normalize=0,apad=whole_dur=${vdur.toFixed(3)}[out]" -map "[out]" -ar 44100 -ac 2 "${sceneWav}"`)
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
execSync(`ffmpeg -y -v error -f lavfi -i anullsrc=r=44100:cl=stereo -t ${vdur.toFixed(3)} "${sceneWav}"`)
|
|
528
|
+
}
|
|
529
|
+
const av = path.join(WORK, 'frames', `${scene.id}.av.mp4`)
|
|
530
|
+
execSync(`ffmpeg -y -v error -i "${sf.video}" -i "${sceneWav}" -c:v copy -c:a aac -b:a 160k -shortest "${av}"`)
|
|
531
|
+
parts.push(av)
|
|
532
|
+
}
|
|
533
|
+
const listFile = path.join(WORK, 'frames', 'concat.txt')
|
|
534
|
+
fs.writeFileSync(listFile, parts.map(p2 => `file '${p2.replace(/\\/g, '/')}'`).join('\n'))
|
|
535
|
+
execSync(`ffmpeg -y -v error -f concat -safe 0 -i "${listFile}" -c copy -movflags +faststart "${finalOut}"`)
|
|
536
|
+
const dur = probeDur(finalOut)
|
|
537
|
+
const size = fs.statSync(finalOut).size
|
|
538
|
+
console.log(`✅ 完成:${finalOut.replace(process.cwd(), '.')}(${(size / 1e6).toFixed(1)} MB,${dur.toFixed(1)}s)`)
|
|
539
|
+
|
|
540
|
+
const posterAt = Math.min(dur - 2, dur * 0.86)
|
|
541
|
+
execSync(`ffmpeg -y -v error -ss ${posterAt.toFixed(1)} -i "${finalOut}" -frames:v 1 -q:v 3 "${path.join(OUT_DIR, 'poster.jpg')}"`)
|
|
542
|
+
await browser.close()
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// ════ 入口 ══════════════════════════════════════════════════════════════════
|
|
546
|
+
const phase = process.argv[2] ?? 'record'
|
|
547
|
+
fs.mkdirSync(OUT_DIR, { recursive: true })
|
|
548
|
+
if (phase === 'tts') {
|
|
549
|
+
await ttsPhase()
|
|
550
|
+
}
|
|
551
|
+
else if (phase === 'record') {
|
|
552
|
+
await recordPhase()
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
console.error('用法: node scripts/ui/record-demo.mjs [tts|record]')
|
|
556
|
+
process.exit(1)
|
|
557
|
+
}
|