llm-simple-router 0.10.13 → 0.10.14

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.
Files changed (46) hide show
  1. package/dist/admin/monitor.js +15 -6
  2. package/dist/admin/providers.js +18 -1
  3. package/dist/index.js +14 -1
  4. package/dist/proxy/handler/create-proxy-handler.js +15 -0
  5. package/dist/proxy/transform/stream-oa2ant.js +3 -0
  6. package/dist/proxy/transport/http.js +6 -0
  7. package/dist/proxy/transport/proxy-agent.js +20 -8
  8. package/dist/proxy/transport/stream.js +8 -1
  9. package/dist/proxy/transport/transport-fn.js +11 -0
  10. package/frontend-dist/assets/{CardContent-DfVo-N85.js → CardContent-BC11_Dvx.js} +1 -1
  11. package/frontend-dist/assets/{CardTitle-npwJSAlz.js → CardTitle-Bbpnsew9.js} +1 -1
  12. package/frontend-dist/assets/{Checkbox-Ddnzkh_i.js → Checkbox-BydyvoVA.js} +1 -1
  13. package/frontend-dist/assets/{CollapsibleContent-BTVazeoQ.js → CollapsibleContent-B_TbpMgv.js} +1 -1
  14. package/frontend-dist/assets/{CollapsibleTrigger-DCQeyHrt.js → CollapsibleTrigger-DzzKSjgt.js} +1 -1
  15. package/frontend-dist/assets/{Dashboard-DjnImtwH.js → Dashboard-BvWOHwBO.js} +1 -1
  16. package/frontend-dist/assets/{Input-Ey_q_5_r.js → Input-D2CS2qq3.js} +1 -1
  17. package/frontend-dist/assets/{Label-Dw5HcYsL.js → Label-CsXEwmPI.js} +1 -1
  18. package/frontend-dist/assets/{Login-CSrfhhm9.js → Login-BpJu4UWA.js} +1 -1
  19. package/frontend-dist/assets/{Logs-HR1DZs1M.js → Logs-C8kQzcaM.js} +1 -1
  20. package/frontend-dist/assets/{MappingEntryEditor-C9pgNL0Q.js → MappingEntryEditor-i_ywne83.js} +1 -1
  21. package/frontend-dist/assets/{ModelCard-IQMwlnCm.js → ModelCard-DqMejvO6.js} +1 -1
  22. package/frontend-dist/assets/{ModelMappings-kRx-GL_7.js → ModelMappings-CFspIMB5.js} +1 -1
  23. package/frontend-dist/assets/{Monitor-y1ofDNK7.js → Monitor-Cp_76u9w.js} +1 -1
  24. package/frontend-dist/assets/{Providers-C1bP2PoM.js → Providers-e7seFLUq.js} +1 -1
  25. package/frontend-dist/assets/{ProxyEnhancement-DQx4coxn.js → ProxyEnhancement-CNzz3Hjz.js} +1 -1
  26. package/frontend-dist/assets/{QuickSetup-DHX9-CnO.js → QuickSetup-DXQfbxPH.js} +1 -1
  27. package/frontend-dist/assets/{RetryRules-zdJE0bFL.js → RetryRules-CvZqdPde.js} +1 -1
  28. package/frontend-dist/assets/{RouterKeys-CD0rI4kv.js → RouterKeys-C-a4io5P.js} +1 -1
  29. package/frontend-dist/assets/{RovingFocusItem-CFmjbm49.js → RovingFocusItem-DlnzFIfo.js} +1 -1
  30. package/frontend-dist/assets/{Schedules-BUm3cC6w.js → Schedules-VoZO1pZ-.js} +1 -1
  31. package/frontend-dist/assets/{Settings-D7z5IRkY.js → Settings-Tm3kmIt9.js} +1 -1
  32. package/frontend-dist/assets/{Setup-i9inmgjB.js → Setup-OYDmvmSz.js} +1 -1
  33. package/frontend-dist/assets/{Switch-C9DeYAnK.js → Switch-CLb1njTR.js} +1 -1
  34. package/frontend-dist/assets/{TooltipTrigger-Dr6kqGSH.js → TooltipTrigger-BNoaZlu1.js} +1 -1
  35. package/frontend-dist/assets/{TransformRulesForm-CyXh4jHa.js → TransformRulesForm-CZDudZgw.js} +1 -1
  36. package/frontend-dist/assets/{UnifiedRequestDialog-6ZRBfjko.js → UnifiedRequestDialog-UxyXcsIx.js} +1 -1
  37. package/frontend-dist/assets/{VisuallyHiddenInput-CwE9jREu.js → VisuallyHiddenInput-BQ3HNrqW.js} +1 -1
  38. package/frontend-dist/assets/{button-C7HO6Dyb.js → button-QVj-5yKZ.js} +2 -2
  39. package/frontend-dist/assets/{copy-DxwFlq2A.js → copy-DgA398xa.js} +1 -1
  40. package/frontend-dist/assets/{dialog-BWB1aLcT.js → dialog-D9t_mm3G.js} +1 -1
  41. package/frontend-dist/assets/{index-itL9--Q_.js → index-RqqKMw85.js} +2 -2
  42. package/frontend-dist/assets/{trash-2-D2SrfECO.js → trash-2-DFUMKLr9.js} +1 -1
  43. package/frontend-dist/assets/{useClipboard-CttzUerj.js → useClipboard-Do3-WhDs.js} +1 -1
  44. package/frontend-dist/assets/{useLogRetention-Dv0deAan.js → useLogRetention-FRLnoepc.js} +1 -1
  45. package/frontend-dist/index.html +2 -2
  46. package/package.json +1 -1
