ctxoptimizer 0.4.1__py3-none-any.whl
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.
- ctxoptimizer/__init__.py +5 -0
- ctxoptimizer/__main__.py +14 -0
- ctxoptimizer/admin_security.py +130 -0
- ctxoptimizer/analysis/__init__.py +1 -0
- ctxoptimizer/analysis/baseline.py +69 -0
- ctxoptimizer/analysis/break_even.py +49 -0
- ctxoptimizer/analysis/drift.py +151 -0
- ctxoptimizer/analysis/edits.py +148 -0
- ctxoptimizer/analysis/markers.py +34 -0
- ctxoptimizer/analysis/recommend.py +114 -0
- ctxoptimizer/analysis/segments.py +57 -0
- ctxoptimizer/analysis/session.py +62 -0
- ctxoptimizer/analysis/state.py +239 -0
- ctxoptimizer/analysis/tokens.py +62 -0
- ctxoptimizer/analysis/waste.py +322 -0
- ctxoptimizer/cli.py +873 -0
- ctxoptimizer/config.py +512 -0
- ctxoptimizer/dashboard/__init__.py +1 -0
- ctxoptimizer/dashboard/api.py +537 -0
- ctxoptimizer/dashboard/service.py +157 -0
- ctxoptimizer/dashboard/static/.gitkeep +1 -0
- ctxoptimizer/dashboard/templates/.gitkeep +1 -0
- ctxoptimizer/dashboard/templates/doctor.html +46 -0
- ctxoptimizer/dashboard/templates/live.html +91 -0
- ctxoptimizer/dashboard/templates/overview.html +172 -0
- ctxoptimizer/dashboard/templates/pricing.html +115 -0
- ctxoptimizer/dashboard/templates/request.html +111 -0
- ctxoptimizer/dashboard/templates/session.html +67 -0
- ctxoptimizer/dashboard/templates/settings.html +79 -0
- ctxoptimizer/dashboard/templates/table.html +56 -0
- ctxoptimizer/dashboard/templates/transforms.html +118 -0
- ctxoptimizer/dashboard/templates/waste.html +120 -0
- ctxoptimizer/dashboard/views.py +429 -0
- ctxoptimizer/data/cache_limits.json +23 -0
- ctxoptimizer/data/default_config.toml +185 -0
- ctxoptimizer/data/pricing.json +421 -0
- ctxoptimizer/doctor.py +129 -0
- ctxoptimizer/guard.py +139 -0
- ctxoptimizer/ids.py +73 -0
- ctxoptimizer/log.py +73 -0
- ctxoptimizer/mcp.py +124 -0
- ctxoptimizer/models.py +145 -0
- ctxoptimizer/pipeline.py +126 -0
- ctxoptimizer/profiles.py +152 -0
- ctxoptimizer/providers/__init__.py +37 -0
- ctxoptimizer/providers/anthropic.py +105 -0
- ctxoptimizer/providers/base.py +191 -0
- ctxoptimizer/providers/gemini.py +143 -0
- ctxoptimizer/providers/ollama.py +133 -0
- ctxoptimizer/providers/openai_chat.py +135 -0
- ctxoptimizer/providers/openai_responses.py +121 -0
- ctxoptimizer/proxy.py +665 -0
- ctxoptimizer/routing.py +259 -0
- ctxoptimizer/server.py +319 -0
- ctxoptimizer/service.py +150 -0
- ctxoptimizer/telemetry/__init__.py +1 -0
- ctxoptimizer/telemetry/blobs.py +57 -0
- ctxoptimizer/telemetry/expand.py +62 -0
- ctxoptimizer/telemetry/pricing.py +168 -0
- ctxoptimizer/telemetry/redact.py +53 -0
- ctxoptimizer/telemetry/sse.py +145 -0
- ctxoptimizer/telemetry/store.py +829 -0
- ctxoptimizer/telemetry/usage.py +435 -0
- ctxoptimizer/transforms/__init__.py +87 -0
- ctxoptimizer/transforms/ansi.py +21 -0
- ctxoptimizer/transforms/anthropic_cache.py +100 -0
- ctxoptimizer/transforms/backref.py +74 -0
- ctxoptimizer/transforms/base.py +93 -0
- ctxoptimizer/transforms/cache_serve.py +146 -0
- ctxoptimizer/transforms/dedupe_lines.py +69 -0
- ctxoptimizer/transforms/eligibility.py +24 -0
- ctxoptimizer/transforms/openai_hints.py +84 -0
- ctxoptimizer/transforms/progress.py +52 -0
- ctxoptimizer/transforms/stale_result_elision.py +109 -0
- ctxoptimizer/transforms/truncate.py +100 -0
- ctxoptimizer/transforms/volatile.py +177 -0
- ctxoptimizer/transforms/whitespace.py +25 -0
- ctxoptimizer-0.4.1.dist-info/METADATA +196 -0
- ctxoptimizer-0.4.1.dist-info/RECORD +83 -0
- ctxoptimizer-0.4.1.dist-info/WHEEL +4 -0
- ctxoptimizer-0.4.1.dist-info/entry_points.txt +3 -0
- ctxoptimizer-0.4.1.dist-info/licenses/LICENSE +21 -0
- ctxoptimizer-0.4.1.dist-info/licenses/NOTICE +13 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>ctxoptimizer — {{ title }}</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: dark; }
|
|
9
|
+
body { margin: 0; padding: 28px; background: #0d1117; color: #e6edf3;
|
|
10
|
+
font-family: 'Segoe UI', system-ui, sans-serif; }
|
|
11
|
+
nav { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 22px; font-size: .88rem; }
|
|
12
|
+
nav a, a { color: #60a5fa; text-decoration: none; }
|
|
13
|
+
h2 { margin: 26px 0 10px; font-size: 1.05rem; color: #93c5fd; }
|
|
14
|
+
.muted { color: #9aa7b4; font-size: .9rem; }
|
|
15
|
+
.banner { background: #3b2300; border: 1px solid #a16207; color: #fde68a;
|
|
16
|
+
border-radius: 8px; padding: 10px 14px; margin: 14px 0; }
|
|
17
|
+
.card { background: #161b22; border: 1px solid #2d3644; border-radius: 10px;
|
|
18
|
+
padding: 14px 16px; margin-bottom: 10px; }
|
|
19
|
+
pre { background: #161b22; border: 1px solid #2d3644; border-radius: 10px;
|
|
20
|
+
padding: 16px; overflow: auto; font-size: .82rem; color: #a5d6ff; }
|
|
21
|
+
button { background: #1f6feb; color: white; border: 0; border-radius: 6px;
|
|
22
|
+
padding: 7px 12px; cursor: pointer; }
|
|
23
|
+
#status { color: #a5d6ff; margin-top: 8px; }
|
|
24
|
+
</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<nav>
|
|
28
|
+
<a href="/_ctxoptimizer/">Overview</a><a href="/_ctxoptimizer/live">Live</a>
|
|
29
|
+
<a href="/_ctxoptimizer/sessions">Sessions</a><a href="/_ctxoptimizer/requests">Requests</a>
|
|
30
|
+
<a href="/_ctxoptimizer/waste">Waste</a><a href="/_ctxoptimizer/transforms">Transforms</a>
|
|
31
|
+
<a href="/_ctxoptimizer/pricing">Pricing</a><a href="/_ctxoptimizer/settings">Settings</a>
|
|
32
|
+
<a href="/_ctxoptimizer/doctor">Doctor</a>
|
|
33
|
+
</nav>
|
|
34
|
+
<h1>{{ title }}</h1>
|
|
35
|
+
<p class="muted">v{{ version }} · local only</p>
|
|
36
|
+
{% if privacy %}
|
|
37
|
+
<div class="banner">Privacy: payload capture is <b>{{ data["capture_payloads"] }}</b>.
|
|
38
|
+
Captured text stays in the local data dir; use <code>ctxoptimizer purge</code> to wipe it.</div>
|
|
39
|
+
{% endif %}
|
|
40
|
+
|
|
41
|
+
<h2>Mode</h2>
|
|
42
|
+
<div class="card">
|
|
43
|
+
<p>Current: <b>{{ data["mode"] }}</b></p>
|
|
44
|
+
<select id="mode">
|
|
45
|
+
{% for option in ("passthrough", "shadow", "active") %}
|
|
46
|
+
<option value="{{ option }}" {% if data["mode"] == option %}selected{% endif %}>{{ option }}</option>
|
|
47
|
+
{% endfor %}
|
|
48
|
+
</select>
|
|
49
|
+
<button id="save-mode">Apply</button>
|
|
50
|
+
<div id="status"></div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<h2>License</h2>
|
|
54
|
+
<div class="card">
|
|
55
|
+
<p><b>MIT License</b> · free and open source</p>
|
|
56
|
+
<p class="muted">See LICENSE and NOTICE in the source repository.</p>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<h2>Effective configuration</h2>
|
|
60
|
+
<pre>{{ data | tojson(indent=2) }}</pre>
|
|
61
|
+
|
|
62
|
+
<script>
|
|
63
|
+
function csrf() {
|
|
64
|
+
const match = document.cookie.match(/(?:^|; )ctxoptimizer_csrf=([^;]+)/);
|
|
65
|
+
return match ? decodeURIComponent(match[1]) : "";
|
|
66
|
+
}
|
|
67
|
+
document.getElementById("save-mode").addEventListener("click", async () => {
|
|
68
|
+
const response = await fetch("/_ctxoptimizer/api/mode", {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: {"content-type": "application/json", "x-ctxoptimizer-csrf": csrf()},
|
|
71
|
+
body: JSON.stringify({mode: document.getElementById("mode").value}),
|
|
72
|
+
});
|
|
73
|
+
const body = await response.json();
|
|
74
|
+
document.getElementById("status").textContent =
|
|
75
|
+
response.ok ? ("Mode set to " + body.mode + ".") : ("Error: " + (body.error || response.status));
|
|
76
|
+
});
|
|
77
|
+
</script>
|
|
78
|
+
</body>
|
|
79
|
+
</html>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>ctxoptimizer — {{ title }}</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: dark; }
|
|
9
|
+
* { box-sizing: border-box; }
|
|
10
|
+
body { margin: 0; padding: 28px; background: #0d1117; color: #e6edf3;
|
|
11
|
+
font-family: 'Segoe UI', system-ui, sans-serif; }
|
|
12
|
+
h1 { margin: 0 0 4px; font-size: 1.5rem; }
|
|
13
|
+
.muted { color: #9aa7b4; font-size: .9rem; }
|
|
14
|
+
nav { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 22px;
|
|
15
|
+
font-size: .88rem; }
|
|
16
|
+
nav a { color: #60a5fa; text-decoration: none; }
|
|
17
|
+
table { width: 100%; border-collapse: collapse; font-size: .87rem;
|
|
18
|
+
background: #161b22; border: 1px solid #2d3644; border-radius: 10px; }
|
|
19
|
+
th, td { padding: 8px 10px; text-align: left; border-bottom: 1px solid #2d3644; }
|
|
20
|
+
th { color: #9aa7b4; font-size: .72rem; text-transform: uppercase; }
|
|
21
|
+
tr:last-child td { border-bottom: none; }
|
|
22
|
+
td { overflow-wrap: anywhere; }
|
|
23
|
+
</style>
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
<nav>
|
|
27
|
+
<a href="/_ctxoptimizer/">Overview</a>
|
|
28
|
+
<a href="/_ctxoptimizer/live">Live</a>
|
|
29
|
+
<a href="/_ctxoptimizer/sessions">Sessions</a>
|
|
30
|
+
<a href="/_ctxoptimizer/requests">Requests</a>
|
|
31
|
+
<a href="/_ctxoptimizer/waste">Waste</a>
|
|
32
|
+
<a href="/_ctxoptimizer/transforms">Transforms</a>
|
|
33
|
+
<a href="/_ctxoptimizer/pricing">Pricing</a>
|
|
34
|
+
<a href="/_ctxoptimizer/settings">Settings</a>
|
|
35
|
+
<a href="/_ctxoptimizer/doctor">Doctor</a>
|
|
36
|
+
</nav>
|
|
37
|
+
<h1>{{ title }}</h1>
|
|
38
|
+
<p class="muted">{{ subtitle }} · v{{ version }} · mode {{ mode }}</p>
|
|
39
|
+
<table>
|
|
40
|
+
<thead>
|
|
41
|
+
<tr>{% for key, label in columns %}<th>{{ label }}</th>{% endfor %}</tr>
|
|
42
|
+
</thead>
|
|
43
|
+
<tbody>
|
|
44
|
+
{% for row in rows %}
|
|
45
|
+
<tr>
|
|
46
|
+
{% for key, label in columns %}
|
|
47
|
+
<td>{{ row[key] if row[key] is not none else "—" }}</td>
|
|
48
|
+
{% endfor %}
|
|
49
|
+
</tr>
|
|
50
|
+
{% else %}
|
|
51
|
+
<tr><td colspan="{{ columns|length }}" class="muted">No data yet.</td></tr>
|
|
52
|
+
{% endfor %}
|
|
53
|
+
</tbody>
|
|
54
|
+
</table>
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>ctxoptimizer — Transforms</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: dark; }
|
|
9
|
+
body { margin: 0; padding: 28px; background: #0d1117; color: #e6edf3;
|
|
10
|
+
font-family: 'Segoe UI', system-ui, sans-serif; }
|
|
11
|
+
nav { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 22px; font-size: .88rem; }
|
|
12
|
+
nav a, a { color: #60a5fa; text-decoration: none; }
|
|
13
|
+
h2 { margin: 26px 0 10px; font-size: 1.05rem; color: #93c5fd; }
|
|
14
|
+
.muted { color: #9aa7b4; font-size: .9rem; }
|
|
15
|
+
table { width: 100%; border-collapse: collapse; font-size: .84rem;
|
|
16
|
+
background: #161b22; border: 1px solid #2d3644; border-radius: 10px; }
|
|
17
|
+
th, td { padding: 7px 9px; text-align: left; border-bottom: 1px solid #2d3644; }
|
|
18
|
+
th { color: #9aa7b4; font-size: .72rem; text-transform: uppercase; }
|
|
19
|
+
tr:last-child td { border-bottom: none; }
|
|
20
|
+
input[type=number] { width: 100px; background: #0d1117; color: #e6edf3;
|
|
21
|
+
border: 1px solid #2d3644; border-radius: 6px; padding: 4px; }
|
|
22
|
+
button { background: #1f6feb; color: white; border: 0; border-radius: 6px;
|
|
23
|
+
padding: 6px 10px; cursor: pointer; }
|
|
24
|
+
#result { margin-top: 8px; color: #a5d6ff; }
|
|
25
|
+
</style>
|
|
26
|
+
</head>
|
|
27
|
+
<body>
|
|
28
|
+
<nav>
|
|
29
|
+
<a href="/_ctxoptimizer/">Overview</a><a href="/_ctxoptimizer/live">Live</a>
|
|
30
|
+
<a href="/_ctxoptimizer/sessions">Sessions</a><a href="/_ctxoptimizer/requests">Requests</a>
|
|
31
|
+
<a href="/_ctxoptimizer/waste">Waste</a><a href="/_ctxoptimizer/transforms">Transforms</a>
|
|
32
|
+
<a href="/_ctxoptimizer/pricing">Pricing</a><a href="/_ctxoptimizer/settings">Settings</a>
|
|
33
|
+
<a href="/_ctxoptimizer/doctor">Doctor</a>
|
|
34
|
+
</nav>
|
|
35
|
+
<h1>Transforms</h1>
|
|
36
|
+
<p class="muted">Toggles apply to this process (hot reload). Stats are from persisted reports.</p>
|
|
37
|
+
|
|
38
|
+
<table>
|
|
39
|
+
<thead><tr><th>Transform</th><th>Enabled</th><th>Quarantined</th><th>Runs</th>
|
|
40
|
+
<th>Tokens saved</th><th>Avg ms</th><th>Errors</th><th>No-regress</th>
|
|
41
|
+
<th>Fallbacks</th></tr></thead>
|
|
42
|
+
<tbody>
|
|
43
|
+
{% for row in rows %}
|
|
44
|
+
<tr>
|
|
45
|
+
<td>{{ row["transform"] }}</td>
|
|
46
|
+
<td><input type="checkbox" data-transform="{{ row['transform'] }}"
|
|
47
|
+
{% if row["enabled"] %}checked{% endif %}></td>
|
|
48
|
+
<td>{{ "yes" if row["quarantined"] else "no" }}</td>
|
|
49
|
+
<td>{{ row["runs"] }}</td><td>{{ row["tokens_saved"] }}</td>
|
|
50
|
+
<td>{{ row["avg_ms"] }}</td><td>{{ row["errors"] }}</td><td>{{ row["no_regress"] }}</td>
|
|
51
|
+
<td>{{ row["fallbacks"] }}</td>
|
|
52
|
+
</tr>
|
|
53
|
+
{% endfor %}
|
|
54
|
+
</tbody>
|
|
55
|
+
</table>
|
|
56
|
+
<p class="muted" id="toggle-status"></p>
|
|
57
|
+
|
|
58
|
+
<h2>Stale-result elision break-even <span class="muted">sim.</span></h2>
|
|
59
|
+
<p class="muted">Elided tokens and batch turns; assumes input-rate savings and one
|
|
60
|
+
cache-miss per boundary. Conservative estimate only.</p>
|
|
61
|
+
<form id="break-even">
|
|
62
|
+
<input type="number" id="tokens" value="8192" min="1"> elided tokens
|
|
63
|
+
<input type="number" id="batch" value="8" min="1"> batch turns
|
|
64
|
+
<input type="number" id="input" value="3.0" step="0.01" min="0"> input $/Mtok
|
|
65
|
+
<input type="number" id="read" value="0.3" step="0.01" min="0"> cache-read $/Mtok
|
|
66
|
+
<button type="submit">Estimate</button>
|
|
67
|
+
</form>
|
|
68
|
+
<div id="result"></div>
|
|
69
|
+
|
|
70
|
+
<h2>Quarantine history</h2>
|
|
71
|
+
<p class="muted">MCP expand usage: {{ expand["total"] }} retrieval(s),
|
|
72
|
+
{{ expand["errors"] }} error(s).</p>
|
|
73
|
+
<table>
|
|
74
|
+
<thead><tr><th>Time</th><th>Session</th><th>Transform</th><th>Reason</th></tr></thead>
|
|
75
|
+
<tbody>
|
|
76
|
+
{% for event in guard_events %}
|
|
77
|
+
<tr><td>{{ event["ts"]|round(0) }}</td><td>{{ event["session_id"] or "—" }}</td>
|
|
78
|
+
<td>{{ event["transform_id"] }}</td><td>{{ event["reason"] }}</td></tr>
|
|
79
|
+
{% else %}
|
|
80
|
+
<tr><td colspan="4" class="muted">No quarantine events.</td></tr>
|
|
81
|
+
{% endfor %}
|
|
82
|
+
</tbody>
|
|
83
|
+
</table>
|
|
84
|
+
|
|
85
|
+
<script>
|
|
86
|
+
function csrf() {
|
|
87
|
+
const match = document.cookie.match(/(?:^|; )ctxoptimizer_csrf=([^;]+)/);
|
|
88
|
+
return match ? decodeURIComponent(match[1]) : "";
|
|
89
|
+
}
|
|
90
|
+
async function patchConfig(patch) {
|
|
91
|
+
const response = await fetch("/_ctxoptimizer/api/config", {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: {"content-type": "application/json", "x-ctxoptimizer-csrf": csrf()},
|
|
94
|
+
body: JSON.stringify(patch),
|
|
95
|
+
});
|
|
96
|
+
const body = await response.json();
|
|
97
|
+
document.getElementById("toggle-status").textContent =
|
|
98
|
+
response.ok ? "Saved." : ("Error: " + (body.error || response.status));
|
|
99
|
+
}
|
|
100
|
+
document.querySelectorAll("input[data-transform]").forEach((box) => {
|
|
101
|
+
box.addEventListener("change", () => {
|
|
102
|
+
patchConfig({transforms: {[box.dataset.transform]: {enabled: box.checked}}});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
document.getElementById("break-even").addEventListener("submit", async (event) => {
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
const params = new URLSearchParams({
|
|
108
|
+
elided_tokens: document.getElementById("tokens").value,
|
|
109
|
+
batch_turns: document.getElementById("batch").value,
|
|
110
|
+
input_per_mtok: document.getElementById("input").value,
|
|
111
|
+
cache_read_per_mtok: document.getElementById("read").value,
|
|
112
|
+
});
|
|
113
|
+
const response = await fetch("/_ctxoptimizer/api/stale-elision/estimate?" + params);
|
|
114
|
+
document.getElementById("result").textContent = JSON.stringify(await response.json(), null, 2);
|
|
115
|
+
});
|
|
116
|
+
</script>
|
|
117
|
+
</body>
|
|
118
|
+
</html>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>ctxoptimizer — Waste</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: dark; }
|
|
9
|
+
* { box-sizing: border-box; }
|
|
10
|
+
body { margin: 0; padding: 28px; background: #0d1117; color: #e6edf3;
|
|
11
|
+
font-family: 'Segoe UI', system-ui, sans-serif; }
|
|
12
|
+
nav { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 22px; font-size: .88rem; }
|
|
13
|
+
nav a, a { color: #60a5fa; text-decoration: none; }
|
|
14
|
+
h1 { margin: 0 0 4px; font-size: 1.6rem; }
|
|
15
|
+
h2 { margin: 28px 0 10px; font-size: 1.1rem; color: #93c5fd; }
|
|
16
|
+
.muted { color: #9aa7b4; font-size: .9rem; }
|
|
17
|
+
table { width: 100%; border-collapse: collapse; font-size: .86rem;
|
|
18
|
+
background: #161b22; border: 1px solid #2d3644; border-radius: 10px; }
|
|
19
|
+
th, td { padding: 8px 10px; text-align: left; border-bottom: 1px solid #2d3644; }
|
|
20
|
+
th { color: #9aa7b4; font-size: .75rem; text-transform: uppercase; }
|
|
21
|
+
tr:last-child td { border-bottom: none; }
|
|
22
|
+
.est { color: #fcd34d; font-weight: 600; }
|
|
23
|
+
.card { background: #161b22; border: 1px solid #2d3644; border-radius: 10px;
|
|
24
|
+
padding: 14px 16px; margin-bottom: 10px; }
|
|
25
|
+
button { background: #1f6feb; color: white; border: 0; border-radius: 6px;
|
|
26
|
+
padding: 4px 8px; cursor: pointer; font-size: .78rem; }
|
|
27
|
+
code { color: #a5d6ff; }
|
|
28
|
+
#status { color: #a5d6ff; margin-top: 8px; }
|
|
29
|
+
</style>
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<nav>
|
|
33
|
+
<a href="/_ctxoptimizer/">Overview</a><a href="/_ctxoptimizer/live">Live</a>
|
|
34
|
+
<a href="/_ctxoptimizer/sessions">Sessions</a><a href="/_ctxoptimizer/requests">Requests</a>
|
|
35
|
+
<a href="/_ctxoptimizer/waste">Waste</a><a href="/_ctxoptimizer/transforms">Transforms</a>
|
|
36
|
+
<a href="/_ctxoptimizer/pricing">Pricing</a><a href="/_ctxoptimizer/settings">Settings</a>
|
|
37
|
+
<a href="/_ctxoptimizer/doctor">Doctor</a>
|
|
38
|
+
</nav>
|
|
39
|
+
<h1>Waste Opportunities</h1>
|
|
40
|
+
<p class="muted">v{{ version }} · mode {{ mode }} · advisory only, never auto-applied</p>
|
|
41
|
+
|
|
42
|
+
<h2>Recommendations <span class="est">sim.</span></h2>
|
|
43
|
+
{% for item in recommendations %}
|
|
44
|
+
<div class="card">
|
|
45
|
+
<b>{{ item["title"] }}</b> <span class="est">{{ item["label"] }}</span><br>
|
|
46
|
+
<span class="muted">{{ item["evidence"] }} · {{ item["action"] }}</span><br>
|
|
47
|
+
<button class="apply" data-id="{{ item['id'] }}">Apply / confirm</button>
|
|
48
|
+
<button class="dismiss" data-id="{{ item['id'] }}">Dismiss 30d</button>
|
|
49
|
+
</div>
|
|
50
|
+
{% else %}
|
|
51
|
+
<p class="muted">No recommendations yet. Run some traffic through the proxy.</p>
|
|
52
|
+
{% endfor %}
|
|
53
|
+
<div id="status"></div>
|
|
54
|
+
|
|
55
|
+
<h2>Findings <span class="est">est.</span></h2>
|
|
56
|
+
<table>
|
|
57
|
+
<thead>
|
|
58
|
+
<tr><th>Detector</th><th>Session</th><th>Project</th><th>Occurrences</th>
|
|
59
|
+
<th>Est. tokens</th><th>Est. USD</th><th>Status</th><th>Evidence</th><th>Actions</th></tr>
|
|
60
|
+
</thead>
|
|
61
|
+
<tbody>
|
|
62
|
+
{% for finding in findings %}
|
|
63
|
+
<tr>
|
|
64
|
+
<td>{{ finding["detector"] }}</td>
|
|
65
|
+
<td><a href="/_ctxoptimizer/sessions/{{ finding['session_id'] }}">{{ finding["session_id"] or "—" }}</a></td>
|
|
66
|
+
<td>{{ finding["project"] or "—" }}</td>
|
|
67
|
+
<td>{{ finding["occurrences"] }}</td>
|
|
68
|
+
<td class="est">{{ finding["est_tokens_wasted"] if finding["est_tokens_wasted"] is not none else "—" }}</td>
|
|
69
|
+
<td class="est">{% if finding["est_cost_usd"] is not none %}${{ "%.6f"|format(finding["est_cost_usd"]) }}{% else %}—{% endif %}</td>
|
|
70
|
+
<td>{{ finding["status"] }}</td>
|
|
71
|
+
<td><code>{{ finding["evidence_json"] }}</code></td>
|
|
72
|
+
<td>
|
|
73
|
+
{% for status in ("ack", "skipped", "fixed") %}
|
|
74
|
+
<button class="status" data-id="{{ finding['finding_id'] }}"
|
|
75
|
+
data-status="{{ status }}">{{ status }}</button>
|
|
76
|
+
{% endfor %}
|
|
77
|
+
</td>
|
|
78
|
+
</tr>
|
|
79
|
+
{% else %}
|
|
80
|
+
<tr><td colspan="9" class="muted">No findings yet.</td></tr>
|
|
81
|
+
{% endfor %}
|
|
82
|
+
</tbody>
|
|
83
|
+
</table>
|
|
84
|
+
|
|
85
|
+
<script>
|
|
86
|
+
function csrf() {
|
|
87
|
+
const match = document.cookie.match(/(?:^|; )ctxoptimizer_csrf=([^;]+)/);
|
|
88
|
+
return match ? decodeURIComponent(match[1]) : "";
|
|
89
|
+
}
|
|
90
|
+
async function post(url, body) {
|
|
91
|
+
const response = await fetch(url, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: {"content-type": "application/json", "x-ctxoptimizer-csrf": csrf()},
|
|
94
|
+
body: JSON.stringify(body),
|
|
95
|
+
});
|
|
96
|
+
const payload = await response.json();
|
|
97
|
+
document.getElementById("status").textContent =
|
|
98
|
+
response.ok ? JSON.stringify(payload) : ("Error: " + (payload.error || response.status));
|
|
99
|
+
}
|
|
100
|
+
document.querySelectorAll("button.status").forEach((button) => {
|
|
101
|
+
button.addEventListener("click", () => post(
|
|
102
|
+
"/_ctxoptimizer/api/waste/" + button.dataset.id + "/status",
|
|
103
|
+
{status: button.dataset.status},
|
|
104
|
+
));
|
|
105
|
+
});
|
|
106
|
+
document.querySelectorAll("button.apply").forEach((button) => {
|
|
107
|
+
button.addEventListener("click", () => {
|
|
108
|
+
if (confirm("Apply this recommendation?")) {
|
|
109
|
+
post("/_ctxoptimizer/api/recommend/" + button.dataset.id + "/apply", {});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
document.querySelectorAll("button.dismiss").forEach((button) => {
|
|
114
|
+
button.addEventListener("click", () => {
|
|
115
|
+
post("/_ctxoptimizer/api/recommend/" + button.dataset.id + "/dismiss", {days: 30});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
</script>
|
|
119
|
+
</body>
|
|
120
|
+
</html>
|