myapikey 0.34.6 → 0.35.0
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/package.json +1 -1
- package/packages/core/src/cli/index.ts +56 -0
- package/packages/core/src/server/admin.ts +39 -5
- package/packages/core/src/server/proxy.ts +58 -3
- package/packages/core/src/server/store.ts +53 -1
- package/packages/core/src/shared/types.ts +41 -0
- package/packages/web/dist/assets/index-BhE0o2aD.css +1 -0
- package/packages/web/dist/assets/index-cZhEr_b8.js +334 -0
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/assets/index-Cl2nRbtQ.js +0 -324
- package/packages/web/dist/assets/index-Dk7PZDM5.css +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myapikey",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Personal LLM API gateway & proxy — one address + one API key for all your models. Forwards OpenAI & Anthropic calls to your backends with failover and a circuit breaker. Pure passthrough, no format translation. Self-hosted (CLI + web UI).",
|
|
6
6
|
"keywords": [
|
|
@@ -283,6 +283,62 @@ model.command("remove <name>").description("remove a model entirely (both format
|
|
|
283
283
|
console.log(`Removed ${name}.`);
|
|
284
284
|
});
|
|
285
285
|
|
|
286
|
+
model
|
|
287
|
+
.command("debug <name> [action] [index]")
|
|
288
|
+
.description("debug capture: on|off toggles recording of the last 50 actual upstream requests/responses; show lists them (add an index to dump one in full)")
|
|
289
|
+
.action(async (name: string, action = "show", indexRaw?: string) => {
|
|
290
|
+
const path = `/admin/models/${encodeURIComponent(name)}/debug`;
|
|
291
|
+
if (action === "on" || action === "off") {
|
|
292
|
+
const r = (await api(ctx(), "PUT", path, { enabled: action === "on" })) as { enabled: boolean };
|
|
293
|
+
console.log(
|
|
294
|
+
r.enabled
|
|
295
|
+
? `Debug capture ON for ${name} — the last 50 upstream attempts (request + response) are recorded in memory; turn off to clear.`
|
|
296
|
+
: `Debug capture OFF for ${name}; captured content cleared.`,
|
|
297
|
+
);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (action !== "show") throw new Error(`Unknown action '${action}' (use on | off | show).`);
|
|
301
|
+
const r = (await api(ctx(), "GET", path)) as { enabled: boolean; captures: any[] };
|
|
302
|
+
if (!r.enabled) console.log(`Debug capture is OFF for ${name}.`);
|
|
303
|
+
const cs = r.captures ?? [];
|
|
304
|
+
if (indexRaw !== undefined) {
|
|
305
|
+
const row = cs[Number(indexRaw) - 1];
|
|
306
|
+
if (!row) throw new Error(`No capture #${indexRaw} (list is newest-first, ${cs.length} captured).`);
|
|
307
|
+
console.log(`#${Number(indexRaw)} ${new Date(row.ts).toLocaleTimeString("en-GB", { hour12: false })} ${row.provider} [${row.format}] status=${row.status} ${row.ms}ms${row.upstreamModel ? ` model=${row.upstreamModel}` : ""}${row.error ? ` error=${row.error}` : ""}${row.truncated ? " (truncated)" : ""}`);
|
|
308
|
+
console.log("\n--- request (forwarded verbatim) ---");
|
|
309
|
+
console.log(prettyJson(row.request));
|
|
310
|
+
if (row.response !== undefined) {
|
|
311
|
+
console.log("\n--- response (upstream) ---");
|
|
312
|
+
console.log(prettyJson(row.response));
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (!cs.length) return console.log("No captures yet — call the model, then `show` again (or pass an index to dump one).");
|
|
317
|
+
console.log(`${name}: ${cs.length} captured (newest first)`);
|
|
318
|
+
for (let i = 0; i < cs.length; i++) {
|
|
319
|
+
const c = cs[i];
|
|
320
|
+
console.log(
|
|
321
|
+
` #${i + 1} ${new Date(c.ts).toLocaleTimeString("en-GB", { hour12: false })} ${c.provider} [${c.format}] status=${c.status} ${c.ms}ms ${fmtBytes(c.request)} req / ${fmtBytes(c.response ?? "")} resp${c.upstreamModel ? ` model=${c.upstreamModel}` : ""}${c.error ? ` ${c.error}` : ""}`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
console.log("\nDump one: myapikey model debug <name> show <index>");
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
/** Pretty-print when the text parses as JSON (a forwarded request body, a JSON
|
|
328
|
+
* response); raw text (e.g. an SSE stream) goes through untouched. */
|
|
329
|
+
function prettyJson(s: string): string {
|
|
330
|
+
try {
|
|
331
|
+
return JSON.stringify(JSON.parse(s), null, 2);
|
|
332
|
+
} catch {
|
|
333
|
+
return s;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function fmtBytes(s: string): string {
|
|
338
|
+
const n = Buffer.byteLength(s, "utf8");
|
|
339
|
+
return n >= 1024 ? `${(n / 1024).toFixed(1)}KB` : `${n}B`;
|
|
340
|
+
}
|
|
341
|
+
|
|
286
342
|
// ---------------------------------------------------------------------------
|
|
287
343
|
// call
|
|
288
344
|
// ---------------------------------------------------------------------------
|
|
@@ -163,6 +163,7 @@ function projectModel(name: string, e: ModelEntry, byId: Map<string, Provider>)
|
|
|
163
163
|
anthropic: proj(e.anthropic),
|
|
164
164
|
responses: proj(e.responses),
|
|
165
165
|
paceRpm: e.paceRpm ?? 0,
|
|
166
|
+
debugCapture: e.debugCapture === true,
|
|
166
167
|
};
|
|
167
168
|
}
|
|
168
169
|
|
|
@@ -566,6 +567,7 @@ export function adminApi(store: Store, auth: MiddlewareHandler, openai: Hono, an
|
|
|
566
567
|
const cfg = store.get();
|
|
567
568
|
if (!cfg.models[name]) return c.json({ error: { message: "model not found" } }, 404);
|
|
568
569
|
if (cfg.models[next]) return c.json({ error: { message: `model already exists: ${next}` } }, 409);
|
|
570
|
+
store.clearCaptures(name); // captured bodies keyed by the old name are unreachable now
|
|
569
571
|
await store.update((d) => {
|
|
570
572
|
const rebuilt: typeof d.models = {};
|
|
571
573
|
for (const [k, v] of Object.entries(d.models)) rebuilt[k === name ? next : k] = v;
|
|
@@ -692,12 +694,43 @@ export function adminApi(store: Store, auth: MiddlewareHandler, openai: Hono, an
|
|
|
692
694
|
errStatus = 404;
|
|
693
695
|
return;
|
|
694
696
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
697
|
+
if (rpm) entry.paceRpm = rpm;
|
|
698
|
+
else delete entry.paceRpm;
|
|
699
|
+
});
|
|
700
|
+
if (errStatus === 404) return c.json({ error: { message: "model not found" } }, 404);
|
|
701
|
+
return c.json({ ok: true, paceRpm: rpm ?? 0 });
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
// Debug capture switch (pure debugging aid — see ModelEntry.debugCapture).
|
|
705
|
+
// ON: dispatch records every upstream attempt for this model (exact forwarded
|
|
706
|
+
// request + response bodies) into an in-memory ring buffer, last 50. OFF: the
|
|
707
|
+
// buffer is cleared immediately — captured conversations shouldn't outlive the
|
|
708
|
+
// debugging session. The switch itself is config state (persists in data.json);
|
|
709
|
+
// the captured bodies do not (in-memory only, restart clears them too).
|
|
710
|
+
app.get("/models/:name/debug", (c) => {
|
|
711
|
+
const name = c.req.param("name");
|
|
712
|
+
if (!store.get().models[name]) return c.json({ error: { message: "model not found" } }, 404);
|
|
713
|
+
return c.json({ enabled: store.isDebug(name), captures: store.getCaptures(name) });
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
app.put("/models/:name/debug", async (c) => {
|
|
717
|
+
const name = c.req.param("name");
|
|
718
|
+
const body = await readJson<{ enabled?: unknown }>(c.req.raw);
|
|
719
|
+
const enabled = body?.enabled === true;
|
|
720
|
+
let errStatus = 0;
|
|
721
|
+
await store.update((d) => {
|
|
722
|
+
const entry = d.models[name];
|
|
723
|
+
if (!entry) {
|
|
724
|
+
errStatus = 404;
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
if (enabled) entry.debugCapture = true;
|
|
728
|
+
else delete entry.debugCapture;
|
|
700
729
|
});
|
|
730
|
+
if (errStatus === 404) return c.json({ error: { message: "model not found" } }, 404);
|
|
731
|
+
if (!enabled) store.clearCaptures(name);
|
|
732
|
+
return c.json({ ok: true, enabled });
|
|
733
|
+
});
|
|
701
734
|
|
|
702
735
|
// Set (or clear) the upstream-model mapping for ONE chain slot (addressed by
|
|
703
736
|
// `index`). An empty `model` clears it (back to identity — send the public
|
|
@@ -904,6 +937,7 @@ export function adminApi(store: Store, auth: MiddlewareHandler, openai: Hono, an
|
|
|
904
937
|
await store.update((d) => {
|
|
905
938
|
delete d.models[name];
|
|
906
939
|
});
|
|
940
|
+
store.clearCaptures(name);
|
|
907
941
|
return c.json({ ok: true });
|
|
908
942
|
});
|
|
909
943
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Hono, type Context, type MiddlewareHandler } from "hono";
|
|
2
2
|
import { trimBase } from "../shared/config";
|
|
3
|
-
import type { Format, Provider, RouteKey, Usage } from "../shared/types";
|
|
4
|
-
import type
|
|
3
|
+
import type { DebugCapture, Format, Provider, RouteKey, Usage } from "../shared/types";
|
|
4
|
+
import { CAPTURE_BODY_MAX, type Store } from "./store";
|
|
5
5
|
import { UsageCollector } from "./tokens";
|
|
6
6
|
|
|
7
7
|
/** HTTP statuses that should trigger failover to the next provider. 401/403
|
|
@@ -342,6 +342,10 @@ function observedBody(
|
|
|
342
342
|
/** The original request's `messages`, used only to estimate prompt tokens
|
|
343
343
|
* on the openai-chat-stream fallback path (see tokens.ts). */
|
|
344
344
|
requestMessages?: unknown;
|
|
345
|
+
/** Called with each decoded text chunk AS IT FLOWS — the debug capture's
|
|
346
|
+
* tee point (the proxy keeps forwarding bytes verbatim regardless).
|
|
347
|
+
* Optional: absent = zero capture overhead. */
|
|
348
|
+
onText?: (txt: string) => void;
|
|
345
349
|
onSettle: (info: SettleInfo) => void;
|
|
346
350
|
},
|
|
347
351
|
): ReadableStream<Uint8Array> {
|
|
@@ -385,6 +389,7 @@ function observedBody(
|
|
|
385
389
|
}
|
|
386
390
|
const txt = dec.decode(value, { stream: true });
|
|
387
391
|
usage.feed(txt, { stream: opts.stream, key: opts.key });
|
|
392
|
+
opts.onText?.(txt);
|
|
388
393
|
if (!terminal && markers.length) {
|
|
389
394
|
const win = tail + txt;
|
|
390
395
|
if (markers.some((m) => win.includes(m))) terminal = true;
|
|
@@ -608,15 +613,45 @@ export function proxyApi(
|
|
|
608
613
|
// Count this attempt toward the source's RPM window — but not for a pinned
|
|
609
614
|
// probe, which (like circuit state) takes no routing side-effects.
|
|
610
615
|
if (pinIndex == null) store.recordDispatch(provider.id);
|
|
616
|
+
// Debug capture (ModelEntry.debugCapture): when the model's switch is
|
|
617
|
+
// on, record EVERY upstream attempt — the exact forwarded body (this
|
|
618
|
+
// slot's model rewrite + thinking injection are already applied) and
|
|
619
|
+
// the response as it flowed. UI probes are excluded (not real
|
|
620
|
+
// conversations). One entry per attempt: a failover chain writes
|
|
621
|
+
// several, each showing what THAT source actually received.
|
|
622
|
+
const debugNow = !isProbe && store.isDebug(model);
|
|
623
|
+
const attemptStart = Date.now();
|
|
624
|
+
const reqText = JSON.stringify(body);
|
|
625
|
+
const capture = (status: number, response: string | undefined, truncated: boolean, error?: string) => {
|
|
626
|
+
if (!debugNow) return;
|
|
627
|
+
const row: DebugCapture = {
|
|
628
|
+
ts: Date.now(),
|
|
629
|
+
model,
|
|
630
|
+
provider: provider.name,
|
|
631
|
+
providerId: provider.id,
|
|
632
|
+
format: wire,
|
|
633
|
+
status,
|
|
634
|
+
ms: Date.now() - attemptStart,
|
|
635
|
+
stream,
|
|
636
|
+
request: reqText,
|
|
637
|
+
...(upstreamModel ? { upstreamModel } : {}),
|
|
638
|
+
...(think ? { thinking: think } : {}),
|
|
639
|
+
...(response ? { response } : {}),
|
|
640
|
+
...(truncated ? { truncated: true } : {}),
|
|
641
|
+
...(error ? { error } : {}),
|
|
642
|
+
};
|
|
643
|
+
store.pushCapture(model, row);
|
|
644
|
+
};
|
|
611
645
|
let upstream: Response;
|
|
612
646
|
try {
|
|
613
647
|
upstream = await fetch(upstreamTarget(provider, key).url, {
|
|
614
648
|
method: "POST",
|
|
615
649
|
headers: upstreamHeaders(provider, wire, clientVersion),
|
|
616
|
-
body:
|
|
650
|
+
body: reqText,
|
|
617
651
|
});
|
|
618
652
|
} catch {
|
|
619
653
|
// Network error / DNS / timeout → try next provider.
|
|
654
|
+
capture(0, undefined, false, "network error");
|
|
620
655
|
lastStatus = 502;
|
|
621
656
|
lastErr = "network error";
|
|
622
657
|
if (pinIndex != null) break; // per-source probe: fail fast, no circuit impact.
|
|
@@ -642,20 +677,38 @@ export function proxyApi(
|
|
|
642
677
|
// the body's end (so the row reflects the real outcome, not just the
|
|
643
678
|
// headers). See observedBody() for the detection rules.
|
|
644
679
|
const ttfb = Date.now() - start;
|
|
680
|
+
// Debug capture tee: accumulate the decoded chunks into a bounded
|
|
681
|
+
// string (the proxy keeps forwarding bytes verbatim regardless).
|
|
682
|
+
const capAcc = debugNow ? { text: "", truncated: false } : undefined;
|
|
645
683
|
const out = observedBody(upstream, {
|
|
646
684
|
stream,
|
|
647
685
|
key,
|
|
648
686
|
requestMessages: body.messages,
|
|
687
|
+
...(capAcc
|
|
688
|
+
? {
|
|
689
|
+
onText: (txt: string) => {
|
|
690
|
+
const room = CAPTURE_BODY_MAX - capAcc.text.length;
|
|
691
|
+
if (room <= 0) {
|
|
692
|
+
capAcc.truncated = true;
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (txt.length > room) capAcc.truncated = true;
|
|
696
|
+
capAcc.text += txt.slice(0, room);
|
|
697
|
+
},
|
|
698
|
+
}
|
|
699
|
+
: {}),
|
|
649
700
|
onSettle: (info) => {
|
|
650
701
|
if (info.ok) {
|
|
651
702
|
store.recordCircuitSuccess(provider.id);
|
|
652
703
|
store.pushLog({ ts: Date.now(), model, upstreamModel, provider: provider.name, providerId: provider.id, format: wire, status: 200, ms: ttfb, stream, thinking: think, usage: info.usage });
|
|
704
|
+
capture(200, capAcc?.text, capAcc?.truncated ?? false);
|
|
653
705
|
} else {
|
|
654
706
|
// A pinned per-source probe takes no circuit side-effects (a manual
|
|
655
707
|
// test must not trip the breaker) — mirrors the retryable branch.
|
|
656
708
|
if (pinIndex == null) store.recordCircuitFailure(provider.id, info.status, info.error || "stream failed");
|
|
657
709
|
if (!isProbe) rt.warn(`proxy stream failed: provider '${provider.name}' status=${info.status} (${info.error || "stream failed"})`);
|
|
658
710
|
store.pushLog({ ts: Date.now(), model, upstreamModel, provider: provider.name, providerId: provider.id, format: wire, status: info.status, ms: ttfb, stream, thinking: think, error: info.error });
|
|
711
|
+
capture(info.status, capAcc?.text, capAcc?.truncated ?? false, info.error);
|
|
659
712
|
}
|
|
660
713
|
},
|
|
661
714
|
});
|
|
@@ -667,6 +720,7 @@ export function proxyApi(
|
|
|
667
720
|
// reason for the log (this branch never streams back to the client).
|
|
668
721
|
const txt = await upstream.text().catch(() => "");
|
|
669
722
|
lastErr = shortError(txt) || `HTTP ${upstream.status}`;
|
|
723
|
+
capture(upstream.status, txt, false, lastErr);
|
|
670
724
|
if (pinIndex != null) break; // per-source probe: fail fast, no circuit impact.
|
|
671
725
|
sayFailover(provider, `HTTP ${lastStatus} (${lastErr})`);
|
|
672
726
|
// A 429/overloaded upstream usually carries Retry-After; honoring it
|
|
@@ -687,6 +741,7 @@ export function proxyApi(
|
|
|
687
741
|
// error text off a CLONE so the original body still streams back.
|
|
688
742
|
const errText = await upstream.clone().text().catch(() => "");
|
|
689
743
|
store.pushLog({ ts: Date.now(), model, upstreamModel, provider: provider.name, providerId: provider.id, format: wire, status: upstream.status, ms: Date.now() - start, stream, thinking: think, error: shortError(errText) || `HTTP ${upstream.status}` });
|
|
744
|
+
capture(upstream.status, errText, false, shortError(errText) || `HTTP ${upstream.status}`);
|
|
690
745
|
return passThrough(upstream, isProbe ? provider.name : undefined);
|
|
691
746
|
}
|
|
692
747
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { defaultConfig, newApiKey, CONFIG_VERSION } from "../shared/config";
|
|
4
|
-
import type { GateConfig, LogEntry, Provider } from "../shared/types";
|
|
4
|
+
import type { DebugCapture, GateConfig, LogEntry, Provider } from "../shared/types";
|
|
5
5
|
import { createLogger, type Logger } from "./logger";
|
|
6
6
|
|
|
7
7
|
/** Call-log retention: the log is bounded two ways — never older than this, and
|
|
@@ -45,6 +45,14 @@ const RPM_WINDOW_MS = 60_000;
|
|
|
45
45
|
* time and the queue depth (at N rpm / 60s wait, at most ~N requests queue). */
|
|
46
46
|
const PACE_MAX_WAIT_MS = 60_000;
|
|
47
47
|
|
|
48
|
+
/** Debug capture (per-model, in-memory ring buffer): how many upstream attempts
|
|
49
|
+
* to keep, and how many CHARS of each request/response body. 50 × 2 × 256KB ≈
|
|
50
|
+
* 25MB worst case per debugged model — acceptable for a home-server debug aid.
|
|
51
|
+
* Bodies (conversations) are sensitive: the buffer lives only in memory, is
|
|
52
|
+
* dropped when the model's debug switch turns off, and never hits disk. */
|
|
53
|
+
export const CAPTURE_MAX = 50;
|
|
54
|
+
export const CAPTURE_BODY_MAX = 256 * 1024;
|
|
55
|
+
|
|
48
56
|
/** Per-provider circuit state (in-memory, never persisted). */
|
|
49
57
|
interface CircuitEntry {
|
|
50
58
|
fails: number;
|
|
@@ -191,6 +199,11 @@ export class Store {
|
|
|
191
199
|
/** Per-model even-pacing queue: model name -> epoch ms of the next free
|
|
192
200
|
* release slot. In-memory, NOT persisted (resets on restart). */
|
|
193
201
|
private pace = new Map<string, number>();
|
|
202
|
+
/** Per-model debug-capture ring buffers: model name -> last CAPTURE_MAX
|
|
203
|
+
* upstream attempts (oldest first). In-memory, NOT persisted — bodies are
|
|
204
|
+
* conversation content, so they live only while the model's debug switch
|
|
205
|
+
* is on and evaporate on restart or toggle-off. */
|
|
206
|
+
private captures = new Map<string, DebugCapture[]>();
|
|
194
207
|
|
|
195
208
|
constructor(dataDir: string, opts: { logger?: Logger } = {}) {
|
|
196
209
|
this.dataDir = dataDir;
|
|
@@ -649,6 +662,45 @@ export class Store {
|
|
|
649
662
|
return wait;
|
|
650
663
|
}
|
|
651
664
|
|
|
665
|
+
// --- debug capture (per model, in-memory ring buffer; never persisted) ---
|
|
666
|
+
|
|
667
|
+
/** Whether a model's debug capture switch is on (config state, persisted). */
|
|
668
|
+
isDebug(model: string): boolean {
|
|
669
|
+
return !!this.data.models[model]?.debugCapture;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/** Record one upstream attempt for a debugged model. Ignores silently when
|
|
673
|
+
* the switch is off (defense against a stale caller); bodies are capped at
|
|
674
|
+
* CAPTURE_BODY_MAX chars each (flagging `truncated`), and the buffer keeps
|
|
675
|
+
* only the most recent CAPTURE_MAX attempts per model. */
|
|
676
|
+
pushCapture(model: string, entry: DebugCapture): void {
|
|
677
|
+
if (!this.isDebug(model)) return;
|
|
678
|
+
const e = { ...entry };
|
|
679
|
+
if (e.request.length > CAPTURE_BODY_MAX) {
|
|
680
|
+
e.request = e.request.slice(0, CAPTURE_BODY_MAX);
|
|
681
|
+
e.truncated = true;
|
|
682
|
+
}
|
|
683
|
+
if (e.response !== undefined && e.response.length > CAPTURE_BODY_MAX) {
|
|
684
|
+
e.response = e.response.slice(0, CAPTURE_BODY_MAX);
|
|
685
|
+
e.truncated = true;
|
|
686
|
+
}
|
|
687
|
+
let arr = this.captures.get(model);
|
|
688
|
+
if (!arr) this.captures.set(model, (arr = []));
|
|
689
|
+
arr.push(e);
|
|
690
|
+
if (arr.length > CAPTURE_MAX) arr.splice(0, arr.length - CAPTURE_MAX);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/** The model's captured attempts, newest first. */
|
|
694
|
+
getCaptures(model: string): DebugCapture[] {
|
|
695
|
+
const arr = this.captures.get(model);
|
|
696
|
+
return arr ? [...arr].reverse() : [];
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/** Drop a model's capture buffer (toggle-off, model delete/rename). */
|
|
700
|
+
clearCaptures(model: string): void {
|
|
701
|
+
this.captures.delete(model);
|
|
702
|
+
}
|
|
703
|
+
|
|
652
704
|
/** Snapshot of every configured provider's circuit state for GET /admin/circuit.
|
|
653
705
|
* Healthy providers appear as state "open"; a provider deleted while cooling
|
|
654
706
|
* simply drops out (we iterate the live config, not the map). */
|
|
@@ -100,6 +100,12 @@ export interface ModelEntry {
|
|
|
100
100
|
* independent of Provider.rpm (that one skips a busy source and fails over);
|
|
101
101
|
* this one paces the model across all three route slots. Absent = unlimited. */
|
|
102
102
|
paceRpm?: number;
|
|
103
|
+
/** Debug capture (pure debugging aid, never on by default). When true, dispatch
|
|
104
|
+
* records every UPSTREAM ATTEMPT for this model — the exact forwarded request
|
|
105
|
+
* body and the response body — into an in-memory ring buffer (last 50, see
|
|
106
|
+
* Store.pushCapture). Turning it off clears the buffer; restarts clear it too.
|
|
107
|
+
* Nothing here ever touches logs.jsonl. */
|
|
108
|
+
debugCapture?: boolean;
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
export interface Account {
|
|
@@ -181,3 +187,38 @@ export interface LogEntry {
|
|
|
181
187
|
cooldownMs?: number;
|
|
182
188
|
fails?: number;
|
|
183
189
|
}
|
|
190
|
+
|
|
191
|
+
/** One debug-captured upstream attempt (in-memory ring buffer ONLY — never
|
|
192
|
+
* persisted to logs.jsonl or data.json; see ModelEntry.debugCapture). One
|
|
193
|
+
* client call that fails over produces several entries, one per attempt,
|
|
194
|
+
* because each attempt's forwarded body can differ (per-slot model rewrite +
|
|
195
|
+
* thinking injection). Bodies are captured VERBATIM: `request` is the exact
|
|
196
|
+
* JSON string sent upstream, `response` the upstream body as it flowed (raw
|
|
197
|
+
* SSE text for streams). Headers are never captured — provider api keys stay
|
|
198
|
+
* out of the buffer. */
|
|
199
|
+
export interface DebugCapture {
|
|
200
|
+
ts: number;
|
|
201
|
+
model: string;
|
|
202
|
+
provider: string;
|
|
203
|
+
providerId: string;
|
|
204
|
+
format: Format;
|
|
205
|
+
/** The upstream model name actually sent this attempt (post per-slot
|
|
206
|
+
* rewrite). Absent when the public name went through verbatim. */
|
|
207
|
+
upstreamModel?: string;
|
|
208
|
+
/** Upstream HTTP status (0 = network error / never reached). */
|
|
209
|
+
status: number;
|
|
210
|
+
/** Latency of THIS attempt (ms). */
|
|
211
|
+
ms: number;
|
|
212
|
+
stream: boolean;
|
|
213
|
+
/** The exact forwarded request body (JSON text). */
|
|
214
|
+
request: string;
|
|
215
|
+
/** The upstream response body as it flowed. Absent when nothing was read
|
|
216
|
+
* (network error) or the client cancelled before the body flowed. */
|
|
217
|
+
response?: string;
|
|
218
|
+
/** True when a body was cut at the capture size cap. */
|
|
219
|
+
truncated?: boolean;
|
|
220
|
+
/** Short failure reason (mirrors LogEntry.error). */
|
|
221
|
+
error?: string;
|
|
222
|
+
/** The thinking level this attempt ran with (same shape as LogEntry.thinking). */
|
|
223
|
+
thinking?: { value: string; from: "client" | "default" };
|
|
224
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@keyframes row-flash-35cb6f16{0%{background-color:color-mix(in oklab,var(--primary) 16%,transparent)}to{background-color:transparent}}.row-flash[data-v-35cb6f16]{animation:row-flash-35cb6f16 1.4s ease-out}/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-500:oklch(63.7% .237 25.331);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-teal-400:oklch(77.7% .152 181.912);--color-teal-500:oklch(70.4% .14 182.503);--color-teal-600:oklch(60% .118 184.704);--color-cyan-400:oklch(78.9% .154 211.53);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-sky-400:oklch(74.6% .16 232.661);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-blue-500:oklch(62.3% .214 259.815);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-violet-300:oklch(81.1% .111 293.571);--color-violet-400:oklch(70.2% .183 293.541);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-600:oklch(54.1% .281 293.009);--color-violet-700:oklch(49.1% .27 292.581);--color-fuchsia-400:oklch(74% .238 322.16);--color-fuchsia-500:oklch(66.7% .295 322.15);--color-fuchsia-600:oklch(59.1% .293 322.896);--color-pink-400:oklch(71.8% .202 349.761);--color-pink-500:oklch(65.6% .241 354.308);--color-pink-600:oklch(59.2% .249 .584);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-3xl:48rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-wide:.025em;--leading-tight:1.25;--leading-relaxed:1.625;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:hsl(var(--border))}body{background-color:hsl(var(--background));color:hsl(var(--foreground));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-feature-settings:"rlig" 1,"calt" 1}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.inset-x-0{inset-inline:0}.top-0{top:0}.top-1{top:var(--spacing)}.top-1\/2{top:50%}.top-4{top:calc(var(--spacing) * 4)}.top-\[50\%\]{top:50%}.right-1{right:var(--spacing)}.right-2{right:calc(var(--spacing) * 2)}.right-4{right:calc(var(--spacing) * 4)}.bottom-0{bottom:0}.bottom-full{bottom:100%}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing) * 2)}.left-2\.5{left:calc(var(--spacing) * 2.5)}.left-\[50\%\]{left:50%}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.z-\[60\]{z-index:60}.-mx-1{margin-inline:calc(var(--spacing) * -1)}.mx-auto{margin-inline:auto}.my-1{margin-block:var(--spacing)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-4{margin-top:calc(var(--spacing) * 4)}.-mr-1{margin-right:calc(var(--spacing) * -1)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.size-3{width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-11{height:calc(var(--spacing) * 11)}.h-14{height:calc(var(--spacing) * 14)}.h-40{height:calc(var(--spacing) * 40)}.h-full{height:100%}.h-px{height:1px}.max-h-56{max-height:calc(var(--spacing) * 56)}.max-h-72{max-height:calc(var(--spacing) * 72)}.max-h-80{max-height:calc(var(--spacing) * 80)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[90vh\]{max-height:90vh}.min-h-0{min-height:0}.min-h-screen{min-height:100vh}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-1\/3{width:33.3333%}.w-2{width:calc(var(--spacing) * 2)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-11{width:calc(var(--spacing) * 11)}.w-12{width:calc(var(--spacing) * 12)}.w-28{width:calc(var(--spacing) * 28)}.w-32{width:calc(var(--spacing) * 32)}.w-36{width:calc(var(--spacing) * 36)}.w-72{width:calc(var(--spacing) * 72)}.w-\[128px\]{width:128px}.w-\[168px\]{width:168px}.w-auto{width:auto}.w-full{width:100%}.w-px{width:1px}.max-w-3xl{max-width:var(--container-3xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-40{max-width:calc(var(--spacing) * 40)}.max-w-\[120px\]{max-width:120px}.max-w-\[160px\]{max-width:160px}.max-w-\[300px\]{max-width:300px}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.min-w-0{min-width:0}.min-w-4{min-width:calc(var(--spacing) * 4)}.min-w-\[8rem\]{min-width:8rem}.min-w-\[180px\]{min-width:180px}.min-w-\[var\(--reka-select-trigger-width\)\]{min-width:var(--reka-select-trigger-width)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.caption-bottom{caption-side:bottom}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-\[-50\%\]{--tw-translate-x:-50%;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-2{--tw-translate-y:calc(var(--spacing) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-\[-50\%\]{--tw-translate-y:-50%;translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-180{rotate:180deg}.animate-ping{animation:var(--animate-ping)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.resize{resize:both}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-\[3\.5rem_1fr\]{grid-template-columns:3.5rem 1fr}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0{gap:0}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-px{gap:1px}:where(.space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block:0}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-1\.5{column-gap:calc(var(--spacing) * 1.5)}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-1{row-gap:var(--spacing)}.gap-y-1\.5{row-gap:calc(var(--spacing) * 1.5)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[4px\]{border-radius:4px}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-xl{border-radius:calc(var(--radius) + 4px)}.rounded-t-sm{border-top-left-radius:calc(var(--radius) - 4px);border-top-right-radius:calc(var(--radius) - 4px)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-500\/30{border-color:#f99c004d}@supports (color:color-mix(in lab,red,red)){.border-amber-500\/30{border-color:color-mix(in oklab,var(--color-amber-500) 30%,transparent)}}.border-border{border-color:hsl(var(--border))}.border-destructive\/40{border-color:hsl(var(--destructive))}@supports (color:color-mix(in lab,red,red)){.border-destructive\/40{border-color:color-mix(in oklab,hsl(var(--destructive)) 40%,transparent)}}.border-emerald-500\/30{border-color:#00bb7f4d}@supports (color:color-mix(in lab,red,red)){.border-emerald-500\/30{border-color:color-mix(in oklab,var(--color-emerald-500) 30%,transparent)}}.border-emerald-500\/40{border-color:#00bb7f66}@supports (color:color-mix(in lab,red,red)){.border-emerald-500\/40{border-color:color-mix(in oklab,var(--color-emerald-500) 40%,transparent)}}.border-input{border-color:hsl(var(--input))}.border-orange-500\/30{border-color:#fe6e004d}@supports (color:color-mix(in lab,red,red)){.border-orange-500\/30{border-color:color-mix(in oklab,var(--color-orange-500) 30%,transparent)}}.border-primary,.border-primary\/40{border-color:hsl(var(--primary))}@supports (color:color-mix(in lab,red,red)){.border-primary\/40{border-color:color-mix(in oklab,hsl(var(--primary)) 40%,transparent)}}.border-transparent{border-color:#0000}.border-violet-500\/30{border-color:#8d54ff4d}@supports (color:color-mix(in lab,red,red)){.border-violet-500\/30{border-color:color-mix(in oklab,var(--color-violet-500) 30%,transparent)}}.bg-accent{background-color:hsl(var(--accent))}.bg-amber-500{background-color:var(--color-amber-500)}.bg-amber-500\/5{background-color:#f99c000d}@supports (color:color-mix(in lab,red,red)){.bg-amber-500\/5{background-color:color-mix(in oklab,var(--color-amber-500) 5%,transparent)}}.bg-amber-500\/15{background-color:#f99c0026}@supports (color:color-mix(in lab,red,red)){.bg-amber-500\/15{background-color:color-mix(in oklab,var(--color-amber-500) 15%,transparent)}}.bg-background,.bg-background\/50{background-color:hsl(var(--background))}@supports (color:color-mix(in lab,red,red)){.bg-background\/50{background-color:color-mix(in oklab,hsl(var(--background)) 50%,transparent)}}.bg-background\/60{background-color:hsl(var(--background))}@supports (color:color-mix(in lab,red,red)){.bg-background\/60{background-color:color-mix(in oklab,hsl(var(--background)) 60%,transparent)}}.bg-background\/80{background-color:hsl(var(--background))}@supports (color:color-mix(in lab,red,red)){.bg-background\/80{background-color:color-mix(in oklab,hsl(var(--background)) 80%,transparent)}}.bg-black\/80{background-color:#000c}@supports (color:color-mix(in lab,red,red)){.bg-black\/80{background-color:color-mix(in oklab,var(--color-black) 80%,transparent)}}.bg-border{background-color:hsl(var(--border))}.bg-card{background-color:hsl(var(--card))}.bg-current{background-color:currentColor}.bg-cyan-500{background-color:var(--color-cyan-500)}.bg-cyan-500\/15{background-color:#00b7d726}@supports (color:color-mix(in lab,red,red)){.bg-cyan-500\/15{background-color:color-mix(in oklab,var(--color-cyan-500) 15%,transparent)}}.bg-destructive,.bg-destructive\/10{background-color:hsl(var(--destructive))}@supports (color:color-mix(in lab,red,red)){.bg-destructive\/10{background-color:color-mix(in oklab,hsl(var(--destructive)) 10%,transparent)}}.bg-destructive\/15{background-color:hsl(var(--destructive))}@supports (color:color-mix(in lab,red,red)){.bg-destructive\/15{background-color:color-mix(in oklab,hsl(var(--destructive)) 15%,transparent)}}.bg-destructive\/70{background-color:hsl(var(--destructive))}@supports (color:color-mix(in lab,red,red)){.bg-destructive\/70{background-color:color-mix(in oklab,hsl(var(--destructive)) 70%,transparent)}}.bg-emerald-500{background-color:var(--color-emerald-500)}.bg-emerald-500\/5{background-color:#00bb7f0d}@supports (color:color-mix(in lab,red,red)){.bg-emerald-500\/5{background-color:color-mix(in oklab,var(--color-emerald-500) 5%,transparent)}}.bg-emerald-500\/10{background-color:#00bb7f1a}@supports (color:color-mix(in lab,red,red)){.bg-emerald-500\/10{background-color:color-mix(in oklab,var(--color-emerald-500) 10%,transparent)}}.bg-emerald-500\/15{background-color:#00bb7f26}@supports (color:color-mix(in lab,red,red)){.bg-emerald-500\/15{background-color:color-mix(in oklab,var(--color-emerald-500) 15%,transparent)}}.bg-emerald-500\/70{background-color:#00bb7fb3}@supports (color:color-mix(in lab,red,red)){.bg-emerald-500\/70{background-color:color-mix(in oklab,var(--color-emerald-500) 70%,transparent)}}.bg-fuchsia-500{background-color:var(--color-fuchsia-500)}.bg-fuchsia-500\/15{background-color:#e12afb26}@supports (color:color-mix(in lab,red,red)){.bg-fuchsia-500\/15{background-color:color-mix(in oklab,var(--color-fuchsia-500) 15%,transparent)}}.bg-indigo-500{background-color:var(--color-indigo-500)}.bg-indigo-500\/15{background-color:#625fff26}@supports (color:color-mix(in lab,red,red)){.bg-indigo-500\/15{background-color:color-mix(in oklab,var(--color-indigo-500) 15%,transparent)}}.bg-muted,.bg-muted\/20{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.bg-muted\/20{background-color:color-mix(in oklab,hsl(var(--muted)) 20%,transparent)}}.bg-muted\/30{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.bg-muted\/30{background-color:color-mix(in oklab,hsl(var(--muted)) 30%,transparent)}}.bg-muted\/40{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.bg-muted\/40{background-color:color-mix(in oklab,hsl(var(--muted)) 40%,transparent)}}.bg-muted\/50{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.bg-muted\/50{background-color:color-mix(in oklab,hsl(var(--muted)) 50%,transparent)}}.bg-muted\/60{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.bg-muted\/60{background-color:color-mix(in oklab,hsl(var(--muted)) 60%,transparent)}}.bg-orange-500{background-color:var(--color-orange-500)}.bg-orange-500\/5{background-color:#fe6e000d}@supports (color:color-mix(in lab,red,red)){.bg-orange-500\/5{background-color:color-mix(in oklab,var(--color-orange-500) 5%,transparent)}}.bg-orange-500\/15{background-color:#fe6e0026}@supports (color:color-mix(in lab,red,red)){.bg-orange-500\/15{background-color:color-mix(in oklab,var(--color-orange-500) 15%,transparent)}}.bg-pink-500{background-color:var(--color-pink-500)}.bg-pink-500\/15{background-color:#f6339a26}@supports (color:color-mix(in lab,red,red)){.bg-pink-500\/15{background-color:color-mix(in oklab,var(--color-pink-500) 15%,transparent)}}.bg-popover,.bg-popover\/95{background-color:hsl(var(--popover))}@supports (color:color-mix(in lab,red,red)){.bg-popover\/95{background-color:color-mix(in oklab,hsl(var(--popover)) 95%,transparent)}}.bg-primary,.bg-primary\/5{background-color:hsl(var(--primary))}@supports (color:color-mix(in lab,red,red)){.bg-primary\/5{background-color:color-mix(in oklab,hsl(var(--primary)) 5%,transparent)}}.bg-primary\/15{background-color:hsl(var(--primary))}@supports (color:color-mix(in lab,red,red)){.bg-primary\/15{background-color:color-mix(in oklab,hsl(var(--primary)) 15%,transparent)}}.bg-primary\/50{background-color:hsl(var(--primary))}@supports (color:color-mix(in lab,red,red)){.bg-primary\/50{background-color:color-mix(in oklab,hsl(var(--primary)) 50%,transparent)}}.bg-rose-500{background-color:var(--color-rose-500)}.bg-rose-500\/15{background-color:#ff235726}@supports (color:color-mix(in lab,red,red)){.bg-rose-500\/15{background-color:color-mix(in oklab,var(--color-rose-500) 15%,transparent)}}.bg-secondary{background-color:hsl(var(--secondary))}.bg-sky-500{background-color:var(--color-sky-500)}.bg-sky-500\/15{background-color:#00a5ef26}@supports (color:color-mix(in lab,red,red)){.bg-sky-500\/15{background-color:color-mix(in oklab,var(--color-sky-500) 15%,transparent)}}.bg-teal-500{background-color:var(--color-teal-500)}.bg-teal-500\/15{background-color:#00baa726}@supports (color:color-mix(in lab,red,red)){.bg-teal-500\/15{background-color:color-mix(in oklab,var(--color-teal-500) 15%,transparent)}}.bg-transparent{background-color:#0000}.bg-violet-500{background-color:var(--color-violet-500)}.bg-violet-500\/5{background-color:#8d54ff0d}@supports (color:color-mix(in lab,red,red)){.bg-violet-500\/5{background-color:color-mix(in oklab,var(--color-violet-500) 5%,transparent)}}.bg-violet-500\/15{background-color:#8d54ff26}@supports (color:color-mix(in lab,red,red)){.bg-violet-500\/15{background-color:color-mix(in oklab,var(--color-violet-500) 15%,transparent)}}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:var(--spacing)}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2{padding:calc(var(--spacing) * 2)}.p-2\.5{padding:calc(var(--spacing) * 2.5)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.px-0\.5{padding-inline:calc(var(--spacing) * .5)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-0{padding-block:0}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-12{padding-block:calc(var(--spacing) * 12)}.pt-0{padding-top:0}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-9{padding-top:calc(var(--spacing) * 9)}.pr-1{padding-right:var(--spacing)}.pr-1\.5{padding-right:calc(var(--spacing) * 1.5)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pr-9{padding-right:calc(var(--spacing) * 9)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pl-2{padding-left:calc(var(--spacing) * 2)}.pl-2\.5{padding-left:calc(var(--spacing) * 2.5)}.pl-6{padding-left:calc(var(--spacing) * 6)}.pl-7{padding-left:calc(var(--spacing) * 7)}.pl-8{padding-left:calc(var(--spacing) * 8)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-accent-foreground{color:hsl(var(--accent-foreground))}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-blue-500{color:var(--color-blue-500)}.text-card-foreground{color:hsl(var(--card-foreground))}.text-current{color:currentColor}.text-cyan-600{color:var(--color-cyan-600)}.text-destructive{color:hsl(var(--destructive))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-emerald-500{color:var(--color-emerald-500)}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-700{color:var(--color-emerald-700)}.text-foreground,.text-foreground\/80{color:hsl(var(--foreground))}@supports (color:color-mix(in lab,red,red)){.text-foreground\/80{color:color-mix(in oklab,hsl(var(--foreground)) 80%,transparent)}}.text-fuchsia-600{color:var(--color-fuchsia-600)}.text-indigo-600{color:var(--color-indigo-600)}.text-muted-foreground,.text-muted-foreground\/40{color:hsl(var(--muted-foreground))}@supports (color:color-mix(in lab,red,red)){.text-muted-foreground\/40{color:color-mix(in oklab,hsl(var(--muted-foreground)) 40%,transparent)}}.text-muted-foreground\/50{color:hsl(var(--muted-foreground))}@supports (color:color-mix(in lab,red,red)){.text-muted-foreground\/50{color:color-mix(in oklab,hsl(var(--muted-foreground)) 50%,transparent)}}.text-muted-foreground\/60{color:hsl(var(--muted-foreground))}@supports (color:color-mix(in lab,red,red)){.text-muted-foreground\/60{color:color-mix(in oklab,hsl(var(--muted-foreground)) 60%,transparent)}}.text-muted-foreground\/70{color:hsl(var(--muted-foreground))}@supports (color:color-mix(in lab,red,red)){.text-muted-foreground\/70{color:color-mix(in oklab,hsl(var(--muted-foreground)) 70%,transparent)}}.text-orange-600{color:var(--color-orange-600)}.text-orange-700{color:var(--color-orange-700)}.text-pink-600{color:var(--color-pink-600)}.text-popover-foreground{color:hsl(var(--popover-foreground))}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-red-500{color:var(--color-red-500)}.text-rose-600{color:var(--color-rose-600)}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-sky-600{color:var(--color-sky-600)}.text-teal-600{color:var(--color-teal-600)}.text-violet-600{color:var(--color-violet-600)}.text-violet-700{color:var(--color-violet-700)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline-offset-4{text-underline-offset:4px}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-primary\/60{--tw-ring-color:hsl(var(--primary))}@supports (color:color-mix(in lab,red,red)){.ring-primary\/60{--tw-ring-color:color-mix(in oklab, hsl(var(--primary)) 60%, transparent)}}.ring-offset-background{--tw-ring-offset-color:hsl(var(--background))}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.running{animation-play-state:running}@media (hover:hover){.group-hover\:block:is(:where(.group):hover *){display:block}.group-hover\:bg-destructive:is(:where(.group):hover *){background-color:hsl(var(--destructive))}.group-hover\:bg-emerald-500:is(:where(.group):hover *){background-color:var(--color-emerald-500)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.focus-within\:opacity-100:focus-within{opacity:1}@media (hover:hover){.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-amber-500\/10:hover{background-color:#f99c001a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-amber-500\/10:hover{background-color:color-mix(in oklab,var(--color-amber-500) 10%,transparent)}}.hover\:bg-destructive\/10:hover{background-color:hsl(var(--destructive))}@supports (color:color-mix(in lab,red,red)){.hover\:bg-destructive\/10:hover{background-color:color-mix(in oklab,hsl(var(--destructive)) 10%,transparent)}}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive))}@supports (color:color-mix(in lab,red,red)){.hover\:bg-destructive\/90:hover{background-color:color-mix(in oklab,hsl(var(--destructive)) 90%,transparent)}}.hover\:bg-muted:hover,.hover\:bg-muted\/30:hover{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.hover\:bg-muted\/30:hover{background-color:color-mix(in oklab,hsl(var(--muted)) 30%,transparent)}}.hover\:bg-muted\/40:hover{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.hover\:bg-muted\/40:hover{background-color:color-mix(in oklab,hsl(var(--muted)) 40%,transparent)}}.hover\:bg-muted\/50:hover{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.hover\:bg-muted\/50:hover{background-color:color-mix(in oklab,hsl(var(--muted)) 50%,transparent)}}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary))}@supports (color:color-mix(in lab,red,red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab,hsl(var(--primary)) 90%,transparent)}}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary))}@supports (color:color-mix(in lab,red,red)){.hover\:bg-secondary\/80:hover{background-color:color-mix(in oklab,hsl(var(--secondary)) 80%,transparent)}}.hover\:bg-transparent:hover{background-color:#0000}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-destructive:hover{color:hsl(var(--destructive))}.hover\:text-foreground:hover{color:hsl(var(--foreground))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}}.focus\:bg-accent:focus{background-color:hsl(var(--accent))}.focus\:bg-destructive\/10:focus{background-color:hsl(var(--destructive))}@supports (color:color-mix(in lab,red,red)){.focus\:bg-destructive\/10:focus{background-color:color-mix(in oklab,hsl(var(--destructive)) 10%,transparent)}}.focus\:text-accent-foreground:focus{color:hsl(var(--accent-foreground))}.focus\:text-destructive:focus{color:hsl(var(--destructive))}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-ring:focus{--tw-ring-color:hsl(var(--ring))}.focus\:ring-offset-1:focus{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-background:focus{--tw-ring-offset-color:hsl(var(--background))}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:opacity-100:focus-visible{opacity:1}.focus-visible\:ring-1:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color:hsl(var(--ring))}.focus-visible\:ring-offset-1:focus-visible{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background:focus-visible{--tw-ring-offset-color:hsl(var(--background))}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[inset\]\:pl-8[data-inset]{padding-left:calc(var(--spacing) * 8)}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y:var(--spacing);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y:calc(2*var(--spacing)*-1)}.data-\[side\=left\]\:translate-x-1[data-side=left]{--tw-translate-x:var(--spacing);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=left\]\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x:calc(2*var(--spacing))}.data-\[side\=right\]\:-translate-x-1[data-side=right]{--tw-translate-x:calc(var(--spacing) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=right\]\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x:calc(2*var(--spacing)*-1)}.data-\[side\=top\]\:-translate-y-1[data-side=top]{--tw-translate-y:calc(var(--spacing) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y:calc(2*var(--spacing))}.data-\[state\=active\]\:bg-background[data-state=active]{background-color:hsl(var(--background))}.data-\[state\=active\]\:text-foreground[data-state=active]{color:hsl(var(--foreground))}.data-\[state\=active\]\:shadow[data-state=active]{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[state\=checked\]\:translate-x-4[data-state=checked]{--tw-translate-x:calc(var(--spacing) * 4);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[state\=checked\]\:bg-primary[data-state=checked]{background-color:hsl(var(--primary))}.data-\[state\=checked\]\:text-primary-foreground[data-state=checked]{color:hsl(var(--primary-foreground))}.data-\[state\=closed\]\:animate-out[data-state=closed]{animation:exit var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity:0}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale:.95}.data-\[state\=open\]\:animate-in[data-state=open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=open\]\:fade-in-0[data-state=open]{--tw-enter-opacity:0}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale:.95}.data-\[state\=selected\]\:bg-muted[data-state=selected]{background-color:hsl(var(--muted))}.data-\[state\=unchecked\]\:translate-x-0[data-state=unchecked]{--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[state\=unchecked\]\:bg-input[data-state=unchecked]{background-color:hsl(var(--input))}@media (min-width:40rem){.sm\:max-w-sm{max-width:var(--container-sm)}.sm\:translate-x-2{--tw-translate-x:calc(var(--spacing) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.sm\:translate-y-0{--tw-translate-y:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-\[auto_1fr_auto_1fr\]{grid-template-columns:auto 1fr auto 1fr}.sm\:flex-row{flex-direction:row}.sm\:items-end{align-items:flex-end}.sm\:justify-end{justify-content:flex-end}.sm\:rounded-lg{border-radius:var(--radius)}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}}.dark\:text-amber-300:is(.dark *){color:var(--color-amber-300)}.dark\:text-amber-400:is(.dark *){color:var(--color-amber-400)}.dark\:text-cyan-400:is(.dark *){color:var(--color-cyan-400)}.dark\:text-emerald-300:is(.dark *){color:var(--color-emerald-300)}.dark\:text-emerald-400:is(.dark *){color:var(--color-emerald-400)}.dark\:text-fuchsia-400:is(.dark *){color:var(--color-fuchsia-400)}.dark\:text-indigo-400:is(.dark *){color:var(--color-indigo-400)}.dark\:text-orange-400:is(.dark *){color:var(--color-orange-400)}.dark\:text-pink-400:is(.dark *){color:var(--color-pink-400)}.dark\:text-rose-400:is(.dark *){color:var(--color-rose-400)}.dark\:text-sky-400:is(.dark *){color:var(--color-sky-400)}.dark\:text-teal-400:is(.dark *){color:var(--color-teal-400)}.dark\:text-violet-300:is(.dark *){color:var(--color-violet-300)}.dark\:text-violet-400:is(.dark *){color:var(--color-violet-400)}.\[\&_svg\]\:size-4 svg{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_tr\]\:border-b tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.\[\&_tr\:last-child\]\:border-0 tr:last-child{border-style:var(--tw-border-style);border-width:0}.\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]){padding-right:0}.\[\&\>span\]\:line-clamp-1>span{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}:root{--background:0 0% 100%;--foreground:240 10% 3.9%;--card:0 0% 100%;--card-foreground:240 10% 3.9%;--popover:0 0% 100%;--popover-foreground:240 10% 3.9%;--primary:222 89% 55%;--primary-foreground:0 0% 100%;--secondary:240 4.8% 95.9%;--secondary-foreground:240 5.9% 10%;--muted:240 4.8% 95.9%;--muted-foreground:240 3.8% 46.1%;--accent:240 4.8% 95.9%;--accent-foreground:240 5.9% 10%;--destructive:0 72% 51%;--destructive-foreground:0 0% 98%;--border:240 5.9% 90%;--input:240 5.9% 90%;--ring:222 89% 55%;--radius:.6rem}.dark{--background:222 22% 7%;--foreground:210 20% 96%;--card:222 20% 9%;--card-foreground:210 20% 96%;--popover:222 20% 9%;--popover-foreground:210 20% 96%;--primary:217 91% 60%;--primary-foreground:222 47% 11%;--secondary:222 16% 16%;--secondary-foreground:210 20% 96%;--muted:222 16% 14%;--muted-foreground:215 16% 62%;--accent:222 16% 16%;--accent-foreground:210 20% 96%;--destructive:0 62% 45%;--destructive-foreground:0 0% 98%;--border:222 14% 18%;--input:222 14% 18%;--ring:217 91% 60%}*{scrollbar-width:thin;scrollbar-color:hsl(var(--border)) transparent}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0));filter:blur(var(--tw-enter-blur,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0)scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1))rotate(var(--tw-exit-rotate,0));filter:blur(var(--tw-exit-blur,0))}}
|