agentworkshop 0.8.3 → 0.8.4

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 (196) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/B4iJT5PX2.js +2 -0
  3. package/.output/public/_nuxt/BNUaS1jv.js +20 -0
  4. package/.output/public/_nuxt/BhJA-E372.js +1 -0
  5. package/.output/public/_nuxt/Bq8YzXzs2.js +1 -0
  6. package/.output/public/_nuxt/Cf7ER7e2.js +1 -0
  7. package/.output/public/_nuxt/{DrlnrdvY2.js → DPnjnDtg.js} +1 -1
  8. package/.output/public/_nuxt/DgTqKyYF2.js +1 -0
  9. package/.output/public/_nuxt/MmlCf-EZ.js +1 -0
  10. package/.output/public/_nuxt/builds/latest.json +1 -1
  11. package/.output/public/_nuxt/builds/meta/8a151630-3285-41b8-a1e5-063fee2002ac.json +1 -0
  12. package/.output/public/_nuxt/{entry.C7koBPom.css → entry.D6nBdXet.css} +1 -1
  13. package/.output/public/_nuxt/{ontology.JqJ6PjV1.css → ontology.D4P756Tc.css} +1 -1
  14. package/.output/public/_nuxt/pipeline.NFs5z5V-.css +1 -0
  15. package/.output/server/chunks/_/character-asset.repo.mjs +1 -1
  16. package/.output/server/chunks/_/chart-repo.mjs +1 -1
  17. package/.output/server/chunks/_/chart-repo.mjs.map +1 -1
  18. package/.output/server/chunks/_/eda-db.mjs +12 -0
  19. package/.output/server/chunks/_/eda-db.mjs.map +1 -1
  20. package/.output/server/chunks/_/host-bindings.mjs +1 -1
  21. package/.output/server/chunks/_/nitro.mjs +1977 -1951
  22. package/.output/server/chunks/_/scene-layout.repo.mjs +1 -1
  23. package/.output/server/chunks/_/template-repo.mjs +97 -0
  24. package/.output/server/chunks/_/template-repo.mjs.map +1 -0
  25. package/.output/server/chunks/build/{TownView-CUbzOT_M.mjs → TownView-CcUPLqF1.mjs} +3 -3
  26. package/.output/server/chunks/build/{TownView-CUbzOT_M.mjs.map → TownView-CcUPLqF1.mjs.map} +1 -1
  27. package/.output/server/chunks/build/{_id_-JaGx0yBE.mjs → _id_-AgACEyL6.mjs} +13 -13
  28. package/.output/server/chunks/build/{_id_-JaGx0yBE.mjs.map → _id_-AgACEyL6.mjs.map} +1 -1
  29. package/.output/server/chunks/build/{_id_-BHWZiRQZ.mjs → _id_-B3yCK2lD.mjs} +8 -8
  30. package/.output/server/chunks/build/{_id_-BHWZiRQZ.mjs.map → _id_-B3yCK2lD.mjs.map} +1 -1
  31. package/.output/server/chunks/build/{_wsId_-C4DKkEqv.mjs → _wsId_-kcDriA4i.mjs} +4 -4
  32. package/.output/server/chunks/build/{_wsId_-C4DKkEqv.mjs.map → _wsId_-kcDriA4i.mjs.map} +1 -1
  33. package/.output/server/chunks/build/{analysis-tNpFLtMt.mjs → analysis-CMsMTMCg.mjs} +3 -3
  34. package/.output/server/chunks/build/{analysis-tNpFLtMt.mjs.map → analysis-CMsMTMCg.mjs.map} +1 -1
  35. package/.output/server/chunks/build/{daq-DHsYtWC-.mjs → daq-DFQQOtcZ.mjs} +7 -7
  36. package/.output/server/chunks/build/{daq-DHsYtWC-.mjs.map → daq-DFQQOtcZ.mjs.map} +1 -1
  37. package/.output/server/chunks/build/{dcw-DnjOM6p1.mjs → dcw-8415S3Km.mjs} +7 -7
  38. package/.output/server/chunks/build/{dcw-DnjOM6p1.mjs.map → dcw-8415S3Km.mjs.map} +1 -1
  39. package/.output/server/chunks/build/{logs-C4vKFkHQ.mjs → logs-C-dWpJ80.mjs} +3 -3
  40. package/.output/server/chunks/build/{logs-C4vKFkHQ.mjs.map → logs-C-dWpJ80.mjs.map} +1 -1
  41. package/.output/server/chunks/build/{ontology-B_0Vm6j-.mjs → ontology-DoXAsa2y.mjs} +62 -62
  42. package/.output/server/chunks/build/ontology-DoXAsa2y.mjs.map +1 -0
  43. package/.output/server/chunks/build/{operations-D8D6-Jm-.mjs → operations-Ch6tZIKo.mjs} +3 -3
  44. package/.output/server/chunks/build/{operations-D8D6-Jm-.mjs.map → operations-Ch6tZIKo.mjs.map} +1 -1
  45. package/.output/server/chunks/build/{pages-Dac4icQC.mjs → pages-CQOfwKB4.mjs} +4 -4
  46. package/.output/server/chunks/build/{pages-Dac4icQC.mjs.map → pages-CQOfwKB4.mjs.map} +1 -1
  47. package/.output/server/chunks/build/pipeline-eo2RWN3f.mjs +226 -0
  48. package/.output/server/chunks/build/pipeline-eo2RWN3f.mjs.map +1 -0
  49. package/.output/server/chunks/build/{town-LkDwWnKA.mjs → town-DLFB4SZl.mjs} +4 -4
  50. package/.output/server/chunks/build/{town-LkDwWnKA.mjs.map → town-DLFB4SZl.mjs.map} +1 -1
  51. package/.output/server/chunks/build/{useDaqAddNode-CRorxgOY.mjs → useDaqAddNode-D_1EBuR3.mjs} +2 -2
  52. package/.output/server/chunks/build/{useDaqAddNode-CRorxgOY.mjs.map → useDaqAddNode-D_1EBuR3.mjs.map} +1 -1
  53. package/.output/server/chunks/build/{useDaqDetailBinding-Ck-YUW9J.mjs → useDaqDetailBinding-j05iSApd.mjs} +2 -2
  54. package/.output/server/chunks/build/{useDaqDetailBinding-Ck-YUW9J.mjs.map → useDaqDetailBinding-j05iSApd.mjs.map} +1 -1
  55. package/.output/server/chunks/build/{useDaqDetailFrames-CLMoreaz.mjs → useDaqDetailFrames-BKciVJ78.mjs} +2 -2
  56. package/.output/server/chunks/build/{useDaqDetailFrames-CLMoreaz.mjs.map → useDaqDetailFrames-BKciVJ78.mjs.map} +1 -1
  57. package/.output/server/chunks/build/{useDaqDetailHistory-BVNQXwC_.mjs → useDaqDetailHistory-DgemICkr.mjs} +2 -2
  58. package/.output/server/chunks/build/{useDaqDetailHistory-BVNQXwC_.mjs.map → useDaqDetailHistory-DgemICkr.mjs.map} +1 -1
  59. package/.output/server/chunks/build/{useDaqDetailNode-BC0e9OfX.mjs → useDaqDetailNode-Cn7_og6t.mjs} +3 -3
  60. package/.output/server/chunks/build/{useDaqDetailNode-BC0e9OfX.mjs.map → useDaqDetailNode-Cn7_og6t.mjs.map} +1 -1
  61. package/.output/server/chunks/build/{useDaqDetailParams-D173iwFg.mjs → useDaqDetailParams-DX_vjFyK.mjs} +2 -2
  62. package/.output/server/chunks/build/{useDaqDetailParams-D173iwFg.mjs.map → useDaqDetailParams-DX_vjFyK.mjs.map} +1 -1
  63. package/.output/server/chunks/build/{useDaqFilters-CC-o_21d.mjs → useDaqFilters-DMS4GN20.mjs} +3 -3
  64. package/.output/server/chunks/build/{useDaqFilters-CC-o_21d.mjs.map → useDaqFilters-DMS4GN20.mjs.map} +1 -1
  65. package/.output/server/chunks/build/{useDaqOptimizations-4vK2w1yn.mjs → useDaqOptimizations-OPbdy5YB.mjs} +2 -2
  66. package/.output/server/chunks/build/{useDaqOptimizations-4vK2w1yn.mjs.map → useDaqOptimizations-OPbdy5YB.mjs.map} +1 -1
  67. package/.output/server/chunks/build/{useDaqStream-BHKpSJFd.mjs → useDaqStream-CS1G6iro.mjs} +1 -1
  68. package/.output/server/chunks/build/{useDaqStream-BHKpSJFd.mjs.map → useDaqStream-CS1G6iro.mjs.map} +1 -1
  69. package/.output/server/chunks/build/{useDaqTemplates-CsXSc7yI.mjs → useDaqTemplates-DqhRYo88.mjs} +2 -2
  70. package/.output/server/chunks/build/{useDaqTemplates-CsXSc7yI.mjs.map → useDaqTemplates-DqhRYo88.mjs.map} +1 -1
  71. package/.output/server/chunks/build/{useDashboardData-DuKjuJTz.mjs → useDashboardData-CeaMjEA9.mjs} +3 -3
  72. package/.output/server/chunks/build/{useDashboardData-DuKjuJTz.mjs.map → useDashboardData-CeaMjEA9.mjs.map} +1 -1
  73. package/.output/server/chunks/build/{useDcwAddNode-C5DcxsHF.mjs → useDcwAddNode-Cytz_odQ.mjs} +2 -2
  74. package/.output/server/chunks/build/{useDcwAddNode-C5DcxsHF.mjs.map → useDcwAddNode-Cytz_odQ.mjs.map} +1 -1
  75. package/.output/server/chunks/build/{useDcwCards-0bAVo6uB.mjs → useDcwCards-BKCFqkRd.mjs} +2 -2
  76. package/.output/server/chunks/build/{useDcwCards-0bAVo6uB.mjs.map → useDcwCards-BKCFqkRd.mjs.map} +1 -1
  77. package/.output/server/chunks/build/{useDcwDetailScope-BNFOlt2v.mjs → useDcwDetailScope-CvdvcKAv.mjs} +3 -3
  78. package/.output/server/chunks/build/{useDcwDetailScope-BNFOlt2v.mjs.map → useDcwDetailScope-CvdvcKAv.mjs.map} +1 -1
  79. package/.output/server/chunks/build/{useDcwDetailTemplates-DSH-o0tE.mjs → useDcwDetailTemplates-CC2FcEge.mjs} +2 -2
  80. package/.output/server/chunks/build/{useDcwDetailTemplates-DSH-o0tE.mjs.map → useDcwDetailTemplates-CC2FcEge.mjs.map} +1 -1
  81. package/.output/server/chunks/build/{useDcwFilter-W61BFUcT.mjs → useDcwFilter-CVoiXbZh.mjs} +2 -2
  82. package/.output/server/chunks/build/{useDcwFilter-W61BFUcT.mjs.map → useDcwFilter-CVoiXbZh.mjs.map} +1 -1
  83. package/.output/server/chunks/build/{useDcwLedger-DsColLdc.mjs → useDcwLedger-Cd-ckHoJ.mjs} +2 -2
  84. package/.output/server/chunks/build/{useDcwLedger-DsColLdc.mjs.map → useDcwLedger-Cd-ckHoJ.mjs.map} +1 -1
  85. package/.output/server/chunks/build/{useDcwLineMutations-Cx4Ir7g3.mjs → useDcwLineMutations-BOQs-fw3.mjs} +2 -2
  86. package/.output/server/chunks/build/{useDcwLineMutations-Cx4Ir7g3.mjs.map → useDcwLineMutations-BOQs-fw3.mjs.map} +1 -1
  87. package/.output/server/chunks/build/{useDcwLineRun-DQ0vB08z.mjs → useDcwLineRun-D9qvcyTM.mjs} +2 -2
  88. package/.output/server/chunks/build/{useDcwLineRun-DQ0vB08z.mjs.map → useDcwLineRun-D9qvcyTM.mjs.map} +1 -1
  89. package/.output/server/chunks/build/{useDcwParamApprovals-CYspRuDk.mjs → useDcwParamApprovals-D36q4can.mjs} +2 -2
  90. package/.output/server/chunks/build/{useDcwParamApprovals-CYspRuDk.mjs.map → useDcwParamApprovals-D36q4can.mjs.map} +1 -1
  91. package/.output/server/chunks/build/{useDcwProducts-CTbm_5S_.mjs → useDcwProducts-BozctrwT.mjs} +2 -2
  92. package/.output/server/chunks/build/{useDcwProducts-CTbm_5S_.mjs.map → useDcwProducts-BozctrwT.mjs.map} +1 -1
  93. package/.output/server/chunks/build/{useDcwQuery-E0yjZs7G.mjs → useDcwQuery-C8FikKgu.mjs} +2 -2
  94. package/.output/server/chunks/build/{useDcwQuery-E0yjZs7G.mjs.map → useDcwQuery-C8FikKgu.mjs.map} +1 -1
  95. package/.output/server/chunks/build/{useDcwQuickActions-BvT5VFOY.mjs → useDcwQuickActions-IyQ8Rkpe.mjs} +2 -2
  96. package/.output/server/chunks/build/{useDcwQuickActions-BvT5VFOY.mjs.map → useDcwQuickActions-IyQ8Rkpe.mjs.map} +1 -1
  97. package/.output/server/chunks/build/{useDcwRecipes-zngShyk0.mjs → useDcwRecipes-k9HGhMwF.mjs} +2 -2
  98. package/.output/server/chunks/build/{useDcwRecipes-zngShyk0.mjs.map → useDcwRecipes-k9HGhMwF.mjs.map} +1 -1
  99. package/.output/server/chunks/build/{useDcwStream-82IdjJKk.mjs → useDcwStream-BnBOiawo.mjs} +1 -1
  100. package/.output/server/chunks/build/{useDcwStream-82IdjJKk.mjs.map → useDcwStream-BnBOiawo.mjs.map} +1 -1
  101. package/.output/server/chunks/build/{useDcwTemplates-CpHrcnvJ.mjs → useDcwTemplates-CnyYOOy0.mjs} +2 -2
  102. package/.output/server/chunks/build/{useDcwTemplates-CpHrcnvJ.mjs.map → useDcwTemplates-CnyYOOy0.mjs.map} +1 -1
  103. package/.output/server/chunks/build/{useDcwWrites-BlOhljp9.mjs → useDcwWrites-Cco590iv.mjs} +2 -2
  104. package/.output/server/chunks/build/{useDcwWrites-BlOhljp9.mjs.map → useDcwWrites-Cco590iv.mjs.map} +1 -1
  105. package/.output/server/chunks/build/{useLogsScope-BRyy550Y.mjs → useLogsScope-C-yrta-v.mjs} +2 -2
  106. package/.output/server/chunks/build/{useLogsScope-BRyy550Y.mjs.map → useLogsScope-C-yrta-v.mjs.map} +1 -1
  107. package/.output/server/chunks/routes/api/workshop/assets/index.post.mjs +1 -1
  108. package/.output/server/chunks/routes/api/workshop/assets/index2.post.mjs +1 -1
  109. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  110. package/.output/server/chunks/routes/api/workshop/channels/_id/activate.post.mjs +1 -1
  111. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.post.mjs +1 -1
  112. package/.output/server/chunks/routes/api/workshop/channels/_id/index.post.mjs +1 -1
  113. package/.output/server/chunks/routes/api/workshop/channels/_id/index3.post.mjs +1 -1
  114. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.post.mjs +1 -1
  115. package/.output/server/chunks/routes/api/workshop/channels/_id/line.put.mjs +1 -1
  116. package/.output/server/chunks/routes/api/workshop/channels/index.get.mjs +1 -1
  117. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +1 -1
  118. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.delete.mjs +1 -1
  119. package/.output/server/chunks/routes/api/workshop/eda/index8.get.mjs +33 -0
  120. package/.output/server/chunks/routes/api/workshop/eda/index8.get.mjs.map +1 -0
  121. package/.output/server/chunks/routes/api/workshop/eda/index8.post.mjs +41 -0
  122. package/.output/server/chunks/routes/api/workshop/eda/index8.post.mjs.map +1 -0
  123. package/.output/server/chunks/routes/api/workshop/eda/templates/_id_.delete.mjs +36 -0
  124. package/.output/server/chunks/routes/api/workshop/eda/templates/_id_.delete.mjs.map +1 -0
  125. package/.output/server/chunks/routes/api/workshop/eda/templates/_id_.get.mjs +37 -0
  126. package/.output/server/chunks/routes/api/workshop/eda/templates/_id_.get.mjs.map +1 -0
  127. package/.output/server/chunks/routes/api/workshop/eda/templates/_id_.patch.mjs +42 -0
  128. package/.output/server/chunks/routes/api/workshop/eda/templates/_id_.patch.mjs.map +1 -0
  129. package/.output/server/chunks/routes/api/workshop/index10.post.mjs +33 -0
  130. package/.output/server/chunks/routes/api/workshop/index10.post.mjs.map +1 -0
  131. package/.output/server/chunks/routes/api/workshop/index2.post.mjs +45 -46
  132. package/.output/server/chunks/routes/api/workshop/index2.post.mjs.map +1 -1
  133. package/.output/server/chunks/routes/api/workshop/index3.post.mjs +46 -23
  134. package/.output/server/chunks/routes/api/workshop/index3.post.mjs.map +1 -1
  135. package/.output/server/chunks/routes/api/workshop/index4.post.mjs +10 -9
  136. package/.output/server/chunks/routes/api/workshop/index4.post.mjs.map +1 -1
  137. package/.output/server/chunks/routes/api/workshop/index5.post.mjs +22 -19
  138. package/.output/server/chunks/routes/api/workshop/index5.post.mjs.map +1 -1
  139. package/.output/server/chunks/routes/api/workshop/index6.post.mjs +18 -42
  140. package/.output/server/chunks/routes/api/workshop/index6.post.mjs.map +1 -1
  141. package/.output/server/chunks/routes/api/workshop/index7.post.mjs +43 -17
  142. package/.output/server/chunks/routes/api/workshop/index7.post.mjs.map +1 -1
  143. package/.output/server/chunks/routes/api/workshop/index8.post.mjs +13 -15
  144. package/.output/server/chunks/routes/api/workshop/index8.post.mjs.map +1 -1
  145. package/.output/server/chunks/routes/api/workshop/index9.post.mjs +18 -4
  146. package/.output/server/chunks/routes/api/workshop/index9.post.mjs.map +1 -1
  147. package/.output/server/chunks/routes/api/workshop/process-model/datasets.post.mjs +1 -1
  148. package/.output/server/chunks/routes/api/workshop/process-model/eligibility.mjs +1 -1
  149. package/.output/server/chunks/routes/api/workshop/process-model/experiments.mjs +1 -1
  150. package/.output/server/chunks/routes/api/workshop/teams/_id/deploy.post.mjs +1 -1
  151. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  152. package/.output/server/chunks/routes/renderer.mjs +1 -1
  153. package/.output/server/chunks/virtual/entry.mjs +140 -43
  154. package/.output/server/chunks/virtual/entry.mjs.map +1 -1
  155. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  156. package/.output/server/chunks/virtual/precomputed.mjs.map +1 -1
  157. package/.output/server/package.json +1 -1
  158. package/README-zh.md +8 -5
  159. package/README.md +8 -5
  160. package/app/components/AwChart.vue +14 -0
  161. package/app/pages/ontology/index.vue +1 -1
  162. package/app/pages/studio/pipeline.vue +812 -0
  163. package/app/utils/chart-pipeline.ts +193 -0
  164. package/config.yml +1 -1
  165. package/docs/cli.en.md +2 -2
  166. package/docs/cli.md +2 -2
  167. package/docs/plugins.en.md +1 -1
  168. package/docs/plugins.md +1 -1
  169. package/docs/sdk.en.md +1 -1
  170. package/docs/sdk.md +1 -1
  171. package/i18n/locales/en.ts +46 -0
  172. package/i18n/locales/zh-CN.ts +46 -0
  173. package/package.json +1 -1
  174. package/scripts/check-docs-sync.mjs +1 -1
  175. package/scripts/e2e-eda-pipeline.mjs +286 -0
  176. package/scripts/e2e-resume-crash.mjs +20 -6
  177. package/scripts/e2e-timezone-settings.mjs +4 -2
  178. package/server/api/workshop/analysis/index.post.ts +54 -0
  179. package/server/api/workshop/eda/templates/[id].delete.ts +14 -0
  180. package/server/api/workshop/eda/templates/[id].get.ts +15 -0
  181. package/server/api/workshop/eda/templates/[id].patch.ts +20 -0
  182. package/server/api/workshop/eda/templates/index.get.ts +11 -0
  183. package/server/api/workshop/eda/templates/index.post.ts +20 -0
  184. package/server/services/workshop/eda/chart-repo.ts +1 -1
  185. package/server/services/workshop/eda/eda-db.ts +13 -0
  186. package/server/services/workshop/eda/template-repo.ts +101 -0
  187. package/.output/public/_nuxt/Bbk1XCCM.js +0 -14
  188. package/.output/public/_nuxt/CMzExk8F.js +0 -1
  189. package/.output/public/_nuxt/D6FhDrYe2.js +0 -1
  190. package/.output/public/_nuxt/DF9ETYJ32.js +0 -1
  191. package/.output/public/_nuxt/DoChWkff.js +0 -1
  192. package/.output/public/_nuxt/YNhAoK-R2.js +0 -1
  193. package/.output/public/_nuxt/builds/meta/fd0033e4-1eaa-42f8-9a04-3132717a703b.json +0 -1
  194. package/.output/server/chunks/build/ontology-B_0Vm6j-.mjs.map +0 -1
  195. /package/.output/public/_nuxt/{DgrK8UCQ.js → BvRk9kiK2.js} +0 -0
  196. /package/.output/public/_nuxt/{DxH5focQ2.js → DxH5focQ.js} +0 -0
