smartcontext-proxy 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/proxy/connect-proxy.d.ts +3 -0
- package/dist/src/proxy/connect-proxy.js +69 -2
- package/dist/src/proxy/server.js +10 -1
- package/dist/src/proxy/transparent-listener.d.ts +31 -0
- package/dist/src/proxy/transparent-listener.js +285 -0
- package/dist/src/system/dns-redirect.d.ts +28 -0
- package/dist/src/system/dns-redirect.js +141 -0
- package/dist/src/system/pf-redirect.d.ts +25 -0
- package/dist/src/system/pf-redirect.js +177 -0
- package/dist/src/test/dashboard.test.js +1 -0
- package/dist/src/ui/dashboard.d.ts +10 -1
- package/dist/src/ui/dashboard.js +119 -34
- package/package.json +1 -1
- package/src/proxy/connect-proxy.ts +67 -3
- package/src/proxy/server.ts +11 -2
- package/src/proxy/transparent-listener.ts +328 -0
- package/src/system/dns-redirect.ts +144 -0
- package/src/system/pf-redirect.ts +175 -0
- package/src/test/dashboard.test.ts +1 -0
- package/src/ui/dashboard.ts +129 -35
package/src/ui/dashboard.ts
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import type { MetricsCollector } from '../metrics/collector.js';
|
|
2
|
-
import type { StorageAdapter } from '../storage/types.js';
|
|
3
2
|
|
|
4
|
-
export
|
|
3
|
+
export interface DashboardState {
|
|
4
|
+
paused: boolean;
|
|
5
|
+
mode: 'transparent' | 'optimizing';
|
|
6
|
+
proxyType: 'connect' | 'legacy';
|
|
7
|
+
abTestEnabled: boolean;
|
|
8
|
+
debugHeaders: boolean;
|
|
9
|
+
caInstalled: boolean;
|
|
10
|
+
systemProxyActive: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function renderDashboard(metrics: MetricsCollector, state: DashboardState): string {
|
|
5
14
|
const stats = metrics.getStats();
|
|
6
15
|
const recent = metrics.getRecent(20);
|
|
7
|
-
const
|
|
8
|
-
const
|
|
16
|
+
const uptimeStr = formatDuration(metrics.getUptime());
|
|
17
|
+
const savingsAmount = estimateCostSaved(stats.totalOriginalTokens - stats.totalOptimizedTokens);
|
|
9
18
|
|
|
10
|
-
const
|
|
19
|
+
const stateBadge = state.paused
|
|
11
20
|
? '<span class="badge paused">PAUSED</span>'
|
|
12
21
|
: '<span class="badge running">RUNNING</span>';
|
|
13
22
|
|
|
14
|
-
const
|
|
23
|
+
const modeBadge = state.mode === 'optimizing'
|
|
24
|
+
? '<span class="badge opt">OPTIMIZING</span>'
|
|
25
|
+
: '<span class="badge transparent">TRANSPARENT</span>';
|
|
15
26
|
|
|
16
27
|
return `<!DOCTYPE html>
|
|
17
28
|
<html lang="en">
|
|
@@ -24,17 +35,23 @@ export function renderDashboard(metrics: MetricsCollector, paused: boolean): str
|
|
|
24
35
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: #0f1117; color: #e1e4e8; }
|
|
25
36
|
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
|
26
37
|
header { display: flex; justify-content: space-between; align-items: center; padding: 16px 0; border-bottom: 1px solid #21262d; margin-bottom: 24px; }
|
|
27
|
-
h1 { font-size: 20px; font-weight: 600; }
|
|
38
|
+
h1 { font-size: 20px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
|
|
28
39
|
h2 { font-size: 16px; font-weight: 600; margin-bottom: 12px; color: #8b949e; }
|
|
29
|
-
.badge { padding: 4px 12px; border-radius: 12px; font-size:
|
|
40
|
+
.badge { padding: 4px 12px; border-radius: 12px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
30
41
|
.badge.running { background: #238636; color: #fff; }
|
|
31
42
|
.badge.paused { background: #d29922; color: #000; }
|
|
32
|
-
.
|
|
33
|
-
.
|
|
34
|
-
.
|
|
43
|
+
.badge.opt { background: #1f6feb; color: #fff; }
|
|
44
|
+
.badge.transparent { background: #30363d; color: #8b949e; }
|
|
45
|
+
.badge.on { background: #238636; color: #fff; }
|
|
46
|
+
.badge.off { background: #30363d; color: #8b949e; }
|
|
47
|
+
.controls { display: flex; gap: 8px; align-items: center; }
|
|
48
|
+
.btn { padding: 6px 16px; border-radius: 6px; border: 1px solid #30363d; background: #21262d; color: #e1e4e8; cursor: pointer; font-size: 13px; transition: all 0.15s; }
|
|
49
|
+
.btn:hover { background: #30363d; border-color: #484f58; }
|
|
35
50
|
.btn.primary { background: #238636; border-color: #238636; }
|
|
51
|
+
.btn.primary:hover { background: #2ea043; }
|
|
36
52
|
.btn.warn { background: #d29922; border-color: #d29922; color: #000; }
|
|
37
|
-
.
|
|
53
|
+
.btn.danger { background: #da3633; border-color: #da3633; }
|
|
54
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
|
38
55
|
.card { background: #161b22; border: 1px solid #21262d; border-radius: 8px; padding: 20px; }
|
|
39
56
|
.card .value { font-size: 32px; font-weight: 700; color: #58a6ff; }
|
|
40
57
|
.card .label { font-size: 13px; color: #8b949e; margin-top: 4px; }
|
|
@@ -50,20 +67,46 @@ export function renderDashboard(metrics: MetricsCollector, paused: boolean): str
|
|
|
50
67
|
.tab.active { color: #e1e4e8; border-bottom-color: #58a6ff; }
|
|
51
68
|
.tab-content { display: none; }
|
|
52
69
|
.tab-content.active { display: block; }
|
|
70
|
+
.settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
71
|
+
.setting { background: #161b22; border: 1px solid #21262d; border-radius: 8px; padding: 16px; display: flex; justify-content: space-between; align-items: center; }
|
|
72
|
+
.setting-info { flex: 1; }
|
|
73
|
+
.setting-name { font-size: 14px; font-weight: 600; margin-bottom: 4px; }
|
|
74
|
+
.setting-desc { font-size: 12px; color: #8b949e; }
|
|
75
|
+
.toggle { position: relative; width: 44px; height: 24px; cursor: pointer; }
|
|
76
|
+
.toggle input { display: none; }
|
|
77
|
+
.toggle .slider { position: absolute; inset: 0; background: #30363d; border-radius: 12px; transition: 0.2s; }
|
|
78
|
+
.toggle .slider:before { content: ''; position: absolute; width: 18px; height: 18px; left: 3px; top: 3px; background: #8b949e; border-radius: 50%; transition: 0.2s; }
|
|
79
|
+
.toggle input:checked + .slider { background: #238636; }
|
|
80
|
+
.toggle input:checked + .slider:before { transform: translateX(20px); background: #fff; }
|
|
81
|
+
.status-row { display: flex; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; }
|
|
82
|
+
.status-pill { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #8b949e; }
|
|
83
|
+
.dot { width: 8px; height: 8px; border-radius: 50%; }
|
|
84
|
+
.dot.green { background: #3fb950; }
|
|
85
|
+
.dot.yellow { background: #d29922; }
|
|
86
|
+
.dot.red { background: #f85149; }
|
|
87
|
+
.dot.gray { background: #484f58; }
|
|
53
88
|
.refresh-note { font-size: 11px; color: #484f58; text-align: right; margin-top: 8px; }
|
|
54
89
|
</style>
|
|
55
90
|
</head>
|
|
56
91
|
<body>
|
|
57
92
|
<div class="container">
|
|
58
93
|
<header>
|
|
59
|
-
<h1>SmartContext Proxy ${
|
|
94
|
+
<h1>SmartContext Proxy ${stateBadge} ${modeBadge}</h1>
|
|
60
95
|
<div class="controls">
|
|
61
|
-
${paused
|
|
96
|
+
${state.paused
|
|
62
97
|
? '<button class="btn primary" onclick="api(\'/_sc/resume\')">Resume</button>'
|
|
63
98
|
: '<button class="btn warn" onclick="api(\'/_sc/pause\')">Pause</button>'}
|
|
64
99
|
</div>
|
|
65
100
|
</header>
|
|
66
101
|
|
|
102
|
+
<div class="status-row">
|
|
103
|
+
<div class="status-pill"><div class="dot ${state.caInstalled ? 'green' : 'red'}"></div>CA Certificate</div>
|
|
104
|
+
<div class="status-pill"><div class="dot ${state.systemProxyActive ? 'green' : 'gray'}"></div>System Proxy</div>
|
|
105
|
+
<div class="status-pill"><div class="dot ${state.mode === 'optimizing' ? 'green' : 'gray'}"></div>Context Optimization</div>
|
|
106
|
+
<div class="status-pill"><div class="dot ${state.abTestEnabled ? 'yellow' : 'gray'}"></div>A/B Test</div>
|
|
107
|
+
<div class="status-pill"><div class="dot ${state.proxyType === 'connect' ? 'green' : 'gray'}"></div>${state.proxyType === 'connect' ? 'Transparent' : 'Legacy'} Mode</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
67
110
|
<div class="grid">
|
|
68
111
|
<div class="card savings">
|
|
69
112
|
<div class="value">$${savingsAmount}</div>
|
|
@@ -84,9 +127,10 @@ export function renderDashboard(metrics: MetricsCollector, paused: boolean): str
|
|
|
84
127
|
</div>
|
|
85
128
|
|
|
86
129
|
<div class="tab-bar">
|
|
87
|
-
<div class="tab active" onclick="switchTab('feed')">Live Feed</div>
|
|
88
|
-
<div class="tab" onclick="switchTab('providers')">By Provider</div>
|
|
89
|
-
<div class="tab" onclick="switchTab('models')">By Model</div>
|
|
130
|
+
<div class="tab active" onclick="switchTab('feed',this)">Live Feed</div>
|
|
131
|
+
<div class="tab" onclick="switchTab('providers',this)">By Provider</div>
|
|
132
|
+
<div class="tab" onclick="switchTab('models',this)">By Model</div>
|
|
133
|
+
<div class="tab" onclick="switchTab('settings',this)">Settings</div>
|
|
90
134
|
</div>
|
|
91
135
|
|
|
92
136
|
<div id="tab-feed" class="tab-content active">
|
|
@@ -116,12 +160,7 @@ export function renderDashboard(metrics: MetricsCollector, paused: boolean): str
|
|
|
116
160
|
<thead><tr><th>Provider</th><th>Requests</th><th>Tokens Saved</th><th>Savings %</th></tr></thead>
|
|
117
161
|
<tbody>
|
|
118
162
|
${Object.entries(stats.byProvider).map(([name, s]) => `
|
|
119
|
-
<tr>
|
|
120
|
-
<td>${name}</td>
|
|
121
|
-
<td class="mono">${s.requests}</td>
|
|
122
|
-
<td class="mono">${formatTokens(s.tokensSaved)}</td>
|
|
123
|
-
<td class="savings-pct">${s.savingsPercent}%</td>
|
|
124
|
-
</tr>
|
|
163
|
+
<tr><td>${name}</td><td class="mono">${s.requests}</td><td class="mono">${formatTokens(s.tokensSaved)}</td><td class="savings-pct">${s.savingsPercent}%</td></tr>
|
|
125
164
|
`).join('')}
|
|
126
165
|
</tbody>
|
|
127
166
|
</table>
|
|
@@ -133,31 +172,88 @@ export function renderDashboard(metrics: MetricsCollector, paused: boolean): str
|
|
|
133
172
|
<thead><tr><th>Model</th><th>Requests</th><th>Tokens Saved</th><th>Savings %</th></tr></thead>
|
|
134
173
|
<tbody>
|
|
135
174
|
${Object.entries(stats.byModel).map(([name, s]) => `
|
|
136
|
-
<tr>
|
|
137
|
-
<td>${name}</td>
|
|
138
|
-
<td class="mono">${s.requests}</td>
|
|
139
|
-
<td class="mono">${formatTokens(s.tokensSaved)}</td>
|
|
140
|
-
<td class="savings-pct">${s.savingsPercent}%</td>
|
|
141
|
-
</tr>
|
|
175
|
+
<tr><td>${name}</td><td class="mono">${s.requests}</td><td class="mono">${formatTokens(s.tokensSaved)}</td><td class="savings-pct">${s.savingsPercent}%</td></tr>
|
|
142
176
|
`).join('')}
|
|
143
177
|
</tbody>
|
|
144
178
|
</table>
|
|
145
179
|
</div>
|
|
146
180
|
|
|
147
|
-
<div
|
|
181
|
+
<div id="tab-settings" class="tab-content">
|
|
182
|
+
<h2>Controls</h2>
|
|
183
|
+
<div class="settings-grid">
|
|
184
|
+
<div class="setting">
|
|
185
|
+
<div class="setting-info">
|
|
186
|
+
<div class="setting-name">Context Optimization</div>
|
|
187
|
+
<div class="setting-desc">Optimize LLM context windows to reduce token usage</div>
|
|
188
|
+
</div>
|
|
189
|
+
<label class="toggle">
|
|
190
|
+
<input type="checkbox" ${!state.paused ? 'checked' : ''} onchange="api(this.checked ? '/_sc/resume' : '/_sc/pause')">
|
|
191
|
+
<span class="slider"></span>
|
|
192
|
+
</label>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div class="setting">
|
|
196
|
+
<div class="setting-info">
|
|
197
|
+
<div class="setting-name">A/B Test Mode</div>
|
|
198
|
+
<div class="setting-desc">Send each request twice to compare quality</div>
|
|
199
|
+
</div>
|
|
200
|
+
<label class="toggle">
|
|
201
|
+
<input type="checkbox" ${state.abTestEnabled ? 'checked' : ''} onchange="api(this.checked ? '/_sc/ab-test/enable' : '/_sc/ab-test/disable')">
|
|
202
|
+
<span class="slider"></span>
|
|
203
|
+
</label>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div class="setting">
|
|
207
|
+
<div class="setting-info">
|
|
208
|
+
<div class="setting-name">Debug Headers</div>
|
|
209
|
+
<div class="setting-desc">Add X-SmartContext-* headers to responses</div>
|
|
210
|
+
</div>
|
|
211
|
+
<label class="toggle">
|
|
212
|
+
<input type="checkbox" ${state.debugHeaders ? 'checked' : ''} onchange="api(this.checked ? '/_sc/debug-headers/enable' : '/_sc/debug-headers/disable')">
|
|
213
|
+
<span class="slider"></span>
|
|
214
|
+
</label>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<div class="setting">
|
|
218
|
+
<div class="setting-info">
|
|
219
|
+
<div class="setting-name">System Proxy</div>
|
|
220
|
+
<div class="setting-desc">Auto-intercept all LLM traffic system-wide</div>
|
|
221
|
+
</div>
|
|
222
|
+
<label class="toggle">
|
|
223
|
+
<input type="checkbox" ${state.systemProxyActive ? 'checked' : ''} onchange="api(this.checked ? '/_sc/system-proxy/enable' : '/_sc/system-proxy/disable')">
|
|
224
|
+
<span class="slider"></span>
|
|
225
|
+
</label>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<div class="refresh-note">v0.2.0 | Uptime: ${uptimeStr} | Auto-refresh: 5s</div>
|
|
148
231
|
</div>
|
|
149
232
|
<script>
|
|
150
|
-
function switchTab(name) {
|
|
233
|
+
function switchTab(name, el) {
|
|
151
234
|
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
|
|
152
235
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
153
236
|
document.getElementById('tab-' + name).classList.add('active');
|
|
154
|
-
|
|
237
|
+
if (el) el.classList.add('active');
|
|
238
|
+
location.hash = name;
|
|
155
239
|
}
|
|
156
240
|
async function api(path) {
|
|
157
241
|
await fetch(path, { method: 'POST' });
|
|
158
242
|
location.reload();
|
|
159
243
|
}
|
|
160
|
-
|
|
244
|
+
// Restore tab from URL hash
|
|
245
|
+
(function() {
|
|
246
|
+
var hash = location.hash.replace('#', '');
|
|
247
|
+
if (hash && document.getElementById('tab-' + hash)) {
|
|
248
|
+
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
|
|
249
|
+
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
250
|
+
document.getElementById('tab-' + hash).classList.add('active');
|
|
251
|
+
document.querySelectorAll('.tab').forEach(t => {
|
|
252
|
+
if (t.getAttribute('onclick') && t.getAttribute('onclick').indexOf("'" + hash + "'") !== -1) t.classList.add('active');
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
})();
|
|
256
|
+
setTimeout(() => { var h = location.hash; location.reload(); if (h) location.hash = h; }, 5000);
|
|
161
257
|
</script>
|
|
162
258
|
</body>
|
|
163
259
|
</html>`;
|
|
@@ -176,9 +272,7 @@ function formatDuration(ms: number): string {
|
|
|
176
272
|
return `${Math.floor(s / 3600)}h ${Math.floor((s % 3600) / 60)}m`;
|
|
177
273
|
}
|
|
178
274
|
|
|
179
|
-
/** Rough cost estimate based on Anthropic/OpenAI pricing */
|
|
180
275
|
function estimateCostSaved(tokensSaved: number): string {
|
|
181
|
-
// Assume avg $15/1M input tokens (Opus pricing)
|
|
182
276
|
const cost = (tokensSaved / 1000000) * 15;
|
|
183
277
|
return cost.toFixed(2);
|
|
184
278
|
}
|