ones-fetch 1.1.0 → 1.2.0

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/README.md CHANGED
@@ -10,6 +10,12 @@
10
10
  npx ones-fetch
11
11
  ```
12
12
 
13
+ 或者使用 npm 安装:
14
+
15
+ ```bash
16
+ npm install -g ones-fetch
17
+ ```
18
+
13
19
  这会自动:
14
20
  - 安装依赖(约 30MB,仅首次需要)
15
21
  - 在桌面创建快捷方式
@@ -17,8 +23,8 @@ npx ones-fetch
17
23
 
18
24
  **注意**:
19
25
  - 依赖只需安装一次,后续启动无需重新安装
20
- - 服务器会在浏览器关闭 5 分钟后自动退出
21
- - 快捷方式使用 Windows 脚本宿主的默认图标
26
+ - 服务器会在浏览器关闭 2 分钟后自动退出
27
+ - 解析任务时会显示实时进度(1/7)
22
28
 
23
29
  ---
24
30
 
package/bin/install.mjs CHANGED
@@ -79,6 +79,37 @@ Categories=Utility;
79
79
  }
80
80
 
81
81
  async function main() {
82
+ // 检测是否是通过 npx 或 postinstall 运行
83
+ const isPostInstall = process.env.npm_lifecycle_event === 'postinstall';
84
+
85
+ if (isPostInstall) {
86
+ // postinstall 时只创建快捷方式,不安装依赖
87
+ console.log('ONES Fetch 安装后配置\n');
88
+ console.log('正在创建桌面快捷方式...');
89
+ try {
90
+ const os = platform();
91
+ if (os === 'win32') {
92
+ await createWindowsShortcut();
93
+ } else if (os === 'darwin') {
94
+ await createMacShortcut();
95
+ } else {
96
+ await createLinuxShortcut();
97
+ }
98
+ } catch (err) {
99
+ console.error('✗ 快捷方式创建失败:', err.message);
100
+ // 不退出,允许安装继续
101
+ }
102
+ console.log('\n✓ 配置完成!');
103
+ console.log('\n使用方法:');
104
+ console.log(' 1. 双击桌面上的 "ONES 采集工具" 图标');
105
+ console.log(' 2. 浏览器会自动打开工具页面');
106
+ console.log('\n或者在命令行运行:');
107
+ console.log(` cd ${projectRoot}`);
108
+ console.log(' npm start');
109
+ return;
110
+ }
111
+
112
+ // npx 运行时的完整安装流程
82
113
  console.log('ONES Fetch 安装程序\n');
83
114
 
84
115
  // 安装依赖
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ones-fetch",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "ONES Fetch — Web app for recursive ONES subtask crawling",
6
6
  "bin": {
@@ -8,7 +8,8 @@
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node src/server.mjs",
11
- "dev": "node --watch src/server.mjs"
11
+ "dev": "node --watch src/server.mjs",
12
+ "postinstall": "node bin/install.mjs"
12
13
  },
13
14
  "dependencies": {
14
15
  "playwright-core": "^1.58.2"
package/public/index.html CHANGED
@@ -49,6 +49,9 @@
49
49
  .spinner { display: inline-block; width: 18px; height: 18px; border: 2px solid #e5e7eb; border-top-color: #6366f1; border-radius: 50%; animation: spin .7s linear infinite; vertical-align: middle; margin-right: 6px; }
50
50
  @keyframes spin { to { transform: rotate(360deg); } }
51
51
  .copied { background: #10b981 !important; }
52
+ .progress-bar { width: 100%; height: 6px; background: #e5e7eb; border-radius: 3px; overflow: hidden; margin-top: 8px; }
53
+ .progress-fill { height: 100%; background: linear-gradient(90deg, #6366f1, #8b5cf6); transition: width 0.3s ease; border-radius: 3px; }
54
+ .progress-text { font-size: 0.8125rem; color: #6b7280; margin-top: 4px; }
52
55
  </style>
53
56
  </head>
54
57
  <body>
@@ -65,6 +68,12 @@
65
68
  <span class="status" id="status"></span>
66
69
  </div>
67
70
  <div class="hint" id="authHint"></div>
71
+ <div id="progressContainer" style="display:none">
72
+ <div class="progress-bar">
73
+ <div class="progress-fill" id="progressFill" style="width:0%"></div>
74
+ </div>
75
+ <div class="progress-text" id="progressText"></div>
76
+ </div>
68
77
  <div class="preview-chips" id="chips"></div>
69
78
  </div>
70
79
 
@@ -174,6 +183,22 @@ function renderChips(taskIds) {
174
183
  el.innerHTML = taskIds.map(id => `<span class="chip">${id}</span>`).join('');
175
184
  }
176
185
 
186
+ function updateProgress(current, total, message) {
187
+ const container = document.getElementById('progressContainer');
188
+ const fill = document.getElementById('progressFill');
189
+ const text = document.getElementById('progressText');
190
+
191
+ if (total === 0) {
192
+ container.style.display = 'none';
193
+ return;
194
+ }
195
+
196
+ container.style.display = 'block';
197
+ const percent = Math.round((current / total) * 100);
198
+ fill.style.width = percent + '%';
199
+ text.textContent = `${message} (${current}/${total})`;
200
+ }
201
+
177
202
  async function parse() {
178
203
  const text = document.getElementById('input').value.trim();
179
204
  if (!text) { setStatus('请先粘贴文本', true); return; }
@@ -185,40 +210,60 @@ async function parse() {
185
210
  setStatus(`已提取 ${taskIds.length} 个任务 ID,正在抓取...`);
186
211
  setAuthHint(context.baseUrl ? `已识别站点:${context.baseUrl}${context.teamId ? `,团队:${context.teamId}` : ''}` : '');
187
212
  document.getElementById('parseBtn').disabled = true;
213
+ updateProgress(0, taskIds.length, '准备开始');
188
214
  document.getElementById('tbody').innerHTML = '<tr><td colspan="5" class="loading"><span class="spinner"></span>加载中...</td></tr>';
189
215
  document.getElementById('resultsSection').style.display = '';
190
216
 
191
217
  try {
192
- const resp = await fetch('/api/crawl', {
193
- method: 'POST',
194
- headers: { 'Content-Type': 'application/json' },
195
- body: JSON.stringify({ taskIds, baseUrl: authContext.baseUrl || context.baseUrl || undefined, teamId: authContext.teamId || context.teamId || undefined }),
196
- });
197
- const data = await resp.json();
198
- if (!resp.ok) {
199
- if (data.error === 'auth_required' || data.error === 'token_expired') {
200
- updateAuthContext(data);
201
- showAuthButton(true, data.error === 'token_expired' ? '重新连接 ONES' : '连接 ONES');
202
- setAuthHint(`将打开一个浏览器窗口用于登录 ONES,完成后会自动重试。${authContext.baseUrl ? ` 当前站点:${authContext.baseUrl}` : ''}`);
218
+ // 逐个解析任务以显示进度
219
+ const allTasks = [];
220
+ const roots = [];
221
+
222
+ for (let i = 0; i < taskIds.length; i++) {
223
+ updateProgress(i + 1, taskIds.length, `正在解析任务 ${taskIds[i]}`);
224
+
225
+ const resp = await fetch('/api/crawl', {
226
+ method: 'POST',
227
+ headers: { 'Content-Type': 'application/json' },
228
+ body: JSON.stringify({
229
+ taskIds: [taskIds[i]],
230
+ baseUrl: authContext.baseUrl || context.baseUrl || undefined,
231
+ teamId: authContext.teamId || context.teamId || undefined
232
+ }),
233
+ });
234
+ const data = await resp.json();
235
+ if (!resp.ok) {
236
+ if (data.error === 'auth_required' || data.error === 'token_expired') {
237
+ updateAuthContext(data);
238
+ showAuthButton(true, data.error === 'token_expired' ? '重新连接 ONES' : '连接 ONES');
239
+ setAuthHint(`将打开一个浏览器窗口用于登录 ONES,完成后会自动重试。${authContext.baseUrl ? ` 当前站点:${authContext.baseUrl}` : ''}`);
240
+ }
241
+ const msg = data.error === 'token_expired'
242
+ ? '登录状态已过期,请点击”连接 ONES”重新登录'
243
+ : data.error === 'auth_required'
244
+ ? '当前还没有可用的 ONES 凭据,请先连接 ONES'
245
+ : (data.detail ?? data.error ?? '抓取失败');
246
+ setStatus(msg, true);
247
+ document.getElementById('tbody').innerHTML = `<tr><td colspan=”5” class=”empty”>${msg}</td></tr>`;
248
+ updateProgress(0, 0, '');
249
+ return;
203
250
  }
204
- const msg = data.error === 'token_expired'
205
- ? '登录状态已过期,请点击“连接 ONES”重新登录'
206
- : data.error === 'auth_required'
207
- ? '当前还没有可用的 ONES 凭据,请先连接 ONES'
208
- : (data.detail ?? data.error ?? '抓取失败');
209
- setStatus(msg, true);
210
- document.getElementById('tbody').innerHTML = `<tr><td colspan="5" class="empty">${msg}</td></tr>`;
211
- return;
251
+
252
+ if (data.roots) roots.push(...data.roots);
253
+ if (data.tasks) allTasks.push(...data.tasks);
212
254
  }
255
+
213
256
  showAuthButton(false);
214
- updateAuthContext(data);
215
- allTasks = data.tasks ?? [];
257
+ updateAuthContext({ baseUrl: authContext.baseUrl, teamId: authContext.teamId });
258
+ allTasks = allTasks;
216
259
  collapsed = new Set();
217
260
  renderTable();
218
261
  setStatus(`共获取 ${allTasks.length} 条任务`);
262
+ updateProgress(0, 0, '');
219
263
  document.getElementById('countLabel').textContent = `共 ${allTasks.length} 条`;
220
264
  } catch (e) {
221
265
  setStatus('网络错误:' + e.message, true);
266
+ updateProgress(0, 0, '');
222
267
  } finally {
223
268
  document.getElementById('parseBtn').disabled = false;
224
269
  }
@@ -382,6 +427,7 @@ function clearAll() {
382
427
  document.getElementById('status').textContent = '';
383
428
  setAuthHint('');
384
429
  showAuthButton(false);
430
+ updateProgress(0, 0, '');
385
431
  authContext = { baseUrl: '', teamId: '' };
386
432
  document.getElementById('resultsSection').style.display = 'none';
387
433
  allTasks = [];