compressed-context 0.1.0 → 0.2.0

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.
Files changed (57) hide show
  1. package/README.md +59 -51
  2. package/package.json +64 -64
  3. package/src/compression/active-summary-cache.ts +50 -50
  4. package/src/compression/adopt.ts +57 -57
  5. package/src/compression/auto.ts +386 -372
  6. package/src/compression/babel.ts +85 -85
  7. package/src/compression/block.ts +25 -25
  8. package/src/compression/cache-history.ts +75 -75
  9. package/src/compression/compaction.ts +189 -189
  10. package/src/compression/decompress-tool.ts +74 -74
  11. package/src/compression/decompress.ts +292 -292
  12. package/src/compression/deleted-sessions.ts +24 -24
  13. package/src/compression/distill.ts +220 -220
  14. package/src/compression/events.ts +275 -275
  15. package/src/compression/markdown.ts +53 -53
  16. package/src/compression/normalize.ts +114 -114
  17. package/src/compression/render.ts +70 -70
  18. package/src/compression/schema.ts +131 -84
  19. package/src/compression/select.ts +123 -123
  20. package/src/compression/session-message.ts +112 -112
  21. package/src/compression/settings-rpc.ts +123 -111
  22. package/src/compression/settings.ts +175 -175
  23. package/src/compression/shape.ts +75 -0
  24. package/src/compression/state.ts +248 -248
  25. package/src/compression/summarize.ts +534 -595
  26. package/src/compression/summary.ts +73 -73
  27. package/src/compression/tool.ts +184 -142
  28. package/src/compression/transcript-store.ts +258 -258
  29. package/src/compression/transcript.ts +63 -63
  30. package/src/compression/validate.ts +81 -81
  31. package/src/i18n/en.ts +93 -93
  32. package/src/i18n/index.ts +28 -28
  33. package/src/i18n/types.ts +89 -89
  34. package/src/i18n/zh.ts +93 -93
  35. package/src/index.ts +116 -108
  36. package/src/shared/blocks.ts +17 -17
  37. package/src/shared/cache.ts +199 -199
  38. package/src/shared/format.ts +23 -23
  39. package/src/shared/media.ts +184 -184
  40. package/src/shared/parse.ts +25 -25
  41. package/src/shared/parts.ts +72 -72
  42. package/src/shared/rpc.ts +145 -145
  43. package/src/shared/scan.ts +32 -32
  44. package/src/shared/settings.ts +466 -471
  45. package/src/shared/stats.ts +142 -142
  46. package/src/shared/tokens.ts +259 -259
  47. package/src/tui/chart/cache-chart.ts +237 -237
  48. package/src/tui/chart/color.ts +58 -58
  49. package/src/tui/index.tsx +109 -111
  50. package/src/tui/panel/cache.tsx +203 -203
  51. package/src/tui/panel/data.ts +105 -105
  52. package/src/tui/panel/index.tsx +176 -176
  53. package/src/tui/panel/summaries.tsx +203 -192
  54. package/src/tui/settings/dialogs.tsx +215 -215
  55. package/src/tui/settings/options.ts +238 -238
  56. package/src/tui/settings.tsx +418 -418
  57. package/src/tui/types.ts +4 -4
