opencode-visual-cache 1.6.1 → 1.6.2
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/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/index.js +152 -2
- package/dist/tui.js +164 -93
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/index.tsx +160 -20
package/dist/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PLUGIN_VERSION = "1.6.
|
|
1
|
+
export declare const PLUGIN_VERSION = "1.6.2";
|
package/dist/_version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION = "1.6.
|
|
2
|
+
export const PLUGIN_VERSION = "1.6.2";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@opentui/solid/jsx-runtime";
|
|
2
|
-
import { createMemo, createSignal, createEffect, onMount, onCleanup, Show, untrack } from "solid-js";
|
|
2
|
+
import { createMemo, createSignal, createEffect, onMount, onCleanup, Show, For, untrack } from "solid-js";
|
|
3
3
|
import { PLUGIN_VERSION } from "./_version";
|
|
4
4
|
import { balanceProviders, getBalanceProvider, maskKey, matchBalanceProvider } from "./balance-providers";
|
|
5
5
|
import { LANG_META, createT, detectLang } from "./i18n";
|
|
@@ -333,6 +333,11 @@ function TokenCachePanel(props) {
|
|
|
333
333
|
const [distOpen, setDistOpen] = createSignal(false);
|
|
334
334
|
const [skillsOpen, setSkillsOpen] = createSignal(true);
|
|
335
335
|
let boxEl;
|
|
336
|
+
// 侧边栏可见性通知:本面板挂载 ⇒ 宿主侧边栏可见(固定占用 42 列输入框宽度)
|
|
337
|
+
createEffect(() => {
|
|
338
|
+
props.signals.setSidebarVisible(true);
|
|
339
|
+
onCleanup(() => props.signals.setSidebarVisible(false));
|
|
340
|
+
});
|
|
336
341
|
// ── shared signals (de-structured so internal code is unchanged) ──
|
|
337
342
|
const { currencySymbol, setCurrencySymbol, exchangeRate, setExchangeRate, langCode, sectionDetail, setSectionDetail, sectionModel, setSectionModel, sectionDist, setSectionDist, sectionSkills, setSectionSkills, sectionBalance, setSectionBalance, balanceRefresh, balanceProviderId, setBalanceProviderId, autoBalance, setAutoBalance, balanceUnsupported, setBalanceUnsupported, balanceState, balanceCurrency, setBalanceCurrency, borderVisible, setBorderVisible, } = props.signals;
|
|
338
343
|
// ── reactive translation (follows langCode signal) ──
|
|
@@ -857,6 +862,32 @@ function TokenCachePanel(props) {
|
|
|
857
862
|
// ---------------------------------------------------------------------------
|
|
858
863
|
// Plugin entry
|
|
859
864
|
// ---------------------------------------------------------------------------
|
|
865
|
+
/**
|
|
866
|
+
* 路径截断的固定开销(列数):仅宿主布局常量,不含任何内容宽度——
|
|
867
|
+
* 统计宽度、宿主 usage、commands 快捷键均为运行时动态计算。
|
|
868
|
+
* - marginLeft=1 + space-between 余量 ≈ 2 列
|
|
869
|
+
*/
|
|
870
|
+
const PATH_CHROME = 2;
|
|
871
|
+
/**
|
|
872
|
+
* 路径可用宽度低于此列数时整体隐藏(极窄终端下路径信息价值太低,
|
|
873
|
+
* 残留的 "E:\Work…" 反而挤压右侧统计与 commands,直接让位更干净)。
|
|
874
|
+
*/
|
|
875
|
+
const HIDE_PATH_BELOW = 14;
|
|
876
|
+
/**
|
|
877
|
+
* 从宿主 keymap 动态读取命令的快捷键显示文本(与宿主 Prompt 同源),
|
|
878
|
+
* 取不到时回退到传入的默认文本。
|
|
879
|
+
*/
|
|
880
|
+
function keyShortcut(api, command, fallback) {
|
|
881
|
+
try {
|
|
882
|
+
const binds = api.tuiConfig.keybinds.get(command);
|
|
883
|
+
const seq = binds?.map((b) => ({ key: b.key }));
|
|
884
|
+
const s = api.keys.formatBindings(seq);
|
|
885
|
+
return s || fallback;
|
|
886
|
+
}
|
|
887
|
+
catch {
|
|
888
|
+
return fallback;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
860
891
|
/**
|
|
861
892
|
* 输入框 hint 行(session_prompt slot 的 hint):单行显示 路径 · 命中率 · 余额 · Tokens。
|
|
862
893
|
* 通过 ui.Prompt 的 hint prop 注入——宿主右侧的 token/commands 提示自动保留,
|
|
@@ -1000,6 +1031,122 @@ function BottomStatusBar(props) {
|
|
|
1000
1031
|
return "";
|
|
1001
1032
|
}
|
|
1002
1033
|
});
|
|
1034
|
+
const [termW, setTermW] = createSignal(props.api.renderer.terminalWidth);
|
|
1035
|
+
// resize 事件(主通道);事件接口若在插件环境不可用则跳过,由轮询兜底
|
|
1036
|
+
createEffect(() => {
|
|
1037
|
+
const r = props.api.renderer;
|
|
1038
|
+
if (typeof r.on !== "function" || typeof r.off !== "function")
|
|
1039
|
+
return;
|
|
1040
|
+
const onResize = () => setTermW(props.api.renderer.terminalWidth);
|
|
1041
|
+
r.on("resize", onResize);
|
|
1042
|
+
onCleanup(() => r.off("resize", onResize));
|
|
1043
|
+
});
|
|
1044
|
+
// 轮询兜底:事件通道若在插件环境不可用,定期同步终端宽度(值不变时不触发更新)
|
|
1045
|
+
createEffect(() => {
|
|
1046
|
+
const timer = setInterval(() => setTermW(props.api.renderer.terminalWidth), 500);
|
|
1047
|
+
onCleanup(() => clearInterval(timer));
|
|
1048
|
+
});
|
|
1049
|
+
// 统计部分分段(单一数据源):量宽拼接 text,渲染逐段着色,避免双源漂移
|
|
1050
|
+
const statsSegs = createMemo(() => {
|
|
1051
|
+
const s = stats();
|
|
1052
|
+
const hr = s && s.hitRate >= 0 ? (Math.floor(s.hitRate * 10) / 10).toFixed(1) + "%" : "--";
|
|
1053
|
+
const segs = [
|
|
1054
|
+
{ text: t("barHit") + " ", color: pal().muted },
|
|
1055
|
+
{ text: hr, color: hitColor() },
|
|
1056
|
+
];
|
|
1057
|
+
const tr = trend();
|
|
1058
|
+
if (tr !== null) {
|
|
1059
|
+
segs.push({ text: " " + (tr > 0 ? "\u2191" : "\u2193") + Math.abs(tr).toFixed(1) + "%", color: tr > 0 ? pal().success : pal().error });
|
|
1060
|
+
}
|
|
1061
|
+
segs.push({ text: " \u00b7 " + t("barTok") + " ", color: pal().muted });
|
|
1062
|
+
segs.push({ text: s ? fmtCompact(s.input + s.read + s.write) : "--", color: pal().text });
|
|
1063
|
+
if (!props.signals.balanceUnsupported()) {
|
|
1064
|
+
segs.push({ text: " \u00b7 " + t("barBal") + " ", color: pal().muted });
|
|
1065
|
+
segs.push({ text: balanceText(), color: pal().text });
|
|
1066
|
+
}
|
|
1067
|
+
segs.push({ text: " \u00b7 ", color: pal().muted });
|
|
1068
|
+
return segs;
|
|
1069
|
+
});
|
|
1070
|
+
const statsW = createMemo(() => {
|
|
1071
|
+
let w = 0;
|
|
1072
|
+
for (const sg of statsSegs())
|
|
1073
|
+
w += visualWidth(sg.text);
|
|
1074
|
+
return w;
|
|
1075
|
+
});
|
|
1076
|
+
// 宿主右侧 usage 文本复刻(1.18.16 Prompt 口径):
|
|
1077
|
+
// 最后一条 output>0 的 assistant 消息 → tokens 合计格式化 + 模型 context limit 百分比 + session 累计费用
|
|
1078
|
+
const sessionCost = createMemo(() => {
|
|
1079
|
+
try {
|
|
1080
|
+
return num(props.api.state.session.get(sid)?.cost);
|
|
1081
|
+
}
|
|
1082
|
+
catch {
|
|
1083
|
+
return 0;
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
const usageText = createMemo(() => {
|
|
1087
|
+
const id = sid;
|
|
1088
|
+
if (!id)
|
|
1089
|
+
return "";
|
|
1090
|
+
const msgs = props.api.state.session.messages(id);
|
|
1091
|
+
let last;
|
|
1092
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
1093
|
+
const m = msgs[i];
|
|
1094
|
+
if (m.role !== "assistant")
|
|
1095
|
+
continue;
|
|
1096
|
+
const tk = m.tokens;
|
|
1097
|
+
if (tk && num(tk.output) > 0) {
|
|
1098
|
+
last = m;
|
|
1099
|
+
break;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
if (!last)
|
|
1103
|
+
return "";
|
|
1104
|
+
const tk = last.tokens;
|
|
1105
|
+
if (!tk)
|
|
1106
|
+
return "";
|
|
1107
|
+
const tokens = num(tk.input) + num(tk.output) + num(tk.reasoning) + num(tk.cache?.read) + num(tk.cache?.write);
|
|
1108
|
+
if (tokens <= 0)
|
|
1109
|
+
return "";
|
|
1110
|
+
let pct = "";
|
|
1111
|
+
try {
|
|
1112
|
+
const p = props.api.state.provider.find((x) => x.id === last.providerID);
|
|
1113
|
+
const limit = p?.models?.[last.modelID]?.limit?.context;
|
|
1114
|
+
if (typeof limit === "number" && limit > 0)
|
|
1115
|
+
pct = ` (${Math.round((tokens / limit) * 100)}%)`;
|
|
1116
|
+
}
|
|
1117
|
+
catch { }
|
|
1118
|
+
const context = fmtCompact(tokens) + pct;
|
|
1119
|
+
const cost = sessionCost();
|
|
1120
|
+
return cost > 0 ? context + " \u00b7 " + fmtCost(cost) : context;
|
|
1121
|
+
});
|
|
1122
|
+
// 宿主 1513 行右侧文本:usage(有数据)或 "快捷键 agents"(无数据)+ commands,
|
|
1123
|
+
// 快捷键从宿主 keymap 动态读取
|
|
1124
|
+
const rightText = createMemo(() => {
|
|
1125
|
+
const cmds = keyShortcut(props.api, "command.palette.show", "ctrl+p") + " commands";
|
|
1126
|
+
const u = usageText();
|
|
1127
|
+
if (u)
|
|
1128
|
+
return u + " " + cmds;
|
|
1129
|
+
return keyShortcut(props.api, "agent.cycle", "") + " agents " + cmds;
|
|
1130
|
+
});
|
|
1131
|
+
const rightW = createMemo(() => visualWidth(rightText()));
|
|
1132
|
+
// 输入框实际宽度 = 终端宽度 - 侧边栏(可见时 42) - 边距 4
|
|
1133
|
+
// (与宿主 session 布局 contentWidth 口径一致;侧边栏可见性由本面板挂载状态驱动)
|
|
1134
|
+
const inputW = createMemo(() => termW() - (props.signals.sidebarVisible() ? 42 : 0) - 4);
|
|
1135
|
+
// 路径可用宽度 = 输入框宽度 - 统计宽度(精确) - 宿主右侧宽度(动态) - 布局开销;
|
|
1136
|
+
// 低于 HIDE_PATH_BELOW 时整体隐藏路径(宽度归零),把空间让给统计与 commands
|
|
1137
|
+
const dirDisplay = createMemo(() => {
|
|
1138
|
+
const avail = inputW() - statsW() - rightW() - PATH_CHROME;
|
|
1139
|
+
if (avail < HIDE_PATH_BELOW)
|
|
1140
|
+
return "";
|
|
1141
|
+
return truncateVisual(directory(), avail);
|
|
1142
|
+
});
|
|
1143
|
+
// 状态栏关闭(仅路径)时同样在极窄条件下隐藏路径
|
|
1144
|
+
const dirFallback = createMemo(() => {
|
|
1145
|
+
const avail = inputW() - rightW() - PATH_CHROME;
|
|
1146
|
+
if (avail < HIDE_PATH_BELOW)
|
|
1147
|
+
return "";
|
|
1148
|
+
return truncateVisual(directory(), avail);
|
|
1149
|
+
});
|
|
1003
1150
|
// 恢复显隐偏好(默认显示);关闭时回退为仅显示路径,与宿主默认 hint 行一致
|
|
1004
1151
|
onMount(() => {
|
|
1005
1152
|
try {
|
|
@@ -1008,7 +1155,7 @@ function BottomStatusBar(props) {
|
|
|
1008
1155
|
}
|
|
1009
1156
|
catch { }
|
|
1010
1157
|
});
|
|
1011
|
-
return (_jsx(Show, { when: props.signals.sectionBottom(), fallback: _jsx("text", { fg: pal().muted, children:
|
|
1158
|
+
return (_jsx(Show, { when: props.signals.sectionBottom(), fallback: _jsx("text", { fg: pal().muted, children: dirFallback() }), children: _jsxs("box", { marginLeft: 1, flexGrow: 1, flexShrink: 0, flexDirection: "row", justifyContent: "space-between", children: [_jsx("text", { fg: pal().muted, children: dirDisplay() }), _jsx("box", { flexDirection: "row", children: _jsx("text", { children: _jsx(For, { each: statsSegs(), children: (sg) => _jsx("span", { style: { fg: sg.color }, children: sg.text }) }) }) })] }) }));
|
|
1012
1159
|
}
|
|
1013
1160
|
function createSidebarSlot(api, signals) {
|
|
1014
1161
|
let lastSlotSid = "";
|
|
@@ -1047,6 +1194,8 @@ const tui = async (api) => {
|
|
|
1047
1194
|
const [borderVisible, setBorderVisible] = createSignal(true);
|
|
1048
1195
|
const [langCode, setLangCode] = createSignal(INIT_LANG);
|
|
1049
1196
|
const [overrideSessionId, setOverrideSessionId] = createSignal(undefined);
|
|
1197
|
+
// 侧边栏可见性(由 TokenCachePanel 挂载状态驱动):可见时宿主输入框宽度 = 终端宽 - 42 - 4
|
|
1198
|
+
const [sidebarVisible, setSidebarVisible] = createSignal(false);
|
|
1050
1199
|
// ── 余额查询状态(共享):侧边栏与底部栏读同一份数据,
|
|
1051
1200
|
// 避免重复请求导致两处余额不一致 ──
|
|
1052
1201
|
const [balanceState, setBalanceState] = createSignal({
|
|
@@ -1072,6 +1221,7 @@ const tui = async (api) => {
|
|
|
1072
1221
|
balanceCurrency, setBalanceCurrency,
|
|
1073
1222
|
borderVisible, setBorderVisible,
|
|
1074
1223
|
overrideSessionId, setOverrideSessionId,
|
|
1224
|
+
sidebarVisible, setSidebarVisible,
|
|
1075
1225
|
};
|
|
1076
1226
|
api.slots.register(createSidebarSlot(api, signals));
|
|
1077
1227
|
// 输入框 hint 行(session_prompt slot,replace 模式):
|
package/dist/tui.js
CHANGED
|
@@ -10,10 +10,10 @@ import { spread as _$spread } from "@opentui/solid";
|
|
|
10
10
|
import { mergeProps as _$mergeProps } from "@opentui/solid";
|
|
11
11
|
import { memo as _$memo } from "@opentui/solid";
|
|
12
12
|
import { createElement as _$createElement } from "@opentui/solid";
|
|
13
|
-
import { createMemo, createSignal, createEffect, onMount, onCleanup, Show, untrack } from "solid-js";
|
|
13
|
+
import { createMemo, createSignal, createEffect, onMount, onCleanup, Show, For, untrack } from "solid-js";
|
|
14
14
|
|
|
15
15
|
// src/_version.ts
|
|
16
|
-
var PLUGIN_VERSION = "1.6.
|
|
16
|
+
var PLUGIN_VERSION = "1.6.2";
|
|
17
17
|
|
|
18
18
|
// src/balance-providers.ts
|
|
19
19
|
var BalanceError = class extends Error {
|
|
@@ -707,6 +707,10 @@ function TokenCachePanel(props) {
|
|
|
707
707
|
const [distOpen, setDistOpen] = createSignal(false);
|
|
708
708
|
const [skillsOpen, setSkillsOpen] = createSignal(true);
|
|
709
709
|
let boxEl;
|
|
710
|
+
createEffect(() => {
|
|
711
|
+
props.signals.setSidebarVisible(true);
|
|
712
|
+
onCleanup(() => props.signals.setSidebarVisible(false));
|
|
713
|
+
});
|
|
710
714
|
const {
|
|
711
715
|
currencySymbol,
|
|
712
716
|
setCurrencySymbol,
|
|
@@ -1988,6 +1992,20 @@ function TokenCachePanel(props) {
|
|
|
1988
1992
|
return _el$;
|
|
1989
1993
|
})();
|
|
1990
1994
|
}
|
|
1995
|
+
var PATH_CHROME = 2;
|
|
1996
|
+
var HIDE_PATH_BELOW = 14;
|
|
1997
|
+
function keyShortcut(api, command, fallback) {
|
|
1998
|
+
try {
|
|
1999
|
+
const binds = api.tuiConfig.keybinds.get(command);
|
|
2000
|
+
const seq = binds?.map((b) => ({
|
|
2001
|
+
key: b.key
|
|
2002
|
+
}));
|
|
2003
|
+
const s = api.keys.formatBindings(seq);
|
|
2004
|
+
return s || fallback;
|
|
2005
|
+
} catch {
|
|
2006
|
+
return fallback;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
1991
2009
|
function BottomStatusBar(props) {
|
|
1992
2010
|
const KV_PREFIX = "cache_panel";
|
|
1993
2011
|
const t = createT(() => props.signals.langCode());
|
|
@@ -2103,6 +2121,119 @@ function BottomStatusBar(props) {
|
|
|
2103
2121
|
return "";
|
|
2104
2122
|
}
|
|
2105
2123
|
});
|
|
2124
|
+
const [termW, setTermW] = createSignal(props.api.renderer.terminalWidth);
|
|
2125
|
+
createEffect(() => {
|
|
2126
|
+
const r = props.api.renderer;
|
|
2127
|
+
if (typeof r.on !== "function" || typeof r.off !== "function") return;
|
|
2128
|
+
const onResize = () => setTermW(props.api.renderer.terminalWidth);
|
|
2129
|
+
r.on("resize", onResize);
|
|
2130
|
+
onCleanup(() => r.off("resize", onResize));
|
|
2131
|
+
});
|
|
2132
|
+
createEffect(() => {
|
|
2133
|
+
const timer = setInterval(() => setTermW(props.api.renderer.terminalWidth), 500);
|
|
2134
|
+
onCleanup(() => clearInterval(timer));
|
|
2135
|
+
});
|
|
2136
|
+
const statsSegs = createMemo(() => {
|
|
2137
|
+
const s = stats();
|
|
2138
|
+
const hr = s && s.hitRate >= 0 ? (Math.floor(s.hitRate * 10) / 10).toFixed(1) + "%" : "--";
|
|
2139
|
+
const segs = [{
|
|
2140
|
+
text: t("barHit") + " ",
|
|
2141
|
+
color: pal().muted
|
|
2142
|
+
}, {
|
|
2143
|
+
text: hr,
|
|
2144
|
+
color: hitColor()
|
|
2145
|
+
}];
|
|
2146
|
+
const tr = trend();
|
|
2147
|
+
if (tr !== null) {
|
|
2148
|
+
segs.push({
|
|
2149
|
+
text: " " + (tr > 0 ? "\u2191" : "\u2193") + Math.abs(tr).toFixed(1) + "%",
|
|
2150
|
+
color: tr > 0 ? pal().success : pal().error
|
|
2151
|
+
});
|
|
2152
|
+
}
|
|
2153
|
+
segs.push({
|
|
2154
|
+
text: " \xB7 " + t("barTok") + " ",
|
|
2155
|
+
color: pal().muted
|
|
2156
|
+
});
|
|
2157
|
+
segs.push({
|
|
2158
|
+
text: s ? fmtCompact(s.input + s.read + s.write) : "--",
|
|
2159
|
+
color: pal().text
|
|
2160
|
+
});
|
|
2161
|
+
if (!props.signals.balanceUnsupported()) {
|
|
2162
|
+
segs.push({
|
|
2163
|
+
text: " \xB7 " + t("barBal") + " ",
|
|
2164
|
+
color: pal().muted
|
|
2165
|
+
});
|
|
2166
|
+
segs.push({
|
|
2167
|
+
text: balanceText(),
|
|
2168
|
+
color: pal().text
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
segs.push({
|
|
2172
|
+
text: " \xB7 ",
|
|
2173
|
+
color: pal().muted
|
|
2174
|
+
});
|
|
2175
|
+
return segs;
|
|
2176
|
+
});
|
|
2177
|
+
const statsW = createMemo(() => {
|
|
2178
|
+
let w = 0;
|
|
2179
|
+
for (const sg of statsSegs()) w += visualWidth(sg.text);
|
|
2180
|
+
return w;
|
|
2181
|
+
});
|
|
2182
|
+
const sessionCost = createMemo(() => {
|
|
2183
|
+
try {
|
|
2184
|
+
return num(props.api.state.session.get(sid)?.cost);
|
|
2185
|
+
} catch {
|
|
2186
|
+
return 0;
|
|
2187
|
+
}
|
|
2188
|
+
});
|
|
2189
|
+
const usageText = createMemo(() => {
|
|
2190
|
+
const id = sid;
|
|
2191
|
+
if (!id) return "";
|
|
2192
|
+
const msgs = props.api.state.session.messages(id);
|
|
2193
|
+
let last;
|
|
2194
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
2195
|
+
const m = msgs[i];
|
|
2196
|
+
if (m.role !== "assistant") continue;
|
|
2197
|
+
const tk2 = m.tokens;
|
|
2198
|
+
if (tk2 && num(tk2.output) > 0) {
|
|
2199
|
+
last = m;
|
|
2200
|
+
break;
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
if (!last) return "";
|
|
2204
|
+
const tk = last.tokens;
|
|
2205
|
+
if (!tk) return "";
|
|
2206
|
+
const tokens = num(tk.input) + num(tk.output) + num(tk.reasoning) + num(tk.cache?.read) + num(tk.cache?.write);
|
|
2207
|
+
if (tokens <= 0) return "";
|
|
2208
|
+
let pct = "";
|
|
2209
|
+
try {
|
|
2210
|
+
const p = props.api.state.provider.find((x) => x.id === last.providerID);
|
|
2211
|
+
const limit = p?.models?.[last.modelID]?.limit?.context;
|
|
2212
|
+
if (typeof limit === "number" && limit > 0) pct = ` (${Math.round(tokens / limit * 100)}%)`;
|
|
2213
|
+
} catch {
|
|
2214
|
+
}
|
|
2215
|
+
const context = fmtCompact(tokens) + pct;
|
|
2216
|
+
const cost = sessionCost();
|
|
2217
|
+
return cost > 0 ? context + " \xB7 " + fmtCost(cost) : context;
|
|
2218
|
+
});
|
|
2219
|
+
const rightText = createMemo(() => {
|
|
2220
|
+
const cmds = keyShortcut(props.api, "command.palette.show", "ctrl+p") + " commands";
|
|
2221
|
+
const u = usageText();
|
|
2222
|
+
if (u) return u + " " + cmds;
|
|
2223
|
+
return keyShortcut(props.api, "agent.cycle", "") + " agents " + cmds;
|
|
2224
|
+
});
|
|
2225
|
+
const rightW = createMemo(() => visualWidth(rightText()));
|
|
2226
|
+
const inputW = createMemo(() => termW() - (props.signals.sidebarVisible() ? 42 : 0) - 4);
|
|
2227
|
+
const dirDisplay = createMemo(() => {
|
|
2228
|
+
const avail = inputW() - statsW() - rightW() - PATH_CHROME;
|
|
2229
|
+
if (avail < HIDE_PATH_BELOW) return "";
|
|
2230
|
+
return truncateVisual(directory(), avail);
|
|
2231
|
+
});
|
|
2232
|
+
const dirFallback = createMemo(() => {
|
|
2233
|
+
const avail = inputW() - rightW() - PATH_CHROME;
|
|
2234
|
+
if (avail < HIDE_PATH_BELOW) return "";
|
|
2235
|
+
return truncateVisual(directory(), avail);
|
|
2236
|
+
});
|
|
2106
2237
|
onMount(() => {
|
|
2107
2238
|
try {
|
|
2108
2239
|
const v = props.api.kv.get(`${KV_PREFIX}.section.bottom`, true);
|
|
@@ -2116,14 +2247,14 @@ function BottomStatusBar(props) {
|
|
|
2116
2247
|
},
|
|
2117
2248
|
get fallback() {
|
|
2118
2249
|
return (() => {
|
|
2119
|
-
var _el$
|
|
2120
|
-
_$insert(_el$
|
|
2121
|
-
_$effect((_$p) => _$setProp(_el$
|
|
2122
|
-
return _el$
|
|
2250
|
+
var _el$101 = _$createElement("text");
|
|
2251
|
+
_$insert(_el$101, dirFallback);
|
|
2252
|
+
_$effect((_$p) => _$setProp(_el$101, "fg", pal().muted, _$p));
|
|
2253
|
+
return _el$101;
|
|
2123
2254
|
})();
|
|
2124
2255
|
},
|
|
2125
2256
|
get children() {
|
|
2126
|
-
var _el$97 = _$createElement("box"), _el$98 = _$createElement("text"), _el$99 = _$createElement("box"), _el$100 = _$createElement("text")
|
|
2257
|
+
var _el$97 = _$createElement("box"), _el$98 = _$createElement("text"), _el$99 = _$createElement("box"), _el$100 = _$createElement("text");
|
|
2127
2258
|
_$insertNode(_el$97, _el$98);
|
|
2128
2259
|
_$insertNode(_el$97, _el$99);
|
|
2129
2260
|
_$setProp(_el$97, "marginLeft", 1);
|
|
@@ -2131,86 +2262,23 @@ function BottomStatusBar(props) {
|
|
|
2131
2262
|
_$setProp(_el$97, "flexShrink", 0);
|
|
2132
2263
|
_$setProp(_el$97, "flexDirection", "row");
|
|
2133
2264
|
_$setProp(_el$97, "justifyContent", "space-between");
|
|
2134
|
-
_$insert(_el$98,
|
|
2265
|
+
_$insert(_el$98, dirDisplay);
|
|
2135
2266
|
_$insertNode(_el$99, _el$100);
|
|
2136
|
-
_$insertNode(_el$99, _el$109);
|
|
2137
2267
|
_$setProp(_el$99, "flexDirection", "row");
|
|
2138
|
-
_$
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
_$insertNode(_el$100, _el$106);
|
|
2142
|
-
_$insertNode(_el$101, _el$102);
|
|
2143
|
-
_$insert(_el$101, () => t("barHit"), _el$102);
|
|
2144
|
-
_$insert(_el$103, (() => {
|
|
2145
|
-
var _c$ = _$memo(() => (stats()?.hitRate ?? -1) >= 0);
|
|
2146
|
-
return () => _c$() ? (Math.floor(stats().hitRate * 10) / 10).toFixed(1) + "%" : "--";
|
|
2147
|
-
})());
|
|
2148
|
-
_$insert(_el$100, _$createComponent(Show, {
|
|
2149
|
-
get when() {
|
|
2150
|
-
return trend() !== null;
|
|
2268
|
+
_$insert(_el$100, _$createComponent(For, {
|
|
2269
|
+
get each() {
|
|
2270
|
+
return statsSegs();
|
|
2151
2271
|
},
|
|
2152
|
-
|
|
2153
|
-
var _el$
|
|
2154
|
-
_$insert(_el$
|
|
2155
|
-
_$effect((_$p) => _$setProp(_el$
|
|
2156
|
-
fg:
|
|
2272
|
+
children: (sg) => (() => {
|
|
2273
|
+
var _el$102 = _$createElement("span");
|
|
2274
|
+
_$insert(_el$102, () => sg.text);
|
|
2275
|
+
_$effect((_$p) => _$setProp(_el$102, "style", {
|
|
2276
|
+
fg: sg.color
|
|
2157
2277
|
}, _$p));
|
|
2158
|
-
return _el$
|
|
2159
|
-
}
|
|
2160
|
-
})
|
|
2161
|
-
_$
|
|
2162
|
-
_$insert(_el$106, (() => {
|
|
2163
|
-
var _c$2 = _$memo(() => !!stats());
|
|
2164
|
-
return () => _c$2() ? fmtCompact(stats().input + stats().read + stats().write) : "--";
|
|
2165
|
-
})());
|
|
2166
|
-
_$insert(_el$100, _$createComponent(Show, {
|
|
2167
|
-
get when() {
|
|
2168
|
-
return !props.signals.balanceUnsupported();
|
|
2169
|
-
},
|
|
2170
|
-
get children() {
|
|
2171
|
-
return [(() => {
|
|
2172
|
-
var _el$107 = _$createElement("span");
|
|
2173
|
-
_$insert(_el$107, () => " \xB7 " + t("barBal") + " ");
|
|
2174
|
-
_$effect((_$p) => _$setProp(_el$107, "style", {
|
|
2175
|
-
fg: pal().muted
|
|
2176
|
-
}, _$p));
|
|
2177
|
-
return _el$107;
|
|
2178
|
-
})(), (() => {
|
|
2179
|
-
var _el$108 = _$createElement("span");
|
|
2180
|
-
_$insert(_el$108, balanceText);
|
|
2181
|
-
_$effect((_$p) => _$setProp(_el$108, "style", {
|
|
2182
|
-
fg: pal().text
|
|
2183
|
-
}, _$p));
|
|
2184
|
-
return _el$108;
|
|
2185
|
-
})()];
|
|
2186
|
-
}
|
|
2187
|
-
}), null);
|
|
2188
|
-
_$insertNode(_el$109, _$createTextNode(` \xB7 `));
|
|
2189
|
-
_$effect((_p$) => {
|
|
2190
|
-
var _v$31 = pal().muted, _v$32 = {
|
|
2191
|
-
fg: pal().muted
|
|
2192
|
-
}, _v$33 = {
|
|
2193
|
-
fg: hitColor()
|
|
2194
|
-
}, _v$34 = {
|
|
2195
|
-
fg: pal().muted
|
|
2196
|
-
}, _v$35 = {
|
|
2197
|
-
fg: pal().text
|
|
2198
|
-
}, _v$36 = pal().muted;
|
|
2199
|
-
_v$31 !== _p$.e && (_p$.e = _$setProp(_el$98, "fg", _v$31, _p$.e));
|
|
2200
|
-
_v$32 !== _p$.t && (_p$.t = _$setProp(_el$101, "style", _v$32, _p$.t));
|
|
2201
|
-
_v$33 !== _p$.a && (_p$.a = _$setProp(_el$103, "style", _v$33, _p$.a));
|
|
2202
|
-
_v$34 !== _p$.o && (_p$.o = _$setProp(_el$105, "style", _v$34, _p$.o));
|
|
2203
|
-
_v$35 !== _p$.i && (_p$.i = _$setProp(_el$106, "style", _v$35, _p$.i));
|
|
2204
|
-
_v$36 !== _p$.n && (_p$.n = _$setProp(_el$109, "fg", _v$36, _p$.n));
|
|
2205
|
-
return _p$;
|
|
2206
|
-
}, {
|
|
2207
|
-
e: void 0,
|
|
2208
|
-
t: void 0,
|
|
2209
|
-
a: void 0,
|
|
2210
|
-
o: void 0,
|
|
2211
|
-
i: void 0,
|
|
2212
|
-
n: void 0
|
|
2213
|
-
});
|
|
2278
|
+
return _el$102;
|
|
2279
|
+
})()
|
|
2280
|
+
}));
|
|
2281
|
+
_$effect((_$p) => _$setProp(_el$98, "fg", pal().muted, _$p));
|
|
2214
2282
|
return _el$97;
|
|
2215
2283
|
}
|
|
2216
2284
|
});
|
|
@@ -2259,6 +2327,7 @@ var tui = async (api) => {
|
|
|
2259
2327
|
const [borderVisible, setBorderVisible] = createSignal(true);
|
|
2260
2328
|
const [langCode, setLangCode] = createSignal(INIT_LANG);
|
|
2261
2329
|
const [overrideSessionId, setOverrideSessionId] = createSignal(void 0);
|
|
2330
|
+
const [sidebarVisible, setSidebarVisible] = createSignal(false);
|
|
2262
2331
|
const [balanceState, setBalanceState] = createSignal({
|
|
2263
2332
|
status: "idle",
|
|
2264
2333
|
data: null,
|
|
@@ -2298,7 +2367,9 @@ var tui = async (api) => {
|
|
|
2298
2367
|
borderVisible,
|
|
2299
2368
|
setBorderVisible,
|
|
2300
2369
|
overrideSessionId,
|
|
2301
|
-
setOverrideSessionId
|
|
2370
|
+
setOverrideSessionId,
|
|
2371
|
+
sidebarVisible,
|
|
2372
|
+
setSidebarVisible
|
|
2302
2373
|
};
|
|
2303
2374
|
api.slots.register(createSidebarSlot(api, signals));
|
|
2304
2375
|
api.slots.register({
|
|
@@ -2441,11 +2512,11 @@ var tui = async (api) => {
|
|
|
2441
2512
|
return provider.name;
|
|
2442
2513
|
},
|
|
2443
2514
|
description: () => (() => {
|
|
2444
|
-
var _el$
|
|
2445
|
-
_$insert(_el$
|
|
2515
|
+
var _el$103 = _$createElement("text");
|
|
2516
|
+
_$insert(_el$103, () => t("balKeyPrompt", {
|
|
2446
2517
|
p: provider.name
|
|
2447
2518
|
}));
|
|
2448
|
-
return _el$
|
|
2519
|
+
return _el$103;
|
|
2449
2520
|
})(),
|
|
2450
2521
|
get placeholder() {
|
|
2451
2522
|
return provider.keyPlaceholder ?? "sk-...";
|
|
@@ -2525,9 +2596,9 @@ var tui = async (api) => {
|
|
|
2525
2596
|
dialog?.replace(() => _$createComponent(api.ui.DialogPrompt, {
|
|
2526
2597
|
title: "Exchange Rate",
|
|
2527
2598
|
description: () => (() => {
|
|
2528
|
-
var _el$
|
|
2529
|
-
_$insertNode(_el$
|
|
2530
|
-
return _el$
|
|
2599
|
+
var _el$104 = _$createElement("text");
|
|
2600
|
+
_$insertNode(_el$104, _$createTextNode(`Enter the exchange rate from USD to your currency (e.g. 7.2 for CNY)`));
|
|
2601
|
+
return _el$104;
|
|
2531
2602
|
})(),
|
|
2532
2603
|
placeholder: "1.0",
|
|
2533
2604
|
get value() {
|
|
@@ -2939,9 +3010,9 @@ var tui = async (api) => {
|
|
|
2939
3010
|
return _$memo(() => !!signals.overrideSessionId())() ? t("subSwitchTitle") : t("subViewTitle");
|
|
2940
3011
|
},
|
|
2941
3012
|
description: () => (() => {
|
|
2942
|
-
var _el$
|
|
2943
|
-
_$insert(_el$
|
|
2944
|
-
return _el$
|
|
3013
|
+
var _el$106 = _$createElement("text");
|
|
3014
|
+
_$insert(_el$106, () => t("subNoFound"));
|
|
3015
|
+
return _el$106;
|
|
2945
3016
|
})(),
|
|
2946
3017
|
placeholder: "ses_...",
|
|
2947
3018
|
get value() {
|
package/package.json
CHANGED
package/src/_version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION="1.6.
|
|
2
|
+
export const PLUGIN_VERSION="1.6.2";
|
package/src/index.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
TuiThemeCurrent,
|
|
11
11
|
TuiDialogStack,
|
|
12
12
|
TuiPromptRef,
|
|
13
|
+
SequenceBindingLike,
|
|
13
14
|
} from "@opencode-ai/plugin/tui"
|
|
14
15
|
import type { UserMessage, AssistantMessage, Message } from "@opencode-ai/sdk"
|
|
15
16
|
import type {
|
|
@@ -19,7 +20,7 @@ import type {
|
|
|
19
20
|
FilePart,
|
|
20
21
|
ReasoningPart,
|
|
21
22
|
} from "@opencode-ai/sdk/v2"
|
|
22
|
-
import { createMemo, createSignal, createEffect, onMount, onCleanup, Show, untrack } from "solid-js"
|
|
23
|
+
import { createMemo, createSignal, createEffect, onMount, onCleanup, Show, For, untrack } from "solid-js"
|
|
23
24
|
import { PLUGIN_VERSION } from "./_version"
|
|
24
25
|
import { balanceProviders, getBalanceProvider, maskKey, matchBalanceProvider, type BalanceEntry, type BalanceProvider } from "./balance-providers"
|
|
25
26
|
import { LANG_META, createT, detectLang, type LangCode } from "./i18n"
|
|
@@ -404,6 +405,9 @@ interface PanelSignals {
|
|
|
404
405
|
/** When set, the panel renders stats for this session instead of the main one. */
|
|
405
406
|
overrideSessionId: () => string | undefined
|
|
406
407
|
setOverrideSessionId: (v: string | undefined) => void
|
|
408
|
+
/** True while our sidebar panel is mounted — host sidebar is visible (occupies 42 cols). */
|
|
409
|
+
sidebarVisible: () => boolean
|
|
410
|
+
setSidebarVisible: (v: boolean) => void
|
|
407
411
|
}
|
|
408
412
|
|
|
409
413
|
const CURRENCIES: Record<string, string> = {
|
|
@@ -441,6 +445,12 @@ function TokenCachePanel(props: {
|
|
|
441
445
|
const [skillsOpen, setSkillsOpen] = createSignal(true)
|
|
442
446
|
let boxEl: any
|
|
443
447
|
|
|
448
|
+
// 侧边栏可见性通知:本面板挂载 ⇒ 宿主侧边栏可见(固定占用 42 列输入框宽度)
|
|
449
|
+
createEffect(() => {
|
|
450
|
+
props.signals.setSidebarVisible(true)
|
|
451
|
+
onCleanup(() => props.signals.setSidebarVisible(false))
|
|
452
|
+
})
|
|
453
|
+
|
|
444
454
|
// ── shared signals (de-structured so internal code is unchanged) ──
|
|
445
455
|
const {
|
|
446
456
|
currencySymbol, setCurrencySymbol,
|
|
@@ -1178,6 +1188,33 @@ function TokenCachePanel(props: {
|
|
|
1178
1188
|
// Plugin entry
|
|
1179
1189
|
// ---------------------------------------------------------------------------
|
|
1180
1190
|
|
|
1191
|
+
/**
|
|
1192
|
+
* 路径截断的固定开销(列数):仅宿主布局常量,不含任何内容宽度——
|
|
1193
|
+
* 统计宽度、宿主 usage、commands 快捷键均为运行时动态计算。
|
|
1194
|
+
* - marginLeft=1 + space-between 余量 ≈ 2 列
|
|
1195
|
+
*/
|
|
1196
|
+
const PATH_CHROME = 2
|
|
1197
|
+
/**
|
|
1198
|
+
* 路径可用宽度低于此列数时整体隐藏(极窄终端下路径信息价值太低,
|
|
1199
|
+
* 残留的 "E:\Work…" 反而挤压右侧统计与 commands,直接让位更干净)。
|
|
1200
|
+
*/
|
|
1201
|
+
const HIDE_PATH_BELOW = 14
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* 从宿主 keymap 动态读取命令的快捷键显示文本(与宿主 Prompt 同源),
|
|
1205
|
+
* 取不到时回退到传入的默认文本。
|
|
1206
|
+
*/
|
|
1207
|
+
function keyShortcut(api: TuiPluginApi, command: string, fallback: string): string {
|
|
1208
|
+
try {
|
|
1209
|
+
const binds = api.tuiConfig.keybinds.get(command)
|
|
1210
|
+
const seq = binds?.map((b) => ({ key: b.key }))
|
|
1211
|
+
const s = api.keys.formatBindings(seq as unknown as SequenceBindingLike[])
|
|
1212
|
+
return s || fallback
|
|
1213
|
+
} catch {
|
|
1214
|
+
return fallback
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1181
1218
|
/**
|
|
1182
1219
|
* 输入框 hint 行(session_prompt slot 的 hint):单行显示 路径 · 命中率 · 余额 · Tokens。
|
|
1183
1220
|
* 通过 ui.Prompt 的 hint prop 注入——宿主右侧的 token/commands 提示自动保留,
|
|
@@ -1301,6 +1338,116 @@ function BottomStatusBar(props: { api: TuiPluginApi; signals: PanelSignals; sess
|
|
|
1301
1338
|
try { return props.api.state.path.directory } catch { return "" }
|
|
1302
1339
|
})
|
|
1303
1340
|
|
|
1341
|
+
// 终端宽度信号:初始读取渲染器,窗口 resize 时更新(宿主不约束 hint 行宽度,
|
|
1342
|
+
// 路径截断必须基于终端宽度手动计算)。
|
|
1343
|
+
// CliRenderer 继承的 EventEmitter 因项目未装 @types/node 类型不可见,
|
|
1344
|
+
// 用最小接口声明补齐 resize 事件的 on/off。
|
|
1345
|
+
interface ResizeEmitter {
|
|
1346
|
+
on(event: "resize", cb: () => void): unknown
|
|
1347
|
+
off(event: "resize", cb: () => void): unknown
|
|
1348
|
+
}
|
|
1349
|
+
const [termW, setTermW] = createSignal(props.api.renderer.terminalWidth)
|
|
1350
|
+
// resize 事件(主通道);事件接口若在插件环境不可用则跳过,由轮询兜底
|
|
1351
|
+
createEffect(() => {
|
|
1352
|
+
const r = props.api.renderer as unknown as ResizeEmitter
|
|
1353
|
+
if (typeof r.on !== "function" || typeof r.off !== "function") return
|
|
1354
|
+
const onResize = () => setTermW(props.api.renderer.terminalWidth)
|
|
1355
|
+
r.on("resize", onResize)
|
|
1356
|
+
onCleanup(() => r.off("resize", onResize))
|
|
1357
|
+
})
|
|
1358
|
+
// 轮询兜底:事件通道若在插件环境不可用,定期同步终端宽度(值不变时不触发更新)
|
|
1359
|
+
createEffect(() => {
|
|
1360
|
+
const timer = setInterval(() => setTermW(props.api.renderer.terminalWidth), 500)
|
|
1361
|
+
onCleanup(() => clearInterval(timer))
|
|
1362
|
+
})
|
|
1363
|
+
|
|
1364
|
+
// 统计部分分段(单一数据源):量宽拼接 text,渲染逐段着色,避免双源漂移
|
|
1365
|
+
const statsSegs = createMemo<{ text: string; color: string | undefined }[]>(() => {
|
|
1366
|
+
const s = stats()
|
|
1367
|
+
const hr = s && s.hitRate >= 0 ? (Math.floor(s.hitRate * 10) / 10).toFixed(1) + "%" : "--"
|
|
1368
|
+
const segs: { text: string; color: string | undefined }[] = [
|
|
1369
|
+
{ text: t("barHit") + " ", color: pal().muted },
|
|
1370
|
+
{ text: hr, color: hitColor() },
|
|
1371
|
+
]
|
|
1372
|
+
const tr = trend()
|
|
1373
|
+
if (tr !== null) {
|
|
1374
|
+
segs.push({ text: " " + (tr > 0 ? "\u2191" : "\u2193") + Math.abs(tr).toFixed(1) + "%", color: tr > 0 ? pal().success : pal().error })
|
|
1375
|
+
}
|
|
1376
|
+
segs.push({ text: " \u00b7 " + t("barTok") + " ", color: pal().muted })
|
|
1377
|
+
segs.push({ text: s ? fmtCompact(s.input + s.read + s.write) : "--", color: pal().text })
|
|
1378
|
+
if (!props.signals.balanceUnsupported()) {
|
|
1379
|
+
segs.push({ text: " \u00b7 " + t("barBal") + " ", color: pal().muted })
|
|
1380
|
+
segs.push({ text: balanceText(), color: pal().text })
|
|
1381
|
+
}
|
|
1382
|
+
segs.push({ text: " \u00b7 ", color: pal().muted })
|
|
1383
|
+
return segs
|
|
1384
|
+
})
|
|
1385
|
+
const statsW = createMemo(() => {
|
|
1386
|
+
let w = 0
|
|
1387
|
+
for (const sg of statsSegs()) w += visualWidth(sg.text)
|
|
1388
|
+
return w
|
|
1389
|
+
})
|
|
1390
|
+
|
|
1391
|
+
// 宿主右侧 usage 文本复刻(1.18.16 Prompt 口径):
|
|
1392
|
+
// 最后一条 output>0 的 assistant 消息 → tokens 合计格式化 + 模型 context limit 百分比 + session 累计费用
|
|
1393
|
+
const sessionCost = createMemo(() => {
|
|
1394
|
+
try { return num(props.api.state.session.get(sid)?.cost) } catch { return 0 }
|
|
1395
|
+
})
|
|
1396
|
+
const usageText = createMemo(() => {
|
|
1397
|
+
const id = sid
|
|
1398
|
+
if (!id) return ""
|
|
1399
|
+
const msgs = props.api.state.session.messages(id) as Message[]
|
|
1400
|
+
let last: AssistantMessage | undefined
|
|
1401
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
1402
|
+
const m = msgs[i]
|
|
1403
|
+
if (m.role !== "assistant") continue
|
|
1404
|
+
const tk = (m as AssistantMessage).tokens
|
|
1405
|
+
if (tk && num(tk.output) > 0) { last = m as AssistantMessage; break }
|
|
1406
|
+
}
|
|
1407
|
+
if (!last) return ""
|
|
1408
|
+
const tk = last.tokens
|
|
1409
|
+
if (!tk) return ""
|
|
1410
|
+
const tokens = num(tk.input) + num(tk.output) + num(tk.reasoning) + num(tk.cache?.read) + num(tk.cache?.write)
|
|
1411
|
+
if (tokens <= 0) return ""
|
|
1412
|
+
let pct = ""
|
|
1413
|
+
try {
|
|
1414
|
+
const p = props.api.state.provider.find((x) => x.id === last.providerID)
|
|
1415
|
+
const limit = p?.models?.[last.modelID]?.limit?.context
|
|
1416
|
+
if (typeof limit === "number" && limit > 0) pct = ` (${Math.round((tokens / limit) * 100)}%)`
|
|
1417
|
+
} catch {}
|
|
1418
|
+
const context = fmtCompact(tokens) + pct
|
|
1419
|
+
const cost = sessionCost()
|
|
1420
|
+
return cost > 0 ? context + " \u00b7 " + fmtCost(cost) : context
|
|
1421
|
+
})
|
|
1422
|
+
|
|
1423
|
+
// 宿主 1513 行右侧文本:usage(有数据)或 "快捷键 agents"(无数据)+ commands,
|
|
1424
|
+
// 快捷键从宿主 keymap 动态读取
|
|
1425
|
+
const rightText = createMemo(() => {
|
|
1426
|
+
const cmds = keyShortcut(props.api, "command.palette.show", "ctrl+p") + " commands"
|
|
1427
|
+
const u = usageText()
|
|
1428
|
+
if (u) return u + " " + cmds
|
|
1429
|
+
return keyShortcut(props.api, "agent.cycle", "") + " agents " + cmds
|
|
1430
|
+
})
|
|
1431
|
+
const rightW = createMemo(() => visualWidth(rightText()))
|
|
1432
|
+
|
|
1433
|
+
// 输入框实际宽度 = 终端宽度 - 侧边栏(可见时 42) - 边距 4
|
|
1434
|
+
// (与宿主 session 布局 contentWidth 口径一致;侧边栏可见性由本面板挂载状态驱动)
|
|
1435
|
+
const inputW = createMemo(() => termW() - (props.signals.sidebarVisible() ? 42 : 0) - 4)
|
|
1436
|
+
|
|
1437
|
+
// 路径可用宽度 = 输入框宽度 - 统计宽度(精确) - 宿主右侧宽度(动态) - 布局开销;
|
|
1438
|
+
// 低于 HIDE_PATH_BELOW 时整体隐藏路径(宽度归零),把空间让给统计与 commands
|
|
1439
|
+
const dirDisplay = createMemo(() => {
|
|
1440
|
+
const avail = inputW() - statsW() - rightW() - PATH_CHROME
|
|
1441
|
+
if (avail < HIDE_PATH_BELOW) return ""
|
|
1442
|
+
return truncateVisual(directory(), avail)
|
|
1443
|
+
})
|
|
1444
|
+
// 状态栏关闭(仅路径)时同样在极窄条件下隐藏路径
|
|
1445
|
+
const dirFallback = createMemo(() => {
|
|
1446
|
+
const avail = inputW() - rightW() - PATH_CHROME
|
|
1447
|
+
if (avail < HIDE_PATH_BELOW) return ""
|
|
1448
|
+
return truncateVisual(directory(), avail)
|
|
1449
|
+
})
|
|
1450
|
+
|
|
1304
1451
|
// 恢复显隐偏好(默认显示);关闭时回退为仅显示路径,与宿主默认 hint 行一致
|
|
1305
1452
|
onMount(() => {
|
|
1306
1453
|
try {
|
|
@@ -1310,28 +1457,18 @@ function BottomStatusBar(props: { api: TuiPluginApi; signals: PanelSignals; sess
|
|
|
1310
1457
|
})
|
|
1311
1458
|
|
|
1312
1459
|
return (
|
|
1313
|
-
<Show
|
|
1460
|
+
<Show
|
|
1461
|
+
when={props.signals.sectionBottom()}
|
|
1462
|
+
fallback={<text fg={pal().muted}>{dirFallback()}</text>}
|
|
1463
|
+
>
|
|
1314
1464
|
<box marginLeft={1} flexGrow={1} flexShrink={0} flexDirection="row" justifyContent="space-between">
|
|
1315
|
-
<text fg={pal().muted}>{
|
|
1316
|
-
|
|
1465
|
+
<text fg={pal().muted}>{dirDisplay()}</text>
|
|
1466
|
+
<box flexDirection="row">
|
|
1317
1467
|
<text>
|
|
1318
|
-
<
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
<span style={{ fg: trend()! > 0 ? pal().success : pal().error }}>
|
|
1322
|
-
{" " + (trend()! > 0 ? "\u2191" : "\u2193") + Math.abs(trend()!).toFixed(1) + "%"}
|
|
1323
|
-
</span>
|
|
1324
|
-
</Show>
|
|
1325
|
-
<span style={{ fg: pal().muted }}>{" \u00b7 " + t("barTok") + " "}</span>
|
|
1326
|
-
<span style={{ fg: pal().text }}>
|
|
1327
|
-
{stats() ? fmtCompact(stats()!.input + stats()!.read + stats()!.write) : "--"}
|
|
1328
|
-
</span>
|
|
1329
|
-
<Show when={!props.signals.balanceUnsupported()}>
|
|
1330
|
-
<span style={{ fg: pal().muted }}>{" \u00b7 " + t("barBal") + " "}</span>
|
|
1331
|
-
<span style={{ fg: pal().text }}>{balanceText()}</span>
|
|
1332
|
-
</Show>
|
|
1468
|
+
<For each={statsSegs()}>
|
|
1469
|
+
{(sg) => <span style={{ fg: sg.color }}>{sg.text}</span>}
|
|
1470
|
+
</For>
|
|
1333
1471
|
</text>
|
|
1334
|
-
<text fg={pal().muted}>{" \u00b7 "}</text>
|
|
1335
1472
|
</box>
|
|
1336
1473
|
</box>
|
|
1337
1474
|
</Show>
|
|
@@ -1383,6 +1520,8 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1383
1520
|
const [borderVisible, setBorderVisible] = createSignal(true)
|
|
1384
1521
|
const [langCode, setLangCode] = createSignal<LangCode>(INIT_LANG)
|
|
1385
1522
|
const [overrideSessionId, setOverrideSessionId] = createSignal<string | undefined>(undefined)
|
|
1523
|
+
// 侧边栏可见性(由 TokenCachePanel 挂载状态驱动):可见时宿主输入框宽度 = 终端宽 - 42 - 4
|
|
1524
|
+
const [sidebarVisible, setSidebarVisible] = createSignal(false)
|
|
1386
1525
|
|
|
1387
1526
|
// ── 余额查询状态(共享):侧边栏与底部栏读同一份数据,
|
|
1388
1527
|
// 避免重复请求导致两处余额不一致 ──
|
|
@@ -1410,6 +1549,7 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1410
1549
|
balanceCurrency, setBalanceCurrency,
|
|
1411
1550
|
borderVisible, setBorderVisible,
|
|
1412
1551
|
overrideSessionId, setOverrideSessionId,
|
|
1552
|
+
sidebarVisible, setSidebarVisible,
|
|
1413
1553
|
}
|
|
1414
1554
|
|
|
1415
1555
|
api.slots.register(createSidebarSlot(api, signals))
|