yiyan-browser-agent 1.6.2 → 1.6.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/browser.js +78 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yiyan-browser-agent",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "AI coding agent powered by Yiyan (文心一言) via browser automation — no API key needed",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/browser.js CHANGED
@@ -293,9 +293,10 @@ class YiyanBrowser {
293
293
 
294
294
  if (!appeared) logger.warn('Response may have been delayed — continuing to wait...');
295
295
 
296
- // Phase 2: wait for text to stabilise
296
+ // Phase 2: wait for text to stabilise (with multiple checks)
297
297
  let lastText = '';
298
298
  let stableStart = null;
299
+ let stableCount = 0; // 连续稳定次数计数
299
300
  let dotCount = 0;
300
301
 
301
302
  while (Date.now() - start < timeout) {
@@ -304,19 +305,32 @@ class YiyanBrowser {
304
305
  if (text !== lastText) {
305
306
  lastText = text;
306
307
  stableStart = null;
308
+ stableCount = 0; // 内容变化,重置计数
307
309
  } else if (text.length > 0) {
308
310
  if (!stableStart) stableStart = Date.now();
309
311
  else if (Date.now() - stableStart >= stableDelay) {
310
- if (!await this._isGenerating()) break;
311
- stableStart = null;
312
+ // 增加稳定性计数
313
+ stableCount++;
314
+
315
+ // 需要连续稳定检测 + _isGenerating 确认
316
+ if (stableCount >= 2) { // 连续 2 次稳定检测
317
+ if (!await this._isGenerating()) break;
318
+ stableCount = 0; // _isGenerating 说还在生成,重置
319
+ stableStart = null;
320
+ } else {
321
+ // 第一次稳定,继续等待下一次检测
322
+ stableStart = null;
323
+ }
312
324
  }
313
325
  }
314
326
 
315
327
  // Progress indicator
316
328
  dotCount = (dotCount + 1) % 4;
317
329
  logger.thinking(`Receiving response${'.'.repeat(dotCount)} (${text.length} chars)`);
318
- }
330
+
331
+ // 等待 200ms 给 DOM 时间更新,避免高频轮询读取不完整内容
319
332
  await this.page.waitForTimeout(200);
333
+ }
320
334
 
321
335
  logger.clearLine();
322
336
 
@@ -477,11 +491,14 @@ class YiyanBrowser {
477
491
 
478
492
  async _isGenerating() {
479
493
  return await this.page.evaluate(() => {
494
+ // ── 1. 检测停止按钮/生成状态 UI ──
480
495
  const stopSelectors = [
481
496
  'button[aria-label*="Stop" i]',
497
+ 'button[aria-label*="停止"]',
482
498
  '[class*="stop-gen"]',
483
499
  '[class*="stopGen"]',
484
500
  '[class*="generating"]',
501
+ '[class*=" Generating"]', // Yiyan 可能的 class
485
502
  ];
486
503
  for (const sel of stopSelectors) {
487
504
  const el = document.querySelector(sel);
@@ -491,6 +508,7 @@ class YiyanBrowser {
491
508
  }
492
509
  }
493
510
 
511
+ // ── 2. 检测加载动画/typing指示器 ──
494
512
  const loaderSelectors = [
495
513
  '[class*="typing"]',
496
514
  '[class*="loading"]',
@@ -498,8 +516,12 @@ class YiyanBrowser {
498
516
  '[class*="blink"]',
499
517
  '[class*="cursor"]',
500
518
  '[class*="pulsing"]',
519
+ '[class*="thinking"]',
501
520
  'svg[class*="loading"]',
502
521
  'svg[class*="spinner"]',
522
+ // Yiyan 特有的加载指示
523
+ '.loading-indicator',
524
+ '.generating-indicator',
503
525
  ];
504
526
  for (const sel of loaderSelectors) {
505
527
  const el = document.querySelector(sel);
@@ -509,6 +531,58 @@ class YiyanBrowser {
509
531
  }
510
532
  }
511
533
 
534
+ // ── 3. 检测输入框状态(生成时通常禁用)──
535
+ const inputSelectors = [
536
+ '[contenteditable="true"]',
537
+ 'textarea',
538
+ '[role="textbox"]',
539
+ '.editable',
540
+ ];
541
+ for (const sel of inputSelectors) {
542
+ const input = document.querySelector(sel);
543
+ if (input) {
544
+ // 检查是否禁用
545
+ if (input.disabled) return true;
546
+ if (input.getAttribute('aria-disabled') === 'true') return true;
547
+ if (input.getAttribute('disabled') !== null) return true;
548
+
549
+ // 检查是否只读(有时会用 readonly 表示生成中)
550
+ if (input.readOnly || input.getAttribute('readonly') !== null) {
551
+ // 但要排除本来就是 readonly 的输入框
552
+ // 这里暂时不返回 true,需要更多测试
553
+ }
554
+ }
555
+ }
556
+
557
+ // ── 4. 检测是否缺少完成标记元素 ──
558
+ // 如果没有复制按钮/重新生成按钮,可能还在生成
559
+ const completionMarkers = [
560
+ '[class*="copy-btn"]',
561
+ '[class*="copyBtn"]',
562
+ '[aria-label*="Copy" i]',
563
+ '[aria-label*="复制"]',
564
+ '[class*="regenerate"]',
565
+ '[class*="retry"]',
566
+ '[class*="action-btn"]', // Yiyan 的操作按钮区域
567
+ ];
568
+ let hasCompletionMarker = false;
569
+ for (const sel of completionMarkers) {
570
+ if (document.querySelector(sel)) {
571
+ hasCompletionMarker = true;
572
+ break;
573
+ }
574
+ }
575
+
576
+ // 如果有响应内容但没有完成标记 → 可能还在生成
577
+ // 这个检测放在后面,避免误判空响应
578
+ const answerArea = document.querySelector('[class*="answer"], [class*="response"], [class*="markdown"]');
579
+ if (answerArea && answerArea.innerText && answerArea.innerText.length > 50) {
580
+ if (!hasCompletionMarker) {
581
+ // 有内容但没有完成按钮,可能还在生成
582
+ return true;
583
+ }
584
+ }
585
+
512
586
  return false;
513
587
  });
514
588
  }