@gafj/gafj 0.1.7 → 0.1.9
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/bridge/providers/anthropic.js +1 -1
- package/bridge/providers/index.js +2 -1
- package/http/api.js +6 -1
- package/package.json +1 -1
- package/ui/screens/kb.js +1 -1
- package/ui/screens/settings.js +9 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const DEFAULT_URL = "https://api.anthropic.com";
|
|
4
|
-
const DEFAULT_MODEL = "claude-
|
|
4
|
+
const DEFAULT_MODEL = "claude-sonnet-5";
|
|
5
5
|
|
|
6
6
|
async function complete({ provider, system, user, max_tokens = 4000, fetch }) {
|
|
7
7
|
const { post } = require("./index");
|
|
@@ -22,7 +22,8 @@ function checkUrl(url) {
|
|
|
22
22
|
return u.toString();
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
// a long resume with a span for every quoted phrase can take a model several minutes; the key test passes its own short limit
|
|
26
|
+
async function post(fetchImpl, url, headers, body, timeoutMs = 600000) {
|
|
26
27
|
const f = fetchImpl || globalThis.fetch;
|
|
27
28
|
const ac = new AbortController();
|
|
28
29
|
const t = setTimeout(() => ac.abort(), timeoutMs);
|
package/http/api.js
CHANGED
|
@@ -154,9 +154,14 @@ route("POST", "/api/kb/questions/:id/dismiss", (c, p) => discover.dismissQuestio
|
|
|
154
154
|
|
|
155
155
|
// usage: tokens per operation from the attempts the app recorded; the pricing test reads this
|
|
156
156
|
route("GET", "/api/usage", (c) => ({
|
|
157
|
-
by_op: c.db.prepare(`SELECT r.operation AS op, count(*) AS attempts,
|
|
157
|
+
by_op: c.db.prepare(`SELECT r.operation AS op, count(*) AS attempts,
|
|
158
|
+
sum(CASE WHEN a.result = 'passed' THEN 1 ELSE 0 END) AS passed, sum(CASE WHEN a.result <> 'passed' THEN 1 ELSE 0 END) AS failed,
|
|
159
|
+
coalesce(sum(CASE WHEN a.result = 'passed' THEN a.tokens_in END), 0) AS in_passed, coalesce(sum(CASE WHEN a.result = 'passed' THEN a.tokens_out END), 0) AS out_passed,
|
|
160
|
+
coalesce(sum(CASE WHEN a.result <> 'passed' THEN a.tokens_in END), 0) AS in_failed, coalesce(sum(CASE WHEN a.result <> 'passed' THEN a.tokens_out END), 0) AS out_failed,
|
|
158
161
|
coalesce(sum(a.tokens_in), 0) AS tokens_in, coalesce(sum(a.tokens_out), 0) AS tokens_out, coalesce(avg(a.latency_ms), 0) AS avg_ms, max(a.model) AS model
|
|
159
162
|
FROM ai_attempt a JOIN ai_run r ON r.id = a.ai_run_id WHERE r.candidate_id = ? AND a.tokens_in IS NOT NULL GROUP BY r.operation ORDER BY r.operation`).all(c.candidate_id),
|
|
163
|
+
// calls that never returned (a timeout, a network drop) recorded no tokens here but were still billed by the provider
|
|
164
|
+
unrecorded: c.db.prepare("SELECT count(*) c FROM ai_run r WHERE r.candidate_id = ? AND r.route IN ('byo_key', 'credits') AND NOT EXISTS (SELECT 1 FROM ai_attempt a WHERE a.ai_run_id = r.id)").get(c.candidate_id).c,
|
|
160
165
|
since: c.db.prepare("SELECT min(a.started_at) AS s FROM ai_attempt a JOIN ai_run r ON r.id = a.ai_run_id WHERE r.candidate_id = ? AND a.tokens_in IS NOT NULL").get(c.candidate_id).s,
|
|
161
166
|
}));
|
|
162
167
|
|
package/package.json
CHANGED
package/ui/screens/kb.js
CHANGED
|
@@ -52,7 +52,7 @@ export function Onboarding({ d, reload, setErr, route, compact }) {
|
|
|
52
52
|
const groups = b ? b.groups.map((g) => ({ id: g, rows: b.pending.filter((p) => p.dedup_group_id === g) })) : [];
|
|
53
53
|
return html`<div class="card">
|
|
54
54
|
<div class="row"><input type="file" multiple accept=".docx,.pdf,.md,.txt" disabled=${busy} onChange=${upload} /><span class="small muted">docx, pdf, md, txt; text is extracted locally, no OCR</span></div>
|
|
55
|
-
${direct && notYet > 1 ? html`<div class="row"><button class="primary" disabled=${!!all} onClick=${extractAll}>${all ? "Extracting " + all : `Extract all ${notYet} files`}</button><span class="small muted">one after another through your AI;
|
|
55
|
+
${direct && notYet > 1 ? html`<div class="row"><button class="primary" disabled=${!!all} onClick=${extractAll}>${all ? "Extracting " + all : `Extract all ${notYet} files`}</button><span class="small muted">one after another through your AI; one to three minutes each for a full resume</span></div>` : ""}
|
|
56
56
|
${!direct && notYet > 1 ? html`<p class="small muted">On the paste route each file is one packet you carry to your chat and back; with a provider key in Settings the whole pile runs in one click.</p>` : ""}
|
|
57
57
|
${b ? html`<ul class="list">${b.sources.map((s) => html`<li key=${s.source_document_id}><b>${s.filename}</b> <span class="tag">${s.kind}</span>
|
|
58
58
|
${s.empty ? html`<span class="tag blocked">no text</span>` : html`<span class="muted small">${s.chars} chars</span>`}
|
package/ui/screens/settings.js
CHANGED
|
@@ -5,7 +5,7 @@ import { api, useInstall } from "../lib.js";
|
|
|
5
5
|
|
|
6
6
|
const KINDS = ["resume", "cover", "onepager", "email", "prep", "deep_answers", "practice", "cheatsheet"];
|
|
7
7
|
const KIND_LABEL = { anthropic: "Anthropic (Claude)", openai_compatible: "OpenAI, or any OpenAI-compatible server", gemini: "Google Gemini" };
|
|
8
|
-
const KIND_MODEL = { anthropic: "claude-
|
|
8
|
+
const KIND_MODEL = { anthropic: "claude-sonnet-5" };
|
|
9
9
|
|
|
10
10
|
export function Settings({ live }) {
|
|
11
11
|
const [s, setS] = useState(null);
|
|
@@ -92,10 +92,14 @@ export function Settings({ live }) {
|
|
|
92
92
|
</div>
|
|
93
93
|
<h2>Usage</h2>
|
|
94
94
|
<div class="card">
|
|
95
|
-
${usage && usage.by_op.length ? html`<table class="cats"><thead><tr><th>operation</th><th>
|
|
96
|
-
${usage.by_op.
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
${usage && usage.by_op.length ? html`<table class="cats"><thead><tr><th>operation</th><th>result</th><th>runs</th><th>tokens in</th><th>tokens out</th><th>model</th></tr></thead><tbody>
|
|
96
|
+
${usage.by_op.flatMap((u) => [
|
|
97
|
+
html`<tr key=${u.op + "p"}><td rowspan="2"><b>${u.op}</b><div class="small muted">${(u.avg_ms / 1000).toFixed(0)} s avg</div></td><td><span class="tag passed">passed</span></td><td>${u.passed}</td><td>${u.in_passed.toLocaleString()}</td><td>${u.out_passed.toLocaleString()}</td><td rowspan="2" class="small muted">${u.model || ""}</td></tr>`,
|
|
98
|
+
html`<tr key=${u.op + "f"}><td><span class="tag blocked">failed</span></td><td>${u.failed}</td><td>${u.in_failed.toLocaleString()}</td><td>${u.out_failed.toLocaleString()}</td></tr>`,
|
|
99
|
+
])}
|
|
100
|
+
<tr><td><b>total</b></td><td></td><td>${usage.by_op.reduce((n, u) => n + u.attempts, 0)}</td><td><b>${usage.by_op.reduce((n, u) => n + u.tokens_in, 0).toLocaleString()}</b></td><td><b>${usage.by_op.reduce((n, u) => n + u.tokens_out, 0).toLocaleString()}</b></td><td></td></tr>
|
|
101
|
+
<tr><td colspan="2"><b>of which wasted on failures</b></td><td>${usage.by_op.reduce((n, u) => n + u.failed, 0)}</td><td>${usage.by_op.reduce((n, u) => n + u.in_failed, 0).toLocaleString()}</td><td>${usage.by_op.reduce((n, u) => n + u.out_failed, 0).toLocaleString()}</td><td></td></tr>
|
|
102
|
+
</tbody></table><p class="small muted">Counted from every run through your own provider or credits since ${usage.since ? new Date(usage.since).toLocaleDateString() : "the start"}. "Failed" is a reply that came back but did not pass the gate. ${usage.unrecorded ? `${usage.unrecorded} call${usage.unrecorded === 1 ? "" : "s"} never returned (a timeout or a dropped connection): no tokens recorded here, but the provider still billed the input.` : ""} Paste runs are not counted. Multiply by your provider's price per million tokens for the cost.</p>`
|
|
99
103
|
: html`<p class="small muted">Nothing counted yet. Runs through your own provider key or credits record their token counts here.</p>`}
|
|
100
104
|
</div>
|
|
101
105
|
<h2>Bullet register</h2>
|