package/src/tui/index.tsx CHANGED
@@ -1,111 +1,109 @@
1
- import { Plugin } from "@opencode/plugin/tui"
2
-
3
- import { initialSettings, normalizeSettings, type Settings } from "../shared/settings.ts"
4
- import { Panel } from "./panel/index.tsx"
5
- import { SettingsCommand } from "./settings.tsx"
6
- import type { UpdateSettings } from "./types.ts"
7
-
8
- /**
9
- * 输入:当前设置 + 写入函数。
10
- * 输出:无。
11
- * 预期:旧版本存下来的设置可能缺字段(例如没有 contextLimit),补齐并写回。
12
- */
13
- function migrate(settings: Settings, update: UpdateSettings): void {
14
- const normalized = normalizeSettings({
15
- enabled: settings.enabled,
16
- expanded: settings.expanded,
17
- locale: settings.locale,
18
- contextLimit: settings.contextLimit,
19
- triggerPercent: settings.triggerPercent,
20
- summaryBlockPercent: settings.summaryBlockPercent,
21
- summaryTotalPercent: settings.summaryTotalPercent,
22
- summaryLanguage: settings.summaryLanguage,
23
- summaryFormat: settings.summaryFormat,
24
- autoCompress: settings.autoCompress,
25
- enableCompressTool: settings.enableCompressTool,
26
- enableDecompressTool: settings.enableDecompressTool,
27
- showTimeline: settings.showTimeline,
28
- showBlockTokens: settings.showBlockTokens,
29
- cacheExpanded: settings.cacheExpanded,
30
- showCacheChart: settings.showCacheChart,
31
- cacheChartExpanded: settings.cacheChartExpanded,
32
- showCacheLog: settings.showCacheLog,
33
- cacheLogExpanded: settings.cacheLogExpanded,
34
- showSummaries: settings.showSummaries,
35
- summariesExpanded: settings.summariesExpanded,
36
- ...(settings.expandedBlockId === undefined ? {} : { expandedBlockId: settings.expandedBlockId }),
37
- decompressInlineMax: settings.decompressInlineMax,
38
- decompressTempCap: settings.decompressTempCap,
39
- decompressShowTokens: settings.decompressShowTokens,
40
- })
41
- if (
42
- settings.enabled === normalized.enabled &&
43
- settings.expanded === normalized.expanded &&
44
- settings.locale === normalized.locale &&
45
- settings.contextLimit === normalized.contextLimit &&
46
- settings.triggerPercent === normalized.triggerPercent &&
47
- settings.summaryBlockPercent === normalized.summaryBlockPercent &&
48
- settings.summaryTotalPercent === normalized.summaryTotalPercent &&
49
- settings.summaryLanguage === normalized.summaryLanguage &&
50
- settings.summaryFormat === normalized.summaryFormat &&
51
- settings.autoCompress === normalized.autoCompress &&
52
- settings.enableCompressTool === normalized.enableCompressTool &&
53
- settings.enableDecompressTool === normalized.enableDecompressTool &&
54
- settings.showTimeline === normalized.showTimeline &&
55
- settings.showBlockTokens === normalized.showBlockTokens &&
56
- settings.cacheExpanded === normalized.cacheExpanded &&
57
- settings.showCacheChart === normalized.showCacheChart &&
58
- settings.cacheChartExpanded === normalized.cacheChartExpanded &&
59
- settings.showCacheLog === normalized.showCacheLog &&
60
- settings.cacheLogExpanded === normalized.cacheLogExpanded &&
61
- settings.showSummaries === normalized.showSummaries &&
62
- settings.summariesExpanded === normalized.summariesExpanded &&
63
- settings.expandedBlockId === normalized.expandedBlockId &&
64
- settings.decompressInlineMax === normalized.decompressInlineMax &&
65
- settings.decompressTempCap === normalized.decompressTempCap &&
66
- settings.decompressShowTokens === normalized.decompressShowTokens
67
- ) {
68
- return
69
- }
70
- void update((draft) => {
71
- Object.assign(draft, normalized)
72
- })
73
- }
74
-
75
- export default Plugin.define({
76
- id: "compressed-context.tui",
77
-
78
- /**
79
- * 输入:TUI 插件上下文(含插件 options)。
80
- * 输出:侧栏面板 + /compressed-context 设置命令。
81
- * 预期:设置存在 TUI storage 里;options 只作为首次默认值。
82
- */
83
- setup(context) {
84
- const [settings, updateSettings] = context.storage.store<Settings>("settings", {
85
- initial: initialSettings(context.options),
86
- })
87
- migrate(settings, updateSettings)
88
-
89
- const disposePanel = context.ui.slot({
90
- append: "sidebar.content",
91
- render: (input) => (
92
- <Panel
93
- context={context}
94
- sessionID={input.sessionID}
95
- settings={settings}
96
- updateSettings={updateSettings}
97
- />
98
- ),
99
- })
100
-
101
- const disposeCommand = context.ui.slot({
102
- append: "app",
103
- render: () => <SettingsCommand context={context} settings={settings} updateSettings={updateSettings} />,
104
- })
105
-
106
- return () => {
107
- disposePanel()
108
- disposeCommand()
109
- }
110
- },
111
- })
1
+ import { Plugin } from "@opencode/plugin/tui"
2
+
3
+ import { initialSettings, normalizeSettings, type Settings } from "../shared/settings.ts"
4
+ import { Panel } from "./panel/index.tsx"
5
+ import { SettingsCommand } from "./settings.tsx"
6
+ import type { UpdateSettings } from "./types.ts"
7
+
8
+ /**
9
+ * 输入:当前设置 + 写入函数。
10
+ * 输出:无。
11
+ * 预期:旧版本存下来的设置可能缺字段(例如没有 contextLimit),补齐并写回。
12
+ */
13
+ function migrate(settings: Settings, update: UpdateSettings): void {
14
+ const normalized = normalizeSettings({
15
+ enabled: settings.enabled,
16
+ expanded: settings.expanded,
17
+ locale: settings.locale,
18
+ contextLimit: settings.contextLimit,
19
+ triggerPercent: settings.triggerPercent,
20
+ summaryBlockPercent: settings.summaryBlockPercent,
21
+ summaryTotalPercent: settings.summaryTotalPercent,
22
+ summaryLanguage: settings.summaryLanguage,
23
+ summaryFormat: settings.summaryFormat,
24
+ autoCompress: settings.autoCompress,
25
+ enableCompressTool: settings.enableCompressTool,
26
+ enableDecompressTool: settings.enableDecompressTool,
27
+ showTimeline: settings.showTimeline,
28
+ showBlockTokens: settings.showBlockTokens,
29
+ cacheExpanded: settings.cacheExpanded,
30
+ showCacheChart: settings.showCacheChart,
31
+ cacheChartExpanded: settings.cacheChartExpanded,
32
+ showCacheLog: settings.showCacheLog,
33
+ cacheLogExpanded: settings.cacheLogExpanded,
34
+ showSummaries: settings.showSummaries,
35
+ summariesExpanded: settings.summariesExpanded,
36
+ decompressInlineMax: settings.decompressInlineMax,
37
+ decompressTempCap: settings.decompressTempCap,
38
+ decompressShowTokens: settings.decompressShowTokens,
39
+ })
40
+ if (
41
+ settings.enabled === normalized.enabled &&
42
+ settings.expanded === normalized.expanded &&
43
+ settings.locale === normalized.locale &&
44
+ settings.contextLimit === normalized.contextLimit &&
45
+ settings.triggerPercent === normalized.triggerPercent &&
46
+ settings.summaryBlockPercent === normalized.summaryBlockPercent &&
47
+ settings.summaryTotalPercent === normalized.summaryTotalPercent &&
48
+ settings.summaryLanguage === normalized.summaryLanguage &&
49
+ settings.summaryFormat === normalized.summaryFormat &&
50
+ settings.autoCompress === normalized.autoCompress &&
51
+ settings.enableCompressTool === normalized.enableCompressTool &&
52
+ settings.enableDecompressTool === normalized.enableDecompressTool &&
53
+ settings.showTimeline === normalized.showTimeline &&
54
+ settings.showBlockTokens === normalized.showBlockTokens &&
55
+ settings.cacheExpanded === normalized.cacheExpanded &&
56
+ settings.showCacheChart === normalized.showCacheChart &&
57
+ settings.cacheChartExpanded === normalized.cacheChartExpanded &&
58
+ settings.showCacheLog === normalized.showCacheLog &&
59
+ settings.cacheLogExpanded === normalized.cacheLogExpanded &&
60
+ settings.showSummaries === normalized.showSummaries &&
61
+ settings.summariesExpanded === normalized.summariesExpanded &&
62
+ settings.decompressInlineMax === normalized.decompressInlineMax &&
63
+ settings.decompressTempCap === normalized.decompressTempCap &&
64
+ settings.decompressShowTokens === normalized.decompressShowTokens
65
+ ) {
66
+ return
67
+ }
68
+ void update((draft) => {
69
+ Object.assign(draft, normalized)
70
+ })
71
+ }
72
+
73
+ export default Plugin.define({
74
+ id: "compressed-context.tui",
75
+
76
+ /**
77
+ * 输入:TUI 插件上下文(含插件 options)。
78
+ * 输出:侧栏面板 + /compressed-context 设置命令。
79
+ * 预期:设置存在 TUI storage 里;options 只作为首次默认值。
80
+ */
81
+ setup(context) {
82
+ const [settings, updateSettings] = context.storage.store<Settings>("settings", {
83
+ initial: initialSettings(context.options),
84
+ })
85
+ migrate(settings, updateSettings)
86
+
87
+ const disposePanel = context.ui.slot({
88
+ append: "sidebar.content",
89
+ render: (input) => (
90
+ <Panel
91
+ context={context}
92
+ sessionID={input.sessionID}
93
+ settings={settings}
94
+ updateSettings={updateSettings}
95
+ />
96
+ ),
97
+ })
98
+
99
+ const disposeCommand = context.ui.slot({
100
+ append: "app",
101
+ render: () => <SettingsCommand context={context} settings={settings} updateSettings={updateSettings} />,
102
+ })
103
+
104
+ return () => {
105
+ disposePanel()
106
+ disposeCommand()
107
+ }
108
+ },
109
+ })
@@ -1,203 +1,203 @@
1
- import type { Context } from "@opencode/plugin/tui/context"
2
- import { createMemo, For, Show } from "solid-js"
3
-
4
- import type { Messages } from "../../i18n/index.ts"
5
- import { formatHitRate, type CacheSample } from "../../shared/cache.ts"
6
- import { formatTokens } from "../../shared/format.ts"
7
- import type { Settings } from "../../shared/settings.ts"
8
- import {
9
- CHART_HEIGHT,
10
- CHART_WIDTH,
11
- chartAxis,
12
- hitRateColor,
13
- renderBrailleChart,
14
- xAxisLabels,
15
- type ChartCell,
16
- } from "../chart/cache-chart.ts"
17
- import { themeHex } from "../chart/color.ts"
18
- import type { UpdateSettings } from "../types.ts"
19
-
20
- /** 输入:一行 chart cell;输出:可直接放进 `<text>` 的 span 序列。 */
21
- function chartLine(row: readonly ChartCell[]) {
22
- return row.map((cell) =>
23
- cell.color === "" ? cell.char : <span style={{ fg: cell.color }}>{cell.char}</span>,
24
- )
25
- }
26
-
27
- /**
28
- * 输入:主题 + 文案 + 设置 + 写入函数 + 缓存样本。
29
- * 输出:可折叠的缓存记录区(Braille 折线 + 最近 10 次日志)。
30
- * 预期:样本由服务端持久化,这里只负责展示;0% 红 → 100% 绿。
31
- */
32
- export function CacheSection(props: {
33
- readonly theme: Context["theme"]
34
- readonly copy: Messages
35
- readonly settings: Settings
36
- readonly updateSettings: UpdateSettings
37
- readonly samples: readonly CacheSample[]
38
- }) {
39
- const chartColors = createMemo(() => ({
40
- success: themeHex(props.theme.text.feedback.success.base, [34, 197, 94]),
41
- warning: themeHex(props.theme.text.feedback.warning.base, [234, 179, 8]),
42
- error: themeHex(props.theme.text.feedback.error.base, [239, 68, 68]),
43
- }))
44
-
45
- const latestHit = createMemo(() => props.samples.at(-1)?.hitRate)
46
- const axis = chartAxis(CHART_HEIGHT)
47
- const chartRows = createMemo(() => {
48
- if (props.samples.length === 0) return []
49
- return renderBrailleChart(
50
- props.samples.map((sample) => sample.hitRate),
51
- CHART_WIDTH,
52
- CHART_HEIGHT,
53
- (pct) => hitRateColor(pct, chartColors()),
54
- axis.gridRows,
55
- themeHex(props.theme.text.muted, [128, 128, 128]),
56
- )
57
- })
58
-
59
- /** 输入:折叠键;输出:无;预期:翻转后写回 TUI storage。 */
60
- const toggleFold = (
61
- key: "cacheExpanded" | "cacheChartExpanded" | "cacheLogExpanded",
62
- ): void => {
63
- void props.updateSettings((draft) => {
64
- draft[key] = !draft[key]
65
- })
66
- }
67
-
68
- return (
69
- <Show when={props.settings.showCacheChart || props.settings.showCacheLog}>
70
- <box width="100%" flexDirection="column">
71
- <box
72
- flexDirection="row"
73
- height={1}
74
- onMouseDown={() => {
75
- toggleFold("cacheExpanded")
76
- }}
77
- >
78
- <box flexDirection="row" gap={1}>
79
- <text fg={props.theme.text.base}>{props.settings.cacheExpanded ? "▼" : "▶"}</text>
80
- <text fg={props.theme.text.base}>
81
- <b>{props.copy.cacheTitle}</b>
82
- </text>
83
- <Show
84
- when={latestHit() !== undefined}
85
- fallback={<text fg={props.theme.text.muted}>{"(--)"}</text>}
86
- >
87
- <text fg={hitRateColor(latestHit() ?? 0, chartColors())}>
88
- {`(${formatHitRate(latestHit() ?? 0)}%)`}
89
- </text>
90
- </Show>
91
- </box>
92
- </box>
93
- <Show when={props.settings.cacheExpanded}>
94
- <box paddingLeft={2} flexDirection="column" width="100%">
95
- <Show when={props.settings.showCacheChart}>
96
- <box flexDirection="column" width="100%">
97
- <box
98
- flexDirection="row"
99
- height={1}
100
- gap={1}
101
- onMouseDown={() => {
102
- toggleFold("cacheChartExpanded")
103
- }}
104
- >
105
- <text fg={props.theme.text.base}>
106
- {props.settings.cacheChartExpanded ? "▼" : "▶"}
107
- </text>
108
- <text fg={props.theme.text.base}>{props.copy.cacheChart}</text>
109
- </box>
110
- <Show when={props.settings.cacheChartExpanded}>
111
- <Show
112
- when={chartRows().length > 0}
113
- fallback={<text fg={props.theme.text.muted}>{props.copy.noData}</text>}
114
- >
115
- <For each={chartRows()}>
116
- {(row, index) => {
117
- const label = axis.labels.find((item) => item.row === index())?.text ?? " "
118
- return (
119
- <box flexDirection="row" height={1}>
120
- <text fg={props.theme.text.muted}>{`${label} `}</text>
121
- <text fg={props.theme.text.muted}>{"┤"}</text>
122
- <text>{chartLine(row)}</text>
123
- </box>
124
- )
125
- }}
126
- </For>
127
- <text fg={props.theme.text.muted}>{` 0% └${"─".repeat(CHART_WIDTH)}`}</text>
128
- <text fg={props.theme.text.muted}>
129
- {`${" ".repeat(6)}${xAxisLabels(props.samples.length, CHART_WIDTH)}`}
130
- </text>
131
- </Show>
132
- </Show>
133
- </box>
134
- </Show>
135
-
136
- <Show when={props.settings.showCacheLog}>
137
- <box flexDirection="column" width="100%">
138
- <box
139
- flexDirection="row"
140
- height={1}
141
- gap={1}
142
- onMouseDown={() => {
143
- toggleFold("cacheLogExpanded")
144
- }}
145
- >
146
- <text fg={props.theme.text.base}>
147
- {props.settings.cacheLogExpanded ? "▼" : "▶"}
148
- </text>
149
- <text fg={props.theme.text.base}>{props.copy.cacheLog}</text>
150
- </box>
151
- <Show when={props.settings.cacheLogExpanded}>
152
- <Show
153
- when={props.samples.length > 0}
154
- fallback={<text fg={props.theme.text.muted}>{props.copy.noData}</text>}
155
- >
156
- <box flexDirection="column" width="100%" paddingLeft={2}>
157
- <box flexDirection="row" gap={1}>
158
- <text width={5} textAlign="left" fg={props.theme.text.muted}>
159
- {props.copy.cacheInput}
160
- </text>
161
- <text width={5} textAlign="left" fg={props.theme.text.muted}>
162
- {props.copy.cacheOutput}
163
- </text>
164
- <text width={5} textAlign="left" fg={props.theme.text.muted}>
165
- {props.copy.cacheCached}
166
- </text>
167
- <text width={7} textAlign="left" fg={props.theme.text.muted}>
168
- {props.copy.cacheHit}
169
- </text>
170
- </box>
171
- <For each={props.samples}>
172
- {(sample) => (
173
- <box flexDirection="row" gap={1}>
174
- <text width={5} textAlign="left" fg={props.theme.text.muted}>
175
- {formatTokens(sample.input)}
176
- </text>
177
- <text width={5} textAlign="left" fg={props.theme.text.muted}>
178
- {formatTokens(sample.output)}
179
- </text>
180
- <text width={5} textAlign="left" fg={props.theme.text.muted}>
181
- {formatTokens(sample.cached)}
182
- </text>
183
- <text
184
- width={7}
185
- textAlign="left"
186
- fg={hitRateColor(sample.hitRate, chartColors())}
187
- >
188
- {`${formatHitRate(sample.hitRate)}%`}
189
- </text>
190
- </box>
191
- )}
192
- </For>
193
- </box>
194
- </Show>
195
- </Show>
196
- </box>
197
- </Show>
198
- </box>
199
- </Show>
200
- </box>
201
- </Show>
202
- )
203
- }
1
+ import type { Context } from "@opencode/plugin/tui/context"
2
+ import { createMemo, For, Show } from "solid-js"
3
+
4
+ import type { Messages } from "../../i18n/index.ts"
5
+ import { formatHitRate, type CacheSample } from "../../shared/cache.ts"
6
+ import { formatTokens } from "../../shared/format.ts"
7
+ import type { Settings } from "../../shared/settings.ts"
8
+ import {
9
+ CHART_HEIGHT,
10
+ CHART_WIDTH,
11
+ chartAxis,
12
+ hitRateColor,
13
+ renderBrailleChart,
14
+ xAxisLabels,
15
+ type ChartCell,
16
+ } from "../chart/cache-chart.ts"
17
+ import { themeHex } from "../chart/color.ts"
18
+ import type { UpdateSettings } from "../types.ts"
19
+
20
+ /** 输入:一行 chart cell;输出:可直接放进 `<text>` 的 span 序列。 */
21
+ function chartLine(row: readonly ChartCell[]) {
22
+ return row.map((cell) =>
23
+ cell.color === "" ? cell.char : <span style={{ fg: cell.color }}>{cell.char}</span>,
24
+ )
25
+ }
26
+
27
+ /**
28
+ * 输入:主题 + 文案 + 设置 + 写入函数 + 缓存样本。
29
+ * 输出:可折叠的缓存记录区(Braille 折线 + 最近 10 次日志)。
30
+ * 预期:样本由服务端持久化,这里只负责展示;0% 红 → 100% 绿。
31
+ */
32
+ export function CacheSection(props: {
33
+ readonly theme: Context["theme"]
34
+ readonly copy: Messages
35
+ readonly settings: Settings
36
+ readonly updateSettings: UpdateSettings
37
+ readonly samples: readonly CacheSample[]
38
+ }) {
39
+ const chartColors = createMemo(() => ({
40
+ success: themeHex(props.theme.text.feedback.success.base, [34, 197, 94]),
41
+ warning: themeHex(props.theme.text.feedback.warning.base, [234, 179, 8]),
42
+ error: themeHex(props.theme.text.feedback.error.base, [239, 68, 68]),
43
+ }))
44
+
45
+ const latestHit = createMemo(() => props.samples.at(-1)?.hitRate)
46
+ const axis = chartAxis(CHART_HEIGHT)
47
+ const chartRows = createMemo(() => {
48
+ if (props.samples.length === 0) return []
49
+ return renderBrailleChart(
50
+ props.samples.map((sample) => sample.hitRate),
51
+ CHART_WIDTH,
52
+ CHART_HEIGHT,
53
+ (pct) => hitRateColor(pct, chartColors()),
54
+ axis.gridRows,
55
+ themeHex(props.theme.text.muted, [128, 128, 128]),
56
+ )
57
+ })
58
+
59
+ /** 输入:折叠键;输出:无;预期:翻转后写回 TUI storage。 */
60
+ const toggleFold = (
61
+ key: "cacheExpanded" | "cacheChartExpanded" | "cacheLogExpanded",
62
+ ): void => {
63
+ void props.updateSettings((draft) => {
64
+ draft[key] = !draft[key]
65
+ })
66
+ }
67
+
68
+ return (
69
+ <Show when={props.settings.showCacheChart || props.settings.showCacheLog}>
70
+ <box width="100%" flexDirection="column">
71
+ <box
72
+ flexDirection="row"
73
+ height={1}
74
+ onMouseDown={() => {
75
+ toggleFold("cacheExpanded")
76
+ }}
77
+ >
78
+ <box flexDirection="row" gap={1}>
79
+ <text fg={props.theme.text.base}>{props.settings.cacheExpanded ? "▼" : "▶"}</text>
80
+ <text fg={props.theme.text.base}>
81
+ <b>{props.copy.cacheTitle}</b>
82
+ </text>
83
+ <Show
84
+ when={latestHit() !== undefined}
85
+ fallback={<text fg={props.theme.text.muted}>{"(--)"}</text>}
86
+ >
87
+ <text fg={hitRateColor(latestHit() ?? 0, chartColors())}>
88
+ {`(${formatHitRate(latestHit() ?? 0)}%)`}
89
+ </text>
90
+ </Show>
91
+ </box>
92
+ </box>
93
+ <Show when={props.settings.cacheExpanded}>
94
+ <box paddingLeft={2} flexDirection="column" width="100%">
95
+ <Show when={props.settings.showCacheChart}>
96
+ <box flexDirection="column" width="100%">
97
+ <box
98
+ flexDirection="row"
99
+ height={1}
100
+ gap={1}
101
+ onMouseDown={() => {
102
+ toggleFold("cacheChartExpanded")
103
+ }}
104
+ >
105
+ <text fg={props.theme.text.base}>
106
+ {props.settings.cacheChartExpanded ? "▼" : "▶"}
107
+ </text>
108
+ <text fg={props.theme.text.base}>{props.copy.cacheChart}</text>
109
+ </box>
110
+ <Show when={props.settings.cacheChartExpanded}>
111
+ <Show
112
+ when={chartRows().length > 0}
113
+ fallback={<text fg={props.theme.text.muted}>{props.copy.noData}</text>}
114
+ >
115
+ <For each={chartRows()}>
116
+ {(row, index) => {
117
+ const label = axis.labels.find((item) => item.row === index())?.text ?? " "
118
+ return (
119
+ <box flexDirection="row" height={1}>
120
+ <text fg={props.theme.text.muted}>{`${label} `}</text>
121
+ <text fg={props.theme.text.muted}>{"┤"}</text>
122
+ <text>{chartLine(row)}</text>
123
+ </box>
124
+ )
125
+ }}
126
+ </For>
127
+ <text fg={props.theme.text.muted}>{` 0% └${"─".repeat(CHART_WIDTH)}`}</text>
128
+ <text fg={props.theme.text.muted}>
129
+ {`${" ".repeat(6)}${xAxisLabels(props.samples.length, CHART_WIDTH)}`}
130
+ </text>
131
+ </Show>
132
+ </Show>
133
+ </box>
134
+ </Show>
135
+
136
+ <Show when={props.settings.showCacheLog}>
137
+ <box flexDirection="column" width="100%">
138
+ <box
139
+ flexDirection="row"
140
+ height={1}
141
+ gap={1}
142
+ onMouseDown={() => {
143
+ toggleFold("cacheLogExpanded")
144
+ }}
145
+ >
146
+ <text fg={props.theme.text.base}>
147
+ {props.settings.cacheLogExpanded ? "▼" : "▶"}
148
+ </text>
149
+ <text fg={props.theme.text.base}>{props.copy.cacheLog}</text>
150
+ </box>
151
+ <Show when={props.settings.cacheLogExpanded}>
152
+ <Show
153
+ when={props.samples.length > 0}
154
+ fallback={<text fg={props.theme.text.muted}>{props.copy.noData}</text>}
155
+ >
156
+ <box flexDirection="column" width="100%" paddingLeft={2}>
157
+ <box flexDirection="row" gap={1}>
158
+ <text width={5} textAlign="left" fg={props.theme.text.muted}>
159
+ {props.copy.cacheInput}
160
+ </text>
161
+ <text width={5} textAlign="left" fg={props.theme.text.muted}>
162
+ {props.copy.cacheOutput}
163
+ </text>
164
+ <text width={5} textAlign="left" fg={props.theme.text.muted}>
165
+ {props.copy.cacheCached}
166
+ </text>
167
+ <text width={7} textAlign="left" fg={props.theme.text.muted}>
168
+ {props.copy.cacheHit}
169
+ </text>
170
+ </box>
171
+ <For each={props.samples}>
172
+ {(sample) => (
173
+ <box flexDirection="row" gap={1}>
174
+ <text width={5} textAlign="left" fg={props.theme.text.muted}>
175
+ {formatTokens(sample.input)}
176
+ </text>
177
+ <text width={5} textAlign="left" fg={props.theme.text.muted}>
178
+ {formatTokens(sample.output)}
179
+ </text>
180
+ <text width={5} textAlign="left" fg={props.theme.text.muted}>
181
+ {formatTokens(sample.cached)}
182
+ </text>
183
+ <text
184
+ width={7}
185
+ textAlign="left"
186
+ fg={hitRateColor(sample.hitRate, chartColors())}
187
+ >
188
+ {`${formatHitRate(sample.hitRate)}%`}
189
+ </text>
190
+ </box>
191
+ )}
192
+ </For>
193
+ </box>
194
+ </Show>
195
+ </Show>
196
+ </box>
197
+ </Show>
198
+ </box>
199
+ </Show>
200
+ </box>
201
+ </Show>
202
+ )
203
+ }