@@ -0,0 +1,812 @@
1
+ <template>
2
+ <div
3
+ class="page"
4
+ data-test="pipeline-page"
5
+ >
6
+ <header class="aw-page-head">
7
+ <div>
8
+ <h1>{{ $t('studio.pipeline.title') }}</h1>
9
+ <p class="sub">
10
+ {{ $t('studio.pipeline.sub') }}
11
+ </p>
12
+ </div>
13
+ <NuxtLink
14
+ class="pill-btn outline"
15
+ to="/studio"
16
+ >{{ $t('studio.pipeline.backToStudio') }}</NuxtLink>
17
+ </header>
18
+
19
+ <!-- Step 1 拉取数据 -->
20
+ <section class="cardBlock">
21
+ <h2><span class="stepNo">1</span>{{ $t('studio.pipeline.s1') }}</h2>
22
+ <div class="row">
23
+ <select
24
+ v-model="lineId"
25
+ data-test="pipeline-lines"
26
+ class="field"
27
+ @change="loadNodes"
28
+ >
29
+ <option
30
+ value=""
31
+ disabled
32
+ >
33
+ {{ $t('studio.pipeline.pickLine') }}
34
+ </option>
35
+ <option
36
+ v-for="l in lines"
37
+ :key="l.id"
38
+ :value="l.id"
39
+ >
40
+ {{ l.name }}
41
+ </option>
42
+ </select>
43
+ <select
44
+ v-model="windowMin"
45
+ class="field narrow"
46
+ >
47
+ <option :value="15">
48
+ {{ $t('studio.pipeline.w15') }}
49
+ </option>
50
+ <option :value="60">
51
+ {{ $t('studio.pipeline.w60') }}
52
+ </option>
53
+ <option :value="240">
54
+ {{ $t('studio.pipeline.w240') }}
55
+ </option>
56
+ </select>
57
+ <select
58
+ v-model="agg"
59
+ class="field narrow"
60
+ >
61
+ <option value="avg">
62
+ avg
63
+ </option>
64
+ <option value="min">
65
+ min
66
+ </option>
67
+ <option value="max">
68
+ max
69
+ </option>
70
+ </select>
71
+ <button
72
+ class="pill-btn"
73
+ data-test="pipeline-fetch"
74
+ :disabled="querying || !lineId || !pickNodes.length"
75
+ @click="doFetch"
76
+ >
77
+ {{ querying ? $t('studio.pipeline.fetching') : $t('studio.pipeline.fetch') }}
78
+ </button>
79
+ </div>
80
+ <div
81
+ class="row wrap"
82
+ data-test="pipeline-nodes"
83
+ >
84
+ <button
85
+ v-for="n in nodes"
86
+ :key="n.id"
87
+ class="chip"
88
+ :class="{ on: pickNodes.includes(n.id) }"
89
+ @click="toggleNode(n.id)"
90
+ >
91
+ {{ n.name }}<span
92
+ v-if="n.unit"
93
+ class="unit"
94
+ > {{ n.unit }}</span>
95
+ </button>
96
+ </div>
97
+ <div class="importRow">
98
+ <textarea
99
+ v-model="importCsv"
100
+ class="code import"
101
+ rows="3"
102
+ spellcheck="false"
103
+ data-test="pipeline-import"
104
+ :placeholder="$t('studio.pipeline.importPlaceholder')"
105
+ />
106
+ <button
107
+ class="pill-btn outline"
108
+ data-test="pipeline-import-btn"
109
+ :disabled="!importCsv.trim()"
110
+ @click="importTable"
111
+ >
112
+ {{ $t('studio.pipeline.importBtn') }}
113
+ </button>
114
+ </div>
115
+ </section>
116
+
117
+ <!-- Step 2 数据表 + 后处理 -->
118
+ <section class="cardBlock">
119
+ <h2><span class="stepNo">2</span>{{ $t('studio.pipeline.s2') }}</h2>
120
+ <div
121
+ v-if="viewRows.length"
122
+ class="tableWrap"
123
+ data-test="pipeline-table"
124
+ >
125
+ <table class="grid">
126
+ <thead>
127
+ <tr>
128
+ <th
129
+ v-for="c in tableCols"
130
+ :key="c"
131
+ >
132
+ {{ c }}
133
+ </th>
134
+ </tr>
135
+ </thead>
136
+ <tbody>
137
+ <tr
138
+ v-for="(r, i) in viewRows.slice(0, 8)"
139
+ :key="i"
140
+ >
141
+ <td
142
+ v-for="c in tableCols"
143
+ :key="c"
144
+ >
145
+ {{ r[c] }}
146
+ </td>
147
+ </tr>
148
+ </tbody>
149
+ </table>
150
+ <p class="hint">
151
+ {{ $t('studio.pipeline.rowsInfo', { n: viewRows.length, show: Math.min(viewRows.length, 8) }) }}
152
+ </p>
153
+ </div>
154
+ <p
155
+ v-else
156
+ class="hint"
157
+ >
158
+ {{ $t('studio.pipeline.noRows') }}
159
+ </p>
160
+ <div class="codeRow">
161
+ <textarea
162
+ v-model="transformCode"
163
+ class="code"
164
+ rows="5"
165
+ spellcheck="false"
166
+ data-test="pipeline-code"
167
+ :placeholder="$t('studio.pipeline.codePlaceholder')"
168
+ />
169
+ <div class="codeSide">
170
+ <button
171
+ class="pill-btn"
172
+ data-test="pipeline-apply"
173
+ :disabled="!viewRows.length"
174
+ @click="applyTransform"
175
+ >
176
+ {{ $t('studio.pipeline.applyTransform') }}
177
+ </button>
178
+ <button
179
+ class="pill-btn outline"
180
+ data-test="pipeline-reset"
181
+ @click="resetTransform"
182
+ >
183
+ {{ $t('studio.pipeline.resetTransform') }}
184
+ </button>
185
+ <p
186
+ v-if="transformError"
187
+ class="err"
188
+ role="alert"
189
+ >
190
+ {{ transformError }}
191
+ </p>
192
+ <p
193
+ v-else-if="applied"
194
+ class="hint"
195
+ >
196
+ {{ $t('studio.pipeline.transformedTo', { n: viewRows.length }) }}
197
+ </p>
198
+ </div>
199
+ </div>
200
+ </section>
201
+
202
+ <!-- Step 3 图表配置 -->
203
+ <section class="cardBlock">
204
+ <h2><span class="stepNo">3</span>{{ $t('studio.pipeline.s3') }}</h2>
205
+ <div class="row wrap">
206
+ <label class="lbl">{{ $t('studio.pipeline.chartType') }}
207
+ <select
208
+ v-model="chartType"
209
+ data-test="pipeline-type"
210
+ class="field narrow"
211
+ >
212
+ <option value="line">
213
+ line
214
+ </option>
215
+ <option value="bar">
216
+ bar
217
+ </option>
218
+ <option value="scatter">
219
+ scatter
220
+ </option>
221
+ <option value="pie">
222
+ pie
223
+ </option>
224
+ <option value="histogram">
225
+ histogram
226
+ </option>
227
+ </select>
228
+ </label>
229
+ <label class="lbl">X
230
+ <select
231
+ v-model="cfg.xField"
232
+ data-test="pipeline-xfield"
233
+ class="field narrow"
234
+ >
235
+ <option
236
+ v-for="f in viewFields"
237
+ :key="f.name"
238
+ :value="f.name"
239
+ >
240
+ {{ f.name }}
241
+ </option>
242
+ </select>
243
+ </label>
244
+ <label
245
+ v-if="chartType !== 'pie' && chartType !== 'histogram'"
246
+ class="lbl"
247
+ >Y
248
+ <select
249
+ v-model="cfg.yFields"
250
+ data-test="pipeline-yfields"
251
+ class="field narrow"
252
+ multiple
253
+ >
254
+ <option
255
+ v-for="f in viewFields.filter(f => f.numeric)"
256
+ :key="f.name"
257
+ :value="f.name"
258
+ >
259
+ {{ f.name }}
260
+ </option>
261
+ </select>
262
+ </label>
263
+ <label
264
+ v-else
265
+ class="lbl"
266
+ >{{ $t('studio.pipeline.valueField') }}
267
+ <select
268
+ v-model="cfg.yFields[0]"
269
+ data-test="pipeline-yfields"
270
+ class="field narrow"
271
+ >
272
+ <option
273
+ v-for="f in viewFields.filter(f => f.numeric)"
274
+ :key="f.name"
275
+ :value="f.name"
276
+ >
277
+ {{ f.name }}
278
+ </option>
279
+ </select>
280
+ </label>
281
+ <label class="lbl">{{ $t('studio.pipeline.groupBy') }}
282
+ <select
283
+ v-model="cfg.groupField"
284
+ data-test="pipeline-group"
285
+ class="field narrow"
286
+ >
287
+ <option value="">
288
+ —
289
+ </option>
290
+ <option
291
+ v-for="f in viewFields.filter(f => !f.numeric)"
292
+ :key="f.name"
293
+ :value="f.name"
294
+ >
295
+ {{ f.name }}
296
+ </option>
297
+ </select>
298
+ </label>
299
+ <label
300
+ v-if="chartType !== 'pie' && chartType !== 'histogram'"
301
+ class="lbl"
302
+ >{{ $t('studio.pipeline.agg') }}
303
+ <select
304
+ v-model="cfg.agg"
305
+ class="field narrow"
306
+ >
307
+ <option value="avg">
308
+ avg
309
+ </option>
310
+ <option value="sum">
311
+ sum
312
+ </option>
313
+ <option value="min">
314
+ min
315
+ </option>
316
+ <option value="max">
317
+ max
318
+ </option>
319
+ <option value="count">
320
+ count
321
+ </option>
322
+ </select>
323
+ </label>
324
+ <label class="lbl">{{ $t('studio.pipeline.chartTitle') }}
325
+ <input
326
+ v-model="cfg.title"
327
+ class="field"
328
+ data-test="pipeline-title"
329
+ >
330
+ </label>
331
+ <label class="lbl">{{ $t('studio.pipeline.refLine') }}
332
+ <input
333
+ v-model="refLineInput"
334
+ class="field tiny"
335
+ data-test="pipeline-refline"
336
+ >
337
+ </label>
338
+ </div>
339
+ </section>
340
+
341
+ <!-- Step 4 模板 -->
342
+ <section class="cardBlock">
343
+ <h2><span class="stepNo">4</span>{{ $t('studio.pipeline.s4') }}</h2>
344
+ <div class="row wrap">
345
+ <input
346
+ v-model="templateName"
347
+ class="field"
348
+ data-test="pipeline-template-name"
349
+ :placeholder="$t('studio.pipeline.templateName')"
350
+ >
351
+ <button
352
+ class="pill-btn"
353
+ data-test="pipeline-template-save"
354
+ :disabled="!templateName.trim()"
355
+ @click="saveTemplate"
356
+ >
357
+ {{ $t('studio.pipeline.templateSave') }}
358
+ </button>
359
+ <span
360
+ v-if="templateMsg"
361
+ class="hint"
362
+ >{{ templateMsg }}</span>
363
+ </div>
364
+ <div
365
+ class="row wrap"
366
+ data-test="pipeline-templates"
367
+ >
368
+ <button
369
+ v-for="tp in templates"
370
+ :key="tp.id"
371
+ class="chip tpl"
372
+ data-test="pipeline-template-item"
373
+ :title="`${tp.chartType} · ${tp.description || tp.name}`"
374
+ @click="applyTemplate(tp)"
375
+ >
376
+ {{ tp.name }}<span class="unit"> · {{ tp.chartType }}</span>
377
+ <span
378
+ class="del"
379
+ @click.stop="removeTemplate(tp.id)"
380
+ >×</span>
381
+ </button>
382
+ </div>
383
+ </section>
384
+
385
+ <!-- Step 5 绘图与输出 -->
386
+ <section class="cardBlock">
387
+ <h2><span class="stepNo">5</span>{{ $t('studio.pipeline.s5') }}</h2>
388
+ <div class="row wrap">
389
+ <button
390
+ class="pill-btn"
391
+ data-test="pipeline-draw"
392
+ :disabled="!canDraw"
393
+ @click="draw"
394
+ >
395
+ {{ $t('studio.pipeline.draw') }}
396
+ </button>
397
+ <button
398
+ class="pill-btn outline"
399
+ data-test="pipeline-viewer"
400
+ :disabled="!hasDrawn"
401
+ @click="viewerOpen = true"
402
+ >
403
+ {{ $t('studio.pipeline.openViewer') }}
404
+ </button>
405
+ <button
406
+ class="pill-btn outline"
407
+ data-test="pipeline-download"
408
+ :disabled="!hasDrawn"
409
+ @click="downloadPng"
410
+ >
411
+ {{ $t('studio.pipeline.download') }}
412
+ </button>
413
+ <button
414
+ class="pill-btn outline"
415
+ data-test="pipeline-save-chart"
416
+ :disabled="!hasDrawn"
417
+ @click="saveChart"
418
+ >
419
+ {{ $t('studio.pipeline.saveChart') }}
420
+ </button>
421
+ <button
422
+ class="pill-btn outline"
423
+ data-test="pipeline-save-result"
424
+ :disabled="!hasDrawn"
425
+ @click="saveResult"
426
+ >
427
+ {{ $t('studio.pipeline.saveResult') }}
428
+ </button>
429
+ <span
430
+ v-if="saveMsg"
431
+ class="hint"
432
+ >{{ saveMsg }}</span>
433
+ <span
434
+ v-if="saveErr"
435
+ class="err"
436
+ role="alert"
437
+ >{{ saveErr }}</span>
438
+ </div>
439
+ <div
440
+ v-if="hasDrawn"
441
+ class="chartHost"
442
+ data-test="pipeline-chart"
443
+ >
444
+ <ClientOnly>
445
+ <AwChart
446
+ ref="chartRef"
447
+ :option="option"
448
+ :height="380"
449
+ />
450
+ </ClientOnly>
451
+ </div>
452
+ </section>
453
+
454
+ <!-- 在线查看器外壳 -->
455
+ <Teleport to="body">
456
+ <div
457
+ v-if="viewerOpen"
458
+ class="viewer"
459
+ data-test="pipeline-viewer-shell"
460
+ @click.self="viewerOpen = false"
461
+ >
462
+ <div class="viewerBody">
463
+ <div class="viewerBar">
464
+ <b>{{ cfg.title || $t('studio.pipeline.chartTitle') }}</b>
465
+ <span class="unit">{{ $t('studio.pipeline.viewerHint') }}</span>
466
+ <button
467
+ class="pill-btn outline"
468
+ data-test="pipeline-viewer-download"
469
+ @click="downloadPng"
470
+ >
471
+ PNG
472
+ </button>
473
+ <button
474
+ class="pill-btn outline"
475
+ data-test="pipeline-viewer-close"
476
+ @click="viewerOpen = false"
477
+ >
478
+ ✕
479
+ </button>
480
+ </div>
481
+ <div class="viewerChart">
482
+ <ClientOnly>
483
+ <AwChart
484
+ :option="viewerOption"
485
+ :height="420"
486
+ />
487
+ </ClientOnly>
488
+ </div>
489
+ </div>
490
+ </div>
491
+ </Teleport>
492
+ </div>
493
+ </template>
494
+
495
+ <script setup lang="ts">
496
+ import { computed, defineAsyncComponent, ref, onMounted } from 'vue'
497
+ import { useI18n } from 'vue-i18n'
498
+ import { apiFetch } from '@/app/composables/workshop/apiClient'
499
+ import { apiErrorMessage } from '@/app/utils/api-error'
500
+ import { deriveFields, runTransform, buildPipelineOption, type PipelineConfig, type PipelineRow } from '@/app/utils/chart-pipeline'
501
+ // echarts 导入链在 dev SSR 摸 document → 必须 client-only 异步组件(静态 import 会在服务端求值)
502
+ const AwChart = defineAsyncComponent(() => import('@/app/components/AwChart.vue'))
503
+
504
+ const { t } = useI18n()
505
+
506
+ interface LineLite { id: string, name: string }
507
+ interface DaqNodeLite { id: string, name: string, unit?: string, lineId?: string }
508
+ interface TemplateRow { id: string, name: string, description: string, chartType: string, config: Record<string, unknown> }
509
+
510
+ const lines = ref<LineLite[]>([])
511
+ const nodes = ref<DaqNodeLite[]>([])
512
+ const lineId = ref('')
513
+ const pickNodes = ref<string[]>([])
514
+ const windowMin = ref(60)
515
+ const agg = ref<'avg' | 'min' | 'max'>('avg')
516
+ const querying = ref(false)
517
+
518
+ const rawRows = ref<PipelineRow[]>([])
519
+ const viewRows = ref<PipelineRow[]>([])
520
+ const transformCode = ref('')
521
+ const transformError = ref('')
522
+ const applied = ref(false)
523
+
524
+ const chartType = ref<'line' | 'bar' | 'scatter' | 'pie' | 'histogram'>('line')
525
+ const cfg = ref<PipelineConfig>({ xField: 'at', yFields: [], groupField: '', agg: 'avg', title: '', yMin: null, yMax: null, refLine: null })
526
+ const refLineInput = ref('')
527
+ const hasDrawn = ref(false)
528
+ const viewerOpen = ref(false)
529
+ const chartRef = ref<{ chartResize?: () => void } | null>(null)
530
+ const chartInstance = computed(() => (chartRef.value as unknown as { chart?: { getDataURL: (o?: Record<string, unknown>) => string } } | null)?.chart ?? null)
531
+
532
+ const templates = ref<TemplateRow[]>([])
533
+ const templateName = ref('')
534
+ const templateMsg = ref('')
535
+ const saveMsg = ref('')
536
+ const saveErr = ref('')
537
+
538
+ const option = ref<Record<string, unknown>>({})
539
+ const viewerOption = computed(() => ({ ...option.value, toolbox: { feature: { saveAsImage: { pixelRatio: 2 } } } }))
540
+
541
+ const tableCols = computed(() => deriveFields(viewRows.value).map(f => f.name))
542
+ const viewFields = computed(() => deriveFields(viewRows.value))
543
+ const canDraw = computed(() => viewRows.value.length > 0 && Boolean(cfg.value.xField) && cfg.value.yFields.filter(Boolean).length > 0)
544
+
545
+ function toggleNode(id: string): void {
546
+ pickNodes.value = pickNodes.value.includes(id) ? pickNodes.value.filter(x => x !== id) : [...pickNodes.value, id]
547
+ }
548
+
549
+ const importCsv = ref('')
550
+
551
+ /** CSV/TSV 导入(首行为表头;数值自动转型)——线上拉取之外的第二数据入口 */
552
+ function importTable(): void {
553
+ const text = importCsv.value.trim()
554
+ if (!text) return
555
+ const lines = text.split(/\r?\n/).filter(l => l.trim())
556
+ if (lines.length < 2) return
557
+ const first = lines[0] ?? ''
558
+ const sep = first.includes('\t') ? '\t' : ','
559
+ const header = first.split(sep).map(h => h.trim().replace(/^"|"$/g, ''))
560
+ const out: PipelineRow[] = []
561
+ for (const line of lines.slice(1)) {
562
+ const cells = line.split(sep)
563
+ const row: PipelineRow = {}
564
+ header.forEach((h, i) => {
565
+ const raw = (cells[i] ?? '').trim().replace(/^"|"$/g, '')
566
+ const num = Number(raw)
567
+ row[h] = raw !== '' && Number.isFinite(num) ? num : raw
568
+ })
569
+ out.push(row)
570
+ }
571
+ rawRows.value = out
572
+ viewRows.value = out
573
+ applied.value = false
574
+ hasDrawn.value = false
575
+ transformError.value = ''
576
+ const numeric = deriveFields(out).filter(f => f.numeric && f.name !== 'at')
577
+ cfg.value = { ...cfg.value, xField: header[0] ?? 'at', yFields: numeric.slice(0, 1).map(f => f.name) }
578
+ }
579
+
580
+ async function loadNodes(): Promise<void> {
581
+ pickNodes.value = []
582
+ const r = await apiFetch<{ nodes: DaqNodeLite[] }>({ base: '/api/workshop/daq', retries: 1 })
583
+ nodes.value = (r.nodes ?? []).filter(n => !lineId.value || n.lineId === lineId.value)
584
+ }
585
+
586
+ /** daq/query 的 series 点阵 → 宽表行(at + 每测点一列,列名=测点名) */
587
+ function seriesToRows(series: Array<{ nodeId: string, name?: string, points: Array<{ x: number | string, y: number }> }>): PipelineRow[] {
588
+ const keyOf = (s: { nodeId: string, name?: string }) => nodes.value.find(n => n.id === s.nodeId)?.name ?? s.name ?? s.nodeId
589
+ const byAt = new Map<string, PipelineRow>()
590
+ for (const s of series) {
591
+ const key = keyOf(s)
592
+ for (const p of s.points ?? []) {
593
+ const at = new Date(p.x).toISOString()
594
+ const row = byAt.get(at) ?? { at }
595
+ row[key] = p.y
596
+ byAt.set(at, row)
597
+ }
598
+ }
599
+ return [...byAt.values()].sort((a, b) => String(a.at).localeCompare(String(b.at)))
600
+ }
601
+
602
+ async function doFetch(): Promise<void> {
603
+ querying.value = true
604
+ transformError.value = ''
605
+ applied.value = false
606
+ hasDrawn.value = false
607
+ saveMsg.value = ''
608
+ saveErr.value = ''
609
+ try {
610
+ const to = Date.now()
611
+ const from = to - windowMin.value * 60_000
612
+ const p = new URLSearchParams()
613
+ p.set('line_id', lineId.value)
614
+ p.set('node_ids', pickNodes.value.join(','))
615
+ p.set('from', String(from))
616
+ p.set('to', String(to))
617
+ p.set('agg', agg.value)
618
+ const r = await apiFetch<{ series: Array<{ nodeId: string, name?: string, points: Array<{ x: number | string, y: number }> }> }>({ base: `/api/workshop/daq/query?${p.toString()}`, retries: 0 })
619
+ rawRows.value = seriesToRows(r.series ?? [])
620
+ viewRows.value = rawRows.value
621
+ if (!cfg.value.yFields.length && viewFields.value.length) {
622
+ const numeric = viewFields.value.filter(f => f.numeric && f.name !== 'at')
623
+ cfg.value = { ...cfg.value, xField: 'at', yFields: numeric.slice(0, 1).map(f => f.name) }
624
+ }
625
+ }
626
+ catch (e) {
627
+ transformError.value = apiErrorMessage(e)
628
+ }
629
+ finally {
630
+ querying.value = false
631
+ }
632
+ }
633
+
634
+ function applyTransform(): void {
635
+ const out = runTransform(rawRows.value, transformCode.value)
636
+ transformError.value = out.error
637
+ if (!out.error) {
638
+ viewRows.value = out.rows
639
+ applied.value = true
640
+ hasDrawn.value = false
641
+ }
642
+ }
643
+
644
+ function resetTransform(): void {
645
+ transformCode.value = ''
646
+ transformError.value = ''
647
+ applied.value = false
648
+ viewRows.value = rawRows.value
649
+ }
650
+
651
+ function draw(): void {
652
+ cfg.value = { ...cfg.value, refLine: refLineInput.value.trim() === '' ? null : Number(refLineInput.value) }
653
+ if (Number.isNaN(cfg.value.refLine as unknown as number)) cfg.value.refLine = null
654
+ option.value = buildPipelineOption(chartType.value, cfg.value, viewRows.value) as unknown as Record<string, unknown>
655
+ hasDrawn.value = true
656
+ }
657
+
658
+ function downloadPng(): void {
659
+ const inst = chartInstance.value
660
+ if (!inst) return
661
+ const url = inst.getDataURL({ pixelRatio: 2, backgroundColor: '#101418' })
662
+ const a = document.createElement('a')
663
+ a.href = url
664
+ a.download = `${cfg.value.title || 'eda-pipeline'}.png`
665
+ a.click()
666
+ }
667
+
668
+ async function saveChart(): Promise<void> {
669
+ saveMsg.value = ''
670
+ saveErr.value = ''
671
+ try {
672
+ await apiFetch({ base: '/api/workshop/eda/charts', init: { method: 'POST', body: JSON.stringify({
673
+ title: cfg.value.title || t('studio.pipeline.chartTitle'),
674
+ viz_type: `pipeline-${chartType.value}`,
675
+ datasource: { line_id: lineId.value, node_ids: pickNodes.value, window_min: windowMin.value, source_agg: agg.value },
676
+ encoding: { ...cfg.value, refLine: cfg.value.refLine ?? null },
677
+ transform: { code: transformCode.value },
678
+ options: { template: currentTemplateId.value || null },
679
+ line_id: lineId.value,
680
+ }) } })
681
+ saveMsg.value = t('studio.pipeline.savedChart')
682
+ }
683
+ catch (e) { saveErr.value = apiErrorMessage(e) }
684
+ }
685
+
686
+ async function saveResult(): Promise<void> {
687
+ saveMsg.value = ''
688
+ saveErr.value = ''
689
+ try {
690
+ const numericCols = viewFields.value.filter(f => f.numeric).map(f => f.name)
691
+ const stats: Record<string, { min: number, max: number, avg: number }> = {}
692
+ for (const col of numericCols) {
693
+ const vals = viewRows.value.map(r => r[col]).filter((v): v is number => typeof v === 'number' && Number.isFinite(v))
694
+ if (vals.length) stats[col] = { min: Math.min(...vals), max: Math.max(...vals), avg: Math.round(vals.reduce((a, b) => a + b, 0) / vals.length * 1e4) / 1e4 }
695
+ }
696
+ await apiFetch({ base: '/api/workshop/analysis', init: { method: 'POST', body: JSON.stringify({
697
+ line_id: lineId.value,
698
+ title: cfg.value.title || t('studio.pipeline.resultTitle'),
699
+ category: 'eda-pipeline',
700
+ summary: t('studio.pipeline.resultSummary', { n: viewRows.value.length, cols: numericCols.length }),
701
+ conclusions: numericCols.map(c => `${c}: min=${stats[c]?.min} max=${stats[c]?.max} avg=${stats[c]?.avg}`),
702
+ report: t('studio.pipeline.resultReport', { type: chartType.value, rows: viewRows.value.length }),
703
+ metrics: { rows: viewRows.value.length, columns: viewFields.value.length, stats },
704
+ context: { pipeline: true, chart_type: chartType.value, config: cfg.value, template_id: currentTemplateId.value || null, transform_code: transformCode.value },
705
+ window_from: new Date(Date.now() - windowMin.value * 60_000).toISOString(),
706
+ window_to: new Date().toISOString(),
707
+ }) } })
708
+ saveMsg.value = t('studio.pipeline.savedResult')
709
+ }
710
+ catch (e) { saveErr.value = apiErrorMessage(e) }
711
+ }
712
+
713
+ const currentTemplateId = ref('')
714
+
715
+ async function loadTemplates(): Promise<void> {
716
+ const r = await apiFetch<{ templates: TemplateRow[] }>({ base: '/api/workshop/eda/templates', retries: 1 })
717
+ templates.value = r.templates ?? []
718
+ }
719
+
720
+ async function saveTemplate(): Promise<void> {
721
+ templateMsg.value = ''
722
+ try {
723
+ await apiFetch({ base: '/api/workshop/eda/templates', init: { method: 'POST', body: JSON.stringify({
724
+ name: templateName.value.trim(),
725
+ description: `${chartType.value} · x=${cfg.value.xField} · y=${cfg.value.yFields.join(',')}`,
726
+ chart_type: chartType.value,
727
+ config: { ...cfg.value, transform_code: transformCode.value },
728
+ }) } })
729
+ templateName.value = ''
730
+ templateMsg.value = t('studio.pipeline.templateSaved')
731
+ await loadTemplates()
732
+ }
733
+ catch (e) { templateMsg.value = apiErrorMessage(e) }
734
+ }
735
+
736
+ function applyTemplate(tp: TemplateRow): void {
737
+ currentTemplateId.value = tp.id
738
+ chartType.value = (tp.chartType as typeof chartType.value) || 'line'
739
+ const c = { ...(tp.config ?? {}) } as unknown as PipelineConfig & { transform_code?: string }
740
+ cfg.value = {
741
+ xField: c.xField ?? 'at',
742
+ yFields: Array.isArray(c.yFields) ? c.yFields : (c.yFields ? [String(c.yFields)] : []),
743
+ groupField: c.groupField ?? '',
744
+ agg: c.agg ?? 'avg',
745
+ title: c.title ?? '',
746
+ yMin: c.yMin ?? null,
747
+ yMax: c.yMax ?? null,
748
+ refLine: c.refLine ?? null,
749
+ }
750
+ refLineInput.value = cfg.value.refLine == null ? '' : String(cfg.value.refLine)
751
+ if (typeof c.transform_code === 'string') transformCode.value = c.transform_code
752
+ templateMsg.value = t('studio.pipeline.templateApplied', { name: tp.name })
753
+ if (viewRows.value.length && canDraw.value) draw()
754
+ }
755
+
756
+ async function removeTemplate(id: string): Promise<void> {
757
+ await apiFetch({ base: `/api/workshop/eda/templates/${id}`, init: { method: 'DELETE' } })
758
+ if (currentTemplateId.value === id) currentTemplateId.value = ''
759
+ await loadTemplates()
760
+ }
761
+
762
+ onMounted(async () => {
763
+ try {
764
+ const [l, tp] = await Promise.all([
765
+ apiFetch<{ lines: LineLite[] }>({ base: '/api/workshop/dcw/lines', retries: 1 }),
766
+ apiFetch<{ templates: TemplateRow[] }>({ base: '/api/workshop/eda/templates', retries: 1 }),
767
+ ])
768
+ lines.value = l.lines ?? []
769
+ templates.value = tp.templates ?? []
770
+ }
771
+ catch { /* 首屏失败不拖垮页面 */ }
772
+ })
773
+ </script>
774
+
775
+ <style scoped>
776
+ .page { display: flex; flex-direction: column; gap: 14px; padding-bottom: 40px }
777
+ .aw-page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px }
778
+ .sub { color: var(--text-dim, #8fa0ad); font-size: 12px; margin-top: 4px }
779
+ .cardBlock { background: var(--panel, rgba(255,255,255,.03)); border: 1px solid var(--line, rgba(255,255,255,.08)); border-radius: 12px; padding: 14px 16px }
780
+ .cardBlock h2 { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px; margin-bottom: 10px }
781
+ .stepNo { width: 20px; height: 20px; border-radius: 6px; background: var(--accent, #35e0a0); color: #06121c; font-size: 12px; display: inline-flex; align-items: center; justify-content: center }
782
+ .row { display: flex; gap: 8px; align-items: center; margin: 6px 0 }
783
+ .row.wrap { flex-wrap: wrap }
784
+ .field { background: var(--bg-deep, rgba(0,0,0,.25)); border: 1px solid var(--line, rgba(255,255,255,.1)); border-radius: 8px; color: inherit; padding: 6px 10px; font-size: 12px; min-width: 170px }
785
+ .field.narrow { min-width: 110px }
786
+ .field.tiny { min-width: 84px }
787
+ .lbl { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-dim, #8fa0ad) }
788
+ .chip { border: 1px solid var(--line, rgba(255,255,255,.12)); background: transparent; color: inherit; border-radius: 999px; padding: 3px 12px; font-size: 12px; cursor: pointer }
789
+ .chip.on { border-color: var(--accent, #35e0a0); color: var(--accent, #35e0a0) }
790
+ .chip.tpl .unit { opacity: .6 }
791
+ .chip.tpl .del { margin-left: 6px; opacity: .5 }
792
+ .chip.tpl .del:hover { opacity: 1; color: #e57373 }
793
+ .unit { opacity: .65; font-size: 11px }
794
+ .tableWrap { overflow: auto; max-height: 240px; border: 1px solid var(--line, rgba(255,255,255,.08)); border-radius: 8px }
795
+ .grid { border-collapse: collapse; width: 100%; font-size: 12px }
796
+ .grid th { text-align: left; position: sticky; top: 0; background: var(--bg-deep, rgba(0,0,0,.4)); padding: 6px 10px; border-bottom: 1px solid var(--line, rgba(255,255,255,.1)) }
797
+ .grid td { padding: 4px 10px; border-bottom: 1px solid var(--line, rgba(255,255,255,.05)); white-space: nowrap }
798
+ .hint { color: var(--text-dim, #8fa0ad); font-size: 12px }
799
+ .err { color: #e57373; font-size: 12px }
800
+ .codeRow { display: flex; gap: 12px; align-items: flex-start; margin-top: 8px }
801
+ .importRow { display: flex; gap: 8px; align-items: flex-start; margin-top: 8px }
802
+ .importRow .import { flex: 1; max-width: 640px }
803
+ .code { flex: 1; font-family: ui-monospace, Consolas, monospace; font-size: 12px; background: var(--bg-deep, rgba(0,0,0,.3)); border: 1px solid var(--line, rgba(255,255,255,.1)); border-radius: 8px; color: inherit; padding: 8px 10px; resize: vertical }
804
+ .codeSide { display: flex; flex-direction: column; gap: 8px; max-width: 220px }
805
+ .chartHost { margin-top: 10px; border: 1px solid var(--line, rgba(255,255,255,.08)); border-radius: 10px; padding: 8px }
806
+ .viewer { position: fixed; inset: 0; z-index: 90; background: rgba(4,8,12,.78); display: flex; align-items: center; justify-content: center; padding: 4vh 4vw }
807
+ .viewerBody { width: 100%; max-width: 1100px; background: var(--panel, #0e141b); border: 1px solid var(--line, rgba(255,255,255,.12)); border-radius: 14px; overflow: hidden }
808
+ .viewerBar { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--line, rgba(255,255,255,.08)) }
809
+ .viewerBar b { font-size: 13px }
810
+ .viewerBar .unit { flex: 1 }
811
+ .viewerChart { padding: 8px 12px 12px }
812
+ </style>