pi-cc-extensions 0.8.64 → 0.8.65
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.en.md +29 -15
- package/README.md +31 -23
- package/extensions/feature/context.ts +209 -158
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -51,24 +51,38 @@ Run `/reload` after installation.
|
|
|
51
51
|
|
|
52
52
|
```js
|
|
53
53
|
{
|
|
54
|
-
"mode": "on", // on
|
|
55
|
-
"excludeRenderers": [], //
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"
|
|
54
|
+
"mode": "on", // on / compact / off
|
|
55
|
+
"excludeRenderers": [], // tools keeping the native renderer; Agent always keeps its dedicated renderer
|
|
56
|
+
|
|
57
|
+
// diff
|
|
58
|
+
"diffViewMode": "auto", // layout: auto / split / unified
|
|
59
|
+
"diffIndicatorMode": "bars", // change indicators: bars / classic / none
|
|
60
|
+
"diffSplitMinWidth": 120, // min terminal width for side-by-side columns
|
|
61
|
+
"editDiffCollapsedLines": 24, // Edit collapse lines; beyond that shows the expand hint
|
|
62
|
+
"writeDiffCollapsedLines": 0, // write collapse lines; 0 = creation summary only
|
|
63
|
+
"diffWordWrap": true, // wrap long diff lines
|
|
64
|
+
"expandedPreviewMaxLines": 40, // max lines for expanded bodies
|
|
65
|
+
|
|
66
|
+
// thinking
|
|
67
|
+
"useSummaryTitlesAsThinkingTitle": true, // use latest summary as thinking title
|
|
68
|
+
"previewLines": 3, // preview lines; 0 hides
|
|
69
|
+
"animationIntervalMs": 90, // title animation interval (ms)
|
|
70
|
+
|
|
71
|
+
// ui
|
|
72
|
+
"showStartupHeader": true, // startup header (logo + tips) toggle
|
|
73
|
+
"scrollStepLines": 3, // fullscreen wheel scroll step
|
|
74
|
+
|
|
75
|
+
// features
|
|
76
|
+
"enableSessionReference": true, // @ session references
|
|
77
|
+
"enableSubagentAutocomplete": true, // @ subagent completion and delegation hints
|
|
78
|
+
"enableContextCommand": true, // /context usage check
|
|
79
|
+
"enableAgentSummary": true, // per-turn tool summary
|
|
80
|
+
"enableWorkingMessage": true, // Working... bottom token/elapsed
|
|
81
|
+
"enableAliases": true // /clear, /exit aliases
|
|
68
82
|
}
|
|
69
83
|
```
|
|
70
84
|
|
|
71
|
-
> **Fullscreen
|
|
85
|
+
> **Fullscreen**: click `click to show more` to expand tool cards, thinking, Skill, and compact summaries. Double-click an expanded panel to collapse it.
|
|
72
86
|
>
|
|
73
87
|
> **Tip**: set `markdown.mermaid` to `final` via `~/.pi/agent/settings.json` or the Mermaid diagrams option in `/settings`. Default `streaming` redraws per frame; `final` renders once at completion.
|
|
74
88
|
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="./assets/readme/hero.svg" width="100%"
|
|
2
|
+
<img src="./assets/readme/hero.svg" width="100%">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
@@ -51,30 +51,38 @@ pi install git:github.com/minuque/pi-cc-extensions
|
|
|
51
51
|
|
|
52
52
|
```js
|
|
53
53
|
{
|
|
54
|
-
"mode": "on", // on
|
|
55
|
-
"excludeRenderers": [], //
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
"
|
|
54
|
+
"mode": "on", // on / compact / off
|
|
55
|
+
"excludeRenderers": [], // 走原生渲染的工具名;Agent 始终保留专用渲染器
|
|
56
|
+
|
|
57
|
+
// diff
|
|
58
|
+
"diffViewMode": "auto", // 布局:auto / split / unified
|
|
59
|
+
"diffIndicatorMode": "bars", // 变更指示:bars / classic / none
|
|
60
|
+
"diffSplitMinWidth": 120, // 左右分栏的最小终端宽度
|
|
61
|
+
"editDiffCollapsedLines": 24, // Edit 折叠行数,超出显示展开提示
|
|
62
|
+
"writeDiffCollapsedLines": 0, // write 折叠行数,0 仅显示创建摘要
|
|
63
|
+
"diffWordWrap": true, // 长 diff 行换行
|
|
64
|
+
"expandedPreviewMaxLines": 40, // 展开正文最大行数
|
|
65
|
+
|
|
66
|
+
// thinking
|
|
67
|
+
"useSummaryTitlesAsThinkingTitle": true, // 用最新摘要作思考标题
|
|
68
|
+
"previewLines": 3, // 预览行数,0 隐藏
|
|
69
|
+
"animationIntervalMs": 90, // 标题动画间隔(毫秒)
|
|
70
|
+
|
|
71
|
+
// ui
|
|
72
|
+
"showStartupHeader": true, // 启动头(logo + tips)开关
|
|
73
|
+
"scrollStepLines": 3, // fullscreen 滚轮步进
|
|
74
|
+
|
|
75
|
+
// features
|
|
76
|
+
"enableSessionReference": true, // @ session 引用
|
|
77
|
+
"enableSubagentAutocomplete": true, // @ subagent 补全与委派提示
|
|
78
|
+
"enableContextCommand": true, // /context 上下文检查
|
|
79
|
+
"enableAgentSummary": true, // 每回合工具摘要
|
|
80
|
+
"enableWorkingMessage": true, // Working... 底部 token/耗时
|
|
81
|
+
"enableAliases": true // /clear、/exit 别名
|
|
74
82
|
}
|
|
75
83
|
```
|
|
76
84
|
|
|
77
|
-
> **Fullscreen
|
|
85
|
+
> **Fullscreen**:单击 `click to show more` 展开工具卡、思考、Skill 和 compact 摘要,双击展开面板收起。
|
|
78
86
|
>
|
|
79
87
|
> **建议**:`markdown.mermaid` 设为 `final`(`~/.pi/agent/settings.json` 或 `/settings` 面板的 Mermaid diagrams 选项)。默认 `streaming` 逐帧重绘,`final` 渲染最终版更稳定。
|
|
80
88
|
|
|
@@ -88,7 +96,7 @@ npm run typecheck
|
|
|
88
96
|
|
|
89
97
|
## 兼容性
|
|
90
98
|
|
|
91
|
-
- Node.js `>=22.19.0`,Pi `^0.84.0
|
|
99
|
+
- Node.js `>=22.19.0`,Pi `^0.84.0`
|
|
92
100
|
|
|
93
101
|
## 推荐搭配
|
|
94
102
|
|
|
@@ -13,10 +13,10 @@ import { padLine } from "../utils/format.ts";
|
|
|
13
13
|
export type ContextPart = {
|
|
14
14
|
label: string;
|
|
15
15
|
tokens: number;
|
|
16
|
-
color: "accent" | "success" | "warning" | "customMessageLabel" | "muted" | "dim";
|
|
16
|
+
color: "accent" | "success" | "warning" | "customMessageLabel" | "muted" | "dim" | "error";
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
type PreviewKey = "systemPrompt" | "tools" | "toolResults" | "contextFiles";
|
|
19
|
+
type PreviewKey = "systemPrompt" | "memoryFiles" | "tools" | "toolResults" | "contextFiles";
|
|
20
20
|
|
|
21
21
|
type ContextPreview = {
|
|
22
22
|
key: PreviewKey;
|
|
@@ -47,11 +47,16 @@ export function escCloseHitbox(bounds: DialogBounds): {
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
let
|
|
50
|
+
let activeContextOverlays = 0;
|
|
51
51
|
|
|
52
|
-
/** fullscreen
|
|
52
|
+
/** fullscreen 输入包装用于把鼠标事件继续传给当前 context 主弹框或文本预览 overlay。 */
|
|
53
53
|
export function hasActiveTextPreview(): boolean {
|
|
54
|
-
return
|
|
54
|
+
return activeContextOverlays > 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 官方 fullscreen 打开 overlay 时会退回 1002;重新启用 1003 才能收到无按键 hover。 */
|
|
58
|
+
function ensureFullscreenMouseMotion(tui: any): void {
|
|
59
|
+
if (tui.mode === "fullscreen") tui.terminal?.write?.("\x1b[?1003h\x1b[?1006h");
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
export async function showTextPreview(
|
|
@@ -60,10 +65,11 @@ export async function showTextPreview(
|
|
|
60
65
|
rawContent: string,
|
|
61
66
|
): Promise<void> {
|
|
62
67
|
const content = normalizePreviewText(rawContent);
|
|
63
|
-
|
|
68
|
+
activeContextOverlays++;
|
|
64
69
|
try {
|
|
65
70
|
await ctx.ui.custom(
|
|
66
71
|
(tui, theme, _keybindings, done) => {
|
|
72
|
+
ensureFullscreenMouseMotion(tui);
|
|
67
73
|
let scrollOffset = 0;
|
|
68
74
|
let pageSize = 1;
|
|
69
75
|
let totalLines = 1;
|
|
@@ -249,7 +255,7 @@ export async function showTextPreview(
|
|
|
249
255
|
},
|
|
250
256
|
);
|
|
251
257
|
} finally {
|
|
252
|
-
|
|
258
|
+
activeContextOverlays--;
|
|
253
259
|
}
|
|
254
260
|
}
|
|
255
261
|
|
|
@@ -337,6 +343,13 @@ export function collectContextBreakdown(
|
|
|
337
343
|
let toolResultTokens = 0;
|
|
338
344
|
let contextTokens = 0;
|
|
339
345
|
|
|
346
|
+
const memoryPreview: string[] = [];
|
|
347
|
+
let memoryTokens = 0;
|
|
348
|
+
for (const file of options.contextFiles ?? []) {
|
|
349
|
+
memoryTokens += tokenEstimate(file.content);
|
|
350
|
+
memoryPreview.push(`## ${file.path}\n\n${previewValue(file.content)}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
340
353
|
for (const tool of allTools) {
|
|
341
354
|
if (!selectedTools.has(tool.name)) continue;
|
|
342
355
|
const definition = {
|
|
@@ -397,12 +410,14 @@ export function collectContextBreakdown(
|
|
|
397
410
|
return {
|
|
398
411
|
parts: [
|
|
399
412
|
{ label: "System prompt", tokens: tokenEstimate(systemPrompt), color: "accent" },
|
|
413
|
+
{ label: "Memory", tokens: memoryTokens, color: "error" },
|
|
400
414
|
{ label: "Tools", tokens: toolDefinitionTokens, color: "success" },
|
|
401
415
|
{ label: "Tool results", tokens: toolResultTokens, color: "customMessageLabel" },
|
|
402
416
|
{ label: "Context", tokens: contextTokens, color: "warning" },
|
|
403
417
|
] satisfies ContextPart[],
|
|
404
418
|
previews: {
|
|
405
419
|
systemPrompt: systemPrompt || "No system prompt.",
|
|
420
|
+
memoryFiles: memoryPreview.join("\n\n") || "No memory files in context.",
|
|
406
421
|
tools: toolDefinitionPreview.join("\n\n") || "No active tool definitions.",
|
|
407
422
|
toolResults: toolResultPreview.join("\n\n") || "No tool results in the current context.",
|
|
408
423
|
contextFiles: contextPreview.join("\n\n") || "No conversation context.",
|
|
@@ -419,9 +434,10 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
|
|
|
419
434
|
const tools = pi.getAllTools();
|
|
420
435
|
const breakdown = collectContextBreakdown(ctx, tools);
|
|
421
436
|
const estimated = breakdown.parts.reduce((sum, part) => sum + part.tokens, 0);
|
|
422
|
-
|
|
437
|
+
// System prompt、Memory、Tools 为固定项,capParts 时保持原值不压缩。
|
|
438
|
+
const fixedTokens = breakdown.parts.slice(0, 3).reduce((sum, part) => sum + part.tokens, 0);
|
|
423
439
|
const used = Math.max(resolveUsedTokens(usage, estimated, contextWindow), fixedTokens);
|
|
424
|
-
const parts = capParts(breakdown.parts, used,
|
|
440
|
+
const parts = capParts(breakdown.parts, used, 3);
|
|
425
441
|
const attributed = parts.reduce((sum, part) => sum + part.tokens, 0);
|
|
426
442
|
const other = Math.max(0, used - attributed);
|
|
427
443
|
const free = Math.max(0, contextWindow - used);
|
|
@@ -444,6 +460,12 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
|
|
|
444
460
|
title: "System Prompt",
|
|
445
461
|
content: breakdown.previews.systemPrompt,
|
|
446
462
|
},
|
|
463
|
+
{
|
|
464
|
+
key: "memoryFiles",
|
|
465
|
+
label: "Memory",
|
|
466
|
+
title: "Memory Files",
|
|
467
|
+
content: breakdown.previews.memoryFiles,
|
|
468
|
+
},
|
|
447
469
|
{
|
|
448
470
|
key: "tools",
|
|
449
471
|
label: "Tools",
|
|
@@ -474,158 +496,187 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
|
|
|
474
496
|
let selectedPreviewIndex = 0;
|
|
475
497
|
|
|
476
498
|
while (true) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
mouse.
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
499
|
+
// 主弹框也计入活动 overlay,fullscreen 下官方输入链才会把鼠标包放行给行点击。
|
|
500
|
+
activeContextOverlays++;
|
|
501
|
+
let action;
|
|
502
|
+
try {
|
|
503
|
+
action = await ctx.ui.custom(
|
|
504
|
+
(tui, theme, _keybindings, done) => {
|
|
505
|
+
ensureFullscreenMouseMotion(tui);
|
|
506
|
+
let previewHitboxes: Array<{
|
|
507
|
+
key: PreviewKey;
|
|
508
|
+
row: number;
|
|
509
|
+
startCol: number;
|
|
510
|
+
endCol: number;
|
|
511
|
+
}> = [];
|
|
512
|
+
let escHitbox: { row: number; startCol: number; endCol: number } | undefined;
|
|
513
|
+
let escHovered = false;
|
|
514
|
+
let hoveredKey: PreviewKey | undefined;
|
|
515
|
+
|
|
516
|
+
return {
|
|
517
|
+
invalidate() {},
|
|
518
|
+
handleInput(data: string) {
|
|
519
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
|
|
520
|
+
done(undefined);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (matchesKey(data, Key.up) && visiblePreviews.length > 0) {
|
|
524
|
+
selectedPreviewIndex =
|
|
525
|
+
(selectedPreviewIndex - 1 + visiblePreviews.length) % visiblePreviews.length;
|
|
526
|
+
tui.requestRender();
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (matchesKey(data, Key.down) && visiblePreviews.length > 0) {
|
|
530
|
+
selectedPreviewIndex = (selectedPreviewIndex + 1) % visiblePreviews.length;
|
|
531
|
+
tui.requestRender();
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
if (matchesKey(data, Key.enter)) {
|
|
535
|
+
done(visiblePreviews[selectedPreviewIndex]?.key);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const mouse = parseSgrMousePacket(data);
|
|
540
|
+
if (!mouse || mouse.final !== "M") return;
|
|
541
|
+
if ((mouse.code & 32) !== 0) {
|
|
542
|
+
// SGR 1003 的无按键 hover code 为 35,不能按左键事件过滤。
|
|
543
|
+
const overEsc = Boolean(
|
|
544
|
+
escHitbox &&
|
|
545
|
+
mouse.row === escHitbox.row &&
|
|
546
|
+
mouse.col >= escHitbox.startCol &&
|
|
547
|
+
mouse.col <= escHitbox.endCol,
|
|
548
|
+
);
|
|
549
|
+
const hovered = previewHitboxes.find(
|
|
550
|
+
(candidate) =>
|
|
551
|
+
mouse.row === candidate.row &&
|
|
552
|
+
mouse.col >= candidate.startCol &&
|
|
553
|
+
mouse.col <= candidate.endCol,
|
|
554
|
+
);
|
|
555
|
+
if (overEsc !== escHovered || hovered?.key !== hoveredKey) {
|
|
556
|
+
escHovered = overEsc;
|
|
557
|
+
hoveredKey = hovered?.key;
|
|
558
|
+
tui.requestRender();
|
|
559
|
+
}
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (mouseBaseButton(mouse.code) !== 0) return;
|
|
563
|
+
if (
|
|
564
|
+
escHitbox &&
|
|
565
|
+
mouse.row === escHitbox.row &&
|
|
566
|
+
mouse.col >= escHitbox.startCol &&
|
|
567
|
+
mouse.col <= escHitbox.endCol
|
|
568
|
+
) {
|
|
569
|
+
done(undefined);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const hitbox = previewHitboxes.find(
|
|
573
|
+
(candidate) =>
|
|
574
|
+
mouse.row === candidate.row &&
|
|
575
|
+
mouse.col >= candidate.startCol &&
|
|
576
|
+
mouse.col <= candidate.endCol,
|
|
536
577
|
);
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
.
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
const
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
578
|
+
if (hitbox) {
|
|
579
|
+
selectedPreviewIndex = Math.max(
|
|
580
|
+
0,
|
|
581
|
+
visiblePreviews.findIndex((preview) => preview.key === hitbox.key),
|
|
582
|
+
);
|
|
583
|
+
done(hitbox.key);
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
render(width: number) {
|
|
587
|
+
const inner = Math.max(1, width - 2);
|
|
588
|
+
const escWidth = visibleWidth("[esc]");
|
|
589
|
+
const percent = contextWindow > 0 ? (used / contextWindow) * 100 : 0;
|
|
590
|
+
const title = theme.bold(theme.fg("accent", "Context Usage"));
|
|
591
|
+
const subtitle = `${formatTokens(used)} / ${formatTokens(contextWindow)} tokens (${percent.toFixed(1)}%)`;
|
|
592
|
+
const barWidth = Math.max(1, Math.min(60, inner - 2));
|
|
593
|
+
let remaining = barWidth;
|
|
594
|
+
const segments = allParts
|
|
595
|
+
.map((part, index) => {
|
|
596
|
+
const cells =
|
|
597
|
+
index === allParts.length - 1
|
|
598
|
+
? remaining
|
|
599
|
+
: Math.min(
|
|
600
|
+
remaining,
|
|
601
|
+
Math.round((part.tokens / Math.max(1, contextWindow)) * barWidth),
|
|
602
|
+
);
|
|
603
|
+
remaining -= cells;
|
|
604
|
+
return theme.fg(part.color, "█".repeat(Math.max(0, cells)));
|
|
605
|
+
})
|
|
606
|
+
.join("");
|
|
607
|
+
const labelWidth = Math.min(
|
|
608
|
+
24,
|
|
609
|
+
Math.max(...allParts.map((part) => part.label.length)),
|
|
610
|
+
);
|
|
611
|
+
const selectedLabel = visiblePreviews[selectedPreviewIndex]?.label;
|
|
612
|
+
const partRows = allParts.map((part) => {
|
|
613
|
+
const pct = contextWindow > 0 ? (part.tokens / contextWindow) * 100 : 0;
|
|
614
|
+
const swatch = theme.fg(part.color, "■");
|
|
615
|
+
const label = part.label.padEnd(labelWidth);
|
|
616
|
+
const amount = `${formatTokens(part.tokens).padStart(7)} ${pct.toFixed(1).padStart(5)}%`;
|
|
617
|
+
const selected = part.label === selectedLabel;
|
|
618
|
+
const hoverLabel = visiblePreviews.find((p) => p.key === hoveredKey)?.label;
|
|
619
|
+
const prefix = selected ? "› " : " ";
|
|
620
|
+
const row = padLine(`${prefix}${swatch} ${label} ${amount}`, inner);
|
|
621
|
+
if (selected) return theme.bg("selectedBg", row);
|
|
622
|
+
return part.label === hoverLabel ? theme.bg("customMessageBg", row) : row;
|
|
623
|
+
});
|
|
624
|
+
const border = (text: string) => theme.fg("border", text);
|
|
625
|
+
const lines = [
|
|
626
|
+
border(`╭${"─".repeat(inner)}╮`),
|
|
627
|
+
`${border("│")}${padLine(` ${title} ${theme.fg("muted", subtitle)}`, inner - escWidth)}${theme.fg(escHovered ? "text" : "muted", "[esc]")}${border("│")}`,
|
|
628
|
+
`${border("├")}${border("─".repeat(inner))}${border("┤")}`,
|
|
629
|
+
`${border("│")}${padLine(` ${segments}`, inner)}${border("│")}`,
|
|
630
|
+
`${border("│")}${" ".repeat(inner)}${border("│")}`,
|
|
631
|
+
...partRows.map((row) => `${border("│")}${row}${border("│")}`),
|
|
632
|
+
`${border("├")}${border("─".repeat(inner))}${border("┤")}`,
|
|
633
|
+
`${border("│")}${padLine(theme.fg("dim", " ↑↓ select · Click / Enter to preview · [esc] close"), inner)}${border("│")}`,
|
|
634
|
+
border(`╰${"─".repeat(inner)}╯`),
|
|
635
|
+
];
|
|
636
|
+
|
|
637
|
+
const terminalHeight = Math.max(1, tui.terminal.rows);
|
|
638
|
+
const maxHeight = Math.min(
|
|
639
|
+
Math.max(1, Math.floor(terminalHeight * 0.9)),
|
|
640
|
+
Math.max(1, terminalHeight - 2),
|
|
641
|
+
);
|
|
642
|
+
const visibleHeight = Math.min(lines.length, maxHeight);
|
|
643
|
+
const overlayTop =
|
|
644
|
+
1 + Math.floor((Math.max(1, terminalHeight - 2) - visibleHeight) / 2);
|
|
645
|
+
const overlayLeft = Math.floor((Math.max(1, tui.terminal.columns) - width) / 2);
|
|
646
|
+
escHitbox = escCloseHitbox({ left: overlayLeft, top: overlayTop, width });
|
|
647
|
+
previewHitboxes = visiblePreviews.flatMap((preview) => {
|
|
648
|
+
const partIndex = allParts.findIndex((part) => part.label === preview.label);
|
|
649
|
+
const line = 5 + partIndex;
|
|
650
|
+
return partIndex >= 0 && line < visibleHeight
|
|
651
|
+
? [
|
|
652
|
+
{
|
|
653
|
+
key: preview.key,
|
|
654
|
+
row: overlayTop + line + 1,
|
|
655
|
+
startCol: overlayLeft + 1,
|
|
656
|
+
endCol: overlayLeft + width,
|
|
657
|
+
},
|
|
658
|
+
]
|
|
659
|
+
: [];
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
return lines;
|
|
663
|
+
},
|
|
664
|
+
};
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
overlay: true,
|
|
668
|
+
overlayOptions: {
|
|
669
|
+
anchor: "center",
|
|
670
|
+
width: 64,
|
|
671
|
+
minWidth: 44,
|
|
672
|
+
maxHeight: "90%",
|
|
673
|
+
margin: 1,
|
|
615
674
|
},
|
|
616
|
-
};
|
|
617
|
-
},
|
|
618
|
-
{
|
|
619
|
-
overlay: true,
|
|
620
|
-
overlayOptions: {
|
|
621
|
-
anchor: "center",
|
|
622
|
-
width: 64,
|
|
623
|
-
minWidth: 44,
|
|
624
|
-
maxHeight: "90%",
|
|
625
|
-
margin: 1,
|
|
626
675
|
},
|
|
627
|
-
|
|
628
|
-
|
|
676
|
+
);
|
|
677
|
+
} finally {
|
|
678
|
+
activeContextOverlays--;
|
|
679
|
+
}
|
|
629
680
|
|
|
630
681
|
if (!action) break;
|
|
631
682
|
const preview = previewByKey.get(action as PreviewKey);
|