helixlife-v5-cli 1.2.8 → 1.2.9

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 (46) hide show
  1. package/README.md +33 -58
  2. package/helix-cli.js +7 -2
  3. package/lib/.cli.runtime.generated.json +10 -1
  4. package/lib/automation-file-chooser-guard.js +182 -0
  5. package/lib/automation-overlay.init.js +332 -68
  6. package/lib/browser-css-cache-guard.init-page.js +49 -0
  7. package/lib/browser-window-maximize.init-page.js +64 -0
  8. package/lib/cli.js +316 -14
  9. package/lib/helix-reload-bypass-cache.run-code.js +7 -0
  10. package/lib/helix-styles-guard.js +77 -0
  11. package/lib/hide-nested-skills.js +120 -0
  12. package/lib/native-mode-patch.js +288 -0
  13. package/lib/postinstall.js +39 -0
  14. package/lib/profile-lock-guard.js +187 -0
  15. package/lib/runtime-config.js +90 -0
  16. package/lib/token-file.js +189 -0
  17. package/lib/visual-page-wrap.js +311 -0
  18. package/lib/visual.js +255 -86
  19. package/package.json +9 -21
  20. package/scripts/README.md +81 -0
  21. package/scripts/{analysis-detail-upload-file.js → analysis/detail-upload-file.js} +1 -1
  22. package/scripts/{jigsaw-inspect-dom.js → analysis/jigsaw-inspect-dom.js} +15 -15
  23. package/scripts/{jigsaw-verify-enumerate.js → analysis/jigsaw-verify-enumerate.js} +1 -1
  24. package/scripts/common/.gitkeep +0 -0
  25. package/scripts/{chrome-pdf-enumerate-a11y.js → edu/chrome-pdf-enumerate-a11y.js} +1 -1
  26. package/scripts/{chrome-pdf-goto-page.js → edu/chrome-pdf-goto-page.js} +1 -1
  27. package/scripts/{chrome-pdf-zoom-out.js → edu/chrome-pdf-zoom-out.js} +1 -1
  28. package/scripts/{hover-partial-chapter-match.js → edu/hover-partial-chapter-match.js} +1 -1
  29. package/scripts/maintainer/strip-extra-skills.ps1 +27 -0
  30. package/scripts/workbench/jova/outline-edit-root-title.js +33 -0
  31. package/scripts/workbench/jova/outline-keyword-edit.js +122 -0
  32. package/scripts/workbench/jova/outline-ops.js +344 -0
  33. package/scripts/workbench/jova/outline-probe-hover.js +56 -0
  34. package/scripts/workbench/jova/proofread-ai-rewrite-section.js +94 -0
  35. package/scripts/workbench/jova/proofread-ops.js +261 -0
  36. package/scripts/workbench/jova/writing-ops.js +376 -0
  37. package/scripts/workbench/jova/writing-proofread-abstract.js +103 -0
  38. package/lib/cli.runtime.config.json +0 -5
  39. package/references/vip.helixlife.cn.site-ops.md +0 -1469
  40. package/scripts/workbench-jova-outline-regenerate-node.js +0 -173
  41. package/scripts/workbench-jova-proofread-section.js +0 -262
  42. package/scripts/workbench-jova-writing-tool.js +0 -308
  43. /package/scripts/{analysis-detail-upload-both.js → analysis/detail-upload-both.js} +0 -0
  44. /package/scripts/{jigsaw-verify-bio-tools.js → analysis/jigsaw-verify-bio-tools.js} +0 -0
  45. /package/scripts/{jigsaw-verify-drag-tool.js → analysis/jigsaw-verify-drag-tool.js} +0 -0
  46. /package/scripts/{jigsaw-verify-reference-lines.js → analysis/jigsaw-verify-reference-lines.js} +0 -0
