@buyi1net/pi-toolkit 0.0.1
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/LICENSE +21 -0
- package/README.md +45 -0
- package/assembler.ts +127 -0
- package/config.ts +201 -0
- package/i18n.ts +693 -0
- package/index.ts +99 -0
- package/menu/items.ts +264 -0
- package/menu/panels.ts +91 -0
- package/menu/settings-list.ts +84 -0
- package/menu/theme.ts +31 -0
- package/menu/toolkit-menu.ts +139 -0
- package/module.ts +156 -0
- package/modules/eyes/chain.ts +173 -0
- package/modules/eyes/config.ts +319 -0
- package/modules/eyes/index.ts +161 -0
- package/modules/eyes/menu.ts +828 -0
- package/modules/eyes/pi-model-backend.ts +245 -0
- package/modules/eyes/resilience.ts +161 -0
- package/modules/eyes/vision-bridge.ts +232 -0
- package/modules/eyes/vision-cache.ts +86 -0
- package/modules/eyes/vision-json.ts +19 -0
- package/modules/eyes/vision-preprocess.ts +192 -0
- package/modules/eyes/vision-probe.ts +21 -0
- package/modules/eyes/vision-prompt.ts +90 -0
- package/modules/eyes/vision-tool.ts +59 -0
- package/modules/eyes/vision-types.ts +33 -0
- package/modules/index.ts +10 -0
- package/modules/subagents/agents/researcher.md +51 -0
- package/modules/subagents/agents/scout.md +40 -0
- package/modules/subagents/agents/worker.md +79 -0
- package/modules/subagents/config.json.example +6 -0
- package/modules/subagents/config.ts +144 -0
- package/modules/subagents/index.ts +91 -0
- package/modules/subagents/menu.ts +388 -0
- package/modules/subagents/src/activity.ts +511 -0
- package/modules/subagents/src/agents.ts +126 -0
- package/modules/subagents/src/command.ts +37 -0
- package/modules/subagents/src/dependencies.ts +246 -0
- package/modules/subagents/src/diagnostics.ts +13 -0
- package/modules/subagents/src/display.ts +94 -0
- package/modules/subagents/src/headless.ts +342 -0
- package/modules/subagents/src/herdr.ts +203 -0
- package/modules/subagents/src/index.ts +2798 -0
- package/modules/subagents/src/inspect-tool.ts +839 -0
- package/modules/subagents/src/launch-config.ts +196 -0
- package/modules/subagents/src/layout-budget.ts +26 -0
- package/modules/subagents/src/list-tool.ts +292 -0
- package/modules/subagents/src/message-tool.ts +808 -0
- package/modules/subagents/src/names.ts +17 -0
- package/modules/subagents/src/pane-layout.ts +49 -0
- package/modules/subagents/src/params.ts +145 -0
- package/modules/subagents/src/renderers.ts +181 -0
- package/modules/subagents/src/result.ts +43 -0
- package/modules/subagents/src/retention.ts +114 -0
- package/modules/subagents/src/route-error.ts +212 -0
- package/modules/subagents/src/routing.ts +235 -0
- package/modules/subagents/src/runtime-registry.ts +159 -0
- package/modules/subagents/src/session.ts +801 -0
- package/modules/subagents/src/status.ts +513 -0
- package/modules/subagents/src/stop-tool.ts +235 -0
- package/modules/subagents/src/subagent-done.ts +590 -0
- package/modules/subagents/src/subagent-tool.ts +1155 -0
- package/modules/subagents/src/surface.ts +355 -0
- package/modules/subagents/src/team-dispatch-tool.ts +219 -0
- package/modules/subagents/src/team.ts +232 -0
- package/modules/subagents/src/tmux.ts +210 -0
- package/modules/subagents/src/tools/safe-bash.ts +72 -0
- package/modules/subagents/src/types.ts +131 -0
- package/modules/tui/adapter/provider-usage.ts +143 -0
- package/modules/tui/config.ts +50 -0
- package/modules/tui/index.ts +120 -0
- package/modules/tui/kernel/pkg/shared/grok-subscription.ts +169 -0
- package/modules/tui/kernel/pkg/shared/official-subscription.ts +237 -0
- package/modules/tui/kernel/pkg/shared/provider-catalog.ts +367 -0
- package/modules/tui/kernel/pkg/shared/provider-contracts.ts +150 -0
- package/modules/tui/kernel/pkg/shared/provider-display.ts +55 -0
- package/modules/tui/kernel/pkg/shared/provider-parsers.ts +171 -0
- package/modules/tui/kernel/pkg/shared/volcengine.ts +191 -0
- package/modules/tui/kernel/pkg/shared/zhipu.ts +149 -0
- package/modules/tui/kernel/pkg/usage-core/index.ts +335 -0
- package/modules/tui/kernel/pkg/usage-core/provider-routes.ts +187 -0
- package/modules/tui/kernel/pkg/usage-node/index.ts +635 -0
- package/modules/tui/kernel/pkg/usage-node/provider-usage.ts +388 -0
- package/modules/tui/kernel/usage-core.ts +2 -0
- package/modules/tui/kernel/usage-node.ts +2 -0
- package/modules/tui/menu.ts +418 -0
- package/modules/tui/plugin/editor.ts +396 -0
- package/modules/tui/plugin/footer.ts +161 -0
- package/modules/tui/plugin/index.ts +30 -0
- package/modules/tui/plugin/lifecycle.ts +637 -0
- package/modules/tui/plugin/package-order.ts +169 -0
- package/modules/tui/plugin/screen-transition.ts +203 -0
- package/modules/tui/plugin/settings-config.ts +297 -0
- package/modules/tui/plugin/status-sources.ts +49 -0
- package/modules/tui/plugin/transition-gate.ts +261 -0
- package/modules/tui/renderer/custom-header.ts +157 -0
- package/modules/tui/renderer/editor.ts +70 -0
- package/modules/tui/renderer/header.ts +72 -0
- package/modules/tui/renderer/icons.ts +149 -0
- package/modules/tui/renderer/pi-installer-logo.ts +194 -0
- package/modules/tui/status/auto-compaction.ts +67 -0
- package/modules/tui/status/project-status.ts +655 -0
- package/modules/tui/status/provider-status.ts +120 -0
- package/modules/tui/status/runtime-status.ts +307 -0
- package/modules/tui/status/session-status.ts +325 -0
- package/modules/tui/status/status-config.ts +95 -0
- package/modules/tui/status/status-segments.ts +263 -0
- package/modules/tui/status/turn-telemetry.ts +466 -0
- package/modules/tui/themes/LICENSE.pi-themes-bundle +21 -0
- package/modules/tui/themes/UPSTREAM.md +7 -0
- package/modules/tui/themes/catppuccin-latte.json +80 -0
- package/modules/tui/themes/catppuccin-mocha.json +79 -0
- package/modules/tui/themes/crimson-noir.json +85 -0
- package/modules/tui/themes/dracula.json +79 -0
- package/modules/tui/themes/everforest-dark.json +85 -0
- package/modules/tui/themes/gruvbox-dark.json +85 -0
- package/modules/tui/themes/gruvbox-light.json +85 -0
- package/modules/tui/themes/matrix.json +85 -0
- package/modules/tui/themes/nord.json +85 -0
- package/modules/tui/themes/one-dark.json +85 -0
- package/modules/tui/themes/rose-pine-dawn.json +85 -0
- package/modules/tui/themes/rose-pine.json +85 -0
- package/modules/tui/themes/solarized-dark.json +85 -0
- package/modules/tui/themes/solarized-light.json +85 -0
- package/modules/tui/themes/tokyo-night-storm.json +79 -0
- package/modules/tui/themes/tokyo-night.json +79 -0
- package/package.json +28 -0
- package/services.ts +38 -0
- package/toolkit.ts +147 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
|
|
3
|
+
export type IconMode = "auto" | "nerd" | "unicode" | "ascii";
|
|
4
|
+
export type ResolvedIconMode = Exclude<IconMode, "auto">;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 组件只依赖语义键,不感知当前字体模式,也不自行选择字形。
|
|
8
|
+
* 新状态段需要图标时必须先在这里增加语义,再同时补齐三套 glyph。
|
|
9
|
+
*/
|
|
10
|
+
export interface IconGlyphs {
|
|
11
|
+
readonly brand: string;
|
|
12
|
+
readonly project: string;
|
|
13
|
+
readonly runtime: string;
|
|
14
|
+
readonly gitBranch: string;
|
|
15
|
+
readonly model: string;
|
|
16
|
+
readonly thinking: string;
|
|
17
|
+
readonly context: string;
|
|
18
|
+
readonly duration: string;
|
|
19
|
+
readonly latency: string;
|
|
20
|
+
readonly speed: string;
|
|
21
|
+
readonly session: string;
|
|
22
|
+
readonly inputTokens: string;
|
|
23
|
+
readonly outputTokens: string;
|
|
24
|
+
readonly cache: string;
|
|
25
|
+
readonly cost: string;
|
|
26
|
+
readonly provider: string;
|
|
27
|
+
readonly usage: string;
|
|
28
|
+
readonly turns: string;
|
|
29
|
+
readonly compaction: string;
|
|
30
|
+
readonly extensions: string;
|
|
31
|
+
readonly changed: string;
|
|
32
|
+
readonly untracked: string;
|
|
33
|
+
readonly ahead: string;
|
|
34
|
+
readonly behind: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const NERD_GLYPHS: IconGlyphs = Object.freeze({
|
|
38
|
+
brand: "",
|
|
39
|
+
project: "",
|
|
40
|
+
runtime: "",
|
|
41
|
+
gitBranch: "",
|
|
42
|
+
model: "",
|
|
43
|
+
thinking: "",
|
|
44
|
+
context: "",
|
|
45
|
+
duration: "",
|
|
46
|
+
latency: "⏳",
|
|
47
|
+
speed: "",
|
|
48
|
+
session: "",
|
|
49
|
+
inputTokens: "",
|
|
50
|
+
outputTokens: "",
|
|
51
|
+
cache: "",
|
|
52
|
+
cost: "",
|
|
53
|
+
usage: "📶",
|
|
54
|
+
turns: "💬",
|
|
55
|
+
provider: "",
|
|
56
|
+
compaction: "📦",
|
|
57
|
+
extensions: "",
|
|
58
|
+
changed: "+",
|
|
59
|
+
untracked: "~",
|
|
60
|
+
ahead: "↑",
|
|
61
|
+
behind: "↓",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const UNICODE_GLYPHS: IconGlyphs = Object.freeze({
|
|
65
|
+
brand: "◇",
|
|
66
|
+
project: "📂",
|
|
67
|
+
runtime: "◩",
|
|
68
|
+
gitBranch: "⎇",
|
|
69
|
+
model: "◆",
|
|
70
|
+
thinking: "✦",
|
|
71
|
+
context: "🪟",
|
|
72
|
+
duration: "⏱️",
|
|
73
|
+
latency: "⏳",
|
|
74
|
+
speed: "⚡",
|
|
75
|
+
session: "●",
|
|
76
|
+
inputTokens: "↑",
|
|
77
|
+
outputTokens: "↓",
|
|
78
|
+
cache: "↻",
|
|
79
|
+
cost: "¤",
|
|
80
|
+
usage: "📶",
|
|
81
|
+
turns: "💬",
|
|
82
|
+
provider: "◈",
|
|
83
|
+
compaction: "📦",
|
|
84
|
+
extensions: "◇",
|
|
85
|
+
changed: "+",
|
|
86
|
+
untracked: "~",
|
|
87
|
+
ahead: "↑",
|
|
88
|
+
behind: "↓",
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const ASCII_GLYPHS: IconGlyphs = Object.freeze({
|
|
92
|
+
brand: "pi",
|
|
93
|
+
project: "dir",
|
|
94
|
+
runtime: "runtime",
|
|
95
|
+
gitBranch: "git",
|
|
96
|
+
model: "model",
|
|
97
|
+
thinking: "think",
|
|
98
|
+
context: "ctx",
|
|
99
|
+
duration: "time",
|
|
100
|
+
latency: "~",
|
|
101
|
+
speed: ">",
|
|
102
|
+
session: "session",
|
|
103
|
+
inputTokens: "in",
|
|
104
|
+
outputTokens: "out",
|
|
105
|
+
cache: "cache",
|
|
106
|
+
cost: "$",
|
|
107
|
+
usage: "sig",
|
|
108
|
+
turns: "msg",
|
|
109
|
+
provider: "provider",
|
|
110
|
+
compaction: "cmp",
|
|
111
|
+
extensions: "ext",
|
|
112
|
+
changed: "+",
|
|
113
|
+
untracked: "~",
|
|
114
|
+
ahead: "^",
|
|
115
|
+
behind: "v",
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
/** 为状态栏左侧图标预留两列,再追加一列文字间距。 */
|
|
119
|
+
export function formatLeadingIcon(glyph: string): string {
|
|
120
|
+
return `${glyph}${" ".repeat(Math.max(1, 3 - visibleWidth(glyph)))}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function isAsciiOnlyTerminal(env: Readonly<Record<string, string | undefined>>): boolean {
|
|
124
|
+
const locale = env.LC_ALL ?? env.LC_CTYPE ?? env.LANG;
|
|
125
|
+
return /^(?:c|posix)$/i.test(locale?.trim() ?? "");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function resolveIconMode(
|
|
129
|
+
mode: IconMode,
|
|
130
|
+
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
131
|
+
): ResolvedIconMode {
|
|
132
|
+
if (mode !== "auto") return mode;
|
|
133
|
+
if (isAsciiOnlyTerminal(env)) return "ascii";
|
|
134
|
+
return "unicode";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function resolveGlyphs(
|
|
138
|
+
mode: IconMode,
|
|
139
|
+
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
140
|
+
): IconGlyphs {
|
|
141
|
+
switch (resolveIconMode(mode, env)) {
|
|
142
|
+
case "nerd":
|
|
143
|
+
return NERD_GLYPHS;
|
|
144
|
+
case "ascii":
|
|
145
|
+
return ASCII_GLYPHS;
|
|
146
|
+
default:
|
|
147
|
+
return UNICODE_GLYPHS;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi 官方安装器 Logo 动画的 TypeScript 适配。
|
|
3
|
+
*
|
|
4
|
+
* 坐标、方块落位顺序、闪烁阶段和时序均对应 pi.dev/install.sh 中的
|
|
5
|
+
* pi_logo_animation;这里只把安装器的 8×9 方格裁剪为横幅使用的 6×4 小尺寸,
|
|
6
|
+
* 每个方格仍保持安装器原始的两个终端字符宽度。
|
|
7
|
+
* 来源:https://pi.dev/install.sh
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type PiLogoColor = "panel" | "cyan" | "red" | "green" | "orange" | "white" | "flash";
|
|
11
|
+
export type PiInstallerLogoFrame = readonly (readonly PiLogoColor[])[];
|
|
12
|
+
|
|
13
|
+
export interface PiInstallerLogoAnimationFrame {
|
|
14
|
+
frame: PiInstallerLogoFrame;
|
|
15
|
+
durationMs: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const SOURCE_WIDTH = 8;
|
|
19
|
+
const SOURCE_HEIGHT = 9;
|
|
20
|
+
// 安装器的源网格按 x=1..8 绘制;裁剪 x=2..7 以去掉左侧空列。
|
|
21
|
+
const CROP_LEFT = 1;
|
|
22
|
+
const CROP_TOP = 3;
|
|
23
|
+
const CROP_WIDTH = 6;
|
|
24
|
+
const CROP_HEIGHT = 4;
|
|
25
|
+
|
|
26
|
+
const LEFT_PIECE: readonly [number, number][] = [
|
|
27
|
+
[0, 0],
|
|
28
|
+
[1, 0],
|
|
29
|
+
[1, 1],
|
|
30
|
+
[2, 0],
|
|
31
|
+
];
|
|
32
|
+
const TOP_PIECE: readonly [number, number][] = [
|
|
33
|
+
[0, 0],
|
|
34
|
+
[0, 1],
|
|
35
|
+
[0, 2],
|
|
36
|
+
[1, 2],
|
|
37
|
+
];
|
|
38
|
+
const RIGHT_PIECE: readonly [number, number][] = [
|
|
39
|
+
[0, 0],
|
|
40
|
+
[1, 0],
|
|
41
|
+
[2, 0],
|
|
42
|
+
[2, 1],
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const WHITE_CELLS: readonly [number, number][] = [
|
|
46
|
+
[3, 2],
|
|
47
|
+
[3, 3],
|
|
48
|
+
[3, 4],
|
|
49
|
+
[4, 2],
|
|
50
|
+
[4, 4],
|
|
51
|
+
[5, 2],
|
|
52
|
+
[5, 3],
|
|
53
|
+
[5, 5],
|
|
54
|
+
[6, 2],
|
|
55
|
+
[6, 5],
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const CYAN_CELLS: readonly [number, number][] = [
|
|
59
|
+
[2, 2],
|
|
60
|
+
[2, 3],
|
|
61
|
+
[2, 4],
|
|
62
|
+
[3, 4],
|
|
63
|
+
];
|
|
64
|
+
const RED_CELLS: readonly [number, number][] = [
|
|
65
|
+
[3, 2],
|
|
66
|
+
[4, 2],
|
|
67
|
+
[4, 3],
|
|
68
|
+
[5, 2],
|
|
69
|
+
];
|
|
70
|
+
const GREEN_CELLS: readonly [number, number][] = [
|
|
71
|
+
[4, 5],
|
|
72
|
+
[5, 5],
|
|
73
|
+
[6, 5],
|
|
74
|
+
[6, 6],
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
function contains(cells: readonly [number, number][], y: number, x: number): boolean {
|
|
78
|
+
return cells.some(([cellY, cellX]) => cellY === y && cellX === x);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function containsPiece(
|
|
82
|
+
cells: readonly [number, number][],
|
|
83
|
+
y: number,
|
|
84
|
+
x: number,
|
|
85
|
+
pieceY: number,
|
|
86
|
+
pieceX: number,
|
|
87
|
+
): boolean {
|
|
88
|
+
return cells.some(([dy, dx]) => y === pieceY + dy && x === pieceX + dx);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function sourceCellColor(
|
|
92
|
+
phase: number,
|
|
93
|
+
active: "none" | "left" | "top" | "right",
|
|
94
|
+
activeX: number,
|
|
95
|
+
activeY: number,
|
|
96
|
+
flash: boolean,
|
|
97
|
+
white: boolean,
|
|
98
|
+
y: number,
|
|
99
|
+
x: number,
|
|
100
|
+
): PiLogoColor {
|
|
101
|
+
if (white) return contains(WHITE_CELLS, y, x) ? "white" : "panel";
|
|
102
|
+
if (flash && y === 6 && x >= 1 && x <= 6) return "flash";
|
|
103
|
+
|
|
104
|
+
if (active === "left" && containsPiece(LEFT_PIECE, y, x, activeY, activeX)) return "red";
|
|
105
|
+
if (active === "top" && containsPiece(TOP_PIECE, y, x, activeY, activeX)) return "cyan";
|
|
106
|
+
if (active === "right" && containsPiece(RIGHT_PIECE, y, x, activeY, activeX)) return "green";
|
|
107
|
+
|
|
108
|
+
if (phase === 4) {
|
|
109
|
+
if (contains(CYAN_CELLS, y, x)) return "cyan";
|
|
110
|
+
if (contains(RED_CELLS, y, x)) return "red";
|
|
111
|
+
if (contains([[4, 5], [5, 5]] as const, y, x)) return "green";
|
|
112
|
+
return "panel";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (phase >= 5) {
|
|
116
|
+
if (contains([[3, 2], [3, 3], [3, 4], [4, 4]] as const, y, x)) return "cyan";
|
|
117
|
+
if (contains([[4, 2], [5, 2], [5, 3], [6, 2]] as const, y, x)) return "red";
|
|
118
|
+
if (contains([[5, 5], [6, 5]] as const, y, x)) return "green";
|
|
119
|
+
return "panel";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (phase <= 3 && contains([[6, 1], [6, 2], [6, 3], [6, 4]] as const, y, x)) return "orange";
|
|
123
|
+
if (phase >= 2 && contains(CYAN_CELLS, y, x)) return "cyan";
|
|
124
|
+
if (phase >= 1 && contains(RED_CELLS, y, x)) return "red";
|
|
125
|
+
if (phase >= 3 && contains(GREEN_CELLS, y, x)) return "green";
|
|
126
|
+
return "panel";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function makeFrame(
|
|
130
|
+
phase: number,
|
|
131
|
+
active: "none" | "left" | "top" | "right",
|
|
132
|
+
activeX: number,
|
|
133
|
+
activeY: number,
|
|
134
|
+
flash: boolean,
|
|
135
|
+
white: boolean,
|
|
136
|
+
): PiInstallerLogoFrame {
|
|
137
|
+
const source = Array.from({ length: SOURCE_HEIGHT }, (_, y) =>
|
|
138
|
+
Array.from({ length: SOURCE_WIDTH }, (_, x) =>
|
|
139
|
+
sourceCellColor(phase, active, activeX, activeY, flash, white, y, x + 1),
|
|
140
|
+
),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return Array.from({ length: CROP_HEIGHT }, (_, row) =>
|
|
144
|
+
source.slice(CROP_TOP, CROP_TOP + CROP_HEIGHT)[row].slice(CROP_LEFT, CROP_LEFT + CROP_WIDTH),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const animationFrames: PiInstallerLogoAnimationFrame[] = [];
|
|
149
|
+
const addFrame = (
|
|
150
|
+
phase: number,
|
|
151
|
+
active: "none" | "left" | "top" | "right",
|
|
152
|
+
activeX: number,
|
|
153
|
+
activeY: number,
|
|
154
|
+
flash: boolean,
|
|
155
|
+
white: boolean,
|
|
156
|
+
durationMs: number,
|
|
157
|
+
): void => {
|
|
158
|
+
animationFrames.push({
|
|
159
|
+
frame: makeFrame(phase, active, activeX, activeY, flash, white),
|
|
160
|
+
durationMs,
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
for (const y of [0, 1, 2, 3]) addFrame(0, "left", 2, y, false, false, 75);
|
|
165
|
+
for (const y of [0, 1, 2]) addFrame(1, "top", 2, y, false, false, 75);
|
|
166
|
+
for (const y of [0, 1, 2, 3, 4]) addFrame(2, "right", 5, y, false, false, 75);
|
|
167
|
+
addFrame(3, "none", 0, 0, false, false, 250);
|
|
168
|
+
addFrame(3, "none", 0, 0, true, false, 80);
|
|
169
|
+
addFrame(3, "none", 0, 0, false, false, 80);
|
|
170
|
+
addFrame(3, "none", 0, 0, true, false, 80);
|
|
171
|
+
addFrame(4, "none", 0, 0, false, false, 100);
|
|
172
|
+
addFrame(5, "none", 0, 0, false, false, 450);
|
|
173
|
+
addFrame(5, "none", 0, 0, false, true, 120);
|
|
174
|
+
addFrame(5, "none", 0, 0, false, false, 120);
|
|
175
|
+
addFrame(5, "none", 0, 0, false, true, 450);
|
|
176
|
+
|
|
177
|
+
function frameWidth(frame: PiInstallerLogoFrame): number {
|
|
178
|
+
let maxCell = 0;
|
|
179
|
+
for (const row of frame) {
|
|
180
|
+
for (let index = row.length - 1; index >= 0; index -= 1) {
|
|
181
|
+
if (row[index] !== "panel") {
|
|
182
|
+
maxCell = Math.max(maxCell, index + 1);
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return maxCell * 2;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export const PI_INSTALLER_LOGO_ANIMATION = animationFrames;
|
|
191
|
+
export const PI_INSTALLER_LOGO_FRAMES = animationFrames.map(({ frame }) => frame);
|
|
192
|
+
export const PI_INSTALLER_LOGO_DURATIONS_MS = animationFrames.map(({ durationMs }) => durationMs);
|
|
193
|
+
// 按完整动画的最大实际占用宽度固定右侧锚点,避免动画帧之间文字跳动。
|
|
194
|
+
export const PI_INSTALLER_LOGO_WIDTH = Math.max(...PI_INSTALLER_LOGO_FRAMES.map(frameWidth));
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { watch } from "node:fs";
|
|
2
|
+
|
|
3
|
+
export function watchAgentSettings(agentDir: string, onChange: () => void): () => void {
|
|
4
|
+
try {
|
|
5
|
+
const watcher = watch(agentDir, { persistent: false }, (_eventType, filename) => {
|
|
6
|
+
if (filename && filename.toString().toLowerCase() !== "settings.json") return;
|
|
7
|
+
onChange();
|
|
8
|
+
});
|
|
9
|
+
watcher.on("error", () => {});
|
|
10
|
+
return () => watcher.close();
|
|
11
|
+
} catch {
|
|
12
|
+
return () => {};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class AutoCompactionStatusController {
|
|
17
|
+
private enabled: boolean;
|
|
18
|
+
private readonly readEnabled: () => boolean;
|
|
19
|
+
private readonly requestRender: () => void;
|
|
20
|
+
private readonly stopWatching: () => void;
|
|
21
|
+
private disposed = false;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
readEnabled: () => boolean,
|
|
25
|
+
subscribe: (onChange: () => void) => () => void,
|
|
26
|
+
requestRender: () => void,
|
|
27
|
+
) {
|
|
28
|
+
this.readEnabled = readEnabled;
|
|
29
|
+
this.requestRender = requestRender;
|
|
30
|
+
this.enabled = this.readCurrentValue();
|
|
31
|
+
try {
|
|
32
|
+
this.stopWatching = subscribe(() => this.refresh());
|
|
33
|
+
} catch {
|
|
34
|
+
this.stopWatching = () => {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getSnapshot(): boolean {
|
|
39
|
+
return this.enabled;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
refresh(): void {
|
|
43
|
+
if (this.disposed) return;
|
|
44
|
+
const enabled = this.readCurrentValue();
|
|
45
|
+
if (enabled === this.enabled) return;
|
|
46
|
+
this.enabled = enabled;
|
|
47
|
+
this.requestRender();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
dispose(): void {
|
|
51
|
+
if (this.disposed) return;
|
|
52
|
+
this.disposed = true;
|
|
53
|
+
try {
|
|
54
|
+
this.stopWatching();
|
|
55
|
+
} catch {
|
|
56
|
+
// 文件监听清理失败不能阻断其余 UI 组件的回滚。
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private readCurrentValue(): boolean {
|
|
61
|
+
try {
|
|
62
|
+
return this.readEnabled();
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|