pi-cc-extensions 0.9.10 → 0.9.11
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 +5 -6
- package/README.md +5 -6
- package/extensions/config/config.ts +23 -6
- package/extensions/config/panel.ts +134 -29
- package/extensions/feature/compact-thinking.ts +12 -12
- package/extensions/feature/shell/footer-currency.ts +29 -0
- package/extensions/feature/shell/footer.ts +5 -2
- package/extensions/renderer/compact-mode.ts +22 -3
- package/extensions/renderer/default-mode.ts +15 -9
- package/extensions/renderer/mouse/interaction.ts +27 -25
- package/extensions/renderer/tool/codemode.ts +4 -1
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -63,7 +63,6 @@ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-exam
|
|
|
63
63
|
"expandedCardBackground": "userMessageBg", // expanded card background slot: userMessageBg (same as user prompts) / toolPendingBg (neutral) etc.
|
|
64
64
|
"inputClip": 0, // tool summary path/command clip length; 0 = fit width
|
|
65
65
|
"showStartupHeader": true, // startup header (logo + tips) toggle
|
|
66
|
-
"scrollStepLines": 3, // fullscreen wheel scroll step
|
|
67
66
|
|
|
68
67
|
// diff
|
|
69
68
|
"diffViewMode": "auto", // layout: auto / split / unified
|
|
@@ -81,6 +80,8 @@ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-exam
|
|
|
81
80
|
|
|
82
81
|
// footer
|
|
83
82
|
"enableCustomFooter": true, // custom status bar
|
|
83
|
+
"footerCurrency": "USD", // display currency for the footer cost
|
|
84
|
+
"footerCurrencyRate": null, // display units per 1 cost unit; null changes the symbol only
|
|
84
85
|
"footerNerdIcons": true, // Nerd Font glyphs for git/cache; false = plain text
|
|
85
86
|
"footerHiddenKeys": [], // hidden plugin chip keys
|
|
86
87
|
"footerLine1Keys": ["pi-usage"], // line1 plugin chip order; pi-usage is shown by default, data from @narumitw/pi-usage
|
|
@@ -98,11 +99,9 @@ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-exam
|
|
|
98
99
|
## Local development
|
|
99
100
|
|
|
100
101
|
```bash
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
test.bat # Windows: same
|
|
105
|
-
# one-off run without touching settings: pi -e .
|
|
102
|
+
./test.sh # macOS/Linux; needs node and pi
|
|
103
|
+
./test.bat # Windows; runs test.sh through Git Bash
|
|
104
|
+
pi -e . # one run, does not edit settings.json
|
|
106
105
|
```
|
|
107
106
|
|
|
108
107
|
## Compatibility
|
package/README.md
CHANGED
|
@@ -63,7 +63,6 @@ pi install git:github.com/minuque/pi-cc-extensions
|
|
|
63
63
|
"expandedCardBackground": "userMessageBg", // 展开卡背景槽位;userMessageBg 与用户消息同色,toolPendingBg 为中性灰
|
|
64
64
|
"inputClip": 0, // 工具摘要 path/command 折叠字符数;0 = 按可用宽度
|
|
65
65
|
"showStartupHeader": true, // 启动头(logo + tips)开关
|
|
66
|
-
"scrollStepLines": 3, // fullscreen 滚轮步进
|
|
67
66
|
|
|
68
67
|
// diff
|
|
69
68
|
"diffViewMode": "auto", // 布局:auto / split / unified
|
|
@@ -81,6 +80,8 @@ pi install git:github.com/minuque/pi-cc-extensions
|
|
|
81
80
|
|
|
82
81
|
// footer
|
|
83
82
|
"enableCustomFooter": true, // 自定义状态栏
|
|
83
|
+
"footerCurrency": "USD", // 底栏费用的显示币种
|
|
84
|
+
"footerCurrencyRate": null, // 1 单位费用换成多少显示币种;null 只换符号、不乘倍率
|
|
84
85
|
"footerNerdIcons": true, // git/缓存用 Nerd Font 图标;false 为纯文本
|
|
85
86
|
"footerHiddenKeys": [], // 隐藏的插件芯片 key
|
|
86
87
|
"footerLine1Keys": ["pi-usage"], // line1 插件芯片顺序;pi-usage 默认显示,数据来自 @narumitw/pi-usage
|
|
@@ -98,11 +99,9 @@ pi install git:github.com/minuque/pi-cc-extensions
|
|
|
98
99
|
## 本地开发
|
|
99
100
|
|
|
100
101
|
```bash
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
test.bat # Windows:同上
|
|
105
|
-
# 只想跑一次、不改配置:pi -e .
|
|
102
|
+
./test.sh # macOS/Linux,需要 node 和 pi
|
|
103
|
+
./test.bat # Windows,经 Git Bash 运行 test.sh
|
|
104
|
+
pi -e . # 只跑一次,不改 settings.json
|
|
106
105
|
```
|
|
107
106
|
|
|
108
107
|
## 兼容性
|
|
@@ -63,7 +63,6 @@ export type Config = {
|
|
|
63
63
|
animationIntervalMs: number;
|
|
64
64
|
dimThinkingText: boolean;
|
|
65
65
|
showStartupHeader: boolean;
|
|
66
|
-
scrollStepLines: number;
|
|
67
66
|
enableSessionReference: boolean;
|
|
68
67
|
enableSubagentAutocomplete: boolean;
|
|
69
68
|
enableContextCommand: boolean;
|
|
@@ -71,6 +70,10 @@ export type Config = {
|
|
|
71
70
|
enableWorkingMessage: boolean;
|
|
72
71
|
enableAliases: boolean;
|
|
73
72
|
enableCustomFooter: boolean;
|
|
73
|
+
/** ISO 4217 显示币种。USD 时不换算。 */
|
|
74
|
+
footerCurrency: string;
|
|
75
|
+
/** 1 单位费用对应的显示币种数量。null 只换符号、不乘倍率。 */
|
|
76
|
+
footerCurrencyRate: number | null;
|
|
74
77
|
footerNerdIcons: boolean;
|
|
75
78
|
footerHiddenKeys: string[];
|
|
76
79
|
footerLine1Keys: string[];
|
|
@@ -112,8 +115,6 @@ export const EXPANDED_OUTPUT_MAX_LINES_VALUES = ["10", "20", "40", "80", "120"];
|
|
|
112
115
|
export const INPUT_CLIP_VALUES = ["0", "40", "60", "80", "100", "120", "160"];
|
|
113
116
|
export const THINKING_PREVIEW_LINES_VALUES = ["0", "1", "3", "5", "10"];
|
|
114
117
|
export const THINKING_ANIMATION_INTERVAL_VALUES = ["40", "60", "90", "120", "180"];
|
|
115
|
-
/** fullscreen 滚轮步进行数预设。 */
|
|
116
|
-
export const SCROLL_STEP_LINES_VALUES = ["1", "2", "3", "5", "10"];
|
|
117
118
|
/** Tools commonly toggled in excludeRenderers via the settings panel. */
|
|
118
119
|
export const EXCLUDE_RENDERER_CANDIDATES = [
|
|
119
120
|
"bash",
|
|
@@ -148,7 +149,6 @@ export const DEFAULT_CONFIG: Config = {
|
|
|
148
149
|
animationIntervalMs: 90,
|
|
149
150
|
dimThinkingText: false,
|
|
150
151
|
showStartupHeader: true,
|
|
151
|
-
scrollStepLines: 3,
|
|
152
152
|
enableSessionReference: true,
|
|
153
153
|
enableSubagentAutocomplete: true,
|
|
154
154
|
enableContextCommand: true,
|
|
@@ -156,6 +156,8 @@ export const DEFAULT_CONFIG: Config = {
|
|
|
156
156
|
enableWorkingMessage: true,
|
|
157
157
|
enableAliases: true,
|
|
158
158
|
enableCustomFooter: true,
|
|
159
|
+
footerCurrency: "USD",
|
|
160
|
+
footerCurrencyRate: null,
|
|
159
161
|
footerNerdIcons: true,
|
|
160
162
|
...DEFAULT_FOOTER_CHIP_LAYOUT,
|
|
161
163
|
};
|
|
@@ -183,6 +185,19 @@ export function pickPositiveNumber(value: unknown, fallback: number, min = 1): n
|
|
|
183
185
|
return Number.isFinite(n) ? Math.max(min, n) : fallback;
|
|
184
186
|
}
|
|
185
187
|
|
|
188
|
+
function normalizeCurrency(value: unknown): string {
|
|
189
|
+
return typeof value === "string" && /^[A-Za-z]{3}$/.test(value.trim())
|
|
190
|
+
? value.trim().toUpperCase()
|
|
191
|
+
: "USD";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** 缺失、空串、非正数都视为不换算。 */
|
|
195
|
+
function normalizeFooterCurrencyRate(value: unknown): number | null {
|
|
196
|
+
if (value === undefined || value === null || value === "") return null;
|
|
197
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
198
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
199
|
+
}
|
|
200
|
+
|
|
186
201
|
export function normalizeConfig(input: unknown): Config {
|
|
187
202
|
const source = (input && typeof input === "object" ? input : {}) as Record<string, unknown>;
|
|
188
203
|
const mode = pickEnum(source.mode, ["on", "compact", "off"], DEFAULT_CONFIG.mode);
|
|
@@ -260,7 +275,6 @@ export function normalizeConfig(input: unknown): Config {
|
|
|
260
275
|
),
|
|
261
276
|
dimThinkingText: source.dimThinkingText === true,
|
|
262
277
|
showStartupHeader: source.showStartupHeader !== false,
|
|
263
|
-
scrollStepLines: pickPositiveInt(source.scrollStepLines, DEFAULT_CONFIG.scrollStepLines, 1, 50),
|
|
264
278
|
enableSessionReference: source.enableSessionReference !== false,
|
|
265
279
|
enableSubagentAutocomplete: source.enableSubagentAutocomplete !== false,
|
|
266
280
|
enableContextCommand: source.enableContextCommand !== false,
|
|
@@ -268,6 +282,8 @@ export function normalizeConfig(input: unknown): Config {
|
|
|
268
282
|
enableWorkingMessage: source.enableWorkingMessage !== false,
|
|
269
283
|
enableAliases: source.enableAliases !== false,
|
|
270
284
|
enableCustomFooter: source.enableCustomFooter !== false,
|
|
285
|
+
footerCurrency: normalizeCurrency(source.footerCurrency),
|
|
286
|
+
footerCurrencyRate: normalizeFooterCurrencyRate(source.footerCurrencyRate),
|
|
271
287
|
footerNerdIcons: source.footerNerdIcons !== false,
|
|
272
288
|
...normalizeFooterChipLayout(source),
|
|
273
289
|
};
|
|
@@ -316,7 +332,6 @@ export function formatConfigStatus(source: Config = config): string {
|
|
|
316
332
|
`thinkingAnimation=${source.animationIntervalMs}ms`,
|
|
317
333
|
`thinkingDim=${source.dimThinkingText ? "on" : "off"}`,
|
|
318
334
|
`startupHeader=${source.showStartupHeader ? "on" : "off"}`,
|
|
319
|
-
`scrollStep=${source.scrollStepLines}`,
|
|
320
335
|
`sessionRef=${source.enableSessionReference ? "on" : "off"}`,
|
|
321
336
|
`subagentAuto=${source.enableSubagentAutocomplete ? "on" : "off"}`,
|
|
322
337
|
`context=${source.enableContextCommand ? "on" : "off"}`,
|
|
@@ -324,6 +339,8 @@ export function formatConfigStatus(source: Config = config): string {
|
|
|
324
339
|
`workingMsg=${source.enableWorkingMessage ? "on" : "off"}`,
|
|
325
340
|
`aliases=${source.enableAliases ? "on" : "off"}`,
|
|
326
341
|
`footer=${source.enableCustomFooter ? "on" : "off"}`,
|
|
342
|
+
`footerCurrency=${source.footerCurrency}`,
|
|
343
|
+
`footerRate=${source.footerCurrencyRate ?? "-"}`,
|
|
327
344
|
`footerIcons=${source.footerNerdIcons ? "nerd" : "plain"}`,
|
|
328
345
|
formatFooterChipSummary(source),
|
|
329
346
|
].join(" · ");
|
|
@@ -12,6 +12,10 @@ import {
|
|
|
12
12
|
clearCustomFooter,
|
|
13
13
|
getFooterStatusSnapshot,
|
|
14
14
|
} from "../feature/shell/footer.ts";
|
|
15
|
+
import {
|
|
16
|
+
FOOTER_CURRENCY_PRESETS,
|
|
17
|
+
presetFooterCurrencyRate,
|
|
18
|
+
} from "../feature/shell/footer-currency.ts";
|
|
15
19
|
import {
|
|
16
20
|
footerChipDescription,
|
|
17
21
|
footerLineOfKey,
|
|
@@ -42,7 +46,6 @@ import {
|
|
|
42
46
|
pickInputClip,
|
|
43
47
|
pickPositiveInt,
|
|
44
48
|
pickPositiveNumber,
|
|
45
|
-
SCROLL_STEP_LINES_VALUES,
|
|
46
49
|
THINKING_ANIMATION_INTERVAL_VALUES,
|
|
47
50
|
THINKING_PREVIEW_LINES_VALUES,
|
|
48
51
|
INPUT_CLIP_VALUES,
|
|
@@ -281,10 +284,22 @@ function buildExcludeRenderersSubmenu(
|
|
|
281
284
|
};
|
|
282
285
|
}
|
|
283
286
|
|
|
284
|
-
|
|
285
|
-
|
|
287
|
+
function footerCurrencyRateLabel(rate: number | null): string {
|
|
288
|
+
return rate === null ? "-" : String(rate);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 手动输入子面板。accept 返回字符串则提交,undefined 取消,null 用 invalid 提示。
|
|
293
|
+
*/
|
|
294
|
+
function buildValueInputSubmenu(
|
|
286
295
|
theme: any,
|
|
287
|
-
|
|
296
|
+
options: {
|
|
297
|
+
prompt: string;
|
|
298
|
+
initial: string;
|
|
299
|
+
hint: string;
|
|
300
|
+
accept: (raw: string) => string | null | undefined;
|
|
301
|
+
invalid?: (raw: string) => string;
|
|
302
|
+
},
|
|
288
303
|
closeSubmenu: (selected?: string) => void,
|
|
289
304
|
): {
|
|
290
305
|
render: (width: number) => string[];
|
|
@@ -293,27 +308,28 @@ function buildNumberInputSubmenu(
|
|
|
293
308
|
} {
|
|
294
309
|
const input = new Input();
|
|
295
310
|
let error = "";
|
|
296
|
-
input.setValue(
|
|
311
|
+
input.setValue(options.initial);
|
|
297
312
|
input.onSubmit = (value: string) => {
|
|
298
313
|
const raw = value.trim();
|
|
299
|
-
|
|
300
|
-
|
|
314
|
+
const next = options.accept(raw);
|
|
315
|
+
if (next === undefined) {
|
|
316
|
+
closeSubmenu();
|
|
301
317
|
return;
|
|
302
318
|
}
|
|
303
|
-
if (
|
|
304
|
-
error =
|
|
319
|
+
if (next === null) {
|
|
320
|
+
error = options.invalid?.(raw) ?? "Invalid value";
|
|
305
321
|
return;
|
|
306
322
|
}
|
|
307
|
-
closeSubmenu(
|
|
323
|
+
closeSubmenu(next);
|
|
308
324
|
};
|
|
309
325
|
input.onEscape = () => closeSubmenu();
|
|
310
326
|
return {
|
|
311
327
|
render: (width: number) => {
|
|
312
328
|
const safe = Math.max(0, Math.floor(width));
|
|
313
329
|
const lines = [
|
|
314
|
-
theme.fg("dim",
|
|
330
|
+
theme.fg("dim", options.prompt),
|
|
315
331
|
...input.render(safe),
|
|
316
|
-
truncateToWidth(theme.fg("dim",
|
|
332
|
+
truncateToWidth(theme.fg("dim", options.hint), safe),
|
|
317
333
|
];
|
|
318
334
|
if (error !== "") lines.push(theme.fg("dim", ` ${error}`));
|
|
319
335
|
return lines;
|
|
@@ -323,6 +339,25 @@ function buildNumberInputSubmenu(
|
|
|
323
339
|
};
|
|
324
340
|
}
|
|
325
341
|
|
|
342
|
+
/** 数值项:预填当前值,空输入取消,非数字留下错误。 */
|
|
343
|
+
function buildNumberInputSubmenu(
|
|
344
|
+
theme: any,
|
|
345
|
+
setting: { label: string; currentValue: string },
|
|
346
|
+
closeSubmenu: (selected?: string) => void,
|
|
347
|
+
) {
|
|
348
|
+
return buildValueInputSubmenu(
|
|
349
|
+
theme,
|
|
350
|
+
{
|
|
351
|
+
prompt: ` ${setting.label} — custom value:`,
|
|
352
|
+
initial: setting.currentValue,
|
|
353
|
+
hint: " Enter to apply · Esc to go back",
|
|
354
|
+
accept: (raw) => (raw === "" ? undefined : Number.isFinite(Number(raw)) ? raw : null),
|
|
355
|
+
invalid: (raw) => `Invalid number: "${raw}"`,
|
|
356
|
+
},
|
|
357
|
+
closeSubmenu,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
326
361
|
/** Section tabs for /ccstyle — matches Zentui-style "A / B / C" headers. */
|
|
327
362
|
type CcstyleSection = {
|
|
328
363
|
id: "style" | "diff" | "thinking" | "ui" | "feature" | "footer";
|
|
@@ -551,15 +586,6 @@ export async function showCcstylePanel(
|
|
|
551
586
|
currentValue: config.showStartupHeader ? "on" : "off",
|
|
552
587
|
values: ["on", "off"],
|
|
553
588
|
};
|
|
554
|
-
const scrollStepSetting = {
|
|
555
|
-
id: "scrollStepLines",
|
|
556
|
-
label: "Scroll step",
|
|
557
|
-
description: "Mouse wheel scroll lines in fullscreen mode.",
|
|
558
|
-
currentValue: String(config.scrollStepLines),
|
|
559
|
-
values: [...SCROLL_STEP_LINES_VALUES],
|
|
560
|
-
submenu: (_current: string, closeSubmenu: (selected?: string) => void) =>
|
|
561
|
-
buildNumberInputSubmenu(theme, scrollStepSetting, closeSubmenu),
|
|
562
|
-
};
|
|
563
589
|
|
|
564
590
|
// 额外功能开关:注册于扩展加载期,切换后需重启(/reload)生效。
|
|
565
591
|
const sessionReferenceToggle = featureToggleSetting(
|
|
@@ -604,6 +630,65 @@ export async function showCcstylePanel(
|
|
|
604
630
|
"Aliases disabled.",
|
|
605
631
|
config.enableAliases,
|
|
606
632
|
);
|
|
633
|
+
const footerCurrencySetting = {
|
|
634
|
+
id: "footerCurrency",
|
|
635
|
+
label: "Cost currency",
|
|
636
|
+
description:
|
|
637
|
+
"ISO code for the status-bar cost. Switching currency fills its preset rate. USD clears the rate.",
|
|
638
|
+
currentValue: config.footerCurrency,
|
|
639
|
+
values: [...FOOTER_CURRENCY_PRESETS],
|
|
640
|
+
submenu: (_current: string, closeSubmenu: (selected?: string) => void) => {
|
|
641
|
+
nestedSubmenuOpen = true;
|
|
642
|
+
nestedHint = " Enter to apply · Esc back to Footer";
|
|
643
|
+
return buildValueInputSubmenu(
|
|
644
|
+
theme,
|
|
645
|
+
{
|
|
646
|
+
prompt: " Cost currency — ISO code:",
|
|
647
|
+
initial: footerCurrencySetting.currentValue,
|
|
648
|
+
hint: " Enter to apply · Esc to go back",
|
|
649
|
+
accept: (raw) => (raw === "" ? undefined : raw),
|
|
650
|
+
},
|
|
651
|
+
(selected) => {
|
|
652
|
+
nestedSubmenuOpen = false;
|
|
653
|
+
nestedHint = "";
|
|
654
|
+
closeSubmenu(selected);
|
|
655
|
+
},
|
|
656
|
+
);
|
|
657
|
+
},
|
|
658
|
+
};
|
|
659
|
+
const footerCurrencyRateSetting = {
|
|
660
|
+
id: "footerCurrencyRate",
|
|
661
|
+
label: "Cost rate",
|
|
662
|
+
description:
|
|
663
|
+
"Display units per 1 USD of cost. Switching currency replaces this. Empty keeps the number and only changes the symbol.",
|
|
664
|
+
currentValue: footerCurrencyRateLabel(config.footerCurrencyRate),
|
|
665
|
+
submenu: (_current: string, closeSubmenu: (selected?: string) => void) => {
|
|
666
|
+
nestedSubmenuOpen = true;
|
|
667
|
+
nestedHint = " Enter to apply · empty clears · Esc back to Footer";
|
|
668
|
+
return buildValueInputSubmenu(
|
|
669
|
+
theme,
|
|
670
|
+
{
|
|
671
|
+
prompt: " Cost rate — display units per 1 cost unit:",
|
|
672
|
+
initial:
|
|
673
|
+
footerCurrencyRateSetting.currentValue === "-"
|
|
674
|
+
? ""
|
|
675
|
+
: footerCurrencyRateSetting.currentValue,
|
|
676
|
+
hint: " Enter to apply · empty clears conversion · Esc to go back",
|
|
677
|
+
accept: (raw) => {
|
|
678
|
+
if (raw === "" || raw === "-") return "-";
|
|
679
|
+
const n = Number(raw);
|
|
680
|
+
return Number.isFinite(n) && n > 0 ? raw : null;
|
|
681
|
+
},
|
|
682
|
+
invalid: () => "Rate must be a positive number",
|
|
683
|
+
},
|
|
684
|
+
(selected) => {
|
|
685
|
+
nestedSubmenuOpen = false;
|
|
686
|
+
nestedHint = "";
|
|
687
|
+
closeSubmenu(selected);
|
|
688
|
+
},
|
|
689
|
+
);
|
|
690
|
+
},
|
|
691
|
+
};
|
|
607
692
|
const footerNerdIconsSetting = {
|
|
608
693
|
id: "footerNerdIcons",
|
|
609
694
|
label: "Nerd Font icons",
|
|
@@ -663,6 +748,27 @@ export async function showCcstylePanel(
|
|
|
663
748
|
ctx.ui.notify(`Updated ${id}: ${value}`, "info");
|
|
664
749
|
return;
|
|
665
750
|
}
|
|
751
|
+
if (id === "footerCurrency") {
|
|
752
|
+
updateConfig({
|
|
753
|
+
footerCurrency: value,
|
|
754
|
+
footerCurrencyRate: presetFooterCurrencyRate(value),
|
|
755
|
+
});
|
|
756
|
+
footerCurrencySetting.currentValue = config.footerCurrency;
|
|
757
|
+
footerCurrencyRateSetting.currentValue = footerCurrencyRateLabel(config.footerCurrencyRate);
|
|
758
|
+
if (config.enableCustomFooter) applyCustomFooter(ctx);
|
|
759
|
+
ctx.ui.notify(
|
|
760
|
+
`Updated ${id}: ${config.footerCurrency} · ${footerCurrencyRateSetting.currentValue}`,
|
|
761
|
+
"info",
|
|
762
|
+
);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
if (id === "footerCurrencyRate") {
|
|
766
|
+
updateConfig({ footerCurrencyRate: value === "-" ? null : Number(value) });
|
|
767
|
+
footerCurrencyRateSetting.currentValue = footerCurrencyRateLabel(config.footerCurrencyRate);
|
|
768
|
+
if (config.enableCustomFooter) applyCustomFooter(ctx);
|
|
769
|
+
ctx.ui.notify(`Updated ${id}: ${footerCurrencyRateSetting.currentValue}`, "info");
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
666
772
|
if (id === "footerNerdIcons") {
|
|
667
773
|
const enabled = value === "on";
|
|
668
774
|
updateConfig({ footerNerdIcons: enabled });
|
|
@@ -810,12 +916,6 @@ export async function showCcstylePanel(
|
|
|
810
916
|
else clearStartupHeader(ctx);
|
|
811
917
|
break;
|
|
812
918
|
}
|
|
813
|
-
case "scrollStepLines":
|
|
814
|
-
updateConfig({
|
|
815
|
-
scrollStepLines: pickPositiveInt(value, DEFAULT_CONFIG.scrollStepLines, 1, 50),
|
|
816
|
-
});
|
|
817
|
-
scrollStepSetting.currentValue = String(config.scrollStepLines);
|
|
818
|
-
break;
|
|
819
919
|
default:
|
|
820
920
|
return;
|
|
821
921
|
}
|
|
@@ -852,7 +952,6 @@ export async function showCcstylePanel(
|
|
|
852
952
|
expandedCardBgSetting,
|
|
853
953
|
inputClipSetting,
|
|
854
954
|
startupHeaderSetting,
|
|
855
|
-
scrollStepSetting,
|
|
856
955
|
],
|
|
857
956
|
},
|
|
858
957
|
{
|
|
@@ -880,7 +979,13 @@ export async function showCcstylePanel(
|
|
|
880
979
|
{
|
|
881
980
|
id: "footer",
|
|
882
981
|
label: "Footer",
|
|
883
|
-
items: [
|
|
982
|
+
items: [
|
|
983
|
+
customFooterSetting,
|
|
984
|
+
footerCurrencySetting,
|
|
985
|
+
footerCurrencyRateSetting,
|
|
986
|
+
footerNerdIconsSetting,
|
|
987
|
+
pluginChipsSetting,
|
|
988
|
+
],
|
|
884
989
|
},
|
|
885
990
|
];
|
|
886
991
|
|
|
@@ -324,7 +324,7 @@ function hiddenPreviewHint(
|
|
|
324
324
|
return undefined;
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
const expandedThinking = new Set<
|
|
327
|
+
const expandedThinking = new Set<string>();
|
|
328
328
|
|
|
329
329
|
/** 折叠预览 + 展开全文。fullscreen 点击 hint 展开、展开卡单击整块收起(拖动选择文本时不收起)。 */
|
|
330
330
|
export class ThinkingPreviewBlock implements Component {
|
|
@@ -335,7 +335,6 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
335
335
|
readonly runStartIndex: number;
|
|
336
336
|
private style: (text: string) => string;
|
|
337
337
|
private theme: Theme | undefined;
|
|
338
|
-
private _expanded: boolean;
|
|
339
338
|
private hintHovered = false;
|
|
340
339
|
private collapsedMemo:
|
|
341
340
|
| {
|
|
@@ -363,7 +362,6 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
363
362
|
this.runStartIndex = runStartIndex;
|
|
364
363
|
this.style = style;
|
|
365
364
|
this.theme = theme;
|
|
366
|
-
this._expanded = expandedThinking.has(messageTimestamp);
|
|
367
365
|
}
|
|
368
366
|
|
|
369
367
|
private paint(color: string, text: string): string {
|
|
@@ -372,18 +370,20 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
372
370
|
: text;
|
|
373
371
|
}
|
|
374
372
|
|
|
373
|
+
/** 展开态按消息时间戳 + run 序号共享:面板重排/内容重建换掉实例后,
|
|
374
|
+
* 布局里的旧实例与 render 的新实例同键,点谁都能展开。 */
|
|
375
375
|
get expanded(): boolean {
|
|
376
|
-
return this.
|
|
376
|
+
return expandedThinking.has(`${this.messageTimestamp}:${this.runStartIndex}`);
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
setExpanded(expanded: boolean): void {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (expanded) expandedThinking.add(this.messageTimestamp);
|
|
380
|
+
const key = `${this.messageTimestamp}:${this.runStartIndex}`;
|
|
381
|
+
if (expanded) expandedThinking.add(key);
|
|
383
382
|
else {
|
|
384
|
-
expandedThinking.delete(
|
|
383
|
+
expandedThinking.delete(key);
|
|
385
384
|
evictExpandedWraps(this.messageTimestamp, this.runStartIndex);
|
|
386
385
|
}
|
|
386
|
+
this.collapsedMemo = undefined;
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
setHintHovered(hovered: boolean): void {
|
|
@@ -392,7 +392,7 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
private headingLines(width: number, hiddenLines: number, padding: number): string[] {
|
|
395
|
-
const hint = this.
|
|
395
|
+
const hint = this.expanded
|
|
396
396
|
? undefined
|
|
397
397
|
: hiddenPreviewHint(hiddenLines, Boolean(this.text) && config.previewLines <= 0);
|
|
398
398
|
if (!hint) return new Text(this.heading, padding, 0).render(width);
|
|
@@ -412,10 +412,10 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
412
412
|
}
|
|
413
413
|
|
|
414
414
|
private bodyLines(width: number, padding: number): { lines: string[]; hiddenLines: number } {
|
|
415
|
-
if (!this.text || (config.previewLines <= 0 && !this.
|
|
415
|
+
if (!this.text || (config.previewLines <= 0 && !this.expanded)) {
|
|
416
416
|
return { lines: [], hiddenLines: 0 };
|
|
417
417
|
}
|
|
418
|
-
if (this.
|
|
418
|
+
if (this.expanded) {
|
|
419
419
|
return {
|
|
420
420
|
lines: wrapExpandedThinking(this.messageTimestamp, this.runStartIndex, this.text, width),
|
|
421
421
|
hiddenLines: 0,
|
|
@@ -426,7 +426,7 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
render(width: number) {
|
|
429
|
-
if (this.
|
|
429
|
+
if (this.expanded) {
|
|
430
430
|
const innerWidth = Math.max(1, width - 2);
|
|
431
431
|
const body = this.bodyLines(innerWidth, 0);
|
|
432
432
|
const heading = this.headingLines(innerWidth, 0, 0);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const KNOWN_CURRENCIES = new Set(Intl.supportedValuesOf("currency"));
|
|
2
|
+
|
|
3
|
+
/** 1 美元费用对应的显示币种数量。面板切换币种时写入。 */
|
|
4
|
+
export const FOOTER_CURRENCY_RATES = {
|
|
5
|
+
CNY: 7.12,
|
|
6
|
+
EUR: 0.92,
|
|
7
|
+
JPY: 150,
|
|
8
|
+
GBP: 0.76,
|
|
9
|
+
INR: 96.25,
|
|
10
|
+
HKD: 7.78,
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export const FOOTER_CURRENCY_PRESETS = ["USD", ...Object.keys(FOOTER_CURRENCY_RATES)] as const;
|
|
14
|
+
|
|
15
|
+
/** 不在表里的币种,包括 USD,返回 null。 */
|
|
16
|
+
export function presetFooterCurrencyRate(currency: string): number | null {
|
|
17
|
+
const code = currency.trim().toUpperCase();
|
|
18
|
+
return FOOTER_CURRENCY_RATES[code as keyof typeof FOOTER_CURRENCY_RATES] ?? null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** 底栏费用文本。USD 或不支持的币种保持美元写法。没有倍率时只换符号。 */
|
|
22
|
+
export function formatFooterCost(cost: number, currency: string, rate: number | null): string {
|
|
23
|
+
const usd = `$${cost.toFixed(2)}`;
|
|
24
|
+
if (currency === "USD" || !KNOWN_CURRENCIES.has(currency)) return usd;
|
|
25
|
+
const formatted = new Intl.NumberFormat("en", { style: "currency", currency }).format(
|
|
26
|
+
rate === null ? cost : cost * rate,
|
|
27
|
+
);
|
|
28
|
+
return rate === null ? formatted : `≈${formatted}`;
|
|
29
|
+
}
|
|
@@ -21,6 +21,7 @@ import { join } from "node:path";
|
|
|
21
21
|
import { pathToFileURL } from "node:url";
|
|
22
22
|
import { config } from "../../config/config.ts";
|
|
23
23
|
import { stripAnsi } from "../../utils/ansi-text.ts";
|
|
24
|
+
import { formatFooterCost } from "./footer-currency.ts";
|
|
24
25
|
import {
|
|
25
26
|
PI_USAGE_KEY,
|
|
26
27
|
isSkippedFooterStatusKey,
|
|
@@ -419,8 +420,10 @@ const createCustomFooterFactory =
|
|
|
419
420
|
cachePct > 0 ? `${glyphs.cache ? `${glyphs.cache} ` : ""}${Math.floor(cachePct)}%` : "";
|
|
420
421
|
const costChip =
|
|
421
422
|
cost || usingSubscription
|
|
422
|
-
? theme.fg(
|
|
423
|
-
|
|
423
|
+
? theme.fg(
|
|
424
|
+
"dim",
|
|
425
|
+
formatFooterCost(cost, config.footerCurrency, config.footerCurrencyRate),
|
|
426
|
+
) + (usingSubscription ? theme.fg("warning", " sub") : "")
|
|
424
427
|
: "";
|
|
425
428
|
const line1 = joinChips([
|
|
426
429
|
theme.fg("accent", modelLabel),
|
|
@@ -535,6 +535,12 @@ function isToolComponent(value: any): boolean {
|
|
|
535
535
|
return value instanceof ToolExecutionComponent;
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
+
/** 成员离开回合后不再算外层面板,避免点别的卡时被跳过收起。 */
|
|
539
|
+
function clearRoundAnchor(component: any): void {
|
|
540
|
+
if (!component || Object.getOwnPropertyDescriptor(component, "roundAnchor")?.get) return;
|
|
541
|
+
delete component.roundAnchor;
|
|
542
|
+
}
|
|
543
|
+
|
|
538
544
|
function detachAssistantExpansion(component: any): void {
|
|
539
545
|
if (
|
|
540
546
|
typeof component?.[ASSISTANT_SET_EXPANDED_KEY] !== "function" ||
|
|
@@ -546,6 +552,7 @@ function detachAssistantExpansion(component: any): void {
|
|
|
546
552
|
delete component[ASSISTANT_SET_EXPANDED_KEY];
|
|
547
553
|
delete component[ASSISTANT_TOGGLE_ROUND_KEY];
|
|
548
554
|
delete component[ASSISTANT_REENTRY_KEY];
|
|
555
|
+
clearRoundAnchor(component);
|
|
549
556
|
}
|
|
550
557
|
|
|
551
558
|
/** 供 mouse-interaction 识别可点击的 compact assistant 行(仅 compact 模式下生效)。 */
|
|
@@ -949,8 +956,11 @@ function compactAssistantLineComponent(
|
|
|
949
956
|
text = `${styleCompactThinkingText(heading, theme)}${plain(tools)}`;
|
|
950
957
|
}
|
|
951
958
|
}
|
|
952
|
-
|
|
953
|
-
const
|
|
959
|
+
// 圆点保持 dim;hover 只高亮可点击文字。
|
|
960
|
+
const hintPart = hint
|
|
961
|
+
? `${theme.fg("dim", " · ")}${theme.fg(hover ? "text" : "dim", showMoreHintText())}`
|
|
962
|
+
: "";
|
|
963
|
+
const line = `${text}${hintPart}`;
|
|
954
964
|
const rendered = `${" ".repeat(pad)}${truncateToWidth(line, available, "")}`;
|
|
955
965
|
const lines = leadingBlank ? ["", rendered] : [rendered];
|
|
956
966
|
paint = {
|
|
@@ -1695,6 +1705,10 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1695
1705
|
for (const [component, message] of round.messages) {
|
|
1696
1706
|
component.lastMessage = message;
|
|
1697
1707
|
ensureAssistantSetExpanded(component);
|
|
1708
|
+
// 尾行用 getter 指向 anchor;成员用同名属性,点内部工具时能认出同一回合。
|
|
1709
|
+
if (!Object.getOwnPropertyDescriptor(component, "roundAnchor")?.get) {
|
|
1710
|
+
component.roundAnchor = round.anchor;
|
|
1711
|
+
}
|
|
1698
1712
|
component[ASSISTANT_TOGGLE_ROUND_KEY] = (expanded: boolean) => {
|
|
1699
1713
|
const wasExpanded = round.anchor.expanded === true;
|
|
1700
1714
|
if (expanded && !wasExpanded) capturePanelViewport(round.anchor);
|
|
@@ -1946,7 +1960,10 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1946
1960
|
if (hasToolCalls) {
|
|
1947
1961
|
let round = roundByComponent.get(this);
|
|
1948
1962
|
if (hasText && (!round || round.anchor !== this)) {
|
|
1949
|
-
if (round)
|
|
1963
|
+
if (round) {
|
|
1964
|
+
round.messages.delete(this);
|
|
1965
|
+
clearRoundAnchor(this);
|
|
1966
|
+
}
|
|
1950
1967
|
if (activeRound) finishRound(activeRound);
|
|
1951
1968
|
round = createRound(this, message);
|
|
1952
1969
|
roundByComponent.set(this, round);
|
|
@@ -1966,6 +1983,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1966
1983
|
// 最终回答开始后,当前组件恢复原生文本;它已完成的 thinking
|
|
1967
1984
|
// 留在上一轮摘要中,避免再次生成独立 Thought 行。
|
|
1968
1985
|
round.messages.delete(this);
|
|
1986
|
+
clearRoundAnchor(this);
|
|
1969
1987
|
round.detachedMessages.push(previousMessage);
|
|
1970
1988
|
roundByComponent.delete(this);
|
|
1971
1989
|
finishRound(round);
|
|
@@ -1975,6 +1993,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1975
1993
|
// 围观态下 anchor 还挂着槽位卡,必须重绘才能收回。
|
|
1976
1994
|
endRound(round, true);
|
|
1977
1995
|
roundByComponent.delete(this);
|
|
1996
|
+
clearRoundAnchor(this);
|
|
1978
1997
|
return passThroughAssistant(this, message);
|
|
1979
1998
|
}
|
|
1980
1999
|
if (activeRound) finishRound(activeRound);
|
|
@@ -85,8 +85,8 @@ type GlobalToolRenderPatch = {
|
|
|
85
85
|
prototype: any;
|
|
86
86
|
active: boolean;
|
|
87
87
|
mode: () => CompactStyleMode;
|
|
88
|
-
wrap: (tool: any) => any;
|
|
89
|
-
byDefinition: WeakMap<object, any
|
|
88
|
+
wrap: (tool: any, toolName: string) => any;
|
|
89
|
+
byDefinition: WeakMap<object, Map<string, any>>;
|
|
90
90
|
byName: Map<string, any>;
|
|
91
91
|
downstream: ToolRenderMethods;
|
|
92
92
|
installed: ToolRenderMethods;
|
|
@@ -217,8 +217,8 @@ function renderExpandedTaskResult(
|
|
|
217
217
|
function createCcstyleTool(
|
|
218
218
|
originalTool: any,
|
|
219
219
|
writeExecutionMetadata: WriteExecutionMetadataStore,
|
|
220
|
+
toolName: string,
|
|
220
221
|
): any {
|
|
221
|
-
const toolName = originalTool.name;
|
|
222
222
|
const label = originalTool.label || toolName;
|
|
223
223
|
const defaultTitle = label === toolName ? humanizeToolLabel(label) : label;
|
|
224
224
|
// MCP 工具直接用 adapter 暴露的真实工具名,不做人性化
|
|
@@ -459,19 +459,24 @@ function shouldGloballyStyleTool(component: any, patch: GlobalToolRenderPatch):
|
|
|
459
459
|
|
|
460
460
|
function getGloballyStyledTool(component: any, patch: GlobalToolRenderPatch): any {
|
|
461
461
|
const definition = component.toolDefinition ?? component.builtInToolDefinition;
|
|
462
|
+
const name = String(component.toolName || definition?.name || "tool");
|
|
462
463
|
if (definition && typeof definition === "object") {
|
|
463
|
-
let
|
|
464
|
+
let byName = patch.byDefinition.get(definition);
|
|
465
|
+
if (!byName) {
|
|
466
|
+
byName = new Map();
|
|
467
|
+
patch.byDefinition.set(definition, byName);
|
|
468
|
+
}
|
|
469
|
+
let wrapped = byName.get(name);
|
|
464
470
|
if (!wrapped) {
|
|
465
|
-
wrapped = patch.wrap(definition);
|
|
466
|
-
|
|
471
|
+
wrapped = patch.wrap(definition, name);
|
|
472
|
+
byName.set(name, wrapped);
|
|
467
473
|
}
|
|
468
474
|
return wrapped;
|
|
469
475
|
}
|
|
470
476
|
|
|
471
|
-
const name = String(component.toolName || "tool");
|
|
472
477
|
let wrapped = patch.byName.get(name);
|
|
473
478
|
if (!wrapped) {
|
|
474
|
-
wrapped = patch.wrap({ name, label: name });
|
|
479
|
+
wrapped = patch.wrap({ name, label: name }, name);
|
|
475
480
|
patch.byName.set(name, wrapped);
|
|
476
481
|
}
|
|
477
482
|
return wrapped;
|
|
@@ -568,7 +573,8 @@ function installGlobalToolRendering(
|
|
|
568
573
|
prototype,
|
|
569
574
|
active: true,
|
|
570
575
|
mode: () => config.mode,
|
|
571
|
-
wrap: (tool: any) =>
|
|
576
|
+
wrap: (tool: any, toolName: string) =>
|
|
577
|
+
createCcstyleTool(tool, writeExecutionMetadata, toolName),
|
|
572
578
|
byDefinition: new WeakMap(),
|
|
573
579
|
byName: new Map(),
|
|
574
580
|
downstream,
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
setHoveredCompactAssistant,
|
|
14
14
|
} from "../compact-mode.ts";
|
|
15
15
|
import { isMessageDisplayComponent } from "../tool/message-display.ts";
|
|
16
|
-
import { config } from "../../config/config.ts";
|
|
17
16
|
import { isLazyProxyTui } from "../../utils/fullscreen-detect.ts";
|
|
18
17
|
import { setToolTuiFullscreen, isCollapseHintLine } from "../tool/show-more-hint.ts";
|
|
19
18
|
import {
|
|
@@ -308,10 +307,6 @@ function ensureFullscreenToolMouseMotion(tui: any): void {
|
|
|
308
307
|
releaseFullscreenToolMouseMotion(tui);
|
|
309
308
|
return;
|
|
310
309
|
}
|
|
311
|
-
// 面板改 scrollStepLines 后,下一帧渲染即同步(restore 仍按 original 恢复)。
|
|
312
|
-
if (typeof tui.wheelScrollLines === "number" && tui.wheelScrollLines !== config.scrollStepLines) {
|
|
313
|
-
tui.wheelScrollLines = config.scrollStepLines;
|
|
314
|
-
}
|
|
315
310
|
if (
|
|
316
311
|
!toolMouseInteractionActive() ||
|
|
317
312
|
tui.mouseEnabled === false ||
|
|
@@ -348,7 +343,6 @@ function releaseFullscreenToolMouseMotion(tui?: any): void {
|
|
|
348
343
|
}
|
|
349
344
|
|
|
350
345
|
const FULLSCREEN_VIEWPORT_PATCH = Symbol("ccstyle.fullscreen-viewport-patch");
|
|
351
|
-
const FULLSCREEN_WHEEL_SCROLL_ORIGINAL = Symbol("ccstyle.fullscreen-wheel-scroll-original");
|
|
352
346
|
|
|
353
347
|
/**
|
|
354
348
|
* diff 结果组件自带 remainder 行:声明存在时只有那一行是展开入口(正文里的同名字样不算);
|
|
@@ -418,11 +412,11 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
418
412
|
if (!isTool && !isGroup && !isAssistant && !isThinking && !isMessage) return false;
|
|
419
413
|
if (!component.expanded) {
|
|
420
414
|
// collapsed 仅按钮文本可展开,不能把同一行正文/留白变成点击区。
|
|
415
|
+
// thinking 例外:标题行(标题、隐藏行数与提示同一行)整行都是展开入口。
|
|
421
416
|
const hint = collapsedHintHitbox(line);
|
|
422
417
|
const onHint = Boolean(
|
|
423
418
|
hint &&
|
|
424
|
-
packet.col >= hint.startCol &&
|
|
425
|
-
packet.col <= hint.endCol &&
|
|
419
|
+
(isThinking || (packet.col >= hint.startCol && packet.col <= hint.endCol)) &&
|
|
426
420
|
isCollapsedHintRow(component, line),
|
|
427
421
|
);
|
|
428
422
|
if (!onHint) {
|
|
@@ -437,18 +431,16 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
437
431
|
if (other !== component && other.expanded) {
|
|
438
432
|
// 展开 round 卡内 thinking/工具时,外层 compact 卡是它的容器,不能收起。
|
|
439
433
|
// 面板挂在摘要行时,anchor 与摘要行是同一张外层卡,点内部工具不能收起它。
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
isCompactAssistantComponent(other) &&
|
|
443
|
-
(other === target.owner || other === target.owner?.roundAnchor)
|
|
444
|
-
)
|
|
445
|
-
continue;
|
|
434
|
+
// 同一回合的摘要行、anchor 和其他消息都是外层面板,收起任一个都会关掉整块。
|
|
435
|
+
if ((isThinking || isTool) && isSameExpandedRound(other, target.owner)) continue;
|
|
446
436
|
other.setExpanded(false);
|
|
447
437
|
other.invalidate?.();
|
|
448
438
|
}
|
|
449
439
|
}
|
|
450
440
|
// 展开 round 卡内工具时,让 compact 的强制折叠放行它(非 round 内工具为空操作)。
|
|
451
441
|
if (isTool) markCompactRoundToolExpanded(component);
|
|
442
|
+
// compact 摘要行自己记展开前的视口(收起时还原跟随),这里不动它。
|
|
443
|
+
if (!isAssistant) keepViewportOnExpand(tui);
|
|
452
444
|
component.setExpanded(true);
|
|
453
445
|
clearPendingCollapsePress();
|
|
454
446
|
} else {
|
|
@@ -489,6 +481,25 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
489
481
|
return true;
|
|
490
482
|
}
|
|
491
483
|
|
|
484
|
+
/** 点到的组件和外层卡是否属于同一张 compact 回合面板。 */
|
|
485
|
+
function isSameExpandedRound(other: any, owner: any): boolean {
|
|
486
|
+
if (!owner || !isCompactAssistantComponent(other)) return false;
|
|
487
|
+
const anchor = owner.roundAnchor ?? owner;
|
|
488
|
+
return other === owner || other === anchor || other.roundAnchor === anchor;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* 原地展开时停在当前滚动位置:transcript 跟随底部时,内容变高会把视口拖到底,
|
|
493
|
+
* 点击的行和它上方的外层面板被顶出屏幕。展开前关掉跟随,点击行留在原位。
|
|
494
|
+
*/
|
|
495
|
+
function keepViewportOnExpand(tui: any): void {
|
|
496
|
+
const view = tui.getPrimaryScrollView?.();
|
|
497
|
+
if (typeof view?.scrollTo !== "function" || typeof view.scrollTop !== "number") return;
|
|
498
|
+
view.scrollTo(view.scrollTop, { disableFollow: true });
|
|
499
|
+
// 已脱离底部跟随:亮出回到底部按钮,新输出到来时仍有入口。
|
|
500
|
+
if (view.isFollowingEnd === false) setScrollButtonVisible(true);
|
|
501
|
+
}
|
|
502
|
+
|
|
492
503
|
/**
|
|
493
504
|
* fullscreen 鼠标悬停:collapsed 卡 [click to show more] hint、
|
|
494
505
|
* expanded 卡截断头 show-more、回到底部按钮。motion 不 consume,官方链照常。
|
|
@@ -528,7 +539,8 @@ function handleFullscreenToolHover(tui: any, packet: SgrMousePacket): void {
|
|
|
528
539
|
if (component instanceof ToolGroupComponent) {
|
|
529
540
|
if (overHint) target = { kind: "group", component };
|
|
530
541
|
} else if (component instanceof ThinkingPreviewBlock) {
|
|
531
|
-
|
|
542
|
+
// thinking 折叠标题行整行可点:整块 hover 都高亮动作词,不只高亮按钮文本。
|
|
543
|
+
if (component.expanded || Boolean(hintBox)) target = { kind: "thinking", component };
|
|
532
544
|
} else if (isToolExecutionComponent(component)) {
|
|
533
545
|
let view: ExpandedToolIoView | null = null;
|
|
534
546
|
let section: ToolIoSection | null = null;
|
|
@@ -571,11 +583,6 @@ function patchFullscreenViewportInput(tui: any): void {
|
|
|
571
583
|
const proto = Object.getPrototypeOf(tui);
|
|
572
584
|
const original = proto?.handleViewportInput;
|
|
573
585
|
if (typeof original !== "function") return;
|
|
574
|
-
// 官方原生 routeWheel 已完整处理嵌套 ScrollView;只调整默认步进(config.scrollStepLines)。
|
|
575
|
-
if (typeof tui.wheelScrollLines === "number") {
|
|
576
|
-
tui[FULLSCREEN_WHEEL_SCROLL_ORIGINAL] = tui.wheelScrollLines;
|
|
577
|
-
tui.wheelScrollLines = config.scrollStepLines;
|
|
578
|
-
}
|
|
579
586
|
tui[FULLSCREEN_VIEWPORT_PATCH] = true;
|
|
580
587
|
tui.handleViewportInput = function (this: any, data: string) {
|
|
581
588
|
if (toolMouseInteractionActive() && tui.mode === "fullscreen") {
|
|
@@ -624,11 +631,6 @@ function restoreFullscreenViewportInput(tui: any): void {
|
|
|
624
631
|
if (typeof proto?.handleViewportInput === "function") {
|
|
625
632
|
tui.handleViewportInput = proto.handleViewportInput;
|
|
626
633
|
}
|
|
627
|
-
const originalWheelScrollLines = tui[FULLSCREEN_WHEEL_SCROLL_ORIGINAL];
|
|
628
|
-
if (typeof originalWheelScrollLines === "number") {
|
|
629
|
-
tui.wheelScrollLines = originalWheelScrollLines;
|
|
630
|
-
tui[FULLSCREEN_WHEEL_SCROLL_ORIGINAL] = undefined;
|
|
631
|
-
}
|
|
632
634
|
tui[FULLSCREEN_VIEWPORT_PATCH] = false;
|
|
633
635
|
}
|
|
634
636
|
|
|
@@ -235,7 +235,10 @@ export function codemodeCollapsedLines(options: {
|
|
|
235
235
|
const summary = codemodeSummaryParts(calls, codemodeOutputLineCount(result), running);
|
|
236
236
|
// 没有子调用时不画衔接符,避免孤零零一个 └
|
|
237
237
|
const prefix = shown.length > 0 ? `${INDENT}${fg("dim", "└")} ` : INDENT;
|
|
238
|
-
|
|
238
|
+
// 圆点保持 dim;hover 只高亮可点击文字。
|
|
239
|
+
const hint = running
|
|
240
|
+
? ""
|
|
241
|
+
: ` ${fg("dim", "·")} ${fg(options.hovered ? "text" : "dim", showMoreHintText())}`;
|
|
239
242
|
const bodyWidth = Math.max(1, rowWidth - visibleWidth(prefix) - visibleWidth(hint));
|
|
240
243
|
const body = fg(isError ? "error" : "muted", fitSummaryParts(summary, bodyWidth));
|
|
241
244
|
lines.push(truncateToWidth(`${prefix}${body}${hint}`, rowWidth, ""));
|