helixlife-v5-cli 1.2.4 → 1.2.5
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/lib/cli.js +60 -1
- package/package.json +1 -1
- package/references/vip.helixlife.cn.site-ops.md +28 -14
- package/scripts/workbench-jova-dispatch.js +196 -21
package/lib/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const fs = require('node:fs');
|
|
3
4
|
const path = require('node:path');
|
|
4
5
|
const { spawnSync } = require('node:child_process');
|
|
5
6
|
|
|
@@ -199,11 +200,67 @@ function helixDoctor(options) {
|
|
|
199
200
|
return helixHome(options);
|
|
200
201
|
}
|
|
201
202
|
|
|
203
|
+
/** npm 包内 scripts/ 目录(WorkBuddy 等工作区无 scripts/ 时回退) */
|
|
204
|
+
function packageScriptsDir() {
|
|
205
|
+
return path.join(__dirname, '..', 'scripts');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* run-code --filename=scripts/foo.js:cwd 下不存在时,回退到 helixlife-v5-cli 包内 scripts/
|
|
210
|
+
* @param {string[]} tokens
|
|
211
|
+
* @returns {string[]}
|
|
212
|
+
*/
|
|
213
|
+
function resolveRunCodeFilename(tokens) {
|
|
214
|
+
const runIdx = tokens.indexOf('run-code');
|
|
215
|
+
if (runIdx === -1) {
|
|
216
|
+
return tokens;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const out = [...tokens];
|
|
220
|
+
for (let i = runIdx + 1; i < out.length; i += 1) {
|
|
221
|
+
let filePath = null;
|
|
222
|
+
let mode = null;
|
|
223
|
+
const t = out[i];
|
|
224
|
+
if (t.startsWith('--filename=')) {
|
|
225
|
+
filePath = t.slice('--filename='.length).replace(/^["']|["']$/g, '');
|
|
226
|
+
mode = 'eq';
|
|
227
|
+
} else if (t === '--filename' && out[i + 1]) {
|
|
228
|
+
filePath = out[i + 1].replace(/^["']|["']$/g, '');
|
|
229
|
+
mode = 'next';
|
|
230
|
+
}
|
|
231
|
+
if (!filePath) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const cwdResolved = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
236
|
+
if (fs.existsSync(cwdResolved)) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const base = path.basename(filePath.replace(/\\/g, '/'));
|
|
241
|
+
const pkgScript = path.join(packageScriptsDir(), base);
|
|
242
|
+
if (!fs.existsSync(pkgScript)) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (mode === 'eq') {
|
|
247
|
+
out[i] = `--filename=${pkgScript}`;
|
|
248
|
+
} else {
|
|
249
|
+
out[i + 1] = pkgScript;
|
|
250
|
+
}
|
|
251
|
+
if (process.env.HELIX_DEBUG_SCRIPT_RESOLVE) {
|
|
252
|
+
console.error(`[${PACKAGE_JSON.name}] run-code: ${filePath} → ${pkgScript}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
|
|
202
258
|
async function runPassthroughWithVisual(tokens) {
|
|
259
|
+
const resolved = resolveRunCodeFilename(tokens);
|
|
203
260
|
let visual;
|
|
204
261
|
let stripped;
|
|
205
262
|
try {
|
|
206
|
-
({ tokens: stripped, visual } = parseVisualFlags(
|
|
263
|
+
({ tokens: stripped, visual } = parseVisualFlags(resolved));
|
|
207
264
|
} catch (e) {
|
|
208
265
|
console.error(`[${PACKAGE_JSON.name}] ${e.message}`);
|
|
209
266
|
return 3;
|
|
@@ -276,4 +333,6 @@ module.exports = {
|
|
|
276
333
|
buildChildEnv,
|
|
277
334
|
DEFAULT_OUTPUT_DIR_NAME,
|
|
278
335
|
runPassthroughWithVisual,
|
|
336
|
+
resolveRunCodeFilename,
|
|
337
|
+
packageScriptsDir,
|
|
279
338
|
};
|
package/package.json
CHANGED
|
@@ -50,19 +50,30 @@
|
|
|
50
50
|
# ① 定页
|
|
51
51
|
helixlife-v5-cli --raw eval "location.pathname"
|
|
52
52
|
|
|
53
|
-
# ② 统一入口(WorkBuddy
|
|
53
|
+
# ② 统一入口(WorkBuddy 默认只跑这一条;用环境变量传 userIntent,勿改脚本文件)
|
|
54
|
+
# PowerShell: $env:HELIX_JOVA_INTENT='扩写前言'
|
|
55
|
+
# bash: HELIX_JOVA_INTENT='扩写前言' helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
54
56
|
helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
55
57
|
```
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
工作区无 `scripts/` 时 CLI **自动回退**到 `node_modules/helixlife-v5-cli/scripts/` 同名文件;**禁止**复制脚本到 WorkBuddy 目录再改 `userIntent`。
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
脚本内按 `pathname` 自动路由;**outline / writing 页**在 dispatch 内 **一次 run-code 跑完全流程**(含替换原文),**绝不**点「添加子级」。proofread 仍返回子脚本名。
|
|
62
|
+
|
|
63
|
+
**WorkBuddy · 执行约束(与 PDF 跳页同级)**
|
|
64
|
+
|
|
65
|
+
1. **只执行一条命令**:上表 `run-code` + `HELIX_JOVA_INTENT`;**禁止**在成功后追加 `hover` / `click e*` / `fill` / `sleep` / `snapshot` 手工重放。
|
|
66
|
+
2. **验收**:读 `Result` JSON 末尾 `"ok": true` → 向用户报完成;`"ok": false` → 只报 `reason`,**禁止**改用手动逐步操作代替。
|
|
67
|
+
3. **禁止**:将脚本复制到 `C:/Users/.../WorkBuddy/.../scripts/` 再编辑;禁止用绝对路径指向 WorkBuddy 副本。
|
|
68
|
+
4. **禁止**:`run-code` 返回 `ok:true` 后仍「深度思考 + snapshot + 逐步 click」——这是导致数分钟慢路径的根因。
|
|
69
|
+
|
|
70
|
+
**子脚本(仅 proofread 等 dispatch 未内联时)**
|
|
60
71
|
|
|
61
72
|
| `pathname` | 示例 userIntent | 子脚本 |
|
|
62
73
|
| --- | --- | --- |
|
|
63
74
|
| `…/jova/outline` | `扩写前言` | (dispatch 内直接执行) |
|
|
75
|
+
| `…/jova/writing` | `扩写前言` / `翻译 前言` | (dispatch 内直接执行;扩写类自动替换原文) |
|
|
64
76
|
| `…/jova/proofread` | `校对 前言` | `workbench-jova-proofread-section.js` |
|
|
65
|
-
| `…/jova/writing` | `扩写 前言` / `翻译 前言` | `workbench-jova-writing-tool.js` |
|
|
66
77
|
|
|
67
78
|
细则与按钮对照表:下文 **WB-1 · 用户说法与脚本路由**。
|
|
68
79
|
|
|
@@ -460,20 +471,21 @@ div.side-menu--item[to='/edu/courses']
|
|
|
460
471
|
|
|
461
472
|
```bash
|
|
462
473
|
helixlife-v5-cli --raw eval "location.pathname"
|
|
474
|
+
# PowerShell: $env:HELIX_JOVA_INTENT='扩写前言'
|
|
463
475
|
helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
464
476
|
```
|
|
465
477
|
|
|
466
|
-
|
|
478
|
+
(用 `HELIX_JOVA_INTENT` 传用户说法;outline/writing 在 dispatch 内直接完成,禁止点「添加子级」。工作区无 `scripts/` 时 CLI 自动用 npm 包内脚本。)
|
|
467
479
|
|
|
468
480
|
**1 · 说法 → 脚本(jova 综述)**
|
|
469
481
|
|
|
470
482
|
| 当前 `pathname` | 用户说法(示例) | 脚本(工作区 `scripts/`) | Agent 改动的变量 |
|
|
471
483
|
| --- | --- | --- | --- |
|
|
472
|
-
| `…/jova/outline` | `扩写前言`、`扩写 前言`、`精简结论`、`润色 1. 乳腺癌…` | **`workbench-jova-dispatch.js
|
|
473
|
-
| `…/jova/
|
|
474
|
-
| `…/jova/
|
|
484
|
+
| `…/jova/outline` | `扩写前言`、`扩写 前言`、`精简结论`、`润色 1. 乳腺癌…` | **`workbench-jova-dispatch.js`** | `HELIX_JOVA_INTENT` |
|
|
485
|
+
| `…/jova/writing` | `扩写前言`、`翻译 前言`、`润色 摘要`、`降重`、`文献列表` | **`workbench-jova-dispatch.js`** | `HELIX_JOVA_INTENT` |
|
|
486
|
+
| `…/jova/proofread` | `校对 前言`、`确认校对`、`AI 改写`、`替换原文` | dispatch 返回 → `workbench-jova-proofread-section.js` | `HELIX_JOVA_INTENT` + `HELIX_JOVA_ACTION` |
|
|
475
487
|
|
|
476
|
-
执行:`helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
488
|
+
执行:`HELIX_JOVA_INTENT='扩写前言' helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js`(**唯一首选**;**禁止**成功后手工 hover/click/fill)
|
|
477
489
|
|
|
478
490
|
**2 · outline 页:扩写 ≠ 添加子级(反模式)**
|
|
479
491
|
|
|
@@ -510,8 +522,9 @@ helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
|
510
522
|
|
|
511
523
|
1. 在 **outline** 页对用户「扩写/润色/精简 + 章节」点「添加子级」或「添加同级」——**永远不对**。
|
|
512
524
|
2. 不 `eval location.pathname` 就在 **proofread / writing** 页找提纲树「编辑」或「添加子级」。
|
|
513
|
-
3.
|
|
514
|
-
4.
|
|
525
|
+
3. `run-code` 已返回 `"ok": true` 仍用手动 `snapshot` + `hover` + `click e*` + `fill` + `sleep` 重放(WorkBuddy 慢路径根因)。
|
|
526
|
+
4. 将 `workbench-jova-dispatch.js` **复制到 WorkBuddy 工作区**再改 `userIntent`(应设 `HELIX_JOVA_INTENT`,CLI 自动回退 npm 包内脚本)。
|
|
527
|
+
5. 同一轮任务内反复 `snapshot` 猜 `e*` ref 定位提纲节点(须脚本内 `aria-label*="操作区"` + 章节文案匹配)。
|
|
515
528
|
|
|
516
529
|
**6 · 执行命令模板**
|
|
517
530
|
|
|
@@ -519,11 +532,12 @@ helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
|
519
532
|
# ① 定页(不可跳过)
|
|
520
533
|
helixlife-v5-cli --raw eval "location.pathname"
|
|
521
534
|
|
|
522
|
-
# ②
|
|
523
|
-
|
|
535
|
+
# ② 一条命令完成(示例 PowerShell)
|
|
536
|
+
$env:HELIX_JOVA_INTENT='扩写前言'
|
|
537
|
+
helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
524
538
|
```
|
|
525
539
|
|
|
526
|
-
|
|
540
|
+
`--filename=scripts/...` 相对于 cwd;cwd 无该文件时自动解析为 `node_modules/helixlife-v5-cli/scripts/...`。
|
|
527
541
|
|
|
528
542
|
---
|
|
529
543
|
|
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
* outline 页「扩写前言」等:本脚本内直接执行(hover → 编辑,禁止添加子级)。
|
|
6
6
|
*
|
|
7
7
|
* 用法:
|
|
8
|
+
* set HELIX_JOVA_INTENT=扩写前言
|
|
8
9
|
* helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
10
|
+
*
|
|
11
|
+
* WorkBuddy 等工作区无 scripts/ 时,CLI 自动回退到 npm 包内同名脚本;勿复制到 WorkBuddy 目录。
|
|
9
12
|
*/
|
|
10
13
|
async page => {
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const action = null; // proofread/writing 子流程用;outline 忽略
|
|
14
|
+
const userIntent = (process.env.HELIX_JOVA_INTENT || '扩写前言').trim();
|
|
15
|
+
const action = process.env.HELIX_JOVA_ACTION?.trim() || null;
|
|
14
16
|
|
|
15
17
|
const pathname = new URL(page.url()).pathname;
|
|
16
18
|
const FORBIDDEN_BUTTONS = ['添加子级', '添加同级'];
|
|
@@ -30,18 +32,6 @@ async page => {
|
|
|
30
32
|
return { nodeTitle: '前言', userRequirement: text };
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
function normalizeWritingIntent(raw) {
|
|
34
|
-
const text = raw.trim();
|
|
35
|
-
const tools = ['校对', '文风', '润色', '降重', '翻译', '改写', '扩写', '缩写', '文献列表'];
|
|
36
|
-
for (const tool of tools) {
|
|
37
|
-
if (text === tool || text.startsWith(`${tool} `) || text.startsWith(tool)) {
|
|
38
|
-
const sectionTitle = text.slice(tool.length).trim();
|
|
39
|
-
return sectionTitle ? `${tool} ${sectionTitle}` : tool;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return text;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
35
|
if (!pathname.includes('/workbench/jova/')) {
|
|
46
36
|
return {
|
|
47
37
|
ok: false,
|
|
@@ -159,16 +149,201 @@ async page => {
|
|
|
159
149
|
}
|
|
160
150
|
|
|
161
151
|
if (pathname.includes('/jova/writing')) {
|
|
152
|
+
const WRITING_TOOLS = ['校对', '文风', '润色', '降重', '翻译', '改写', '扩写', '缩写', '文献列表'];
|
|
153
|
+
|
|
154
|
+
function parseWritingIntent(raw) {
|
|
155
|
+
const text = raw.trim();
|
|
156
|
+
for (const tool of WRITING_TOOLS) {
|
|
157
|
+
if (text === tool || text.startsWith(`${tool} `) || text.startsWith(tool)) {
|
|
158
|
+
const sectionTitle = text.slice(tool.length).trim();
|
|
159
|
+
return { tool, sectionTitle: sectionTitle || null };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const verb of outlineVerbs) {
|
|
163
|
+
if (text.startsWith(verb)) {
|
|
164
|
+
const sectionTitle = text.slice(verb.length).trim();
|
|
165
|
+
if (sectionTitle) return { tool: verb === '优化' ? '润色' : verb, sectionTitle };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { tool: null, sectionTitle: null };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const { tool, sectionTitle } = parseWritingIntent(userIntent);
|
|
172
|
+
if (!tool || !WRITING_TOOLS.includes(tool)) {
|
|
173
|
+
return {
|
|
174
|
+
ok: false,
|
|
175
|
+
route: 'writing',
|
|
176
|
+
userIntent,
|
|
177
|
+
pathname,
|
|
178
|
+
forbiddenNeverClick: FORBIDDEN_BUTTONS,
|
|
179
|
+
reason: `writing 页无法解析工具,支持:${WRITING_TOOLS.join('、')} 或 ${outlineVerbs.join('/')} + 章节`,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (tool !== '文献列表' && !sectionTitle) {
|
|
183
|
+
return {
|
|
184
|
+
ok: false,
|
|
185
|
+
route: 'writing',
|
|
186
|
+
userIntent,
|
|
187
|
+
pathname,
|
|
188
|
+
forbiddenNeverClick: FORBIDDEN_BUTTONS,
|
|
189
|
+
reason: '请指定章节,如「扩写前言」或「翻译 前言」',
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const main = page.locator('main');
|
|
194
|
+
|
|
195
|
+
async function closeWritingPanel() {
|
|
196
|
+
const closeIcons = main.getByRole('img', { name: 'close' });
|
|
197
|
+
for (let i = 0; i < (await closeIcons.count()); i++) {
|
|
198
|
+
const icon = closeIcons.nth(i);
|
|
199
|
+
if (await icon.isVisible().catch(() => false)) {
|
|
200
|
+
await icon.click();
|
|
201
|
+
await page.waitForTimeout(400);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function selectWritingSection(title) {
|
|
207
|
+
if (title) {
|
|
208
|
+
const toc = main.locator('[class*="cursor-pointer"]').getByText(title, { exact: true });
|
|
209
|
+
if ((await toc.count()) > 0) {
|
|
210
|
+
await toc.first().click({ timeout: 5000 }).catch(() => {});
|
|
211
|
+
await page.waitForTimeout(500);
|
|
212
|
+
}
|
|
213
|
+
const heading = main.getByRole('heading', { name: title, exact: true }).first();
|
|
214
|
+
if ((await heading.count()) > 0) {
|
|
215
|
+
await heading.scrollIntoViewIfNeeded();
|
|
216
|
+
await page.waitForTimeout(300);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const selected = await page.evaluate(t => {
|
|
220
|
+
const editor = document.querySelector('main [role="textbox"]');
|
|
221
|
+
if (!editor) return { ok: false, reason: '未找到富文本编辑器' };
|
|
222
|
+
if (!t) {
|
|
223
|
+
const range = document.createRange();
|
|
224
|
+
range.selectNodeContents(editor);
|
|
225
|
+
const sel = window.getSelection();
|
|
226
|
+
sel?.removeAllRanges();
|
|
227
|
+
sel?.addRange(range);
|
|
228
|
+
return { ok: true };
|
|
229
|
+
}
|
|
230
|
+
const headings = [...editor.querySelectorAll('h1,h2,h3,h4,h5,h6')];
|
|
231
|
+
const idx = headings.findIndex(h => h.textContent?.trim() === t);
|
|
232
|
+
if (idx < 0) return { ok: false, reason: `编辑器内未找到章节「${t}」` };
|
|
233
|
+
const start = headings[idx];
|
|
234
|
+
const level = parseInt(start.tagName[1], 10);
|
|
235
|
+
let end = null;
|
|
236
|
+
for (let i = idx + 1; i < headings.length; i++) {
|
|
237
|
+
if (parseInt(headings[i].tagName[1], 10) <= level) {
|
|
238
|
+
end = headings[i];
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const range = document.createRange();
|
|
243
|
+
range.setStartBefore(start);
|
|
244
|
+
range.setEndBefore(end || editor.lastChild || editor);
|
|
245
|
+
const sel = window.getSelection();
|
|
246
|
+
sel?.removeAllRanges();
|
|
247
|
+
sel?.addRange(range);
|
|
248
|
+
return { ok: true, sectionText: range.toString().slice(0, 200) };
|
|
249
|
+
}, title);
|
|
250
|
+
await page.waitForTimeout(400);
|
|
251
|
+
return selected;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
await closeWritingPanel();
|
|
255
|
+
const selected = await selectWritingSection(sectionTitle);
|
|
256
|
+
if (!selected.ok) {
|
|
257
|
+
return {
|
|
258
|
+
ok: false,
|
|
259
|
+
route: 'writing',
|
|
260
|
+
userIntent,
|
|
261
|
+
tool,
|
|
262
|
+
sectionTitle,
|
|
263
|
+
forbiddenNeverClick: FORBIDDEN_BUTTONS,
|
|
264
|
+
reason: selected.reason,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (tool === '文献列表') {
|
|
269
|
+
await main.getByText(tool, { exact: true }).first().click();
|
|
270
|
+
await page.waitForTimeout(800);
|
|
271
|
+
return {
|
|
272
|
+
ok: true,
|
|
273
|
+
route: 'writing',
|
|
274
|
+
userIntent,
|
|
275
|
+
tool,
|
|
276
|
+
sectionTitle,
|
|
277
|
+
mode: 'list',
|
|
278
|
+
message: '已打开文献列表面板。',
|
|
279
|
+
url: page.url(),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
await main.getByText(tool, { exact: true }).first().click();
|
|
284
|
+
await page.waitForTimeout(800);
|
|
285
|
+
await main.getByText('原文内容', { exact: true }).waitFor({ state: 'visible', timeout: 10000 }).catch(() => {});
|
|
286
|
+
|
|
287
|
+
if (tool === '文风') {
|
|
288
|
+
const refBox = main.getByRole('textbox', { name: '参考文风' });
|
|
289
|
+
if ((await refBox.count()) > 0 && (await refBox.first().isVisible().catch(() => false))) {
|
|
290
|
+
const ref =
|
|
291
|
+
selected.sectionText ||
|
|
292
|
+
'This review synthesizes molecular subtyping and precision therapeutic strategies in breast cancer.';
|
|
293
|
+
await refBox.first().fill(ref);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (tool === '翻译') {
|
|
297
|
+
for (const label of ['English', '英文']) {
|
|
298
|
+
const opt = main.getByText(label, { exact: true });
|
|
299
|
+
if ((await opt.count()) > 0 && (await opt.first().isVisible().catch(() => false))) {
|
|
300
|
+
await opt.first().click();
|
|
301
|
+
await page.waitForTimeout(300);
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
await main.getByRole('button', { name: '一键生成' }).click();
|
|
308
|
+
const replaceBtn = main.getByRole('button', { name: '替换原文' });
|
|
309
|
+
await replaceBtn.waitFor({ state: 'visible', timeout: 120000 });
|
|
310
|
+
const deadline = Date.now() + 120000;
|
|
311
|
+
while (Date.now() < deadline) {
|
|
312
|
+
if (await replaceBtn.isEnabled()) break;
|
|
313
|
+
await page.waitForTimeout(1000);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const shouldReplace = action === 'replace' || outlineVerbs.some(v => userIntent.trim().startsWith(v));
|
|
317
|
+
if (shouldReplace && (await replaceBtn.isEnabled())) {
|
|
318
|
+
await replaceBtn.click();
|
|
319
|
+
await main
|
|
320
|
+
.getByText('是否将本次生成结果替换原文?', { exact: true })
|
|
321
|
+
.waitFor({ state: 'visible', timeout: 10000 });
|
|
322
|
+
await main.getByRole('button', { name: '确 定' }).click();
|
|
323
|
+
await page.waitForTimeout(1000);
|
|
324
|
+
return {
|
|
325
|
+
ok: true,
|
|
326
|
+
route: 'writing',
|
|
327
|
+
userIntent,
|
|
328
|
+
tool,
|
|
329
|
+
sectionTitle,
|
|
330
|
+
replaced: true,
|
|
331
|
+
forbiddenNeverClick: FORBIDDEN_BUTTONS,
|
|
332
|
+
message: `writing 页已通过「${tool}→一键生成→替换原文」完成「${sectionTitle}」。`,
|
|
333
|
+
url: page.url(),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
162
337
|
return {
|
|
163
|
-
ok:
|
|
338
|
+
ok: true,
|
|
164
339
|
route: 'writing',
|
|
165
340
|
userIntent,
|
|
166
|
-
|
|
341
|
+
tool,
|
|
342
|
+
sectionTitle,
|
|
343
|
+
needsReplaceChoice: true,
|
|
167
344
|
forbiddenNeverClick: FORBIDDEN_BUTTONS,
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
setAction: action,
|
|
171
|
-
message: '请改 writing-tool.js 的 intent 后 run-code;writing 页无添加子级按钮。',
|
|
345
|
+
message: `「${tool}」已生成;用户确认后再设 HELIX_JOVA_ACTION=replace 或说「替换原文」。`,
|
|
346
|
+
url: page.url(),
|
|
172
347
|
};
|
|
173
348
|
}
|
|
174
349
|
|