llm-simple-router 0.5.2 → 0.5.4
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/config/recommended-providers.json +234 -19
- package/dist/admin/api-response.d.ts +0 -1
- package/dist/admin/api-response.js +8 -4
- package/dist/admin/groups.js +35 -0
- package/dist/admin/monitor.js +2 -0
- package/dist/admin/providers.js +188 -22
- package/dist/admin/proxy-enhancement.js +9 -9
- package/dist/config/model-context.d.ts +10 -0
- package/dist/config/model-context.js +105 -0
- package/dist/db/index.d.ts +3 -1
- package/dist/db/index.js +2 -1
- package/dist/db/logs.d.ts +4 -0
- package/dist/db/logs.js +7 -3
- package/dist/db/mappings.d.ts +2 -1
- package/dist/db/mappings.js +2 -2
- package/dist/db/migrations/023_create_provider_model_info.sql +8 -0
- package/dist/db/migrations/024_add_mapping_groups_is_active.sql +1 -0
- package/dist/db/migrations/025_add_client_status_code.sql +3 -0
- package/dist/db/model-info.d.ts +14 -0
- package/dist/db/model-info.js +27 -0
- package/dist/db/providers.d.ts +1 -0
- package/dist/db/providers.js +1 -1
- package/dist/index.js +15 -3
- package/dist/middleware/auth.js +1 -1
- package/dist/monitor/request-tracker.d.ts +2 -0
- package/dist/monitor/request-tracker.js +18 -0
- package/dist/proxy/anthropic.js +13 -0
- package/dist/proxy/enhancement/directive-parser.d.ts +8 -2
- package/dist/proxy/enhancement/directive-parser.js +44 -17
- package/dist/proxy/enhancement/enhancement-handler.js +184 -54
- package/dist/proxy/enhancement/index.d.ts +1 -1
- package/dist/proxy/enhancement/index.js +1 -1
- package/dist/proxy/enhancement-config.d.ts +6 -0
- package/dist/proxy/enhancement-config.js +19 -0
- package/dist/proxy/openai.js +40 -3
- package/dist/proxy/overflow.d.ts +18 -0
- package/dist/proxy/overflow.js +128 -0
- package/dist/proxy/patch/deepseek/index.d.ts +6 -0
- package/dist/proxy/patch/deepseek/index.js +11 -0
- package/dist/proxy/patch/deepseek/patch-orphan-tool-results.d.ts +12 -0
- package/dist/proxy/patch/deepseek/patch-orphan-tool-results.js +90 -0
- package/dist/proxy/patch/deepseek/patch-thinking-blocks.d.ts +6 -0
- package/dist/proxy/patch/deepseek/patch-thinking-blocks.js +24 -0
- package/dist/proxy/patch/index.d.ts +9 -0
- package/dist/proxy/patch/index.js +17 -0
- package/dist/proxy/proxy-core.d.ts +9 -2
- package/dist/proxy/proxy-core.js +24 -2
- package/dist/proxy/proxy-handler.js +34 -9
- package/dist/proxy/proxy-logging.js +23 -2
- package/dist/proxy/resilience.d.ts +4 -0
- package/dist/proxy/resilience.js +8 -1
- package/dist/proxy/strategy/types.d.ts +2 -0
- package/dist/proxy/stream-proxy.js +2 -1
- package/dist/proxy/transport-fn.js +3 -2
- package/dist/proxy/transport.js +3 -2
- package/dist/proxy/types.d.ts +3 -1
- package/dist/proxy/types.js +5 -1
- package/dist/upgrade/checker.js +5 -2
- package/dist/utils/time-range.js +28 -13
- package/frontend-dist/assets/CardContent-GNY_j_L3.js +1 -0
- package/frontend-dist/assets/CardTitle-BhXJbSoh.js +1 -0
- package/frontend-dist/assets/Checkbox-n_sh6Lvx.js +1 -0
- package/frontend-dist/assets/CollapsibleTrigger-DDCUOXDR.js +1 -0
- package/frontend-dist/assets/Collection-DbtqQ1jF.js +1 -0
- package/frontend-dist/assets/Dashboard-Dy9frcgO.js +3 -0
- package/frontend-dist/assets/DialogTitle-BEWUnuJQ.js +1 -0
- package/frontend-dist/assets/{Input-O0ebU-Va.js → Input-CmibY9Fx.js} +1 -1
- package/frontend-dist/assets/Label-Cs__wFH0.js +1 -0
- package/frontend-dist/assets/Login-BciEc1TW.js +1 -0
- package/frontend-dist/assets/Logs-BkqwWW0-.js +1 -0
- package/frontend-dist/assets/ModelMappings-DrCJ_TCf.js +1 -0
- package/frontend-dist/assets/Monitor-C-b4qyuI.js +1 -0
- package/frontend-dist/assets/PopoverTrigger-DaKOMSVs.js +1 -0
- package/frontend-dist/assets/PopperContent-DZ6plcjf.js +1 -0
- package/frontend-dist/assets/Providers-u8utX74M.js +1 -0
- package/frontend-dist/assets/ProxyEnhancement-8_xhndGt.js +5 -0
- package/frontend-dist/assets/RetryRules-D1psYDEP.js +1 -0
- package/frontend-dist/assets/RouterKeys-ovPFGhjy.js +1 -0
- package/frontend-dist/assets/RovingFocusItem-Dsv9AkP7.js +1 -0
- package/frontend-dist/assets/SelectValue-BoUWfZAg.js +1 -0
- package/frontend-dist/assets/Settings-DXF-6A8C.js +6 -0
- package/frontend-dist/assets/Setup-rVLqiz0d.js +1 -0
- package/frontend-dist/assets/Switch-po5ZVBE3.js +1 -0
- package/frontend-dist/assets/TableHeader-Zyvq_0p2.js +1 -0
- package/frontend-dist/assets/{TabsTrigger-CPCi2HIa.js → TabsTrigger-CgDhZGkT.js} +1 -1
- package/frontend-dist/assets/Teleport-CgTHarey.js +3 -0
- package/frontend-dist/assets/TooltipTrigger-C2qO21dQ.js +1 -0
- package/frontend-dist/assets/UnifiedRequestDialog-Dksad8eN.js +3 -0
- package/frontend-dist/assets/{VisuallyHidden-Cyk-jWwh.js → VisuallyHidden-fbPmoMwi.js} +1 -1
- package/frontend-dist/assets/VisuallyHiddenInput-7j8wkPrW.js +1 -0
- package/frontend-dist/assets/alert-dialog-DbT3PzoF.js +1 -0
- package/frontend-dist/assets/badge-BVxnlnsH.js +1 -0
- package/frontend-dist/assets/{button-BQ3s7yNh.js → button-BCrIpNwA.js} +2 -2
- package/frontend-dist/assets/chevron-down-CWBwGxSp.js +1 -0
- package/frontend-dist/assets/circle-question-mark-DRkkqjgG.js +1 -0
- package/frontend-dist/assets/dialog-BNlCZpHK.js +1 -0
- package/frontend-dist/assets/file-text-BavS6SrF.js +1 -0
- package/frontend-dist/assets/format-K3VR67cG.js +1 -0
- package/frontend-dist/assets/index-BP4imfye.css +1 -0
- package/frontend-dist/assets/index-DrBJPq6d.js +1 -0
- package/frontend-dist/assets/lib-CGpNhf06.js +1 -0
- package/frontend-dist/assets/loader-circle-Cpd89XQ7.js +1 -0
- package/frontend-dist/assets/ohash.D__AXeF1-DkJnWU8a.js +1 -0
- package/frontend-dist/assets/{useClipboard-Cnnz6AAN.js → useClipboard-Bq8yZunx.js} +1 -1
- package/frontend-dist/assets/useLogRetention-BWPm3G_A.js +1 -0
- package/frontend-dist/assets/useNonce-D5lpSPNk.js +1 -0
- package/frontend-dist/assets/x-BFIp7DLt.js +1 -0
- package/frontend-dist/index.html +20 -17
- package/package.json +2 -1
- package/frontend-dist/assets/CardContent-WrBnGhTg.js +0 -1
- package/frontend-dist/assets/CardTitle-BcDYk7cq.js +0 -1
- package/frontend-dist/assets/Checkbox-MZf0YsDG.js +0 -1
- package/frontend-dist/assets/CollapsibleTrigger-CrOH9HlW.js +0 -1
- package/frontend-dist/assets/Collection-DcTx_Y54.js +0 -1
- package/frontend-dist/assets/Dashboard-D0oDrSLr.js +0 -3
- package/frontend-dist/assets/DialogTitle-Cl5Cd7QH.js +0 -1
- package/frontend-dist/assets/Label-C_S0y7Um.js +0 -1
- package/frontend-dist/assets/Login-DGY7uF8P.js +0 -1
- package/frontend-dist/assets/Logs-ls8pv89b.js +0 -1
- package/frontend-dist/assets/ModelMappings-DGlf0S4s.js +0 -1
- package/frontend-dist/assets/Monitor-BSI87grz.js +0 -1
- package/frontend-dist/assets/PopperContent-C6Q7hDmf.js +0 -1
- package/frontend-dist/assets/Providers-ZkRpj8_m.js +0 -1
- package/frontend-dist/assets/ProxyEnhancement-DFPI1W6Z.js +0 -5
- package/frontend-dist/assets/RetryRules-DtM31qsl.js +0 -1
- package/frontend-dist/assets/RouterKeys-D63tRFKm.js +0 -1
- package/frontend-dist/assets/RovingFocusItem-BJoylAKU.js +0 -1
- package/frontend-dist/assets/SelectValue-CLp5z6_I.js +0 -1
- package/frontend-dist/assets/Settings-DSgRKbTQ.js +0 -6
- package/frontend-dist/assets/Setup-BDmj6CRk.js +0 -1
- package/frontend-dist/assets/Switch-Wz-t_zkv.js +0 -1
- package/frontend-dist/assets/TableHeader-DGtcqGkw.js +0 -1
- package/frontend-dist/assets/Teleport-DdjYHlNK.js +0 -3
- package/frontend-dist/assets/TooltipTrigger-H_QoPY1n.js +0 -1
- package/frontend-dist/assets/UnifiedRequestDialog-BAAfMJJl.js +0 -3
- package/frontend-dist/assets/VisuallyHiddenInput-CYjNe_H8.js +0 -1
- package/frontend-dist/assets/alert-dialog-Bi3dliLl.js +0 -1
- package/frontend-dist/assets/badge-Kkta3e9W.js +0 -1
- package/frontend-dist/assets/createLucideIcon-D1tkPDOQ.js +0 -1
- package/frontend-dist/assets/dialog-DoIATUYw.js +0 -1
- package/frontend-dist/assets/file-text-Dt6QP1bZ.js +0 -1
- package/frontend-dist/assets/format-DOVIVsQC.js +0 -1
- package/frontend-dist/assets/index-BY0E7CHR.js +0 -1
- package/frontend-dist/assets/index-Bnrh1mFY.css +0 -1
- package/frontend-dist/assets/lib-CxwxnlwW.js +0 -1
- package/frontend-dist/assets/ohash.D__AXeF1-b0PiKZB_.js +0 -1
- package/frontend-dist/assets/useLogRetention-DYP5LOAc.js +0 -1
- package/frontend-dist/assets/useNonce-DKbOCfgM.js +0 -1
- package/frontend-dist/assets/x-CAoitXRt.js +0 -1
package/dist/proxy/resilience.js
CHANGED
|
@@ -67,6 +67,10 @@ export class ResilienceLayer {
|
|
|
67
67
|
if (result.kind === "stream_abort") {
|
|
68
68
|
return { action: "abort", reason: "stream_abort" };
|
|
69
69
|
}
|
|
70
|
+
// stream_error + statusCode < failoverThreshold -> 上游返回 200 但 body 包含错误内容(early error),不可恢复
|
|
71
|
+
if (result.kind === "stream_error" && result.statusCode < config.failoverThreshold) {
|
|
72
|
+
return { action: "abort", reason: "stream_error" };
|
|
73
|
+
}
|
|
70
74
|
// success + statusCode < failoverThreshold -> done
|
|
71
75
|
if ((result.kind === "success" || result.kind === "stream_success") &&
|
|
72
76
|
result.statusCode < config.failoverThreshold) {
|
|
@@ -158,6 +162,7 @@ export class ResilienceLayer {
|
|
|
158
162
|
target: currentTarget, attemptIndex: globalAttemptIndex,
|
|
159
163
|
statusCode: null, error: throwErr instanceof Error ? throwErr.message : String(throwErr),
|
|
160
164
|
latencyMs: Date.now() - start, responseBody: null,
|
|
165
|
+
responseHeaders: null, resultKind: transportResult.kind,
|
|
161
166
|
});
|
|
162
167
|
}
|
|
163
168
|
else {
|
|
@@ -165,6 +170,8 @@ export class ResilienceLayer {
|
|
|
165
170
|
target: currentTarget, attemptIndex: globalAttemptIndex,
|
|
166
171
|
statusCode: transportResult.statusCode, error: null,
|
|
167
172
|
latencyMs: Date.now() - start, responseBody: extractBody(transportResult),
|
|
173
|
+
responseHeaders: extractHeaders(transportResult) ?? null,
|
|
174
|
+
resultKind: transportResult.kind,
|
|
168
175
|
});
|
|
169
176
|
}
|
|
170
177
|
const state = {
|
|
@@ -186,7 +193,7 @@ export class ResilienceLayer {
|
|
|
186
193
|
// 跨 provider failover 需要切换信号量,抛出异常让上层处理
|
|
187
194
|
const nextAvail = targets().filter(t => !excludedTargets.some(e => e.backend_model === t.backend_model && e.provider_id === t.provider_id));
|
|
188
195
|
if (nextAvail.length > 0 && nextAvail[0].provider_id !== currentTarget.provider_id) {
|
|
189
|
-
throw new ProviderSwitchNeeded(nextAvail[0].provider_id);
|
|
196
|
+
throw new ProviderSwitchNeeded(nextAvail[0].provider_id, [...allAttempts], transportResult);
|
|
190
197
|
}
|
|
191
198
|
continue;
|
|
192
199
|
case "abort":
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PassThrough } from "stream";
|
|
2
2
|
import { UPSTREAM_SUCCESS, filterHeaders } from "./types.js";
|
|
3
|
+
import { buildUpstreamUrl } from "./proxy-core.js";
|
|
3
4
|
import { _transportInternals, buildRequestOptions, } from "./transport.js";
|
|
4
5
|
const UPSTREAM_BAD_GATEWAY = 502;
|
|
5
6
|
const BUFFER_SIZE_LIMIT = 4096;
|
|
@@ -224,7 +225,7 @@ class StreamProxy {
|
|
|
224
225
|
export function callStream(backend, apiKey, body, clientHeaders, reply, timeoutMs, upstreamPath, buildHeaders, metricsTransform, checkEarlyError, compatResolve) {
|
|
225
226
|
return new Promise((resolve) => {
|
|
226
227
|
const effectiveResolve = compatResolve ?? resolve;
|
|
227
|
-
const url = new URL(
|
|
228
|
+
const url = new URL(buildUpstreamUrl(backend.base_url, upstreamPath));
|
|
228
229
|
const payload = JSON.stringify(body);
|
|
229
230
|
const upstreamHeaders = buildHeaders(clientHeaders, apiKey, Buffer.byteLength(payload));
|
|
230
231
|
const options = buildRequestOptions(url, upstreamHeaders);
|
|
@@ -17,6 +17,7 @@ function toStreamMetrics(m) {
|
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
export function buildTransportFn(p) {
|
|
20
|
+
const buildHeaders = (cliHdrs, key, bytes) => buildUpstreamHeaders(cliHdrs, key, bytes, p.apiType);
|
|
20
21
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
21
22
|
return async (_target) => {
|
|
22
23
|
if (p.isStream) {
|
|
@@ -25,14 +26,14 @@ export function buildTransportFn(p) {
|
|
|
25
26
|
onChunk: (rawLine) => { p.tracker?.appendStreamChunk(p.logId, rawLine, p.apiType, STREAM_CONTENT_MAX_RAW, STREAM_CONTENT_MAX_TEXT); },
|
|
26
27
|
});
|
|
27
28
|
const checkEarlyError = p.matcher ? (data) => p.matcher.test(UPSTREAM_SUCCESS, data) : undefined;
|
|
28
|
-
const streamResult = await callStream(p.provider, p.apiKey, p.body, p.cliHdrs, p.reply, p.streamTimeoutMs, p.upstreamPath,
|
|
29
|
+
const streamResult = await callStream(p.provider, p.apiKey, p.body, p.cliHdrs, p.reply, p.streamTimeoutMs, p.upstreamPath, buildHeaders, metricsTransform, checkEarlyError);
|
|
29
30
|
const m = (streamResult.kind === "stream_success" || streamResult.kind === "stream_abort")
|
|
30
31
|
? streamResult.metrics : undefined;
|
|
31
32
|
if (m)
|
|
32
33
|
p.tracker?.update(p.logId, { streamMetrics: toStreamMetrics(m) });
|
|
33
34
|
return streamResult;
|
|
34
35
|
}
|
|
35
|
-
const result = await callNonStream(p.provider, p.apiKey, p.body, p.cliHdrs, p.upstreamPath,
|
|
36
|
+
const result = await callNonStream(p.provider, p.apiKey, p.body, p.cliHdrs, p.upstreamPath, buildHeaders);
|
|
36
37
|
if (result.kind === "success") {
|
|
37
38
|
const mr = MetricsExtractor.fromNonStreamResponse(p.apiType, result.body);
|
|
38
39
|
if (mr)
|
package/dist/proxy/transport.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { request as httpRequestFn } from "http";
|
|
2
2
|
import { request as httpsRequestFn } from "https";
|
|
3
3
|
import { UPSTREAM_SUCCESS, filterHeaders } from "./types.js";
|
|
4
|
+
import { buildUpstreamUrl } from "./proxy-core.js";
|
|
4
5
|
// Re-export callStream from stream-proxy.ts for external consumers
|
|
5
6
|
export { callStream } from "./stream-proxy.js";
|
|
6
7
|
// ---------- Constants ----------
|
|
@@ -28,7 +29,7 @@ export function buildRequestOptions(url, headers, method = "POST") {
|
|
|
28
29
|
// ---------- callNonStream ----------
|
|
29
30
|
export function callNonStream(backend, apiKey, body, clientHeaders, upstreamPath, buildHeaders) {
|
|
30
31
|
return new Promise((resolve) => {
|
|
31
|
-
const url = new URL(
|
|
32
|
+
const url = new URL(buildUpstreamUrl(backend.base_url, upstreamPath));
|
|
32
33
|
const payload = JSON.stringify(body);
|
|
33
34
|
const upstreamHeaders = buildHeaders(clientHeaders, apiKey, Buffer.byteLength(payload));
|
|
34
35
|
const options = buildRequestOptions(url, upstreamHeaders);
|
|
@@ -69,7 +70,7 @@ export function callNonStream(backend, apiKey, body, clientHeaders, upstreamPath
|
|
|
69
70
|
}
|
|
70
71
|
export function callGet(backend, apiKey, clientHeaders, upstreamPath, buildHeaders) {
|
|
71
72
|
return new Promise((resolve, reject) => {
|
|
72
|
-
const url = new URL(
|
|
73
|
+
const url = new URL(buildUpstreamUrl(backend.base_url, upstreamPath));
|
|
73
74
|
const headers = buildHeaders(clientHeaders, apiKey);
|
|
74
75
|
const options = buildRequestOptions(url, headers, "GET");
|
|
75
76
|
const req = _transportInternals.createUpstreamRequest(url, options);
|
package/dist/proxy/types.d.ts
CHANGED
|
@@ -52,7 +52,9 @@ export type TransportResult = {
|
|
|
52
52
|
*/
|
|
53
53
|
export declare class ProviderSwitchNeeded extends Error {
|
|
54
54
|
readonly targetProviderId: string;
|
|
55
|
-
|
|
55
|
+
readonly attempts?: import("./resilience.js").ResilienceAttempt[] | undefined;
|
|
56
|
+
readonly lastResult?: TransportResult | undefined;
|
|
57
|
+
constructor(targetProviderId: string, attempts?: import("./resilience.js").ResilienceAttempt[] | undefined, lastResult?: TransportResult | undefined);
|
|
56
58
|
}
|
|
57
59
|
/** 流式传输阶段状态 */
|
|
58
60
|
export type StreamState = "BUFFERING" | "STREAMING" | "COMPLETED" | "EARLY_ERROR" | "ABORTED";
|
package/dist/proxy/types.js
CHANGED
|
@@ -22,9 +22,13 @@ export function filterHeaders(raw) {
|
|
|
22
22
|
*/
|
|
23
23
|
export class ProviderSwitchNeeded extends Error {
|
|
24
24
|
targetProviderId;
|
|
25
|
-
|
|
25
|
+
attempts;
|
|
26
|
+
lastResult;
|
|
27
|
+
constructor(targetProviderId, attempts, lastResult) {
|
|
26
28
|
super(`Provider switch needed: ${targetProviderId}`);
|
|
27
29
|
this.targetProviderId = targetProviderId;
|
|
30
|
+
this.attempts = attempts;
|
|
31
|
+
this.lastResult = lastResult;
|
|
28
32
|
this.name = "ProviderSwitchNeeded";
|
|
29
33
|
}
|
|
30
34
|
}
|
package/dist/upgrade/checker.js
CHANGED
|
@@ -6,6 +6,9 @@ import { getInstalledVersion } from './version.js';
|
|
|
6
6
|
const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org/llm-simple-router';
|
|
7
7
|
const DEFAULT_GITHUB_CONFIG_BASE = 'https://raw.githubusercontent.com/zhushanwen321/llm-simple-router/main/config';
|
|
8
8
|
const CHECK_TIMEOUT_MS = 5000;
|
|
9
|
+
const HTTP_STATUS_REDIRECT_LOWER = 300;
|
|
10
|
+
const HTTP_STATUS_REDIRECT_UPPER = 400;
|
|
11
|
+
const HTTP_STATUS_OK_LOWER = 200;
|
|
9
12
|
const MAX_REDIRECTS = 5;
|
|
10
13
|
export async function fetchJson(url, redirects = 0) {
|
|
11
14
|
if (redirects > MAX_REDIRECTS)
|
|
@@ -14,13 +17,13 @@ export async function fetchJson(url, redirects = 0) {
|
|
|
14
17
|
return new Promise((resolve, reject) => {
|
|
15
18
|
const timer = setTimeout(() => reject(new Error('timeout')), CHECK_TIMEOUT_MS);
|
|
16
19
|
mod.get(url, (res) => {
|
|
17
|
-
if (res.statusCode && res.statusCode >=
|
|
20
|
+
if (res.statusCode && res.statusCode >= HTTP_STATUS_REDIRECT_LOWER && res.statusCode < HTTP_STATUS_REDIRECT_UPPER && res.headers.location) {
|
|
18
21
|
clearTimeout(timer);
|
|
19
22
|
res.resume();
|
|
20
23
|
fetchJson(res.headers.location, redirects + 1).then(resolve, reject);
|
|
21
24
|
return;
|
|
22
25
|
}
|
|
23
|
-
if (res.statusCode && (res.statusCode <
|
|
26
|
+
if (res.statusCode && (res.statusCode < HTTP_STATUS_OK_LOWER || res.statusCode >= HTTP_STATUS_REDIRECT_LOWER)) {
|
|
24
27
|
clearTimeout(timer);
|
|
25
28
|
res.resume();
|
|
26
29
|
reject(new Error(`HTTP ${res.statusCode}`));
|
package/dist/utils/time-range.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import { getLatestWindow, insertWindow } from "../db/usage-windows.js";
|
|
3
|
+
import { toSqliteDatetime, parseSqliteDatetime } from "./datetime.js";
|
|
4
|
+
const WINDOW_HOURS = 5;
|
|
5
|
+
const MS_PER_HOUR = 3600_000;
|
|
6
|
+
// 与 usage-windows 的默认窗口时长对齐
|
|
7
|
+
const WINDOW_DURATION_MS = WINDOW_HOURS * MS_PER_HOUR;
|
|
5
8
|
export function resolveTimeRange(period, db, routerKeyId) {
|
|
6
9
|
const now = new Date();
|
|
7
10
|
switch (period) {
|
|
8
11
|
case "window": {
|
|
9
12
|
const latest = getLatestWindow(db, routerKeyId);
|
|
10
13
|
if (!latest) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
endTime: toSqliteDatetime(new Date(start.getTime() + WINDOW_DURATION_MS)),
|
|
17
|
-
};
|
|
14
|
+
return createAndReturnWindow(db, now, routerKeyId);
|
|
15
|
+
}
|
|
16
|
+
// 最新窗口已过期(无请求触发新窗口创建),主动补齐
|
|
17
|
+
if (now > parseSqliteDatetime(latest.end_time)) {
|
|
18
|
+
return createAndReturnWindow(db, now, routerKeyId);
|
|
18
19
|
}
|
|
19
20
|
return { startTime: latest.start_time, endTime: latest.end_time };
|
|
20
21
|
}
|
|
@@ -33,8 +34,22 @@ export function resolveTimeRange(period, db, routerKeyId) {
|
|
|
33
34
|
export function getMonday(date) {
|
|
34
35
|
const d = new Date(date);
|
|
35
36
|
const day = d.getDay();
|
|
36
|
-
// 周日 getDay()=0
|
|
37
|
-
const
|
|
37
|
+
// 周日 getDay()=0,需要回退到上周一;+1 将周日=0 映射到周一=1 基准
|
|
38
|
+
const SUNDAY_OFFSET = -6;
|
|
39
|
+
const MONDAY_BASE = 1;
|
|
40
|
+
const diff = d.getDate() - day + (day === 0 ? SUNDAY_OFFSET : MONDAY_BASE);
|
|
38
41
|
d.setDate(diff);
|
|
39
42
|
return d;
|
|
40
43
|
}
|
|
44
|
+
function createAndReturnWindow(db, now, routerKeyId) {
|
|
45
|
+
const start = new Date(now);
|
|
46
|
+
start.setMinutes(0, 0, 0);
|
|
47
|
+
const end = new Date(start.getTime() + WINDOW_DURATION_MS);
|
|
48
|
+
insertWindow(db, {
|
|
49
|
+
id: randomUUID(),
|
|
50
|
+
router_key_id: routerKeyId ?? null,
|
|
51
|
+
start_time: toSqliteDatetime(start),
|
|
52
|
+
end_time: toSqliteDatetime(end),
|
|
53
|
+
});
|
|
54
|
+
return { startTime: toSqliteDatetime(start), endTime: toSqliteDatetime(end) };
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{G as e,It as t,J as n,Pt as r,lt as i,ot as a,r as o}from"./button-BCrIpNwA.js";var s=[`data-size`],c=n({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{default:`default`}},setup(n){let c=n;return(l,u)=>(a(),e(`div`,{"data-slot":`card`,"data-size":n.size,class:t(r(o)(`ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-lg py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg group/card flex flex-col`,c.class))},[i(l.$slots,`default`)],10,s))}}),l=n({__name:`CardContent`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(n){let s=n;return(n,c)=>(a(),e(`div`,{"data-slot":`card-content`,class:t(r(o)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[i(n.$slots,`default`)],2))}});export{c as n,l as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{G as e,It as t,J as n,Pt as r,lt as i,ot as a,r as o}from"./button-BCrIpNwA.js";var s=n({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(n){let s=n;return(n,c)=>(a(),e(`div`,{"data-slot":`card-header`,class:t(r(o)(`gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]`,s.class))},[i(n.$slots,`default`)],2))}}),c=n({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(n){let s=n;return(n,c)=>(a(),e(`div`,{"data-slot":`card-title`,class:t(r(o)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[i(n.$slots,`default`)],2))}});export{s as n,c as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{J as e,L as t,Lt as n,Pt as r,R as i,U as a,V as o,W as s,X as c,dt as l,i as u,lt as d,m as f,o as p,ot as m,q as h,r as g,tt as _,x as v,yt as y}from"./button-BCrIpNwA.js";import{g as b,o as x,u as S,y as C}from"./Teleport-CgTHarey.js";import{n as w,t as T}from"./ohash.D__AXeF1-DkJnWU8a.js";import{n as E}from"./VisuallyHidden-fbPmoMwi.js";import{t as D}from"./VisuallyHiddenInput-7j8wkPrW.js";import{t as O}from"./RovingFocusItem-Dsv9AkP7.js";function k(e,t){return b(e)?!1:Array.isArray(e)?e.some(e=>T(e,t)):T(e,t)}var[A,j]=C(`CheckboxGroupRoot`);function M(e){return e===`indeterminate`}function N(e){return M(e)?`indeterminate`:e?`checked`:`unchecked`}var[P,F]=C(`CheckboxRoot`),I=e({inheritAttrs:!1,__name:`CheckboxRoot`,props:{defaultValue:{type:null,required:!1},modelValue:{type:null,required:!1,default:void 0},disabled:{type:Boolean,required:!1},value:{type:null,required:!1,default:`on`},id:{type:String,required:!1},trueValue:{type:null,required:!1,default:()=>!0},falseValue:{type:null,required:!1,default:()=>!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`},name:{type:String,required:!1},required:{type:Boolean,required:!1}},emits:[`update:modelValue`],setup(e,{emit:n}){let c=e,h=n,{forwardRef:g,currentElement:v}=p(),x=A(null),S=f(c,`modelValue`,h,{defaultValue:c.defaultValue??c.falseValue,passive:c.modelValue===void 0}),C=o(()=>x?.disabled.value||c.disabled),w=o(()=>T(S.value,c.trueValue)),j=o(()=>b(x?.modelValue.value)?S.value===`indeterminate`?`indeterminate`:w.value:k(x.modelValue.value,c.value));function P(){if(b(x?.modelValue.value))S.value===`indeterminate`?S.value=c.trueValue:S.value=w.value?c.falseValue:c.trueValue;else{let e=[...x.modelValue.value||[]];if(k(e,c.value)){let t=e.findIndex(e=>T(e,c.value));e.splice(t,1)}else e.push(c.value);x.modelValue.value=e}}let I=E(v),L=o(()=>c.id&&v.value?document.querySelector(`[for="${c.id}"]`)?.innerText:void 0);return F({disabled:C,state:j}),(e,n)=>(m(),a(l(r(x)?.rovingFocus.value?r(O):r(u)),_(e.$attrs,{id:e.id,ref:r(g),role:`checkbox`,"as-child":e.asChild,as:e.as,type:e.as===`button`?`button`:void 0,"aria-checked":r(M)(j.value)?`mixed`:j.value,"aria-required":e.required,"aria-label":e.$attrs[`aria-label`]||L.value,"data-state":r(N)(j.value),"data-disabled":C.value?``:void 0,disabled:C.value,focusable:r(x)?.rovingFocus.value?!C.value:void 0,onKeydown:t(i(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:y(()=>[d(e.$slots,`default`,{modelValue:r(S),state:j.value}),r(I)&&e.name&&!r(x)?(m(),a(r(D),{key:0,type:`checkbox`,checked:!!j.value,name:e.name,value:e.value,disabled:C.value,required:e.required},null,8,[`checked`,`name`,`value`,`disabled`,`required`])):s(`v-if`,!0)]),_:3},16,[`id`,`as-child`,`as`,`type`,`aria-checked`,`aria-required`,`aria-label`,`data-state`,`data-disabled`,`disabled`,`focusable`,`onKeydown`]))}}),L=e({__name:`CheckboxIndicator`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`span`}},setup(e){let{forwardRef:t}=p(),n=P();return(e,i)=>(m(),a(r(x),{present:e.forceMount||r(M)(r(n).state.value)||r(n).state.value===!0},{default:y(()=>[h(r(u),_({ref:r(t),"data-state":r(N)(r(n).state.value),"data-disabled":r(n).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":e.asChild,as:e.as},e.$attrs),{default:y(()=>[d(e.$slots,`default`)]),_:3},16,[`data-state`,`data-disabled`,`as-child`,`as`])]),_:3},8,[`present`]))}}),R=e({__name:`Checkbox`,props:{defaultValue:{},modelValue:{},disabled:{type:Boolean},value:{},id:{},trueValue:{},falseValue:{},asChild:{type:Boolean},as:{},name:{},required:{type:Boolean},class:{type:[Boolean,null,String,Object,Array]}},emits:[`update:modelValue`],setup(e,{emit:t}){let i=e,o=t,s=S(v(i,`class`),o);return(e,t)=>(m(),a(r(I),_({"data-slot":`checkbox`},r(s),{class:r(g)(`border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-md border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50`,i.class)}),{default:y(t=>[h(r(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:y(()=>[d(e.$slots,`default`,n(c(t)),()=>[h(r(w))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Dt as e,J as t,Lt as n,Mt as r,Pt as i,U as a,V as o,W as s,X as c,d as l,gt as u,i as d,it as f,lt as p,m,nt as h,o as g,ot as _,q as v,tt as y,yt as b}from"./button-BCrIpNwA.js";import{c as x,o as S,u as C,y as w}from"./Teleport-CgTHarey.js";var[T,E]=w(`CollapsibleRoot`),D=t({__name:`CollapsibleRoot`,props:{defaultOpen:{type:Boolean,required:!1,default:!1},open:{type:Boolean,required:!1,default:void 0},disabled:{type:Boolean,required:!1},unmountOnHide:{type:Boolean,required:!1,default:!0},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`update:open`],setup(e,{expose:t,emit:n}){let o=e,s=m(o,`open`,n,{defaultValue:o.defaultOpen,passive:o.open===void 0}),{disabled:c,unmountOnHide:l}=r(o);return E({contentId:``,disabled:c,open:s,unmountOnHide:l,onOpenToggle:()=>{c.value||(s.value=!s.value)}}),t({open:s}),g(),(e,t)=>(_(),a(i(d),{as:e.as,"as-child":o.asChild,"data-state":i(s)?`open`:`closed`,"data-disabled":i(c)?``:void 0},{default:b(()=>[p(e.$slots,`default`,{open:i(s)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),O=t({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(t,{emit:n}){let r=t,c=n,m=T();m.contentId||=x(void 0,`reka-collapsible-content`);let C=e(),{forwardRef:w,currentElement:E}=g(),D=e(0),O=e(0),k=o(()=>m.open.value),A=e(k.value),j=e();u(()=>[k.value,C.value?.present],async()=>{await h();let e=E.value;if(!e)return;j.value=j.value||{transitionDuration:e.style.transitionDuration,animationName:e.style.animationName},e.style.transitionDuration=`0s`,e.style.animationName=`none`;let t=e.getBoundingClientRect();O.value=t.height,D.value=t.width,A.value||(e.style.transitionDuration=j.value.transitionDuration,e.style.animationName=j.value.animationName)},{immediate:!0});let M=o(()=>A.value&&m.open.value);return f(()=>{requestAnimationFrame(()=>{A.value=!1})}),l(E,`beforematch`,e=>{requestAnimationFrame(()=>{m.onOpenToggle(),c(`contentFound`)})}),(e,t)=>(_(),a(i(S),{ref_key:`presentRef`,ref:C,present:e.forceMount||i(m).open.value,"force-mount":!0},{default:b(({present:t})=>[v(i(d),y(e.$attrs,{id:i(m).contentId,ref:i(w),"as-child":r.asChild,as:e.as,hidden:t?void 0:i(m).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:i(m).open.value?`open`:`closed`,"data-disabled":i(m).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:b(()=>[!i(m).unmountOnHide.value||t?p(e.$slots,`default`,{key:0}):s(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),k=t({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(e){let t=e;g();let n=T();return(e,r)=>(_(),a(i(d),{type:e.as===`button`?`button`:void 0,as:e.as,"as-child":t.asChild,"aria-controls":i(n).contentId,"aria-expanded":i(n).open.value,"data-state":i(n).open.value?`open`:`closed`,"data-disabled":i(n).disabled?.value?``:void 0,disabled:i(n).disabled?.value,onClick:i(n).onOpenToggle},{default:b(()=>[p(e.$slots,`default`)]),_:3},8,[`type`,`as`,`as-child`,`aria-controls`,`aria-expanded`,`data-state`,`data-disabled`,`disabled`,`onClick`]))}}),A=t({__name:`Collapsible`,props:{defaultOpen:{type:Boolean},open:{type:Boolean},disabled:{type:Boolean},unmountOnHide:{type:Boolean},asChild:{type:Boolean},as:{}},emits:[`update:open`],setup(e,{emit:t}){let r=C(e,t);return(e,t)=>(_(),a(i(D),y({"data-slot":`collapsible`},i(r)),{default:b(t=>[p(e.$slots,`default`,n(c(t)))]),_:3},16))}}),j=t({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,n)=>(_(),a(i(O),y({"data-slot":`collapsible-content`},t),{default:b(()=>[p(e.$slots,`default`)]),_:3},16))}}),M=t({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,n)=>(_(),a(i(k),y({"data-slot":`collapsible-trigger`},t),{default:b(()=>[p(e.$slots,`default`)]),_:3},16))}});export{j as n,A as r,M as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Dt as e,J as t,Q as n,V as r,Z as i,_t as a,a as o,gt as s,st as c,u as l,wt as u}from"./button-BCrIpNwA.js";import{h as d}from"./Teleport-CgTHarey.js";function f(t){let n=d({dir:e(`ltr`)});return r(()=>t?.value||n.dir?.value||`ltr`)}function p(){let t=e();return{primitiveElement:t,currentElement:r(()=>[`#text`,`#comment`].includes(t.value?.$el.nodeName)?t.value?.$el.nextElementSibling:l(t))}}var m=`data-reka-collection-item`;function h(l={}){let{key:d=``,isProvider:f=!1}=l,h=`${d}CollectionProvider`,g;if(f){let t=e(new Map);g={collectionRef:e(),itemMap:t},c(h,g)}else g=n(h);let _=(e=!1)=>{let t=g.collectionRef.value;if(!t)return[];let n=Array.from(t.querySelectorAll(`[${m}]`)),r=Array.from(g.itemMap.value.values()).sort((e,t)=>n.indexOf(e.ref)-n.indexOf(t.ref));return e?r:r.filter(e=>e.ref.dataset.disabled!==``)},v=t({name:`CollectionSlot`,inheritAttrs:!1,setup(e,{slots:t,attrs:n}){let{primitiveElement:r,currentElement:a}=p();return s(a,()=>{g.collectionRef.value=a.value}),()=>i(o,{ref:r,...n},t)}}),y=t({name:`CollectionItem`,inheritAttrs:!1,props:{value:{validator:()=>!0}},setup(e,{slots:t,attrs:n}){let{primitiveElement:r,currentElement:s}=p();return a(t=>{if(s.value){let n=u(s.value);g.itemMap.value.set(n,{ref:s.value,value:e.value}),t(()=>g.itemMap.value.delete(n))}}),()=>i(o,{...n,[m]:``,ref:r},t)}});return{getItems:_,reactiveItems:r(()=>Array.from(g.itemMap.value.values())),itemMapSize:r(()=>g.itemMap.value.size),CollectionSlot:v,CollectionItem:y}}export{p as n,f as r,h as t};
|