@herbertgao/pi-extensions 2026.10.4 → 2026.10.5
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 +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +12 -7
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +12 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +67 -16
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +240 -48
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer-currency.ts +29 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +92 -59
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/git-stats.ts +162 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/token-rate.ts +192 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/working-message.ts +51 -65
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +113 -25
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +160 -24
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +30 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +26 -24
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/sticky-prompt.ts +402 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/ask-user-question.ts +173 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +4 -8
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-edit-render.ts +64 -11
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +41 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +5 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +35 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +101 -31
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +3 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +106 -15
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/format.ts +9 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/patch-keys.ts +5 -0
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +4 -4
- package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-dark.json +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-light.json +1 -1
- package/package.json +2 -2
|
@@ -11,7 +11,12 @@ import {
|
|
|
11
11
|
applyCustomFooter,
|
|
12
12
|
clearCustomFooter,
|
|
13
13
|
getFooterStatusSnapshot,
|
|
14
|
+
refreshFooterGitStats,
|
|
14
15
|
} from "../feature/shell/footer.ts";
|
|
16
|
+
import {
|
|
17
|
+
FOOTER_CURRENCY_PRESETS,
|
|
18
|
+
presetFooterCurrencyRate,
|
|
19
|
+
} from "../feature/shell/footer-currency.ts";
|
|
15
20
|
import {
|
|
16
21
|
footerChipDescription,
|
|
17
22
|
footerLineOfKey,
|
|
@@ -37,21 +42,24 @@ import {
|
|
|
37
42
|
EXPANDED_INPUT_MAX_LINES_VALUES,
|
|
38
43
|
EXPANDED_OUTPUT_MAX_LINES_VALUES,
|
|
39
44
|
EXPANDED_PREVIEW_MAX_LINES_VALUES,
|
|
45
|
+
FOOTER_GIT_STATS_MODES,
|
|
46
|
+
FOOTER_TOKEN_RATE_MODES,
|
|
40
47
|
formatExcludeRenderers,
|
|
41
48
|
getCompactThinkingConfig,
|
|
42
|
-
pickInputClip,
|
|
43
49
|
pickPositiveInt,
|
|
44
50
|
pickPositiveNumber,
|
|
45
|
-
|
|
51
|
+
pickToolLabelClip,
|
|
46
52
|
THINKING_ANIMATION_INTERVAL_VALUES,
|
|
47
53
|
THINKING_PREVIEW_LINES_VALUES,
|
|
48
|
-
|
|
54
|
+
TOOL_LABEL_CLIP_VALUES,
|
|
49
55
|
WRITE_DIFF_COLLAPSED_LINES_VALUES,
|
|
50
56
|
updateConfig,
|
|
51
57
|
type CompactStyleMode,
|
|
52
58
|
type Config,
|
|
53
59
|
type DiffIndicatorMode,
|
|
54
60
|
type DiffViewMode,
|
|
61
|
+
type FooterGitStatsMode,
|
|
62
|
+
type FooterTokenRateMode,
|
|
55
63
|
} from "./config.ts";
|
|
56
64
|
|
|
57
65
|
/** renderer 注入的渲染副作用,面板自身不触碰渲染状态。 */
|
|
@@ -62,7 +70,7 @@ export type CcstylePanelHooks = {
|
|
|
62
70
|
|
|
63
71
|
function modeSettingDescription(mode: CompactStyleMode): string {
|
|
64
72
|
if (mode === "compact") {
|
|
65
|
-
return "
|
|
73
|
+
return "One summary line per assistant round; edit/write reuse the same Diff preview settings as on.";
|
|
66
74
|
}
|
|
67
75
|
if (mode === "off") {
|
|
68
76
|
return "Pi native tool rendering. Diff options below still apply independently.";
|
|
@@ -81,6 +89,24 @@ function customFooterDescription(enabled: boolean): string {
|
|
|
81
89
|
return "Custom status bar with model, context, cache, cost, git, and plugin chips.";
|
|
82
90
|
}
|
|
83
91
|
|
|
92
|
+
function footerTokenRateDescription(enabled: boolean): string {
|
|
93
|
+
return enabled
|
|
94
|
+
? "Output rate chip after the cache hit rate. Turn off to hide it."
|
|
95
|
+
: "Output rate chip hidden.";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function footerTokenRateModeDescription(mode: FooterTokenRateMode): string {
|
|
99
|
+
return mode === "live"
|
|
100
|
+
? "Sliding 1s rate while generating (live). Tool waits are excluded. Falls back to the run average between chunks and after the prompt."
|
|
101
|
+
: "Average over the whole user prompt (avg), including tool waits. Stays until the next prompt.";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function footerGitStatsDescription(mode: FooterGitStatsMode): string {
|
|
105
|
+
return mode === "branch"
|
|
106
|
+
? "Net changes since the merge base, including uncommitted edits. Base: origin/HEAD, then local main/master. Untracked files excluded; unavailable stats hidden."
|
|
107
|
+
: "Uncommitted net changes relative to HEAD (default). Includes staged and unstaged edits; excludes untracked files.";
|
|
108
|
+
}
|
|
109
|
+
|
|
84
110
|
function pluginChipsDescription(): string {
|
|
85
111
|
return `Arrange plugin chips: ${formatFooterChipSummary(config)}. Enter to edit.`;
|
|
86
112
|
}
|
|
@@ -281,10 +307,22 @@ function buildExcludeRenderersSubmenu(
|
|
|
281
307
|
};
|
|
282
308
|
}
|
|
283
309
|
|
|
284
|
-
|
|
285
|
-
|
|
310
|
+
function footerCurrencyRateLabel(rate: number | null): string {
|
|
311
|
+
return rate === null ? "-" : String(rate);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* 手动输入子面板。accept 返回字符串则提交,undefined 取消,null 用 invalid 提示。
|
|
316
|
+
*/
|
|
317
|
+
function buildValueInputSubmenu(
|
|
286
318
|
theme: any,
|
|
287
|
-
|
|
319
|
+
options: {
|
|
320
|
+
prompt: string;
|
|
321
|
+
initial: string;
|
|
322
|
+
hint: string;
|
|
323
|
+
accept: (raw: string) => string | null | undefined;
|
|
324
|
+
invalid?: (raw: string) => string;
|
|
325
|
+
},
|
|
288
326
|
closeSubmenu: (selected?: string) => void,
|
|
289
327
|
): {
|
|
290
328
|
render: (width: number) => string[];
|
|
@@ -293,27 +331,28 @@ function buildNumberInputSubmenu(
|
|
|
293
331
|
} {
|
|
294
332
|
const input = new Input();
|
|
295
333
|
let error = "";
|
|
296
|
-
input.setValue(
|
|
334
|
+
input.setValue(options.initial);
|
|
297
335
|
input.onSubmit = (value: string) => {
|
|
298
336
|
const raw = value.trim();
|
|
299
|
-
|
|
300
|
-
|
|
337
|
+
const next = options.accept(raw);
|
|
338
|
+
if (next === undefined) {
|
|
339
|
+
closeSubmenu();
|
|
301
340
|
return;
|
|
302
341
|
}
|
|
303
|
-
if (
|
|
304
|
-
error =
|
|
342
|
+
if (next === null) {
|
|
343
|
+
error = options.invalid?.(raw) ?? "Invalid value";
|
|
305
344
|
return;
|
|
306
345
|
}
|
|
307
|
-
closeSubmenu(
|
|
346
|
+
closeSubmenu(next);
|
|
308
347
|
};
|
|
309
348
|
input.onEscape = () => closeSubmenu();
|
|
310
349
|
return {
|
|
311
350
|
render: (width: number) => {
|
|
312
351
|
const safe = Math.max(0, Math.floor(width));
|
|
313
352
|
const lines = [
|
|
314
|
-
theme.fg("dim",
|
|
353
|
+
theme.fg("dim", options.prompt),
|
|
315
354
|
...input.render(safe),
|
|
316
|
-
truncateToWidth(theme.fg("dim",
|
|
355
|
+
truncateToWidth(theme.fg("dim", options.hint), safe),
|
|
317
356
|
];
|
|
318
357
|
if (error !== "") lines.push(theme.fg("dim", ` ${error}`));
|
|
319
358
|
return lines;
|
|
@@ -323,6 +362,25 @@ function buildNumberInputSubmenu(
|
|
|
323
362
|
};
|
|
324
363
|
}
|
|
325
364
|
|
|
365
|
+
/** 数值项:预填当前值,空输入取消,非数字留下错误。 */
|
|
366
|
+
function buildNumberInputSubmenu(
|
|
367
|
+
theme: any,
|
|
368
|
+
setting: { label: string; currentValue: string },
|
|
369
|
+
closeSubmenu: (selected?: string) => void,
|
|
370
|
+
) {
|
|
371
|
+
return buildValueInputSubmenu(
|
|
372
|
+
theme,
|
|
373
|
+
{
|
|
374
|
+
prompt: ` ${setting.label} — custom value:`,
|
|
375
|
+
initial: setting.currentValue,
|
|
376
|
+
hint: " Enter to apply · Esc to go back",
|
|
377
|
+
accept: (raw) => (raw === "" ? undefined : Number.isFinite(Number(raw)) ? raw : null),
|
|
378
|
+
invalid: (raw) => `Invalid number: "${raw}"`,
|
|
379
|
+
},
|
|
380
|
+
closeSubmenu,
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
326
384
|
/** Section tabs for /ccstyle — matches Zentui-style "A / B / C" headers. */
|
|
327
385
|
type CcstyleSection = {
|
|
328
386
|
id: "style" | "diff" | "thinking" | "ui" | "feature" | "footer";
|
|
@@ -378,8 +436,8 @@ export async function showCcstylePanel(
|
|
|
378
436
|
id: "mode",
|
|
379
437
|
label: "Mode",
|
|
380
438
|
description: modeSettingDescription(config.mode),
|
|
381
|
-
currentValue: config.mode
|
|
382
|
-
values: ["on", "compact
|
|
439
|
+
currentValue: config.mode,
|
|
440
|
+
values: ["on", "compact", "off"],
|
|
383
441
|
};
|
|
384
442
|
// Tracks whether a nested submenu is open so Tab switches sections
|
|
385
443
|
// only at the top level (mirrors Zentui settings: Tab = switch sections).
|
|
@@ -409,15 +467,15 @@ export async function showCcstylePanel(
|
|
|
409
467
|
);
|
|
410
468
|
},
|
|
411
469
|
};
|
|
412
|
-
const
|
|
413
|
-
id: "
|
|
414
|
-
label: "
|
|
470
|
+
const toolLabelClipSetting = {
|
|
471
|
+
id: "toolLabelClip",
|
|
472
|
+
label: "Tool label clip",
|
|
415
473
|
description:
|
|
416
474
|
"Max characters for path/command/name in single and grouped tool summaries; 0 fits available width. Enter to type a custom value.",
|
|
417
|
-
currentValue: String(config.
|
|
418
|
-
values: [...
|
|
475
|
+
currentValue: String(config.toolLabelClip),
|
|
476
|
+
values: [...TOOL_LABEL_CLIP_VALUES],
|
|
419
477
|
submenu: (_current: string, closeSubmenu: (selected?: string) => void) =>
|
|
420
|
-
buildNumberInputSubmenu(theme,
|
|
478
|
+
buildNumberInputSubmenu(theme, toolLabelClipSetting, closeSubmenu),
|
|
421
479
|
};
|
|
422
480
|
const diffViewSetting = {
|
|
423
481
|
id: "diffViewMode",
|
|
@@ -447,7 +505,7 @@ export async function showCcstylePanel(
|
|
|
447
505
|
id: "editDiffCollapsedLines",
|
|
448
506
|
label: "Edit collapsed lines",
|
|
449
507
|
description:
|
|
450
|
-
"
|
|
508
|
+
"Edit diff body lines before the expand hint (Ctrl+O / click). 0 shows only change counts and the hint.",
|
|
451
509
|
currentValue: String(config.editDiffCollapsedLines),
|
|
452
510
|
values: [...DIFF_COLLAPSED_LINES_VALUES],
|
|
453
511
|
submenu: (_current: string, closeSubmenu: (selected?: string) => void) =>
|
|
@@ -551,14 +609,14 @@ export async function showCcstylePanel(
|
|
|
551
609
|
currentValue: config.showStartupHeader ? "on" : "off",
|
|
552
610
|
values: ["on", "off"],
|
|
553
611
|
};
|
|
554
|
-
const
|
|
555
|
-
id: "
|
|
556
|
-
label: "
|
|
557
|
-
description:
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
612
|
+
const stickyUserMessageSetting = {
|
|
613
|
+
id: "stickyUserMessage",
|
|
614
|
+
label: "Sticky user message",
|
|
615
|
+
description: config.stickyUserMessage
|
|
616
|
+
? "Fullscreen: pin a user message's first line once it scrolls off the top edge, until the next one takes over."
|
|
617
|
+
: "User messages scroll normally.",
|
|
618
|
+
currentValue: config.stickyUserMessage ? "on" : "off",
|
|
619
|
+
values: ["on", "off"],
|
|
562
620
|
};
|
|
563
621
|
|
|
564
622
|
// 额外功能开关:注册于扩展加载期,切换后需重启(/reload)生效。
|
|
@@ -604,6 +662,65 @@ export async function showCcstylePanel(
|
|
|
604
662
|
"Aliases disabled.",
|
|
605
663
|
config.enableAliases,
|
|
606
664
|
);
|
|
665
|
+
const footerCurrencySetting = {
|
|
666
|
+
id: "footerCurrency",
|
|
667
|
+
label: "Cost currency",
|
|
668
|
+
description:
|
|
669
|
+
"ISO code for the status-bar cost. Switching currency fills its preset rate. USD clears the rate.",
|
|
670
|
+
currentValue: config.footerCurrency,
|
|
671
|
+
values: [...FOOTER_CURRENCY_PRESETS],
|
|
672
|
+
submenu: (_current: string, closeSubmenu: (selected?: string) => void) => {
|
|
673
|
+
nestedSubmenuOpen = true;
|
|
674
|
+
nestedHint = " Enter to apply · Esc back to Footer";
|
|
675
|
+
return buildValueInputSubmenu(
|
|
676
|
+
theme,
|
|
677
|
+
{
|
|
678
|
+
prompt: " Cost currency — ISO code:",
|
|
679
|
+
initial: footerCurrencySetting.currentValue,
|
|
680
|
+
hint: " Enter to apply · Esc to go back",
|
|
681
|
+
accept: (raw) => (raw === "" ? undefined : raw),
|
|
682
|
+
},
|
|
683
|
+
(selected) => {
|
|
684
|
+
nestedSubmenuOpen = false;
|
|
685
|
+
nestedHint = "";
|
|
686
|
+
closeSubmenu(selected);
|
|
687
|
+
},
|
|
688
|
+
);
|
|
689
|
+
},
|
|
690
|
+
};
|
|
691
|
+
const footerCurrencyRateSetting = {
|
|
692
|
+
id: "footerCurrencyRate",
|
|
693
|
+
label: "Cost rate",
|
|
694
|
+
description:
|
|
695
|
+
"Display units per 1 USD of cost. Switching currency replaces this. Empty keeps the number and only changes the symbol.",
|
|
696
|
+
currentValue: footerCurrencyRateLabel(config.footerCurrencyRate),
|
|
697
|
+
submenu: (_current: string, closeSubmenu: (selected?: string) => void) => {
|
|
698
|
+
nestedSubmenuOpen = true;
|
|
699
|
+
nestedHint = " Enter to apply · empty clears · Esc back to Footer";
|
|
700
|
+
return buildValueInputSubmenu(
|
|
701
|
+
theme,
|
|
702
|
+
{
|
|
703
|
+
prompt: " Cost rate — display units per 1 cost unit:",
|
|
704
|
+
initial:
|
|
705
|
+
footerCurrencyRateSetting.currentValue === "-"
|
|
706
|
+
? ""
|
|
707
|
+
: footerCurrencyRateSetting.currentValue,
|
|
708
|
+
hint: " Enter to apply · empty clears conversion · Esc to go back",
|
|
709
|
+
accept: (raw) => {
|
|
710
|
+
if (raw === "" || raw === "-") return "-";
|
|
711
|
+
const n = Number(raw);
|
|
712
|
+
return Number.isFinite(n) && n > 0 ? raw : null;
|
|
713
|
+
},
|
|
714
|
+
invalid: () => "Rate must be a positive number",
|
|
715
|
+
},
|
|
716
|
+
(selected) => {
|
|
717
|
+
nestedSubmenuOpen = false;
|
|
718
|
+
nestedHint = "";
|
|
719
|
+
closeSubmenu(selected);
|
|
720
|
+
},
|
|
721
|
+
);
|
|
722
|
+
},
|
|
723
|
+
};
|
|
607
724
|
const footerNerdIconsSetting = {
|
|
608
725
|
id: "footerNerdIcons",
|
|
609
726
|
label: "Nerd Font icons",
|
|
@@ -620,6 +737,27 @@ export async function showCcstylePanel(
|
|
|
620
737
|
currentValue: config.enableCustomFooter ? "on" : "off",
|
|
621
738
|
values: ["on", "off"],
|
|
622
739
|
};
|
|
740
|
+
const footerGitStatsSetting = {
|
|
741
|
+
id: "footerGitStatsMode",
|
|
742
|
+
label: "Git changes",
|
|
743
|
+
description: footerGitStatsDescription(config.footerGitStatsMode),
|
|
744
|
+
currentValue: config.footerGitStatsMode,
|
|
745
|
+
values: FOOTER_GIT_STATS_MODES,
|
|
746
|
+
};
|
|
747
|
+
const footerTokenRateSetting = {
|
|
748
|
+
id: "footerTokenRate",
|
|
749
|
+
label: "Token rate",
|
|
750
|
+
description: footerTokenRateDescription(config.footerTokenRate),
|
|
751
|
+
currentValue: config.footerTokenRate ? "on" : "off",
|
|
752
|
+
values: ["on", "off"],
|
|
753
|
+
};
|
|
754
|
+
const footerTokenRateModeSetting = {
|
|
755
|
+
id: "footerTokenRateMode",
|
|
756
|
+
label: "Token rate mode",
|
|
757
|
+
description: footerTokenRateModeDescription(config.footerTokenRateMode),
|
|
758
|
+
currentValue: config.footerTokenRateMode,
|
|
759
|
+
values: FOOTER_TOKEN_RATE_MODES,
|
|
760
|
+
};
|
|
623
761
|
const pluginChipsSetting = {
|
|
624
762
|
id: "footerPluginChips",
|
|
625
763
|
label: "Plugin chips",
|
|
@@ -663,6 +801,52 @@ export async function showCcstylePanel(
|
|
|
663
801
|
ctx.ui.notify(`Updated ${id}: ${value}`, "info");
|
|
664
802
|
return;
|
|
665
803
|
}
|
|
804
|
+
if (id === "footerCurrency") {
|
|
805
|
+
updateConfig({
|
|
806
|
+
footerCurrency: value,
|
|
807
|
+
footerCurrencyRate: presetFooterCurrencyRate(value),
|
|
808
|
+
});
|
|
809
|
+
footerCurrencySetting.currentValue = config.footerCurrency;
|
|
810
|
+
footerCurrencyRateSetting.currentValue = footerCurrencyRateLabel(config.footerCurrencyRate);
|
|
811
|
+
if (config.enableCustomFooter) applyCustomFooter(ctx);
|
|
812
|
+
ctx.ui.notify(
|
|
813
|
+
`Updated ${id}: ${config.footerCurrency} · ${footerCurrencyRateSetting.currentValue}`,
|
|
814
|
+
"info",
|
|
815
|
+
);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (id === "footerCurrencyRate") {
|
|
819
|
+
updateConfig({ footerCurrencyRate: value === "-" ? null : Number(value) });
|
|
820
|
+
footerCurrencyRateSetting.currentValue = footerCurrencyRateLabel(config.footerCurrencyRate);
|
|
821
|
+
if (config.enableCustomFooter) applyCustomFooter(ctx);
|
|
822
|
+
ctx.ui.notify(`Updated ${id}: ${footerCurrencyRateSetting.currentValue}`, "info");
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (id === "footerGitStatsMode") {
|
|
826
|
+
updateConfig({ footerGitStatsMode: value as FooterGitStatsMode });
|
|
827
|
+
footerGitStatsSetting.currentValue = config.footerGitStatsMode;
|
|
828
|
+
footerGitStatsSetting.description = footerGitStatsDescription(config.footerGitStatsMode);
|
|
829
|
+
refreshFooterGitStats();
|
|
830
|
+
ctx.ui.notify(`Updated ${id}: ${value}`, "info");
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (id === "footerTokenRate") {
|
|
834
|
+
const enabled = value === "on";
|
|
835
|
+
updateConfig({ footerTokenRate: enabled });
|
|
836
|
+
footerTokenRateSetting.currentValue = enabled ? "on" : "off";
|
|
837
|
+
footerTokenRateSetting.description = footerTokenRateDescription(enabled);
|
|
838
|
+
ctx.ui.notify(`Updated ${id}: ${value}`, "info");
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
if (id === "footerTokenRateMode") {
|
|
842
|
+
updateConfig({ footerTokenRateMode: value as FooterTokenRateMode });
|
|
843
|
+
footerTokenRateModeSetting.currentValue = config.footerTokenRateMode;
|
|
844
|
+
footerTokenRateModeSetting.description = footerTokenRateModeDescription(
|
|
845
|
+
config.footerTokenRateMode,
|
|
846
|
+
);
|
|
847
|
+
ctx.ui.notify(`Updated ${id}: ${value}`, "info");
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
666
850
|
if (id === "footerNerdIcons") {
|
|
667
851
|
const enabled = value === "on";
|
|
668
852
|
updateConfig({ footerNerdIcons: enabled });
|
|
@@ -682,16 +866,14 @@ export async function showCcstylePanel(
|
|
|
682
866
|
return;
|
|
683
867
|
}
|
|
684
868
|
switch (id) {
|
|
685
|
-
case "
|
|
869
|
+
case "toolLabelClip":
|
|
686
870
|
updateConfig({
|
|
687
|
-
|
|
871
|
+
toolLabelClip: pickToolLabelClip(value),
|
|
688
872
|
});
|
|
689
|
-
|
|
873
|
+
toolLabelClipSetting.currentValue = String(config.toolLabelClip);
|
|
690
874
|
break;
|
|
691
875
|
case "mode": {
|
|
692
|
-
|
|
693
|
-
const mode: CompactStyleMode =
|
|
694
|
-
value === "compact (Experimental)" ? "compact" : (value as CompactStyleMode);
|
|
876
|
+
const mode = value as CompactStyleMode;
|
|
695
877
|
modeSetting.description = modeSettingDescription(mode);
|
|
696
878
|
hooks.applyStyleMode(mode, ctx, toolGrouping);
|
|
697
879
|
return;
|
|
@@ -719,7 +901,7 @@ export async function showCcstylePanel(
|
|
|
719
901
|
editDiffCollapsedLines: pickPositiveInt(
|
|
720
902
|
value,
|
|
721
903
|
DEFAULT_CONFIG.editDiffCollapsedLines,
|
|
722
|
-
|
|
904
|
+
0,
|
|
723
905
|
500,
|
|
724
906
|
),
|
|
725
907
|
});
|
|
@@ -800,6 +982,13 @@ export async function showCcstylePanel(
|
|
|
800
982
|
? "Thinking text uses the theme's dim color."
|
|
801
983
|
: "Keep the default thinking text color.";
|
|
802
984
|
break;
|
|
985
|
+
case "stickyUserMessage": {
|
|
986
|
+
updateConfig({ stickyUserMessage: value === "on" });
|
|
987
|
+
stickyUserMessageSetting.description = config.stickyUserMessage
|
|
988
|
+
? "Fullscreen: pin a user message's first line once it scrolls off the top edge, until the next one takes over."
|
|
989
|
+
: "User messages scroll normally.";
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
803
992
|
case "showStartupHeader": {
|
|
804
993
|
const enabled = value === "on";
|
|
805
994
|
updateConfig({ showStartupHeader: enabled });
|
|
@@ -810,12 +999,6 @@ export async function showCcstylePanel(
|
|
|
810
999
|
else clearStartupHeader(ctx);
|
|
811
1000
|
break;
|
|
812
1001
|
}
|
|
813
|
-
case "scrollStepLines":
|
|
814
|
-
updateConfig({
|
|
815
|
-
scrollStepLines: pickPositiveInt(value, DEFAULT_CONFIG.scrollStepLines, 1, 50),
|
|
816
|
-
});
|
|
817
|
-
scrollStepSetting.currentValue = String(config.scrollStepLines);
|
|
818
|
-
break;
|
|
819
1002
|
default:
|
|
820
1003
|
return;
|
|
821
1004
|
}
|
|
@@ -850,9 +1033,9 @@ export async function showCcstylePanel(
|
|
|
850
1033
|
expandedOutputSetting,
|
|
851
1034
|
expandedMaxSetting,
|
|
852
1035
|
expandedCardBgSetting,
|
|
853
|
-
|
|
1036
|
+
toolLabelClipSetting,
|
|
1037
|
+
stickyUserMessageSetting,
|
|
854
1038
|
startupHeaderSetting,
|
|
855
|
-
scrollStepSetting,
|
|
856
1039
|
],
|
|
857
1040
|
},
|
|
858
1041
|
{
|
|
@@ -880,7 +1063,16 @@ export async function showCcstylePanel(
|
|
|
880
1063
|
{
|
|
881
1064
|
id: "footer",
|
|
882
1065
|
label: "Footer",
|
|
883
|
-
items: [
|
|
1066
|
+
items: [
|
|
1067
|
+
customFooterSetting,
|
|
1068
|
+
footerTokenRateSetting,
|
|
1069
|
+
footerTokenRateModeSetting,
|
|
1070
|
+
footerCurrencySetting,
|
|
1071
|
+
footerCurrencyRateSetting,
|
|
1072
|
+
footerNerdIconsSetting,
|
|
1073
|
+
footerGitStatsSetting,
|
|
1074
|
+
pluginChipsSetting,
|
|
1075
|
+
],
|
|
884
1076
|
},
|
|
885
1077
|
];
|
|
886
1078
|
|
|
@@ -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
|
+
}
|