helixlife-v5-cli 1.2.5 → 1.2.7

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.
@@ -1,370 +0,0 @@
1
- /**
2
- * 综述工作台(jova)· 统一入口(WorkBuddy 默认只用本脚本)
3
- *
4
- * 改 userIntent 一行;自动按 pathname 路由。
5
- * outline 页「扩写前言」等:本脚本内直接执行(hover → 编辑,禁止添加子级)。
6
- *
7
- * 用法:
8
- * set HELIX_JOVA_INTENT=扩写前言
9
- * helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
10
- *
11
- * WorkBuddy 等工作区无 scripts/ 时,CLI 自动回退到 npm 包内同名脚本;勿复制到 WorkBuddy 目录。
12
- */
13
- async page => {
14
- const userIntent = (process.env.HELIX_JOVA_INTENT || '扩写前言').trim();
15
- const action = process.env.HELIX_JOVA_ACTION?.trim() || null;
16
-
17
- const pathname = new URL(page.url()).pathname;
18
- const FORBIDDEN_BUTTONS = ['添加子级', '添加同级'];
19
-
20
- const outlineVerbs = ['扩写', '精简', '改写', '润色', '重写', '优化'];
21
- const isOutlineVerbIntent = outlineVerbs.some(v => userIntent.trim().startsWith(v));
22
- const isProofreadIntent = /^校对/.test(userIntent.trim());
23
-
24
- function parseOutlineIntent(raw) {
25
- const text = raw.trim();
26
- for (const verb of outlineVerbs) {
27
- if (text.startsWith(verb)) {
28
- const title = text.slice(verb.length).trim();
29
- if (title) return { nodeTitle: title, userRequirement: verb };
30
- }
31
- }
32
- return { nodeTitle: '前言', userRequirement: text };
33
- }
34
-
35
- if (!pathname.includes('/workbench/jova/')) {
36
- return {
37
- ok: false,
38
- userIntent,
39
- pathname,
40
- forbiddenNeverClick: FORBIDDEN_BUTTONS,
41
- reason: '当前不在综述工作台 jova 流程页',
42
- };
43
- }
44
-
45
- // ── outline:扩写/润色/精简 + 章节(禁止添加子级)──
46
- if (pathname.includes('/jova/outline') && isOutlineVerbIntent) {
47
- const { nodeTitle, userRequirement } = parseOutlineIntent(userIntent);
48
- const main = page.locator('main');
49
-
50
- async function resetEditingState() {
51
- const closeIcons = main.getByRole('img', { name: 'close' });
52
- for (let i = 0; i < (await closeIcons.count()); i++) {
53
- const icon = closeIcons.nth(i);
54
- if (await icon.isVisible()) {
55
- await icon.click();
56
- await page.waitForTimeout(400);
57
- }
58
- }
59
- const cancelReplace = main.getByRole('button', { name: '取 消' });
60
- if ((await cancelReplace.count()) > 0 && (await cancelReplace.first().isVisible())) {
61
- await cancelReplace.first().click();
62
- await page.waitForTimeout(400);
63
- }
64
- const saveBtn = main.getByRole('button', { name: '保 存' });
65
- if ((await saveBtn.count()) > 0 && (await saveBtn.first().isVisible())) {
66
- await saveBtn.first().click();
67
- await page.waitForTimeout(400);
68
- }
69
- }
70
-
71
- function outlineNode(title) {
72
- const base = title.replace(/[::]\s*$/, '');
73
- const titleRe = new RegExp(`^${base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[::]?$`);
74
- return main
75
- .locator('[aria-label*="操作区"]')
76
- .filter({
77
- has: page.locator(':scope > *').first().filter({ hasText: titleRe }),
78
- })
79
- .first();
80
- }
81
-
82
- await resetEditingState();
83
-
84
- const opArea = outlineNode(nodeTitle);
85
- if ((await opArea.count()) === 0) {
86
- return {
87
- ok: false,
88
- route: 'outline',
89
- userIntent,
90
- nodeTitle,
91
- userRequirement,
92
- forbiddenNeverClick: FORBIDDEN_BUTTONS,
93
- reason: `未找到提纲节点「${nodeTitle}」`,
94
- };
95
- }
96
-
97
- await opArea.hover();
98
- await page.waitForTimeout(400);
99
- await opArea.getByRole('button', { name: '编辑' }).click();
100
-
101
- const regenTitle = main.getByText('重新生成', { exact: true });
102
- await regenTitle.waitFor({ state: 'visible', timeout: 10000 });
103
-
104
- const userReqBox = main.getByRole('textbox', { name: '用户需求' });
105
- await userReqBox.waitFor({ state: 'visible', timeout: 5000 });
106
- await userReqBox.fill(userRequirement);
107
-
108
- await main.getByRole('button', { name: '一键生成' }).click();
109
- const replaceBtn = main.getByRole('button', { name: '替换原文' });
110
- await replaceBtn.waitFor({ state: 'visible', timeout: 120000 });
111
- await page.waitForTimeout(800);
112
-
113
- await replaceBtn.click();
114
- await main.getByText('是否将本次生成结果替换原文?', { exact: true }).waitFor({ state: 'visible', timeout: 10000 });
115
- await main.getByRole('button', { name: '确 定' }).click();
116
- await page.waitForTimeout(800);
117
-
118
- const saveBtn = main.getByRole('button', { name: '保 存' });
119
- await saveBtn.waitFor({ state: 'visible', timeout: 10000 });
120
- await saveBtn.click();
121
- await page.waitForTimeout(1000);
122
-
123
- return {
124
- ok: true,
125
- route: 'outline',
126
- userIntent,
127
- nodeTitle,
128
- userRequirement,
129
- clickedButton: '编辑',
130
- forbiddenNeverClick: FORBIDDEN_BUTTONS,
131
- message: `已通过「编辑→重新生成」完成「${userRequirement}」节点「${nodeTitle}」(未使用添加子级)。`,
132
- url: page.url(),
133
- };
134
- }
135
-
136
- // ── proofread / writing:返回子脚本,仍禁止添加子级 ──
137
- if (pathname.includes('/jova/proofread') || isProofreadIntent) {
138
- return {
139
- ok: false,
140
- route: 'proofread',
141
- userIntent,
142
- pathname,
143
- forbiddenNeverClick: FORBIDDEN_BUTTONS,
144
- nextScript: 'scripts/workbench-jova-proofread-section.js',
145
- setIntent: userIntent,
146
- setAction: action,
147
- message: '请改 proofread-section.js 的 intent/action 后 run-code;禁止点添加子级。',
148
- };
149
- }
150
-
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
-
337
- return {
338
- ok: true,
339
- route: 'writing',
340
- userIntent,
341
- tool,
342
- sectionTitle,
343
- needsReplaceChoice: true,
344
- forbiddenNeverClick: FORBIDDEN_BUTTONS,
345
- message: `「${tool}」已生成;用户确认后再设 HELIX_JOVA_ACTION=replace 或说「替换原文」。`,
346
- url: page.url(),
347
- };
348
- }
349
-
350
- if (pathname.includes('/jova/outline')) {
351
- return {
352
- ok: false,
353
- route: 'outline',
354
- userIntent,
355
- pathname,
356
- forbiddenNeverClick: FORBIDDEN_BUTTONS,
357
- nextScript: 'scripts/workbench-jova-outline-regenerate-node.js',
358
- setIntent: userIntent,
359
- reason: 'userIntent 未匹配扩写/精简/润色/改写/重写/优化 前缀',
360
- };
361
- }
362
-
363
- return {
364
- ok: false,
365
- userIntent,
366
- pathname,
367
- forbiddenNeverClick: FORBIDDEN_BUTTONS,
368
- reason: '当前 jova 步骤页不支持本章 AI 操作(topic/proofread 以外)',
369
- };
370
- };