llm-simple-router 0.11.25 → 0.11.27
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/dist/core/monitor/stream-extractor.js +32 -14
- package/dist/proxy/format/adapters/anthropic.js +1 -0
- package/dist/proxy/format/adapters/shared-error-meta.js +1 -0
- package/dist/proxy/format/types.d.ts +1 -1
- package/dist/proxy/handler/create-proxy-handler.js +1 -0
- package/dist/proxy/handler/failover-loop.js +24 -31
- package/dist/proxy/handler/proxy-handler-utils.js +19 -2
- package/dist/proxy/proxy-core.d.ts +3 -1
- package/dist/proxy/proxy-core.js +4 -0
- package/dist/proxy/routing/modality-redirect.d.ts +6 -3
- package/dist/proxy/routing/modality-redirect.js +87 -64
- package/frontend-dist/assets/{CardContent-C3yh4PGf.js → CardContent-BLlnppsG.js} +1 -1
- package/frontend-dist/assets/{CardTitle-BjctPLMN.js → CardTitle-8HHCqwln.js} +1 -1
- package/frontend-dist/assets/{CascadingModelSelect-D9g0YGCD.js → CascadingModelSelect-CrtNasp8.js} +1 -1
- package/frontend-dist/assets/{Checkbox-C9AN3_0E.js → Checkbox-B8Vw60jj.js} +1 -1
- package/frontend-dist/assets/{CollapsibleContent-Dvc5Q0jk.js → CollapsibleContent-CvvkOB4a.js} +1 -1
- package/frontend-dist/assets/{CollapsibleTrigger-COO-0iMJ.js → CollapsibleTrigger-DeMItRLr.js} +1 -1
- package/frontend-dist/assets/{Dashboard-CvIsUmiT.js → Dashboard-9dJqc_2w.js} +1 -1
- package/frontend-dist/assets/{Input-HMh9MVqW.js → Input-vc5GUwkU.js} +1 -1
- package/frontend-dist/assets/{Label-WihZ5JyO.js → Label-FWcCWV4Q.js} +1 -1
- package/frontend-dist/assets/{Login-BLfBZ8QI.js → Login-9QkzUJm_.js} +1 -1
- package/frontend-dist/assets/{Logs-Cbdbrly6.js → Logs-B2Ywp4Aj.js} +1 -1
- package/frontend-dist/assets/{MappingEntryEditor-85XYz-6b.js → MappingEntryEditor-Cx85eUWy.js} +1 -1
- package/frontend-dist/assets/{ModelMappings-CL86Xmxc.js → ModelMappings-rUvMpbjc.js} +1 -1
- package/frontend-dist/assets/{Monitor-CdPLST7K.js → Monitor-DCo3DSbc.js} +1 -1
- package/frontend-dist/assets/{Providers-BS-GHbKg.js → Providers-CwvTWI9Z.js} +1 -1
- package/frontend-dist/assets/{ProxyEnhancement-BRRVvv2n.js → ProxyEnhancement-Cn1rEBFB.js} +1 -1
- package/frontend-dist/assets/{QuickSetup-BwjL5C3V.js → QuickSetup-CdpVo45V.js} +1 -1
- package/frontend-dist/assets/{RetryRules-Br_p5QFd.js → RetryRules-DDJMY1IF.js} +1 -1
- package/frontend-dist/assets/{RouterKeys-MVj7WKCD.js → RouterKeys-DMyGtSxS.js} +1 -1
- package/frontend-dist/assets/{RovingFocusItem-Cksm8vEW.js → RovingFocusItem-Bx5uXXN2.js} +1 -1
- package/frontend-dist/assets/{Schedules-CFRHIUaY.js → Schedules-CDDBsNpW.js} +1 -1
- package/frontend-dist/assets/{Settings-Ct2pXj7T.js → Settings-Cr9sYZKR.js} +1 -1
- package/frontend-dist/assets/{Setup-6xTd2xao.js → Setup-LSmgyhc3.js} +1 -1
- package/frontend-dist/assets/{Switch-B-sHFO9X.js → Switch-DyyEun5Q.js} +1 -1
- package/frontend-dist/assets/{TooltipTrigger-Dr21-MXr.js → TooltipTrigger-DCldZMnb.js} +1 -1
- package/frontend-dist/assets/{TransformRulesForm-CmFd3-6R.js → TransformRulesForm-Gfvlk0Xk.js} +1 -1
- package/frontend-dist/assets/UnifiedRequestDialog-BnrOpRXA.js +3 -0
- package/frontend-dist/assets/{VisuallyHiddenInput-ClOeZr8Z.js → VisuallyHiddenInput-Bkpr-VfM.js} +1 -1
- package/frontend-dist/assets/{button-D37rLhG3.js → button-CT60E_JD.js} +2 -2
- package/frontend-dist/assets/{copy-DbUbyYpk.js → copy-CVYlfrkY.js} +1 -1
- package/frontend-dist/assets/{dialog-dnuhN9AI.js → dialog-B4JCV9g7.js} +1 -1
- package/frontend-dist/assets/{index-n3QfsdYg.js → index-bGqHF9Js.js} +2 -2
- package/frontend-dist/assets/{model-patches-QbKfCo2A.js → model-patches-C-_TDTXT.js} +1 -1
- package/frontend-dist/assets/plus-C0-OMLZ3.js +1 -0
- package/frontend-dist/assets/{sparkles-CEvoWhC-.js → sparkles-CbfRkkI9.js} +1 -1
- package/frontend-dist/assets/{trash-2-NR9QxAhS.js → trash-2-wGNSUXAY.js} +1 -1
- package/frontend-dist/assets/{useClipboard-DaZSrO7r.js → useClipboard-Br_VVE9B.js} +1 -1
- package/frontend-dist/assets/{useLogRetention-BsuqKeeK.js → useLogRetention-DOuufHTD.js} +1 -1
- package/frontend-dist/index.html +2 -2
- package/package.json +2 -1
- package/frontend-dist/assets/UnifiedRequestDialog-BX3m_-da.js +0 -3
- package/frontend-dist/assets/plus-DMzNAXWz.js +0 -1
|
@@ -3,6 +3,18 @@ const SSE_DATA_PREFIX = "data: ";
|
|
|
3
3
|
const OPENAI_BLOCK_REASONING = 0;
|
|
4
4
|
const OPENAI_BLOCK_TEXT = 1;
|
|
5
5
|
const OPENAI_BLOCK_TOOLS = 2;
|
|
6
|
+
// Responses SSE 事件类型 → block 类型映射
|
|
7
|
+
// 与 transform/types-responses.ts 的 RESPONSES_SSE_EVENTS 保持同步
|
|
8
|
+
const RESPONSES_DELTA_MAP = {
|
|
9
|
+
"response.output_text.delta": "text",
|
|
10
|
+
"response.function_call_arguments.delta": "tool_use",
|
|
11
|
+
"response.reasoning_summary_text.delta": "thinking",
|
|
12
|
+
"response.reasoning_text.delta": "thinking",
|
|
13
|
+
"response.refusal.delta": "text",
|
|
14
|
+
"response.code_interpreter_call_code.delta": "text",
|
|
15
|
+
};
|
|
16
|
+
// 多种 Provider 的思考内容字段名(按优先级排列)
|
|
17
|
+
const REASONING_FIELDS = ["reasoning_content", "reasoning", "reasoning_text"];
|
|
6
18
|
export function extractStreamText(line, apiType) {
|
|
7
19
|
const empty = { text: "", block: null };
|
|
8
20
|
if (!line.startsWith(SSE_DATA_PREFIX))
|
|
@@ -21,7 +33,15 @@ export function extractStreamText(line, apiType) {
|
|
|
21
33
|
const choices = obj.choices;
|
|
22
34
|
const delta = choices?.[0]?.delta;
|
|
23
35
|
const text = delta?.content ?? "";
|
|
24
|
-
|
|
36
|
+
// 多种 Provider 的思考字段名:reasoning_content(标准)、reasoning、reasoning_text
|
|
37
|
+
let reasoning = "";
|
|
38
|
+
for (const field of REASONING_FIELDS) {
|
|
39
|
+
const val = delta?.[field];
|
|
40
|
+
if (typeof val === "string" && val) {
|
|
41
|
+
reasoning = val;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
25
45
|
// OpenAI 不像 Anthropic 那样为不同 content type 分配独立 index。
|
|
26
46
|
// 策略:reasoning → OPENAI_BLOCK_REASONING, text → OPENAI_BLOCK_TEXT,
|
|
27
47
|
// tool_calls[N] → OPENAI_BLOCK_TOOLS + N。
|
|
@@ -32,6 +52,11 @@ export function extractStreamText(line, apiType) {
|
|
|
32
52
|
if (text) {
|
|
33
53
|
return { text, block: { index: OPENAI_BLOCK_TEXT, type: "text", content: text } };
|
|
34
54
|
}
|
|
55
|
+
// refusal 降级为 text block(内容审核拒绝原因)
|
|
56
|
+
const refusal = delta?.refusal ?? "";
|
|
57
|
+
if (refusal) {
|
|
58
|
+
return { text: refusal, block: { index: OPENAI_BLOCK_TEXT, type: "text", content: refusal } };
|
|
59
|
+
}
|
|
35
60
|
const toolCalls = delta?.tool_calls;
|
|
36
61
|
if (toolCalls) {
|
|
37
62
|
const tc = toolCalls[0];
|
|
@@ -51,20 +76,13 @@ export function extractStreamText(line, apiType) {
|
|
|
51
76
|
// Responses SSE uses named events, but line format is "data: {json}" (same as Anthropic)
|
|
52
77
|
// The event type is in the data JSON's "type" field
|
|
53
78
|
const type = obj.type;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
return { text, block: text ? { index: outputIndex, type: "text", content: text } : empty.block };
|
|
58
|
-
}
|
|
59
|
-
if (type === "response.function_call_arguments.delta") {
|
|
60
|
-
const partialJson = obj.delta ?? "";
|
|
61
|
-
const outputIndex = obj.output_index ?? 0;
|
|
62
|
-
return { text: "", block: { index: outputIndex, type: "tool_use", content: partialJson } };
|
|
63
|
-
}
|
|
64
|
-
if (type === "response.reasoning_summary_text.delta") {
|
|
65
|
-
const thinking = obj.delta ?? "";
|
|
79
|
+
const blockType = RESPONSES_DELTA_MAP[type];
|
|
80
|
+
if (blockType) {
|
|
81
|
+
const delta = obj.delta ?? "";
|
|
66
82
|
const outputIndex = obj.output_index ?? 0;
|
|
67
|
-
|
|
83
|
+
if (delta) {
|
|
84
|
+
return { text: blockType === "text" ? delta : "", block: { index: outputIndex, type: blockType, content: delta } };
|
|
85
|
+
}
|
|
68
86
|
}
|
|
69
87
|
return empty;
|
|
70
88
|
}
|
|
@@ -7,6 +7,7 @@ const ANTHROPIC_ERROR_META = {
|
|
|
7
7
|
concurrencyQueueFull: { type: "api_error", code: "concurrency_queue_full" },
|
|
8
8
|
concurrencyTimeout: { type: "api_error", code: "concurrency_timeout" },
|
|
9
9
|
promptTooLong: { type: "invalid_request_error", code: "context_window_exceeded" },
|
|
10
|
+
unsupportedModality: { type: "invalid_request_error", code: "unsupported_modality" },
|
|
10
11
|
};
|
|
11
12
|
export const anthropicAdapter = {
|
|
12
13
|
apiType: "anthropic",
|
|
@@ -11,4 +11,5 @@ export const OPENAI_FAMILY_ERROR_META = {
|
|
|
11
11
|
concurrencyQueueFull: { type: "server_error", code: "concurrency_queue_full" },
|
|
12
12
|
concurrencyTimeout: { type: "server_error", code: "concurrency_timeout" },
|
|
13
13
|
promptTooLong: { type: "invalid_request_error", code: "context_window_exceeded" },
|
|
14
|
+
unsupportedModality: { type: "invalid_request_error", code: "unsupported_modality" },
|
|
14
15
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Transform } from "stream";
|
|
2
|
-
export type ErrorKind = "modelNotFound" | "modelNotAllowed" | "providerUnavailable" | "providerTypeMismatch" | "upstreamConnectionFailed" | "concurrencyQueueFull" | "concurrencyTimeout" | "promptTooLong";
|
|
2
|
+
export type ErrorKind = "modelNotFound" | "modelNotAllowed" | "providerUnavailable" | "providerTypeMismatch" | "upstreamConnectionFailed" | "concurrencyQueueFull" | "concurrencyTimeout" | "promptTooLong" | "unsupportedModality";
|
|
3
3
|
export interface FormatAdapter {
|
|
4
4
|
readonly apiType: string;
|
|
5
5
|
readonly defaultPath: string;
|
|
@@ -112,6 +112,7 @@ export function createProxyHandler(config) {
|
|
|
112
112
|
concurrencyQueueFull: { type: "server_error", code: "concurrency_queue_full" },
|
|
113
113
|
concurrencyTimeout: { type: "server_error", code: "concurrency_timeout" },
|
|
114
114
|
promptTooLong: { type: "invalid_request_error", code: "context_window_exceeded" },
|
|
115
|
+
unsupportedModality: { type: "invalid_request_error", code: "unsupported_modality" },
|
|
115
116
|
};
|
|
116
117
|
const apiTypeErrors = createErrorFormatter((kind, message) => ({ error: { message, ...errorMeta[kind] } }));
|
|
117
118
|
// 默认 upstream path 从 adapter 获取
|
|
@@ -120,24 +120,31 @@ export async function executeFailoverLoop(ctx, errors, deps, upstreamPath, adapt
|
|
|
120
120
|
const resolveResult = resolveMapping(db, clientModel, { now: new Date() });
|
|
121
121
|
// resolveMapping 返回 null 时,需要用占位 snapshot 做错误日志
|
|
122
122
|
const rejectSnapshot = new PipelineSnapshot();
|
|
123
|
+
/** 构建 RejectParams,消除 4 处重复构造 */
|
|
124
|
+
const buildRejectCtx = (logId, snapshot, overrides) => ({
|
|
125
|
+
db, logId, apiType: ctx.apiType, model: clientModel,
|
|
126
|
+
startTime: Date.now(),
|
|
127
|
+
isStream: ctx.body.stream === true,
|
|
128
|
+
routerKeyId: request.routerKey?.id ?? null,
|
|
129
|
+
originalBody: rawBody, clientHeaders: cliHdrs,
|
|
130
|
+
isFailover: false, originalRequestId: null,
|
|
131
|
+
sessionId: ctx.metadata.get("session_id"),
|
|
132
|
+
pipelineSnapshot: snapshot.toJSON(),
|
|
133
|
+
matcher, logFileWriter,
|
|
134
|
+
...overrides,
|
|
135
|
+
});
|
|
123
136
|
if (!resolveResult) {
|
|
124
137
|
const logId = randomUUID();
|
|
125
|
-
|
|
126
|
-
const isStream = ctx.body.stream === true;
|
|
127
|
-
const rCtx = {
|
|
128
|
-
db, logId, apiType: ctx.apiType, model: clientModel,
|
|
129
|
-
startTime, isStream, routerKeyId: request.routerKey?.id ?? null, originalBody: rawBody, clientHeaders: cliHdrs,
|
|
130
|
-
isFailover: false, originalRequestId: null,
|
|
131
|
-
sessionId: ctx.metadata.get("session_id"),
|
|
132
|
-
pipelineSnapshot: rejectSnapshot.toJSON(),
|
|
133
|
-
matcher, logFileWriter,
|
|
134
|
-
};
|
|
135
|
-
return rejectAndReply(reply, rCtx, errors.modelNotFound(clientModel), `No mapping found for model '${clientModel}'`);
|
|
138
|
+
return rejectAndReply(reply, buildRejectCtx(logId, rejectSnapshot), errors.modelNotFound(clientModel), `No mapping found for model '${clientModel}'`);
|
|
136
139
|
}
|
|
137
140
|
let allTargets = resolveResult.allTargets ?? [resolveResult.target];
|
|
138
141
|
const concurrencyOverride = resolveResult.concurrency_override;
|
|
139
142
|
// 2. modality-redirect 层:模态重定向 → 可能 prepend fallback target
|
|
140
143
|
allTargets = computeModalityRedirectTargets(db, allTargets, clientModel, ctx.body, precomputeSnapshot);
|
|
144
|
+
// 2a. modality-redirect 层返回空列表 → 提前报错(无 target 支持请求模态)
|
|
145
|
+
if (allTargets.length === 0) {
|
|
146
|
+
return rejectAndReply(reply, buildRejectCtx(randomUUID(), precomputeSnapshot), errors.unsupportedModality(), `No eligible target: request modalities not supported by any available model`);
|
|
147
|
+
}
|
|
141
148
|
// 3. OF 层:为每个 target 预计算 overflow
|
|
142
149
|
const targetsBeforeOF = allTargets.length;
|
|
143
150
|
const ofResult = expandOverflowTargets(allTargets, db, ctx.body);
|
|
@@ -160,18 +167,7 @@ export async function executeFailoverLoop(ctx, errors, deps, upstreamPath, adapt
|
|
|
160
167
|
allTargets = filtered;
|
|
161
168
|
overflowIndices = newOverflowIndices;
|
|
162
169
|
if (allTargets.length === 0) {
|
|
163
|
-
|
|
164
|
-
const startTime = Date.now();
|
|
165
|
-
const isStream = ctx.body.stream === true;
|
|
166
|
-
const rCtx = {
|
|
167
|
-
db, logId, apiType: ctx.apiType, model: clientModel,
|
|
168
|
-
startTime, isStream, routerKeyId: request.routerKey?.id ?? null, originalBody: rawBody, clientHeaders: cliHdrs,
|
|
169
|
-
isFailover: false, originalRequestId: null,
|
|
170
|
-
sessionId: ctx.metadata.get("session_id"),
|
|
171
|
-
pipelineSnapshot: precomputeSnapshot.toJSON(),
|
|
172
|
-
matcher, logFileWriter,
|
|
173
|
-
};
|
|
174
|
-
return rejectAndReply(reply, rCtx, errors.modelNotAllowed(clientModel), `No allowed model available for '${clientModel}'`);
|
|
170
|
+
return rejectAndReply(reply, buildRejectCtx(randomUUID(), precomputeSnapshot), errors.modelNotAllowed(clientModel), `No allowed model available for '${clientModel}'`);
|
|
175
171
|
}
|
|
176
172
|
}
|
|
177
173
|
// 预计算完成,缓存到循环外
|
|
@@ -206,14 +202,11 @@ export async function executeFailoverLoop(ctx, errors, deps, upstreamPath, adapt
|
|
|
206
202
|
let currentBody = { ...ctx.body };
|
|
207
203
|
const isStream = currentBody.stream === true;
|
|
208
204
|
const iterationSnapshot = new PipelineSnapshot(precomputeSnapshot.getStages());
|
|
209
|
-
const rCtx = {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
pipelineSnapshot: iterationSnapshot.toJSON(),
|
|
215
|
-
matcher, logFileWriter,
|
|
216
|
-
};
|
|
205
|
+
const rCtx = buildRejectCtx(logId, iterationSnapshot, {
|
|
206
|
+
startTime,
|
|
207
|
+
isFailover: isFailoverIteration,
|
|
208
|
+
originalRequestId: isFailoverIteration ? rootLogId : null,
|
|
209
|
+
});
|
|
217
210
|
// --- 选第一个非 excluded target ---
|
|
218
211
|
const filtered = filterExcluded(cachedTargets, excludeTargets);
|
|
219
212
|
if (filtered.length === 0) {
|
|
@@ -112,8 +112,25 @@ export function serializeBlocksForStorage(blocks, apiType) {
|
|
|
112
112
|
});
|
|
113
113
|
return JSON.stringify({ content });
|
|
114
114
|
}
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
// OpenAI / openai-responses:按类型保留结构,前端 parseOpenAIChoices 可完整解析
|
|
116
|
+
const message = {};
|
|
117
|
+
const thinkingParts = blocks.filter(b => b.type === "thinking");
|
|
118
|
+
const textParts = blocks.filter(b => b.type === "text");
|
|
119
|
+
const toolParts = blocks.filter(b => b.type === "tool_use");
|
|
120
|
+
if (thinkingParts.length > 0) {
|
|
121
|
+
message.reasoning_content = thinkingParts.map(b => b.content).join("");
|
|
122
|
+
}
|
|
123
|
+
if (textParts.length > 0) {
|
|
124
|
+
message.content = textParts.map(b => b.content).join("");
|
|
125
|
+
}
|
|
126
|
+
if (toolParts.length > 0) {
|
|
127
|
+
message.tool_calls = toolParts.map((b, i) => ({
|
|
128
|
+
id: `call_storage_${i}`,
|
|
129
|
+
type: "function",
|
|
130
|
+
function: { name: b.name ?? "", arguments: b.content },
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
return JSON.stringify({ choices: [{ message }] });
|
|
117
134
|
}
|
|
118
135
|
/** 从请求体中提取最后一次工具调用记录 */
|
|
119
136
|
export function extractLastToolUse(body) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Provider } from "../db/index.js";
|
|
2
2
|
import type { GetTransportResult } from "./transport/http.js";
|
|
3
3
|
import type { RawHeaders } from "./types.js";
|
|
4
|
+
import type { ErrorKind } from "./format/types.js";
|
|
4
5
|
export interface ProxyErrorResponse {
|
|
5
6
|
statusCode: number;
|
|
6
7
|
body: unknown;
|
|
@@ -14,8 +15,9 @@ export interface ProxyErrorFormatter {
|
|
|
14
15
|
concurrencyQueueFull(providerId: string): ProxyErrorResponse;
|
|
15
16
|
concurrencyTimeout(providerId: string, timeoutMs: number): ProxyErrorResponse;
|
|
16
17
|
promptTooLong(): ProxyErrorResponse;
|
|
18
|
+
unsupportedModality(): ProxyErrorResponse;
|
|
17
19
|
}
|
|
18
|
-
export type ErrorKind
|
|
20
|
+
export type { ErrorKind } from "./format/types.js";
|
|
19
21
|
/**
|
|
20
22
|
* 工厂函数,消除 openai/anthropic 错误格式化的重复代码。
|
|
21
23
|
* statusCode 和 message 两个 provider 完全一致,仅 body 格式不同,
|
package/dist/proxy/proxy-core.js
CHANGED
|
@@ -38,6 +38,10 @@ export function createErrorFormatter(formatBody) {
|
|
|
38
38
|
statusCode: 400,
|
|
39
39
|
body: formatBody("promptTooLong", "Prompt is too long: the input tokens exceed the model context window limit."),
|
|
40
40
|
}),
|
|
41
|
+
unsupportedModality: () => ({
|
|
42
|
+
statusCode: 400,
|
|
43
|
+
body: formatBody("unsupportedModality", "Request contains multimodal content but no available model supports the required modality."),
|
|
44
|
+
}),
|
|
41
45
|
};
|
|
42
46
|
}
|
|
43
47
|
// ---------- URL utilities ----------
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MRL(Modality Redirect)预计算层
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 纯函数:检测请求体是否包含多模态内容(图片、音频等),过滤不支持对应模态的 targets,
|
|
5
|
+
* 必要时用 multimodal_fallback 替换全部被过滤的 targets。
|
|
6
6
|
*/
|
|
7
7
|
import type Database from "better-sqlite3";
|
|
8
8
|
import type { Target } from "../../core/types.js";
|
|
@@ -17,6 +17,9 @@ import { PipelineSnapshot } from "../pipeline-snapshot.js";
|
|
|
17
17
|
*/
|
|
18
18
|
export declare function detectModalities(body: Record<string, unknown>): Set<string>;
|
|
19
19
|
/**
|
|
20
|
-
* MRL
|
|
20
|
+
* MRL 层主函数。采用 filter+replace 策略:
|
|
21
|
+
* 1. 过滤不支持请求模态的 targets
|
|
22
|
+
* 2. 全部过滤完时尝试用 multimodal_fallback 替换
|
|
23
|
+
* 异常安全:任何内部错误均 catch 并返回原始 targets。
|
|
21
24
|
*/
|
|
22
25
|
export declare function computeModalityRedirectTargets(db: Database.Database, targets: Target[], clientModel: string, body: Record<string, unknown>, snapshot: PipelineSnapshot): Target[];
|
|
@@ -80,16 +80,27 @@ function supportsModality(capabilities, modality) {
|
|
|
80
80
|
return Array.isArray(capabilities) && capabilities.includes(modality);
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
|
-
* MRL
|
|
83
|
+
* MRL 层主函数。采用 filter+replace 策略:
|
|
84
|
+
* 1. 过滤不支持请求模态的 targets
|
|
85
|
+
* 2. 全部过滤完时尝试用 multimodal_fallback 替换
|
|
86
|
+
* 异常安全:任何内部错误均 catch 并返回原始 targets。
|
|
84
87
|
*/
|
|
85
88
|
export function computeModalityRedirectTargets(db, targets, clientModel, body, snapshot) {
|
|
86
89
|
try {
|
|
87
|
-
//
|
|
88
|
-
if (targets.length === 0)
|
|
90
|
+
// 1. 空列表 → 记录诊断信息后返回(运维排查 400 时可看到 modality-redirect 阶段记录)
|
|
91
|
+
if (targets.length === 0) {
|
|
92
|
+
snapshot.add({
|
|
93
|
+
stage: "modality-redirect",
|
|
94
|
+
triggered: false,
|
|
95
|
+
original_model: "",
|
|
96
|
+
redirect_to: "",
|
|
97
|
+
redirect_provider: "",
|
|
98
|
+
reason: "empty-targets-input",
|
|
99
|
+
});
|
|
89
100
|
return targets;
|
|
90
|
-
|
|
101
|
+
}
|
|
102
|
+
// 2. 检测多模态内容
|
|
91
103
|
const modalities = detectModalities(body);
|
|
92
|
-
// 无多模态内容 → no-op
|
|
93
104
|
if (modalities.size === 0) {
|
|
94
105
|
snapshot.add({
|
|
95
106
|
stage: "modality-redirect",
|
|
@@ -101,39 +112,65 @@ export function computeModalityRedirectTargets(db, targets, clientModel, body, s
|
|
|
101
112
|
});
|
|
102
113
|
return targets;
|
|
103
114
|
}
|
|
104
|
-
//
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
115
|
+
// 3. 过滤:遍历所有 targets,检查每个是否支持所有检测到的模态
|
|
116
|
+
const eligible = [];
|
|
117
|
+
for (const target of targets) {
|
|
118
|
+
const provider = getProviderById(db, target.provider_id);
|
|
119
|
+
if (!provider) {
|
|
120
|
+
// provider 不存在 → 保留(安全行为)
|
|
121
|
+
eligible.push(target);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
108
124
|
const entries = parseModels(provider.models);
|
|
109
|
-
const entry = entries.find(e => e.name ===
|
|
110
|
-
const
|
|
111
|
-
const allSupported = [...modalities].every(m => supportsModality(
|
|
125
|
+
const entry = entries.find(e => e.name === target.backend_model);
|
|
126
|
+
const capabilities = entry?.capabilities ?? [];
|
|
127
|
+
const allSupported = [...modalities].every(m => supportsModality(capabilities, m));
|
|
112
128
|
if (allSupported) {
|
|
113
|
-
|
|
114
|
-
stage: "modality-redirect",
|
|
115
|
-
triggered: false,
|
|
116
|
-
original_model: firstTarget.backend_model,
|
|
117
|
-
redirect_to: "",
|
|
118
|
-
redirect_provider: "",
|
|
119
|
-
reason: "first-target-supports-all-modalities",
|
|
120
|
-
});
|
|
121
|
-
return targets;
|
|
129
|
+
eligible.push(target);
|
|
122
130
|
}
|
|
131
|
+
// 不支持 → 过滤掉
|
|
123
132
|
}
|
|
124
|
-
//
|
|
133
|
+
// 4. 全部支持 → 不需要过滤
|
|
134
|
+
if (eligible.length === targets.length) {
|
|
135
|
+
snapshot.add({
|
|
136
|
+
stage: "modality-redirect",
|
|
137
|
+
triggered: false,
|
|
138
|
+
original_model: targets[0].backend_model,
|
|
139
|
+
redirect_to: "",
|
|
140
|
+
redirect_provider: "",
|
|
141
|
+
reason: "all-targets-support-modalities",
|
|
142
|
+
});
|
|
143
|
+
return targets;
|
|
144
|
+
}
|
|
145
|
+
// 5. 部分过滤 → 返回 eligible
|
|
146
|
+
if (eligible.length > 0) {
|
|
147
|
+
snapshot.add({
|
|
148
|
+
stage: "modality-redirect",
|
|
149
|
+
triggered: true,
|
|
150
|
+
original_model: targets[0].backend_model,
|
|
151
|
+
redirect_to: "",
|
|
152
|
+
redirect_provider: "",
|
|
153
|
+
reason: "filtered-ineligible-targets",
|
|
154
|
+
detected_modalities: [...modalities],
|
|
155
|
+
});
|
|
156
|
+
return eligible;
|
|
157
|
+
}
|
|
158
|
+
// 6. 全部过滤完 → 尝试 fallback
|
|
159
|
+
const firstOriginalModel = targets[0].backend_model;
|
|
160
|
+
// 6a. 查找映射组
|
|
125
161
|
const group = getMappingGroup(db, clientModel);
|
|
126
162
|
if (!group) {
|
|
127
163
|
snapshot.add({
|
|
128
164
|
stage: "modality-redirect",
|
|
129
165
|
triggered: false,
|
|
130
|
-
original_model:
|
|
166
|
+
original_model: firstOriginalModel,
|
|
131
167
|
redirect_to: "",
|
|
132
168
|
redirect_provider: "",
|
|
133
169
|
reason: "no-mapping-group",
|
|
134
170
|
});
|
|
135
|
-
return
|
|
171
|
+
return [];
|
|
136
172
|
}
|
|
173
|
+
// 6b. 解析 rule
|
|
137
174
|
let rule;
|
|
138
175
|
try {
|
|
139
176
|
rule = JSON.parse(group.rule);
|
|
@@ -142,24 +179,25 @@ export function computeModalityRedirectTargets(db, targets, clientModel, body, s
|
|
|
142
179
|
snapshot.add({
|
|
143
180
|
stage: "modality-redirect",
|
|
144
181
|
triggered: false,
|
|
145
|
-
original_model:
|
|
182
|
+
original_model: firstOriginalModel,
|
|
146
183
|
redirect_to: "",
|
|
147
184
|
redirect_provider: "",
|
|
148
185
|
reason: "rule-parse-error",
|
|
149
186
|
});
|
|
150
|
-
return
|
|
187
|
+
return [];
|
|
151
188
|
}
|
|
189
|
+
// 6c. 检查 multimodal_fallback 配置
|
|
152
190
|
const fallback = rule.multimodal_fallback;
|
|
153
191
|
if (fallback == null || typeof fallback !== "object") {
|
|
154
192
|
snapshot.add({
|
|
155
193
|
stage: "modality-redirect",
|
|
156
194
|
triggered: false,
|
|
157
|
-
original_model:
|
|
195
|
+
original_model: firstOriginalModel,
|
|
158
196
|
redirect_to: "",
|
|
159
197
|
redirect_provider: "",
|
|
160
|
-
reason: "no-
|
|
198
|
+
reason: "no-eligible-targets",
|
|
161
199
|
});
|
|
162
|
-
return
|
|
200
|
+
return [];
|
|
163
201
|
}
|
|
164
202
|
const fb = fallback;
|
|
165
203
|
const fbProviderId = fb.provider_id;
|
|
@@ -168,59 +206,43 @@ export function computeModalityRedirectTargets(db, targets, clientModel, body, s
|
|
|
168
206
|
snapshot.add({
|
|
169
207
|
stage: "modality-redirect",
|
|
170
208
|
triggered: false,
|
|
171
|
-
original_model:
|
|
209
|
+
original_model: firstOriginalModel,
|
|
172
210
|
redirect_to: "",
|
|
173
211
|
redirect_provider: "",
|
|
174
|
-
reason: "
|
|
212
|
+
reason: "no-eligible-targets",
|
|
175
213
|
});
|
|
176
|
-
return
|
|
214
|
+
return [];
|
|
177
215
|
}
|
|
178
|
-
// fallback provider 必须存在且 active
|
|
216
|
+
// 6d. fallback provider 必须存在且 active
|
|
179
217
|
const fbProvider = getProviderById(db, fbProviderId);
|
|
180
218
|
if (!fbProvider || fbProvider.is_active !== 1) {
|
|
181
219
|
snapshot.add({
|
|
182
220
|
stage: "modality-redirect",
|
|
183
221
|
triggered: false,
|
|
184
|
-
original_model:
|
|
222
|
+
original_model: firstOriginalModel,
|
|
185
223
|
redirect_to: fbBackendModel,
|
|
186
224
|
redirect_provider: fbProviderId,
|
|
187
|
-
reason: "
|
|
225
|
+
reason: "no-eligible-targets",
|
|
188
226
|
});
|
|
189
|
-
return
|
|
227
|
+
return [];
|
|
190
228
|
}
|
|
191
|
-
//
|
|
192
|
-
const firstTargetCapabilities = provider
|
|
193
|
-
? parseModels(provider.models).find(e => e.name === firstTarget.backend_model)?.capabilities ?? []
|
|
194
|
-
: [];
|
|
195
|
-
const missingModalities = [...modalities].filter(m => !supportsModality(firstTargetCapabilities, m));
|
|
229
|
+
// 6e. fallback 必须覆盖所有检测到的模态
|
|
196
230
|
const fbEntry = parseModels(fbProvider.models).find(e => e.name === fbBackendModel);
|
|
197
231
|
const fbCapabilities = fbEntry?.capabilities ?? [];
|
|
198
|
-
const fbMissing =
|
|
232
|
+
const fbMissing = [...modalities].filter(m => !supportsModality(fbCapabilities, m));
|
|
199
233
|
if (fbMissing.length > 0) {
|
|
200
234
|
snapshot.add({
|
|
201
235
|
stage: "modality-redirect",
|
|
202
236
|
triggered: false,
|
|
203
|
-
original_model:
|
|
237
|
+
original_model: firstOriginalModel,
|
|
204
238
|
redirect_to: fbBackendModel,
|
|
205
239
|
redirect_provider: fbProviderId,
|
|
206
|
-
reason: "
|
|
240
|
+
reason: "no-eligible-targets",
|
|
207
241
|
detected_modalities: [...modalities],
|
|
208
242
|
});
|
|
209
|
-
return
|
|
210
|
-
}
|
|
211
|
-
// prepend fallback target(如果与首 target 相同则跳过,避免重复消耗 failover 迭代)
|
|
212
|
-
if (fbProviderId === firstTarget.provider_id && fbBackendModel === firstTarget.backend_model) {
|
|
213
|
-
snapshot.add({
|
|
214
|
-
stage: "modality-redirect",
|
|
215
|
-
triggered: false,
|
|
216
|
-
original_model: firstTarget.backend_model,
|
|
217
|
-
redirect_to: fbBackendModel,
|
|
218
|
-
redirect_provider: fbProviderId,
|
|
219
|
-
reason: "fallback-same-as-first-target",
|
|
220
|
-
detected_modalities: [...modalities],
|
|
221
|
-
});
|
|
222
|
-
return targets;
|
|
243
|
+
return [];
|
|
223
244
|
}
|
|
245
|
+
// 6f. fallback 覆盖所有模态 → 替换
|
|
224
246
|
const fbTarget = {
|
|
225
247
|
provider_id: fbProviderId,
|
|
226
248
|
backend_model: fbBackendModel,
|
|
@@ -228,17 +250,18 @@ export function computeModalityRedirectTargets(db, targets, clientModel, body, s
|
|
|
228
250
|
snapshot.add({
|
|
229
251
|
stage: "modality-redirect",
|
|
230
252
|
triggered: true,
|
|
231
|
-
original_model:
|
|
253
|
+
original_model: firstOriginalModel,
|
|
232
254
|
redirect_to: fbBackendModel,
|
|
233
255
|
redirect_provider: fbProviderId,
|
|
234
|
-
reason: "
|
|
256
|
+
reason: "replaced-with-fallback",
|
|
235
257
|
detected_modalities: [...modalities],
|
|
236
258
|
});
|
|
237
|
-
return [fbTarget
|
|
259
|
+
return [fbTarget];
|
|
238
260
|
}
|
|
239
261
|
catch (err) {
|
|
240
|
-
//
|
|
241
|
-
|
|
262
|
+
// 异常安全:返回空数组,让 failover-loop 统一走 unsupportedModality 错误路径
|
|
263
|
+
// 避免将多模态请求发给不支持模态的 provider(比返回原始 targets 更安全)
|
|
264
|
+
console.error('computeModalityRedirectTargets: internal error, returning empty targets', err);
|
|
242
265
|
snapshot.add({
|
|
243
266
|
stage: "modality-redirect",
|
|
244
267
|
triggered: false,
|
|
@@ -247,6 +270,6 @@ export function computeModalityRedirectTargets(db, targets, clientModel, body, s
|
|
|
247
270
|
redirect_provider: "",
|
|
248
271
|
reason: "internal-error",
|
|
249
272
|
});
|
|
250
|
-
return
|
|
273
|
+
return [];
|
|
251
274
|
}
|
|
252
275
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Gt as e,et as t,gt as n,it as r,qt as i,r as a,yt as o}from"./button-
|
|
1
|
+
import{Gt as e,et as t,gt as n,it as r,qt as i,r as a,yt as o}from"./button-CT60E_JD.js";var s=[`data-size`],c=r({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{default:`default`}},setup(r){let c=r;return(l,u)=>(n(),t(`div`,{"data-slot":`card`,"data-size":r.size,class:i(e(a)(`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))},[o(l.$slots,`default`)],10,s))}}),l=r({__name:`CardContent`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(n(),t(`div`,{"data-slot":`card-content`,class:i(e(a)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[o(r.$slots,`default`)],2))}});export{c as n,l as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Gt as e,et as t,gt as n,it as r,qt as i,r as a,yt as o}from"./button-
|
|
1
|
+
import{Gt as e,et as t,gt as n,it as r,qt as i,r as a,yt as o}from"./button-CT60E_JD.js";var s=r({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(n(),t(`div`,{"data-slot":`card-header`,class:i(e(a)(`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))},[o(r.$slots,`default`)],2))}}),c=r({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(n(),t(`div`,{"data-slot":`card-title`,class:i(e(a)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[o(r.$slots,`default`)],2))}});export{s as n,c as t};
|
package/frontend-dist/assets/{CascadingModelSelect-D9g0YGCD.js → CascadingModelSelect-CrtNasp8.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,Gt as t,H as n,J as r,L as i,Lt as a,Q as o,X as s,Xt as c,Z as l,et as u,gt as d,it as f,kt as p,qt as m,rt as h,vt as g}from"./button-
|
|
1
|
+
import{$ as e,Gt as t,H as n,J as r,L as i,Lt as a,Q as o,X as s,Xt as c,Z as l,et as u,gt as d,it as f,kt as p,qt as m,rt as h,vt as g}from"./button-CT60E_JD.js";import{b as _,ft as v,ht as y,v as b,y as x}from"./index-bGqHF9Js.js";var S=i(`chevron-right`,[[`path`,{d:`m9 18 6-6-6-6`,key:`mthhwq`}]]),C=[`onMouseenter`],w={class:`truncate max-w-40`},T={key:0,class:`ml-1 text-[10px] px-1 py-px rounded bg-emerald-500/15 text-emerald-400 shrink-0`},E=[`onMouseenter`],D=[`onClick`],O={class:`truncate`},k={key:0,class:`shrink-0 text-xs text-muted-foreground`},A={key:0,class:`px-2 py-1.5 text-sm text-muted-foreground`},j=f({__name:`CascadingSelect`,props:{groups:{},modelValue:{},placeholder:{default:``},compact:{type:Boolean,default:!1}},emits:[`update:modelValue`],setup(i,{emit:f}){let{t:y}=n(),j=i,M=s(()=>j.placeholder||y(`common.selectPlaceholder`)),N=f,P=a(!1),F=a(null),I=s(()=>{if(!j.modelValue)return``;let e=j.groups.find(e=>e.key===j.modelValue.groupKey);if(!e)return``;let t=e.options.find(e=>e.value===j.modelValue.value);return t?`${e.label} / ${t.label}`:``});function L(e,t){N(`update:modelValue`,{groupKey:e,value:t}),P.value=!1}function R(e){P.value=e,e||(F.value=null)}return(n,a)=>(d(),o(t(_),{open:P.value,"onUpdate:open":R},{default:p(()=>[h(t(b),{"as-child":``},{default:p(()=>[l(`div`,{class:m([`flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background cursor-pointer hover:bg-accent hover:text-accent-foreground`,[i.compact?`h-8 text-xs px-2.5 py-1`:`h-10 text-sm px-3 py-2`,{"ring-2 ring-ring ring-offset-2":P.value}]])},[l(`span`,{class:m([`truncate`,i.modelValue?`text-foreground`:`text-muted-foreground`])},c(I.value||M.value),3),h(t(v),{class:`h-4 w-4 shrink-0 opacity-50`})],2)]),_:1}),h(t(x),{align:`start`,"side-offset":4,class:`z-[200] w-auto min-w-56 overflow-visible p-1`},{default:p(()=>[(d(!0),u(r,null,g(i.groups,n=>(d(),u(`div`,{key:n.key,class:m([`relative flex cursor-pointer items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground`,{"bg-accent text-accent-foreground z-10":F.value===n.key}]),onMouseenter:e=>F.value=n.key},[l(`span`,w,c(n.label),1),n.badge?(d(),u(`span`,T,c(n.badge),1)):e(``,!0),h(t(S),{class:`ml-1 h-4 w-4 shrink-0 opacity-50`}),F.value===n.key&&n.options.length>0?(d(),u(`div`,{key:1,class:`absolute left-full top-0 ml-0.5 min-w-48 rounded-md border bg-popover p-1 text-popover-foreground shadow-md`,onMouseenter:e=>F.value=n.key},[(d(!0),u(r,null,g(n.options,t=>(d(),u(`div`,{key:t.value,class:m([`flex cursor-pointer items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground`,{"bg-accent text-accent-foreground":i.modelValue?.groupKey===n.key&&i.modelValue?.value===t.value}]),onClick:e=>L(n.key,t.value)},[l(`span`,O,c(t.label),1),t.tag?(d(),u(`span`,k,c(t.tag),1)):e(``,!0)],10,D))),128))],40,E)):e(``,!0)],42,C))),128)),i.groups.length===0?(d(),u(`div`,A,c(t(y)(`common.noOptions`)),1)):e(``,!0)]),_:1})]),_:1},8,[`open`]))}}),M=f({__name:`CascadingModelSelect`,props:{providers:{},modelValue:{},placeholder:{default:``},compact:{type:Boolean}},emits:[`update:modelValue`],setup(e,{emit:t}){let{t:r}=n(),i=e,a=s(()=>i.placeholder||r(`mappings.selectProviderModel`)),c=t,l=s(()=>i.providers.map(e=>({key:e.provider.id,label:e.provider.name,badge:e.isNew?r(`common.new`):void 0,options:e.models.map(e=>({value:e.name,label:e.name,tag:y(e.contextWindow)}))}))),u=s(()=>i.modelValue?{groupKey:i.modelValue.provider_id,value:i.modelValue.model}:void 0);function f(e){c(`update:modelValue`,{provider_id:e.groupKey,model:e.value})}return(t,n)=>(d(),o(j,{groups:l.value,"model-value":u.value,placeholder:a.value,compact:e.compact,"onUpdate:modelValue":f},null,8,[`groups`,`model-value`,`placeholder`,`compact`]))}});export{M as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,Gt as t,Jt as n,K as r,Q as i,X as a,dt as o,gt as s,i as c,it as l,kt as u,m as d,o as f,ot as p,q as m,r as h,rt as g,x as _,xt as v,yt as y}from"./button-
|
|
1
|
+
import{$ as e,Gt as t,Jt as n,K as r,Q as i,X as a,dt as o,gt as s,i as c,it as l,kt as u,m as d,o as f,ot as p,q as m,r as h,rt as g,x as _,xt as v,yt as y}from"./button-CT60E_JD.js";import{t as b}from"./VisuallyHiddenInput-Bkpr-VfM.js";import{t as x}from"./RovingFocusItem-Bx5uXXN2.js";import{J as S,K as C,R as w,U as T,V as E,X as D,pt as O}from"./index-bGqHF9Js.js";function k(e,t){return C(e)?!1:Array.isArray(e)?e.some(e=>D(e,t)):D(e,t)}var[A,j]=S(`CheckboxGroupRoot`);function M(e){return e===`indeterminate`}function N(e){return M(e)?`indeterminate`:e?`checked`:`unchecked`}var[P,F]=S(`CheckboxRoot`),I=l({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(n,{emit:l}){let p=n,h=l,{forwardRef:g,currentElement:_}=f(),S=A(null),w=d(p,`modelValue`,h,{defaultValue:p.defaultValue??p.falseValue,passive:p.modelValue===void 0}),E=a(()=>S?.disabled.value||p.disabled),O=a(()=>D(w.value,p.trueValue)),j=a(()=>C(S?.modelValue.value)?w.value===`indeterminate`?`indeterminate`:O.value:k(S.modelValue.value,p.value));function P(){if(C(S?.modelValue.value))w.value===`indeterminate`?w.value=p.trueValue:w.value=O.value?p.falseValue:p.trueValue;else{let e=[...S.modelValue.value||[]];if(k(e,p.value)){let t=e.findIndex(e=>D(e,p.value));e.splice(t,1)}else e.push(p.value);S.modelValue.value=e}}let I=T(_),L=a(()=>p.id&&_.value?document.querySelector(`[for="${p.id}"]`)?.innerText:void 0);return F({disabled:E,state:j}),(n,a)=>(s(),i(v(t(S)?.rovingFocus.value?t(x):t(c)),o(n.$attrs,{id:n.id,ref:t(g),role:`checkbox`,"as-child":n.asChild,as:n.as,type:n.as===`button`?`button`:void 0,"aria-checked":t(M)(j.value)?`mixed`:j.value,"aria-required":n.required,"aria-label":n.$attrs[`aria-label`]||L.value,"data-state":t(N)(j.value),"data-disabled":E.value?``:void 0,disabled:E.value,focusable:t(S)?.rovingFocus.value?!E.value:void 0,onKeydown:r(m(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:u(()=>[y(n.$slots,`default`,{modelValue:t(w),state:j.value}),t(I)&&n.name&&!t(S)?(s(),i(t(b),{key:0,type:`checkbox`,checked:!!j.value,name:n.name,value:n.value,disabled:E.value,required:n.required},null,8,[`checked`,`name`,`value`,`disabled`,`required`])):e(`v-if`,!0)]),_:3},16,[`id`,`as-child`,`as`,`type`,`aria-checked`,`aria-required`,`aria-label`,`data-state`,`data-disabled`,`disabled`,`focusable`,`onKeydown`]))}}),L=l({__name:`CheckboxIndicator`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`span`}},setup(e){let{forwardRef:n}=f(),r=P();return(e,a)=>(s(),i(t(w),{present:e.forceMount||t(M)(t(r).state.value)||t(r).state.value===!0},{default:u(()=>[g(t(c),o({ref:t(n),"data-state":t(N)(t(r).state.value),"data-disabled":t(r).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":e.asChild,as:e.as},e.$attrs),{default:u(()=>[y(e.$slots,`default`)]),_:3},16,[`data-state`,`data-disabled`,`as-child`,`as`])]),_:3},8,[`present`]))}}),R=l({__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:r}){let a=e,c=r,l=E(_(a,`class`),c);return(e,r)=>(s(),i(t(I),o({"data-slot":`checkbox`},t(l),{class:t(h)(`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`,a.class)}),{default:u(r=>[g(t(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:u(()=>[y(e.$slots,`default`,n(p(r)),()=>[g(t(O))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
|
package/frontend-dist/assets/{CollapsibleContent-Dvc5Q0jk.js → CollapsibleContent-CvvkOB4a.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,Et as t,Gt as n,Ht as r,Jt as i,Lt as a,Q as o,X as s,d as c,dt as l,ft as u,gt as d,i as f,it as p,kt as m,m as h,mt as g,o as _,ot as v,rt as y,yt as b}from"./button-
|
|
1
|
+
import{$ as e,Et as t,Gt as n,Ht as r,Jt as i,Lt as a,Q as o,X as s,d as c,dt as l,ft as u,gt as d,i as f,it as p,kt as m,m as h,mt as g,o as _,ot as v,rt as y,yt as b}from"./button-CT60E_JD.js";import{B as x,J as S,R as C,V as w}from"./index-bGqHF9Js.js";var[T,E]=S(`CollapsibleRoot`),D=p({__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:i}){let a=e,s=h(a,`open`,i,{defaultValue:a.defaultOpen,passive:a.open===void 0}),{disabled:c,unmountOnHide:l}=r(a);return E({contentId:``,disabled:c,open:s,unmountOnHide:l,onOpenToggle:()=>{c.value||(s.value=!s.value)}}),t({open:s}),_(),(e,t)=>(d(),o(n(f),{as:e.as,"as-child":a.asChild,"data-state":n(s)?`open`:`closed`,"data-disabled":n(c)?``:void 0},{default:m(()=>[b(e.$slots,`default`,{open:n(s)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),O=p({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(r,{emit:i}){let p=r,h=i,v=T();v.contentId||=x(void 0,`reka-collapsible-content`);let S=a(),{forwardRef:w,currentElement:E}=_(),D=a(0),O=a(0),k=s(()=>v.open.value),A=a(k.value),j=a();t(()=>[k.value,S.value?.present],async()=>{await u();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=s(()=>A.value&&v.open.value);return g(()=>{requestAnimationFrame(()=>{A.value=!1})}),c(E,`beforematch`,e=>{requestAnimationFrame(()=>{v.onOpenToggle(),h(`contentFound`)})}),(t,r)=>(d(),o(n(C),{ref_key:`presentRef`,ref:S,present:t.forceMount||n(v).open.value,"force-mount":!0},{default:m(({present:r})=>[y(n(f),l(t.$attrs,{id:n(v).contentId,ref:n(w),"as-child":p.asChild,as:t.as,hidden:r?void 0:n(v).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:n(v).open.value?`open`:`closed`,"data-disabled":n(v).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:m(()=>[!n(v).unmountOnHide.value||r?b(t.$slots,`default`,{key:0}):e(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),k=p({__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=w(e,t);return(e,t)=>(d(),o(n(D),l({"data-slot":`collapsible`},n(r)),{default:m(t=>[b(e.$slots,`default`,i(v(t)))]),_:3},16))}}),A=p({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,r)=>(d(),o(n(O),l({"data-slot":`collapsible-content`},t),{default:m(()=>[b(e.$slots,`default`)]),_:3},16))}});export{k as n,T as r,A as t};
|