@goodandready/dsh-context-lens 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/README.ru.md +8 -0
- package/lib/client.js +168 -17
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -127,6 +127,14 @@ dsh-context-lens:
|
|
|
127
127
|
|
|
128
128
|
## 📝 Version History
|
|
129
129
|
|
|
130
|
+
### v0.1.10
|
|
131
|
+
* **Fix**: Register session header chip in `conversation.session.header.utilities` (`order: 7`).
|
|
132
|
+
* **Fix**: Ensure chip is always visible (`◐ Lens` on initial session, `◐ <N>%` when savings available, `⚠` on low budget).
|
|
133
|
+
* **Feature**: Interactive dropdown Popover on chip click: token savings breakdown, budget progress bar, recent operations, and refresh button.
|
|
134
|
+
|
|
135
|
+
### v0.1.9
|
|
136
|
+
* **Fix**: Remove obsolete kernel modules from client injects for DSH 0.1.2-rc.1 compatibility.
|
|
137
|
+
|
|
130
138
|
### v0.1.8
|
|
131
139
|
* **Fix**: Support both `text` and `log` parameter names in `context_lens_compress_log`.
|
|
132
140
|
* **Fix**: Cross-platform path resolution in unit tests on Windows (`fileURLToPath`).
|
package/README.ru.md
CHANGED
|
@@ -124,6 +124,14 @@ dsh-context-lens:
|
|
|
124
124
|
|
|
125
125
|
## 📝 История версий
|
|
126
126
|
|
|
127
|
+
### v0.1.10
|
|
128
|
+
* **Fix**: Регистрация индикатора сессии в актуальном слоте ядра `conversation.session.header.utilities` (`order: 7`).
|
|
129
|
+
* **Fix**: Чип теперь отображается всегда (`◐ Lens` при отсутствии сжатий, `◐ <N>%` при наличии сэкономленных токенов, `⚠` при низком остатке бюджета).
|
|
130
|
+
* **Feature**: Интерактивный выпадающий Popover по клику на чип: детальные метрики токенов, шкала прогресса бюджета, последние 3 операции и кнопка обновления.
|
|
131
|
+
|
|
132
|
+
### v0.1.9
|
|
133
|
+
* **Fix**: Удаление устаревших модулей ядра из инъекций клиента для совместимости с DSH 0.1.2-rc.1.
|
|
134
|
+
|
|
127
135
|
### v0.1.8
|
|
128
136
|
* **Fix**: Поддержка как `text`, так и `log` в параметрах инструмента `context_lens_compress_log`.
|
|
129
137
|
* **Fix**: Кроссплатформенное разрешение путей в тестах на Windows (`fileURLToPath`).
|
package/lib/client.js
CHANGED
|
@@ -81,17 +81,163 @@ window.__ModuleLoader__.load({
|
|
|
81
81
|
);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
function
|
|
84
|
+
function HeaderChip({ ctx: _ctx }) {
|
|
85
85
|
const [stats, setStats] = React.useState(null);
|
|
86
|
+
const [history, setHistory] = React.useState([]);
|
|
87
|
+
const [open, setOpen] = React.useState(false);
|
|
88
|
+
const ref = React.useRef(null);
|
|
89
|
+
|
|
90
|
+
const fetchStatus = () => {
|
|
91
|
+
fetch('/dsh-context-lens/status', { headers: { accept: 'application/json' } })
|
|
92
|
+
.then(r => r.ok ? r.json() : null)
|
|
93
|
+
.then(j => {
|
|
94
|
+
if (j && j.stats) setStats(j.stats);
|
|
95
|
+
if (j && j.history) setHistory(j.history);
|
|
96
|
+
})
|
|
97
|
+
.catch(() => {});
|
|
98
|
+
};
|
|
99
|
+
|
|
86
100
|
React.useEffect(() => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const id = setInterval(
|
|
90
|
-
return () => clearInterval(id);
|
|
101
|
+
let alive = true;
|
|
102
|
+
fetchStatus();
|
|
103
|
+
const id = setInterval(() => { if (alive) fetchStatus(); }, 5000);
|
|
104
|
+
return () => { alive = false; clearInterval(id); };
|
|
91
105
|
}, []);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
106
|
+
|
|
107
|
+
React.useEffect(() => {
|
|
108
|
+
if (!open) return;
|
|
109
|
+
const onDocClick = (e) => {
|
|
110
|
+
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
111
|
+
};
|
|
112
|
+
document.addEventListener('click', onDocClick);
|
|
113
|
+
return () => document.removeEventListener('click', onDocClick);
|
|
114
|
+
}, [open]);
|
|
115
|
+
|
|
116
|
+
const warn = !!(stats && stats.lowBudget);
|
|
117
|
+
const hasSavings = !!(stats && stats.savedTokens > 0);
|
|
118
|
+
const label = hasSavings ? `Lens ${stats.savedPercent}%` : 'Lens';
|
|
119
|
+
const color = warn ? '#d73a4a' : 'var(--dsw-alias-label-secondary)';
|
|
120
|
+
const borderColor = warn ? '#d73a4a' : open ? 'var(--dsw-alias-border-l1)' : 'var(--dsw-alias-border-l2)';
|
|
121
|
+
|
|
122
|
+
const popover = open ? React.createElement('div', {
|
|
123
|
+
style: {
|
|
124
|
+
position: 'absolute',
|
|
125
|
+
top: 'calc(100% + 6px)',
|
|
126
|
+
right: 0,
|
|
127
|
+
width: 270,
|
|
128
|
+
background: 'var(--dsw-alias-bg-layer-3)',
|
|
129
|
+
border: '1px solid var(--dsw-alias-border-l2)',
|
|
130
|
+
borderRadius: 10,
|
|
131
|
+
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.28)',
|
|
132
|
+
padding: '12px 14px',
|
|
133
|
+
zIndex: 1000,
|
|
134
|
+
fontSize: 12,
|
|
135
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
136
|
+
cursor: 'default',
|
|
137
|
+
textAlign: 'left'
|
|
138
|
+
},
|
|
139
|
+
onClick: (e) => e.stopPropagation()
|
|
140
|
+
},
|
|
141
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 } },
|
|
142
|
+
React.createElement('div', { style: { fontWeight: 600, fontSize: 13, display: 'flex', alignItems: 'center', gap: 6 } },
|
|
143
|
+
React.createElement('span', { style: { opacity: 0.8 } }, '◐'),
|
|
144
|
+
'Context Lens'
|
|
145
|
+
),
|
|
146
|
+
React.createElement('span', {
|
|
147
|
+
style: {
|
|
148
|
+
fontSize: 11,
|
|
149
|
+
padding: '1px 6px',
|
|
150
|
+
borderRadius: 4,
|
|
151
|
+
background: warn ? 'rgba(215, 58, 74, 0.15)' : 'var(--dsw-alias-bg-layer-2)',
|
|
152
|
+
color: warn ? '#d73a4a' : 'var(--dsw-alias-label-tertiary)',
|
|
153
|
+
border: '1px solid ' + (warn ? 'rgba(215, 58, 74, 0.3)' : 'var(--dsw-alias-border-l2)')
|
|
154
|
+
}
|
|
155
|
+
}, warn ? 'Low Budget ⚠' : (stats ? 'Active' : 'Ready'))
|
|
156
|
+
),
|
|
157
|
+
hasSavings
|
|
158
|
+
? React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginBottom: 8 } },
|
|
159
|
+
`Saved ${stats.savedTokens} tokens (${stats.savedPercent}%) · ${stats.calls} ops`
|
|
160
|
+
)
|
|
161
|
+
: React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-label-tertiary)', marginBottom: 8 } },
|
|
162
|
+
'No compression yet · Monitoring active'
|
|
163
|
+
),
|
|
164
|
+
stats && stats.budgetLimit ? React.createElement('div', { style: { marginTop: 6, marginBottom: 8 } },
|
|
165
|
+
React.createElement('div', {
|
|
166
|
+
style: {
|
|
167
|
+
display: 'flex',
|
|
168
|
+
justifyContent: 'space-between',
|
|
169
|
+
fontSize: 11,
|
|
170
|
+
color: warn ? '#d73a4a' : 'var(--dsw-alias-label-tertiary)',
|
|
171
|
+
marginBottom: 4
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
React.createElement('span', null, 'Budget'),
|
|
175
|
+
React.createElement('span', null, `${stats.budgetUsed || 0} / ${stats.budgetLimit} (${stats.budgetPercent || 0}%)`)
|
|
176
|
+
),
|
|
177
|
+
React.createElement('div', { style: { height: 5, borderRadius: 3, background: 'var(--dsw-alias-bg-layer-1)', overflow: 'hidden' } },
|
|
178
|
+
React.createElement('div', {
|
|
179
|
+
style: {
|
|
180
|
+
height: '100%',
|
|
181
|
+
width: Math.min(100, stats.budgetPercent || 0) + '%',
|
|
182
|
+
background: warn ? '#d73a4a' : 'var(--dsw-alias-label-primary)',
|
|
183
|
+
transition: 'width .2s'
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
)
|
|
187
|
+
) : null,
|
|
188
|
+
history && history.length ? React.createElement('div', { style: { marginTop: 8, paddingTop: 8, borderTop: '1px solid var(--dsw-alias-border-l3)' } },
|
|
189
|
+
React.createElement('div', { style: { fontSize: 11, color: 'var(--dsw-alias-label-tertiary)', marginBottom: 4 } }, 'Recent operations'),
|
|
190
|
+
...history.slice(0, 3).map((h) => React.createElement('div', {
|
|
191
|
+
key: h.id,
|
|
192
|
+
style: { fontSize: 11, color: 'var(--dsw-alias-label-secondary)', padding: '2px 0', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }
|
|
193
|
+
}, `−${h.savedTokens} tk (${h.savedPercent}%) · ${(h.preview || '').slice(0, 28)}`))
|
|
194
|
+
) : null,
|
|
195
|
+
React.createElement('div', { style: { marginTop: 10, display: 'flex', justifyContent: 'flex-end' } },
|
|
196
|
+
React.createElement('button', {
|
|
197
|
+
type: 'button',
|
|
198
|
+
onClick: fetchStatus,
|
|
199
|
+
style: {
|
|
200
|
+
appearance: 'none',
|
|
201
|
+
cursor: 'pointer',
|
|
202
|
+
fontSize: 11,
|
|
203
|
+
padding: '3px 8px',
|
|
204
|
+
borderRadius: 6,
|
|
205
|
+
border: '1px solid var(--dsw-alias-border-l2)',
|
|
206
|
+
background: 'var(--dsw-alias-bg-layer-2)',
|
|
207
|
+
color: 'var(--dsw-alias-label-secondary)'
|
|
208
|
+
}
|
|
209
|
+
}, 'Refresh')
|
|
210
|
+
)
|
|
211
|
+
) : null;
|
|
212
|
+
|
|
213
|
+
return React.createElement('div', { ref, style: { position: 'relative', display: 'inline-flex', alignItems: 'center' } },
|
|
214
|
+
React.createElement('button', {
|
|
215
|
+
type: 'button',
|
|
216
|
+
title: hasSavings ? `Context Lens: Saved ${stats.savedTokens} tk (${stats.savedPercent}%)` : 'Context Lens: Active',
|
|
217
|
+
onClick: () => setOpen((v) => !v),
|
|
218
|
+
style: {
|
|
219
|
+
appearance: 'none',
|
|
220
|
+
font: 'inherit',
|
|
221
|
+
cursor: 'pointer',
|
|
222
|
+
display: 'inline-flex',
|
|
223
|
+
alignItems: 'center',
|
|
224
|
+
gap: 4,
|
|
225
|
+
padding: '2px 8px',
|
|
226
|
+
borderRadius: 999,
|
|
227
|
+
fontSize: 11,
|
|
228
|
+
lineHeight: '16px',
|
|
229
|
+
background: open ? 'var(--dsw-alias-bg-layer-3)' : 'var(--dsw-alias-bg-layer-2)',
|
|
230
|
+
color,
|
|
231
|
+
border: '1px solid ' + borderColor,
|
|
232
|
+
marginLeft: 6
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
React.createElement('span', { style: { fontSize: 10, opacity: 0.8 } }, '◐'),
|
|
236
|
+
label,
|
|
237
|
+
warn ? React.createElement('span', { style: { color: '#d73a4a' } }, ' ⚠') : null
|
|
238
|
+
),
|
|
239
|
+
popover
|
|
240
|
+
);
|
|
95
241
|
}
|
|
96
242
|
|
|
97
243
|
function PluginCard({ ctx: _ctx, t }) {
|
|
@@ -308,24 +454,29 @@ window.__ModuleLoader__.load({
|
|
|
308
454
|
console.warn('[dsh-context-lens] betterSidebar registerTab failed', e && e.message || e);
|
|
309
455
|
}
|
|
310
456
|
}
|
|
311
|
-
// Header
|
|
457
|
+
// Header chip (#31) — telemetry and token guard in conversation session utilities slot
|
|
312
458
|
if (ctx.slots) {
|
|
313
|
-
const
|
|
459
|
+
const headerChipRegister = () => {
|
|
314
460
|
try {
|
|
315
461
|
return ctx.slots.register({
|
|
316
|
-
name: 'conversation.header',
|
|
317
|
-
id: 'dsh-context-lens
|
|
318
|
-
order:
|
|
462
|
+
name: 'conversation.session.header.utilities',
|
|
463
|
+
id: 'dsh-context-lens-header-chip',
|
|
464
|
+
order: 7,
|
|
319
465
|
inject: () => ({ ctx })
|
|
320
|
-
},
|
|
466
|
+
}, HeaderChip);
|
|
321
467
|
} catch (e) {
|
|
322
|
-
console.warn('[dsh-context-lens] header
|
|
468
|
+
console.warn('[dsh-context-lens] header chip register failed', e && e.message || e);
|
|
323
469
|
}
|
|
324
470
|
};
|
|
325
471
|
if (typeof ctx.slots.inject === 'function') {
|
|
326
|
-
try {
|
|
472
|
+
try {
|
|
473
|
+
ctx.slots.inject('conversation.session.header.utilities', headerChipRegister);
|
|
474
|
+
} catch (e) {
|
|
475
|
+
console.warn('[dsh-context-lens] utilities inject failed', e && e.message || e);
|
|
476
|
+
try { headerChipRegister(); } catch (e2) {}
|
|
477
|
+
}
|
|
327
478
|
} else {
|
|
328
|
-
try {
|
|
479
|
+
try { headerChipRegister(); } catch (e) {}
|
|
329
480
|
}
|
|
330
481
|
}
|
|
331
482
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-context-lens",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "DSH plugin for AST context compression, test log filtering, and token budget guard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -44,7 +44,6 @@
|
|
|
44
44
|
"platform": "web",
|
|
45
45
|
"inject": [
|
|
46
46
|
"@deepseek-ai/dsh-client-locale",
|
|
47
|
-
"@deepseek-ai/dsh-client-ui-slots",
|
|
48
47
|
"@deepseek-ai/dsh-client-ui-settings"
|
|
49
48
|
]
|
|
50
49
|
}
|