@@ -16,16 +16,25 @@ export const adminMonitorRoutes = (app, options, done) => {
16
16
  app.get("/admin/api/monitor/stream", (request, reply) => {
17
17
  // hijack() 让 Fastify 完全放弃响应管理,避免 onSend hook 向 SSE 流注入信封 JSON
18
18
  reply.hijack();
19
- reply.raw.writeHead(HTTP_OK, {
20
- "Content-Type": "text/event-stream",
21
- "Cache-Control": "no-cache",
22
- Connection: "keep-alive",
23
- });
24
19
  const sseClient = adaptSSEClient(reply.raw);
25
20
  tracker.addClient(sseClient);
26
- request.raw.on("close", () => {
21
+ // 在 writeHead 之前注册 close 处理器,避免竞态导致 tracker 泄漏
22
+ reply.raw.on("close", () => {
27
23
  tracker.removeClient(sseClient);
28
24
  });
25
+ // 客户端在 hijack 之前已断连,无需发送响应头
26
+ if (reply.raw.destroyed)
27
+ return;
28
+ try {
29
+ reply.raw.writeHead(HTTP_OK, {
30
+ "Content-Type": "text/event-stream",
31
+ "Cache-Control": "no-cache",
32
+ Connection: "keep-alive",
33
+ });
34
+ }
35
+ catch {
36
+ request.log.debug("client disconnected before writeHead");
37
+ }
29
38
  });
30
39
  app.get("/admin/api/monitor/request/:id", async (request, reply) => {
31
40
  const { id } = request.params;
@@ -83,6 +83,16 @@ function extractModelOverrides(models) {
83
83
  }
84
84
  const API_KEY_PREVIEW_PREFIX_LEN = 4;
85
85
  const PROVIDER_NAME_RE = /^[a-zA-Z0-9_-]+$/;
86
+ /** 校验 base_url 是否为合法的 HTTP(S) URL */
87
+ function isValidHttpUrl(str) {
88
+ try {
89
+ const url = new URL(str);
90
+ return url.protocol === "http:" || url.protocol === "https:";
91
+ }
92
+ catch {
93
+ return false;
94
+ }
95
+ }
86
96
  const CreateProviderSchema = Type.Object({
87
97
  name: Type.String({ minLength: 1 }),
88
98
  api_type: Type.Union([Type.Literal("openai"), Type.Literal("anthropic")]),
@@ -165,6 +175,9 @@ export const adminProviderRoutes = (app, options, done) => {
165
175
  if (existing) {
166
176
  return reply.code(HTTP_CONFLICT).send(apiError(API_CODE.CONFLICT_NAME, `Provider 名称 '${body.name}' 已存在`));
167
177
  }
178
+ if (!isValidHttpUrl(body.base_url)) {
179
+ return reply.code(HTTP_BAD_REQUEST).send(apiError(API_CODE.VALIDATION_FAILED, "base_url 格式无效,必须是以 http:// 或 https:// 开头的合法 URL"));
180
+ }
168
181
  const encryptedKey = encrypt(body.api_key, getSetting(db, "encryption_key"));
169
182
  const { entries: normalizedModels, overrides: contextOverrides } = extractModelOverrides((body.models ?? []));
170
183
  const isAdaptiveEnabled = body.adaptive_enabled ?? 0;
@@ -231,8 +244,12 @@ export const adminProviderRoutes = (app, options, done) => {
231
244
  fields.name = body.name;
232
245
  if (body.api_type !== undefined)
233
246
  fields.api_type = body.api_type;
234
- if (body.base_url !== undefined)
247
+ if (body.base_url !== undefined) {
248
+ if (!isValidHttpUrl(body.base_url)) {
249
+ return reply.code(HTTP_BAD_REQUEST).send(apiError(API_CODE.VALIDATION_FAILED, "base_url 格式无效,必须是以 http:// 或 https:// 开头的合法 URL"));
250
+ }
235
251
  fields.base_url = body.base_url;
252
+ }
236
253
  if (body.upstream_path !== undefined)
237
254
  fields.upstream_path = body.upstream_path || null;
238
255
  if (body.is_active !== undefined)
package/dist/index.js CHANGED
@@ -120,8 +120,21 @@ export async function buildApp(options) {
120
120
  return new Error(message);
121
121
  });
122
122
  // 记录请求到达时间,供全局错误处理计算延迟
123
- app.addHook("onRequest", (request, _reply, done) => {
123
+ app.addHook("onRequest", (request, reply, done) => {
124
124
  request.receivedAt = Date.now();
125
+ // 全局 EPIPE 防护:ServerResponse 的 write 异步完成失败时,
126
+ // 内部 socketErrorListener → response.destroy(err) → response.emit('error')。
127
+ // 若无 listener 则该 error 成为 uncaught exception。
128
+ // 代理路由在 create-proxy-handler.ts 中已有额外监听,此处覆盖所有路由。
129
+ reply.raw.on("error", (err) => {
130
+ const code = err.code;
131
+ if (code === 'EPIPE') {
132
+ request.log.debug({ err }, "client disconnected (EPIPE)");
133
+ }
134
+ else {
135
+ request.log.warn({ err }, "response stream error");
136
+ }
137
+ });
125
138
  done();
126
139
  });
127
140
  // 统一错误处理:代理路由保持 {error:{message}},Admin API 使用信封格式
@@ -192,8 +192,23 @@ export function createProxyHandler(config) {
192
192
  // Socket error handling
193
193
  const socketErrorHandler = (err) => request.log.debug({ err }, "client socket error");
194
194
  request.raw.socket.on("error", socketErrorHandler);
195
+ // reply.raw (ServerResponse) error handling
196
+ // Node.js 中,TCP socket write 异步完成失败时(如 EPIPE),
197
+ // 内部 socketErrorListener → response.destroy(err) → response.emit('error')。
198
+ // 若无 listener,该 error 成为 uncaught exception 导致进程退出。
199
+ const replyErrorHandler = (err) => {
200
+ const code = err.code;
201
+ if (code === 'EPIPE') {
202
+ request.log.debug({ err }, "client disconnected (EPIPE)");
203
+ }
204
+ else {
205
+ request.log.warn({ err }, "response stream error");
206
+ }
207
+ };
208
+ reply.raw.on("error", replyErrorHandler);
195
209
  reply.raw.on("close", () => {
196
210
  request.raw.socket.removeListener("error", socketErrorHandler);
211
+ reply.raw.removeListener("error", replyErrorHandler);
197
212
  });
198
213
  // 创建 pipeline context
199
214
  const ctx = createPipelineContext(request, reply, apiType);
@@ -16,6 +16,9 @@ export class OpenAIToAnthropicTransform extends BaseSSETransform {
16
16
  completedToolCallIndices = new Set();
17
17
  finishReasonReceived = false;
18
18
  processEvent(event) {
19
+ // OpenAI SSE 标准结束信号 [DONE] 不是 JSON,跳过解析
20
+ if (event.data === '[DONE]')
21
+ return;
19
22
  let chunk;
20
23
  try {
21
24
  chunk = JSON.parse(event.data);
@@ -63,6 +63,9 @@ export function callNonStream(backend, apiKey, body, clientHeaders, upstreamPath
63
63
  });
64
64
  }
65
65
  });
66
+ // 上游响应过程中连接中断时,IncomingMessage 发射 'error' 事件。
67
+ // 无 listener 会导致 uncaught exception 使进程退出。
68
+ res.on("error", (error) => resolve({ kind: "throw", error }));
66
69
  });
67
70
  req.on("error", (error) => resolve({ kind: "throw", error }));
68
71
  req.write(payload);
@@ -85,6 +88,9 @@ export function callGet(backend, apiKey, clientHeaders, upstreamPath, buildHeade
85
88
  headers: filterHeaders(res.headers),
86
89
  });
87
90
  });
91
+ // 上游响应过程中连接中断时,IncomingMessage 发射 'error' 事件。
92
+ // 无 listener 会导致 uncaught exception 使进程退出。
93
+ res.on("error", (err) => reject(err));
88
94
  });
89
95
  req.on("error", (err) => reject(err));
90
96
  req.end();
@@ -24,9 +24,15 @@ export class ProxyAgentFactory {
24
24
  cached.agent.destroy();
25
25
  this.cache.delete(provider.id);
26
26
  }
27
- const agent = this.createAgent(provider.proxy_type, fullUrl);
28
- this.cache.set(provider.id, { agent, proxyUrl: fullUrl });
29
- return agent;
27
+ try {
28
+ const agent = this.createAgent(provider.proxy_type, fullUrl);
29
+ this.cache.set(provider.id, { agent, proxyUrl: fullUrl });
30
+ return agent;
31
+ }
32
+ catch {
33
+ // proxy_url 格式无效时返回 undefined,由调用方回退到非代理的 keep-alive agent
34
+ return undefined;
35
+ }
30
36
  }
31
37
  invalidate(providerId) {
32
38
  const cached = this.cache.get(providerId);
@@ -64,11 +70,17 @@ export class ProxyAgentFactory {
64
70
  const username = provider.proxy_username;
65
71
  const password = provider.proxy_password;
66
72
  if (username) {
67
- const parsed = new URL(url);
68
- parsed.username = username;
69
- if (password)
70
- parsed.password = password;
71
- url = parsed.toString();
73
+ try {
74
+ const parsed = new URL(url);
75
+ parsed.username = username;
76
+ if (password)
77
+ parsed.password = password;
78
+ url = parsed.toString();
79
+ }
80
+ catch {
81
+ // proxy_url 格式无效时返回原始 URL,由上游请求层处理连接错误
82
+ return url;
83
+ }
72
84
  }
73
85
  return url;
74
86
  }
@@ -170,7 +170,14 @@ class StreamProxy {
170
170
  }
171
171
  this.transition("STREAMING");
172
172
  this.headersSent = true;
173
- this.reply.raw.writeHead(this.statusCode, this.sseHeaders);
173
+ try {
174
+ this.reply.raw.writeHead(this.statusCode, this.sseHeaders);
175
+ }
176
+ catch {
177
+ // 客户端在 state transition 和 writeHead 之间断连,可安全忽略
178
+ this.terminal("stream_abort");
179
+ return;
180
+ }
174
181
  if (this.metricsTransform) {
175
182
  this.metricsTransform.pipe(this.formatTransform ?? this.passThrough, { end: true });
176
183
  }
@@ -53,6 +53,17 @@ export function buildTransportFn(p) {
53
53
  onChunk: (rawLine) => { p.tracker?.appendStreamChunk(p.logId, rawLine, p.apiType, STREAM_CONTENT_MAX_RAW, STREAM_CONTENT_MAX_TEXT); },
54
54
  onContentDelta: streamLoopGuard ? (text) => streamLoopGuard.feed(text) : undefined,
55
55
  });
56
+ // Transform stream 内部异常若无 listener 会触发 uncaught exception,
57
+ // 指标采集错误不影响业务数据流,仅记录 warn 日志。
58
+ metricsTransform.on("error", (err) => {
59
+ p.request.log.warn({ err, logId: p.logId }, "metricsTransform stream error");
60
+ });
61
+ if (p.formatTransform) {
62
+ // 格式转换异常会破坏管道,记录 warn 后由 StreamProxy 的空闲超时兜底清理。
63
+ p.formatTransform.on("error", (err) => {
64
+ p.request.log.warn({ err, logId: p.logId }, "formatTransform stream error");
65
+ });
66
+ }
56
67
  const checkEarlyError = p.matcher ? (data) => p.matcher.test(UPSTREAM_SUCCESS, data) : undefined;
57
68
  const streamResult = await callStream(p.provider, p.apiKey, p.body, p.cliHdrs, p.reply, p.streamTimeoutMs, p.upstreamPath, buildHeaders, metricsTransform, checkEarlyError, undefined, streamLoopGuard, p.formatTransform, p.timeoutContext, undefined, agent);
58
69
  const m = (streamResult.kind === "stream_success" || streamResult.kind === "stream_abort")
@@ -1 +1 @@
1
- import{Ut as e,Vt as t,Z as n,et as r,ft as i,ht as a,r as o}from"./button-C7HO6Dyb.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)=>(i(),n(`div`,{"data-slot":`card`,"data-size":r.size,class:e(t(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))},[a(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)=>(i(),n(`div`,{"data-slot":`card-content`,class:e(t(o)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[a(r.$slots,`default`)],2))}});export{c as n,l as t};
1
+ import{Ut as e,Vt as t,Z as n,et as r,ft as i,ht as a,r as o}from"./button-QVj-5yKZ.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)=>(i(),n(`div`,{"data-slot":`card`,"data-size":r.size,class:e(t(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))},[a(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)=>(i(),n(`div`,{"data-slot":`card-content`,class:e(t(o)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[a(r.$slots,`default`)],2))}});export{c as n,l as t};
@@ -1 +1 @@
1
- import{Ut as e,Vt as t,Z as n,et as r,ft as i,ht as a,r as o}from"./button-C7HO6Dyb.js";var s=r({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`div`,{"data-slot":`card-header`,class:e(t(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))},[a(r.$slots,`default`)],2))}}),c=r({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`div`,{"data-slot":`card-title`,class:e(t(o)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[a(r.$slots,`default`)],2))}});export{s as n,c as t};
1
+ import{Ut as e,Vt as t,Z as n,et as r,ft as i,ht as a,r as o}from"./button-QVj-5yKZ.js";var s=r({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`div`,{"data-slot":`card-header`,class:e(t(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))},[a(r.$slots,`default`)],2))}}),c=r({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`div`,{"data-slot":`card-title`,class:e(t(o)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[a(r.$slots,`default`)],2))}});export{s as n,c as t};
@@ -1 +1 @@
1
- import{$ as e,Tt as t,U as n,Vt as r,W as i,Wt as a,X as o,Y as s,_t as c,et as l,ft as u,ht as d,i as f,m as p,nt as m,o as h,q as g,r as _,st as v,x as y}from"./button-C7HO6Dyb.js";import{t as b}from"./VisuallyHiddenInput-CwE9jREu.js";import{t as x}from"./RovingFocusItem-CFmjbm49.js";import{B as S,G as C,H as w,L as T,Y as E,q as D,ut as O}from"./index-itL9--Q_.js";function k(e,t){return C(e)?!1:Array.isArray(e)?e.some(e=>E(e,t)):E(e,t)}var[A,j]=D(`CheckboxGroupRoot`);function M(e){return e===`indeterminate`}function N(e){return M(e)?`indeterminate`:e?`checked`:`unchecked`}var[P,F]=D(`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(e,{emit:a}){let l=e,m=a,{forwardRef:_,currentElement:y}=h(),S=A(null),T=p(l,`modelValue`,m,{defaultValue:l.defaultValue??l.falseValue,passive:l.modelValue===void 0}),D=g(()=>S?.disabled.value||l.disabled),O=g(()=>E(T.value,l.trueValue)),j=g(()=>C(S?.modelValue.value)?T.value===`indeterminate`?`indeterminate`:O.value:k(S.modelValue.value,l.value));function P(){if(C(S?.modelValue.value))T.value===`indeterminate`?T.value=l.trueValue:T.value=O.value?l.falseValue:l.trueValue;else{let e=[...S.modelValue.value||[]];if(k(e,l.value)){let t=e.findIndex(e=>E(e,l.value));e.splice(t,1)}else e.push(l.value);S.modelValue.value=e}}let I=w(y),L=g(()=>l.id&&y.value?document.querySelector(`[for="${l.id}"]`)?.innerText:void 0);return F({disabled:D,state:j}),(e,a)=>(u(),s(c(r(S)?.rovingFocus.value?r(x):r(f)),v(e.$attrs,{id:e.id,ref:r(_),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":D.value?``:void 0,disabled:D.value,focusable:r(S)?.rovingFocus.value?!D.value:void 0,onKeydown:n(i(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:t(()=>[d(e.$slots,`default`,{modelValue:r(T),state:j.value}),r(I)&&e.name&&!r(S)?(u(),s(r(b),{key:0,type:`checkbox`,checked:!!j.value,name:e.name,value:e.value,disabled:D.value,required:e.required},null,8,[`checked`,`name`,`value`,`disabled`,`required`])):o(`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(n){let{forwardRef:i}=h(),a=P();return(n,o)=>(u(),s(r(T),{present:n.forceMount||r(M)(r(a).state.value)||r(a).state.value===!0},{default:t(()=>[e(r(f),v({ref:r(i),"data-state":r(N)(r(a).state.value),"data-disabled":r(a).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":n.asChild,as:n.as},n.$attrs),{default:t(()=>[d(n.$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(n,{emit:i}){let o=n,c=i,l=S(y(o,`class`),c);return(n,i)=>(u(),s(r(I),v({"data-slot":`checkbox`},r(l),{class:r(_)(`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`,o.class)}),{default:t(i=>[e(r(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:t(()=>[d(n.$slots,`default`,a(m(i)),()=>[e(r(O))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
1
+ import{$ as e,Tt as t,U as n,Vt as r,W as i,Wt as a,X as o,Y as s,_t as c,et as l,ft as u,ht as d,i as f,m as p,nt as m,o as h,q as g,r as _,st as v,x as y}from"./button-QVj-5yKZ.js";import{t as b}from"./VisuallyHiddenInput-BQ3HNrqW.js";import{t as x}from"./RovingFocusItem-DlnzFIfo.js";import{B as S,G as C,H as w,L as T,Y as E,q as D,ut as O}from"./index-RqqKMw85.js";function k(e,t){return C(e)?!1:Array.isArray(e)?e.some(e=>E(e,t)):E(e,t)}var[A,j]=D(`CheckboxGroupRoot`);function M(e){return e===`indeterminate`}function N(e){return M(e)?`indeterminate`:e?`checked`:`unchecked`}var[P,F]=D(`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(e,{emit:a}){let l=e,m=a,{forwardRef:_,currentElement:y}=h(),S=A(null),T=p(l,`modelValue`,m,{defaultValue:l.defaultValue??l.falseValue,passive:l.modelValue===void 0}),D=g(()=>S?.disabled.value||l.disabled),O=g(()=>E(T.value,l.trueValue)),j=g(()=>C(S?.modelValue.value)?T.value===`indeterminate`?`indeterminate`:O.value:k(S.modelValue.value,l.value));function P(){if(C(S?.modelValue.value))T.value===`indeterminate`?T.value=l.trueValue:T.value=O.value?l.falseValue:l.trueValue;else{let e=[...S.modelValue.value||[]];if(k(e,l.value)){let t=e.findIndex(e=>E(e,l.value));e.splice(t,1)}else e.push(l.value);S.modelValue.value=e}}let I=w(y),L=g(()=>l.id&&y.value?document.querySelector(`[for="${l.id}"]`)?.innerText:void 0);return F({disabled:D,state:j}),(e,a)=>(u(),s(c(r(S)?.rovingFocus.value?r(x):r(f)),v(e.$attrs,{id:e.id,ref:r(_),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":D.value?``:void 0,disabled:D.value,focusable:r(S)?.rovingFocus.value?!D.value:void 0,onKeydown:n(i(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:t(()=>[d(e.$slots,`default`,{modelValue:r(T),state:j.value}),r(I)&&e.name&&!r(S)?(u(),s(r(b),{key:0,type:`checkbox`,checked:!!j.value,name:e.name,value:e.value,disabled:D.value,required:e.required},null,8,[`checked`,`name`,`value`,`disabled`,`required`])):o(`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(n){let{forwardRef:i}=h(),a=P();return(n,o)=>(u(),s(r(T),{present:n.forceMount||r(M)(r(a).state.value)||r(a).state.value===!0},{default:t(()=>[e(r(f),v({ref:r(i),"data-state":r(N)(r(a).state.value),"data-disabled":r(a).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":n.asChild,as:n.as},n.$attrs),{default:t(()=>[d(n.$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(n,{emit:i}){let o=n,c=i,l=S(y(o,`class`),c);return(n,i)=>(u(),s(r(I),v({"data-slot":`checkbox`},r(l),{class:r(_)(`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`,o.class)}),{default:t(i=>[e(r(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:t(()=>[d(n.$slots,`default`,a(m(i)),()=>[e(r(O))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
@@ -1 +1 @@
1
- import{$ as e,Nt as t,Rt as n,St as r,Tt as i,Vt as a,Wt as o,X as s,Y as c,ct as l,d as u,et as d,ft as f,ht as p,i as m,m as h,nt as g,o as _,q as v,st as y,ut as b}from"./button-C7HO6Dyb.js";import{B as x,L as S,q as C,z as w}from"./index-itL9--Q_.js";var[T,E]=C(`CollapsibleRoot`),D=d({__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:r}){let o=e,s=h(o,`open`,r,{defaultValue:o.defaultOpen,passive:o.open===void 0}),{disabled:l,unmountOnHide:u}=n(o);return E({contentId:``,disabled:l,open:s,unmountOnHide:u,onOpenToggle:()=>{l.value||(s.value=!s.value)}}),t({open:s}),_(),(e,t)=>(f(),c(a(m),{as:e.as,"as-child":o.asChild,"data-state":a(s)?`open`:`closed`,"data-disabled":a(l)?``:void 0},{default:i(()=>[p(e.$slots,`default`,{open:a(s)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),O=d({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(n,{emit:o}){let d=n,h=o,g=T();g.contentId||=w(void 0,`reka-collapsible-content`);let x=t(),{forwardRef:C,currentElement:E}=_(),D=t(0),O=t(0),k=v(()=>g.open.value),A=t(k.value),j=t();r(()=>[k.value,x.value?.present],async()=>{await l();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=v(()=>A.value&&g.open.value);return b(()=>{requestAnimationFrame(()=>{A.value=!1})}),u(E,`beforematch`,e=>{requestAnimationFrame(()=>{g.onOpenToggle(),h(`contentFound`)})}),(t,n)=>(f(),c(a(S),{ref_key:`presentRef`,ref:x,present:t.forceMount||a(g).open.value,"force-mount":!0},{default:i(({present:n})=>[e(a(m),y(t.$attrs,{id:a(g).contentId,ref:a(C),"as-child":d.asChild,as:t.as,hidden:n?void 0:a(g).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:a(g).open.value?`open`:`closed`,"data-disabled":a(g).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:i(()=>[!a(g).unmountOnHide.value||n?p(t.$slots,`default`,{key:0}):s(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),k=d({__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 n=x(e,t);return(e,t)=>(f(),c(a(D),y({"data-slot":`collapsible`},a(n)),{default:i(t=>[p(e.$slots,`default`,o(g(t)))]),_:3},16))}}),A=d({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,n)=>(f(),c(a(O),y({"data-slot":`collapsible-content`},t),{default:i(()=>[p(e.$slots,`default`)]),_:3},16))}});export{k as n,T as r,A as t};
1
+ import{$ as e,Nt as t,Rt as n,St as r,Tt as i,Vt as a,Wt as o,X as s,Y as c,ct as l,d as u,et as d,ft as f,ht as p,i as m,m as h,nt as g,o as _,q as v,st as y,ut as b}from"./button-QVj-5yKZ.js";import{B as x,L as S,q as C,z as w}from"./index-RqqKMw85.js";var[T,E]=C(`CollapsibleRoot`),D=d({__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:r}){let o=e,s=h(o,`open`,r,{defaultValue:o.defaultOpen,passive:o.open===void 0}),{disabled:l,unmountOnHide:u}=n(o);return E({contentId:``,disabled:l,open:s,unmountOnHide:u,onOpenToggle:()=>{l.value||(s.value=!s.value)}}),t({open:s}),_(),(e,t)=>(f(),c(a(m),{as:e.as,"as-child":o.asChild,"data-state":a(s)?`open`:`closed`,"data-disabled":a(l)?``:void 0},{default:i(()=>[p(e.$slots,`default`,{open:a(s)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),O=d({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(n,{emit:o}){let d=n,h=o,g=T();g.contentId||=w(void 0,`reka-collapsible-content`);let x=t(),{forwardRef:C,currentElement:E}=_(),D=t(0),O=t(0),k=v(()=>g.open.value),A=t(k.value),j=t();r(()=>[k.value,x.value?.present],async()=>{await l();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=v(()=>A.value&&g.open.value);return b(()=>{requestAnimationFrame(()=>{A.value=!1})}),u(E,`beforematch`,e=>{requestAnimationFrame(()=>{g.onOpenToggle(),h(`contentFound`)})}),(t,n)=>(f(),c(a(S),{ref_key:`presentRef`,ref:x,present:t.forceMount||a(g).open.value,"force-mount":!0},{default:i(({present:n})=>[e(a(m),y(t.$attrs,{id:a(g).contentId,ref:a(C),"as-child":d.asChild,as:t.as,hidden:n?void 0:a(g).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:a(g).open.value?`open`:`closed`,"data-disabled":a(g).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:i(()=>[!a(g).unmountOnHide.value||n?p(t.$slots,`default`,{key:0}):s(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),k=d({__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 n=x(e,t);return(e,t)=>(f(),c(a(D),y({"data-slot":`collapsible`},a(n)),{default:i(t=>[p(e.$slots,`default`,o(g(t)))]),_:3},16))}}),A=d({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,n)=>(f(),c(a(O),y({"data-slot":`collapsible-content`},t),{default:i(()=>[p(e.$slots,`default`)]),_:3},16))}});export{k as n,T as r,A as t};
@@ -1 +1 @@
1
- import{Tt as e,Vt as t,Y as n,et as r,ft as i,ht as a,i as o,o as s,st as c}from"./button-C7HO6Dyb.js";import{r as l}from"./CollapsibleContent-BTVazeoQ.js";var u=r({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(r){let c=r;s();let u=l();return(r,s)=>(i(),n(t(o),{type:r.as===`button`?`button`:void 0,as:r.as,"as-child":c.asChild,"aria-controls":t(u).contentId,"aria-expanded":t(u).open.value,"data-state":t(u).open.value?`open`:`closed`,"data-disabled":t(u).disabled?.value?``:void 0,disabled:t(u).disabled?.value,onClick:t(u).onOpenToggle},{default:e(()=>[a(r.$slots,`default`)]),_:3},8,[`type`,`as`,`as-child`,`aria-controls`,`aria-expanded`,`data-state`,`data-disabled`,`disabled`,`onClick`]))}}),d=r({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean},as:{}},setup(r){let o=r;return(r,s)=>(i(),n(t(u),c({"data-slot":`collapsible-trigger`},o),{default:e(()=>[a(r.$slots,`default`)]),_:3},16))}});export{d as t};
1
+ import{Tt as e,Vt as t,Y as n,et as r,ft as i,ht as a,i as o,o as s,st as c}from"./button-QVj-5yKZ.js";import{r as l}from"./CollapsibleContent-B_TbpMgv.js";var u=r({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(r){let c=r;s();let u=l();return(r,s)=>(i(),n(t(o),{type:r.as===`button`?`button`:void 0,as:r.as,"as-child":c.asChild,"aria-controls":t(u).contentId,"aria-expanded":t(u).open.value,"data-state":t(u).open.value?`open`:`closed`,"data-disabled":t(u).disabled?.value?``:void 0,disabled:t(u).disabled?.value,onClick:t(u).onOpenToggle},{default:e(()=>[a(r.$slots,`default`)]),_:3},8,[`type`,`as`,`as-child`,`aria-controls`,`aria-expanded`,`data-state`,`data-disabled`,`disabled`,`onClick`]))}}),d=r({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean},as:{}},setup(r){let o=r;return(r,s)=>(i(),n(t(u),c({"data-slot":`collapsible-trigger`},o),{default:e(()=>[a(r.$slots,`default`)]),_:3},16))}});export{d as t};