pi-mega-compact 0.8.11 → 0.8.14
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/extensions/dashboard-client/dist/assets/AchievementTiles-BH26e95w.js +2 -0
- package/extensions/dashboard-client/dist/assets/AchievementTiles-BH26e95w.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/AchievementsTab-B94wvRV_.js +2 -0
- package/extensions/dashboard-client/dist/assets/AchievementsTab-B94wvRV_.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/CacheTab-DPmw3NHR.js +2 -0
- package/extensions/dashboard-client/dist/assets/CacheTab-DPmw3NHR.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/ConfigTab-C3g3smRL.js +2 -0
- package/extensions/dashboard-client/dist/assets/ConfigTab-C3g3smRL.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/EventsTab-B5PicfFw.js +2 -0
- package/extensions/dashboard-client/dist/assets/EventsTab-B5PicfFw.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/GameTab-CsVwp78a.js +2 -0
- package/extensions/dashboard-client/dist/assets/GameTab-CsVwp78a.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/MetricsTab-Cf72UewX.js +2 -0
- package/extensions/dashboard-client/dist/assets/MetricsTab-Cf72UewX.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/OverviewTab-Dh6aG3gy.js +2 -0
- package/extensions/dashboard-client/dist/assets/OverviewTab-Dh6aG3gy.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/ReposTab-HXf2dorl.js +2 -0
- package/extensions/dashboard-client/dist/assets/ReposTab-HXf2dorl.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/TimeSavedCard-kUnoospE.js +2 -0
- package/extensions/dashboard-client/dist/assets/TimeSavedCard-kUnoospE.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/index-Cfxniu2R.css +1 -0
- package/extensions/dashboard-client/dist/assets/index-DMntLu-8.js +42 -0
- package/extensions/dashboard-client/dist/assets/index-DMntLu-8.js.map +1 -0
- package/extensions/dashboard-client/dist/index.html +13 -0
- package/extensions/dashboard-client/src/App.tsx +18 -4
- package/extensions/dashboard-client/src/components/AchievementTiles.tsx +105 -0
- package/extensions/dashboard-client/src/components/ActiveReposTable.tsx +116 -0
- package/extensions/dashboard-client/src/components/CacheHitsCard.tsx +73 -0
- package/extensions/dashboard-client/src/components/CacheStatusPerModel.tsx +78 -35
- package/extensions/dashboard-client/src/components/ConfigSummaryCard.tsx +101 -0
- package/extensions/dashboard-client/src/components/ContextGauge.tsx +11 -15
- package/extensions/dashboard-client/src/components/DataSafetyCard.tsx +42 -33
- package/extensions/dashboard-client/src/components/EventCategoryFilter.tsx +46 -9
- package/extensions/dashboard-client/src/components/LegendCard.tsx +49 -0
- package/extensions/dashboard-client/src/components/ModelBadge.tsx +42 -11
- package/extensions/dashboard-client/src/components/PerfCards.tsx +91 -0
- package/extensions/dashboard-client/src/components/RepoAllSessionsCard.tsx +102 -0
- package/extensions/dashboard-client/src/components/RepoDetailModal.tsx +9 -2
- package/extensions/dashboard-client/src/components/RepoTable.tsx +30 -22
- package/extensions/dashboard-client/src/components/SavingsByModelTable.tsx +226 -0
- package/extensions/dashboard-client/src/components/SessionInfo.tsx +25 -50
- package/extensions/dashboard-client/src/components/SummaryTiles.tsx +20 -11
- package/extensions/dashboard-client/src/components/TimeSavedCard.tsx +61 -0
- package/extensions/dashboard-client/src/components/TriggerStatus.tsx +31 -19
- package/extensions/dashboard-client/src/components/VectorStoreCard.tsx +134 -0
- package/extensions/dashboard-client/src/main.tsx +5 -0
- package/extensions/dashboard-client/src/styles/base.css +76 -9
- package/extensions/dashboard-client/src/styles/config.css +149 -0
- package/extensions/dashboard-client/src/styles/game-achievements.css +265 -0
- package/extensions/dashboard-client/src/styles/metrics-extra.css +69 -0
- package/extensions/dashboard-client/src/styles/overview-extra.css +170 -0
- package/extensions/dashboard-client/src/styles/repos-extra.css +99 -0
- package/extensions/dashboard-client/src/styles/repos-metrics.css +14 -23
- package/extensions/dashboard-client/src/tabs/AchievementsTab.tsx +55 -0
- package/extensions/dashboard-client/src/tabs/CacheTab.tsx +50 -0
- package/extensions/dashboard-client/src/tabs/ConfigTab.tsx +240 -5
- package/extensions/dashboard-client/src/tabs/EventsTab.tsx +55 -3
- package/extensions/dashboard-client/src/tabs/GameTab.tsx +287 -0
- package/extensions/dashboard-client/src/tabs/MetricsTab.tsx +7 -1
- package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +103 -17
- package/extensions/dashboard-client/src/tabs/ReposTab.tsx +57 -22
- package/extensions/dashboard-client/src/utils/format.ts +85 -0
- package/extensions/dashboard-client/src/utils/types.ts +27 -0
- package/package.json +3 -2
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/styles/overview-extra.css — styles for new Overview cards.
|
|
3
|
+
*
|
|
4
|
+
* Covers: VectorStoreCard, RepoAllSessionsCard, DataSafetyCard,
|
|
5
|
+
* ConfigSummaryCard, CacheStatusPerModel (Model & Cost), SessionInfo (Crew),
|
|
6
|
+
* CacheHitsCard, TimeSavedCard, LegendCard, compression meters, bullet-na.
|
|
7
|
+
* Does NOT modify overview-events.css (integration phase handles that).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/* Overview card grid — allow cards to wrap naturally */
|
|
11
|
+
.overview-card-grid {
|
|
12
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Stat rows (shared layout for all new cards) */
|
|
16
|
+
.ov-stat-row {
|
|
17
|
+
display: flex;
|
|
18
|
+
justify-content: space-between;
|
|
19
|
+
align-items: baseline;
|
|
20
|
+
padding: 4px 0;
|
|
21
|
+
border-bottom: 1px solid var(--border);
|
|
22
|
+
font-size: clamp(13px, 1vw, 17px);
|
|
23
|
+
}
|
|
24
|
+
.ov-stat-label {
|
|
25
|
+
color: var(--muted);
|
|
26
|
+
font-size: 12px;
|
|
27
|
+
cursor: help;
|
|
28
|
+
}
|
|
29
|
+
.ov-stat-value {
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
color: var(--text);
|
|
32
|
+
font-family: monospace;
|
|
33
|
+
}
|
|
34
|
+
.ov-value-ok {
|
|
35
|
+
color: var(--ok) !important;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Compression meter (Vector Store + Repo cards) */
|
|
39
|
+
.ov-compression-meter {
|
|
40
|
+
margin-top: 10px;
|
|
41
|
+
}
|
|
42
|
+
.meter-track {
|
|
43
|
+
height: clamp(6px, 0.6vw, 10px);
|
|
44
|
+
background: var(--bg);
|
|
45
|
+
border-radius: 5px;
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
border: 1px solid var(--border);
|
|
48
|
+
}
|
|
49
|
+
.meter-fill {
|
|
50
|
+
height: 100%;
|
|
51
|
+
transition: width 0.4s ease;
|
|
52
|
+
border-radius: 4px;
|
|
53
|
+
}
|
|
54
|
+
.meter-green {
|
|
55
|
+
background: var(--ok);
|
|
56
|
+
}
|
|
57
|
+
.meter-yellow {
|
|
58
|
+
background: var(--warn);
|
|
59
|
+
}
|
|
60
|
+
.meter-red {
|
|
61
|
+
background: var(--err);
|
|
62
|
+
}
|
|
63
|
+
.meter-sub {
|
|
64
|
+
display: block;
|
|
65
|
+
margin-top: 4px;
|
|
66
|
+
font-size: 11px;
|
|
67
|
+
color: var(--muted);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Trigger status: na (yellow) bullet */
|
|
71
|
+
.bullet-na .bullet-dot {
|
|
72
|
+
background: var(--warn);
|
|
73
|
+
box-shadow: 0 0 6px var(--warn);
|
|
74
|
+
}
|
|
75
|
+
.bullet-na .bullet-label {
|
|
76
|
+
color: var(--warn);
|
|
77
|
+
}
|
|
78
|
+
.trigger-state-text {
|
|
79
|
+
margin: 6px 0 0;
|
|
80
|
+
font-size: 12px;
|
|
81
|
+
color: var(--muted);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Data safety card */
|
|
85
|
+
.data-safety-card .safe-note {
|
|
86
|
+
margin: 10px 0 0;
|
|
87
|
+
padding: 8px;
|
|
88
|
+
font-size: 11px;
|
|
89
|
+
color: var(--muted);
|
|
90
|
+
background: rgba(63, 185, 80, 0.06);
|
|
91
|
+
border-radius: 6px;
|
|
92
|
+
border: 1px solid rgba(63, 185, 80, 0.2);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Model & Cost Savings card */
|
|
96
|
+
.model-cost-card .ov-cost-usd {
|
|
97
|
+
font-size: clamp(16px, 1.4vw, 22px);
|
|
98
|
+
font-weight: 700;
|
|
99
|
+
color: #a371f7;
|
|
100
|
+
margin-bottom: 2px;
|
|
101
|
+
}
|
|
102
|
+
.model-cost-card .ov-cost-sub {
|
|
103
|
+
font-size: 12px;
|
|
104
|
+
color: var(--muted);
|
|
105
|
+
margin-bottom: 8px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Crew / Agents card */
|
|
109
|
+
.crew-card .ov-stat-value {
|
|
110
|
+
font-family: inherit;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Legend card — full width */
|
|
114
|
+
.legend-card {
|
|
115
|
+
grid-column: 1 / -1;
|
|
116
|
+
}
|
|
117
|
+
.legend-card details {
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
}
|
|
120
|
+
.legend-card summary {
|
|
121
|
+
font-size: 13px;
|
|
122
|
+
color: var(--muted);
|
|
123
|
+
font-weight: 600;
|
|
124
|
+
margin-bottom: 4px;
|
|
125
|
+
}
|
|
126
|
+
.legend-list {
|
|
127
|
+
margin: 8px 0;
|
|
128
|
+
padding-left: 20px;
|
|
129
|
+
font-size: 12px;
|
|
130
|
+
color: var(--muted);
|
|
131
|
+
line-height: 1.6;
|
|
132
|
+
}
|
|
133
|
+
.legend-list li {
|
|
134
|
+
margin-bottom: 4px;
|
|
135
|
+
}
|
|
136
|
+
.legend-list b {
|
|
137
|
+
color: var(--text);
|
|
138
|
+
}
|
|
139
|
+
.legend-note {
|
|
140
|
+
font-size: 11px;
|
|
141
|
+
color: var(--dim);
|
|
142
|
+
font-style: italic;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Cache tab layout */
|
|
146
|
+
.cache-tab {
|
|
147
|
+
display: flex;
|
|
148
|
+
flex-direction: column;
|
|
149
|
+
gap: 12px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Responsive breakpoints */
|
|
153
|
+
@media (max-width: 640px) {
|
|
154
|
+
.overview-card-grid {
|
|
155
|
+
grid-template-columns: 1fr;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@media (min-width: 1025px) {
|
|
160
|
+
.overview-card-grid {
|
|
161
|
+
grid-template-columns: repeat(3, 1fr);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@media (min-width: 1601px) {
|
|
166
|
+
.overview-card-grid {
|
|
167
|
+
grid-template-columns: repeat(4, 1fr);
|
|
168
|
+
font-size: clamp(13px, 0.9vw, 16px);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/styles/repos-extra.css — Active repos + Savings by Model.
|
|
3
|
+
*
|
|
4
|
+
* Styles for ActiveReposTable and SavingsByModelTable components.
|
|
5
|
+
* Does NOT modify repos-metrics.css (per task constraint).
|
|
6
|
+
* Responsive: tables scroll horizontally on narrow screens.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/* Section headers within Repos tab */
|
|
10
|
+
.section-header {
|
|
11
|
+
font-size: 13px;
|
|
12
|
+
text-transform: uppercase;
|
|
13
|
+
letter-spacing: 0.5px;
|
|
14
|
+
color: var(--muted);
|
|
15
|
+
margin: 20px 0 10px;
|
|
16
|
+
font-weight: 600;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Horizontal scroll wrapper for wide tables */
|
|
20
|
+
.table-scroll {
|
|
21
|
+
overflow-x: auto;
|
|
22
|
+
-webkit-overflow-scrolling: touch;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Shared table styles for active-repos and savings tables */
|
|
26
|
+
.active-repos-table,
|
|
27
|
+
.savings-table {
|
|
28
|
+
width: 100%;
|
|
29
|
+
border-collapse: collapse;
|
|
30
|
+
font-size: 13px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.active-repos-table th,
|
|
34
|
+
.savings-table th {
|
|
35
|
+
text-align: left;
|
|
36
|
+
padding: 6px 10px;
|
|
37
|
+
border-bottom: 1px solid var(--border);
|
|
38
|
+
white-space: nowrap;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.active-repos-table th.num,
|
|
42
|
+
.savings-table th.num {
|
|
43
|
+
text-align: right;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.active-repos-table td,
|
|
47
|
+
.savings-table td {
|
|
48
|
+
padding: 6px 10px;
|
|
49
|
+
border-bottom: 1px solid var(--border);
|
|
50
|
+
white-space: nowrap;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.active-repos-table td.num,
|
|
54
|
+
.savings-table td.num {
|
|
55
|
+
text-align: right;
|
|
56
|
+
font-family: monospace;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.active-repos-table tbody tr:hover,
|
|
60
|
+
.savings-table tbody tr:hover {
|
|
61
|
+
background: var(--panel);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Model cell emphasis */
|
|
65
|
+
.savings-table .repo-model {
|
|
66
|
+
font-weight: 600;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Empty-state row */
|
|
70
|
+
.repo-empty {
|
|
71
|
+
text-align: center;
|
|
72
|
+
color: var(--muted);
|
|
73
|
+
padding: 24px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Section spacing */
|
|
77
|
+
.active-repos-section {
|
|
78
|
+
margin-bottom: 8px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.savings-by-model {
|
|
82
|
+
margin-bottom: 16px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Responsive: ensure tables have min-width so columns don't crush */
|
|
86
|
+
@media (max-width: 640px) {
|
|
87
|
+
.active-repos-table {
|
|
88
|
+
min-width: 900px;
|
|
89
|
+
}
|
|
90
|
+
.savings-table {
|
|
91
|
+
min-width: 1200px;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@media (min-width: 641px) and (max-width: 1024px) {
|
|
96
|
+
.savings-table {
|
|
97
|
+
min-width: 1000px;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -187,9 +187,8 @@
|
|
|
187
187
|
|
|
188
188
|
/* Model badge */
|
|
189
189
|
.model-badge {
|
|
190
|
-
display:
|
|
191
|
-
|
|
192
|
-
align-items: center;
|
|
190
|
+
display: grid;
|
|
191
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr));
|
|
193
192
|
gap: 8px;
|
|
194
193
|
padding: 12px 14px;
|
|
195
194
|
background: var(--panel);
|
|
@@ -197,26 +196,19 @@
|
|
|
197
196
|
border-radius: 8px;
|
|
198
197
|
margin-bottom: 12px;
|
|
199
198
|
}
|
|
200
|
-
.model-
|
|
201
|
-
font-size: 15px;
|
|
202
|
-
font-weight: 600;
|
|
203
|
-
}
|
|
204
|
-
.model-provider {
|
|
205
|
-
color: var(--accent);
|
|
206
|
-
font-size: 13px;
|
|
207
|
-
}
|
|
208
|
-
.model-provider-id {
|
|
209
|
-
color: var(--muted);
|
|
210
|
-
font-size: 11px;
|
|
211
|
-
}
|
|
212
|
-
.model-rates {
|
|
213
|
-
margin-left: auto;
|
|
199
|
+
.model-field {
|
|
214
200
|
display: flex;
|
|
215
|
-
|
|
201
|
+
flex-direction: column;
|
|
202
|
+
gap: 2px;
|
|
216
203
|
}
|
|
217
|
-
.model-
|
|
218
|
-
font-size:
|
|
204
|
+
.model-field-label {
|
|
205
|
+
font-size: 11px;
|
|
219
206
|
color: var(--muted);
|
|
207
|
+
text-transform: uppercase;
|
|
208
|
+
}
|
|
209
|
+
.model-field-value {
|
|
210
|
+
font-size: 14px;
|
|
211
|
+
font-weight: 600;
|
|
220
212
|
}
|
|
221
213
|
|
|
222
214
|
/* Perf chart */
|
|
@@ -300,8 +292,7 @@
|
|
|
300
292
|
.modal-grid {
|
|
301
293
|
grid-template-columns: 1fr 1fr;
|
|
302
294
|
}
|
|
303
|
-
.model-
|
|
304
|
-
|
|
305
|
-
width: 100%;
|
|
295
|
+
.model-badge {
|
|
296
|
+
grid-template-columns: 1fr 1fr;
|
|
306
297
|
}
|
|
307
298
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/tabs/AchievementsTab.tsx — Achievements tab (NEW).
|
|
3
|
+
*
|
|
4
|
+
* Renders the shared AchievementTiles component standalone, plus a toast area.
|
|
5
|
+
* Polls /api/achievements every 15s.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type React from "react";
|
|
9
|
+
import { useState, useEffect, useCallback } from "react";
|
|
10
|
+
import type { AchievementRow } from "@contracts";
|
|
11
|
+
import { fetchAchievements } from "../api/client";
|
|
12
|
+
import { AchievementTiles } from "../components/AchievementTiles";
|
|
13
|
+
|
|
14
|
+
export default function AchievementsTab(): React.ReactElement {
|
|
15
|
+
const [achievements, setAchievements] = useState<AchievementRow[]>([]);
|
|
16
|
+
const [loading, setLoading] = useState(true);
|
|
17
|
+
const [error, setError] = useState<Error | null>(null);
|
|
18
|
+
|
|
19
|
+
const doFetch = useCallback(async (): Promise<void> => {
|
|
20
|
+
try {
|
|
21
|
+
const rows = await fetchAchievements();
|
|
22
|
+
setAchievements(rows);
|
|
23
|
+
setError(null);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
26
|
+
} finally {
|
|
27
|
+
setLoading(false);
|
|
28
|
+
}
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
let active = true;
|
|
33
|
+
const run = async (): Promise<void> => {
|
|
34
|
+
if (active) await doFetch();
|
|
35
|
+
};
|
|
36
|
+
void run();
|
|
37
|
+
const timer = setInterval(() => void run(), 15000);
|
|
38
|
+
return () => {
|
|
39
|
+
active = false;
|
|
40
|
+
clearInterval(timer);
|
|
41
|
+
};
|
|
42
|
+
}, [doFetch]);
|
|
43
|
+
|
|
44
|
+
if (loading && achievements.length === 0)
|
|
45
|
+
return <div className="tab-stub">Loading achievements…</div>;
|
|
46
|
+
if (error && achievements.length === 0)
|
|
47
|
+
return <div className="tab-stub">Error: {error.message}</div>;
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className="achievements-tab">
|
|
51
|
+
<h2>Achievements</h2>
|
|
52
|
+
<AchievementTiles achievements={achievements} />
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/tabs/CacheTab.tsx — Cache tab (NEW).
|
|
3
|
+
*
|
|
4
|
+
* Renders CacheHitsCard + TimeSavedCard from /api/snapshot
|
|
5
|
+
* via the useApi hook with 5s polling.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type React from "react";
|
|
9
|
+
import { useCallback } from "react";
|
|
10
|
+
import type { SnapshotResponse } from "@contracts";
|
|
11
|
+
import { useApi } from "../hooks/useApi";
|
|
12
|
+
import { fetchSnapshot } from "../api/client";
|
|
13
|
+
import { CacheHitsCard } from "../components/CacheHitsCard";
|
|
14
|
+
import { TimeSavedCard } from "../components/TimeSavedCard";
|
|
15
|
+
|
|
16
|
+
export default function CacheTab(): React.ReactElement {
|
|
17
|
+
const { data: snapshot, loading, error } = useApi<SnapshotResponse>(
|
|
18
|
+
useCallback(() => fetchSnapshot(), []),
|
|
19
|
+
{ pollInterval: 5000 },
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
if (loading && !snapshot)
|
|
23
|
+
return <div className="tab-stub">Loading snapshot…</div>;
|
|
24
|
+
if (error && !snapshot)
|
|
25
|
+
return <div className="tab-stub">Error: {error.message}</div>;
|
|
26
|
+
if (!snapshot) return <div className="tab-stub">No snapshot data.</div>;
|
|
27
|
+
|
|
28
|
+
const { cacheHits, compacts, timeSaved } = snapshot;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="cache-tab">
|
|
32
|
+
<div className="card-grid overview-card-grid">
|
|
33
|
+
<CacheHitsCard
|
|
34
|
+
cacheHitsSession={cacheHits.session}
|
|
35
|
+
cacheHitsTotal={cacheHits.total}
|
|
36
|
+
tokensSavedSession={cacheHits.sessionTokensSaved}
|
|
37
|
+
tokensSavedTotal={cacheHits.totalTokensSaved}
|
|
38
|
+
compactionsSession={compacts.session}
|
|
39
|
+
compactionsTotal={compacts.total}
|
|
40
|
+
/>
|
|
41
|
+
<TimeSavedCard
|
|
42
|
+
compactSessionSec={timeSaved.compact.sessionSec}
|
|
43
|
+
compactTotalSec={timeSaved.compact.totalSec}
|
|
44
|
+
cacheHitSessionSec={timeSaved.cacheHit.sessionSec}
|
|
45
|
+
cacheHitTotalSec={timeSaved.cacheHit.totalSec}
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -1,16 +1,251 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dashboard-client/src/tabs/ConfigTab.tsx — Config tab (
|
|
2
|
+
* dashboard-client/src/tabs/ConfigTab.tsx — Config tab (C3).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Full configuration editor:
|
|
5
|
+
* (1) Game mode toggle (checkbox)
|
|
6
|
+
* (2) Theme selector (dropdown) from src/config/themes.ts THEMES array
|
|
7
|
+
* (3) TUI mode selector (Full/Minimal dropdown)
|
|
8
|
+
* (4) Read-only config display section from snapshot (Tier, Preset, Pressure,
|
|
9
|
+
* Threshold, Fast Gate, Auto, Anchor) with verbatim tooltips from html.ts.
|
|
10
|
+
*
|
|
11
|
+
* Uses fetchGameState() to load current state, putGameState() to save changes.
|
|
12
|
+
* Polls gameState and snapshot every 5s. Applies theme by setting
|
|
13
|
+
* document.documentElement.dataset.theme.
|
|
5
14
|
*/
|
|
6
15
|
|
|
7
16
|
import type React from "react";
|
|
17
|
+
import { useState, useEffect, useCallback } from "react";
|
|
18
|
+
import type { SnapshotResponse, GameStatePatch } from "@contracts";
|
|
19
|
+
import { fetchSnapshot, fetchGameState, putGameState } from "../api/client";
|
|
20
|
+
import { THEMES } from "../../../../src/config/themes";
|
|
21
|
+
|
|
22
|
+
/** Actual flat game state returned by the server (the server returns a flat
|
|
23
|
+
* { game_mode_on, theme, tui_display_mode } shape, NOT the contract's nested
|
|
24
|
+
* { config: { enabled, theme, displayMode }, activeRitual } shape). */
|
|
25
|
+
interface FlatGameState {
|
|
26
|
+
game_mode_on: boolean;
|
|
27
|
+
theme: string;
|
|
28
|
+
tui_display_mode: "full" | "minimal";
|
|
29
|
+
}
|
|
30
|
+
type FlatGameStatePatch = Partial<FlatGameState>;
|
|
31
|
+
|
|
32
|
+
/** Extended snapshot config — the runtime includes tierPct (not in contract). */
|
|
33
|
+
interface ConfigExt extends SnapshotResponse["config"] {
|
|
34
|
+
tierPct?: number | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Tooltips copied verbatim from extensions/dashboard-server/html.ts */
|
|
38
|
+
const TOOLTIPS = {
|
|
39
|
+
gameMode: "Turn game mode on/off (themes the widget + dashboard)",
|
|
40
|
+
theme: "Visual theme (applies instantly)",
|
|
41
|
+
tuiMode: "TUI widget display density",
|
|
42
|
+
tier: "Live pressure band — climbs low\u2192mega as context fills the window.",
|
|
43
|
+
preset:
|
|
44
|
+
"The env-resolved base compaction preset (low/medium/high/ultra/mega) that set the token threshold.",
|
|
45
|
+
pressure:
|
|
46
|
+
"Live pressure = currentTokens / threshold \u2014 % of the model context window (threshold fires at the tier's % of window).",
|
|
47
|
+
threshold:
|
|
48
|
+
"Compaction threshold = tierPct \u00d7 model context window \u2014 mega-compact trims BELOW pi's native ~80% auto-compact for any model size.",
|
|
49
|
+
fastGate:
|
|
50
|
+
"Fast-gate arming floor \u2014 the live trim arms once context passes this % of the window.",
|
|
51
|
+
} as const;
|
|
52
|
+
|
|
53
|
+
function formatThreshold(
|
|
54
|
+
tokens: number,
|
|
55
|
+
tierPct: number | null | undefined,
|
|
56
|
+
cw: number,
|
|
57
|
+
): string {
|
|
58
|
+
let txt = tokens.toLocaleString();
|
|
59
|
+
if (tierPct != null && cw > 0) {
|
|
60
|
+
txt += ` (${Math.round(tierPct * 100)}% of ${cw.toLocaleString()})`;
|
|
61
|
+
}
|
|
62
|
+
return txt;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function formatPressure(pressure: number): string {
|
|
66
|
+
/* Runtime sends 0\u20131 fraction despite contract saying 0\u2013100. */
|
|
67
|
+
return `${Math.round((pressure || 0) * 100)}%`;
|
|
68
|
+
}
|
|
8
69
|
|
|
9
70
|
export default function ConfigTab(): React.ReactElement {
|
|
71
|
+
const [snapshot, setSnapshot] = useState<SnapshotResponse | null>(null);
|
|
72
|
+
const [gameState, setGameState] = useState<FlatGameState | null>(null);
|
|
73
|
+
const [saving, setSaving] = useState(false);
|
|
74
|
+
|
|
75
|
+
/* Poll snapshot every 5s for config display section. */
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
let active = true;
|
|
78
|
+
const doFetch = async (): Promise<void> => {
|
|
79
|
+
try {
|
|
80
|
+
const s = await fetchSnapshot();
|
|
81
|
+
if (active) setSnapshot(s);
|
|
82
|
+
} catch {
|
|
83
|
+
/* non-fatal */
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
void doFetch();
|
|
87
|
+
const timer = setInterval(() => void doFetch(), 5000);
|
|
88
|
+
return () => {
|
|
89
|
+
active = false;
|
|
90
|
+
clearInterval(timer);
|
|
91
|
+
};
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
/* Poll game state every 5s. */
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
let active = true;
|
|
97
|
+
const doFetch = async (): Promise<void> => {
|
|
98
|
+
try {
|
|
99
|
+
const gs = (await fetchGameState()) as unknown as FlatGameState;
|
|
100
|
+
if (active) {
|
|
101
|
+
setGameState(gs);
|
|
102
|
+
if (gs.theme)
|
|
103
|
+
document.documentElement.dataset.theme = gs.theme;
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
/* non-fatal */
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
void doFetch();
|
|
110
|
+
const timer = setInterval(() => void doFetch(), 5000);
|
|
111
|
+
return () => {
|
|
112
|
+
active = false;
|
|
113
|
+
clearInterval(timer);
|
|
114
|
+
};
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
117
|
+
/* PUT a patch to the server immediately on change. */
|
|
118
|
+
const handlePatch = useCallback(async (patch: FlatGameStatePatch) => {
|
|
119
|
+
setSaving(true);
|
|
120
|
+
try {
|
|
121
|
+
const result = (await putGameState(
|
|
122
|
+
patch as unknown as GameStatePatch,
|
|
123
|
+
)) as unknown as FlatGameState;
|
|
124
|
+
setGameState(result);
|
|
125
|
+
if (result.theme)
|
|
126
|
+
document.documentElement.dataset.theme = result.theme;
|
|
127
|
+
} catch {
|
|
128
|
+
/* non-fatal */
|
|
129
|
+
}
|
|
130
|
+
setSaving(false);
|
|
131
|
+
}, []);
|
|
132
|
+
|
|
133
|
+
const gmOn = gameState?.game_mode_on ?? false;
|
|
134
|
+
const themeVal = gameState?.theme ?? "transparent";
|
|
135
|
+
const tuiVal = gameState?.tui_display_mode ?? "full";
|
|
136
|
+
|
|
137
|
+
const cfg = snapshot?.config as ConfigExt | undefined;
|
|
138
|
+
const cw = snapshot?.context.contextWindow ?? 0;
|
|
139
|
+
|
|
10
140
|
return (
|
|
11
|
-
<div className="tab
|
|
12
|
-
<
|
|
13
|
-
|
|
141
|
+
<div className="config-tab">
|
|
142
|
+
<section className="config-settings">
|
|
143
|
+
<h3>Settings</h3>
|
|
144
|
+
<div className="settings-row">
|
|
145
|
+
<label title={TOOLTIPS.gameMode}>
|
|
146
|
+
<input
|
|
147
|
+
type="checkbox"
|
|
148
|
+
checked={gmOn}
|
|
149
|
+
onChange={(e) =>
|
|
150
|
+
handlePatch({ game_mode_on: e.currentTarget.checked })
|
|
151
|
+
}
|
|
152
|
+
disabled={saving}
|
|
153
|
+
/>
|
|
154
|
+
Game Mode
|
|
155
|
+
</label>
|
|
156
|
+
</div>
|
|
157
|
+
<div className="settings-row">
|
|
158
|
+
<label title={TOOLTIPS.theme}>
|
|
159
|
+
Theme
|
|
160
|
+
<select
|
|
161
|
+
value={themeVal}
|
|
162
|
+
onChange={(e) => handlePatch({ theme: e.currentTarget.value })}
|
|
163
|
+
disabled={saving}
|
|
164
|
+
>
|
|
165
|
+
{THEMES.map((t) => (
|
|
166
|
+
<option key={t.id} value={t.id}>
|
|
167
|
+
{t.label}
|
|
168
|
+
</option>
|
|
169
|
+
))}
|
|
170
|
+
</select>
|
|
171
|
+
</label>
|
|
172
|
+
</div>
|
|
173
|
+
<div className="settings-row">
|
|
174
|
+
<label title={TOOLTIPS.tuiMode}>
|
|
175
|
+
TUI Mode
|
|
176
|
+
<select
|
|
177
|
+
value={tuiVal}
|
|
178
|
+
onChange={(e) =>
|
|
179
|
+
handlePatch({
|
|
180
|
+
tui_display_mode: e.currentTarget.value as "full" | "minimal",
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
disabled={saving}
|
|
184
|
+
>
|
|
185
|
+
<option value="full">Full</option>
|
|
186
|
+
<option value="minimal">Minimal</option>
|
|
187
|
+
</select>
|
|
188
|
+
</label>
|
|
189
|
+
</div>
|
|
190
|
+
</section>
|
|
191
|
+
|
|
192
|
+
<section className="config-display">
|
|
193
|
+
<h3>Configuration</h3>
|
|
194
|
+
<div className="config-grid">
|
|
195
|
+
<div className="config-item">
|
|
196
|
+
<span className="config-label" title={TOOLTIPS.tier}>
|
|
197
|
+
Tier
|
|
198
|
+
</span>
|
|
199
|
+
<span className="config-value">
|
|
200
|
+
{snapshot ? `${snapshot.tier} (live)` : "\u2014"}
|
|
201
|
+
</span>
|
|
202
|
+
</div>
|
|
203
|
+
<div className="config-item">
|
|
204
|
+
<span className="config-label" title={TOOLTIPS.preset}>
|
|
205
|
+
Preset
|
|
206
|
+
</span>
|
|
207
|
+
<span className="config-value">
|
|
208
|
+
{snapshot?.presetTier ?? "\u2014"}
|
|
209
|
+
</span>
|
|
210
|
+
</div>
|
|
211
|
+
<div className="config-item">
|
|
212
|
+
<span className="config-label" title={TOOLTIPS.pressure}>
|
|
213
|
+
Pressure
|
|
214
|
+
</span>
|
|
215
|
+
<span className="config-value">
|
|
216
|
+
{snapshot ? formatPressure(snapshot.pressure) : "\u2014"}
|
|
217
|
+
</span>
|
|
218
|
+
</div>
|
|
219
|
+
<div className="config-item">
|
|
220
|
+
<span className="config-label" title={TOOLTIPS.threshold}>
|
|
221
|
+
Threshold
|
|
222
|
+
</span>
|
|
223
|
+
<span className="config-value">
|
|
224
|
+
{cfg ? formatThreshold(cfg.thresholdTokens, cfg.tierPct, cw) : "\u2014"}
|
|
225
|
+
</span>
|
|
226
|
+
</div>
|
|
227
|
+
<div className="config-item">
|
|
228
|
+
<span className="config-label" title={TOOLTIPS.fastGate}>
|
|
229
|
+
Fast Gate
|
|
230
|
+
</span>
|
|
231
|
+
<span className="config-value">
|
|
232
|
+
{cfg ? `${cfg.fastGatePct}%` : "\u2014"}
|
|
233
|
+
</span>
|
|
234
|
+
</div>
|
|
235
|
+
<div className="config-item">
|
|
236
|
+
<span className="config-label">Auto</span>
|
|
237
|
+
<span className="config-value">
|
|
238
|
+
{cfg ? (cfg.auto ? "enabled" : "disabled") : "\u2014"}
|
|
239
|
+
</span>
|
|
240
|
+
</div>
|
|
241
|
+
<div className="config-item">
|
|
242
|
+
<span className="config-label">Anchor</span>
|
|
243
|
+
<span className="config-value">
|
|
244
|
+
{cfg?.anchorUserMessages ?? "\u2014"}
|
|
245
|
+
</span>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
</section>
|
|
14
249
|
</div>
|
|
15
250
|
);
|
|
16
251
|
}
|