llm-simple-router 0.8.0 → 0.8.2
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/admin/proxy-enhancement.js +3 -1
- package/dist/index.js +22 -0
- package/dist/monitor/request-tracker.d.ts +2 -0
- package/dist/monitor/request-tracker.js +14 -0
- package/dist/proxy/handler/proxy-handler.js +13 -3
- package/dist/proxy/patch/tool-round-limiter.d.ts +38 -0
- package/dist/proxy/patch/tool-round-limiter.js +115 -0
- package/dist/proxy/pipeline-snapshot.d.ts +4 -0
- package/dist/proxy/routing/enhancement-config.d.ts +1 -0
- package/dist/proxy/routing/enhancement-config.js +2 -0
- package/dist/storage/log-file-compressor.js +5 -6
- package/dist/storage/log-file-writer.js +11 -13
- package/dist/storage/types.d.ts +2 -0
- package/dist/storage/types.js +7 -0
- package/frontend-dist/assets/{CardContent-WAXChVto.js → CardContent-BVMQ2_pg.js} +1 -1
- package/frontend-dist/assets/{CardTitle-2TP7C40C.js → CardTitle-GLv7QyIY.js} +1 -1
- package/frontend-dist/assets/{CascadingModelSelect-CBe9pEx_.js → CascadingModelSelect-CBhqKFDX.js} +1 -1
- package/frontend-dist/assets/{Checkbox-Bbf909ia.js → Checkbox-HPVDmEdV.js} +1 -1
- package/frontend-dist/assets/{CollapsibleTrigger-H2GGomkv.js → CollapsibleTrigger-DhxD9tpM.js} +1 -1
- package/frontend-dist/assets/{Collection-BdkMCE5V.js → Collection-BRt7YxN8.js} +1 -1
- package/frontend-dist/assets/{Dashboard-BQvc6U98.js → Dashboard-D1Ys8Zog.js} +1 -1
- package/frontend-dist/assets/{DialogTitle-CS0Nuvko.js → DialogTitle-23q73lwF.js} +1 -1
- package/frontend-dist/assets/{Input-B56t8UfI.js → Input-CAnKUBBK.js} +1 -1
- package/frontend-dist/assets/{Label-CXRQoDIZ.js → Label-DWdYtVMI.js} +1 -1
- package/frontend-dist/assets/{Login-DRNqP0bt.js → Login-w5WFOinP.js} +1 -1
- package/frontend-dist/assets/{Logs-DeJosCWl.js → Logs-C1F1ZmWF.js} +1 -1
- package/frontend-dist/assets/{ModelMappings-DV-NmdF7.js → ModelMappings-BzmecWEH.js} +1 -1
- package/frontend-dist/assets/{Monitor-DwqkpkuK.js → Monitor-DrAZFTKR.js} +1 -1
- package/frontend-dist/assets/{PopoverTrigger-srlKRM2q.js → PopoverTrigger-Bj65uUbv.js} +1 -1
- package/frontend-dist/assets/{PopperContent-9j4ZA5oc.js → PopperContent-gzzf1XHe.js} +1 -1
- package/frontend-dist/assets/{Providers-oYOUgEsH.js → Providers-DSgf4mb6.js} +1 -1
- package/frontend-dist/assets/ProxyEnhancement-Bb1cCP6d.js +5 -0
- package/frontend-dist/assets/{RetryRules-rlrPpTd0.js → RetryRules-BwPfEZtm.js} +1 -1
- package/frontend-dist/assets/{RouterKeys-COpe69A8.js → RouterKeys-CzTSq1Mx.js} +1 -1
- package/frontend-dist/assets/{RovingFocusItem-DbXUYGXA.js → RovingFocusItem-CXM_Yfkm.js} +1 -1
- package/frontend-dist/assets/{Schedules-gszIRN_S.js → Schedules-DVilCXrC.js} +1 -1
- package/frontend-dist/assets/{SelectValue-CpY2uWSk.js → SelectValue-C0-LzGQY.js} +1 -1
- package/frontend-dist/assets/{Settings-Xu6V0Sve.js → Settings-Bpk53zVX.js} +1 -1
- package/frontend-dist/assets/{Setup-BfcLFnBR.js → Setup-Dn7EgC49.js} +1 -1
- package/frontend-dist/assets/{Switch-DVfy7Q4A.js → Switch-BO8Ooae6.js} +1 -1
- package/frontend-dist/assets/{TableHeader-BqZo28x_.js → TableHeader-Bded9VTC.js} +1 -1
- package/frontend-dist/assets/{TabsTrigger-0L00h4oy.js → TabsTrigger-BzKMi9AF.js} +1 -1
- package/frontend-dist/assets/{Teleport-Czq5P0IN.js → Teleport-DizRK5O3.js} +1 -1
- package/frontend-dist/assets/{TooltipTrigger-ChkMBqtC.js → TooltipTrigger-EiIy2zn8.js} +1 -1
- package/frontend-dist/assets/{UnifiedRequestDialog-B3GxGgwz.js → UnifiedRequestDialog-BABsTaGb.js} +1 -1
- package/frontend-dist/assets/{VisuallyHidden-DAFM-4dn.js → VisuallyHidden-5AozJQza.js} +1 -1
- package/frontend-dist/assets/{VisuallyHiddenInput-BwfFtaqi.js → VisuallyHiddenInput-DdiZrV2i.js} +1 -1
- package/frontend-dist/assets/{alert-dialog-vdmYFjPE.js → alert-dialog-DlKUuTPe.js} +1 -1
- package/frontend-dist/assets/arrow-down-CxWKmZ2I.js +1 -0
- package/frontend-dist/assets/{badge-0gUIxSR9.js → badge-9KJEMa53.js} +1 -1
- package/frontend-dist/assets/{button-CLKo-tBF.js → button-Ul8WlrM5.js} +2 -2
- package/frontend-dist/assets/check-7ahK--N4.js +1 -0
- package/frontend-dist/assets/{copy-7NlsO7pN.js → copy-DzU2pAMG.js} +1 -1
- package/frontend-dist/assets/{dialog-DUkre9Rw.js → dialog-B9j-FMrd.js} +1 -1
- package/frontend-dist/assets/{file-text-Di1QQ-B6.js → file-text-Bj3ZIo-E.js} +1 -1
- package/frontend-dist/assets/{index-Be9MymBh.js → index-MedWZMHB.js} +1 -1
- package/frontend-dist/assets/{lib-CweCSowO.js → lib-Hhs3NqfD.js} +1 -1
- package/frontend-dist/assets/loader-circle-5TJUukEe.js +1 -0
- package/frontend-dist/assets/{useClipboard-tSRRbabN.js → useClipboard-BmmsNSGV.js} +1 -1
- package/frontend-dist/assets/{useFocusGuards-BxD_AgQe.js → useFocusGuards-A-9V2Y-b.js} +1 -1
- package/frontend-dist/assets/useFormControl-DEO19lRe.js +1 -0
- package/frontend-dist/assets/{useLogRetention-DeCxyOiV.js → useLogRetention-BfnBFZ5K.js} +1 -1
- package/frontend-dist/assets/useNonce-BfwUJ1Ci.js +1 -0
- package/frontend-dist/assets/x-Cfopt3QL.js +1 -0
- package/frontend-dist/index.html +19 -19
- package/package.json +1 -1
- package/frontend-dist/assets/ProxyEnhancement-3tQzXNGn.js +0 -5
- package/frontend-dist/assets/arrow-down-Da-mukXD.js +0 -1
- package/frontend-dist/assets/check-CEd3A-kB.js +0 -1
- package/frontend-dist/assets/loader-circle-Cb19pB9Z.js +0 -1
- package/frontend-dist/assets/useFormControl-DP5JWFRS.js +0 -1
- package/frontend-dist/assets/useNonce-DTHUE2m9.js +0 -1
- package/frontend-dist/assets/x-ztwrQbIz.js +0 -1
|
@@ -4,6 +4,7 @@ const UpdateProxyEnhancementSchema = Type.Object({
|
|
|
4
4
|
claude_code_enabled: Type.Boolean(),
|
|
5
5
|
tool_call_loop_enabled: Type.Boolean(),
|
|
6
6
|
stream_loop_enabled: Type.Boolean(),
|
|
7
|
+
tool_round_limit_enabled: Type.Boolean(),
|
|
7
8
|
});
|
|
8
9
|
const SessionParamsSchema = Type.Object({
|
|
9
10
|
keyId: Type.String(),
|
|
@@ -13,7 +14,7 @@ import { getSessionStates, getSessionHistory, } from "../db/session-states.js";
|
|
|
13
14
|
export const adminProxyEnhancementRoutes = (app, options, done) => {
|
|
14
15
|
const { db, stateRegistry } = options;
|
|
15
16
|
app.get("/admin/api/proxy-enhancement", async (_request, reply) => {
|
|
16
|
-
const config = stateRegistry?.getEnhancementConfig() ?? { claude_code_enabled: false, tool_call_loop_enabled: false, stream_loop_enabled: false };
|
|
17
|
+
const config = stateRegistry?.getEnhancementConfig() ?? { claude_code_enabled: false, tool_call_loop_enabled: false, stream_loop_enabled: false, tool_round_limit_enabled: true };
|
|
17
18
|
return reply.send(config);
|
|
18
19
|
});
|
|
19
20
|
app.put("/admin/api/proxy-enhancement", { schema: { body: UpdateProxyEnhancementSchema } }, async (request, reply) => {
|
|
@@ -22,6 +23,7 @@ export const adminProxyEnhancementRoutes = (app, options, done) => {
|
|
|
22
23
|
claude_code_enabled: body.claude_code_enabled,
|
|
23
24
|
tool_call_loop_enabled: body.tool_call_loop_enabled,
|
|
24
25
|
stream_loop_enabled: body.stream_loop_enabled,
|
|
26
|
+
tool_round_limit_enabled: body.tool_round_limit_enabled,
|
|
25
27
|
};
|
|
26
28
|
setSetting(db, "proxy_enhancement", JSON.stringify(config));
|
|
27
29
|
return reply.send({ success: true });
|
package/dist/index.js
CHANGED
|
@@ -279,11 +279,17 @@ export async function buildApp(options) {
|
|
|
279
279
|
const dbSizeMonitor = scheduleDbSizeMonitor(db, config.DB_PATH, {
|
|
280
280
|
log: app.log,
|
|
281
281
|
});
|
|
282
|
+
let closed = false;
|
|
282
283
|
let close = async () => {
|
|
284
|
+
if (closed)
|
|
285
|
+
return;
|
|
286
|
+
closed = true;
|
|
283
287
|
stopUpgradeChecker();
|
|
284
288
|
logCleanup.stop();
|
|
285
289
|
dbSizeMonitor.stop();
|
|
286
290
|
tracker.stopPushInterval();
|
|
291
|
+
// 关闭所有 SSE 长连接,防止 app.close() 因 hijack 的连接无限等待
|
|
292
|
+
tracker.closeAllClients();
|
|
287
293
|
modelState.clearAll();
|
|
288
294
|
semaphoreManager.removeAll();
|
|
289
295
|
const sessionTracker = container.resolve(SERVICE_KEYS.sessionTracker);
|
|
@@ -339,7 +345,22 @@ export async function main() {
|
|
|
339
345
|
/* eslint-enable taste/no-silent-catch */
|
|
340
346
|
});
|
|
341
347
|
// 优雅关闭:SIGTERM(systemd/docker stop)和 SIGINT(Ctrl+C)
|
|
348
|
+
let isShuttingDown = false;
|
|
349
|
+
const GRACEFUL_SHUTDOWN_TIMEOUT_MS = 10_000;
|
|
342
350
|
const shutdown = async (signal) => {
|
|
351
|
+
// 防止重复触发:多次 Ctrl+C 只执行一次关闭
|
|
352
|
+
if (isShuttingDown) {
|
|
353
|
+
app.log.info(`Received ${signal} again, already shutting down...`);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
isShuttingDown = true;
|
|
357
|
+
// 强制退出兜底:优雅关闭超过 N 秒则强制退出
|
|
358
|
+
const forceTimer = setTimeout(() => {
|
|
359
|
+
app.log.error("Graceful shutdown timed out, forcing exit");
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}, GRACEFUL_SHUTDOWN_TIMEOUT_MS);
|
|
362
|
+
// 不阻止进程退出
|
|
363
|
+
forceTimer.unref();
|
|
343
364
|
try {
|
|
344
365
|
app.log.info(`Received ${signal}, shutting down gracefully...`);
|
|
345
366
|
await close();
|
|
@@ -348,6 +369,7 @@ export async function main() {
|
|
|
348
369
|
catch (err) {
|
|
349
370
|
app.log.error({ err }, "Error during shutdown");
|
|
350
371
|
}
|
|
372
|
+
clearTimeout(forceTimer);
|
|
351
373
|
process.exit(0);
|
|
352
374
|
};
|
|
353
375
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
@@ -52,6 +52,8 @@ export declare class RequestTracker {
|
|
|
52
52
|
/** 向单个客户端发送当前活跃请求快照(保留 clientRequest 以便前端即时展示) */
|
|
53
53
|
private sendInitialSnapshot;
|
|
54
54
|
removeClient(res: ServerResponse): void;
|
|
55
|
+
/** 主动关闭所有 SSE 客户端连接,确保 app.close() 不会因长连接阻塞 */
|
|
56
|
+
closeAllClients(): void;
|
|
55
57
|
startPushInterval(): void;
|
|
56
58
|
stopPushInterval(): void;
|
|
57
59
|
broadcast(event: string, data: unknown): void;
|
|
@@ -188,6 +188,20 @@ export class RequestTracker {
|
|
|
188
188
|
removeClient(res) {
|
|
189
189
|
this.clients.delete(res);
|
|
190
190
|
}
|
|
191
|
+
/** 主动关闭所有 SSE 客户端连接,确保 app.close() 不会因长连接阻塞 */
|
|
192
|
+
closeAllClients() {
|
|
193
|
+
const clients = [...this.clients];
|
|
194
|
+
this.clients.clear();
|
|
195
|
+
for (const client of clients) {
|
|
196
|
+
try {
|
|
197
|
+
if (!client.writableEnded)
|
|
198
|
+
client.end();
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// 忽略已关闭的连接
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
191
205
|
// --- Push interval ---
|
|
192
206
|
startPushInterval() {
|
|
193
207
|
if (this.pushTimer)
|
|
@@ -16,6 +16,7 @@ import { applyOverflowRedirect } from "../routing/overflow.js";
|
|
|
16
16
|
import { applyProviderPatches } from "../patch/index.js";
|
|
17
17
|
import { PipelineSnapshot } from "../pipeline-snapshot.js";
|
|
18
18
|
import { maybeInjectModelInfoTag } from "../response-transform.js";
|
|
19
|
+
import { applyToolRoundLimit } from "../patch/tool-round-limiter.js";
|
|
19
20
|
import { loadEnhancementConfig } from "../routing/enhancement-config.js";
|
|
20
21
|
import { getTransportStatusCode, serializeBlocksForStorage, extractLastToolUse } from "./proxy-handler-utils.js";
|
|
21
22
|
const HTTP_ERROR_THRESHOLD = 400;
|
|
@@ -58,13 +59,22 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
58
59
|
// enhancement 阶段
|
|
59
60
|
const { body: enhancedBody, effectiveModel, originalModel, interceptResponse, meta: enhMeta } = applyEnhancement(deps.db, request.body, clientModel, sessionId, request.routerKey);
|
|
60
61
|
snapshot.add({ stage: "enhancement", router_tags_stripped: enhMeta.router_tags_stripped, directive: enhMeta.directive });
|
|
61
|
-
// tool
|
|
62
|
+
// tool round limiter 阶段 — 检测连续工具调用轮数,超阈值时注入提示词
|
|
62
63
|
let pipelineBody = enhancedBody;
|
|
64
|
+
if (enhancementConfig.tool_round_limit_enabled) {
|
|
65
|
+
const roundResult = applyToolRoundLimit(enhancedBody, apiType);
|
|
66
|
+
if (roundResult.injected) {
|
|
67
|
+
pipelineBody = roundResult.body;
|
|
68
|
+
snapshot.add({ stage: "tool_round_limit", action: "inject_warning", rounds: roundResult.rounds });
|
|
69
|
+
request.log.info({ sessionId, rounds: roundResult.rounds }, "Tool round limit reached, injecting warning prompt");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// tool guard 阶段 — 使用 pipelineBody(可能已被 round limiter 修改)
|
|
63
73
|
const sessionTracker = deps.container.resolve(SERVICE_KEYS.sessionTracker);
|
|
64
74
|
if (enhancementConfig.tool_call_loop_enabled && sessionTracker && sessionId) {
|
|
65
75
|
const routerKeyId = request.routerKey?.id ?? null;
|
|
66
76
|
const sessionKey = routerKeyId ? `${routerKeyId}:${sessionId}` : sessionId;
|
|
67
|
-
const lastToolUse = extractLastToolUse(
|
|
77
|
+
const lastToolUse = extractLastToolUse(pipelineBody);
|
|
68
78
|
if (lastToolUse) {
|
|
69
79
|
const toolGuard = new ToolLoopGuard(sessionTracker, {
|
|
70
80
|
enabled: true,
|
|
@@ -76,7 +86,7 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
76
86
|
const loopCount = sessionTracker.getLoopCount(sessionKey);
|
|
77
87
|
if (loopCount === 1) {
|
|
78
88
|
// 层级 1:透明重试 — 注入中断提示词
|
|
79
|
-
pipelineBody = toolGuard.injectLoopBreakPrompt(
|
|
89
|
+
pipelineBody = toolGuard.injectLoopBreakPrompt(pipelineBody, apiType, lastToolUse.toolName);
|
|
80
90
|
snapshot.add({ stage: "tool_guard", action: "inject_break_prompt", tool: lastToolUse.toolName });
|
|
81
91
|
request.log.warn({ sessionId, toolName: lastToolUse.toolName, loopCount }, "Tool call loop detected, injecting break prompt");
|
|
82
92
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具调用轮数限制器。
|
|
3
|
+
*
|
|
4
|
+
* 检测 messages 中连续的 "assistant(tool_use) → user(tool_result)" 轮次数量,
|
|
5
|
+
* 超过阈值时注入提示词提醒 AI 不要陷入无限循环。
|
|
6
|
+
*
|
|
7
|
+
* 与 loop-prevention/tool-loop-guard 不同:
|
|
8
|
+
* - tool-loop-guard 关注"同一工具重复调用"(N-gram 检测 input 重复)
|
|
9
|
+
* - 本模块关注"工具调用轮数过多"(不管是否同一工具,反映 AI 反复操作却无法完成)
|
|
10
|
+
*/
|
|
11
|
+
/** OpenAI 格式的 tool_call */
|
|
12
|
+
interface OpenAIToolCall {
|
|
13
|
+
type?: string;
|
|
14
|
+
function?: {
|
|
15
|
+
name?: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
interface Message {
|
|
19
|
+
role?: string;
|
|
20
|
+
content?: unknown;
|
|
21
|
+
tool_calls?: OpenAIToolCall[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 统计 messages 中连续的"工具调用轮数"。
|
|
25
|
+
*
|
|
26
|
+
* 一轮定义:assistant 消息包含 tool_use → 后面紧接 user 消息包含 tool_result。
|
|
27
|
+
* 从最后一条消息向前扫描,遇到非工具轮即停止。
|
|
28
|
+
*/
|
|
29
|
+
export declare function countConsecutiveToolRounds(messages: Message[]): number;
|
|
30
|
+
/**
|
|
31
|
+
* 检测并注入提示词。返回可能修改后的 body(浅拷贝),未超阈值时原样返回。
|
|
32
|
+
*/
|
|
33
|
+
export declare function applyToolRoundLimit(body: Record<string, unknown>, apiType: "openai" | "anthropic", maxRounds?: number): {
|
|
34
|
+
body: Record<string, unknown>;
|
|
35
|
+
injected: boolean;
|
|
36
|
+
rounds: number;
|
|
37
|
+
};
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具调用轮数限制器。
|
|
3
|
+
*
|
|
4
|
+
* 检测 messages 中连续的 "assistant(tool_use) → user(tool_result)" 轮次数量,
|
|
5
|
+
* 超过阈值时注入提示词提醒 AI 不要陷入无限循环。
|
|
6
|
+
*
|
|
7
|
+
* 与 loop-prevention/tool-loop-guard 不同:
|
|
8
|
+
* - tool-loop-guard 关注"同一工具重复调用"(N-gram 检测 input 重复)
|
|
9
|
+
* - 本模块关注"工具调用轮数过多"(不管是否同一工具,反映 AI 反复操作却无法完成)
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_MAX_ROUNDS = 5;
|
|
12
|
+
const LOOP_WARNING_PROMPT = "[系统提醒] 你已经连续进行了多轮工具调用但似乎还没有完成任务。请注意不要陷入无限循环,停下来总结当前进展,如果无法继续请直接告知用户。";
|
|
13
|
+
/**
|
|
14
|
+
* 统计 messages 中连续的"工具调用轮数"。
|
|
15
|
+
*
|
|
16
|
+
* 一轮定义:assistant 消息包含 tool_use → 后面紧接 user 消息包含 tool_result。
|
|
17
|
+
* 从最后一条消息向前扫描,遇到非工具轮即停止。
|
|
18
|
+
*/
|
|
19
|
+
export function countConsecutiveToolRounds(messages) {
|
|
20
|
+
let rounds = 0;
|
|
21
|
+
let i = messages.length - 1;
|
|
22
|
+
while (i >= 1) {
|
|
23
|
+
const msg = messages[i];
|
|
24
|
+
// 期望:user 消息包含 tool_result(Anthropic)或 role=tool(OpenAI)
|
|
25
|
+
if (msg.role === "user" || msg.role === "tool") {
|
|
26
|
+
const hasToolResult = hasToolResultContent(msg);
|
|
27
|
+
if (hasToolResult || msg.role === "tool") {
|
|
28
|
+
// 向前找对应的 assistant 消息
|
|
29
|
+
let j = i - 1;
|
|
30
|
+
while (j >= 0 && messages[j].role !== "assistant")
|
|
31
|
+
j--;
|
|
32
|
+
if (j >= 0 && hasToolUseContent(messages[j])) {
|
|
33
|
+
rounds++;
|
|
34
|
+
i = j - 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// assistant 消息本身可能包含 tool_use(最后一轮可能还没 tool_result)
|
|
40
|
+
if (msg.role === "assistant" && hasToolUseContent(msg)) {
|
|
41
|
+
rounds++;
|
|
42
|
+
i--;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
return rounds;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 检测并注入提示词。返回可能修改后的 body(浅拷贝),未超阈值时原样返回。
|
|
51
|
+
*/
|
|
52
|
+
export function applyToolRoundLimit(body, apiType, maxRounds = DEFAULT_MAX_ROUNDS) {
|
|
53
|
+
const messages = body.messages ?? [];
|
|
54
|
+
if (messages.length === 0)
|
|
55
|
+
return { body, injected: false, rounds: 0 };
|
|
56
|
+
const rounds = countConsecutiveToolRounds(messages);
|
|
57
|
+
if (rounds <= maxRounds)
|
|
58
|
+
return { body, injected: false, rounds };
|
|
59
|
+
const cloned = { ...body, messages: [...messages] };
|
|
60
|
+
const clonedMessages = cloned.messages;
|
|
61
|
+
// 在尾部注入:修改最后一条消息的 content,不插入新消息到头部
|
|
62
|
+
// 这样不会使 LLM 的 KV cache 失效(前面的 messages 保持不变)
|
|
63
|
+
if (apiType === "anthropic") {
|
|
64
|
+
// Anthropic:将提示词作为 text block 追加到最后一条消息的 content 末尾
|
|
65
|
+
const lastMsg = clonedMessages[clonedMessages.length - 1];
|
|
66
|
+
if (lastMsg && Array.isArray(lastMsg.content)) {
|
|
67
|
+
const patched = [...lastMsg.content];
|
|
68
|
+
patched.push({ type: "text", text: LOOP_WARNING_PROMPT });
|
|
69
|
+
lastMsg.content = patched;
|
|
70
|
+
}
|
|
71
|
+
else if (lastMsg && typeof lastMsg.content === "string") {
|
|
72
|
+
lastMsg.content = [
|
|
73
|
+
{ type: "text", text: lastMsg.content },
|
|
74
|
+
{ type: "text", text: LOOP_WARNING_PROMPT },
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// fallback:追加 user 消息
|
|
79
|
+
clonedMessages.push({ role: "user", content: [{ type: "text", text: LOOP_WARNING_PROMPT }] });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// OpenAI 格式:将提示词追加到最后一条消息的 content 末尾
|
|
84
|
+
const lastMsg = clonedMessages[clonedMessages.length - 1];
|
|
85
|
+
if (lastMsg && typeof lastMsg.content === "string") {
|
|
86
|
+
lastMsg.content = lastMsg.content + "\n\n" + LOOP_WARNING_PROMPT;
|
|
87
|
+
}
|
|
88
|
+
else if (lastMsg && lastMsg.content != null) {
|
|
89
|
+
lastMsg.content = JSON.stringify(lastMsg.content) + "\n\n" + LOOP_WARNING_PROMPT;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// fallback:追加 user 消息
|
|
93
|
+
clonedMessages.push({ role: "user", content: LOOP_WARNING_PROMPT });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { body: cloned, injected: true, rounds };
|
|
97
|
+
}
|
|
98
|
+
// --- helpers ---
|
|
99
|
+
/** Anthropic 格式:检查 content 数组中是否包含 tool_use */
|
|
100
|
+
function hasToolUseContent(msg) {
|
|
101
|
+
if (Array.isArray(msg.content)) {
|
|
102
|
+
return msg.content.some((block) => block.type === "tool_use");
|
|
103
|
+
}
|
|
104
|
+
// OpenAI 格式
|
|
105
|
+
if (msg.tool_calls && msg.tool_calls.length > 0)
|
|
106
|
+
return true;
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
/** Anthropic 格式:检查 content 数组中是否包含 tool_result */
|
|
110
|
+
function hasToolResultContent(msg) {
|
|
111
|
+
if (Array.isArray(msg.content)) {
|
|
112
|
+
return msg.content.some((block) => block.type === "tool_result");
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
@@ -3,6 +3,7 @@ export interface EnhancementConfig {
|
|
|
3
3
|
claude_code_enabled: boolean;
|
|
4
4
|
tool_call_loop_enabled: boolean;
|
|
5
5
|
stream_loop_enabled: boolean;
|
|
6
|
+
tool_round_limit_enabled: boolean;
|
|
6
7
|
}
|
|
7
8
|
/** 集中加载 proxy_enhancement 配置,避免多处重复 getSetting + JSON.parse */
|
|
8
9
|
export declare function loadEnhancementConfig(db: Database.Database): EnhancementConfig;
|
|
@@ -3,6 +3,7 @@ const DEFAULT_CONFIG = {
|
|
|
3
3
|
claude_code_enabled: false,
|
|
4
4
|
tool_call_loop_enabled: false,
|
|
5
5
|
stream_loop_enabled: false,
|
|
6
|
+
tool_round_limit_enabled: true,
|
|
6
7
|
};
|
|
7
8
|
/** 集中加载 proxy_enhancement 配置,避免多处重复 getSetting + JSON.parse */
|
|
8
9
|
export function loadEnhancementConfig(db) {
|
|
@@ -15,6 +16,7 @@ export function loadEnhancementConfig(db) {
|
|
|
15
16
|
claude_code_enabled: parsed.claude_code_enabled ?? false,
|
|
16
17
|
tool_call_loop_enabled: parsed.tool_call_loop_enabled ?? false,
|
|
17
18
|
stream_loop_enabled: parsed.stream_loop_enabled ?? false,
|
|
19
|
+
tool_round_limit_enabled: parsed.tool_round_limit_enabled ?? true,
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
catch {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdirSync, readFileSync, writeFileSync, unlinkSync, rmSync, existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { gzipSync } from "node:zlib";
|
|
4
|
-
import { WINDOW_MINUTES,
|
|
4
|
+
import { WINDOW_MINUTES, localDateStr } from "./types.js";
|
|
5
5
|
const SECONDS_PER_MINUTE = 60;
|
|
6
6
|
const MS_PER_SECOND = 1000;
|
|
7
7
|
const COMPRESSION_INTERVAL_MS = WINDOW_MINUTES * SECONDS_PER_MINUTE * MS_PER_SECOND;
|
|
@@ -23,8 +23,9 @@ export function compressFinishedFiles(baseDir, now) {
|
|
|
23
23
|
continue;
|
|
24
24
|
const fileHour = parseInt(match[1], 10);
|
|
25
25
|
const fileMinute = parseInt(match[2], 10);
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// 使用本地时间构建窗口结束时间(使用 Date 构造函数,避免字符串解析的 V8 依赖)
|
|
27
|
+
const dateParts = dayDir.name.split("-").map(Number);
|
|
28
|
+
const windowEnd = new Date(dateParts[0], dateParts[1] - 1, dateParts[2], fileHour, fileMinute + WINDOW_MINUTES);
|
|
28
29
|
if (now >= windowEnd) {
|
|
29
30
|
const filePath = join(dirPath, file);
|
|
30
31
|
try {
|
|
@@ -47,9 +48,7 @@ export function compressFinishedFiles(baseDir, now) {
|
|
|
47
48
|
export function cleanExpiredDirs(baseDir, retentionDays, now) {
|
|
48
49
|
if (!existsSync(baseDir))
|
|
49
50
|
return 0;
|
|
50
|
-
const
|
|
51
|
-
cutoff.setUTCDate(cutoff.getUTCDate() - retentionDays);
|
|
52
|
-
const cutoffStr = cutoff.toISOString().slice(0, ISO_DATE_LENGTH);
|
|
51
|
+
const cutoffStr = localDateStr(new Date(now.getFullYear(), now.getMonth(), now.getDate() - retentionDays));
|
|
53
52
|
let deleted = 0;
|
|
54
53
|
const dayDirs = readdirSync(baseDir, { withFileTypes: true })
|
|
55
54
|
.filter(d => d.isDirectory() && /^\d{4}-\d{2}-\d{2}$/.test(d.name));
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { appendFileSync, mkdirSync, existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { gunzipSync } from "node:zlib";
|
|
4
|
-
import { WINDOW_MINUTES, TIME_PAD_WIDTH,
|
|
4
|
+
import { WINDOW_MINUTES, TIME_PAD_WIDTH, localDateStr } from "./types.js";
|
|
5
|
+
/** 从日期对象生成本地时区的日志文件路径片段 */
|
|
6
|
+
function localFilePathParts(d) {
|
|
7
|
+
const dateStr = localDateStr(d);
|
|
8
|
+
const hour = d.getHours().toString().padStart(TIME_PAD_WIDTH, "0");
|
|
9
|
+
const minute = Math.floor(d.getMinutes() / WINDOW_MINUTES) * WINDOW_MINUTES;
|
|
10
|
+
const minuteStr = minute.toString().padStart(TIME_PAD_WIDTH, "0");
|
|
11
|
+
return { dateStr, fileName: `${hour}-${minuteStr}.jsonl` };
|
|
12
|
+
}
|
|
5
13
|
export class LogFileWriter {
|
|
6
14
|
baseDir;
|
|
7
15
|
enabled;
|
|
@@ -15,12 +23,7 @@ export class LogFileWriter {
|
|
|
15
23
|
write(entry) {
|
|
16
24
|
if (!this.enabled)
|
|
17
25
|
return;
|
|
18
|
-
const
|
|
19
|
-
const dateStr = date.toISOString().slice(0, ISO_DATE_LENGTH);
|
|
20
|
-
const hour = date.getUTCHours().toString().padStart(TIME_PAD_WIDTH, "0");
|
|
21
|
-
const minute = Math.floor(date.getUTCMinutes() / WINDOW_MINUTES) * WINDOW_MINUTES;
|
|
22
|
-
const minuteStr = minute.toString().padStart(TIME_PAD_WIDTH, "0");
|
|
23
|
-
const fileName = `${hour}-${minuteStr}.jsonl`;
|
|
26
|
+
const { dateStr, fileName } = localFilePathParts(new Date(entry.created_at));
|
|
24
27
|
const dayDir = join(this.baseDir, dateStr);
|
|
25
28
|
if (!existsSync(dayDir)) {
|
|
26
29
|
mkdirSync(dayDir, { recursive: true });
|
|
@@ -43,12 +46,7 @@ export class LogFileWriter {
|
|
|
43
46
|
read(id, createdAt) {
|
|
44
47
|
if (!this.enabled)
|
|
45
48
|
return null;
|
|
46
|
-
const
|
|
47
|
-
const dateStr = date.toISOString().slice(0, ISO_DATE_LENGTH);
|
|
48
|
-
const hour = date.getUTCHours().toString().padStart(TIME_PAD_WIDTH, "0");
|
|
49
|
-
const minute = Math.floor(date.getUTCMinutes() / WINDOW_MINUTES) * WINDOW_MINUTES;
|
|
50
|
-
const minuteStr = minute.toString().padStart(TIME_PAD_WIDTH, "0");
|
|
51
|
-
const fileName = `${hour}-${minuteStr}.jsonl`;
|
|
49
|
+
const { dateStr, fileName } = localFilePathParts(new Date(createdAt));
|
|
52
50
|
const dayDir = join(this.baseDir, dateStr);
|
|
53
51
|
// 尝试未压缩文件
|
|
54
52
|
const filePath = join(dayDir, fileName);
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export declare const WINDOW_MINUTES = 10;
|
|
|
3
3
|
export declare const TIME_PAD_WIDTH = 2;
|
|
4
4
|
/** ISO 日期字符串长度("YYYY-MM-DD") */
|
|
5
5
|
export declare const ISO_DATE_LENGTH = 10;
|
|
6
|
+
/** 格式化本地日期为 YYYY-MM-DD */
|
|
7
|
+
export declare function localDateStr(d: Date): string;
|
|
6
8
|
export interface LogFileEntry {
|
|
7
9
|
id: string;
|
|
8
10
|
created_at: string;
|
package/dist/storage/types.js
CHANGED
|
@@ -3,3 +3,10 @@ export const WINDOW_MINUTES = 10;
|
|
|
3
3
|
export const TIME_PAD_WIDTH = 2;
|
|
4
4
|
/** ISO 日期字符串长度("YYYY-MM-DD") */
|
|
5
5
|
export const ISO_DATE_LENGTH = 10;
|
|
6
|
+
/** 格式化本地日期为 YYYY-MM-DD */
|
|
7
|
+
export function localDateStr(d) {
|
|
8
|
+
const y = d.getFullYear().toString();
|
|
9
|
+
const m = (d.getMonth() + 1).toString().padStart(TIME_PAD_WIDTH, "0");
|
|
10
|
+
const day = d.getDate().toString().padStart(TIME_PAD_WIDTH, "0");
|
|
11
|
+
return `${y}-${m}-${day}`;
|
|
12
|
+
}
|
|
@@ -1 +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-
|
|
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-Ul8WlrM5.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};
|
|
@@ -1 +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-
|
|
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-Ul8WlrM5.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};
|
package/frontend-dist/assets/{CascadingModelSelect-CBe9pEx_.js → CascadingModelSelect-CBhqKFDX.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Dt as e,G as t,H as n,It as r,J as i,Pt as a,U as o,V as s,W as c,ct as l,ot as u,q as d,yt as f,z as p,zt as m}from"./button-
|
|
1
|
+
import{Dt as e,G as t,H as n,It as r,J as i,Pt as a,U as o,V as s,W as c,ct as l,ot as u,q as d,yt as f,z as p,zt as m}from"./button-Ul8WlrM5.js";import{b as h}from"./Teleport-DizRK5O3.js";import{a as g}from"./PopperContent-gzzf1XHe.js";import{n as _,r as v,t as y}from"./PopoverTrigger-Bj65uUbv.js";var b=h(`chevron-right`,[[`path`,{d:`m9 18 6-6-6-6`,key:`mthhwq`}]]),x=[`onMouseenter`],S={class:`truncate max-w-40`},C=[`onMouseenter`],w=[`onClick`],T={class:`truncate`},E={key:0,class:`shrink-0 text-xs text-muted-foreground`},D={key:0,class:`px-2 py-1.5 text-sm text-muted-foreground`},O=i({__name:`CascadingSelect`,props:{groups:{},modelValue:{},placeholder:{default:`请选择...`}},emits:[`update:modelValue`],setup(i,{emit:h}){let O=i,k=h,A=e(!1),j=e(null),M=s(()=>{if(!O.modelValue)return``;let e=O.groups.find(e=>e.key===O.modelValue.groupKey);if(!e)return``;let t=e.options.find(e=>e.value===O.modelValue.value);return t?`${e.label} / ${t.label}`:``});function N(e,t){k(`update:modelValue`,{groupKey:e,value:t}),A.value=!1}function P(e){A.value=e,e||(j.value=null)}return(e,s)=>(u(),o(a(v),{open:A.value,"onUpdate:open":P},{default:f(()=>[d(a(y),{"as-child":``},{default:f(()=>[n(`div`,{class:r([`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`,{"ring-2 ring-ring ring-offset-2":A.value}])},[n(`span`,{class:r([`truncate`,i.modelValue?`text-foreground`:`text-muted-foreground`])},m(M.value||i.placeholder),3),d(a(g),{class:`h-4 w-4 shrink-0 opacity-50`})],2)]),_:1}),d(a(_),{align:`start`,"side-offset":4,class:`z-[200] w-auto min-w-56 overflow-visible p-1`},{default:f(()=>[(u(!0),t(p,null,l(i.groups,e=>(u(),t(`div`,{key:e.key,class:r([`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":j.value===e.key}]),onMouseenter:t=>j.value=e.key},[n(`span`,S,m(e.label),1),d(a(b),{class:`ml-1 h-4 w-4 shrink-0 opacity-50`}),j.value===e.key&&e.options.length>0?(u(),t(`div`,{key:0,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:t=>j.value=e.key},[(u(!0),t(p,null,l(e.options,a=>(u(),t(`div`,{key:a.value,class:r([`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===e.key&&i.modelValue?.value===a.value}]),onClick:t=>N(e.key,a.value)},[n(`span`,T,m(a.label),1),a.tag?(u(),t(`span`,E,m(a.tag),1)):c(``,!0)],10,w))),128))],40,C)):c(``,!0)],42,x))),128)),i.groups.length===0?(u(),t(`div`,D,` 暂无选项 `)):c(``,!0)]),_:1})]),_:1},8,[`open`]))}}),k=i({__name:`CascadingModelSelect`,props:{providers:{},modelValue:{},placeholder:{default:`选择供应商 / 模型`}},emits:[`update:modelValue`],setup(e,{emit:t}){let n=e,r=t;function i(e){return e>=1e6?`${e/1e6}M`:`${e/1e3}K`}let a=s(()=>n.providers.map(e=>({key:e.provider.id,label:e.provider.name,options:e.models.map(e=>({value:e.name,label:e.name,tag:i(e.contextWindow)}))}))),c=s(()=>n.modelValue?{groupKey:n.modelValue.provider_id,value:n.modelValue.model}:void 0);function l(e){r(`update:modelValue`,{provider_id:e.groupKey,model:e.value})}return(t,n)=>(u(),o(O,{groups:a.value,"model-value":c.value,placeholder:e.placeholder,"onUpdate:modelValue":l},null,8,[`groups`,`model-value`,`placeholder`]))}});export{k as t};
|
|
@@ -1 +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-
|
|
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-Ul8WlrM5.js";import{g as b,o as x,u as S,y as C}from"./Teleport-DizRK5O3.js";import{t as w}from"./check-7ahK--N4.js";import{t as T}from"./ohash.D__AXeF1-D5e5Wyzx.js";import{t as E}from"./useFormControl-DEO19lRe.js";import{t as D}from"./VisuallyHiddenInput-DdiZrV2i.js";import{t as O}from"./RovingFocusItem-CXM_Yfkm.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};
|
package/frontend-dist/assets/{CollapsibleTrigger-H2GGomkv.js → CollapsibleTrigger-DhxD9tpM.js}
RENAMED
|
@@ -1 +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-
|
|
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-Ul8WlrM5.js";import{c as x,o as S,u as C,y as w}from"./Teleport-DizRK5O3.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};
|
|
@@ -1 +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-
|
|
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-Ul8WlrM5.js";import{h as d}from"./Teleport-DizRK5O3.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};
|