logit-classifier 0.1.0__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.
- logit_classifier/__init__.py +90 -0
- logit_classifier/__main__.py +10 -0
- logit_classifier/backends/__init__.py +5 -0
- logit_classifier/backends/base.py +125 -0
- logit_classifier/backends/hf.py +412 -0
- logit_classifier/calibrate.py +141 -0
- logit_classifier/classifier.py +329 -0
- logit_classifier/cli.py +51 -0
- logit_classifier/config.py +187 -0
- logit_classifier/deps.py +30 -0
- logit_classifier/errors.py +16 -0
- logit_classifier/labels.py +91 -0
- logit_classifier/prompt.py +154 -0
- logit_classifier/py.typed +0 -0
- logit_classifier/schema.py +285 -0
- logit_classifier/scoring.py +102 -0
- logit_classifier/service.py +174 -0
- logit_classifier/vision.py +95 -0
- logit_classifier/web/index.html +411 -0
- logit_classifier-0.1.0.dist-info/METADATA +354 -0
- logit_classifier-0.1.0.dist-info/RECORD +24 -0
- logit_classifier-0.1.0.dist-info/WHEEL +4 -0
- logit_classifier-0.1.0.dist-info/entry_points.txt +2 -0
- logit_classifier-0.1.0.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="auto">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3E%3Crect width=%2716%27 height=%2716%27 rx=%273%27 fill=%27%232f6df6%27/%3E%3Crect x=%273%27 y=%274%27 width=%2710%27 height=%272%27 rx=%271%27 fill=%27white%27/%3E%3Crect x=%273%27 y=%277%27 width=%276%27 height=%272%27 rx=%271%27 fill=%27white%27/%3E%3Crect x=%273%27 y=%2710%27 width=%273%27 height=%272%27 rx=%271%27 fill=%27white%27/%3E%3C/svg%3E">
|
|
7
|
+
<title>Logit Classifier</title>
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--bg: #f6f7f9; --panel: #ffffff; --ink: #14161a; --muted: #5d6470;
|
|
11
|
+
--line: #dfe3e8; --accent: #2f6df6; --accent-soft: #e7eeff;
|
|
12
|
+
--bar: #2f6df6; --bar-bg: #eceff3; --warn: #b4560a;
|
|
13
|
+
--radius: 10px; --mono: ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace;
|
|
14
|
+
}
|
|
15
|
+
@media (prefers-color-scheme: dark) {
|
|
16
|
+
:root:not([data-theme="light"]) {
|
|
17
|
+
--bg: #101216; --panel: #181b21; --ink: #e8eaed; --muted: #99a1ae;
|
|
18
|
+
--line: #2a2f38; --accent: #6b9bff; --accent-soft: #1d2740;
|
|
19
|
+
--bar: #6b9bff; --bar-bg: #232832; --warn: #e0954a;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
:root[data-theme="dark"] {
|
|
23
|
+
--bg: #101216; --panel: #181b21; --ink: #e8eaed; --muted: #99a1ae;
|
|
24
|
+
--line: #2a2f38; --accent: #6b9bff; --accent-soft: #1d2740;
|
|
25
|
+
--bar: #6b9bff; --bar-bg: #232832; --warn: #e0954a;
|
|
26
|
+
}
|
|
27
|
+
* { box-sizing: border-box; }
|
|
28
|
+
body {
|
|
29
|
+
margin: 0; background: var(--bg); color: var(--ink);
|
|
30
|
+
font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
31
|
+
}
|
|
32
|
+
header {
|
|
33
|
+
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
|
|
34
|
+
padding: 14px 16px; border-bottom: 1px solid var(--line); background: var(--panel);
|
|
35
|
+
}
|
|
36
|
+
header h1 { font-size: 16px; margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
37
|
+
.badge {
|
|
38
|
+
font: 12px/1 var(--mono); color: var(--muted); background: var(--bar-bg);
|
|
39
|
+
border: 1px solid var(--line); border-radius: 999px; padding: 5px 10px;
|
|
40
|
+
}
|
|
41
|
+
.spacer { flex: 1; }
|
|
42
|
+
main {
|
|
43
|
+
display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr);
|
|
44
|
+
gap: 16px; padding: 16px; max-width: 1400px; margin: 0 auto;
|
|
45
|
+
}
|
|
46
|
+
@media (max-width: 900px) { main { grid-template-columns: minmax(0,1fr); } }
|
|
47
|
+
.panel {
|
|
48
|
+
background: var(--panel); border: 1px solid var(--line);
|
|
49
|
+
border-radius: var(--radius); padding: 14px; min-width: 0;
|
|
50
|
+
}
|
|
51
|
+
h2 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.06em;
|
|
52
|
+
color: var(--muted); margin: 0 0 10px; font-weight: 600; }
|
|
53
|
+
textarea, input, select {
|
|
54
|
+
width: 100%; font: inherit; color: var(--ink); background: var(--bg);
|
|
55
|
+
border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px;
|
|
56
|
+
}
|
|
57
|
+
textarea { resize: vertical; min-height: 110px; }
|
|
58
|
+
textarea.code { font: 13px/1.5 var(--mono); min-height: 260px; }
|
|
59
|
+
button {
|
|
60
|
+
font: inherit; font-weight: 550; cursor: pointer; border-radius: 8px;
|
|
61
|
+
border: 1px solid var(--line); background: var(--panel); color: var(--ink);
|
|
62
|
+
padding: 7px 13px;
|
|
63
|
+
}
|
|
64
|
+
button:hover { border-color: var(--accent); }
|
|
65
|
+
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
66
|
+
button.primary:disabled { opacity: .55; cursor: progress; }
|
|
67
|
+
button.tiny { padding: 3px 8px; font-size: 12px; }
|
|
68
|
+
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
|
69
|
+
.qcard { border: 1px solid var(--line); border-radius: 8px; padding: 10px; margin-bottom: 10px; }
|
|
70
|
+
.qcard .head { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; }
|
|
71
|
+
.qcard .head input { flex: 1 1 120px; font: 13px/1.4 var(--mono); }
|
|
72
|
+
.pill {
|
|
73
|
+
font: 11px/1 var(--mono); text-transform: uppercase; letter-spacing: .05em;
|
|
74
|
+
padding: 5px 8px; border-radius: 6px; background: var(--accent-soft); color: var(--accent);
|
|
75
|
+
}
|
|
76
|
+
.opt { display: flex; gap: 6px; margin-top: 6px; }
|
|
77
|
+
.opt input:first-child { flex: 0 0 34%; }
|
|
78
|
+
.hint { font-size: 12px; color: var(--muted); margin-top: 6px; }
|
|
79
|
+
.answer { border: 1px solid var(--line); border-radius: 8px; padding: 11px; margin-bottom: 11px; }
|
|
80
|
+
.answer .top { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin-bottom: 9px; }
|
|
81
|
+
.answer .qid { font: 13px/1 var(--mono); color: var(--muted); }
|
|
82
|
+
.answer .verdict { font-size: 17px; font-weight: 650; }
|
|
83
|
+
.bar-row { display: grid; grid-template-columns: minmax(70px, 30%) 1fr 52px; gap: 8px; align-items: center; margin: 4px 0; font-size: 13px; }
|
|
84
|
+
.bar-row .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
85
|
+
.bar-row .name.win { font-weight: 650; }
|
|
86
|
+
.track { background: var(--bar-bg); border-radius: 4px; height: 9px; overflow: hidden; }
|
|
87
|
+
.fill { display: block; background: var(--bar); height: 100%; border-radius: 4px; min-width: 0; }
|
|
88
|
+
.bar-row .val { font: 12px/1 var(--mono); color: var(--muted); text-align: right; }
|
|
89
|
+
.meta { font: 12px/1.5 var(--mono); color: var(--muted); margin-top: 8px; }
|
|
90
|
+
.err { color: var(--warn); font-size: 13px; white-space: pre-wrap; }
|
|
91
|
+
.tabs { display: flex; gap: 6px; margin-bottom: 10px; }
|
|
92
|
+
.tabs button[aria-selected="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
|
|
93
|
+
.hidden { display: none !important; }
|
|
94
|
+
pre.out { font: 12px/1.5 var(--mono); background: var(--bg); border: 1px solid var(--line);
|
|
95
|
+
border-radius: 8px; padding: 10px; overflow: auto; max-height: 340px; margin: 0; }
|
|
96
|
+
</style>
|
|
97
|
+
</head>
|
|
98
|
+
<body>
|
|
99
|
+
<header>
|
|
100
|
+
<h1>Logit Classifier</h1>
|
|
101
|
+
<span class="badge" id="modelBadge">connecting</span>
|
|
102
|
+
<span class="spacer"></span>
|
|
103
|
+
<button class="tiny" id="themeBtn">Theme</button>
|
|
104
|
+
</header>
|
|
105
|
+
|
|
106
|
+
<main>
|
|
107
|
+
<section class="panel">
|
|
108
|
+
<div class="tabs">
|
|
109
|
+
<button id="tabForm" aria-selected="true">Builder</button>
|
|
110
|
+
<button id="tabJson" aria-selected="false">Raw JSON</button>
|
|
111
|
+
<span class="spacer"></span>
|
|
112
|
+
<button class="tiny" id="exampleBtn">Load example</button>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div id="formView">
|
|
116
|
+
<h2>Context</h2>
|
|
117
|
+
<textarea id="context" placeholder="The text to classify."></textarea>
|
|
118
|
+
|
|
119
|
+
<h2 style="margin-top:14px">Questions</h2>
|
|
120
|
+
<div id="questions"></div>
|
|
121
|
+
<div class="row" style="margin-top:8px">
|
|
122
|
+
<button class="tiny" data-add="choice">Add choice</button>
|
|
123
|
+
<button class="tiny" data-add="score">Add score</button>
|
|
124
|
+
<button class="tiny" data-add="noul">Add yes/no</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div id="jsonView" class="hidden">
|
|
129
|
+
<h2>Request body</h2>
|
|
130
|
+
<textarea id="jsonBody" class="code" spellcheck="false"></textarea>
|
|
131
|
+
<p class="hint">Accepts a TypeSafe System One request body unchanged.</p>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div class="row" style="margin-top:14px">
|
|
135
|
+
<button class="primary" id="runBtn">Classify</button>
|
|
136
|
+
<label class="row" style="gap:5px;font-size:13px">
|
|
137
|
+
<input type="checkbox" id="diagBox" style="width:auto" checked> Diagnostics
|
|
138
|
+
</label>
|
|
139
|
+
</div>
|
|
140
|
+
<div class="err" id="error"></div>
|
|
141
|
+
</section>
|
|
142
|
+
|
|
143
|
+
<section class="panel">
|
|
144
|
+
<div class="tabs">
|
|
145
|
+
<button id="tabAnswers" aria-selected="true">Answers</button>
|
|
146
|
+
<button id="tabRaw" aria-selected="false">Response JSON</button>
|
|
147
|
+
</div>
|
|
148
|
+
<div id="answers"><p class="hint">No result yet.</p></div>
|
|
149
|
+
<pre class="out hidden" id="rawOut"></pre>
|
|
150
|
+
<div class="meta" id="meta"></div>
|
|
151
|
+
</section>
|
|
152
|
+
</main>
|
|
153
|
+
|
|
154
|
+
<script>
|
|
155
|
+
const $ = (id) => document.getElementById(id);
|
|
156
|
+
let questions = [];
|
|
157
|
+
let seq = 0;
|
|
158
|
+
let lastResponse = null;
|
|
159
|
+
|
|
160
|
+
const EXAMPLE = {
|
|
161
|
+
context: "Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. I'm losing sales. Please help ASAP.",
|
|
162
|
+
questions: [
|
|
163
|
+
{ id: "department", type: "choice", instructions: "Which team should handle this",
|
|
164
|
+
options: [["billing", "Payment or subscription issues"], ["technical", "Bugs or integration problems"], ["sales", "Pricing or account questions"]] },
|
|
165
|
+
{ id: "frustration", type: "score", instructions: "How frustrated the customer appears",
|
|
166
|
+
levels: ["Calm, just stating facts", "Frustrated but civil", "Very angry, strong language"] },
|
|
167
|
+
{ id: "is_urgent", type: "noul", instructions: "The message conveys urgency or time-sensitivity", yes: "", no: "" }
|
|
168
|
+
]
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
function blank(type) {
|
|
172
|
+
seq += 1;
|
|
173
|
+
const base = { id: type + "_" + seq, type, instructions: "" };
|
|
174
|
+
if (type === "choice") base.options = [["", ""], ["", ""]];
|
|
175
|
+
if (type === "score") base.levels = ["", ""];
|
|
176
|
+
if (type === "noul") { base.yes = ""; base.no = ""; }
|
|
177
|
+
return base;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function renderQuestions() {
|
|
181
|
+
const host = $("questions");
|
|
182
|
+
host.innerHTML = "";
|
|
183
|
+
questions.forEach((q, qi) => {
|
|
184
|
+
const card = document.createElement("div");
|
|
185
|
+
card.className = "qcard";
|
|
186
|
+
const label = q.type === "noul" ? "yes/no" : q.type;
|
|
187
|
+
card.innerHTML = `
|
|
188
|
+
<div class="head">
|
|
189
|
+
<span class="pill">${label}</span>
|
|
190
|
+
<input value="${esc(q.id)}" data-f="id" placeholder="question id">
|
|
191
|
+
<button class="tiny" data-del="${qi}">Remove</button>
|
|
192
|
+
</div>
|
|
193
|
+
<input value="${esc(q.instructions)}" data-f="instructions"
|
|
194
|
+
placeholder="${q.type === "noul" ? "Statement to judge true or false" : "What to decide"}">
|
|
195
|
+
<div class="body"></div>`;
|
|
196
|
+
const body = card.querySelector(".body");
|
|
197
|
+
|
|
198
|
+
if (q.type === "choice") {
|
|
199
|
+
q.options.forEach((opt, oi) => body.appendChild(optionRow(qi, oi, opt[0], opt[1], "option", "description (optional)")));
|
|
200
|
+
body.appendChild(addButton(qi, "option", "Add option"));
|
|
201
|
+
body.appendChild(hint(`${q.options.length} options. Above 52 the service splits them into groups automatically.`));
|
|
202
|
+
} else if (q.type === "score") {
|
|
203
|
+
q.levels.forEach((lv, li) => body.appendChild(levelRow(qi, li, lv)));
|
|
204
|
+
body.appendChild(addButton(qi, "level", "Add level"));
|
|
205
|
+
body.appendChild(hint("Ordered low to high. Two to ten levels."));
|
|
206
|
+
} else {
|
|
207
|
+
body.appendChild(optionRow(qi, 0, "yes", q.yes, null, "what a yes means (optional)"));
|
|
208
|
+
body.appendChild(optionRow(qi, 1, "no", q.no, null, "what a no means (optional)"));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
card.querySelectorAll("[data-f]").forEach(el => {
|
|
212
|
+
el.oninput = () => { questions[qi][el.dataset.f] = el.value; };
|
|
213
|
+
});
|
|
214
|
+
card.querySelector("[data-del]").onclick = () => { questions.splice(qi, 1); renderQuestions(); };
|
|
215
|
+
host.appendChild(card);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function esc(s) { return String(s ?? "").replace(/"/g, """); }
|
|
220
|
+
|
|
221
|
+
function hint(text) { const p = document.createElement("p"); p.className = "hint"; p.textContent = text; return p; }
|
|
222
|
+
|
|
223
|
+
function optionRow(qi, oi, name, desc, nameField, descPlaceholder) {
|
|
224
|
+
const wrap = document.createElement("div");
|
|
225
|
+
wrap.className = "opt";
|
|
226
|
+
const n = document.createElement("input");
|
|
227
|
+
n.value = name; n.placeholder = "name";
|
|
228
|
+
if (nameField === null) { n.disabled = true; } else {
|
|
229
|
+
n.oninput = () => { questions[qi].options[oi][0] = n.value; };
|
|
230
|
+
}
|
|
231
|
+
const d = document.createElement("input");
|
|
232
|
+
d.value = desc || ""; d.placeholder = descPlaceholder;
|
|
233
|
+
d.oninput = () => {
|
|
234
|
+
if (nameField === null) { questions[qi][oi === 0 ? "yes" : "no"] = d.value; }
|
|
235
|
+
else { questions[qi].options[oi][1] = d.value; }
|
|
236
|
+
};
|
|
237
|
+
wrap.append(n, d);
|
|
238
|
+
if (nameField !== null) {
|
|
239
|
+
const x = document.createElement("button");
|
|
240
|
+
x.className = "tiny"; x.textContent = "x";
|
|
241
|
+
x.onclick = () => { questions[qi].options.splice(oi, 1); renderQuestions(); };
|
|
242
|
+
wrap.append(x);
|
|
243
|
+
}
|
|
244
|
+
return wrap;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function levelRow(qi, li, value) {
|
|
248
|
+
const wrap = document.createElement("div");
|
|
249
|
+
wrap.className = "opt";
|
|
250
|
+
const n = document.createElement("input");
|
|
251
|
+
n.value = li; n.disabled = true; n.style.flex = "0 0 44px";
|
|
252
|
+
const d = document.createElement("input");
|
|
253
|
+
d.value = value; d.placeholder = "what this level means";
|
|
254
|
+
d.oninput = () => { questions[qi].levels[li] = d.value; };
|
|
255
|
+
const x = document.createElement("button");
|
|
256
|
+
x.className = "tiny"; x.textContent = "x";
|
|
257
|
+
x.onclick = () => { questions[qi].levels.splice(li, 1); renderQuestions(); };
|
|
258
|
+
wrap.append(n, d, x);
|
|
259
|
+
return wrap;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function addButton(qi, kind, text) {
|
|
263
|
+
const b = document.createElement("button");
|
|
264
|
+
b.className = "tiny"; b.textContent = text; b.style.marginTop = "6px";
|
|
265
|
+
b.onclick = () => {
|
|
266
|
+
if (kind === "option") questions[qi].options.push(["", ""]);
|
|
267
|
+
else questions[qi].levels.push("");
|
|
268
|
+
renderQuestions();
|
|
269
|
+
};
|
|
270
|
+
return b;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function buildRequest() {
|
|
274
|
+
const out = { state: $("context").value, model: "logit-latest", questions: {} };
|
|
275
|
+
questions.forEach(q => {
|
|
276
|
+
if (!q.id) return;
|
|
277
|
+
if (q.type === "choice") {
|
|
278
|
+
const criteria = {};
|
|
279
|
+
q.options.forEach(([name, desc]) => { if (name) criteria[name] = desc || null; });
|
|
280
|
+
out.questions[q.id] = { type: "choice", instructions: q.instructions, criteria };
|
|
281
|
+
} else if (q.type === "score") {
|
|
282
|
+
out.questions[q.id] = { type: "score", instructions: q.instructions, criteria: q.levels.filter(Boolean) };
|
|
283
|
+
} else {
|
|
284
|
+
const body = { type: "noul", instructions: q.instructions };
|
|
285
|
+
if (q.yes || q.no) body.criteria = { true: q.yes || null, false: q.no || null };
|
|
286
|
+
out.questions[q.id] = body;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function bars(entries, winner) {
|
|
293
|
+
return entries.map(([name, p]) => `
|
|
294
|
+
<div class="bar-row">
|
|
295
|
+
<span class="name ${name === winner ? "win" : ""}" title="${esc(name)}">${esc(name)}</span>
|
|
296
|
+
<span class="track"><span class="fill" style="width:${(p * 100).toFixed(1)}%"></span></span>
|
|
297
|
+
<span class="val">${p.toFixed(3)}</span>
|
|
298
|
+
</div>`).join("");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function renderAnswers(data) {
|
|
302
|
+
const host = $("answers");
|
|
303
|
+
const diag = data.diagnostics || {};
|
|
304
|
+
host.innerHTML = Object.entries(data.answers).map(([qid, a]) => {
|
|
305
|
+
let verdict = "", body = "", extra = "";
|
|
306
|
+
if (a.type === "choice") {
|
|
307
|
+
verdict = a.choice;
|
|
308
|
+
body = bars(Object.entries(a.probabilities).sort((x, y) => y[1] - x[1]), a.choice);
|
|
309
|
+
extra = `confidence ${a.confidence.toFixed(3)}`;
|
|
310
|
+
} else if (a.type === "score") {
|
|
311
|
+
verdict = a.score.toFixed(2);
|
|
312
|
+
body = bars(Object.entries(a.probabilities).map(([i, p]) => [`${i} ${a.legend[i]}`, p]), null);
|
|
313
|
+
extra = `confidence ${a.confidence.toFixed(3)}`;
|
|
314
|
+
} else {
|
|
315
|
+
verdict = a.noul >= 0.5 ? "yes" : "no";
|
|
316
|
+
body = bars([["yes", a.noul], ["no", 1 - a.noul]], a.noul >= 0.5 ? "yes" : "no");
|
|
317
|
+
extra = `P(true) ${a.noul.toFixed(4)}`;
|
|
318
|
+
}
|
|
319
|
+
const mass = (diag.candidate_mass || {})[qid];
|
|
320
|
+
const massNote = mass ? ` | label mass ${Math.min(...mass).toFixed(3)}` : "";
|
|
321
|
+
return `<div class="answer">
|
|
322
|
+
<div class="top"><span class="qid">${esc(qid)}</span><span class="verdict">${esc(verdict)}</span></div>
|
|
323
|
+
${body}<div class="meta">${extra}${massNote}</div></div>`;
|
|
324
|
+
}).join("") || `<p class="hint">No answers returned.</p>`;
|
|
325
|
+
|
|
326
|
+
const bits = [`${data.usage.input_tokens} input tokens`, `${data.usage.output_tokens} scored positions`];
|
|
327
|
+
if (diag.latency_ms) bits.push(`${diag.latency_ms} ms`);
|
|
328
|
+
if (diag.temperature) bits.push(`T=${diag.temperature}`);
|
|
329
|
+
if (diag.score_method) bits.push(`score=${diag.score_method}`);
|
|
330
|
+
bits.push(`model ${data.model}`);
|
|
331
|
+
$("meta").textContent = bits.join(" | ");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function run() {
|
|
335
|
+
const btn = $("runBtn");
|
|
336
|
+
$("error").textContent = "";
|
|
337
|
+
btn.disabled = true; btn.textContent = "Classifying";
|
|
338
|
+
try {
|
|
339
|
+
const body = $("jsonView").classList.contains("hidden")
|
|
340
|
+
? buildRequest() : JSON.parse($("jsonBody").value);
|
|
341
|
+
const res = await fetch("/v1/systemone?diagnostics=" + ($("diagBox").checked ? "1" : "0"), {
|
|
342
|
+
method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body)
|
|
343
|
+
});
|
|
344
|
+
const data = await res.json();
|
|
345
|
+
if (!res.ok) throw new Error(data.message ? `${data.error}: ${data.message}` + (data.field ? ` (${data.field})` : "") : JSON.stringify(data, null, 2));
|
|
346
|
+
lastResponse = data;
|
|
347
|
+
renderAnswers(data);
|
|
348
|
+
$("rawOut").textContent = JSON.stringify(data, null, 2);
|
|
349
|
+
} catch (e) {
|
|
350
|
+
$("error").textContent = String(e.message || e);
|
|
351
|
+
} finally {
|
|
352
|
+
btn.disabled = false; btn.textContent = "Classify";
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function showTab(which) {
|
|
357
|
+
const form = which === "form";
|
|
358
|
+
$("tabForm").ariaSelected = String(form);
|
|
359
|
+
$("tabJson").ariaSelected = String(!form);
|
|
360
|
+
$("formView").classList.toggle("hidden", !form);
|
|
361
|
+
$("jsonView").classList.toggle("hidden", form);
|
|
362
|
+
if (!form) $("jsonBody").value = JSON.stringify(buildRequest(), null, 2);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function showOut(which) {
|
|
366
|
+
const ans = which === "answers";
|
|
367
|
+
$("tabAnswers").ariaSelected = String(ans);
|
|
368
|
+
$("tabRaw").ariaSelected = String(!ans);
|
|
369
|
+
$("answers").classList.toggle("hidden", !ans);
|
|
370
|
+
$("rawOut").classList.toggle("hidden", ans);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function loadExample() {
|
|
374
|
+
$("context").value = EXAMPLE.context;
|
|
375
|
+
questions = JSON.parse(JSON.stringify(EXAMPLE.questions));
|
|
376
|
+
renderQuestions();
|
|
377
|
+
showTab("form");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
document.querySelectorAll("[data-add]").forEach(b => {
|
|
381
|
+
b.onclick = () => { questions.push(blank(b.dataset.add)); renderQuestions(); };
|
|
382
|
+
});
|
|
383
|
+
$("runBtn").onclick = run;
|
|
384
|
+
$("exampleBtn").onclick = loadExample;
|
|
385
|
+
$("tabForm").onclick = () => showTab("form");
|
|
386
|
+
$("tabJson").onclick = () => showTab("json");
|
|
387
|
+
$("tabAnswers").onclick = () => showOut("answers");
|
|
388
|
+
$("tabRaw").onclick = () => showOut("raw");
|
|
389
|
+
$("themeBtn").onclick = () => {
|
|
390
|
+
const root = document.documentElement;
|
|
391
|
+
const next = root.dataset.theme === "dark" ? "light" : "dark";
|
|
392
|
+
root.dataset.theme = next;
|
|
393
|
+
try { localStorage.setItem("logit-theme", next); } catch (_) {}
|
|
394
|
+
};
|
|
395
|
+
try {
|
|
396
|
+
const saved = localStorage.getItem("logit-theme");
|
|
397
|
+
if (saved) document.documentElement.dataset.theme = saved;
|
|
398
|
+
} catch (_) {}
|
|
399
|
+
|
|
400
|
+
document.addEventListener("keydown", e => {
|
|
401
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") { e.preventDefault(); run(); }
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
fetch("/healthz").then(r => r.json()).then(h => {
|
|
405
|
+
$("modelBadge").textContent = h.ready ? h.model_id : "loading model";
|
|
406
|
+
}).catch(() => { $("modelBadge").textContent = "offline"; });
|
|
407
|
+
|
|
408
|
+
loadExample();
|
|
409
|
+
</script>
|
|
410
|
+
</body>
|
|
411
|
+
</html>
|