@goodandready/dsh-context-lens 0.1.15 → 0.1.17
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 +19 -5
- package/README.ru.md +19 -5
- package/README.zh.md +11 -1
- package/lib/ast/skeletonizer.js +31 -15
- package/lib/client.js +453 -142
- package/lib/index.js +36 -8
- package/lib/tokens/tracker.js +14 -6
- package/package.json +3 -1
package/lib/client.js
CHANGED
|
@@ -5,7 +5,7 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
const React = require('react');
|
|
6
6
|
const NS = '@goodandready/dsh-context-lens';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const en = {
|
|
9
9
|
title: 'Context Lens & Token Guard',
|
|
10
10
|
sub: 'AST compression, log filtering, token budget',
|
|
11
11
|
mode: 'Compression mode',
|
|
@@ -23,62 +23,289 @@ window.__ModuleLoader__.load({
|
|
|
23
23
|
lowBudget: 'Budget nearly exhausted',
|
|
24
24
|
threshold: 'Auto-compress threshold (chars)',
|
|
25
25
|
autoCollapse: 'Warn when budget is nearly exhausted',
|
|
26
|
+
budgetAlertPercent: 'Budget alert threshold (%)',
|
|
26
27
|
previewApprox: 'Server preview',
|
|
27
28
|
noData: 'No data yet',
|
|
28
29
|
refresh: 'Refresh',
|
|
29
30
|
ops: 'ops',
|
|
30
31
|
active: 'Active',
|
|
31
32
|
readyState: 'Ready',
|
|
32
|
-
lowBudgetBadge: 'Low Budget ⚠'
|
|
33
|
+
lowBudgetBadge: 'Low Budget ⚠',
|
|
34
|
+
focusPaths: 'Active focus',
|
|
35
|
+
noFocus: 'No focus paths set',
|
|
36
|
+
clearFocus: 'Clear focus'
|
|
33
37
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
38
|
+
|
|
39
|
+
const zh = {
|
|
40
|
+
title: 'Context Lens 上下文镜头与 Token 守卫',
|
|
41
|
+
sub: 'AST 代码精简、日志过滤与 Token 预算监控',
|
|
42
|
+
mode: '压缩模式',
|
|
43
|
+
depth: 'AST 最大深度',
|
|
44
|
+
tracking: '追踪 Token 节省',
|
|
45
|
+
preview: '日志预览',
|
|
46
|
+
compress: '压缩',
|
|
47
|
+
saved: '已节省',
|
|
48
|
+
tokens: 'Token',
|
|
49
|
+
placeholder: '在此粘贴 Jest/Pytest 等测试日志…',
|
|
50
|
+
saving: '保存中…',
|
|
51
|
+
ready: '已就绪',
|
|
52
|
+
budget: '预算',
|
|
53
|
+
history: '近期操作',
|
|
54
|
+
lowBudget: 'Token 预算即将耗尽',
|
|
55
|
+
threshold: '自动压缩阈值 (字符数)',
|
|
56
|
+
autoCollapse: '预算偏低时在界面提示警告',
|
|
57
|
+
budgetAlertPercent: '预算警告阈值 (%)',
|
|
58
|
+
previewApprox: '服务端压缩预览',
|
|
59
|
+
noData: '暂无数据',
|
|
60
|
+
refresh: '刷新',
|
|
61
|
+
ops: '次操作',
|
|
62
|
+
active: '运行中',
|
|
63
|
+
readyState: '已就绪',
|
|
64
|
+
lowBudgetBadge: '预算紧张 ⚠',
|
|
65
|
+
focusPaths: '当前聚焦路径',
|
|
66
|
+
noFocus: '未设置聚焦路径',
|
|
67
|
+
clearFocus: '清除聚焦'
|
|
59
68
|
};
|
|
60
69
|
|
|
61
|
-
|
|
70
|
+
function ensureCss() {
|
|
71
|
+
if (typeof document === 'undefined') return;
|
|
72
|
+
if (document.getElementById('dsh-context-lens-css')) return;
|
|
73
|
+
const style = document.createElement('style');
|
|
74
|
+
style.id = 'dsh-context-lens-css';
|
|
75
|
+
style.dataset.dshPlugin = NS;
|
|
76
|
+
style.textContent = `
|
|
77
|
+
.cl-section-card {
|
|
78
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
79
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
80
|
+
border-radius: 12px;
|
|
81
|
+
padding: 16px 18px;
|
|
82
|
+
margin-bottom: 12px;
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-direction: column;
|
|
85
|
+
gap: 12px;
|
|
86
|
+
list-style: none;
|
|
87
|
+
}
|
|
88
|
+
.cl-section-header {
|
|
89
|
+
background: none;
|
|
90
|
+
border: none;
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
width: 100%;
|
|
95
|
+
padding: 0;
|
|
96
|
+
text-align: left;
|
|
97
|
+
}
|
|
98
|
+
.cl-badge {
|
|
99
|
+
font-size: 11px;
|
|
100
|
+
padding: 2px 8px;
|
|
101
|
+
border-radius: 999px;
|
|
102
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
103
|
+
display: inline-flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
gap: 4px;
|
|
106
|
+
font-weight: 500;
|
|
107
|
+
}
|
|
108
|
+
.cl-badge-ok {
|
|
109
|
+
border-color: var(--dsw-alias-state-success-primary, #10b981);
|
|
110
|
+
color: var(--dsw-alias-state-success-primary, #10b981);
|
|
111
|
+
background: rgba(16, 185, 129, 0.08);
|
|
112
|
+
}
|
|
113
|
+
.cl-badge-warn {
|
|
114
|
+
border-color: var(--dsw-alias-state-error-primary, #ef4444);
|
|
115
|
+
color: var(--dsw-alias-state-error-primary, #ef4444);
|
|
116
|
+
background: rgba(239, 68, 68, 0.08);
|
|
117
|
+
}
|
|
118
|
+
.cl-stat-grid {
|
|
119
|
+
display: grid;
|
|
120
|
+
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
|
|
121
|
+
gap: 10px;
|
|
122
|
+
margin-top: 4px;
|
|
123
|
+
}
|
|
124
|
+
.cl-stat-box {
|
|
125
|
+
padding: 10px 12px;
|
|
126
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
127
|
+
border-radius: 8px;
|
|
128
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-direction: column;
|
|
131
|
+
gap: 3px;
|
|
132
|
+
}
|
|
133
|
+
.cl-stat-val {
|
|
134
|
+
font-size: 16px;
|
|
135
|
+
font-weight: 700;
|
|
136
|
+
color: var(--dsw-alias-label-primary);
|
|
137
|
+
}
|
|
138
|
+
.cl-stat-lbl {
|
|
139
|
+
font-size: 11px;
|
|
140
|
+
color: var(--dsw-alias-label-secondary);
|
|
141
|
+
}
|
|
142
|
+
.cl-bar-box {
|
|
143
|
+
display: flex;
|
|
144
|
+
flex-direction: column;
|
|
145
|
+
gap: 6px;
|
|
146
|
+
padding: 10px 12px;
|
|
147
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
148
|
+
border-radius: 8px;
|
|
149
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
150
|
+
}
|
|
151
|
+
.cl-bar-track {
|
|
152
|
+
width: 100%;
|
|
153
|
+
height: 8px;
|
|
154
|
+
border-radius: 999px;
|
|
155
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
156
|
+
overflow: hidden;
|
|
157
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
158
|
+
}
|
|
159
|
+
.cl-bar-fill {
|
|
160
|
+
height: 100%;
|
|
161
|
+
border-radius: 999px;
|
|
162
|
+
transition: width 0.3s;
|
|
163
|
+
}
|
|
164
|
+
.cl-input {
|
|
165
|
+
height: 34px;
|
|
166
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
167
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
168
|
+
color: var(--dsw-alias-label-primary);
|
|
169
|
+
border-radius: 8px;
|
|
170
|
+
padding: 0 12px;
|
|
171
|
+
font-size: 13px;
|
|
172
|
+
box-sizing: border-box;
|
|
173
|
+
}
|
|
174
|
+
.cl-input:focus {
|
|
175
|
+
outline: none;
|
|
176
|
+
border-color: var(--dsw-alias-state-brand-primary, #3b82f6);
|
|
177
|
+
}
|
|
178
|
+
.cl-btn {
|
|
179
|
+
appearance: none;
|
|
180
|
+
font: inherit;
|
|
181
|
+
cursor: pointer;
|
|
182
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
183
|
+
border-radius: 8px;
|
|
184
|
+
padding: 6px 12px;
|
|
185
|
+
font-size: 12px;
|
|
186
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
187
|
+
color: var(--dsw-alias-label-primary);
|
|
188
|
+
font-weight: 500;
|
|
189
|
+
display: inline-flex;
|
|
190
|
+
align-items: center;
|
|
191
|
+
justify-content: center;
|
|
192
|
+
gap: 6px;
|
|
193
|
+
transition: all 0.15s ease;
|
|
194
|
+
}
|
|
195
|
+
.cl-btn:hover:not(:disabled) {
|
|
196
|
+
background: var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2));
|
|
197
|
+
border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2));
|
|
198
|
+
}
|
|
199
|
+
.cl-btn-primary {
|
|
200
|
+
background: var(--dsw-alias-label-primary);
|
|
201
|
+
color: var(--dsw-alias-bg-layer-3);
|
|
202
|
+
border-color: transparent;
|
|
203
|
+
}
|
|
204
|
+
.cl-btn-primary:hover:not(:disabled) {
|
|
205
|
+
opacity: 0.88;
|
|
206
|
+
}
|
|
207
|
+
.cl-btn-disabled {
|
|
208
|
+
opacity: 0.5;
|
|
209
|
+
cursor: not-allowed;
|
|
210
|
+
}
|
|
211
|
+
.cl-banner-warn {
|
|
212
|
+
padding: 10px 14px;
|
|
213
|
+
border-radius: 8px;
|
|
214
|
+
background: rgba(239, 68, 68, 0.1);
|
|
215
|
+
border: 1px solid var(--dsw-alias-state-error-primary, #ef4444);
|
|
216
|
+
color: var(--dsw-alias-state-error-primary, #ef4444);
|
|
217
|
+
font-size: 12px;
|
|
218
|
+
display: flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
gap: 8px;
|
|
221
|
+
font-weight: 500;
|
|
222
|
+
}
|
|
223
|
+
.cl-hist-item {
|
|
224
|
+
font-size: 11px;
|
|
225
|
+
color: var(--dsw-alias-label-secondary);
|
|
226
|
+
padding: 4px 0;
|
|
227
|
+
border-bottom: 1px solid var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));
|
|
228
|
+
}
|
|
229
|
+
.cl-hist-item:last-child {
|
|
230
|
+
border-bottom: none;
|
|
231
|
+
}
|
|
232
|
+
.cl-tag {
|
|
233
|
+
display: inline-flex;
|
|
234
|
+
align-items: center;
|
|
235
|
+
gap: 4px;
|
|
236
|
+
padding: 2px 6px;
|
|
237
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
238
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
239
|
+
border-radius: 4px;
|
|
240
|
+
font-size: 11px;
|
|
241
|
+
font-family: monospace;
|
|
242
|
+
color: var(--dsw-alias-label-secondary);
|
|
243
|
+
}
|
|
244
|
+
.cl-error-box {
|
|
245
|
+
padding: 12px;
|
|
246
|
+
border-radius: 8px;
|
|
247
|
+
background: rgba(239, 68, 68, 0.08);
|
|
248
|
+
border: 1px solid var(--dsw-alias-state-error-primary, #ef4444);
|
|
249
|
+
color: var(--dsw-alias-state-error-primary, #ef4444);
|
|
250
|
+
font-size: 12px;
|
|
251
|
+
}
|
|
252
|
+
`;
|
|
253
|
+
document.head.appendChild(style);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function createErrorBoundary() {
|
|
257
|
+
if (!React || typeof React.Component !== 'function') {
|
|
258
|
+
return function NoopBoundary(props) { return (props && props.children) || null; };
|
|
259
|
+
}
|
|
260
|
+
return class ErrorBoundary extends React.Component {
|
|
261
|
+
constructor(props) {
|
|
262
|
+
super(props);
|
|
263
|
+
this.state = { hasError: false, error: null };
|
|
264
|
+
}
|
|
265
|
+
static getDerivedStateFromError(error) {
|
|
266
|
+
return { hasError: true, error };
|
|
267
|
+
}
|
|
268
|
+
componentDidCatch(error, errorInfo) {
|
|
269
|
+
console.error('[dsh-context-lens] UI Error:', error, errorInfo);
|
|
270
|
+
}
|
|
271
|
+
render() {
|
|
272
|
+
if (this.state.hasError) {
|
|
273
|
+
return React.createElement('div', { className: 'cl-error-box' },
|
|
274
|
+
React.createElement('div', { style: { fontWeight: 600, marginBottom: 4 } }, '⚠ Context Lens UI Error'),
|
|
275
|
+
React.createElement('div', { style: { fontSize: 11, wordBreak: 'break-all' } }, String(this.state.error?.message || this.state.error)),
|
|
276
|
+
React.createElement('button', {
|
|
277
|
+
type: 'button',
|
|
278
|
+
className: 'cl-btn',
|
|
279
|
+
style: { marginTop: 8, fontSize: 11, padding: '3px 8px' },
|
|
280
|
+
onClick: () => this.setState({ hasError: false, error: null })
|
|
281
|
+
}, 'Retry')
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
return (this.props && this.props.children) || null;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
const ErrorBoundary = createErrorBoundary();
|
|
62
289
|
|
|
63
|
-
|
|
64
|
-
function getActiveLocale(ctx) {
|
|
290
|
+
function getActiveLocale(ctx) {
|
|
65
291
|
try {
|
|
66
292
|
if (ctx && ctx.locale && typeof ctx.locale.getSnapshot === 'function') {
|
|
67
293
|
const snap = ctx.locale.getSnapshot();
|
|
68
|
-
if (snap && snap.active && snap.active.startsWith('
|
|
294
|
+
if (snap && snap.active && snap.active.startsWith('zh')) return zh;
|
|
69
295
|
}
|
|
70
296
|
} catch {}
|
|
71
297
|
return en;
|
|
72
298
|
}
|
|
73
299
|
|
|
74
|
-
function StatusPanel({ stats, history, labels, onRefresh, title, showBadge, compact }) {
|
|
300
|
+
function StatusPanel({ stats, history, focus, sessionId, labels, onRefresh, onClearFocus, title, showBadge, compact }) {
|
|
301
|
+
ensureCss();
|
|
75
302
|
const L = labels || en;
|
|
76
303
|
const warn = !!(stats && stats.lowBudget);
|
|
77
304
|
const hasSavings = !!(stats && stats.savedTokens > 0);
|
|
78
|
-
const barColor = warn ? '#
|
|
79
|
-
const previewLen = compact ? 28 :
|
|
80
|
-
const barH = compact ? 5 : 6;
|
|
305
|
+
const barColor = warn ? 'var(--dsw-alias-state-error-primary, #ef4444)' : 'var(--dsw-alias-state-success-primary, #10b981)';
|
|
306
|
+
const previewLen = compact ? 28 : 44;
|
|
81
307
|
const children = [];
|
|
308
|
+
|
|
82
309
|
if (title || showBadge) {
|
|
83
310
|
children.push(React.createElement('div', {
|
|
84
311
|
key: 'head',
|
|
@@ -92,78 +319,113 @@ window.__ModuleLoader__.load({
|
|
|
92
319
|
: React.createElement('span'),
|
|
93
320
|
showBadge
|
|
94
321
|
? React.createElement('span', {
|
|
95
|
-
|
|
96
|
-
fontSize: 11, padding: '1px 6px', borderRadius: 4,
|
|
97
|
-
background: warn ? 'rgba(215, 58, 74, 0.15)' : 'var(--dsw-alias-bg-layer-2)',
|
|
98
|
-
color: warn ? '#d73a4a' : 'var(--dsw-alias-label-tertiary)',
|
|
99
|
-
border: '1px solid ' + (warn ? 'rgba(215, 58, 74, 0.3)' : 'var(--dsw-alias-border-l2)')
|
|
100
|
-
}
|
|
322
|
+
className: 'cl-badge ' + (warn ? 'cl-badge-warn' : 'cl-badge-ok')
|
|
101
323
|
}, warn ? L.lowBudgetBadge : (stats ? L.active : L.readyState))
|
|
102
324
|
: null
|
|
103
325
|
));
|
|
104
326
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
327
|
+
|
|
328
|
+
if (stats) {
|
|
329
|
+
children.push(React.createElement('div', { key: 'stats', className: 'cl-stat-grid' },
|
|
330
|
+
React.createElement('div', { className: 'cl-stat-box' },
|
|
331
|
+
React.createElement('div', { className: 'cl-stat-val' }, stats.savedTokens ? `${stats.savedTokens}` : '0'),
|
|
332
|
+
React.createElement('div', { className: 'cl-stat-lbl' }, `${L.saved} ${L.tokens}`)
|
|
333
|
+
),
|
|
334
|
+
React.createElement('div', { className: 'cl-stat-box' },
|
|
335
|
+
React.createElement('div', { className: 'cl-stat-val' }, `${stats.savedPercent || 0}%`),
|
|
336
|
+
React.createElement('div', { className: 'cl-stat-lbl' }, '% ' + L.saved)
|
|
337
|
+
),
|
|
338
|
+
React.createElement('div', { className: 'cl-stat-box' },
|
|
339
|
+
React.createElement('div', { className: 'cl-stat-val' }, `${stats.calls || 0}`),
|
|
340
|
+
React.createElement('div', { className: 'cl-stat-lbl' }, L.ops)
|
|
341
|
+
)
|
|
342
|
+
));
|
|
343
|
+
} else {
|
|
344
|
+
children.push(React.createElement('div', {
|
|
345
|
+
key: 'nodata',
|
|
346
|
+
style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', padding: '6px 0' }
|
|
347
|
+
}, L.noData));
|
|
348
|
+
}
|
|
349
|
+
|
|
111
350
|
if (stats && stats.lowBudget && !showBadge) {
|
|
112
|
-
children.push(React.createElement('div', {
|
|
351
|
+
children.push(React.createElement('div', {
|
|
352
|
+
key: 'warn',
|
|
353
|
+
className: 'cl-banner-warn',
|
|
354
|
+
style: { marginTop: 8 }
|
|
355
|
+
}, '⚠ ' + L.lowBudget));
|
|
113
356
|
}
|
|
357
|
+
|
|
114
358
|
if (stats && stats.budgetLimit) {
|
|
115
|
-
children.push(React.createElement('div', { key: 'budget', style: { marginTop:
|
|
359
|
+
children.push(React.createElement('div', { key: 'budget', className: 'cl-bar-box', style: { marginTop: 8 } },
|
|
116
360
|
React.createElement('div', {
|
|
117
|
-
style: {
|
|
118
|
-
display: compact ? 'flex' : 'block',
|
|
119
|
-
justifyContent: 'space-between',
|
|
120
|
-
fontSize: 11,
|
|
121
|
-
color: warn ? '#d73a4a' : 'var(--dsw-alias-label-secondary)',
|
|
122
|
-
marginBottom: 4
|
|
123
|
-
}
|
|
361
|
+
style: { display: 'flex', justifyContent: 'space-between', fontSize: 11, color: warn ? 'var(--dsw-alias-state-error-primary, #ef4444)' : 'var(--dsw-alias-label-secondary)' }
|
|
124
362
|
},
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
React.createElement('span', null, L.budget),
|
|
128
|
-
React.createElement('span', null, `${stats.budgetUsed || 0} / ${stats.budgetLimit} (${stats.budgetPercent || 0}%)`)
|
|
129
|
-
)
|
|
130
|
-
: `${L.budget} ${stats.budgetUsed}/${stats.budgetLimit} ${L.tokens} (${stats.budgetPercent}%)${warn ? ' ⚠' : ''}`
|
|
363
|
+
React.createElement('span', { style: { fontWeight: 500 } }, L.budget),
|
|
364
|
+
React.createElement('span', null, `${stats.budgetUsed || 0} / ${stats.budgetLimit} (${stats.budgetPercent || 0}%)`)
|
|
131
365
|
),
|
|
132
|
-
React.createElement('div', {
|
|
133
|
-
React.createElement('div', {
|
|
366
|
+
React.createElement('div', { className: 'cl-bar-track' },
|
|
367
|
+
React.createElement('div', {
|
|
368
|
+
className: 'cl-bar-fill',
|
|
369
|
+
style: { width: Math.min(100, stats.budgetPercent || 0) + '%', background: barColor }
|
|
370
|
+
})
|
|
134
371
|
)
|
|
135
372
|
));
|
|
136
373
|
}
|
|
374
|
+
|
|
375
|
+
// Active focus paths section
|
|
376
|
+
const focusPaths = (focus && focus.paths) || [];
|
|
377
|
+
if (focusPaths.length > 0 || onClearFocus) {
|
|
378
|
+
children.push(React.createElement('div', {
|
|
379
|
+
key: 'focus',
|
|
380
|
+
style: { marginTop: 10, paddingTop: 8, borderTop: '1px solid var(--dsw-alias-border-l2)' }
|
|
381
|
+
},
|
|
382
|
+
React.createElement('div', {
|
|
383
|
+
style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }
|
|
384
|
+
},
|
|
385
|
+
React.createElement('span', { style: { fontSize: 11, fontWeight: 600, color: 'var(--dsw-alias-label-secondary)' } }, L.focusPaths),
|
|
386
|
+
focusPaths.length > 0 && onClearFocus
|
|
387
|
+
? React.createElement('button', {
|
|
388
|
+
type: 'button',
|
|
389
|
+
onClick: onClearFocus,
|
|
390
|
+
style: { background: 'none', border: 'none', padding: 0, fontSize: 10, color: 'var(--dsw-alias-label-tertiary)', cursor: 'pointer', textDecoration: 'underline' }
|
|
391
|
+
}, L.clearFocus)
|
|
392
|
+
: null
|
|
393
|
+
),
|
|
394
|
+
focusPaths.length > 0
|
|
395
|
+
? React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 4 } },
|
|
396
|
+
...focusPaths.slice(0, 4).map((p, i) => React.createElement('span', { key: i, className: 'cl-tag' }, p))
|
|
397
|
+
)
|
|
398
|
+
: React.createElement('div', { style: { fontSize: 11, color: 'var(--dsw-alias-label-tertiary)', fontStyle: 'italic' } }, L.noFocus)
|
|
399
|
+
));
|
|
400
|
+
}
|
|
401
|
+
|
|
137
402
|
if (history && history.length) {
|
|
138
403
|
children.push(React.createElement('div', {
|
|
139
404
|
key: 'hist',
|
|
140
|
-
style: { marginTop:
|
|
405
|
+
style: { marginTop: 10, paddingTop: 8, borderTop: '1px solid var(--dsw-alias-border-l2)' }
|
|
141
406
|
},
|
|
142
|
-
React.createElement('div', { style: { fontSize: 11, color: 'var(--dsw-alias-label-tertiary)', marginBottom: 4 } }, L.history),
|
|
407
|
+
React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: 'var(--dsw-alias-label-tertiary)', marginBottom: 4 } }, L.history),
|
|
143
408
|
...history.slice(0, 3).map((h) => React.createElement('div', {
|
|
144
409
|
key: h.id,
|
|
145
|
-
|
|
410
|
+
className: 'cl-hist-item',
|
|
411
|
+
style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }
|
|
146
412
|
}, `−${h.savedTokens} tk (${h.savedPercent}%) · ${(h.preview || '').slice(0, previewLen)}`))
|
|
147
413
|
));
|
|
148
414
|
}
|
|
415
|
+
|
|
149
416
|
if (onRefresh) {
|
|
150
417
|
children.push(React.createElement('div', {
|
|
151
418
|
key: 'act',
|
|
152
|
-
style: { marginTop:
|
|
419
|
+
style: { marginTop: 10, display: 'flex', justifyContent: 'flex-end' }
|
|
153
420
|
},
|
|
154
421
|
React.createElement('button', {
|
|
155
422
|
type: 'button',
|
|
156
423
|
onClick: onRefresh,
|
|
157
|
-
|
|
158
|
-
appearance: 'none', cursor: 'pointer', fontSize: compact ? 11 : 12,
|
|
159
|
-
padding: compact ? '3px 8px' : '4px 10px', borderRadius: 6,
|
|
160
|
-
border: '1px solid var(--dsw-alias-border-l2)',
|
|
161
|
-
background: compact ? 'var(--dsw-alias-bg-layer-2)' : 'var(--dsw-alias-bg-layer-3)',
|
|
162
|
-
color: compact ? 'var(--dsw-alias-label-secondary)' : 'var(--dsw-alias-label-primary)'
|
|
163
|
-
}
|
|
424
|
+
className: 'cl-btn'
|
|
164
425
|
}, L.refresh)
|
|
165
426
|
));
|
|
166
427
|
}
|
|
428
|
+
|
|
167
429
|
return React.createElement(React.Fragment, null, ...children);
|
|
168
430
|
}
|
|
169
431
|
|
|
@@ -172,26 +434,47 @@ window.__ModuleLoader__.load({
|
|
|
172
434
|
.then((r) => r.ok ? r.json() : null);
|
|
173
435
|
}
|
|
174
436
|
|
|
175
|
-
function
|
|
437
|
+
function clearLensFocus(sessionId) {
|
|
438
|
+
const q = sessionId ? '?sessionId=' + encodeURIComponent(sessionId) : '';
|
|
439
|
+
return fetch('/dsh-context-lens/clear-focus' + q, { method: 'POST', headers: { accept: 'application/json' } })
|
|
440
|
+
.then((r) => r.ok ? r.json() : null);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function LensTabInner({ ctx: _ctx, scope }) {
|
|
444
|
+
ensureCss();
|
|
176
445
|
const [stats, setStats] = React.useState(null);
|
|
177
446
|
const [history, setHistory] = React.useState([]);
|
|
447
|
+
const [focus, setFocus] = React.useState(null);
|
|
448
|
+
const [sessionId, setSessionId] = React.useState(null);
|
|
178
449
|
const L = React.useMemo(() => getActiveLocale(_ctx), [_ctx]);
|
|
179
450
|
const apply = (j) => {
|
|
180
451
|
if (j && j.stats) setStats(j.stats);
|
|
181
452
|
if (j && j.history) setHistory(j.history || []);
|
|
453
|
+
if (j && j.focus) setFocus(j.focus);
|
|
454
|
+
if (j && j.sessionId) setSessionId(j.sessionId);
|
|
182
455
|
};
|
|
183
456
|
const refresh = () => fetchLensStatus().then(apply).catch(() => {});
|
|
457
|
+
const handleClearFocus = () => clearLensFocus(sessionId).then(() => refresh()).catch(() => {});
|
|
184
458
|
React.useEffect(() => { refresh(); }, []);
|
|
185
459
|
return React.createElement('div', { style: { padding: 12 } },
|
|
186
460
|
React.createElement(StatusPanel, {
|
|
187
|
-
stats, history, labels: L, onRefresh: refresh, title: 'Context Lens', showBadge: false, compact: false
|
|
461
|
+
stats, history, focus, sessionId, labels: L, onRefresh: refresh, onClearFocus: handleClearFocus, title: 'Context Lens', showBadge: false, compact: false
|
|
188
462
|
})
|
|
189
463
|
);
|
|
190
464
|
}
|
|
191
465
|
|
|
466
|
+
function LensTab(props) {
|
|
467
|
+
return React.createElement(ErrorBoundary, null,
|
|
468
|
+
React.createElement(LensTabInner, props)
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
192
472
|
function HeaderChip({ ctx: _ctx }) {
|
|
473
|
+
ensureCss();
|
|
193
474
|
const [stats, setStats] = React.useState(null);
|
|
194
475
|
const [history, setHistory] = React.useState([]);
|
|
476
|
+
const [focus, setFocus] = React.useState(null);
|
|
477
|
+
const [sessionId, setSessionId] = React.useState(null);
|
|
195
478
|
const [open, setOpen] = React.useState(false);
|
|
196
479
|
const ref = React.useRef(null);
|
|
197
480
|
const L = React.useMemo(() => getActiveLocale(_ctx), [_ctx]);
|
|
@@ -199,12 +482,15 @@ window.__ModuleLoader__.load({
|
|
|
199
482
|
const fetchStatus = () => fetchLensStatus().then((j) => {
|
|
200
483
|
if (j && j.stats) setStats(j.stats);
|
|
201
484
|
if (j && j.history) setHistory(j.history || []);
|
|
485
|
+
if (j && j.focus) setFocus(j.focus);
|
|
486
|
+
if (j && j.sessionId) setSessionId(j.sessionId);
|
|
202
487
|
}).catch(() => {});
|
|
203
488
|
|
|
489
|
+
const handleClearFocus = () => clearLensFocus(sessionId).then(() => fetchStatus()).catch(() => {});
|
|
490
|
+
|
|
204
491
|
React.useEffect(() => {
|
|
205
492
|
let alive = true;
|
|
206
493
|
fetchStatus();
|
|
207
|
-
// Adaptive polling: poll every 4s when popover is open, 15s when closed, pause when tab hidden
|
|
208
494
|
const pollInterval = open ? 4000 : 15000;
|
|
209
495
|
const id = setInterval(() => {
|
|
210
496
|
if (!alive) return;
|
|
@@ -241,12 +527,12 @@ window.__ModuleLoader__.load({
|
|
|
241
527
|
const warn = !!(stats && stats.lowBudget);
|
|
242
528
|
const hasSavings = !!(stats && stats.savedTokens > 0);
|
|
243
529
|
const label = hasSavings ? `Lens ${stats.savedPercent}%` : 'Lens';
|
|
244
|
-
const color = warn ? '#
|
|
245
|
-
const borderColor = warn ? '#
|
|
530
|
+
const color = warn ? 'var(--dsw-alias-state-error-primary, #ef4444)' : 'var(--dsw-alias-label-secondary)';
|
|
531
|
+
const borderColor = warn ? 'var(--dsw-alias-state-error-primary, #ef4444)' : open ? 'var(--dsw-alias-border-l1)' : 'var(--dsw-alias-border-l2)';
|
|
246
532
|
|
|
247
533
|
const popover = open ? React.createElement('div', {
|
|
248
534
|
style: {
|
|
249
|
-
position: 'absolute', top: 'calc(100% + 6px)', right: 0, width:
|
|
535
|
+
position: 'absolute', top: 'calc(100% + 6px)', right: 0, width: 280,
|
|
250
536
|
background: 'var(--dsw-alias-bg-layer-3)', border: '1px solid var(--dsw-alias-border-l2)',
|
|
251
537
|
borderRadius: 10, boxShadow: '0 8px 24px rgba(0, 0, 0, 0.28)', padding: '12px 14px',
|
|
252
538
|
zIndex: 1000, fontSize: 12, color: 'var(--dsw-alias-label-primary)', cursor: 'default', textAlign: 'left'
|
|
@@ -254,7 +540,7 @@ window.__ModuleLoader__.load({
|
|
|
254
540
|
onClick: (e) => e.stopPropagation()
|
|
255
541
|
},
|
|
256
542
|
React.createElement(StatusPanel, {
|
|
257
|
-
stats, history, labels: L, onRefresh: fetchStatus, title: 'Context Lens', showBadge: true, compact: true
|
|
543
|
+
stats, history, focus, sessionId, labels: L, onRefresh: fetchStatus, onClearFocus: handleClearFocus, title: 'Context Lens', showBadge: true, compact: true
|
|
258
544
|
})
|
|
259
545
|
) : null;
|
|
260
546
|
|
|
@@ -272,16 +558,24 @@ window.__ModuleLoader__.load({
|
|
|
272
558
|
},
|
|
273
559
|
React.createElement('span', { style: { fontSize: 10, opacity: 0.8 } }, '◐'),
|
|
274
560
|
label,
|
|
275
|
-
warn ? React.createElement('span', { style: { color: '#
|
|
561
|
+
warn ? React.createElement('span', { style: { color: 'var(--dsw-alias-state-error-primary, #ef4444)' } }, ' ⚠') : null
|
|
276
562
|
),
|
|
277
563
|
popover
|
|
278
564
|
);
|
|
279
565
|
}
|
|
280
566
|
|
|
281
|
-
function
|
|
567
|
+
function PluginCardInner({ ctx: _ctx, t }) {
|
|
568
|
+
ensureCss();
|
|
282
569
|
const [expanded, setExpanded] = React.useState(false);
|
|
283
|
-
|
|
284
|
-
|
|
570
|
+
const [draft, setDraft] = React.useState({
|
|
571
|
+
compressionMode: 'balanced',
|
|
572
|
+
astSkeletonMaxDepth: 3,
|
|
573
|
+
tokenSavingsTracking: true,
|
|
574
|
+
budgetLimit: 100000,
|
|
575
|
+
budgetAlertPercent: 90,
|
|
576
|
+
autoCollapse: true,
|
|
577
|
+
autoCompressThreshold: 4000
|
|
578
|
+
});
|
|
285
579
|
const [status, setStatus] = React.useState('loading');
|
|
286
580
|
const [saving, setSaving] = React.useState(false);
|
|
287
581
|
const [saveErr, setSaveErr] = React.useState('');
|
|
@@ -293,8 +587,9 @@ window.__ModuleLoader__.load({
|
|
|
293
587
|
const scopeRef = React.useRef(null);
|
|
294
588
|
if (!scopeRef.current && _ctx) {
|
|
295
589
|
try {
|
|
296
|
-
|
|
297
|
-
|
|
590
|
+
const s = (_ctx.get && _ctx.get('lanSettings')) || _ctx.settingsScope;
|
|
591
|
+
if (s && typeof s.bind === 'function') {
|
|
592
|
+
scopeRef.current = s.bind({ namespace: NS });
|
|
298
593
|
}
|
|
299
594
|
} catch (e) { scopeRef.current = null; }
|
|
300
595
|
}
|
|
@@ -303,16 +598,17 @@ window.__ModuleLoader__.load({
|
|
|
303
598
|
React.useEffect(() => {
|
|
304
599
|
if (!scope) { setStatus('unavailable'); return; }
|
|
305
600
|
let cancelled = false;
|
|
306
|
-
|
|
601
|
+
|
|
602
|
+
const loadSnap = async () => {
|
|
307
603
|
try {
|
|
308
|
-
const snap =
|
|
604
|
+
const snap = (typeof scope.getSnapshot === 'function')
|
|
605
|
+
? scope.getSnapshot()
|
|
606
|
+
: (typeof scope.get === 'function' ? await scope.get() : null);
|
|
309
607
|
if (cancelled) return;
|
|
310
|
-
// check snapshot status if available
|
|
311
608
|
if (snap && typeof snap === 'object' && 'status' in snap) {
|
|
312
609
|
if (snap.status === 'loading') { setStatus('loading'); return; }
|
|
313
610
|
if (snap.status === 'unavailable') { setStatus('unavailable'); return; }
|
|
314
611
|
}
|
|
315
|
-
// snap is either values or wrapper with values
|
|
316
612
|
const vals = snap && snap.values ? snap.values : snap;
|
|
317
613
|
if (vals && typeof vals === 'object') {
|
|
318
614
|
setDraft((d) => ({ ...d, ...vals }));
|
|
@@ -321,8 +617,23 @@ window.__ModuleLoader__.load({
|
|
|
321
617
|
} catch (e) {
|
|
322
618
|
if (!cancelled) setStatus('unavailable');
|
|
323
619
|
}
|
|
324
|
-
}
|
|
325
|
-
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
loadSnap();
|
|
623
|
+
|
|
624
|
+
let unsub = null;
|
|
625
|
+
if (typeof scope.subscribe === 'function') {
|
|
626
|
+
try {
|
|
627
|
+
unsub = scope.subscribe(() => {
|
|
628
|
+
if (!cancelled) loadSnap();
|
|
629
|
+
});
|
|
630
|
+
} catch (_) {}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return () => {
|
|
634
|
+
cancelled = true;
|
|
635
|
+
if (typeof unsub === 'function') unsub();
|
|
636
|
+
};
|
|
326
637
|
}, [scope]);
|
|
327
638
|
|
|
328
639
|
React.useEffect(() => {
|
|
@@ -330,7 +641,6 @@ window.__ModuleLoader__.load({
|
|
|
330
641
|
fetch('/dsh-context-lens/status').then((r) => r.ok ? r.json() : null).then((j) => {
|
|
331
642
|
if (j && j.stats) {
|
|
332
643
|
setStats(j.stats);
|
|
333
|
-
// #41: warn instead of force-closing the card
|
|
334
644
|
setBudgetWarn(!!(j.stats.lowBudget && draft.autoCollapse !== false));
|
|
335
645
|
}
|
|
336
646
|
}).catch(() => {});
|
|
@@ -387,106 +697,111 @@ window.__ModuleLoader__.load({
|
|
|
387
697
|
}
|
|
388
698
|
}
|
|
389
699
|
|
|
390
|
-
|
|
391
|
-
return React.createElement('li', { className: 'cl-card', style: { border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-3)', borderRadius: 12, listStyle: 'none' } },
|
|
700
|
+
return React.createElement('li', { className: 'cl-section-card' },
|
|
392
701
|
React.createElement('button', {
|
|
393
|
-
|
|
702
|
+
type: 'button',
|
|
703
|
+
className: 'cl-section-header',
|
|
394
704
|
onClick: () => setExpanded(!expanded),
|
|
395
|
-
'aria-expanded': expanded
|
|
396
|
-
style: { appearance: 'none', width: '100%', font: 'inherit', color: 'inherit', textAlign: 'left', cursor: 'pointer', background: '0 0', border: 0, borderRadius: 12, display: 'flex', alignItems: 'center', gap: 12, padding: '14px 16px' }
|
|
705
|
+
'aria-expanded': expanded
|
|
397
706
|
},
|
|
398
|
-
React.createElement('span', { style: { display: 'flex', flexDirection: 'column' } },
|
|
707
|
+
React.createElement('span', { style: { display: 'flex', flexDirection: 'column', gap: 2 } },
|
|
399
708
|
React.createElement('span', { className: 'cl-title', style: { color: 'var(--dsw-alias-label-primary)', fontSize: 15, fontWeight: 600, lineHeight: 1.4 } }, tt('title')),
|
|
400
709
|
React.createElement('span', { className: 'cl-sub', style: { color: 'var(--dsw-alias-label-secondary)', fontSize: 13 } }, tt('sub') + (stats ? ' · ' + tt('saved') + ' ' + (stats.savedTokens || 0) + ' ' + tt('tokens') + ' (' + (stats.savedPercent || 0) + '%)' : ''))
|
|
401
710
|
),
|
|
402
711
|
React.createElement(Chevron, { open: expanded })
|
|
403
712
|
),
|
|
404
|
-
expanded ? React.createElement('div', { className: 'cl-body', style: { borderTop: '1px solid var(--dsw-alias-border-l2)',
|
|
405
|
-
budgetWarn ? React.createElement('div', {
|
|
713
|
+
expanded ? React.createElement('div', { className: 'cl-body', style: { borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 12 } },
|
|
714
|
+
budgetWarn ? React.createElement('div', { className: 'cl-banner-warn', style: { marginBottom: 12 } }, '⚠ ' + tt('lowBudget')) : null,
|
|
406
715
|
status === 'loading' ? React.createElement('div', { style: { padding: 12, color: 'var(--dsw-alias-label-secondary)' } }, 'Loading…') :
|
|
407
716
|
status === 'unavailable' ? React.createElement('div', { style: { padding: 12, color: 'var(--dsw-alias-label-secondary)' } }, 'Settings unavailable — plugin not registered on host yet') :
|
|
408
717
|
React.createElement(React.Fragment, null,
|
|
409
|
-
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '
|
|
718
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
|
|
410
719
|
React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('mode')),
|
|
411
720
|
React.createElement('select', {
|
|
412
721
|
className: 'cl-input',
|
|
413
722
|
value: draft.compressionMode,
|
|
414
|
-
onChange: (e) => setDraft((d) => ({ ...d, compressionMode: e.target.value }))
|
|
415
|
-
style: { height: 34, border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-3)', color: 'var(--dsw-alias-label-primary)', borderRadius: 8, padding: '0 12px', fontSize: 13 }
|
|
723
|
+
onChange: (e) => setDraft((d) => ({ ...d, compressionMode: e.target.value }))
|
|
416
724
|
},
|
|
417
725
|
React.createElement('option', { value: 'raw' }, 'raw'),
|
|
418
726
|
React.createElement('option', { value: 'balanced' }, 'balanced'),
|
|
419
727
|
React.createElement('option', { value: 'aggressive' }, 'aggressive')
|
|
420
728
|
)
|
|
421
729
|
),
|
|
422
|
-
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '
|
|
730
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
|
|
423
731
|
React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('depth')),
|
|
424
732
|
React.createElement('input', {
|
|
425
733
|
className: 'cl-input',
|
|
426
734
|
type: 'number', min: 1, max: 10,
|
|
427
735
|
value: draft.astSkeletonMaxDepth,
|
|
428
|
-
onChange: (e) => setDraft((d) => ({ ...d, astSkeletonMaxDepth: parseInt(e.target.value, 10) || 3 }))
|
|
429
|
-
style: { height: 34, border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-3)', color: 'var(--dsw-alias-label-primary)', borderRadius: 8, padding: '0 12px', fontSize: 13 }
|
|
736
|
+
onChange: (e) => setDraft((d) => ({ ...d, astSkeletonMaxDepth: parseInt(e.target.value, 10) || 3 }))
|
|
430
737
|
})
|
|
431
738
|
),
|
|
432
|
-
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8, padding: '
|
|
739
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8, padding: '8px 0' } },
|
|
433
740
|
React.createElement('input', {
|
|
434
741
|
type: 'checkbox',
|
|
435
742
|
checked: !!draft.tokenSavingsTracking,
|
|
436
743
|
onChange: (e) => setDraft((d) => ({ ...d, tokenSavingsTracking: e.target.checked })),
|
|
437
744
|
id: 'cl-tracking'
|
|
438
745
|
}),
|
|
439
|
-
React.createElement('label', { htmlFor: 'cl-tracking', style: { fontSize: 13 } }, tt('tracking'))
|
|
746
|
+
React.createElement('label', { htmlFor: 'cl-tracking', style: { fontSize: 13, color: 'var(--dsw-alias-label-primary)' } }, tt('tracking'))
|
|
440
747
|
),
|
|
441
|
-
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8, padding: '
|
|
748
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8, padding: '8px 0' } },
|
|
442
749
|
React.createElement('input', {
|
|
443
750
|
type: 'checkbox',
|
|
444
751
|
checked: !!draft.autoCollapse,
|
|
445
752
|
onChange: (e) => setDraft((d) => ({ ...d, autoCollapse: e.target.checked })),
|
|
446
753
|
id: 'cl-autocollapse'
|
|
447
754
|
}),
|
|
448
|
-
React.createElement('label', { htmlFor: 'cl-autocollapse', style: { fontSize: 13 } }, tt('autoCollapse'))
|
|
755
|
+
React.createElement('label', { htmlFor: 'cl-autocollapse', style: { fontSize: 13, color: 'var(--dsw-alias-label-primary)' } }, tt('autoCollapse'))
|
|
449
756
|
),
|
|
450
|
-
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '
|
|
757
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
|
|
451
758
|
React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('budget') + ' (tokens)'),
|
|
452
759
|
React.createElement('input', {
|
|
453
760
|
className: 'cl-input', type: 'number', min: 1000, step: 1000,
|
|
454
761
|
value: draft.budgetLimit,
|
|
455
|
-
onChange: (e) => setDraft((d) => ({ ...d, budgetLimit: parseInt(e.target.value, 10) || 100000 }))
|
|
456
|
-
|
|
762
|
+
onChange: (e) => setDraft((d) => ({ ...d, budgetLimit: parseInt(e.target.value, 10) || 100000 }))
|
|
763
|
+
})
|
|
764
|
+
),
|
|
765
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
|
|
766
|
+
React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('budgetAlertPercent')),
|
|
767
|
+
React.createElement('input', {
|
|
768
|
+
className: 'cl-input', type: 'number', min: 50, max: 99, step: 1,
|
|
769
|
+
value: draft.budgetAlertPercent,
|
|
770
|
+
onChange: (e) => setDraft((d) => ({ ...d, budgetAlertPercent: parseInt(e.target.value, 10) || 90 }))
|
|
457
771
|
})
|
|
458
772
|
),
|
|
459
|
-
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '
|
|
773
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
|
|
460
774
|
React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('threshold')),
|
|
461
775
|
React.createElement('input', {
|
|
462
776
|
className: 'cl-input', type: 'number', min: 0, step: 500,
|
|
463
777
|
value: draft.autoCompressThreshold,
|
|
464
|
-
onChange: (e) => setDraft((d) => ({ ...d, autoCompressThreshold: parseInt(e.target.value, 10) || 0 }))
|
|
465
|
-
style: { height: 34, border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-3)', color: 'var(--dsw-alias-label-primary)', borderRadius: 8, padding: '0 12px', fontSize: 13 }
|
|
778
|
+
onChange: (e) => setDraft((d) => ({ ...d, autoCompressThreshold: parseInt(e.target.value, 10) || 0 }))
|
|
466
779
|
})
|
|
467
780
|
),
|
|
468
|
-
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '
|
|
781
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
|
|
469
782
|
React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('preview') + ' (' + tt('previewApprox') + ')'),
|
|
470
783
|
React.createElement('textarea', {
|
|
471
784
|
value: previewIn,
|
|
472
785
|
onChange: (e) => setPreviewIn(e.target.value),
|
|
473
786
|
placeholder: tt('placeholder'),
|
|
474
787
|
rows: 5,
|
|
475
|
-
style: { border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-
|
|
788
|
+
style: { border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-primary)', borderRadius: 8, padding: 12, fontSize: 12, fontFamily: 'monospace' }
|
|
476
789
|
}),
|
|
477
790
|
React.createElement('button', {
|
|
791
|
+
type: 'button',
|
|
792
|
+
className: 'cl-btn cl-btn-primary',
|
|
478
793
|
onClick: onPreview,
|
|
479
|
-
style: {
|
|
794
|
+
style: { alignSelf: 'flex-start', marginTop: 4 }
|
|
480
795
|
}, tt('compress')),
|
|
481
|
-
previewOut ? React.createElement('pre', { style: { whiteSpace: 'pre-wrap', fontSize: 12, background: 'var(--dsw-alias-bg-layer-2
|
|
796
|
+
previewOut ? React.createElement('pre', { style: { whiteSpace: 'pre-wrap', fontSize: 12, background: 'var(--dsw-alias-bg-layer-2)', padding: 12, borderRadius: 8, maxHeight: 200, overflow: 'auto', border: '1px solid var(--dsw-alias-border-l2)', marginTop: 8 } }, previewOut) : null
|
|
482
797
|
),
|
|
483
|
-
saveErr ? React.createElement('div', { style: { color: '#
|
|
484
|
-
React.createElement('div', { className: 'cl-foot', style: { borderTop: '1px solid var(--dsw-alias-border-l2)', display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8,
|
|
798
|
+
saveErr ? React.createElement('div', { style: { color: 'var(--dsw-alias-state-error-primary, #ef4444)', fontSize: 12, padding: '4px 0' } }, saveErr) : null,
|
|
799
|
+
React.createElement('div', { className: 'cl-foot', style: { borderTop: '1px solid var(--dsw-alias-border-l2)', display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8, paddingTop: 12 } },
|
|
485
800
|
React.createElement('button', {
|
|
486
|
-
|
|
801
|
+
type: 'button',
|
|
802
|
+
className: 'cl-btn cl-btn-primary' + (saving ? ' cl-btn-disabled' : ''),
|
|
487
803
|
onClick: onSave,
|
|
488
|
-
disabled: saving
|
|
489
|
-
style: { appearance: 'none', font: 'inherit', cursor: 'pointer', border: '1px solid transparent', borderRadius: 8, padding: '5px 14px', fontSize: 13, background: 'var(--dsw-alias-label-primary)', color: 'var(--dsw-alias-bg-layer-3)', opacity: saving ? 0.6 : 1 }
|
|
804
|
+
disabled: saving
|
|
490
805
|
}, saving ? tt('saving') : tt('ready'))
|
|
491
806
|
)
|
|
492
807
|
)
|
|
@@ -494,9 +809,15 @@ window.__ModuleLoader__.load({
|
|
|
494
809
|
);
|
|
495
810
|
}
|
|
496
811
|
|
|
812
|
+
function PluginCard(props) {
|
|
813
|
+
return React.createElement(ErrorBoundary, null,
|
|
814
|
+
React.createElement(PluginCardInner, props)
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
|
|
497
818
|
module.exports.inject = ['slots', 'locale'];
|
|
498
819
|
module.exports.apply = function apply(ctx) {
|
|
499
|
-
try { ctx.locale.register(NS, { en,
|
|
820
|
+
try { ctx.locale.register(NS, { en, zh }); } catch (e) { console.warn('[dsh-context-lens] locale register failed', e && e.message || e); }
|
|
500
821
|
if (!ctx.slots) return;
|
|
501
822
|
let registered = false;
|
|
502
823
|
const doRegister = () => {
|
|
@@ -511,26 +832,22 @@ window.__ModuleLoader__.load({
|
|
|
511
832
|
registered = true;
|
|
512
833
|
return dispose;
|
|
513
834
|
} catch (e) {
|
|
514
|
-
// Do not swallow real registration errors — surface diagnostics
|
|
515
835
|
console.error('[dsh-context-lens] settings.plugin.item register failed', e && e.stack || e);
|
|
516
836
|
throw e;
|
|
517
837
|
}
|
|
518
838
|
};
|
|
519
|
-
// Declaration-aware: wait for host to declare the slot (alpha2 SlotCore throws if not declared)
|
|
520
839
|
if (typeof ctx.slots.inject === 'function') {
|
|
521
840
|
try {
|
|
522
841
|
const ok = ctx.slots.inject('settings.plugin.item', doRegister);
|
|
523
842
|
if (!ok) console.warn('[dsh-context-lens] settings.plugin.item inject returned false — slot not declared yet');
|
|
524
843
|
} catch (e) {
|
|
525
844
|
console.error('[dsh-context-lens] settings.plugin.item inject failed', e && e.stack || e);
|
|
526
|
-
// Fallback for older DSH without inject or if inject itself fails — surface error
|
|
527
845
|
try { doRegister(); } catch (e2) { console.error('[dsh-context-lens] fallback register failed', e2 && e2.stack || e2); throw e2; }
|
|
528
846
|
throw e;
|
|
529
847
|
}
|
|
530
848
|
} else {
|
|
531
849
|
try { doRegister(); } catch (e) { console.error('[dsh-context-lens] direct register failed (no inject)', e && e.stack || e); throw e; }
|
|
532
850
|
}
|
|
533
|
-
// Native DSH Right Sidebar (#54, DSH 0.1.5-alpha.1+)
|
|
534
851
|
if (typeof ctx.inject === "function") {
|
|
535
852
|
try {
|
|
536
853
|
ctx.inject(["sidebarRightTabs"], (sctx) => {
|
|
@@ -555,7 +872,6 @@ window.__ModuleLoader__.load({
|
|
|
555
872
|
tabs.register(def);
|
|
556
873
|
}
|
|
557
874
|
|
|
558
|
-
// Register body in sidebar.right.pane.tab slot
|
|
559
875
|
if (ctx.slots) {
|
|
560
876
|
const registerPaneTab = () => {
|
|
561
877
|
try {
|
|
@@ -584,12 +900,9 @@ window.__ModuleLoader__.load({
|
|
|
584
900
|
console.warn("[dsh-context-lens] native sidebar registration failed", e && e.message || e);
|
|
585
901
|
}
|
|
586
902
|
});
|
|
587
|
-
} catch (e) {
|
|
588
|
-
// host without sidebarRightTabs declared or inject failure
|
|
589
|
-
}
|
|
903
|
+
} catch (e) {}
|
|
590
904
|
}
|
|
591
905
|
|
|
592
|
-
// BetterSidebar tab (optional, for dsh-better-sidebar via ctx.inject)
|
|
593
906
|
if (typeof ctx.inject === 'function') {
|
|
594
907
|
try {
|
|
595
908
|
ctx.inject(['betterSidebar'], (sctx) => {
|
|
@@ -612,11 +925,9 @@ window.__ModuleLoader__.load({
|
|
|
612
925
|
console.warn('[dsh-context-lens] betterSidebar registerTab failed', e && e.message || e);
|
|
613
926
|
}
|
|
614
927
|
});
|
|
615
|
-
} catch (e) {
|
|
616
|
-
// host without betterSidebar declared or inject failure
|
|
617
|
-
}
|
|
928
|
+
} catch (e) {}
|
|
618
929
|
}
|
|
619
|
-
|
|
930
|
+
|
|
620
931
|
if (ctx.slots) {
|
|
621
932
|
const headerChipRegister = () => {
|
|
622
933
|
try {
|