@@ -0,0 +1,376 @@
1
+ /**
2
+ * 综述工作台 · 初稿精修(/workbench/jova/writing)· 全流程标准化脚本
3
+ *
4
+ * 运行(须已在初稿精修页且全文已生成完毕):
5
+ * helixlife-v5-cli run-code --filename=scripts/workbench/jova/writing-ops.js
6
+ *
7
+ * 默认 probeOnly:结构探测 + 只读验收(不调用 AI 生成、不替换原文)。
8
+ * 在 CONFIG 中开启对应开关后,可执行单工具生成 / 复制 / 插入 / 替换 / 文献查询等写操作。
9
+ *
10
+ * 覆盖链路(与用户规格 100% 对齐,2026-06-11 有头会话实测):
11
+ * 0. 页面结构:左目录 · 中央 Slate 富文本 · 右侧功能栏(9 项)
12
+ * 1. 触发顺序 A:先选文本 → 后点功能
13
+ * 2. 触发顺序 B:先点功能 → 后选文本(选中内容自动填入「原文内容」,验收「总字数:N」且 N>0)
14
+ * 3. 通用功能(校对/润色/降重/翻译/改写/扩写/缩写):原文内容 + 一键生成 + 生成结果 + 一键复制/插入下方/替换原文
15
+ * 4. 文风:通用组件 + 参考文风(placeholder「请输入参考文风」)必填;空提交 toast「参考文风 是必填项」
16
+ * 5. 文献列表:面板 Tab「文献列表|查询文献」+ 顶栏「检查文献|下载|close」
17
+ * 6. 查询文献:原文内容 + 查阅文献 + 生成结果 + 一键复制/替换原文/重校文献序号(线上 aria 文案)
18
+ * 7. 二次确认:替换原文 Popover「是否将本次生成结果替换原文?」;重校 Popover「是否进行全文序号校对?」
19
+ * 8. 底栏:返回提纲 / 修订模式 / 新建论文 / 下载文档
20
+ */
21
+ async page => {
22
+ const CONFIG = {
23
+ /** 左侧目录探测项 */
24
+ navProbe: '1.1 棕榈酰化的生化基础和调控机制',
25
+ /** 中间内容区选文片段(部分匹配) */
26
+ selectPhrase: '蛋白质脂化作为关键的翻译后修饰',
27
+ /** 触发顺序:'A' 先选后点 | 'B' 先点后选 */
28
+ triggerOrder: 'A',
29
+ /** 默认只探测,不写稿 */
30
+ probeOnly: true,
31
+
32
+ /** 通用工具探测列表(probeOnly 时逐项打开面板验收结构) */
33
+ generalTools: ['校对', '润色', '降重', '翻译', '改写', '扩写', '缩写'],
34
+
35
+ /** 写操作:指定单个通用工具并生成(probeOnly=false 时生效) */
36
+ generalTool: '校对',
37
+ runGenerate: false,
38
+ runCopy: false,
39
+ runInsertBelow: false,
40
+ runReplace: false,
41
+ runReplaceConfirm: false,
42
+
43
+ /** 文风 */
44
+ refStyleText: '学术综述风格,客观严谨',
45
+ probeStyleTool: true,
46
+ runStyleGenerate: false,
47
+
48
+ /** 文献列表 / 查询文献 */
49
+ probeRefPanel: true,
50
+ runRefQuery: false,
51
+ runRefConsult: false,
52
+ runRefReindex: false,
53
+ runRefReindexConfirm: false,
54
+
55
+ generateTimeoutMs: 120000,
56
+ };
57
+
58
+ const wait = ms => page.waitForTimeout(ms);
59
+ const pathname = await page.evaluate(() => location.pathname);
60
+ if (pathname !== '/workbench/jova/writing') {
61
+ return { ok: false, reason: '当前不在初稿精修页', pathname };
62
+ }
63
+
64
+ const GENERAL_TOOLS = ['校对', '文风', '润色', '降重', '翻译', '改写', '扩写', '缩写'];
65
+ const REPLACE_CONFIRM_TEXT = '是否将本次生成结果替换原文?';
66
+ const REINDEX_CONFIRM_SNIPPET = '是否进行全文序号校对';
67
+
68
+ const editor = () => page.locator('main [role=textbox]').first();
69
+ const sidebarTool = label => page.locator('main').getByText(label, { exact: true }).last();
70
+ const closePanel = async () => {
71
+ const close = page.getByRole('img', { name: 'close' });
72
+ if ((await close.count()) > 0) {
73
+ await close.last().click();
74
+ await wait(350);
75
+ }
76
+ };
77
+
78
+ const readWordCount = async () =>
79
+ page.locator('main').evaluate(() => {
80
+ const el = [...document.querySelectorAll('main *')].find(e =>
81
+ /^总字数:\d+$/.test((e.textContent || '').trim()),
82
+ );
83
+ const m = el?.textContent?.match(/总字数:(\d+)/);
84
+ return m ? parseInt(m[1], 10) : 0;
85
+ });
86
+
87
+ /** 在中间 Slate 编辑器选中文本(triple=段落级三击;drag=按短语坐标框选) */
88
+ const selectInEditor = async (phrase, mode = 'triple') => {
89
+ const ed = editor();
90
+ await ed.scrollIntoViewIfNeeded();
91
+ if (mode === 'drag') {
92
+ const rect = await ed.evaluate((root, t) => {
93
+ const w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
94
+ let n;
95
+ while ((n = w.nextNode())) {
96
+ const i = n.textContent.indexOf(t);
97
+ if (i >= 0) {
98
+ const r = document.createRange();
99
+ r.setStart(n, i);
100
+ r.setEnd(n, i + t.length);
101
+ return r.getBoundingClientRect();
102
+ }
103
+ }
104
+ return null;
105
+ }, phrase);
106
+ if (!rect) return false;
107
+ await page.mouse.move(rect.x + 2, rect.y + rect.height / 2);
108
+ await page.mouse.down();
109
+ await page.mouse.move(rect.x + rect.width - 2, rect.y + rect.height / 2, { steps: 16 });
110
+ await page.mouse.up();
111
+ } else {
112
+ const loc = ed.getByText(phrase, { exact: false }).first();
113
+ await loc.scrollIntoViewIfNeeded();
114
+ await loc.click({ clickCount: 3 });
115
+ }
116
+ await wait(450);
117
+ return true;
118
+ };
119
+
120
+ /** 顺序 A / B 触发右侧功能 */
121
+ const triggerTool = async (label, order, phrase) => {
122
+ const btn = sidebarTool(label);
123
+ if (order === 'B') {
124
+ await btn.click();
125
+ await wait(650);
126
+ await selectInEditor(phrase);
127
+ } else {
128
+ await selectInEditor(phrase);
129
+ await btn.click();
130
+ }
131
+ await wait(750);
132
+ return readWordCount();
133
+ };
134
+
135
+ const inspectGeneralPanel = async expectedTitle => ({
136
+ title: (await page.getByText(expectedTitle, { exact: true }).count()) > 0,
137
+ originalLabel: (await page.getByText('原文内容', { exact: true }).count()) > 0,
138
+ generate: (await page.getByRole('button', { name: '一键生成' }).count()) > 0,
139
+ resultLabel: (await page.getByText('生成结果', { exact: true }).count()) > 0,
140
+ copy: (await page.getByRole('button', { name: '一键复制' }).count()) > 0,
141
+ insertBelow: (await page.getByRole('button', { name: '插入下方' }).count()) > 0,
142
+ replace: (await page.getByRole('button', { name: '替换原文' }).count()) > 0,
143
+ wordCount: await readWordCount(),
144
+ });
145
+
146
+ const inspectStylePanel = async () => ({
147
+ ...(await inspectGeneralPanel('文风')),
148
+ refStyleLabel: (await page.getByText('参考文风', { exact: true }).count()) > 0,
149
+ refStyleInput: (await page.locator('main textarea[placeholder="请输入参考文风"]').count()) > 0,
150
+ });
151
+
152
+ const inspectRefListPanel = async () => {
153
+ const refPanel = page
154
+ .locator('main')
155
+ .filter({ has: page.getByRole('button', { name: '查询文献' }) })
156
+ .last();
157
+ return {
158
+ tabList: (await page.getByRole('button', { name: '文献列表' }).count()) > 0,
159
+ tabQuery: (await page.getByRole('button', { name: '查询文献' }).count()) > 0,
160
+ checkRefs:
161
+ (await refPanel.locator('[title="检查文献"]').count()) > 0 ||
162
+ (await refPanel.getByText('检查文献', { exact: true }).count()) > 0,
163
+ download:
164
+ (await refPanel.locator('[title="下载"]').count()) > 0 ||
165
+ (await refPanel.getByText('下载', { exact: true }).count()) > 0,
166
+ close: (await page.getByRole('img', { name: 'close' }).count()) > 0,
167
+ };
168
+ };
169
+
170
+ const inspectRefQueryPanel = async () => ({
171
+ originalLabel: (await page.getByText('原文内容', { exact: true }).count()) > 0,
172
+ consult: (await page.getByRole('button', { name: '查阅文献' }).count()) > 0,
173
+ resultLabel: (await page.getByText('生成结果', { exact: true }).count()) > 0,
174
+ copy: (await page.getByRole('button', { name: '一键复制' }).count()) > 0,
175
+ replace: (await page.getByRole('button', { name: '替换原文' }).count()) > 0,
176
+ reindex: (await page.getByRole('button', { name: /重校文献/ }).count()) > 0,
177
+ wordCount: await readWordCount(),
178
+ });
179
+
180
+ const waitGenerateDone = async (timeoutMs = CONFIG.generateTimeoutMs) => {
181
+ let elapsed = 0;
182
+ while (elapsed < timeoutMs) {
183
+ if ((await page.getByRole('button', { name: '生成中' }).count()) === 0) {
184
+ const copy = page.getByRole('button', { name: '一键复制' });
185
+ if ((await copy.count()) > 0 && (await copy.first().isEnabled())) return true;
186
+ }
187
+ await wait(1500);
188
+ elapsed += 1500;
189
+ }
190
+ return false;
191
+ };
192
+
193
+ const dismissPopover = async () => {
194
+ const cancel = page.getByRole('button', { name: '取 消' });
195
+ if ((await cancel.count()) > 0 && (await cancel.first().isVisible())) {
196
+ await cancel.first().click();
197
+ await wait(300);
198
+ }
199
+ };
200
+
201
+ const confirmPopover = async () => {
202
+ const ok = page.getByRole('button', { name: '确 定' });
203
+ if ((await ok.count()) > 0) {
204
+ await ok.first().click();
205
+ await wait(500);
206
+ return true;
207
+ }
208
+ return false;
209
+ };
210
+
211
+ const results = { pathname, triggerOrder: CONFIG.triggerOrder };
212
+
213
+ // --- 0. 左侧目录 → 滚动 ---
214
+ const nav = page.locator('main span').filter({ hasText: CONFIG.navProbe, exact: true }).first();
215
+ if ((await nav.count()) === 0) {
216
+ results.navScroll = { ok: false, reason: `目录无「${CONFIG.navProbe}」` };
217
+ } else {
218
+ await nav.click();
219
+ await wait(900);
220
+ const target = editor().locator('h3').filter({ hasText: CONFIG.navProbe }).first();
221
+ const inView = await target
222
+ .evaluate(el => {
223
+ const r = el.getBoundingClientRect();
224
+ return r.top >= 0 && r.top < window.innerHeight * 0.85;
225
+ })
226
+ .catch(() => false);
227
+ results.navScroll = { ok: inView, target: CONFIG.navProbe };
228
+ }
229
+
230
+ // --- 1. 右侧功能栏存在性 ---
231
+ results.sidebar = {};
232
+ for (const t of GENERAL_TOOLS) {
233
+ results.sidebar[t] = (await sidebarTool(t).count()) > 0;
234
+ }
235
+
236
+ // --- 2. 通用工具面板结构(逐项探测) ---
237
+ results.generalPanels = {};
238
+ if (CONFIG.probeOnly || CONFIG.probeGeneralTools !== false) {
239
+ for (const tool of CONFIG.generalTools) {
240
+ await closePanel();
241
+ const wc = await triggerTool(tool, CONFIG.triggerOrder, CONFIG.selectPhrase);
242
+ results.generalPanels[tool] = {
243
+ ...(await inspectGeneralPanel(tool)),
244
+ wordCountOk: wc > 0,
245
+ };
246
+ await closePanel();
247
+ }
248
+ }
249
+
250
+ // --- 3. 文风专项 ---
251
+ if (CONFIG.probeStyleTool) {
252
+ await closePanel();
253
+ await triggerTool('文风', CONFIG.triggerOrder, CONFIG.selectPhrase);
254
+ results.stylePanel = await inspectStylePanel();
255
+ const refTa = page.locator('main textarea[placeholder="请输入参考文风"]');
256
+ await refTa.fill('');
257
+ await wait(150);
258
+ await page.getByRole('button', { name: '一键生成' }).click({ timeout: 3000 }).catch(() => {});
259
+ await wait(300);
260
+ results.styleRequiredToast =
261
+ (await page.locator('.ant-message-notice-content, .ant-message').filter({ hasText: '参考文风' }).count()) > 0;
262
+ if (!CONFIG.probeOnly && CONFIG.runStyleGenerate) {
263
+ await refTa.fill(CONFIG.refStyleText);
264
+ await page.getByRole('button', { name: '一键生成' }).click();
265
+ results.styleGenerateDone = await waitGenerateDone();
266
+ }
267
+ await closePanel();
268
+ }
269
+
270
+ // --- 4. 文献列表面板 ---
271
+ if (CONFIG.probeRefPanel) {
272
+ await closePanel();
273
+ await sidebarTool('文献列表').click();
274
+ await wait(800);
275
+ await page.getByRole('button', { name: '文献列表' }).click();
276
+ await wait(400);
277
+ results.refListPanel = await inspectRefListPanel();
278
+ await page.getByRole('button', { name: '查询文献' }).click();
279
+ await wait(600);
280
+ results.refQueryPanelIdle = await inspectRefQueryPanel();
281
+
282
+ const wc = await triggerTool('文献列表', 'B', CONFIG.selectPhrase);
283
+ await page.getByRole('button', { name: '查询文献' }).click();
284
+ await wait(600);
285
+ results.refQueryAfterSelect = {
286
+ ...(await inspectRefQueryPanel()),
287
+ wordCountOk: wc > 0,
288
+ };
289
+
290
+ if (!CONFIG.probeOnly && CONFIG.runRefConsult) {
291
+ await page.getByRole('button', { name: '查阅文献' }).click();
292
+ results.refConsultDone = await waitGenerateDone();
293
+ }
294
+
295
+ if (!CONFIG.probeOnly && CONFIG.runRefReindex && results.refConsultDone !== false) {
296
+ await page.getByRole('button', { name: /重校文献/ }).first().click();
297
+ await wait(500);
298
+ results.reindexPopover =
299
+ (await page.getByText(REINDEX_CONFIRM_SNIPPET).count()) > 0;
300
+ if (CONFIG.runRefReindexConfirm) {
301
+ await confirmPopover();
302
+ results.reindexConfirmed = true;
303
+ } else {
304
+ await dismissPopover();
305
+ }
306
+ }
307
+ await closePanel();
308
+ }
309
+
310
+ // --- 5. 单工具写操作(非 probeOnly) ---
311
+ if (!CONFIG.probeOnly && CONFIG.runGenerate) {
312
+ await closePanel();
313
+ const wc = await triggerTool(CONFIG.generalTool, CONFIG.triggerOrder, CONFIG.selectPhrase);
314
+ results.activeTool = { tool: CONFIG.generalTool, wordCount: wc };
315
+ if (wc > 0) {
316
+ await page.getByRole('button', { name: '一键生成' }).click();
317
+ results.generateDone = await waitGenerateDone();
318
+ if (results.generateDone) {
319
+ if (CONFIG.runCopy) {
320
+ await page.getByRole('button', { name: '一键复制' }).click();
321
+ results.copyToast = (await page.getByText('复制成功').count()) > 0;
322
+ }
323
+ if (CONFIG.runInsertBelow) {
324
+ await page.getByRole('button', { name: '插入下方' }).click();
325
+ results.insertBelow = true;
326
+ }
327
+ if (CONFIG.runReplace) {
328
+ await page.getByRole('button', { name: '替换原文' }).click();
329
+ await wait(450);
330
+ results.replacePopover =
331
+ (await page.getByText(REPLACE_CONFIRM_TEXT).count()) > 0;
332
+ if (CONFIG.runReplaceConfirm) {
333
+ await confirmPopover();
334
+ results.replaced = true;
335
+ } else {
336
+ await dismissPopover();
337
+ }
338
+ }
339
+ }
340
+ }
341
+ await closePanel();
342
+ }
343
+
344
+ // --- 6. 底栏 ---
345
+ results.footer = {
346
+ returnOutline: (await page.getByRole('button', { name: '返回提纲' }).count()) > 0,
347
+ revisionMode: (await page.getByRole('button', { name: /修订模式/ }).count()) > 0,
348
+ newPaper: (await page.getByRole('button', { name: '新建论文' }).count()) > 0,
349
+ download: (await page.getByRole('button', { name: '下载文档' }).count()) > 0,
350
+ };
351
+
352
+ results.ok =
353
+ results.navScroll?.ok !== false &&
354
+ GENERAL_TOOLS.every(t => results.sidebar?.[t]) &&
355
+ Object.values(results.generalPanels || {}).every(p => p.title && p.originalLabel && p.generate) &&
356
+ results.stylePanel?.refStyleInput !== false &&
357
+ results.refListPanel?.tabList &&
358
+ results.refQueryAfterSelect?.consult !== false;
359
+
360
+ results.summary = {
361
+ pathname: '/workbench/jova/writing',
362
+ editor: 'main [role=textbox](Slate contenteditable)',
363
+ sidebarTool: "main getByText('<功能名>', { exact: true }).last()",
364
+ triggerA: 'selectInEditor → sidebarTool.click',
365
+ triggerB: 'sidebarTool.click → selectInEditor',
366
+ wordCountAccept: '总字数:N(N>0)',
367
+ generalPanel: '原文内容 · 一键生成 · 生成结果 · 一键复制/插入下方/替换原文',
368
+ styleExtra: 'textarea[placeholder="请输入参考文风"] 必填',
369
+ refPanel: 'Tab 文献列表|查询文献 · getByTitle(检查文献|下载) · img[name=close]',
370
+ refQueryActions: '查阅文献 · aria 一键复制/替换原文/重校文献序号',
371
+ replaceConfirm: `${REPLACE_CONFIRM_TEXT} → 取 消 | 确 定`,
372
+ reindexConfirm: `${REINDEX_CONFIRM_SNIPPET}… → 取 消 | 确 定`,
373
+ };
374
+
375
+ return results;
376
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * 初稿精修 · 校对「摘要」段落(一键生成 + 替换原文)
3
+ * helixlife-v5-cli run-code --filename=scripts/workbench/jova/writing-proofread-abstract.js
4
+ */
5
+ async page => {
6
+ const wait = ms => page.waitForTimeout(ms);
7
+ const pathname = await page.evaluate(() => location.pathname);
8
+ if (pathname !== '/workbench/jova/writing') {
9
+ return { ok: false, reason: '当前不在初稿精修页', pathname };
10
+ }
11
+
12
+ const ABSTRACT_START = '蛋白质脂化作为关键的翻译后修饰';
13
+ const editor = () => page.locator('main [role=textbox]').first();
14
+ const sidebarTool = label => page.locator('main').getByText(label, { exact: true }).last();
15
+
16
+ const nav = page.locator('main span').filter({ hasText: '摘要', exact: true }).first();
17
+ if ((await nav.count()) === 0) {
18
+ return { ok: false, reason: '左侧目录无「摘要」' };
19
+ }
20
+ await nav.click();
21
+ await wait(900);
22
+
23
+ const abstractLoc = editor().getByText(ABSTRACT_START, { exact: false }).first();
24
+ if ((await abstractLoc.count()) === 0) {
25
+ return { ok: false, reason: '未找到摘要正文' };
26
+ }
27
+ await abstractLoc.scrollIntoViewIfNeeded();
28
+ await abstractLoc.click({ clickCount: 3 });
29
+ await wait(500);
30
+
31
+ await sidebarTool('校对').click();
32
+ await wait(800);
33
+
34
+ const readWordCount = async () =>
35
+ page.locator('main').evaluate(() => {
36
+ const el = [...document.querySelectorAll('main *')].find(e =>
37
+ /^总字数:\d+$/.test((e.textContent || '').trim()),
38
+ );
39
+ const m = el?.textContent?.match(/总字数:(\d+)/);
40
+ return m ? parseInt(m[1], 10) : 0;
41
+ });
42
+
43
+ const wordCount = await readWordCount();
44
+ if (wordCount <= 0) {
45
+ return { ok: false, reason: '校对面板未捕获摘要选区', wordCount };
46
+ }
47
+
48
+ await page.getByRole('button', { name: '一键生成' }).click();
49
+
50
+ let generateDone = false;
51
+ for (let i = 0; i < 80; i++) {
52
+ if ((await page.getByRole('button', { name: '生成中' }).count()) === 0) {
53
+ const copy = page.getByRole('button', { name: '一键复制' });
54
+ if ((await copy.count()) > 0 && (await copy.first().isEnabled())) {
55
+ generateDone = true;
56
+ break;
57
+ }
58
+ }
59
+ await wait(1500);
60
+ }
61
+ if (!generateDone) {
62
+ return { ok: false, reason: '校对生成超时', wordCount };
63
+ }
64
+
65
+ const resultText = await page.locator('main').evaluate(() => {
66
+ const labels = [...document.querySelectorAll('main *')].filter(
67
+ e => (e.textContent || '').trim() === '生成结果',
68
+ );
69
+ for (const label of labels) {
70
+ let sib = label.parentElement;
71
+ for (let d = 0; d < 6 && sib; d++, sib = sib.parentElement) {
72
+ const box = sib.querySelector('[contenteditable], textarea, .ant-input');
73
+ if (box?.textContent?.trim()) return box.textContent.trim().slice(0, 200);
74
+ }
75
+ }
76
+ return null;
77
+ });
78
+
79
+ await page.getByRole('button', { name: '替换原文' }).click();
80
+ await wait(450);
81
+ const popover = (await page.getByText('是否将本次生成结果替换原文?').count()) > 0;
82
+ if (!popover) {
83
+ return { ok: false, reason: '未出现替换确认 Popover', generateDone, resultPreview: resultText };
84
+ }
85
+ await page.getByRole('button', { name: '确 定' }).click();
86
+ await wait(700);
87
+
88
+ const afterText = await editor()
89
+ .getByText(ABSTRACT_START.slice(0, 12), { exact: false })
90
+ .first()
91
+ .textContent()
92
+ .catch(() => null);
93
+
94
+ return {
95
+ ok: true,
96
+ section: '摘要',
97
+ wordCount,
98
+ generateDone,
99
+ replaced: true,
100
+ resultPreview: resultText,
101
+ afterPreview: afterText?.slice(0, 120),
102
+ };
103
+ }
@@ -1,5 +0,0 @@
1
- {
2
- "browser": {
3
- "initScript": ["./automation-overlay.init.js"]
4
- }
5
- }