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.
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/B4iJT5PX2.js +2 -0
- package/.output/public/_nuxt/BNUaS1jv.js +20 -0
- package/.output/public/_nuxt/BhJA-E372.js +1 -0
- package/.output/public/_nuxt/Bq8YzXzs2.js +1 -0
- package/.output/public/_nuxt/Cf7ER7e2.js +1 -0
- package/.output/public/_nuxt/{DrlnrdvY2.js → DPnjnDtg.js} +1 -1
- package/.output/public/_nuxt/DgTqKyYF2.js +1 -0
- package/.output/public/_nuxt/MmlCf-EZ.js +1 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/8a151630-3285-41b8-a1e5-063fee2002ac.json +1 -0
- package/.output/public/_nuxt/{entry.C7koBPom.css → entry.D6nBdXet.css} +1 -1
- package/.output/public/_nuxt/{ontology.JqJ6PjV1.css → ontology.D4P756Tc.css} +1 -1
- package/.output/public/_nuxt/pipeline.NFs5z5V-.css +1 -0
- package/.output/server/chunks/_/character-asset.repo.mjs +1 -1
- package/.output/server/chunks/_/chart-repo.mjs +1 -1
- package/.output/server/chunks/_/chart-repo.mjs.map +1 -1
- package/.output/server/chunks/_/eda-db.mjs +12 -0
- package/.output/server/chunks/_/eda-db.mjs.map +1 -1
- package/.output/server/chunks/_/host-bindings.mjs +1 -1
- package/.output/server/chunks/_/nitro.mjs +1977 -1951
- package/.output/server/chunks/_/scene-layout.repo.mjs +1 -1
- package/.output/server/chunks/_/template-repo.mjs +97 -0
- package/.output/server/chunks/_/template-repo.mjs.map +1 -0
- package/.output/server/chunks/build/{TownView-CUbzOT_M.mjs → TownView-CcUPLqF1.mjs} +3 -3
- package/.output/server/chunks/build/{TownView-CUbzOT_M.mjs.map → TownView-CcUPLqF1.mjs.map} +1 -1
- package/.output/server/chunks/build/{_id_-JaGx0yBE.mjs → _id_-AgACEyL6.mjs} +13 -13
- package/.output/server/chunks/build/{_id_-JaGx0yBE.mjs.map → _id_-AgACEyL6.mjs.map} +1 -1
- package/.output/server/chunks/build/{_id_-BHWZiRQZ.mjs → _id_-B3yCK2lD.mjs} +8 -8
- package/.output/server/chunks/build/{_id_-BHWZiRQZ.mjs.map → _id_-B3yCK2lD.mjs.map} +1 -1
- package/.output/server/chunks/build/{_wsId_-C4DKkEqv.mjs → _wsId_-kcDriA4i.mjs} +4 -4
- package/.output/server/chunks/build/{_wsId_-C4DKkEqv.mjs.map → _wsId_-kcDriA4i.mjs.map} +1 -1
- package/.output/server/chunks/build/{analysis-tNpFLtMt.mjs → analysis-CMsMTMCg.mjs} +3 -3
- package/.output/server/chunks/build/{analysis-tNpFLtMt.mjs.map → analysis-CMsMTMCg.mjs.map} +1 -1
- package/.output/server/chunks/build/{daq-DHsYtWC-.mjs → daq-DFQQOtcZ.mjs} +7 -7
- package/.output/server/chunks/build/{daq-DHsYtWC-.mjs.map → daq-DFQQOtcZ.mjs.map} +1 -1
- package/.output/server/chunks/build/{dcw-DnjOM6p1.mjs → dcw-8415S3Km.mjs} +7 -7
- package/.output/server/chunks/build/{dcw-DnjOM6p1.mjs.map → dcw-8415S3Km.mjs.map} +1 -1
- package/.output/server/chunks/build/{logs-C4vKFkHQ.mjs → logs-C-dWpJ80.mjs} +3 -3
- package/.output/server/chunks/build/{logs-C4vKFkHQ.mjs.map → logs-C-dWpJ80.mjs.map} +1 -1
- package/.output/server/chunks/build/{ontology-B_0Vm6j-.mjs → ontology-DoXAsa2y.mjs} +62 -62
- package/.output/server/chunks/build/ontology-DoXAsa2y.mjs.map +1 -0
- package/.output/server/chunks/build/{operations-D8D6-Jm-.mjs → operations-Ch6tZIKo.mjs} +3 -3
- package/.output/server/chunks/build/{operations-D8D6-Jm-.mjs.map → operations-Ch6tZIKo.mjs.map} +1 -1
- package/.output/server/chunks/build/{pages-Dac4icQC.mjs → pages-CQOfwKB4.mjs} +4 -4
- package/.output/server/chunks/build/{pages-Dac4icQC.mjs.map → pages-CQOfwKB4.mjs.map} +1 -1
- package/.output/server/chunks/build/pipeline-eo2RWN3f.mjs +226 -0
- package/.output/server/chunks/build/pipeline-eo2RWN3f.mjs.map +1 -0
- package/.output/server/chunks/build/{town-LkDwWnKA.mjs → town-DLFB4SZl.mjs} +4 -4
- package/.output/server/chunks/build/{town-LkDwWnKA.mjs.map → town-DLFB4SZl.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDaqAddNode-CRorxgOY.mjs → useDaqAddNode-D_1EBuR3.mjs} +2 -2
- package/.output/server/chunks/build/{useDaqAddNode-CRorxgOY.mjs.map → useDaqAddNode-D_1EBuR3.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDaqDetailBinding-Ck-YUW9J.mjs → useDaqDetailBinding-j05iSApd.mjs} +2 -2
- package/.output/server/chunks/build/{useDaqDetailBinding-Ck-YUW9J.mjs.map → useDaqDetailBinding-j05iSApd.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDaqDetailFrames-CLMoreaz.mjs → useDaqDetailFrames-BKciVJ78.mjs} +2 -2
- package/.output/server/chunks/build/{useDaqDetailFrames-CLMoreaz.mjs.map → useDaqDetailFrames-BKciVJ78.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDaqDetailHistory-BVNQXwC_.mjs → useDaqDetailHistory-DgemICkr.mjs} +2 -2
- package/.output/server/chunks/build/{useDaqDetailHistory-BVNQXwC_.mjs.map → useDaqDetailHistory-DgemICkr.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDaqDetailNode-BC0e9OfX.mjs → useDaqDetailNode-Cn7_og6t.mjs} +3 -3
- package/.output/server/chunks/build/{useDaqDetailNode-BC0e9OfX.mjs.map → useDaqDetailNode-Cn7_og6t.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDaqDetailParams-D173iwFg.mjs → useDaqDetailParams-DX_vjFyK.mjs} +2 -2
- package/.output/server/chunks/build/{useDaqDetailParams-D173iwFg.mjs.map → useDaqDetailParams-DX_vjFyK.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDaqFilters-CC-o_21d.mjs → useDaqFilters-DMS4GN20.mjs} +3 -3
- package/.output/server/chunks/build/{useDaqFilters-CC-o_21d.mjs.map → useDaqFilters-DMS4GN20.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDaqOptimizations-4vK2w1yn.mjs → useDaqOptimizations-OPbdy5YB.mjs} +2 -2
- package/.output/server/chunks/build/{useDaqOptimizations-4vK2w1yn.mjs.map → useDaqOptimizations-OPbdy5YB.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDaqStream-BHKpSJFd.mjs → useDaqStream-CS1G6iro.mjs} +1 -1
- package/.output/server/chunks/build/{useDaqStream-BHKpSJFd.mjs.map → useDaqStream-CS1G6iro.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDaqTemplates-CsXSc7yI.mjs → useDaqTemplates-DqhRYo88.mjs} +2 -2
- package/.output/server/chunks/build/{useDaqTemplates-CsXSc7yI.mjs.map → useDaqTemplates-DqhRYo88.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDashboardData-DuKjuJTz.mjs → useDashboardData-CeaMjEA9.mjs} +3 -3
- package/.output/server/chunks/build/{useDashboardData-DuKjuJTz.mjs.map → useDashboardData-CeaMjEA9.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwAddNode-C5DcxsHF.mjs → useDcwAddNode-Cytz_odQ.mjs} +2 -2
- package/.output/server/chunks/build/{useDcwAddNode-C5DcxsHF.mjs.map → useDcwAddNode-Cytz_odQ.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwCards-0bAVo6uB.mjs → useDcwCards-BKCFqkRd.mjs} +2 -2
- package/.output/server/chunks/build/{useDcwCards-0bAVo6uB.mjs.map → useDcwCards-BKCFqkRd.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwDetailScope-BNFOlt2v.mjs → useDcwDetailScope-CvdvcKAv.mjs} +3 -3
- package/.output/server/chunks/build/{useDcwDetailScope-BNFOlt2v.mjs.map → useDcwDetailScope-CvdvcKAv.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwDetailTemplates-DSH-o0tE.mjs → useDcwDetailTemplates-CC2FcEge.mjs} +2 -2
- package/.output/server/chunks/build/{useDcwDetailTemplates-DSH-o0tE.mjs.map → useDcwDetailTemplates-CC2FcEge.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwFilter-W61BFUcT.mjs → useDcwFilter-CVoiXbZh.mjs} +2 -2
- package/.output/server/chunks/build/{useDcwFilter-W61BFUcT.mjs.map → useDcwFilter-CVoiXbZh.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwLedger-DsColLdc.mjs → useDcwLedger-Cd-ckHoJ.mjs} +2 -2
- package/.output/server/chunks/build/{useDcwLedger-DsColLdc.mjs.map → useDcwLedger-Cd-ckHoJ.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwLineMutations-Cx4Ir7g3.mjs → useDcwLineMutations-BOQs-fw3.mjs} +2 -2
- package/.output/server/chunks/build/{useDcwLineMutations-Cx4Ir7g3.mjs.map → useDcwLineMutations-BOQs-fw3.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwLineRun-DQ0vB08z.mjs → useDcwLineRun-D9qvcyTM.mjs} +2 -2
- package/.output/server/chunks/build/{useDcwLineRun-DQ0vB08z.mjs.map → useDcwLineRun-D9qvcyTM.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwParamApprovals-CYspRuDk.mjs → useDcwParamApprovals-D36q4can.mjs} +2 -2
- package/.output/server/chunks/build/{useDcwParamApprovals-CYspRuDk.mjs.map → useDcwParamApprovals-D36q4can.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwProducts-CTbm_5S_.mjs → useDcwProducts-BozctrwT.mjs} +2 -2
- package/.output/server/chunks/build/{useDcwProducts-CTbm_5S_.mjs.map → useDcwProducts-BozctrwT.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwQuery-E0yjZs7G.mjs → useDcwQuery-C8FikKgu.mjs} +2 -2
- package/.output/server/chunks/build/{useDcwQuery-E0yjZs7G.mjs.map → useDcwQuery-C8FikKgu.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwQuickActions-BvT5VFOY.mjs → useDcwQuickActions-IyQ8Rkpe.mjs} +2 -2
- package/.output/server/chunks/build/{useDcwQuickActions-BvT5VFOY.mjs.map → useDcwQuickActions-IyQ8Rkpe.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwRecipes-zngShyk0.mjs → useDcwRecipes-k9HGhMwF.mjs} +2 -2
- package/.output/server/chunks/build/{useDcwRecipes-zngShyk0.mjs.map → useDcwRecipes-k9HGhMwF.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwStream-82IdjJKk.mjs → useDcwStream-BnBOiawo.mjs} +1 -1
- package/.output/server/chunks/build/{useDcwStream-82IdjJKk.mjs.map → useDcwStream-BnBOiawo.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwTemplates-CpHrcnvJ.mjs → useDcwTemplates-CnyYOOy0.mjs} +2 -2
- package/.output/server/chunks/build/{useDcwTemplates-CpHrcnvJ.mjs.map → useDcwTemplates-CnyYOOy0.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwWrites-BlOhljp9.mjs → useDcwWrites-Cco590iv.mjs} +2 -2
- package/.output/server/chunks/build/{useDcwWrites-BlOhljp9.mjs.map → useDcwWrites-Cco590iv.mjs.map} +1 -1
- package/.output/server/chunks/build/{useLogsScope-BRyy550Y.mjs → useLogsScope-C-yrta-v.mjs} +2 -2
- package/.output/server/chunks/build/{useLogsScope-BRyy550Y.mjs.map → useLogsScope-C-yrta-v.mjs.map} +1 -1
- package/.output/server/chunks/routes/api/workshop/assets/index.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/assets/index2.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/activate.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index3.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index4.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/line.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/index.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/eda/index8.get.mjs +33 -0
- package/.output/server/chunks/routes/api/workshop/eda/index8.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/eda/index8.post.mjs +41 -0
- package/.output/server/chunks/routes/api/workshop/eda/index8.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/eda/templates/_id_.delete.mjs +36 -0
- package/.output/server/chunks/routes/api/workshop/eda/templates/_id_.delete.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/eda/templates/_id_.get.mjs +37 -0
- package/.output/server/chunks/routes/api/workshop/eda/templates/_id_.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/eda/templates/_id_.patch.mjs +42 -0
- package/.output/server/chunks/routes/api/workshop/eda/templates/_id_.patch.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/index10.post.mjs +33 -0
- package/.output/server/chunks/routes/api/workshop/index10.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/index2.post.mjs +45 -46
- package/.output/server/chunks/routes/api/workshop/index2.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index3.post.mjs +46 -23
- package/.output/server/chunks/routes/api/workshop/index3.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index4.post.mjs +10 -9
- package/.output/server/chunks/routes/api/workshop/index4.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index5.post.mjs +22 -19
- package/.output/server/chunks/routes/api/workshop/index5.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index6.post.mjs +18 -42
- package/.output/server/chunks/routes/api/workshop/index6.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index7.post.mjs +43 -17
- package/.output/server/chunks/routes/api/workshop/index7.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index8.post.mjs +13 -15
- package/.output/server/chunks/routes/api/workshop/index8.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index9.post.mjs +18 -4
- package/.output/server/chunks/routes/api/workshop/index9.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/process-model/datasets.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/process-model/eligibility.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/process-model/experiments.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/teams/_id/deploy.post.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 +1 -1
- package/.output/server/chunks/virtual/entry.mjs +140 -43
- package/.output/server/chunks/virtual/entry.mjs.map +1 -1
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/chunks/virtual/precomputed.mjs.map +1 -1
- package/.output/server/package.json +1 -1
- package/README-zh.md +8 -5
- package/README.md +8 -5
- package/app/components/AwChart.vue +14 -0
- package/app/pages/ontology/index.vue +1 -1
- package/app/pages/studio/pipeline.vue +812 -0
- package/app/utils/chart-pipeline.ts +193 -0
- package/config.yml +1 -1
- package/docs/cli.en.md +2 -2
- package/docs/cli.md +2 -2
- package/docs/plugins.en.md +1 -1
- package/docs/plugins.md +1 -1
- package/docs/sdk.en.md +1 -1
- package/docs/sdk.md +1 -1
- package/i18n/locales/en.ts +46 -0
- package/i18n/locales/zh-CN.ts +46 -0
- package/package.json +1 -1
- package/scripts/check-docs-sync.mjs +1 -1
- package/scripts/e2e-eda-pipeline.mjs +286 -0
- package/scripts/e2e-resume-crash.mjs +20 -6
- package/scripts/e2e-timezone-settings.mjs +4 -2
- package/server/api/workshop/analysis/index.post.ts +54 -0
- package/server/api/workshop/eda/templates/[id].delete.ts +14 -0
- package/server/api/workshop/eda/templates/[id].get.ts +15 -0
- package/server/api/workshop/eda/templates/[id].patch.ts +20 -0
- package/server/api/workshop/eda/templates/index.get.ts +11 -0
- package/server/api/workshop/eda/templates/index.post.ts +20 -0
- package/server/services/workshop/eda/chart-repo.ts +1 -1
- package/server/services/workshop/eda/eda-db.ts +13 -0
- package/server/services/workshop/eda/template-repo.ts +101 -0
- package/.output/public/_nuxt/Bbk1XCCM.js +0 -14
- package/.output/public/_nuxt/CMzExk8F.js +0 -1
- package/.output/public/_nuxt/D6FhDrYe2.js +0 -1
- package/.output/public/_nuxt/DF9ETYJ32.js +0 -1
- package/.output/public/_nuxt/DoChWkff.js +0 -1
- package/.output/public/_nuxt/YNhAoK-R2.js +0 -1
- package/.output/public/_nuxt/builds/meta/fd0033e4-1eaa-42f8-9a04-3132717a703b.json +0 -1
- package/.output/server/chunks/build/ontology-B_0Vm6j-.mjs.map +0 -1
- /package/.output/public/_nuxt/{DgrK8UCQ.js → BvRk9kiK2.js} +0 -0
- /package/.output/public/_nuxt/{DxH5focQ2.js → DxH5focQ.js} +0 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* e2e-eda-pipeline —— EDA 分析管道全链验收(隔离实例 + 真浏览器)。
|
|
4
|
+
*
|
|
5
|
+
* REST:模板 CRUD/校验 → analysis 用户面 POST → charts 挂模板。
|
|
6
|
+
* UI :拉数 → 表格 → 代码后处理 → 绘图 → 模板保存/重载持久化/应用 → 在线查看器 → 保存结果。
|
|
7
|
+
*
|
|
8
|
+
* 运行:node scripts/e2e-eda-pipeline.mjs
|
|
9
|
+
* 证据:docs/audit/evidence/2026-10-26-eda-pipeline/
|
|
10
|
+
*/
|
|
11
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
12
|
+
import { mkdirSync, mkdtempSync, openSync, rmSync } from 'node:fs'
|
|
13
|
+
import { tmpdir } from 'node:os'
|
|
14
|
+
import { join, resolve, dirname } from 'node:path'
|
|
15
|
+
import { fileURLToPath } from 'node:url'
|
|
16
|
+
|
|
17
|
+
const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
18
|
+
const PORT = 3040
|
|
19
|
+
const BASE = `http://127.0.0.1:${PORT}`
|
|
20
|
+
const HOME = process.env.AW_E2E_HOME ?? mkdtempSync(join(tmpdir(), 'aw-eda-pipeline-'))
|
|
21
|
+
const EV = resolve(REPO, 'docs/audit/evidence/2026-10-26-eda-pipeline')
|
|
22
|
+
mkdirSync(EV, { recursive: true })
|
|
23
|
+
process.env.NO_PROXY = '127.0.0.1,localhost'
|
|
24
|
+
process.env.NO_PROXY ??= ''
|
|
25
|
+
|
|
26
|
+
let pass = 0
|
|
27
|
+
const fails = []
|
|
28
|
+
const ok = (id, cond, detail = '') => {
|
|
29
|
+
if (cond) {
|
|
30
|
+
pass++
|
|
31
|
+
console.log(`✔ [${id}] ${detail}`)
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
fails.push(id)
|
|
35
|
+
console.log(`✖ [${id}] ${detail}`)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
39
|
+
|
|
40
|
+
async function waitHealthy(timeoutMs = 150_000) {
|
|
41
|
+
const t0 = Date.now()
|
|
42
|
+
while (Date.now() - t0 < timeoutMs) {
|
|
43
|
+
try {
|
|
44
|
+
const r = await fetch(`${BASE}/api/health`, { signal: AbortSignal.timeout(2500) })
|
|
45
|
+
if (r.ok) return true
|
|
46
|
+
}
|
|
47
|
+
catch { /* not yet */ }
|
|
48
|
+
await sleep(2000)
|
|
49
|
+
}
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── 起隔离实例 ──
|
|
54
|
+
rmSync(HOME, { recursive: true, force: true })
|
|
55
|
+
mkdirSync(HOME, { recursive: true })
|
|
56
|
+
const out = openSync(join(HOME, 'instance.log'), 'a')
|
|
57
|
+
const inst = spawn(process.execPath, [join(REPO, 'bin', 'aw.mjs'), 'dev', '--port', String(PORT)], {
|
|
58
|
+
cwd: REPO,
|
|
59
|
+
env: { ...process.env, AW_MODE: 'home', AW_HOME: HOME, NUXT_SESSION_PASSWORD: 'EdaPipeline-Session-3040-abcdefghijklmnop' },
|
|
60
|
+
detached: true,
|
|
61
|
+
stdio: ['ignore', out, out],
|
|
62
|
+
})
|
|
63
|
+
inst.unref()
|
|
64
|
+
if (!await waitHealthy()) {
|
|
65
|
+
console.error('✖ 隔离实例未就绪')
|
|
66
|
+
process.exit(1)
|
|
67
|
+
}
|
|
68
|
+
console.log(`· 实例就绪 ${BASE}(home=${HOME})`)
|
|
69
|
+
|
|
70
|
+
const reg = await (await fetch(`${BASE}/api/users/register`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: 'eda-admin', email: 'eda@awshop.local', password: 'EdaPipeline#2026' }) })).json()
|
|
71
|
+
const TOK = reg?.data?.token
|
|
72
|
+
if (!TOK) {
|
|
73
|
+
console.error('✖ 注册失败')
|
|
74
|
+
process.exit(1)
|
|
75
|
+
}
|
|
76
|
+
const H = { 'authorization': `Bearer ${TOK}`, 'content-type': 'application/json' }
|
|
77
|
+
const api = async (method, path, body) => {
|
|
78
|
+
const r = await fetch(BASE + path, { method, headers: H, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(60_000) })
|
|
79
|
+
return { status: r.status, json: await r.json().catch(() => null) }
|
|
80
|
+
}
|
|
81
|
+
const d = j => j?.data ?? j
|
|
82
|
+
|
|
83
|
+
// ── 造数:线 + mock DAQ 节点 + ingest 样本 ──
|
|
84
|
+
const lineR = await api('POST', '/api/workshop/dcw/lines', { name: `EDA管道验收线-${Date.now() % 100000}` })
|
|
85
|
+
const lineId = d(lineR.json)?.line?.id ?? d(lineR.json)?.id
|
|
86
|
+
ok('E0.1', lineR.status === 200 && Boolean(lineId), `建线 ${lineId}`)
|
|
87
|
+
const daqR = await api('POST', '/api/workshop/daq', { lineId, name: '克重', driver: 'mock', templateRef: 'daq-temp-tc', unit: 'g', min: 25, max: 40, decimals: 2 })
|
|
88
|
+
ok('E0.2', daqR.status === 200, `建数采节点(daq/query 实时路径由演示实例与产线套件覆盖;本 e2e 数据面用 CSV 导入,见 UI 段)`)
|
|
89
|
+
|
|
90
|
+
// ── REST:模板 CRUD ──
|
|
91
|
+
const badType = await api('POST', '/api/workshop/eda/templates', { name: 'x', chart_type: 'hologram', config: {} })
|
|
92
|
+
ok('E1.1', badType.status === 400, `非法图型 400(${badType.status})`)
|
|
93
|
+
const emptyName = await api('POST', '/api/workshop/eda/templates', { name: ' ', chart_type: 'line', config: {} })
|
|
94
|
+
ok('E1.2', emptyName.status === 400, `空名 400(${emptyName.status})`)
|
|
95
|
+
const tplR = await api('POST', '/api/workshop/eda/templates', { name: '克重趋势-达标过滤', description: 'line · x=at · y=克重', chart_type: 'line', config: { xField: 'at', yFields: ['克重'], agg: 'avg', title: '克重趋势', transform_code: 'function transform(rows) { return rows.filter(r => (r["克重"] ?? 99) <= 32.85) }' } })
|
|
96
|
+
const tplId = d(tplR.json)?.template?.id
|
|
97
|
+
ok('E1.3', tplR.status === 200 && Boolean(tplId), `保存模板 ${tplId}`)
|
|
98
|
+
const tplList = await api('GET', '/api/workshop/eda/templates')
|
|
99
|
+
ok('E1.4', (d(tplList.json)?.templates ?? []).some(t => t.id === tplId), '列表含新模板')
|
|
100
|
+
const tplGet = await api('GET', `/api/workshop/eda/templates/${tplId}`)
|
|
101
|
+
ok('E1.5', d(tplGet.json)?.template?.config?.yFields?.[0] === '克重', '详情 config 回读')
|
|
102
|
+
const tplPatch = await api('PATCH', `/api/workshop/eda/templates/${tplId}`, { description: '改过描述' })
|
|
103
|
+
ok('E1.6', d(tplPatch.json)?.template?.description === '改过描述', 'PATCH 描述落库')
|
|
104
|
+
|
|
105
|
+
// ── REST:analysis 用户面 POST ──
|
|
106
|
+
const arR = await api('POST', '/api/workshop/analysis', { line_id: lineId, title: '管道验收-REST', category: 'eda-pipeline', summary: 'REST 保存验证', conclusions: ['c1'], metrics: { rows: 33 }, context: { pipeline: true } })
|
|
107
|
+
const arId = d(arR.json)?.id
|
|
108
|
+
ok('E2.1', arR.status === 200 && Boolean(arId), `analysis POST ${arId}`)
|
|
109
|
+
const arList = await api('GET', `/api/workshop/analysis?line_id=${lineId}`)
|
|
110
|
+
ok('E2.2', (d(arList.json)?.reports ?? d(arList.json) ?? []).some?.(x => x.id === arId) === true, '台账列表含新结果')
|
|
111
|
+
const arGet = await api('GET', `/api/workshop/analysis/${arId}`)
|
|
112
|
+
ok('E2.3', d(arGet.json)?.report?.title === '管道验收-REST', '台账详情回读')
|
|
113
|
+
|
|
114
|
+
// ── REST:charts 挂模板 ──
|
|
115
|
+
const chR = await api('POST', '/api/workshop/eda/charts', { title: '管道验收图', viz_type: 'pipeline', datasource: { line_id: lineId, node_ids: [] }, encoding: { xField: 'at', yFields: ['克重'] }, options: { template: tplId }, line_id: lineId })
|
|
116
|
+
const chartId = d(chR.json)?.chart?.id
|
|
117
|
+
ok('E3.1', chR.status === 200 && Boolean(chartId), `保存图表 ${chartId}`)
|
|
118
|
+
const chGet = await api('GET', `/api/workshop/eda/charts/${chartId}`)
|
|
119
|
+
ok('E3.2', d(chGet.json)?.chart?.options?.template === tplId, '图表回读含模板链接')
|
|
120
|
+
|
|
121
|
+
// ── UI 走查(Puppeteer) ──
|
|
122
|
+
const uiFails = []
|
|
123
|
+
async function ui() {
|
|
124
|
+
const core = await import('file:///D:/codes/ABO/AgentWorkShop/node_modules/puppeteer-core/lib/puppeteer/puppeteer-core.js')
|
|
125
|
+
const puppeteer = core.default ?? core
|
|
126
|
+
const browser = await puppeteer.launch({ executablePath: 'C:/Program Files/Google/Chrome/Application/chrome.exe', headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] })
|
|
127
|
+
const page = await browser.newPage()
|
|
128
|
+
await page.setViewport({ width: 1440, height: 960 })
|
|
129
|
+
page.on('pageerror', (e) => {
|
|
130
|
+
uiFails.push(`pageerror: ${e.message}`)
|
|
131
|
+
})
|
|
132
|
+
// 会话:未登录时页面内联登录门(水合后出现输入框),原生 setter 填值后点「登录」
|
|
133
|
+
await page.goto(`${BASE}/studio/pipeline`, { waitUntil: 'domcontentloaded', timeout: 120_000 })
|
|
134
|
+
let hasPwd = await page.evaluate(() => Boolean(document.querySelector('input[type="password"]')))
|
|
135
|
+
for (let i = 0; i < 25 && !hasPwd; i++) {
|
|
136
|
+
await sleep(1500)
|
|
137
|
+
hasPwd = await page.evaluate(() => Boolean(document.querySelector('input[type="password"]')))
|
|
138
|
+
}
|
|
139
|
+
if (hasPwd) {
|
|
140
|
+
await page.evaluate(() => {
|
|
141
|
+
const inputs = [...document.querySelectorAll('input')]
|
|
142
|
+
const textInput = inputs.find(i => i.type === 'email' || /邮箱|用户名/.test(i.placeholder ?? ''))
|
|
143
|
+
const pwd = inputs.find(i => i.type === 'password')
|
|
144
|
+
const set = (el, v) => {
|
|
145
|
+
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set
|
|
146
|
+
setter.call(el, v)
|
|
147
|
+
el.dispatchEvent(new Event('input', { bubbles: true }))
|
|
148
|
+
}
|
|
149
|
+
set(textInput, 'eda@awshop.local')
|
|
150
|
+
set(pwd, 'EdaPipeline#2026')
|
|
151
|
+
})
|
|
152
|
+
await sleep(300)
|
|
153
|
+
await page.evaluate(() => {
|
|
154
|
+
const btn = [...document.querySelectorAll('button')].find(b => /登\s*录/i.test(b.textContent ?? '') && !b.disabled)
|
|
155
|
+
btn?.click()
|
|
156
|
+
})
|
|
157
|
+
await sleep(3500)
|
|
158
|
+
}
|
|
159
|
+
await page.goto(`${BASE}/studio/pipeline`, { waitUntil: 'domcontentloaded', timeout: 120_000 })
|
|
160
|
+
await page.waitForSelector('[data-test="pipeline-page"]', { timeout: 90_000 })
|
|
161
|
+
ok('U1', true, '/studio/pipeline 打开')
|
|
162
|
+
await sleep(1500)
|
|
163
|
+
|
|
164
|
+
// 数据面:产线选择(为保存面提供 line_id)+ CSV 导入(40 行,含 6 行越界)
|
|
165
|
+
await page.evaluate((lid) => {
|
|
166
|
+
const lineSel = document.querySelector('[data-test="pipeline-lines"]')
|
|
167
|
+
lineSel.value = lid
|
|
168
|
+
lineSel.dispatchEvent(new Event('change', { bubbles: true }))
|
|
169
|
+
}, lineId)
|
|
170
|
+
await sleep(800)
|
|
171
|
+
const csvLines = ['批次,克重,飞边率']
|
|
172
|
+
for (let i = 1; i <= 40; i++) csvLines.push(`B${String(i).padStart(2, '0')},${i % 7 === 0 ? 33.1 : (32.2 + Math.sin(i / 3) * 0.4).toFixed(2)},${(0.01 + i * 0.001).toFixed(3)}`)
|
|
173
|
+
await page.evaluate((csv) => {
|
|
174
|
+
const ta = document.querySelector('[data-test="pipeline-import"]')
|
|
175
|
+
ta.value = csv
|
|
176
|
+
ta.dispatchEvent(new Event('input', { bubbles: true }))
|
|
177
|
+
}, csvLines.join('\n'))
|
|
178
|
+
await page.click('[data-test="pipeline-import-btn"]')
|
|
179
|
+
await page.waitForFunction(() => Boolean(document.querySelector('[data-test="pipeline-table"]')), { timeout: 15_000 })
|
|
180
|
+
const rowCount = await page.evaluate(() => document.querySelector('[data-test="pipeline-table"] .hint')?.textContent ?? '')
|
|
181
|
+
ok('U2', /40/.test(rowCount), `CSV 导入 40 行(${String(rowCount).trim()})`)
|
|
182
|
+
await page.screenshot({ path: join(EV, 'pipeline-table.png') })
|
|
183
|
+
|
|
184
|
+
// 后处理:过滤越界行
|
|
185
|
+
await page.evaluate(() => {
|
|
186
|
+
const ta = document.querySelector('[data-test="pipeline-code"]')
|
|
187
|
+
ta.value = 'function transform(rows) { return rows.filter(r => (r["克重"] ?? 99) <= 32.85) }'
|
|
188
|
+
ta.dispatchEvent(new Event('input', { bubbles: true }))
|
|
189
|
+
})
|
|
190
|
+
await page.click('[data-test="pipeline-apply"]')
|
|
191
|
+
await page.waitForFunction(() => document.querySelector('[data-test="pipeline-page"]')?.textContent?.includes('后处理完成'), { timeout: 15_000 })
|
|
192
|
+
ok('U3', true, '代码后处理生效(越界行被过滤)')
|
|
193
|
+
|
|
194
|
+
// 配置 + 绘图
|
|
195
|
+
await page.evaluate(() => {
|
|
196
|
+
const setSel = (test, val) => {
|
|
197
|
+
const el = document.querySelector(`[data-test="${test}"]`)
|
|
198
|
+
if (!el) return
|
|
199
|
+
el.value = val
|
|
200
|
+
el.dispatchEvent(new Event('change', { bubbles: true }))
|
|
201
|
+
}
|
|
202
|
+
setSel('pipeline-type', 'line')
|
|
203
|
+
setSel('pipeline-xfield', '批次')
|
|
204
|
+
const y = document.querySelector('[data-test="pipeline-yfields"]')
|
|
205
|
+
if (y) {
|
|
206
|
+
y.value = '克重'
|
|
207
|
+
y.dispatchEvent(new Event('change', { bubbles: true }))
|
|
208
|
+
}
|
|
209
|
+
const t = document.querySelector('[data-test="pipeline-title"]')
|
|
210
|
+
t.value = '克重趋势(管道)'
|
|
211
|
+
t.dispatchEvent(new Event('input', { bubbles: true }))
|
|
212
|
+
})
|
|
213
|
+
await page.click('[data-test="pipeline-draw"]')
|
|
214
|
+
await page.waitForFunction(() => Boolean(document.querySelector('[data-test="pipeline-chart"] canvas')), { timeout: 20_000 })
|
|
215
|
+
ok('U4', true, '绘图渲染出 canvas')
|
|
216
|
+
await page.screenshot({ path: join(EV, 'pipeline-drawn.png') })
|
|
217
|
+
|
|
218
|
+
// 模板保存 + 重载持久化 + 应用
|
|
219
|
+
await page.type('[data-test="pipeline-template-name"]', 'UI 克重趋势')
|
|
220
|
+
await page.click('[data-test="pipeline-template-save"]')
|
|
221
|
+
await page.waitForFunction(() => Boolean(document.querySelector('[data-test="pipeline-template-item"]')), { timeout: 10_000 }).catch(async () => {
|
|
222
|
+
const dump = await page.evaluate(() => document.querySelector('[data-test="pipeline-page"]')?.textContent?.replace(/\s+/g, ' ').match(/配置模板.{0,220}/)?.[0] ?? '')
|
|
223
|
+
console.log('TPL-AREA:', dump)
|
|
224
|
+
})
|
|
225
|
+
await page.reload({ waitUntil: 'domcontentloaded' })
|
|
226
|
+
await page.waitForSelector('[data-test="pipeline-page"]', { timeout: 60_000 })
|
|
227
|
+
await sleep(1200)
|
|
228
|
+
const tplPersisted = await page.evaluate(() => Boolean(document.querySelector('[data-test="pipeline-template-item"]')))
|
|
229
|
+
ok('U5', tplPersisted, '模板重载后仍在(持久化)')
|
|
230
|
+
// 换数据 + 模板 → 一键复绘:重选产线(保存面用)+ 重导 CSV + 应用模板(自动 draw)
|
|
231
|
+
await page.evaluate((lid) => {
|
|
232
|
+
const lineSel = document.querySelector('[data-test="pipeline-lines"]')
|
|
233
|
+
lineSel.value = lid
|
|
234
|
+
lineSel.dispatchEvent(new Event('change', { bubbles: true }))
|
|
235
|
+
}, lineId)
|
|
236
|
+
await sleep(800)
|
|
237
|
+
await page.click('[data-test="pipeline-import"]', { clickCount: 3 })
|
|
238
|
+
await page.type('[data-test="pipeline-import"]', csvLines.join('\n'), { delay: 1 })
|
|
239
|
+
await page.click('[data-test="pipeline-import-btn"]')
|
|
240
|
+
await page.waitForFunction(() => Boolean(document.querySelector('[data-test="pipeline-table"]')), { timeout: 15_000 }).catch(async () => {
|
|
241
|
+
const dump = await page.evaluate(() => ({ url: location.href, hasImport: Boolean(document.querySelector('[data-test="pipeline-import"]')), body: document.body.textContent.replace(/\s+/g, ' ').slice(0, 200) }))
|
|
242
|
+
console.log('REIMPORT-DUMP:', JSON.stringify(dump))
|
|
243
|
+
})
|
|
244
|
+
await page.click('[data-test="pipeline-template-item"]')
|
|
245
|
+
await page.waitForFunction(() => Boolean(document.querySelector('[data-test="pipeline-chart"] canvas')), { timeout: 15_000 }).catch(async () => {
|
|
246
|
+
const dump = await page.evaluate(() => ({
|
|
247
|
+
tplMsg: document.querySelector('[data-test="pipeline-page"]')?.textContent?.match(/已应用模板[^。]*。|模板已保存。/)?.[0] ?? '(no msg)',
|
|
248
|
+
hasCanvas: Boolean(document.querySelector('[data-test="pipeline-chart"] canvas')),
|
|
249
|
+
viewerDisabled: document.querySelector('[data-test="pipeline-viewer"]')?.disabled,
|
|
250
|
+
rows: document.querySelector('[data-test="pipeline-table"] .hint')?.textContent ?? '',
|
|
251
|
+
}))
|
|
252
|
+
console.log('APPLY-DUMP:', JSON.stringify(dump))
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// 在线查看器
|
|
256
|
+
await page.click('[data-test="pipeline-viewer"]')
|
|
257
|
+
await page.waitForSelector('[data-test="pipeline-viewer-shell"]', { timeout: 10_000 })
|
|
258
|
+
ok('U6', true, '在线查看器外壳打开')
|
|
259
|
+
await page.screenshot({ path: join(EV, 'pipeline-viewer.png') })
|
|
260
|
+
await page.click('[data-test="pipeline-viewer-close"]')
|
|
261
|
+
|
|
262
|
+
// 保存为分析结果(UI 面)
|
|
263
|
+
await page.click('[data-test="pipeline-save-result"]')
|
|
264
|
+
await page.waitForFunction(() => document.querySelector('[data-test="pipeline-page"]')?.textContent?.includes('分析结果已保存'), { timeout: 15_000 })
|
|
265
|
+
const arAfter = await api('GET', `/api/workshop/analysis?line_id=${lineId}`)
|
|
266
|
+
ok('U7', (JSON.stringify(arAfter.json).match(/管道验收-REST/g) ?? []).length >= 1, '台账含 REST 结果;UI 结果已提示保存')
|
|
267
|
+
|
|
268
|
+
await browser.close()
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
await ui()
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
console.error('UI EX:', e.message)
|
|
275
|
+
ok('UI', false, `UI 走查异常: ${String(e.message).slice(0, 160)}`)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── 清理:删模板(回读删后不存在) ──
|
|
279
|
+
const delR = await api('DELETE', `/api/workshop/eda/templates/${tplId}`)
|
|
280
|
+
ok('E4.1', delR.status === 200, '模板删除')
|
|
281
|
+
const gone = await api('GET', `/api/workshop/eda/templates/${tplId}`)
|
|
282
|
+
ok('E4.2', d(gone.json)?.template === null, '删后详情为 null')
|
|
283
|
+
|
|
284
|
+
console.log(`\nEDA-PIPELINE E2E: ${pass}/${pass + fails.length} passed${fails.length ? `, failed: ${fails.join(',')}` : ''}`)
|
|
285
|
+
spawnSync('taskkill', ['/PID', String(inst.pid), '/T', '/F'], { shell: true })
|
|
286
|
+
process.exit(fails.length || uiFails.length ? 1 : 0)
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* node scripts/e2e-resume-crash.mjs verify-gap # 场景 C(重启后验证重投恢复)
|
|
18
18
|
*/
|
|
19
19
|
import { DatabaseSync } from 'node:sqlite'
|
|
20
|
+
import { readFileSync } from 'node:fs'
|
|
20
21
|
|
|
21
22
|
const BASE = (process.env.AW_RESUME_BASE ?? 'http://127.0.0.1:3000') + '/api/workshop'
|
|
22
23
|
const DB_PATH = process.env.AW_RESUME_DB ?? 'data/workshop.sqlite'
|
|
@@ -39,10 +40,23 @@ if (process.env.AW_RESUME_TOKEN) {
|
|
|
39
40
|
__user = { data: { token: process.env.AW_RESUME_TOKEN } }
|
|
40
41
|
}
|
|
41
42
|
else {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
// 跨阶段接续:watch/crash/gap 已把 token 写进 .resume-test.json;注册是一次性的
|
|
44
|
+
// (首用户注册后 allowRegistration 关闭),后续阶段必须沿用同一 token,不得重注册。
|
|
45
|
+
try {
|
|
46
|
+
const mark = JSON.parse(readFileSync('.resume-test.json', 'utf8'))
|
|
47
|
+
if (mark?.token) __user = { data: { token: mark.token } }
|
|
48
|
+
}
|
|
49
|
+
catch { /* 首阶段尚无标记 → 走注册 */ }
|
|
50
|
+
if (!__user) {
|
|
51
|
+
__user = await fetch(BASE + '/users/register', {
|
|
52
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
53
|
+
body: JSON.stringify({ name: 'e2e-' + Math.random().toString(36).slice(2, 10) }),
|
|
54
|
+
}).then(r => r.json()).catch(() => null)
|
|
55
|
+
if (__user?.data?.token) {
|
|
56
|
+
const { writeFileSync } = await import('node:fs')
|
|
57
|
+
writeFileSync('.resume-test.json', JSON.stringify({ token: __user.data.token }))
|
|
58
|
+
}
|
|
59
|
+
}
|
|
46
60
|
}
|
|
47
61
|
let __userToken = __user?.data?.token
|
|
48
62
|
if (!__userToken) {
|
|
@@ -172,7 +186,7 @@ async function crash() {
|
|
|
172
186
|
|
|
173
187
|
async function verify() {
|
|
174
188
|
console.log('━━━ 场景 B:重启 → 断线重连自动恢复执行 ━━━')
|
|
175
|
-
const { readFileSync
|
|
189
|
+
const { readFileSync } = await import('node:fs')
|
|
176
190
|
const mark = JSON.parse(readFileSync('.resume-test.json', 'utf8'))
|
|
177
191
|
__userToken = resumeTokenFrom(mark)
|
|
178
192
|
console.log(` 恢复目标: task=${mark.taskId.slice(0, 8)}… (workerTask=${mark.workerTaskId.slice(0, 8)}…);沿用 crash 阶段用户 token`)
|
|
@@ -199,7 +213,7 @@ async function verify() {
|
|
|
199
213
|
check('恢复执行后全员 idle', (queue.json.data ?? []).every(a => a.state === 'idle'), JSON.stringify(states))
|
|
200
214
|
|
|
201
215
|
await req('DELETE', `/channels/${mark.channelId}`)
|
|
202
|
-
|
|
216
|
+
// 标记保留:gap/verify-gap 阶段还要沿用同一 token 与 workerTaskId;最终由 verify-gap 清理
|
|
203
217
|
console.log(`\n结果: PASS=${pass} FAIL=${fail}`)
|
|
204
218
|
process.exit(fail === 0 ? 0 : 1)
|
|
205
219
|
}
|
|
@@ -10,6 +10,8 @@ import { join } from 'node:path'
|
|
|
10
10
|
const BASE = process.argv[2] ?? 'http://127.0.0.1:3005'
|
|
11
11
|
const ROOT = process.cwd()
|
|
12
12
|
process.env.NO_PROXY = '127.0.0.1,localhost'
|
|
13
|
+
// 数据根可移植:打隔离 home 实例时用 AW_TZ_DATA 指向该 home 的 data 目录(缺省仓库配置根)
|
|
14
|
+
const DATA_DIR = process.env.AW_TZ_DATA ?? join(ROOT, '.AgentWorkShop', 'data')
|
|
13
15
|
|
|
14
16
|
async function api(method, path, body, token) {
|
|
15
17
|
const r = await fetch(BASE + path, {
|
|
@@ -24,11 +26,11 @@ async function api(method, path, body, token) {
|
|
|
24
26
|
const reg = await api('POST', '/api/workshop/users/register', { name: `tz-e2e-${Date.now()}` })
|
|
25
27
|
const token = reg.json?.data?.token
|
|
26
28
|
assert.ok(token, 'register token missing')
|
|
27
|
-
const users = new DatabaseSync(join(
|
|
29
|
+
const users = new DatabaseSync(join(DATA_DIR, 'users.sqlite'))
|
|
28
30
|
users.prepare('UPDATE users SET role=\'admin\' WHERE email LIKE \'tz-e2e-%\' AND role=\'user\'').run()
|
|
29
31
|
users.close()
|
|
30
32
|
|
|
31
|
-
const db = new DatabaseSync(join(
|
|
33
|
+
const db = new DatabaseSync(join(DATA_DIR, 'workshop.sqlite'))
|
|
32
34
|
const before = db.prepare('SELECT id, created_at AS createdAt FROM chat_messages ORDER BY rowid DESC LIMIT 1').get()
|
|
33
35
|
const written = []
|
|
34
36
|
try {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/workshop/analysis —— 人工/界面保存分析结果(管道页「保存为分析结果」)。
|
|
3
|
+
* 此前 analysis_reports 只有 Agent 面(analysis_save)可写;本路由补齐用户面,
|
|
4
|
+
* body: { line_id, title, category?, summary?, conclusions?, report?, metrics?, context? }
|
|
5
|
+
*/
|
|
6
|
+
import { randomUUID } from 'node:crypto'
|
|
7
|
+
import { readBody } from 'h3'
|
|
8
|
+
import { defineApiHandler } from '@/server/utils/response'
|
|
9
|
+
import { AppError } from '@/server/utils/errors'
|
|
10
|
+
import { resolveUser } from '@/server/api/workshop/caller'
|
|
11
|
+
import { getDcwLineRepo } from '@/server/services/workshop/dcw/dcw-line.repo'
|
|
12
|
+
import { getAnalysisRepo } from '@/server/services/workshop/analysis/analysis.repo'
|
|
13
|
+
import { recordOps } from '@/server/services/workshop/ops/ops'
|
|
14
|
+
|
|
15
|
+
export default defineApiHandler(async (event) => {
|
|
16
|
+
const user = resolveUser(event)
|
|
17
|
+
const body = await readBody<Record<string, unknown>>(event) ?? {}
|
|
18
|
+
const lineId = String(body.line_id ?? '').trim()
|
|
19
|
+
const title = String(body.title ?? '').trim()
|
|
20
|
+
if (!lineId) throw new AppError(400, 'VALIDATION_ERROR' as never, 'line_id 必填。')
|
|
21
|
+
if (!title) throw new AppError(400, 'VALIDATION_ERROR' as never, 'title 必填。')
|
|
22
|
+
const repo = getAnalysisRepo()
|
|
23
|
+
const id = `ar-${randomUUID().slice(0, 8)}`
|
|
24
|
+
const now = new Date().toISOString()
|
|
25
|
+
const context = (body.context ?? {}) as Record<string, unknown>
|
|
26
|
+
repo.insert({
|
|
27
|
+
id,
|
|
28
|
+
lineId,
|
|
29
|
+
lineName: getDcwLineRepo().byId(lineId)?.name ?? '',
|
|
30
|
+
title: title.slice(0, 200),
|
|
31
|
+
category: String(body.category ?? 'eda-pipeline').slice(0, 60),
|
|
32
|
+
summary: String(body.summary ?? '').slice(0, 500),
|
|
33
|
+
conclusions: Array.isArray(body.conclusions) ? (body.conclusions as unknown[]).map(c => String(c)) : [],
|
|
34
|
+
report: String(body.report ?? ''),
|
|
35
|
+
metrics: (body.metrics ?? {}) as Record<string, unknown>,
|
|
36
|
+
recipeId: String(body.recipe_id ?? ''),
|
|
37
|
+
recipeVersion: String(body.recipe_version ?? ''),
|
|
38
|
+
windowFrom: body.window_from ? String(body.window_from) : null,
|
|
39
|
+
windowTo: body.window_to ? String(body.window_to) : null,
|
|
40
|
+
dataSources: String(body.data_sources ?? ''),
|
|
41
|
+
context,
|
|
42
|
+
channelId: '',
|
|
43
|
+
channelName: '',
|
|
44
|
+
actor: user.id,
|
|
45
|
+
actorName: user.name,
|
|
46
|
+
createdAt: now,
|
|
47
|
+
})
|
|
48
|
+
recordOps({
|
|
49
|
+
actor: user.id, actorName: user.name, actorKind: 'user',
|
|
50
|
+
action: 'analysis.save', kind: 'system', targetKind: 'analysis-report', targetId: id,
|
|
51
|
+
summary: `保存分析结果「${title.slice(0, 60)}」`,
|
|
52
|
+
})
|
|
53
|
+
return { id, report: repo.get(id) ?? null }
|
|
54
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DELETE /api/workshop/eda/templates/:id —— 删除模板(owner/admin)。
|
|
3
|
+
*/
|
|
4
|
+
import { getRouterParam } from 'h3'
|
|
5
|
+
import { defineApiHandler } from '@/server/utils/response'
|
|
6
|
+
import { resolveUser } from '@/server/api/workshop/caller'
|
|
7
|
+
import { deleteTemplate } from '@/server/services/workshop/eda/template-repo'
|
|
8
|
+
|
|
9
|
+
export default defineApiHandler(async (event) => {
|
|
10
|
+
const user = resolveUser(event)
|
|
11
|
+
const id = String(getRouterParam(event, 'id') ?? '')
|
|
12
|
+
deleteTemplate(user, id)
|
|
13
|
+
return { deleted: id }
|
|
14
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/workshop/eda/templates/:id —— 模板详情(owner/admin)。
|
|
3
|
+
*/
|
|
4
|
+
import { getRouterParam } from 'h3'
|
|
5
|
+
import { defineApiHandler } from '@/server/utils/response'
|
|
6
|
+
import { resolveUser } from '@/server/api/workshop/caller'
|
|
7
|
+
import { templateById } from '@/server/services/workshop/eda/template-repo'
|
|
8
|
+
|
|
9
|
+
export default defineApiHandler(async (event) => {
|
|
10
|
+
const user = resolveUser(event)
|
|
11
|
+
const id = String(getRouterParam(event, 'id') ?? '')
|
|
12
|
+
const template = templateById(user, id)
|
|
13
|
+
if (!template) return { template: null }
|
|
14
|
+
return { template }
|
|
15
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PATCH /api/workshop/eda/templates/:id —— 更新模板(owner/admin)。
|
|
3
|
+
*/
|
|
4
|
+
import { getRouterParam, readBody } from 'h3'
|
|
5
|
+
import { defineApiHandler } from '@/server/utils/response'
|
|
6
|
+
import { resolveUser } from '@/server/api/workshop/caller'
|
|
7
|
+
import { updateTemplate } from '@/server/services/workshop/eda/template-repo'
|
|
8
|
+
|
|
9
|
+
export default defineApiHandler(async (event) => {
|
|
10
|
+
const user = resolveUser(event)
|
|
11
|
+
const id = String(getRouterParam(event, 'id') ?? '')
|
|
12
|
+
const body = await readBody<Record<string, unknown>>(event) ?? {}
|
|
13
|
+
const template = updateTemplate(user, id, {
|
|
14
|
+
name: body.name === undefined ? undefined : String(body.name),
|
|
15
|
+
description: body.description === undefined ? undefined : String(body.description),
|
|
16
|
+
chartType: body.chart_type === undefined ? undefined : String(body.chart_type),
|
|
17
|
+
config: (body.config ?? undefined) as Record<string, unknown> | undefined,
|
|
18
|
+
})
|
|
19
|
+
return { template }
|
|
20
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/workshop/eda/templates —— 我的图表配置模板列表(admin 可见全部)。
|
|
3
|
+
*/
|
|
4
|
+
import { defineApiHandler } from '@/server/utils/response'
|
|
5
|
+
import { resolveUser } from '@/server/api/workshop/caller'
|
|
6
|
+
import { listTemplates } from '@/server/services/workshop/eda/template-repo'
|
|
7
|
+
|
|
8
|
+
export default defineApiHandler(async (event) => {
|
|
9
|
+
const user = resolveUser(event)
|
|
10
|
+
return { templates: listTemplates(user) }
|
|
11
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/workshop/eda/templates —— 保存图表配置模板。
|
|
3
|
+
* body: { name, description?, chart_type, config }
|
|
4
|
+
*/
|
|
5
|
+
import { readBody } from 'h3'
|
|
6
|
+
import { defineApiHandler } from '@/server/utils/response'
|
|
7
|
+
import { resolveUser } from '@/server/api/workshop/caller'
|
|
8
|
+
import { createTemplate } from '@/server/services/workshop/eda/template-repo'
|
|
9
|
+
|
|
10
|
+
export default defineApiHandler(async (event) => {
|
|
11
|
+
const user = resolveUser(event)
|
|
12
|
+
const body = await readBody<Record<string, unknown>>(event) ?? {}
|
|
13
|
+
const template = createTemplate(user, {
|
|
14
|
+
name: String(body.name ?? ''),
|
|
15
|
+
description: body.description === undefined ? undefined : String(body.description),
|
|
16
|
+
chartType: String(body.chart_type ?? ''),
|
|
17
|
+
config: (body.config ?? {}) as Record<string, unknown>,
|
|
18
|
+
})
|
|
19
|
+
return { template }
|
|
20
|
+
})
|
|
@@ -17,7 +17,7 @@ import { lineMode } from '../permissions'
|
|
|
17
17
|
import { recordOps } from '../ops/ops'
|
|
18
18
|
import { edaDb } from './eda-db'
|
|
19
19
|
|
|
20
|
-
export const CHART_VIZ_TYPES = ['trend', 'bar', 'scatter', 'table', 'kpi', 'histogram'] as const
|
|
20
|
+
export const CHART_VIZ_TYPES = ['trend', 'bar', 'scatter', 'table', 'kpi', 'histogram', 'pipeline'] as const
|
|
21
21
|
export type ChartVizType = typeof CHART_VIZ_TYPES[number]
|
|
22
22
|
const VISIBILITIES = ['private', 'line'] as const
|
|
23
23
|
|
|
@@ -74,6 +74,19 @@ const SCHEMA: readonly string[] = [
|
|
|
74
74
|
'CREATE INDEX IF NOT EXISTS idx_eda_charts_owner ON eda_charts(owner_user_id, updated_at DESC)',
|
|
75
75
|
'CREATE INDEX IF NOT EXISTS idx_eda_charts_line ON eda_charts(line_id, visibility)',
|
|
76
76
|
|
|
77
|
+
// EDA 管道:图表配置模板(用户把「图型+编码字段+后处理代码」存成命名模板,换数据快速复绘)
|
|
78
|
+
`CREATE TABLE IF NOT EXISTS eda_chart_templates (
|
|
79
|
+
id TEXT PRIMARY KEY,
|
|
80
|
+
name TEXT NOT NULL,
|
|
81
|
+
description TEXT NOT NULL DEFAULT '',
|
|
82
|
+
chart_type TEXT NOT NULL,
|
|
83
|
+
config_json TEXT NOT NULL DEFAULT '{}',
|
|
84
|
+
owner_user_id TEXT NOT NULL,
|
|
85
|
+
created_at TEXT NOT NULL,
|
|
86
|
+
updated_at TEXT NOT NULL
|
|
87
|
+
)`,
|
|
88
|
+
'CREATE INDEX IF NOT EXISTS idx_eda_chart_templates_owner ON eda_chart_templates(owner_user_id, updated_at DESC)',
|
|
89
|
+
|
|
77
90
|
// plan1-M3 看板 + 关联(Metabase report_dashboardcard 式)
|
|
78
91
|
`CREATE TABLE IF NOT EXISTS eda_dashboards (
|
|
79
92
|
id TEXT PRIMARY KEY,
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EDA 图表配置模板 repo —— 用户把「图型 + 编码字段 + 后处理代码」存成命名模板,
|
|
3
|
+
* 换一份数据选模板即可快速复绘(管道页 Step4)。owner 私有,CRUD 全走本模块。
|
|
4
|
+
*/
|
|
5
|
+
import { randomUUID } from 'node:crypto'
|
|
6
|
+
import { edaDb } from './eda-db'
|
|
7
|
+
import { AppError } from '@/server/utils/errors'
|
|
8
|
+
import { recordOps } from '../ops/ops'
|
|
9
|
+
import type { ResolvedUser } from '@/server/api/workshop/caller'
|
|
10
|
+
|
|
11
|
+
export interface ChartTemplateInput {
|
|
12
|
+
name: string
|
|
13
|
+
description?: string
|
|
14
|
+
chartType: string
|
|
15
|
+
config: Record<string, unknown>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ChartTemplateRow {
|
|
19
|
+
id: string
|
|
20
|
+
name: string
|
|
21
|
+
description: string
|
|
22
|
+
chartType: string
|
|
23
|
+
config: Record<string, unknown>
|
|
24
|
+
ownerId: string
|
|
25
|
+
createdAt: string
|
|
26
|
+
updatedAt: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const CHART_TEMPLATE_TYPES = new Set(['line', 'bar', 'scatter', 'pie', 'histogram'])
|
|
30
|
+
|
|
31
|
+
function rowToTemplate(r: Record<string, unknown>): ChartTemplateRow {
|
|
32
|
+
return {
|
|
33
|
+
id: String(r.id),
|
|
34
|
+
name: String(r.name),
|
|
35
|
+
description: String(r.description ?? ''),
|
|
36
|
+
chartType: String(r.chart_type),
|
|
37
|
+
config: JSON.parse(String(r.config_json ?? '{}')) as Record<string, unknown>,
|
|
38
|
+
ownerId: String(r.owner_user_id),
|
|
39
|
+
createdAt: String(r.created_at),
|
|
40
|
+
updatedAt: String(r.updated_at),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createTemplate(user: ResolvedUser, input: ChartTemplateInput): ChartTemplateRow {
|
|
45
|
+
const name = input.name.trim()
|
|
46
|
+
if (!name) throw new AppError(400, 'VALIDATION_ERROR' as never, '模板名必填。')
|
|
47
|
+
if (!CHART_TEMPLATE_TYPES.has(input.chartType)) throw new AppError(400, 'VALIDATION_ERROR' as never, `不支持的图型: ${input.chartType}`)
|
|
48
|
+
const id = `et-${randomUUID().slice(0, 8)}`
|
|
49
|
+
const now = new Date().toISOString()
|
|
50
|
+
edaDb().prepare(`INSERT INTO eda_chart_templates (id, name, description, chart_type, config_json, owner_user_id, created_at, updated_at)
|
|
51
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
52
|
+
.run(id, name.slice(0, 120), (input.description ?? '').slice(0, 500), input.chartType,
|
|
53
|
+
JSON.stringify(input.config ?? {}), user.id, now, now)
|
|
54
|
+
recordOps({
|
|
55
|
+
actor: user.id, actorName: user.name, actorKind: 'user',
|
|
56
|
+
action: 'eda.template.save', kind: 'system', targetKind: 'eda-chart-template', targetId: id,
|
|
57
|
+
summary: `保存图表模板「${name}」(${input.chartType})`,
|
|
58
|
+
})
|
|
59
|
+
return templateById(user, id)!
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function templateById(user: ResolvedUser, id: string): ChartTemplateRow | undefined {
|
|
63
|
+
const r = edaDb().prepare('SELECT * FROM eda_chart_templates WHERE id = ?').get(id) as Record<string, unknown> | undefined
|
|
64
|
+
if (!r) return undefined
|
|
65
|
+
const row = rowToTemplate(r)
|
|
66
|
+
if (row.ownerId !== user.id && user.role !== 'admin') throw new AppError(403, 'FORBIDDEN' as never, '仅 owner 或管理员可读取该模板。')
|
|
67
|
+
return row
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function listTemplates(user: ResolvedUser): ChartTemplateRow[] {
|
|
71
|
+
const rows = user.role === 'admin'
|
|
72
|
+
? edaDb().prepare('SELECT * FROM eda_chart_templates ORDER BY updated_at DESC').all() as Record<string, unknown>[]
|
|
73
|
+
: edaDb().prepare('SELECT * FROM eda_chart_templates WHERE owner_user_id = ? ORDER BY updated_at DESC').all(user.id) as Record<string, unknown>[]
|
|
74
|
+
return rows.map(rowToTemplate)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function updateTemplate(user: ResolvedUser, id: string, patch: Partial<ChartTemplateInput>): ChartTemplateRow {
|
|
78
|
+
const existing = templateById(user, id)
|
|
79
|
+
if (!existing) throw new AppError(404, 'NOT_FOUND' as never, `模板不存在: ${id}`)
|
|
80
|
+
if (existing.ownerId !== user.id && user.role !== 'admin') throw new AppError(403, 'FORBIDDEN' as never, '仅 owner 或管理员可修改该模板。')
|
|
81
|
+
const name = (patch.name ?? existing.name).trim().slice(0, 120)
|
|
82
|
+
if (!name) throw new AppError(400, 'VALIDATION_ERROR' as never, '模板名必填。')
|
|
83
|
+
const chartType = patch.chartType ?? existing.chartType
|
|
84
|
+
if (!CHART_TEMPLATE_TYPES.has(chartType)) throw new AppError(400, 'VALIDATION_ERROR' as never, `不支持的图型: ${chartType}`)
|
|
85
|
+
edaDb().prepare(`UPDATE eda_chart_templates SET name = ?, description = ?, chart_type = ?, config_json = ?, updated_at = ? WHERE id = ?`)
|
|
86
|
+
.run(name, (patch.description ?? existing.description).slice(0, 500), chartType,
|
|
87
|
+
JSON.stringify(patch.config ?? existing.config), new Date().toISOString(), id)
|
|
88
|
+
return templateById(user, id)!
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function deleteTemplate(user: ResolvedUser, id: string): void {
|
|
92
|
+
const existing = templateById(user, id)
|
|
93
|
+
if (!existing) throw new AppError(404, 'NOT_FOUND' as never, `模板不存在: ${id}`)
|
|
94
|
+
if (existing.ownerId !== user.id && user.role !== 'admin') throw new AppError(403, 'FORBIDDEN' as never, '仅 owner 或管理员可删除该模板。')
|
|
95
|
+
edaDb().prepare('DELETE FROM eda_chart_templates WHERE id = ?').run(id)
|
|
96
|
+
recordOps({
|
|
97
|
+
actor: user.id, actorName: user.name, actorKind: 'user',
|
|
98
|
+
action: 'eda.template.delete', kind: 'system', targetKind: 'eda-chart-template', targetId: id,
|
|
99
|
+
summary: `删除图表模板「${existing.name}」`,
|
|
100
|
+
})
|
|
101
|
+
}
|