@trim21/personal-pi-extensions 0.1.512 → 0.1.513
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/package.json +1 -1
- package/src/aft/tools.ts +99 -6
package/package.json
CHANGED
package/src/aft/tools.ts
CHANGED
|
@@ -444,18 +444,20 @@ const semanticIndexProgressSchema = Type.Object({
|
|
|
444
444
|
|
|
445
445
|
type SemanticIndexProgress = Static<typeof semanticIndexProgressSchema>;
|
|
446
446
|
|
|
447
|
-
/**
|
|
448
|
-
|
|
447
|
+
/** 解析快照里的语义索引构建进度;非 Building 或形状不符返回 undefined(不显示)。 */
|
|
448
|
+
function parseSemanticIndexProgress(snapshot: StatusSnapshot): SemanticIndexProgress | undefined {
|
|
449
449
|
const raw = snapshot.semantic_index;
|
|
450
450
|
if (raw === undefined) {
|
|
451
451
|
return undefined;
|
|
452
452
|
}
|
|
453
|
-
let progress: SemanticIndexProgress;
|
|
454
453
|
try {
|
|
455
|
-
|
|
454
|
+
return Value.Parse(semanticIndexProgressSchema, raw);
|
|
456
455
|
} catch {
|
|
457
456
|
return undefined;
|
|
458
457
|
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function semanticIndexProgressParts(progress: SemanticIndexProgress): string[] {
|
|
459
461
|
const parts = [`语义索引构建中${progress.stage === undefined ? "" : ` (${progress.stage})`}`];
|
|
460
462
|
if (
|
|
461
463
|
progress.embedded_chunks !== undefined &&
|
|
@@ -475,7 +477,97 @@ export function formatSemanticIndexProgress(snapshot: StatusSnapshot): string |
|
|
|
475
477
|
) {
|
|
476
478
|
parts.push(`batch ${progress.current_batch}/${progress.total_batches}`);
|
|
477
479
|
}
|
|
478
|
-
return parts
|
|
480
|
+
return parts;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** status 快照 → 一行构建进度文本(不含 ETA)。 */
|
|
484
|
+
export function formatSemanticIndexProgress(snapshot: StatusSnapshot): string | undefined {
|
|
485
|
+
const progress = parseSemanticIndexProgress(snapshot);
|
|
486
|
+
if (progress === undefined) {
|
|
487
|
+
return undefined;
|
|
488
|
+
}
|
|
489
|
+
return semanticIndexProgressParts(progress).join(" · ");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** eta 速率估计的滑动窗口:只看最近这段时间的样本,丢弃更早的。 */
|
|
493
|
+
const ETA_WINDOW_MS = 15_000;
|
|
494
|
+
/** 样本跨度过短时速率噪声太大(快照约每秒一条),不显示 ETA。 */
|
|
495
|
+
const ETA_MIN_WINDOW_MS = 3_000;
|
|
496
|
+
/** 滑动窗口里最多保留的样本数,防快照异常密集时无界增长。 */
|
|
497
|
+
const ETA_MAX_SAMPLES = 30;
|
|
498
|
+
|
|
499
|
+
interface EtaSample {
|
|
500
|
+
readonly at: number;
|
|
501
|
+
readonly embedded: number;
|
|
502
|
+
readonly stage: string | undefined;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function formatEta(ms: number): string {
|
|
506
|
+
// 取整到 5s 粒度,避免给出虚假的精确感。
|
|
507
|
+
const seconds = Math.max(5, Math.round(ms / 1000 / 5) * 5);
|
|
508
|
+
if (seconds < 60) {
|
|
509
|
+
return `${seconds}s`;
|
|
510
|
+
}
|
|
511
|
+
const minutes = Math.floor(seconds / 60);
|
|
512
|
+
const rest = seconds % 60;
|
|
513
|
+
return rest === 0 ? `${minutes}m` : `${minutes}m${rest}s`;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* 带剩余时间估计的进度格式化器。基于快照序列做滑动窗口速率外推
|
|
518
|
+
* (embedding 批次速率受远端 API 波动影响并非线性,窗口越近越准)。
|
|
519
|
+
* total_chunks / total_batches 变化只影响分母,批次处理速率不变,
|
|
520
|
+
* 直接用新 total 重算 ETA,不清空样本;仅 stage 变化(换工作内容)或
|
|
521
|
+
* embedded 回退(watcher 重建)时重置。工厂 + 闭包持有状态:每次
|
|
522
|
+
* 工具调用 / CLI 运行创建独立实例。
|
|
523
|
+
*/
|
|
524
|
+
export function createSemanticIndexProgressFormatter(options?: {
|
|
525
|
+
now?: () => number;
|
|
526
|
+
}): (snapshot: StatusSnapshot) => string | undefined {
|
|
527
|
+
const now = options?.now ?? Date.now;
|
|
528
|
+
let samples: EtaSample[] = [];
|
|
529
|
+
|
|
530
|
+
return (snapshot: StatusSnapshot): string | undefined => {
|
|
531
|
+
const progress = parseSemanticIndexProgress(snapshot);
|
|
532
|
+
if (progress === undefined) {
|
|
533
|
+
return undefined;
|
|
534
|
+
}
|
|
535
|
+
const parts = semanticIndexProgressParts(progress);
|
|
536
|
+
|
|
537
|
+
const { embedded_chunks: embedded, total_chunks: total, stage } = progress;
|
|
538
|
+
const last = samples.at(-1);
|
|
539
|
+
if (last !== undefined && last.stage !== stage) {
|
|
540
|
+
samples = [];
|
|
541
|
+
}
|
|
542
|
+
if (embedded !== undefined && total !== undefined && total > 0) {
|
|
543
|
+
if (last !== undefined && embedded < last.embedded) {
|
|
544
|
+
samples = [];
|
|
545
|
+
}
|
|
546
|
+
const at = now();
|
|
547
|
+
samples.push({ at, embedded, stage });
|
|
548
|
+
samples = samples.filter((sample) => at - sample.at <= ETA_WINDOW_MS);
|
|
549
|
+
if (samples.length > ETA_MAX_SAMPLES) {
|
|
550
|
+
samples = samples.slice(-ETA_MAX_SAMPLES);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const first = samples.at(0);
|
|
555
|
+
const current = samples.at(-1);
|
|
556
|
+
if (
|
|
557
|
+
first !== undefined &&
|
|
558
|
+
current !== undefined &&
|
|
559
|
+
total !== undefined &&
|
|
560
|
+
first !== current &&
|
|
561
|
+
current.at - first.at >= ETA_MIN_WINDOW_MS
|
|
562
|
+
) {
|
|
563
|
+
const rate = (current.embedded - first.embedded) / (current.at - first.at);
|
|
564
|
+
const remaining = total - current.embedded;
|
|
565
|
+
if (rate > 0 && remaining > 0) {
|
|
566
|
+
parts.push(`剩余约 ${formatEta(remaining / rate)}`);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return parts.join(" · ");
|
|
570
|
+
};
|
|
479
571
|
}
|
|
480
572
|
|
|
481
573
|
const SearchParams = Type.Object(
|
|
@@ -520,11 +612,12 @@ export function registerSearchTool(pi: ExtensionAPI, ctx: AftToolContext): void
|
|
|
520
612
|
});
|
|
521
613
|
|
|
522
614
|
const bridge = bridgeFor(ctx);
|
|
615
|
+
const formatProgress = createSemanticIndexProgressFormatter();
|
|
523
616
|
const stopProgress =
|
|
524
617
|
onUpdate === undefined
|
|
525
618
|
? undefined
|
|
526
619
|
: subscribeBridgeStatus(bridge, (snapshot) => {
|
|
527
|
-
const text =
|
|
620
|
+
const text = formatProgress(snapshot);
|
|
528
621
|
if (text !== undefined) {
|
|
529
622
|
onUpdate({ content: [{ type: "text", text }], details: undefined });
|
|
530
623
|
}
|