From b69c60fdbd4f9d529dac102e2a18abfe2e45e7b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E5=8F=8A?= <522caiji@gmail.com> Date: Mon, 14 Sep 2026 18:18:32 +0800 Subject: [PATCH] feat(proxy): add global and per-account outbound proxy support Add outbound proxy routing across every account family. Global setting - New internal/proxy package: Parse/ValidateHTTPOnly, Redact, Preserve, Effective resolution, and a bounded LRU TransportCache (32 entries, evicts with CloseIdleConnections). - Global proxy stored in app_secrets as proxy_url, bootstrapped from QODER_PROXY_URL, editable on the System page. - proxy.ValidateHTTPOnly keeps the global value http(s)-only because Qoder child workers inherit it. direct / none explicitly bypass. Account override - New SQLite migration 019_account_proxy.sql adds accounts.proxy_url. - Account override wins over the global value; Qoder account-level proxies are http(s)-only, WorkBuddy / Trae also accept socks5. - Proxy credentials are redacted in console responses; redacted re-submits are resolved back to the stored value via proxy.Preserve. Runtime wiring - Qoder workers: ExecStarter pushes the effective proxy as HTTP(S)_PROXY and QODER_PROXY_URL; worker/src/daemon.mjs installs an undici ProxyAgent (or a plain Agent for direct/none) as the global dispatcher. Worker deps pin undici 6.21.2 and deploy/Dockerfile runs npm ci. - WorkBuddy / Trae in-process adapters pick a Go transport from a per-client proxy.TransportCache so CONNECT tunnels are reused. - Manager.ReloadProxyURL restarts only enabled child-process accounts that inherit the global proxy; a failed reload stays pending so an identical PATCH retries. Account proxy edits restart that account. - Console System page edits the global value; account create/edit forms edit the override. Docs: README / README_EN / deploy/README describe QODER_PROXY_URL. Console static assets rebuilt to match frontend/dist. --- README.md | 1 + README_EN.md | 1 + deploy/Dockerfile | 5 + deploy/README.md | 1 + deploy/docker-compose.yml | 1 + frontend/src/api/overview.ts | 2 + frontend/src/api/system.ts | 3 +- frontend/src/api/types.ts | 1 + frontend/src/components/AddAccountModal.tsx | 14 + .../components/account/EditAccountModal.tsx | 16 +- frontend/src/i18n/messages.ts | 10 + frontend/src/pages/AccountsPage.tsx | 2 +- frontend/src/pages/SystemPage.tsx | 54 ++- go.mod | 6 +- go.sum | 32 ++ internal/accounts/manager.go | 221 +++++++-- internal/accounts/manager_test.go | 17 + internal/accounts/migrations.go | 2 + internal/accounts/proxy_test.go | 423 ++++++++++++++++++ internal/accounts/store.go | 84 +++- internal/api/accounts.go | 15 +- internal/api/auth_test.go | 14 +- internal/api/server.go | 6 +- internal/api/system_settings.go | 69 ++- internal/api/system_settings_test.go | 230 ++++++++++ internal/api/workerproxy.go | 12 +- internal/api/workerproxy_test.go | 35 ++ internal/config/config.go | 2 + internal/providers/trae/client.go | 103 ++++- internal/providers/trae/client_test.go | 111 ++++- internal/providers/workbuddy/catalog.go | 11 +- internal/providers/workbuddy/client.go | 137 +++++- internal/providers/workbuddy/client_test.go | 233 +++++++++- internal/providers/workbuddy/credential.go | 9 + internal/proxy/proxy.go | 289 ++++++++++++ internal/proxy/proxy_test.go | 282 ++++++++++++ ...t-D43-fLy7.js => TrafficChart-BKUjj-xR.js} | 2 +- .../webui/static/assets/index-DyfuUCZL.js | 28 -- .../webui/static/assets/index-Nd7wuGyD.js | 28 ++ internal/webui/static/index.html | 2 +- worker/package-lock.json | 24 + worker/package.json | 3 + worker/src/daemon.mjs | 29 ++ 43 files changed, 2426 insertions(+), 144 deletions(-) create mode 100644 internal/accounts/proxy_test.go create mode 100644 internal/proxy/proxy.go create mode 100644 internal/proxy/proxy_test.go rename internal/webui/static/assets/{TrafficChart-D43-fLy7.js => TrafficChart-BKUjj-xR.js} (99%) delete mode 100644 internal/webui/static/assets/index-DyfuUCZL.js create mode 100644 internal/webui/static/assets/index-Nd7wuGyD.js create mode 100644 worker/package-lock.json diff --git a/README.md b/README.md index cd5b7f7..8c5cfd4 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ - **OpenAI / Anthropic 兼容代理**:`/v1/chat/completions`、`/v1/responses`、`/v1/messages`、`/v1/models`;支持流式/非流式、文本与函数工具调用;图片能力取决于 provider(当前 Qoder 支持,WorkBuddy / Trae 不支持);文件输入会明确拒绝。`messages` / `responses` 当前为无状态适配层,不支持服务端会话或上游专属工具。 - **多渠道账号池**:Qoder 国际版 / 国内版、WorkBuddy 国际版 / 国内版、Trae 国内版 Solo;地域隔离、账号固定、并发限制、冷却与同族故障切换 +- **代理出口**:支持统一 HTTP(S) 代理,也支持账号级覆盖;账号可用 `direct` / `none` 显式直连。SOCKS5 仅支持 WorkBuddy / Trae 的账号级代理,Qoder 账号级代理只支持 HTTP(S) - **账号级常驻运行时**:Qoder 账号使用独立 Node 进程、HOME 与 WASM 上下文;WorkBuddy / Trae 使用进程内 HTTP/SSE 适配器。登录态、云端连接和账号隔离由各 provider 的运行时负责 - **按 provider 支持多种登录方式**:浏览器 Device Flow OAuth、PAT,以及适用 provider 的凭证导入/导出 - **Web 控制台**:账号、模型、接入、请求历史与运行时日志,明暗主题 diff --git a/README_EN.md b/README_EN.md index d08b4ff..05c7436 100644 --- a/README_EN.md +++ b/README_EN.md @@ -21,6 +21,7 @@ Long-lived account runtimes, multi-account scheduling. Deploy with Docker; that - **OpenAI / Anthropic-compatible proxy**: `/v1/chat/completions`, `/v1/responses`, `/v1/messages`, `/v1/models` — streaming/non-streaming text and function tools; image support depends on the provider (currently supported by Qoder, not WorkBuddy / Trae); file inputs are rejected explicitly. `messages` / `responses` are stateless adapters today and do not support server-side conversations or upstream-specific tools. - **Multi-channel account pool**: Qoder Global / Qoder CN, WorkBuddy Global / WorkBuddy CN, Trae CN Solo — region isolation, account pinning, concurrency limits, cooldowns, and same-family failover +- **Outbound proxies**: set one global HTTP(S) proxy or override it per account; use `direct` / `none` for explicit direct access. SOCKS5 is available for WorkBuddy / Trae account-level proxies only; Qoder account-level proxies are HTTP(S) only - **Account-level runtimes**: Qoder accounts use an isolated Node process, HOME, and WASM context; WorkBuddy / Trae use in-process HTTP/SSE adapters. Each provider owns its login and upstream runtime boundary - **Provider-specific login methods**: browser Device Flow OAuth, PAT, and credential import/export where supported - **Web console**: accounts, models, access, request history, and runtime logs, with light and dark themes diff --git a/deploy/Dockerfile b/deploy/Dockerfile index a0e7e8d..1d41db6 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -29,8 +29,13 @@ RUN apt-get update \ WORKDIR /app COPY --from=build /out/qoder-api-proxy /app/qoder-api-proxy COPY --from=build /out/cli2api-updater /app/cli2api-updater +COPY worker/package.json worker/package-lock.json /app/worker/ +RUN cd /app/worker && npm ci --omit=dev COPY worker/src /app/worker COPY worker/last-plain.sample.json /app/worker/last-plain.sample.json +RUN test -f /app/worker/daemon.mjs \ + && test -f /app/worker/compat.mjs \ + && test -d /app/worker/node_modules/undici RUN mkdir -p /data && chmod 700 /data ENV HOST=0.0.0.0 \ PORT=3010 \ diff --git a/deploy/README.md b/deploy/README.md index 50966aa..12a0df8 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -99,6 +99,7 @@ the same conversation prefer the same account from the first user message | `QODER_MAX_RETRY_ACCOUNTS` | `4` | Maximum accounts attempted for one request (1-64) | | `QODER_SSE_DIAGNOSTIC_MODELS` | empty | Comma-separated Qoder model IDs for redacted SSE diagnostics in Runtime Logs; `*` enables all | | `QODER_WORKER_BASE_PORT` | `32100` | Internal child-runtime port range | +| `QODER_PROXY_URL` | empty | Global outbound proxy; supports `http(s)://`, `direct`, or `none`. Global proxies must be HTTP(S); SOCKS5 is only accepted for WorkBuddy / Trae account-level proxies | | `QODERCLI_JS` | image default | Pinned Qoder Global CLI bundle | | `QODERCNCLI_JS` | image default | Pinned Qoder CN CLI bundle | | `UPDATE_GITHUB_TOKEN` | empty | Optional GitHub token for release checks | diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 43c6eba..0440734 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -12,6 +12,7 @@ services: QODER_RUNTIME_DIR: "/run/cli2api" QODER_SSE_DIAGNOSTIC_MODELS: "${QODER_SSE_DIAGNOSTIC_MODELS:-}" QODER_WORKER_BASE_PORT: "${QODER_WORKER_BASE_PORT:-32100}" + QODER_PROXY_URL: "${QODER_PROXY_URL:-}" UPDATE_SOCKET_PATH: "/run/cli2api-updater/updater.sock" UPDATE_AGENT_URL: "${UPDATE_AGENT_URL:-}" UPDATE_AGENT_TOKEN: "${UPDATE_AGENT_TOKEN:-}" diff --git a/frontend/src/api/overview.ts b/frontend/src/api/overview.ts index 093d4d2..5645cec 100644 --- a/frontend/src/api/overview.ts +++ b/frontend/src/api/overview.ts @@ -191,6 +191,7 @@ export function createAccount( drop_system_prompt?: boolean workbuddy_auto_checkin?: boolean workbuddy_checkin_time?: string + proxy_url?: string }, ) { return api('/api/accounts', { @@ -205,6 +206,7 @@ export function createAccount( drop_system_prompt: options?.drop_system_prompt, workbuddy_auto_checkin: options?.workbuddy_auto_checkin, workbuddy_checkin_time: options?.workbuddy_checkin_time, + proxy_url: options?.proxy_url, }), }) } diff --git a/frontend/src/api/system.ts b/frontend/src/api/system.ts index 90c51dc..2f9065b 100644 --- a/frontend/src/api/system.ts +++ b/frontend/src/api/system.ts @@ -61,6 +61,7 @@ export type SystemUpdateInfo = { export type SystemSettings = { cross_provider_model_pool: boolean + proxy_url: string routing_strategy: 'round-robin' | 'weighted-round-robin' | 'fill-first' session_affinity?: { ttl_seconds?: number @@ -94,7 +95,7 @@ export function fetchSystemSettings() { return api('/api/system/settings') } -export function updateSystemSettings(input: { cross_provider_model_pool?: boolean; routing_strategy?: SystemSettings['routing_strategy'] }) { +export function updateSystemSettings(input: { cross_provider_model_pool?: boolean; routing_strategy?: SystemSettings['routing_strategy']; proxy_url?: string }) { return api('/api/system/settings', { method: 'PATCH', body: JSON.stringify(input), diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 9390004..30a62a4 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -118,6 +118,7 @@ export type Overview = { drop_system_prompt?: boolean workbuddy_auto_checkin?: boolean workbuddy_checkin_time?: string + proxy_url?: string status?: string cooldown_until?: string | null url?: string diff --git a/frontend/src/components/AddAccountModal.tsx b/frontend/src/components/AddAccountModal.tsx index 25ed5e8..a904332 100644 --- a/frontend/src/components/AddAccountModal.tsx +++ b/frontend/src/components/AddAccountModal.tsx @@ -132,6 +132,7 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) { const [dropSystemPrompt, setDropSystemPrompt] = useState(true) const [autoCheckin, setAutoCheckin] = useState(false) const [autoCheckinTime, setAutoCheckinTime] = useState('09:00') + const [proxyUrl, setProxyUrl] = useState('') const [pat, setPat] = useState('') const [json, setJson] = useState('') const [phase, setPhase] = useState('idle') @@ -210,6 +211,7 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) { drop_system_prompt: showDropSystem ? dropSystemPrompt : true, workbuddy_auto_checkin: showAutoCheckin ? autoCheckin : false, workbuddy_checkin_time: showAutoCheckin ? autoCheckinTime : undefined, + proxy_url: proxyUrl.trim(), } } @@ -226,6 +228,7 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) { setDropSystemPrompt(true) setAutoCheckin(false) setAutoCheckinTime('09:00') + setProxyUrl('') setPat('') setJson('') setAdvancedOpen(false) @@ -553,6 +556,17 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {

{t('priorityHint')}

+ {showDropSystem ? (
diff --git a/frontend/src/components/account/EditAccountModal.tsx b/frontend/src/components/account/EditAccountModal.tsx index 9052535..cd8fe85 100644 --- a/frontend/src/components/account/EditAccountModal.tsx +++ b/frontend/src/components/account/EditAccountModal.tsx @@ -12,13 +12,14 @@ type Props = { busy: boolean t: Translate onClose: () => void - onSave: (input: { name: string; max_inflight: number; priority: number }) => Promise + onSave: (input: { name: string; max_inflight: number; priority: number; proxy_url: string }) => Promise } export function EditAccountModal({ account, busy, t, onClose, onSave }: Props) { const [name, setName] = useState(account?.name || '') const [maxInFlight, setMaxInFlight] = useState(account?.max_inflight ?? 4) const [priority, setPriority] = useState(account?.priority ?? 50) + const [proxyUrl, setProxyUrl] = useState(account?.proxy_url || '') const [error, setError] = useState('') const title = t('editAccountTitle', { name: account?.name || account?.id || '' }) const provider = account ? accountProviderLabel(account.provider, account.region, t) : '' @@ -40,7 +41,7 @@ export function EditAccountModal({ account, busy, t, onClose, onSave }: Props) { } setError('') try { - await onSave({ name: trimmed, max_inflight: maxInFlight, priority }) + await onSave({ name: trimmed, max_inflight: maxInFlight, priority, proxy_url: proxyUrl.trim() }) onClose() } catch (err) { setError(err instanceof Error ? err.message : String(err)) @@ -127,6 +128,17 @@ export function EditAccountModal({ account, busy, t, onClose, onSave }: Props) { {t('priorityHint')}
+
+ + setProxyUrl(event.target.value)} + placeholder={t('proxyUrlPlaceholder')} + aria-label={t('proxyUrl')} + disabled={busy} + /> + {t('proxyUrlHint')} +
diff --git a/frontend/src/i18n/messages.ts b/frontend/src/i18n/messages.ts index cf4a939..c3df712 100644 --- a/frontend/src/i18n/messages.ts +++ b/frontend/src/i18n/messages.ts @@ -550,6 +550,11 @@ export const messages: Record = { wizardNamePh: 'Account name', maxInflightHint: 'How many chats this account may run at once. Changing it later restarts the worker.', priorityHint: 'Higher numbers are preferred when routing later uses this field. Default 50.', + proxyUrl: 'Proxy URL', + proxyUrlPlaceholder: 'http://127.0.0.1:7890, direct', + proxyUrlHint: 'Blank inherits the global proxy. Use direct or none to bypass proxies for this account. Qoder supports HTTP(S) only; WorkBuddy/Trae also support SOCKS5.', + proxySettingsTitle: 'Outbound proxy', + proxySettingsHint: 'Set the default proxy for upstream requests. Global proxies support HTTP(S) only. SOCKS5 is available for WorkBuddy/Trae account-level proxies. Account-level settings override it.', dropSystemPromptCreateHint: 'WorkBuddy only. Strip caller system prompts, then send an empty leading system slot so Global still accepts the request.', wizardBrowserLead: 'Opens the authorization page. Finish in the browser and this wizard continues automatically.', wizardStartBrowser: 'Start browser login', @@ -1159,6 +1164,11 @@ export const messages: Record = { wizardNamePh: '账号名称', maxInflightHint: '这个账号同时能跑多少条对话。之后改并发会重启该账号的 worker。', priorityHint: '数字越大,之后调度会越优先选这个账号。默认 50。', + proxyUrl: '代理地址', + proxyUrlPlaceholder: 'http://127.0.0.1:7890、direct', + proxyUrlHint: '留空继承全局代理;填写 direct 或 none 可让此账号直连。Qoder 仅支持 HTTP(S),WorkBuddy/Trae 额外支持 SOCKS5。', + proxySettingsTitle: '统一代理出口', + proxySettingsHint: '设置所有上游请求的默认代理;全局代理支持 HTTP(S),SOCKS5 仅支持 WorkBuddy/Trae 的账号级代理;账号级代理优先于全局代理。', dropSystemPromptCreateHint: '仅 WorkBuddy。剥离调用方系统提示词后仍补一条空的 system,避免国际版拒绝请求。', wizardBrowserLead: '会打开授权页。在浏览器完成授权后,向导会自动继续。', wizardStartBrowser: '开始浏览器登录', diff --git a/frontend/src/pages/AccountsPage.tsx b/frontend/src/pages/AccountsPage.tsx index 90daace..3cde7d3 100644 --- a/frontend/src/pages/AccountsPage.tsx +++ b/frontend/src/pages/AccountsPage.tsx @@ -318,7 +318,7 @@ export function AccountsPage() { } } - async function onSaveSettings(id: string, input: { name: string; max_inflight: number; priority: number }) { + async function onSaveSettings(id: string, input: { name: string; max_inflight: number; priority: number; proxy_url: string }) { if (!id) throw new Error(t('accountNameRequired')) setNameById((current) => ({ ...current, [id]: input.name })) setInflightById((current) => ({ ...current, [id]: input.max_inflight })) diff --git a/frontend/src/pages/SystemPage.tsx b/frontend/src/pages/SystemPage.tsx index 72a1877..317f5ee 100644 --- a/frontend/src/pages/SystemPage.tsx +++ b/frontend/src/pages/SystemPage.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useState } from 'react' -import { Button, Card, Chip, Description, Label, ListBox, Modal, Select } from '@heroui/react' +import { Button, Card, Chip, Description, Input, Label, ListBox, Modal, Select } from '@heroui/react' import { ArrowClockwise, ArrowCircleUp, @@ -33,6 +33,7 @@ export function SystemPage() { const [info, setInfo] = useState(null) const [consoleKey, setConsoleKey] = useState(null) const [settings, setSettings] = useState(null) + const [proxyDraft, setProxyDraft] = useState('') const [settingsBusy, setSettingsBusy] = useState(false) const [consoleBusy, setConsoleBusy] = useState(false) const [rotateOpen, setRotateOpen] = useState(false) @@ -72,7 +73,10 @@ export function SystemPage() { const timer = window.setTimeout(() => { void load(false) void fetchConsoleKey().then(setConsoleKey).catch(() => undefined) - void fetchSystemSettings().then(setSettings).catch((err) => setError(err instanceof Error ? err.message : String(err))) + void fetchSystemSettings().then((result) => { + setSettings(result) + setProxyDraft(result.proxy_url || '') + }).catch((err) => setError(err instanceof Error ? err.message : String(err))) }, 0) return () => window.clearTimeout(timer) }, [load]) @@ -225,6 +229,31 @@ export function SystemPage() { } } + async function updateProxyURL(value: string) { + const saved = settings?.proxy_url || '' + // Nothing changed in the field: keep the draft as-is and skip the PATCH so + // a no-op blur never reloads workers. + if (value === saved) { + setProxyDraft(saved) + return + } + const previousDraft = proxyDraft + setProxyDraft(value) + setSettingsBusy(true) + setError('') + try { + const updated = await updateSystemSettings({ proxy_url: value }) + setSettings(updated) + setProxyDraft(updated.proxy_url || '') + } catch (err) { + setProxyDraft(previousDraft) + setSettings((current) => current ? { ...current, proxy_url: saved } : current) + setError(err instanceof Error ? err.message : String(err)) + } finally { + setSettingsBusy(false) + } + } + async function updateRoutingStrategy(strategy: SystemSettings['routing_strategy']) { const previous = settings?.routing_strategy || 'round-robin' setSettings((current) => current ? { ...current, routing_strategy: strategy } : current) @@ -339,6 +368,27 @@ export function SystemPage() {
+ +
+
+
+

{t('proxySettingsTitle')}

+

{t('proxySettingsHint')}

+
+
+
+ + setProxyDraft(event.target.value)} + onBlur={(event) => void updateProxyURL(event.target.value.trim())} + placeholder={t('proxyUrlPlaceholder')} + disabled={settingsBusy || !settings} + /> + {t('proxyUrlHint')} +
+
+
diff --git a/go.mod b/go.mod index a44adc2..ea6e81e 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,11 @@ go 1.25.6 require github.com/joho/godotenv v1.5.1 +require ( + golang.org/x/net v0.56.0 + modernc.org/sqlite v1.57.0 +) + require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect @@ -14,5 +19,4 @@ require ( modernc.org/libc v1.74.4 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect - modernc.org/sqlite v1.57.0 // indirect ) diff --git a/go.sum b/go.sum index a338abf..c447e45 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,11 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= @@ -10,13 +14,41 @@ github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOF github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI= +modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU= +modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI= +modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k= modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sqlite v1.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg= modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/internal/accounts/manager.go b/internal/accounts/manager.go index 777d2f3..04302d2 100644 --- a/internal/accounts/manager.go +++ b/internal/accounts/manager.go @@ -17,6 +17,7 @@ import ( "time" "github.com/caigee-cmd/cli2api/internal/providers" + proxyutil "github.com/caigee-cmd/cli2api/internal/proxy" ) var errManagerClosed = errors.New("account manager closed") @@ -30,6 +31,7 @@ type ManagerConfig struct { QoderCNCLIPath string TemplatePath string ProxyAPIKey string + ProxyURL string MaxLogWriters io.Writer RestartDelay time.Duration RestartMaxDelay time.Duration @@ -45,6 +47,19 @@ type ProcessStarter interface { Start(context.Context, Account, string, int) (ManagedProcess, error) } +// ProxyConfigurableStarter lets the manager push the global outbound proxy to a +// starter that can apply it to newly spawned workers. Kept optional so test +// starters need not implement it. +type ProxyConfigurableStarter interface { + SetProxyURL(string) +} + +// APIKeyConfigurableStarter is the sibling of ProxyConfigurableStarter for the +// manager's proxy API key. +type APIKeyConfigurableStarter interface { + SetProxyAPIKey(string) +} + // WorkBuddyMaintainer is the Phase N ops surface. Implemented by // workbuddy.Client; kept narrow so accounts does not grow a generic // check-in capability on AccountProber. @@ -90,6 +105,14 @@ type Manager struct { persistClosed bool persistCloseCh chan struct{} // closed by Close(); drainer's retry backoff watches it persistDone sync.WaitGroup + + // Serializes ReloadProxyURL so two settings PATCHes cannot interleave + // stop/start cycles. proxyReloadPending stays true while the applied global + // proxy differs from what the running workers use (a reload attempt failed, + // or one was never made), so resubmitting the same value can still retry + // instead of being treated as a no-op. Guarded by mu. + proxyReloadMu sync.Mutex + proxyReloadPending bool } func NewManager(config ManagerConfig, store *Store, starter ProcessStarter) *Manager { @@ -106,7 +129,7 @@ func NewManager(config ManagerConfig, store *Store, starter ProcessStarter) *Man config.RestartMaxDelay = config.RestartDelay } if starter == nil { - starter = ExecStarter{Config: config} + starter = &ExecStarter{Config: config} } runCtx, cancel := context.WithCancel(context.Background()) manager := &Manager{ @@ -392,10 +415,6 @@ func (m *Manager) ReplaceProxyAPIKey(ctx context.Context, key string) error { } m.mu.Lock() m.config.ProxyAPIKey = key - if starter, ok := m.starter.(ExecStarter); ok { - starter.Config.ProxyAPIKey = key - m.starter = starter - } accounts := make([]Account, 0, len(m.processes)) for id := range m.processes { account, err := m.store.Get(ctx, id) @@ -408,6 +427,12 @@ func (m *Manager) ReplaceProxyAPIKey(ctx context.Context, key string) error { } } m.mu.Unlock() + // Push to the starter outside the manager lock; the default starter is the + // pointer *ExecStarter, so assert the behavior interface rather than the + // concrete (and never-matching) value type. + if starter, ok := m.starter.(APIKeyConfigurableStarter); ok { + starter.SetProxyAPIKey(key) + } for _, account := range accounts { if err := m.stopAccount(account.ID); err != nil { return err @@ -603,9 +628,33 @@ func (w *prefixLogWriter) Write(p []byte) (int, error) { } type ExecStarter struct { + mu sync.RWMutex Config ManagerConfig } +// configSnapshot returns a stable copy of the starter config. Start uses one +// snapshot for the whole spawn so a concurrent SetProxyURL cannot race with +// reading fields. +func (s *ExecStarter) configSnapshot() ManagerConfig { + s.mu.RLock() + defer s.mu.RUnlock() + return s.Config +} + +// SetProxyURL updates the global proxy used for future spawns. +func (s *ExecStarter) SetProxyURL(value string) { + s.mu.Lock() + s.Config.ProxyURL = strings.TrimSpace(value) + s.mu.Unlock() +} + +// SetProxyAPIKey updates the manager proxy API key used for future spawns. +func (s *ExecStarter) SetProxyAPIKey(value string) { + s.mu.Lock() + s.Config.ProxyAPIKey = value + s.mu.Unlock() +} + type execProcess struct { cmd *exec.Cmd url string @@ -624,34 +673,38 @@ func (p *execProcess) Stop() error { return p.cmd.Process.Kill() } -func (s ExecStarter) Start(_ context.Context, account Account, home string, port int) (ManagedProcess, error) { - node := s.Config.NodeBinary - if node == "" { - node = "node" +func proxyEnv(env []string, raw string) []string { + if strings.TrimSpace(raw) == "" { + return env } - if s.Config.DaemonPath == "" { - return nil, fmt.Errorf("worker daemon path required") + filtered := make([]string, 0, len(env)+2) + for _, value := range env { + key := strings.SplitN(value, "=", 2)[0] + switch strings.ToUpper(key) { + case "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "NO_PROXY": + continue + } + filtered = append(filtered, value) + } + if setting, err := proxyutil.Parse(raw); err == nil && setting.Mode == proxyutil.ModeProxy { + filtered = append(filtered, "HTTP_PROXY="+raw, "HTTPS_PROXY="+raw, "http_proxy="+raw, "https_proxy="+raw) } - cliPath, site, configDir, configEnv, err := qoderRuntimeSpec(s.Config, account, home) + return filtered +} + +func (s *ExecStarter) Start(_ context.Context, account Account, home string, port int) (ManagedProcess, error) { + config := s.configSnapshot() + env, err := starterEnv(config, account, home, port) if err != nil { return nil, err } - cmd := exec.Command(node, s.Config.DaemonPath) - cmd.Env = append(os.Environ(), - "HOME="+home, - "QODER_HOME="+configDir, - configEnv+"="+configDir, - "QODER_SITE="+site, - "QODER_ACCOUNT_ID="+account.ID, - "QODER_MAX_INFLIGHT="+strconv.Itoa(account.MaxInFlight), - "WORKER_HOST=127.0.0.1", - "WORKER_PORT="+strconv.Itoa(port), - "PROXY_API_KEY="+s.Config.ProxyAPIKey, - "QODERCLI_JS="+cliPath, - "PLAIN_TEMPLATE_PATH="+s.Config.TemplatePath, - "QODER_WARMUP_CWD="+filepath.Join(home, "work"), - ) - writer := s.Config.MaxLogWriters + node := config.NodeBinary + if node == "" { + node = "node" + } + cmd := exec.Command(node, config.DaemonPath) + cmd.Env = env + writer := config.MaxLogWriters if writer == nil { writer = os.Stderr } @@ -672,6 +725,36 @@ func (s ExecStarter) Start(_ context.Context, account Account, home string, port return process, nil } +// starterEnv builds the worker environment from a stable config snapshot. The +// effective proxy is resolved once (account override wins, else global) and +// applied to both the proxy env vars and QODER_PROXY_URL. +func starterEnv(config ManagerConfig, account Account, home string, port int) ([]string, error) { + if config.DaemonPath == "" { + return nil, fmt.Errorf("worker daemon path required") + } + cliPath, site, configDir, configEnv, err := qoderRuntimeSpec(config, account, home) + if err != nil { + return nil, err + } + effectiveProxy := proxyutil.Effective(account.ProxyURL, config.ProxyURL) + env := proxyEnv(os.Environ(), effectiveProxy) + return append(env, + "HOME="+home, + "QODER_HOME="+configDir, + configEnv+"="+configDir, + "QODER_SITE="+site, + "QODER_ACCOUNT_ID="+account.ID, + "QODER_MAX_INFLIGHT="+strconv.Itoa(account.MaxInFlight), + "WORKER_HOST=127.0.0.1", + "WORKER_PORT="+strconv.Itoa(port), + "PROXY_API_KEY="+config.ProxyAPIKey, + "QODERCLI_JS="+cliPath, + "PLAIN_TEMPLATE_PATH="+config.TemplatePath, + "QODER_WARMUP_CWD="+filepath.Join(home, "work"), + "QODER_PROXY_URL="+effectiveProxy, + ), nil +} + func (m *Manager) Create(ctx context.Context, input CreateAccount) (Account, error) { account, err := m.store.Create(ctx, input) if err != nil { @@ -685,6 +768,71 @@ func (m *Manager) Create(ctx context.Context, input CreateAccount) (Account, err return account, nil } +func (m *Manager) ReloadProxyURL(ctx context.Context, value string) error { + // Serialize reloads so concurrent PATCHes cannot interleave stop/start. + m.proxyReloadMu.Lock() + defer m.proxyReloadMu.Unlock() + + value = strings.TrimSpace(value) + + m.mu.Lock() + unchanged := strings.TrimSpace(m.config.ProxyURL) == value + // Skip only when nothing changed AND the running workers already match the + // desired value. A previous failure leaves proxyReloadPending set, so the + // same value can be retried. + if unchanged && !m.proxyReloadPending { + m.mu.Unlock() + return nil + } + m.config.ProxyURL = value + m.proxyReloadPending = true + m.mu.Unlock() + + // Push to the starter outside the manager lock so we never nest the + // manager lock around the starter lock. + if starter, ok := m.starter.(ProxyConfigurableStarter); ok { + starter.SetProxyURL(value) + } + + accounts, err := m.store.List(ctx) + if err != nil { + return err + } + var joined error + for _, account := range accounts { + if !m.shouldRestartForGlobalProxy(account) { + continue + } + if err := m.stopAccount(account.ID); err != nil { + joined = errors.Join(joined, fmt.Errorf("stop account %s: %w", account.ID, err)) + continue + } + if err := m.startAccountWithRecovery(ctx, account); err != nil { + joined = errors.Join(joined, fmt.Errorf("restart account %s: %w", account.ID, err)) + } + } + + // Clear the pending flag only when every worker switched successfully, so a + // later identical PATCH retries the ones that failed. + if joined == nil { + m.mu.Lock() + m.proxyReloadPending = false + m.mu.Unlock() + } + return joined +} + +// shouldRestartForGlobalProxy reports whether a global proxy change must +// restart the account's worker: only enabled child-process (Qoder) accounts +// with no per-account proxy inherit the global setting. +func (m *Manager) shouldRestartForGlobalProxy(account Account) bool { + descriptor, _, err := providers.Resolve(account.Provider, account.ProviderRegion) + return err == nil && + account.Enabled && + strings.TrimSpace(account.ProxyURL) == "" && + descriptor.Runtime == providers.RuntimeChildProcess +} + func (m *Manager) Update(ctx context.Context, id string, input UpdateAccount) error { before, err := m.store.Get(ctx, id) if err != nil { @@ -711,6 +859,15 @@ func (m *Manager) Update(ctx context.Context, id string, input UpdateAccount) er if !before.Enabled && after.Enabled { return m.startAccountWithRecovery(ctx, after) } + if before.Enabled && after.Enabled && before.ProxyURL != after.ProxyURL { + descriptor, _, resolveErr := providers.Resolve(after.Provider, after.ProviderRegion) + if resolveErr == nil && descriptor.Runtime == providers.RuntimeChildProcess { + if err := m.stopAccount(id); err != nil { + return err + } + return m.startAccountWithRecovery(ctx, after) + } + } if before.Enabled && after.Enabled && before.MaxInFlight != after.MaxInFlight { if err := m.stopAccount(id); err != nil { return err @@ -915,6 +1072,7 @@ type ImportAccount struct { DropSystemPrompt *bool WorkBuddyAutoCheckin *bool WorkBuddyCheckinTime string + ProxyURL string Credential NativeCredential } @@ -930,6 +1088,7 @@ type AccountView struct { DownUntil string `json:"down_until,omitempty"` ModelCooldowns map[string]string `json:"model_cooldowns,omitempty"` Quota *QuotaSnapshot `json:"quota,omitempty"` + ProxyURL string `json:"proxy_url,omitempty"` } func (m *Manager) Import(ctx context.Context, input ImportAccount) (Account, error) { @@ -937,7 +1096,7 @@ func (m *Manager) Import(ctx context.Context, input ImportAccount) (Account, err Name: input.Name, Provider: input.Provider, Region: input.Region, Enabled: false, MaxInFlight: input.MaxInFlight, Priority: input.Priority, DropSystemPrompt: input.DropSystemPrompt, WorkBuddyAutoCheckin: input.WorkBuddyAutoCheckin, - WorkBuddyCheckinTime: input.WorkBuddyCheckinTime, + WorkBuddyCheckinTime: input.WorkBuddyCheckinTime, ProxyURL: input.ProxyURL, }) if err != nil { return Account{}, err @@ -987,7 +1146,7 @@ func (m *Manager) AccountView(ctx context.Context, id string) (AccountView, erro if err != nil { return AccountView{}, err } - view := AccountView{Account: account, Quota: account.Quota} + view := AccountView{Account: account, Quota: account.Quota, ProxyURL: proxyutil.Redact(account.ProxyURL)} if item, ok := m.pool.ByID(account.ID); ok { view.Ready = item.Ready == nil || *item.Ready view.Hot = item.Hot != nil && *item.Hot @@ -1022,7 +1181,7 @@ func (m *Manager) Accounts(ctx context.Context) ([]AccountView, error) { } views := make([]AccountView, 0, len(stored)) for _, account := range stored { - view := AccountView{Account: account, Quota: account.Quota} + view := AccountView{Account: account, Quota: account.Quota, ProxyURL: proxyutil.Redact(account.ProxyURL)} if item, ok := m.pool.ByID(account.ID); ok { view.Ready = item.Ready == nil || *item.Ready view.Hot = item.Hot != nil && *item.Hot diff --git a/internal/accounts/manager_test.go b/internal/accounts/manager_test.go index 12b749b..e093b57 100644 --- a/internal/accounts/manager_test.go +++ b/internal/accounts/manager_test.go @@ -64,6 +64,23 @@ type fakeStarter struct { homes []string started chan *fakeProcess failures int + + setProxyMu sync.Mutex + proxyURLs []string +} + +// SetProxyURL lets fakeStarter satisfy ProxyConfigurableStarter so reload tests +// can observe whether the manager attempted a reload. +func (s *fakeStarter) SetProxyURL(value string) { + s.setProxyMu.Lock() + s.proxyURLs = append(s.proxyURLs, value) + s.setProxyMu.Unlock() +} + +func (s *fakeStarter) proxySetCount() int { + s.setProxyMu.Lock() + defer s.setProxyMu.Unlock() + return len(s.proxyURLs) } func (s *fakeStarter) Start(_ context.Context, account Account, home string, port int) (ManagedProcess, error) { diff --git a/internal/accounts/migrations.go b/internal/accounts/migrations.go index d780e75..7760182 100644 --- a/internal/accounts/migrations.go +++ b/internal/accounts/migrations.go @@ -210,6 +210,8 @@ ALTER TABLE request_logs ADD COLUMN empty_message_indexes TEXT NOT NULL DEFAULT ALTER TABLE request_logs ADD COLUMN message_roles TEXT NOT NULL DEFAULT '';`}, {filename: "018_workbuddy_checkin_time.sql", sql: ` ALTER TABLE accounts ADD COLUMN workbuddy_checkin_time TEXT NOT NULL DEFAULT '09:00';`}, + {filename: "019_account_proxy.sql", sql: ` +ALTER TABLE accounts ADD COLUMN proxy_url TEXT NOT NULL DEFAULT '';`}, } const schemaMigrationsDDL = ` diff --git a/internal/accounts/proxy_test.go b/internal/accounts/proxy_test.go new file mode 100644 index 0000000..2c726ed --- /dev/null +++ b/internal/accounts/proxy_test.go @@ -0,0 +1,423 @@ +package accounts + +import ( + "context" + "path/filepath" + "strings" + "sync" + "testing" +) + +func TestValidateAccountProxy(t *testing.T) { + ok := []struct { + provider string + region string + raw string + }{ + {provider: "qoder", region: "global", raw: ""}, + {provider: "qoder", region: "global", raw: "direct"}, + {provider: "qoder", region: "cn", raw: "http://proxy.example:8080"}, + {provider: "qoder", region: "global", raw: "https://proxy.example:8443"}, + {provider: "workbuddy", raw: "socks5://proxy.example:1080"}, + {provider: "trae", raw: "socks5h://proxy.example:1080"}, + } + for _, test := range ok { + if err := validateAccountProxy(test.provider, test.region, test.raw); err != nil { + t.Fatalf("validateAccountProxy(%q,%q,%q) = %v, want nil", test.provider, test.region, test.raw, err) + } + } + + rejected := []struct { + provider string + region string + raw string + }{ + {provider: "qoder", region: "global", raw: "socks5://proxy.example:1080"}, + {provider: "qoder", region: "cn", raw: "socks5h://proxy.example:1080"}, + } + for _, test := range rejected { + if err := validateAccountProxy(test.provider, test.region, test.raw); err == nil { + t.Fatalf("validateAccountProxy(%q,%q,%q) unexpectedly succeeded", test.provider, test.region, test.raw) + } + } +} + +func TestStoreCreateRejectsQoderSOCKS(t *testing.T) { + ctx := context.Background() + store, err := OpenStore(filepath.Join(t.TempDir(), "qoder.db")) + if err != nil { + t.Fatal(err) + } + defer store.Close() + + if _, err := store.Create(ctx, CreateAccount{Name: "QoderSocks", Enabled: true, ProxyURL: "socks5://proxy.example:1080"}); err == nil { + t.Fatal("Qoder account with SOCKS proxy was accepted") + } + if _, err := store.Create(ctx, CreateAccount{Name: "QoderHTTP", Enabled: true, ProxyURL: "http://proxy.example:8080"}); err != nil { + t.Fatalf("Qoder account with HTTP proxy rejected: %v", err) + } + if _, err := store.Create(ctx, CreateAccount{Name: "WbSocks", Provider: "workbuddy", ProxyURL: "socks5://proxy.example:1080"}); err != nil { + t.Fatalf("WorkBuddy account with SOCKS proxy rejected: %v", err) + } + if _, err := store.Create(ctx, CreateAccount{Name: "TraeSocks", Provider: "trae", ProxyURL: "socks5://proxy.example:1080"}); err != nil { + t.Fatalf("Trae account with SOCKS proxy rejected: %v", err) + } +} + +func TestStoreUpdateKeepsOriginalOnRejectedProxy(t *testing.T) { + ctx := context.Background() + store, err := OpenStore(filepath.Join(t.TempDir(), "qoder.db")) + if err != nil { + t.Fatal(err) + } + defer store.Close() + + account, err := store.Create(ctx, CreateAccount{Name: "QoderUpdate", Enabled: true, ProxyURL: "http://proxy.example:8080"}) + if err != nil { + t.Fatal(err) + } + + socks := "socks5://proxy.example:1080" + if err := store.Update(ctx, account.ID, UpdateAccount{ProxyURL: &socks}); err == nil { + t.Fatal("Qoder proxy update to SOCKS was accepted") + } + + reloaded, err := store.Get(ctx, account.ID) + if err != nil { + t.Fatal(err) + } + if reloaded.ProxyURL != "http://proxy.example:8080" { + t.Fatalf("stored proxy changed after rejected update: %q", reloaded.ProxyURL) + } +} + +func TestExecStarterSetProxyURLAppliesToNewWorkers(t *testing.T) { + starter := &ExecStarter{Config: ManagerConfig{ + DaemonPath: "/app/worker/daemon.mjs", + QoderCLIPath: "/usr/lib/qodercli.js", + }} + + starter.SetProxyURL("http://proxy.example:8080") + env := starterEnvForTest(t, starter, Account{ID: "acc1", Provider: "qoder", ProviderRegion: "global", MaxInFlight: 4}, "/tmp/home", 32100) + + if got := envValue(env, "QODER_PROXY_URL"); got != "http://proxy.example:8080" { + t.Fatalf("QODER_PROXY_URL = %q", got) + } + if got := envValue(env, "HTTPS_PROXY"); got != "http://proxy.example:8080" { + t.Fatalf("HTTPS_PROXY = %q", got) + } + + // A later update is visible to the next spawn. + starter.SetProxyURL(" direct ") + env = starterEnvForTest(t, starter, Account{ID: "acc1", Provider: "qoder", ProviderRegion: "global", MaxInFlight: 4}, "/tmp/home", 32100) + if got := envValue(env, "QODER_PROXY_URL"); got != "direct" { + t.Fatalf("QODER_PROXY_URL after update = %q", got) + } + if got := envValue(env, "HTTPS_PROXY"); got != "" { + t.Fatalf("direct must not inject HTTPS_PROXY, got %q", got) + } +} + +func TestStarterEnvAccountProxyOverridesGlobal(t *testing.T) { + config := ManagerConfig{ + DaemonPath: "/app/worker/daemon.mjs", + QoderCLIPath: "/usr/lib/qodercli.js", + ProxyURL: "http://global.example:8080", + } + + // Account override wins. + env := starterEnvForTestConfig(t, config, Account{ + ID: "acc1", Provider: "qoder", ProviderRegion: "global", MaxInFlight: 4, + ProxyURL: "http://account.example:9090", + }, "/tmp/home", 32100) + if got := envValue(env, "QODER_PROXY_URL"); got != "http://account.example:9090" { + t.Fatalf("account override QODER_PROXY_URL = %q", got) + } + + // Account "direct" beats the global HTTP proxy. + env = starterEnvForTestConfig(t, config, Account{ + ID: "acc1", Provider: "qoder", ProviderRegion: "global", MaxInFlight: 4, + ProxyURL: "direct", + }, "/tmp/home", 32100) + if got := envValue(env, "QODER_PROXY_URL"); got != "direct" { + t.Fatalf("account direct QODER_PROXY_URL = %q", got) + } + if got := envValue(env, "HTTPS_PROXY"); got != "" { + t.Fatalf("account direct must not inject HTTPS_PROXY, got %q", got) + } + + // Empty account proxy inherits the global. + env = starterEnvForTestConfig(t, config, Account{ + ID: "acc1", Provider: "qoder", ProviderRegion: "global", MaxInFlight: 4, + }, "/tmp/home", 32100) + if got := envValue(env, "QODER_PROXY_URL"); got != "http://global.example:8080" { + t.Fatalf("inherited QODER_PROXY_URL = %q", got) + } +} + +func TestExecStarterConfigSnapshotConcurrentWithSetProxyURL(t *testing.T) { + starter := &ExecStarter{Config: ManagerConfig{ + DaemonPath: "/app/worker/daemon.mjs", + QoderCLIPath: "/usr/lib/qodercli.js", + }} + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + for i := 0; i < 500; i++ { + starter.SetProxyURL("http://proxy.example:8080") + } + }() + go func() { + defer wg.Done() + for i := 0; i < 500; i++ { + _ = starter.configSnapshot() + } + }() + wg.Wait() +} + +func TestReloadProxyURLRestartsOnlyInheritingQoder(t *testing.T) { + ctx := context.Background() + store, err := OpenStore(filepath.Join(t.TempDir(), "qoder.db")) + if err != nil { + t.Fatal(err) + } + defer store.Close() + + inherits, err := store.Create(ctx, CreateAccount{Name: "InheritsGlobal", Enabled: true}) + if err != nil { + t.Fatal(err) + } + overrides, err := store.Create(ctx, CreateAccount{Name: "HasAccountProxy", Enabled: true, ProxyURL: "http://account.example:9090"}) + if err != nil { + t.Fatal(err) + } + workbuddy, err := store.Create(ctx, CreateAccount{Name: "WorkBuddy", Provider: "workbuddy", Enabled: true}) + if err != nil { + t.Fatal(err) + } + trae, err := store.Create(ctx, CreateAccount{Name: "Trae", Provider: "trae", Enabled: true}) + if err != nil { + t.Fatal(err) + } + + starter := &fakeStarter{} + manager := NewManager(ManagerConfig{DataDir: t.TempDir()}, store, starter) + defer manager.Close() + if err := manager.Start(ctx); err != nil { + t.Fatal(err) + } + before := len(starter.accounts) + + if err := manager.ReloadProxyURL(ctx, "http://new-global.example:8080"); err != nil { + t.Fatalf("ReloadProxyURL: %v", err) + } + + restarted := map[string]bool{} + for _, account := range starter.accounts[before:] { + restarted[account.ID] = true + } + if !restarted[inherits.ID] { + t.Fatal("inheriting Qoder account was not restarted") + } + if restarted[overrides.ID] { + t.Fatal("Qoder account with its own proxy was restarted") + } + if restarted[workbuddy.ID] { + t.Fatal("WorkBuddy account was restarted (in-process)") + } + if restarted[trae.ID] { + t.Fatal("Trae account was restarted (in-process)") + } +} + +func TestReloadProxyURLLogsAllFailuresAndContinues(t *testing.T) { + ctx := context.Background() + store, err := OpenStore(filepath.Join(t.TempDir(), "qoder.db")) + if err != nil { + t.Fatal(err) + } + defer store.Close() + + first, err := store.Create(ctx, CreateAccount{Name: "First", Enabled: true}) + if err != nil { + t.Fatal(err) + } + second, err := store.Create(ctx, CreateAccount{Name: "Second", Enabled: true}) + if err != nil { + t.Fatal(err) + } + + starter := &fakeStarter{} + manager := NewManager(ManagerConfig{DataDir: t.TempDir()}, store, starter) + defer manager.Close() + if err := manager.Start(ctx); err != nil { + t.Fatal(err) + } + + // Force the next start attempt to fail; the reload must still attempt the + // remaining accounts and report the failure. + starter.failures = 1 + if err := manager.ReloadProxyURL(ctx, "http://new-global.example:8080"); err == nil { + t.Fatal("expected a joined reload error") + } + + attempted := map[string]bool{} + for _, account := range starter.accounts { + attempted[account.ID] = true + } + if !attempted[first.ID] || !attempted[second.ID] { + t.Fatalf("reload did not attempt every account: %v", attempted) + } +} + +func starterEnvForTest(t *testing.T, starter *ExecStarter, account Account, home string, port int) []string { + t.Helper() + return starterEnvForTestConfig(t, starter.configSnapshot(), account, home, port) +} + +func starterEnvForTestConfig(t *testing.T, config ManagerConfig, account Account, home string, port int) []string { + t.Helper() + env, err := starterEnv(config, account, home, port) + if err != nil { + t.Fatalf("starterEnv: %v", err) + } + return env +} + +func envValue(env []string, key string) string { + for _, entry := range env { + if strings.HasPrefix(entry, key+"=") { + return strings.TrimPrefix(entry, key+"=") + } + } + return "" +} + +func TestReloadProxyURLSkipsUnchangedValue(t *testing.T) { + ctx := context.Background() + store, err := OpenStore(filepath.Join(t.TempDir(), "qoder.db")) + if err != nil { + t.Fatal(err) + } + defer store.Close() + + if _, err := store.Create(ctx, CreateAccount{Name: "Inherits", Enabled: true}); err != nil { + t.Fatal(err) + } + + starter := &fakeStarter{} + manager := NewManager(ManagerConfig{DataDir: t.TempDir(), ProxyURL: "http://global.example:8080"}, store, starter) + defer manager.Close() + if err := manager.Start(ctx); err != nil { + t.Fatal(err) + } + if got := len(starter.accounts); got != 1 { + t.Fatalf("initial starts = %d, want 1", got) + } + + // Same value (module whitespace): no worker restart. + if err := manager.ReloadProxyURL(ctx, " http://global.example:8080 "); err != nil { + t.Fatalf("ReloadProxyURL: %v", err) + } + if got := len(starter.accounts); got != 1 { + t.Fatalf("worker restarted for an unchanged proxy: starts = %d, want 1", got) + } + + // A real change still restarts. + if err := manager.ReloadProxyURL(ctx, "http://other.example:9090"); err != nil { + t.Fatalf("ReloadProxyURL: %v", err) + } + if got := len(starter.accounts); got != 2 { + t.Fatalf("worker not restarted for a changed proxy: starts = %d, want 2", got) + } +} + +func TestSetSecretOrEmptyPersistsClearedValue(t *testing.T) { + ctx := context.Background() + store, err := OpenStore(filepath.Join(t.TempDir(), "qoder.db")) + if err != nil { + t.Fatal(err) + } + defer store.Close() + + if err := store.SetSecretOrEmpty(ctx, "proxy_url", "http://proxy.example:8080"); err != nil { + t.Fatal(err) + } + value, found, err := store.GetSecret(ctx, "proxy_url") + if err != nil || !found || value != "http://proxy.example:8080" { + t.Fatalf("value=%q found=%v err=%v", value, found, err) + } + + // Clearing keeps the row present with an empty value, unlike DeleteSecret. + if err := store.SetSecretOrEmpty(ctx, "proxy_url", " "); err != nil { + t.Fatal(err) + } + value, found, err = store.GetSecret(ctx, "proxy_url") + if err != nil || !found || value != "" { + t.Fatalf("after clear: value=%q found=%v err=%v (want found empty row)", value, found, err) + } + + // SetSecret still rejects empty so unrelated secrets keep their contract. + if err := store.SetSecret(ctx, "other", ""); err == nil { + t.Fatal("SetSecret accepted an empty value") + } +} + +// A failed global-proxy reload must stay retryable with the same value. The +// manager tracks a pending flag so "same value" alone does not short-circuit +// the reload while workers still run on the old proxy. +func TestReloadProxyURLRetriesAfterFailureWithSameValue(t *testing.T) { + ctx := context.Background() + store, err := OpenStore(filepath.Join(t.TempDir(), "qoder.db")) + if err != nil { + t.Fatal(err) + } + defer store.Close() + + if _, err := store.Create(ctx, CreateAccount{Name: "Inherits", Enabled: true}); err != nil { + t.Fatal(err) + } + + starter := &fakeStarter{} + manager := NewManager(ManagerConfig{DataDir: t.TempDir(), ProxyURL: "http://old.example:8080"}, store, starter) + defer manager.Close() + if err := manager.Start(ctx); err != nil { + t.Fatal(err) + } + if got := len(starter.accounts); got != 1 { + t.Fatalf("initial starts = %d, want 1", got) + } + + const newProxy = "http://new.example:9090" + + // Attempt to switch to the new proxy with an already-cancelled context: + // the restart fails, so the reload reports an error. + cancelled, cancel := context.WithCancel(ctx) + cancel() + if err := manager.ReloadProxyURL(cancelled, newProxy); err == nil { + t.Fatal("reload with a cancelled context unexpectedly succeeded") + } + if got := len(starter.accounts); got != 1 { + t.Fatalf("failed reload restarted workers: starts = %d, want 1", got) + } + + // Resubmitting the *same* value with a healthy context must retry and + // restart the inheriting worker. + if err := manager.ReloadProxyURL(ctx, newProxy); err != nil { + t.Fatalf("retry with the same value failed: %v", err) + } + if got := len(starter.accounts); got != 2 { + t.Fatalf("retry did not restart the worker: starts = %d, want 2", got) + } + + // Now that the reload succeeded, an identical value is a genuine no-op. + if err := manager.ReloadProxyURL(ctx, newProxy); err != nil { + t.Fatalf("post-success no-op reload: %v", err) + } + if got := len(starter.accounts); got != 2 { + t.Fatalf("post-success identical value restarted the worker: starts = %d, want 2", got) + } +} diff --git a/internal/accounts/store.go b/internal/accounts/store.go index d490959..b970256 100644 --- a/internal/accounts/store.go +++ b/internal/accounts/store.go @@ -16,6 +16,7 @@ import ( _ "modernc.org/sqlite" "github.com/caigee-cmd/cli2api/internal/providers" + "github.com/caigee-cmd/cli2api/internal/proxy" ) var ErrAccountNotFound = errors.New("account not found") @@ -40,6 +41,7 @@ type Account struct { WorkBuddyAutoCheckin bool `json:"workbuddy_auto_checkin"` // WorkBuddyCheckinTime is the process-local daily check-in time. WorkBuddyCheckinTime string `json:"workbuddy_checkin_time"` + ProxyURL string `json:"-"` // LastCheckin* are display-only WorkBuddy ops results. LastCheckinAt string `json:"last_checkin_at,omitempty"` LastCheckinMsg string `json:"last_checkin_msg,omitempty"` @@ -63,6 +65,7 @@ type CreateAccount struct { DropSystemPrompt *bool WorkBuddyAutoCheckin *bool WorkBuddyCheckinTime string + ProxyURL string } type UpdateAccount struct { @@ -73,6 +76,7 @@ type UpdateAccount struct { DropSystemPrompt *bool WorkBuddyAutoCheckin *bool WorkBuddyCheckinTime *string + ProxyURL *string } type NativeCredential struct { @@ -120,6 +124,27 @@ func (s *Store) migrate(ctx context.Context) error { return s.runMigrations(ctx) } +// validateAccountProxy enforces the per-provider proxy boundary. Child-process +// providers (Qoder) can only forward every cloud request through an http(s) +// proxy, so SOCKS is rejected for them; in-process providers (WorkBuddy, Trae) +// may use any scheme Parse accepts. +func validateAccountProxy(providerID, region, raw string) error { + descriptor, _, err := providers.Resolve(providerID, region) + if err != nil { + return err + } + + if descriptor.Runtime == providers.RuntimeChildProcess { + if err := proxy.ValidateHTTPOnly(raw); err != nil { + return fmt.Errorf("Qoder account proxy: %w", err) + } + return nil + } + + _, err = proxy.Parse(raw) + return err +} + func (s *Store) Create(ctx context.Context, input CreateAccount) (Account, error) { name := strings.TrimSpace(input.Name) if name == "" { @@ -129,6 +154,9 @@ func (s *Store) Create(ctx context.Context, input CreateAccount) (Account, error if err != nil { return Account{}, err } + if err := validateAccountProxy(input.Provider, input.Region, input.ProxyURL); err != nil { + return Account{}, err + } maxInFlight := input.MaxInFlight if maxInFlight <= 0 { maxInFlight = 4 @@ -162,6 +190,7 @@ func (s *Store) Create(ctx context.Context, input CreateAccount) (Account, error DropSystemPrompt: dropSystemPrompt, WorkBuddyAutoCheckin: autoCheckin, WorkBuddyCheckinTime: checkinTime, + ProxyURL: strings.TrimSpace(input.ProxyURL), Status: "offline", CreatedAt: now, UpdatedAt: now, @@ -169,11 +198,11 @@ func (s *Store) Create(ctx context.Context, input CreateAccount) (Account, error _, err = s.db.ExecContext(ctx, ` INSERT INTO accounts ( id, name, provider, provider_region, auth_type, enabled, max_inflight, priority, drop_system_prompt, - workbuddy_auto_checkin, workbuddy_checkin_time, status, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + workbuddy_auto_checkin, workbuddy_checkin_time, proxy_url, status, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, account.ID, account.Name, account.Provider, account.ProviderRegion, account.AuthType, account.Enabled, account.MaxInFlight, account.Priority, account.DropSystemPrompt, - account.WorkBuddyAutoCheckin, account.WorkBuddyCheckinTime, account.Status, + account.WorkBuddyAutoCheckin, account.WorkBuddyCheckinTime, account.ProxyURL, account.Status, formatTime(account.CreatedAt), formatTime(account.UpdatedAt), ) if err != nil { @@ -185,7 +214,7 @@ func (s *Store) Create(ctx context.Context, input CreateAccount) (Account, error func (s *Store) Get(ctx context.Context, id string) (Account, error) { row := s.db.QueryRowContext(ctx, ` SELECT id, name, provider, provider_region, remote_uid, auth_type, enabled, max_inflight, priority, - drop_system_prompt, workbuddy_auto_checkin, workbuddy_checkin_time, last_checkin_at, last_checkin_msg, last_checkin_status, + drop_system_prompt, workbuddy_auto_checkin, workbuddy_checkin_time, proxy_url, last_checkin_at, last_checkin_msg, last_checkin_status, status, last_error, last_error_kind, cooldown_until, quota_json, created_at, updated_at FROM accounts WHERE id = ?`, strings.TrimSpace(id)) account, err := scanAccount(row) @@ -208,7 +237,7 @@ func scanAccount(row rowScanner) (Account, error) { err := row.Scan( &account.ID, &account.Name, &account.Provider, &account.ProviderRegion, &account.RemoteUID, &account.AuthType, &account.Enabled, &account.MaxInFlight, &account.Priority, - &account.DropSystemPrompt, &account.WorkBuddyAutoCheckin, &account.WorkBuddyCheckinTime, &account.LastCheckinAt, &account.LastCheckinMsg, &account.LastCheckinStatus, + &account.DropSystemPrompt, &account.WorkBuddyAutoCheckin, &account.WorkBuddyCheckinTime, &account.ProxyURL, &account.LastCheckinAt, &account.LastCheckinMsg, &account.LastCheckinStatus, &account.Status, &account.LastError, &account.LastErrorKind, &cooldown, "aJSON, &created, &updated, ) if err != nil { @@ -258,7 +287,7 @@ func parseTime(value string) time.Time { func (s *Store) List(ctx context.Context) ([]Account, error) { rows, err := s.db.QueryContext(ctx, ` SELECT id, name, provider, provider_region, remote_uid, auth_type, enabled, max_inflight, priority, - drop_system_prompt, workbuddy_auto_checkin, workbuddy_checkin_time, last_checkin_at, last_checkin_msg, last_checkin_status, + drop_system_prompt, workbuddy_auto_checkin, workbuddy_checkin_time, proxy_url, last_checkin_at, last_checkin_msg, last_checkin_status, status, last_error, last_error_kind, cooldown_until, quota_json, created_at, updated_at FROM accounts ORDER BY created_at, id`) if err != nil { @@ -305,12 +334,19 @@ func (s *Store) Update(ctx context.Context, id string, input UpdateAccount) erro return err } } + if input.ProxyURL != nil { + proxyURL := proxy.Preserve(account.ProxyURL, *input.ProxyURL) + if err := validateAccountProxy(account.Provider, account.ProviderRegion, proxyURL); err != nil { + return err + } + account.ProxyURL = proxyURL + } account.UpdatedAt = time.Now().UTC() result, err := s.db.ExecContext(ctx, ` UPDATE accounts SET name = ?, enabled = ?, max_inflight = ?, priority = ?, drop_system_prompt = ?, - workbuddy_auto_checkin = ?, workbuddy_checkin_time = ?, updated_at = ? + workbuddy_auto_checkin = ?, workbuddy_checkin_time = ?, proxy_url = ?, updated_at = ? WHERE id = ?`, account.Name, account.Enabled, account.MaxInFlight, account.Priority, account.DropSystemPrompt, - account.WorkBuddyAutoCheckin, account.WorkBuddyCheckinTime, formatTime(account.UpdatedAt), account.ID) + account.WorkBuddyAutoCheckin, account.WorkBuddyCheckinTime, account.ProxyURL, formatTime(account.UpdatedAt), account.ID) if err != nil { return fmt.Errorf("update account: %w", err) } @@ -533,6 +569,38 @@ ON CONFLICT(name) DO UPDATE SET value=excluded.value, updated_at=excluded.update return nil } +// SetSecretOrEmpty stores a secret, allowing an explicitly empty value to +// persist. Use it when "cleared" is a meaningful state that must be +// distinguished from "never configured" (for example the global proxy, where +// an absent row triggers first-run bootstrap from the environment). +func (s *Store) SetSecretOrEmpty(ctx context.Context, name, value string) error { + name = strings.TrimSpace(name) + value = strings.TrimSpace(value) + if name == "" { + return fmt.Errorf("secret name required") + } + now := formatTime(time.Now().UTC()) + _, err := s.db.ExecContext(ctx, ` +INSERT INTO app_secrets (name, value, created_at, updated_at) VALUES (?, ?, ?, ?) +ON CONFLICT(name) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at`, + name, value, now, now) + if err != nil { + return fmt.Errorf("save secret: %w", err) + } + return nil +} + +func (s *Store) DeleteSecret(ctx context.Context, name string) error { + name = strings.TrimSpace(name) + if name == "" { + return fmt.Errorf("secret name required") + } + if _, err := s.db.ExecContext(ctx, `DELETE FROM app_secrets WHERE name = ?`, name); err != nil { + return fmt.Errorf("delete secret: %w", err) + } + return nil +} + func (s *Store) SaveCredential(ctx context.Context, accountID, authType string, credential NativeCredential) error { if len(credential.UserBlob) == 0 || strings.TrimSpace(credential.MachineID) == "" { return fmt.Errorf("native credential requires user blob and machine id") diff --git a/internal/api/accounts.go b/internal/api/accounts.go index 83ce670..53678f7 100644 --- a/internal/api/accounts.go +++ b/internal/api/accounts.go @@ -44,6 +44,7 @@ func (s *Server) handleAccounts(w http.ResponseWriter, r *http.Request) { DropSystemPrompt *bool `json:"drop_system_prompt"` WorkBuddyAutoCheckin *bool `json:"workbuddy_auto_checkin"` WorkBuddyCheckinTime string `json:"workbuddy_checkin_time"` + ProxyURL string `json:"proxy_url"` } if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeErr(w, http.StatusBadRequest, "invalid_request", err.Error()) @@ -57,7 +58,7 @@ func (s *Server) handleAccounts(w http.ResponseWriter, r *http.Request) { Name: input.Name, Provider: input.Provider, Region: input.Region, Enabled: input.Enabled, MaxInFlight: input.MaxInFlight, Priority: input.Priority, DropSystemPrompt: input.DropSystemPrompt, WorkBuddyAutoCheckin: input.WorkBuddyAutoCheckin, - WorkBuddyCheckinTime: input.WorkBuddyCheckinTime, + WorkBuddyCheckinTime: input.WorkBuddyCheckinTime, ProxyURL: input.ProxyURL, }) if err != nil { writeErr(w, http.StatusBadRequest, "account_create_failed", err.Error()) @@ -90,6 +91,7 @@ func (s *Server) handleAccountImport(w http.ResponseWriter, r *http.Request) { DropSystemPrompt *bool `json:"drop_system_prompt"` WorkBuddyAutoCheckin *bool `json:"workbuddy_auto_checkin"` WorkBuddyCheckinTime string `json:"workbuddy_checkin_time"` + ProxyURL string `json:"proxy_url"` UserBlob string `json:"user_blob"` MachineID string `json:"machine_id"` Credential json.RawMessage `json:"credential"` @@ -108,7 +110,7 @@ func (s *Server) handleAccountImport(w http.ResponseWriter, r *http.Request) { account, err := s.manager.Import(r.Context(), accounts.ImportAccount{ Name: input.Name, Provider: input.Provider, Region: input.Region, Enabled: input.Enabled, MaxInFlight: input.MaxInFlight, Priority: input.Priority, DropSystemPrompt: input.DropSystemPrompt, - WorkBuddyAutoCheckin: input.WorkBuddyAutoCheckin, WorkBuddyCheckinTime: input.WorkBuddyCheckinTime, + WorkBuddyAutoCheckin: input.WorkBuddyAutoCheckin, WorkBuddyCheckinTime: input.WorkBuddyCheckinTime, ProxyURL: input.ProxyURL, Credential: accounts.NativeCredential{UserBlob: blob, MachineID: input.MachineID}, }) if err != nil { @@ -140,7 +142,7 @@ func (s *Server) handleAccountImport(w http.ResponseWriter, r *http.Request) { Name: input.Name, Provider: "trae", Region: input.Region, Enabled: false, MaxInFlight: input.MaxInFlight, Priority: input.Priority, DropSystemPrompt: input.DropSystemPrompt, WorkBuddyAutoCheckin: input.WorkBuddyAutoCheckin, - WorkBuddyCheckinTime: input.WorkBuddyCheckinTime, + WorkBuddyCheckinTime: input.WorkBuddyCheckinTime, ProxyURL: input.ProxyURL, }) if err != nil { writeErr(w, http.StatusBadRequest, "account_import_failed", err.Error()) @@ -177,7 +179,7 @@ func (s *Server) handleAccountImport(w http.ResponseWriter, r *http.Request) { Name: input.Name, Provider: "workbuddy", Region: input.Region, Enabled: false, MaxInFlight: input.MaxInFlight, Priority: input.Priority, DropSystemPrompt: input.DropSystemPrompt, WorkBuddyAutoCheckin: input.WorkBuddyAutoCheckin, - WorkBuddyCheckinTime: input.WorkBuddyCheckinTime, + WorkBuddyCheckinTime: input.WorkBuddyCheckinTime, ProxyURL: input.ProxyURL, }) if err != nil { writeErr(w, http.StatusBadRequest, "account_import_failed", err.Error()) @@ -214,7 +216,7 @@ func (s *Server) handleAccountByID(w http.ResponseWriter, r *http.Request) { if len(parts) == 1 { switch r.Method { case http.MethodGet: - account, err := s.manager.Store().Get(r.Context(), accountID) + account, err := s.manager.AccountView(r.Context(), accountID) if err != nil { writeErr(w, http.StatusNotFound, "account_not_found", err.Error()) return @@ -229,6 +231,7 @@ func (s *Server) handleAccountByID(w http.ResponseWriter, r *http.Request) { DropSystemPrompt *bool `json:"drop_system_prompt"` WorkBuddyAutoCheckin *bool `json:"workbuddy_auto_checkin"` WorkBuddyCheckinTime *string `json:"workbuddy_checkin_time"` + ProxyURL *string `json:"proxy_url"` } if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeErr(w, http.StatusBadRequest, "invalid_request", err.Error()) @@ -237,7 +240,7 @@ func (s *Server) handleAccountByID(w http.ResponseWriter, r *http.Request) { err := s.manager.Update(r.Context(), accountID, accounts.UpdateAccount{ Name: input.Name, Enabled: input.Enabled, MaxInFlight: input.MaxInFlight, Priority: input.Priority, DropSystemPrompt: input.DropSystemPrompt, WorkBuddyAutoCheckin: input.WorkBuddyAutoCheckin, - WorkBuddyCheckinTime: input.WorkBuddyCheckinTime, + WorkBuddyCheckinTime: input.WorkBuddyCheckinTime, ProxyURL: input.ProxyURL, }) if err != nil { writeErr(w, http.StatusBadRequest, "account_update_failed", err.Error()) diff --git a/internal/api/auth_test.go b/internal/api/auth_test.go index aff41db..bd72f71 100644 --- a/internal/api/auth_test.go +++ b/internal/api/auth_test.go @@ -105,12 +105,14 @@ func TestOpenAIEndpointsAllowCORSPreflightWithoutAPIKey(t *testing.T) { t.Fatalf("unauthenticated chat missing CORS headers: %v", chatRec.Header()) } - management := httptest.NewRequest(http.MethodOptions, "/api/chat", nil) - management.Header.Set("Origin", "chrome-extension://example") - managementRec := httptest.NewRecorder() - srv.Handler().ServeHTTP(managementRec, management) - if managementRec.Code != http.StatusUnauthorized { - t.Fatalf("management OPTIONS: got %d want 401", managementRec.Code) + for _, path := range []string{"/api/chat", "/api/keys", "/api/accounts"} { + management := httptest.NewRequest(http.MethodOptions, path, nil) + management.Header.Set("Origin", "chrome-extension://example") + managementRec := httptest.NewRecorder() + srv.Handler().ServeHTTP(managementRec, management) + if managementRec.Code != http.StatusUnauthorized { + t.Fatalf("management OPTIONS %s: got %d want 401", path, managementRec.Code) + } } } diff --git a/internal/api/server.go b/internal/api/server.go index defd87c..f7015f0 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -74,6 +74,10 @@ func New(cfg config.Config) *Server { if initialized { log.Printf("[security] initialized API key and stored it in SQLite: %s", proxyAPIKey) } + proxyURL, err := ensureProxyURL(context.Background(), store, cfg.ProxyURL) + if err != nil { + panic(err) + } crossProviderModelPool, err := ensureCrossProviderModelPool(context.Background(), store) if err != nil { panic(err) @@ -92,7 +96,7 @@ func New(cfg config.Config) *Server { manager := accounts.NewManager(accounts.ManagerConfig{ DataDir: runtimeDir, BasePort: cfg.WorkerBasePort, NodeBinary: cfg.NodeBinary, DaemonPath: cfg.WorkerDaemonPath, QoderCLIPath: cfg.QoderCLIPath, QoderCNCLIPath: cfg.QoderCNCLIPath, - TemplatePath: cfg.PlainTemplatePath, ProxyAPIKey: proxyAPIKey, + TemplatePath: cfg.PlainTemplatePath, ProxyAPIKey: proxyAPIKey, ProxyURL: proxyURL, MaxLogWriters: io.MultiWriter(os.Stderr, ring), }, store, nil) if err := manager.Start(context.Background()); err != nil { diff --git a/internal/api/system_settings.go b/internal/api/system_settings.go index b4347d1..8553f7a 100644 --- a/internal/api/system_settings.go +++ b/internal/api/system_settings.go @@ -9,19 +9,46 @@ import ( "github.com/caigee-cmd/cli2api/internal/accounts" "github.com/caigee-cmd/cli2api/internal/executor" + "github.com/caigee-cmd/cli2api/internal/proxy" ) const ( crossProviderModelPoolSecret = "cross_provider_model_pool" routingStrategySecret = "routing_strategy" + proxyURLSecret = "proxy_url" ) type systemSettings struct { CrossProviderModelPool bool `json:"cross_provider_model_pool"` RoutingStrategy string `json:"routing_strategy"` + ProxyURL string `json:"proxy_url"` SessionAffinity executor.SessionAffinityStats `json:"session_affinity"` } +func ensureProxyURL(ctx context.Context, store *accounts.Store, bootstrap string) (string, error) { + value, ok, err := store.GetSecret(ctx, proxyURLSecret) + if err != nil { + return "", err + } + if !ok { + value = strings.TrimSpace(bootstrap) + if value != "" { + if err := proxy.ValidateHTTPOnly(value); err != nil { + return "", fmt.Errorf("invalid %s setting: %w", proxyURLSecret, err) + } + } + if value != "" { + if err := store.SetSecret(ctx, proxyURLSecret, value); err != nil { + return "", fmt.Errorf("initialize system settings: %w", err) + } + } + } + if err := proxy.ValidateHTTPOnly(value); err != nil { + return "", fmt.Errorf("invalid %s setting: %w", proxyURLSecret, err) + } + return strings.TrimSpace(value), nil +} + func ensureCrossProviderModelPool(ctx context.Context, store *accounts.Store) (bool, error) { value, ok, err := store.GetSecret(ctx, crossProviderModelPoolSecret) if err != nil { @@ -56,9 +83,11 @@ func ensureRoutingStrategy(ctx context.Context, store *accounts.Store) (string, } func (s *Server) currentSystemSettings() systemSettings { + proxyURL, _, _ := s.manager.Store().GetSecret(context.Background(), proxyURLSecret) return systemSettings{ CrossProviderModelPool: s.crossProviderModelPool.Load(), RoutingStrategy: s.pool.RoutingStrategy(), + ProxyURL: proxy.Redact(proxyURL), SessionAffinity: s.executor.SessionAffinity.Stats(), } } @@ -82,12 +111,13 @@ func (s *Server) handleSystemSettings(w http.ResponseWriter, r *http.Request) { var input struct { CrossProviderModelPool *bool `json:"cross_provider_model_pool"` RoutingStrategy *string `json:"routing_strategy"` + ProxyURL *string `json:"proxy_url"` } if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeErr(w, http.StatusBadRequest, "invalid_request", err.Error()) return } - if input.CrossProviderModelPool == nil && input.RoutingStrategy == nil { + if input.CrossProviderModelPool == nil && input.RoutingStrategy == nil && input.ProxyURL == nil { writeErr(w, http.StatusBadRequest, "invalid_request", "a system setting is required") return } @@ -103,6 +133,43 @@ func (s *Server) handleSystemSettings(w http.ResponseWriter, r *http.Request) { s.settingsMu.Lock() defer s.settingsMu.Unlock() + if input.ProxyURL != nil { + // Read the persisted value, resolve redacted re-submits, validate, + // and compare inside the same critical section that saves and + // reloads. Doing the read outside the lock would let two concurrent + // PATCHes interleave: a request submitting the old value could + // compute proxyChanged against a stale read and then skip the write + // while switching the runtime back to the old proxy, leaving the + // database and the running workers disagreeing. + existing, _, err := s.manager.Store().GetSecret(r.Context(), proxyURLSecret) + if err != nil { + writeErr(w, http.StatusInternalServerError, "system_settings_read_failed", err.Error()) + return + } + proxyURL := proxy.Preserve(existing, *input.ProxyURL) + if err := proxy.ValidateHTTPOnly(proxyURL); err != nil { + writeErr(w, http.StatusBadRequest, "invalid_proxy_url", err.Error()) + return + } + proxyChanged := proxyURL != strings.TrimSpace(existing) + + // Persist clears as an explicit empty value (not a delete) so the + // next boot distinguishes "user cleared it" from "never set" and + // does not re-apply the environment bootstrap. An unchanged value + // skips the write, but we still call ReloadProxyURL: whether the + // workers actually need restarting is the Manager's call, which + // knows if a previous reload failed. + if proxyChanged { + if err := s.manager.Store().SetSecretOrEmpty(r.Context(), proxyURLSecret, proxyURL); err != nil { + writeErr(w, http.StatusInternalServerError, "system_settings_save_failed", err.Error()) + return + } + } + if err := s.manager.ReloadProxyURL(r.Context(), proxyURL); err != nil { + writeErr(w, http.StatusInternalServerError, "proxy_reload_failed", err.Error()) + return + } + } if input.CrossProviderModelPool != nil { enabled := *input.CrossProviderModelPool value := "0" diff --git a/internal/api/system_settings_test.go b/internal/api/system_settings_test.go index 3ab2a51..ece3b36 100644 --- a/internal/api/system_settings_test.go +++ b/internal/api/system_settings_test.go @@ -7,6 +7,7 @@ import ( "net/http/httptest" "path/filepath" "testing" + "time" "github.com/caigee-cmd/cli2api/internal/accounts" "github.com/caigee-cmd/cli2api/internal/config" @@ -123,3 +124,232 @@ func TestChatRejectsBareModelWhenCrossProviderPoolDisabled(t *testing.T) { t.Fatalf("bare model response: %d %s", response.Code, response.Body.String()) } } + +func TestEnsureProxyURLRejectsSOCKSBootstrap(t *testing.T) { + store, err := accounts.OpenStore(filepath.Join(t.TempDir(), "qoder.db")) + if err != nil { + t.Fatal(err) + } + defer store.Close() + + if _, err := ensureProxyURL(context.Background(), store, "socks5://proxy.example:1080"); err == nil { + t.Fatal("SOCKS bootstrap was accepted for the global proxy") + } + if _, ok, err := store.GetSecret(context.Background(), proxyURLSecret); err != nil || ok { + t.Fatalf("rejected bootstrap was persisted: ok=%v err=%v", ok, err) + } +} + +func TestEnsureProxyURLAcceptsHTTPBootstrap(t *testing.T) { + store, err := accounts.OpenStore(filepath.Join(t.TempDir(), "qoder.db")) + if err != nil { + t.Fatal(err) + } + defer store.Close() + + value, err := ensureProxyURL(context.Background(), store, "http://proxy.example:8080") + if err != nil || value != "http://proxy.example:8080" { + t.Fatalf("value=%q err=%v", value, err) + } + stored, ok, err := store.GetSecret(context.Background(), proxyURLSecret) + if err != nil || !ok || stored != "http://proxy.example:8080" { + t.Fatalf("stored=%q ok=%v err=%v", stored, ok, err) + } +} + +func TestSystemSettingsProxyURLRejectsSOCKS(t *testing.T) { + srv := New(config.Config{ + Host: "127.0.0.1", Port: 3010, ProxyAPIKey: "secret", + QoderHome: t.TempDir(), DataDir: t.TempDir(), + }) + defer srv.Close() + + request := httptest.NewRequest(http.MethodPatch, "/api/system/settings", bytes.NewBufferString(`{"proxy_url":"socks5://proxy.example:1080"}`)) + request.Header.Set("Authorization", "Bearer secret") + response := httptest.NewRecorder() + srv.Handler().ServeHTTP(response, request) + if response.Code != http.StatusBadRequest { + t.Fatalf("SOCKS global proxy response: %d %s", response.Code, response.Body.String()) + } + if _, ok, err := srv.manager.Store().GetSecret(context.Background(), proxyURLSecret); err != nil || ok { + t.Fatalf("rejected global proxy was persisted: ok=%v err=%v", ok, err) + } +} + +func TestSystemSettingsProxyURLAcceptsHTTP(t *testing.T) { + srv := New(config.Config{ + Host: "127.0.0.1", Port: 3010, ProxyAPIKey: "secret", + QoderHome: t.TempDir(), DataDir: t.TempDir(), + }) + defer srv.Close() + + request := httptest.NewRequest(http.MethodPatch, "/api/system/settings", bytes.NewBufferString(`{"proxy_url":"http://proxy.example:8080"}`)) + request.Header.Set("Authorization", "Bearer secret") + response := httptest.NewRecorder() + srv.Handler().ServeHTTP(response, request) + if response.Code != http.StatusOK { + t.Fatalf("HTTP global proxy response: %d %s", response.Code, response.Body.String()) + } + stored, ok, err := srv.manager.Store().GetSecret(context.Background(), proxyURLSecret) + if err != nil || !ok || stored != "http://proxy.example:8080" { + t.Fatalf("stored=%q ok=%v err=%v", stored, ok, err) + } +} + +// A failed global-proxy reload must stay retryable. The database already holds +// the new value after the first attempt, so repeating the PATCH with the *same* +// value must still attempt the reload (and keep reporting the failure) instead +// of being short-circuited as a no-op. +func TestSystemSettingsProxyURLRetriesFailedReloadWithSameValue(t *testing.T) { + srv := New(config.Config{ + Host: "127.0.0.1", Port: 3010, ProxyAPIKey: "secret", + QoderHome: t.TempDir(), DataDir: t.TempDir(), + }) + defer srv.Close() + + // An enabled inheriting Qoder account; with no daemon path configured its + // worker cannot start, so every reload attempt fails deterministically. + if _, err := srv.manager.Store().Create(context.Background(), accounts.CreateAccount{Name: "Inherits", Enabled: true}); err != nil { + t.Fatal(err) + } + patch := func(body string) *httptest.ResponseRecorder { + req := httptest.NewRequest(http.MethodPatch, "/api/system/settings", bytes.NewBufferString(body)) + req.Header.Set("Authorization", "Bearer secret") + rec := httptest.NewRecorder() + srv.Handler().ServeHTTP(rec, req) + return rec + } + const newProxy = "http://new-global.example:8080" + + first := patch(`{"proxy_url":"` + newProxy + `"}`) + if first.Code != http.StatusInternalServerError { + t.Fatalf("first reload response: %d %s", first.Code, first.Body.String()) + } + stored, ok, err := srv.manager.Store().GetSecret(context.Background(), proxyURLSecret) + if err != nil || !ok || stored != newProxy { + t.Fatalf("stored after failed reload: %q ok=%v err=%v", stored, ok, err) + } + + // Identical value: the write is skipped, but the reload is retried. + second := patch(`{"proxy_url":"` + newProxy + `"}`) + if second.Code != http.StatusInternalServerError { + t.Fatalf("retry was treated as a no-op: %d %s", second.Code, second.Body.String()) + } + if !bytes.Contains(second.Body.Bytes(), []byte("proxy_reload_failed")) { + t.Fatalf("retry body = %s", second.Body.String()) + } +} + +func TestSystemSettingsProxyURLClearPersistsEmptyValue(t *testing.T) { + srv := New(config.Config{ + Host: "127.0.0.1", Port: 3010, ProxyAPIKey: "secret", + QoderHome: t.TempDir(), DataDir: t.TempDir(), + }) + defer srv.Close() + + if err := srv.manager.Store().SetSecret(context.Background(), proxyURLSecret, "http://proxy.example:8080"); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest(http.MethodPatch, "/api/system/settings", bytes.NewBufferString(`{"proxy_url":""}`)) + req.Header.Set("Authorization", "Bearer secret") + rec := httptest.NewRecorder() + srv.Handler().ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("clear response: %d %s", rec.Code, rec.Body.String()) + } + + // The row must remain with an empty value so a restart does not treat the + // clear as "never configured" and re-apply the environment bootstrap. + stored, ok, err := srv.manager.Store().GetSecret(context.Background(), proxyURLSecret) + if err != nil || !ok || stored != "" { + t.Fatalf("cleared proxy: stored=%q ok=%v err=%v (want present empty row)", stored, ok, err) + } +} + +func TestEnsureProxyURLDoesNotReapplyBootstrapAfterClear(t *testing.T) { + ctx := context.Background() + store, err := accounts.OpenStore(filepath.Join(t.TempDir(), "qoder.db")) + if err != nil { + t.Fatal(err) + } + defer store.Close() + + // Simulate a user clearing the proxy: the row exists with an empty value. + if err := store.SetSecretOrEmpty(ctx, proxyURLSecret, ""); err != nil { + t.Fatal(err) + } + + value, err := ensureProxyURL(ctx, store, "http://env-bootstrap.example:8080") + if err != nil { + t.Fatalf("ensureProxyURL: %v", err) + } + if value != "" { + t.Fatalf("cleared proxy was re-bootstrapped from the environment: %q", value) + } +} + +// Two concurrent proxy saves must not leave SQLite and the running workers +// disagreeing. The read of the persisted value, Preserve, validation, and the +// change comparison must all run inside the same settingsMu critical section +// that saves and reloads. This test holds settingsMu, fires a PATCH that +// submits the *old* value, changes the stored value underneath it, then +// releases the lock. With the read outside the lock the request would see the +// stale old value, skip its write, and reload the runtime to old.example while +// the database holds new.example. Correct behavior re-reads inside the lock, +// notices the difference, and persists its own value so the two agree. +func TestSystemSettingsProxyURLReadIsInsideSettingsLock(t *testing.T) { + srv := New(config.Config{ + Host: "127.0.0.1", Port: 3010, ProxyAPIKey: "secret", + QoderHome: t.TempDir(), DataDir: t.TempDir(), + }) + defer srv.Close() + ctx := context.Background() + + const oldProxy = "http://old.example:8080" + const newProxy = "http://new.example:9090" + if err := srv.manager.Store().SetSecret(ctx, proxyURLSecret, oldProxy); err != nil { + t.Fatal(err) + } + + // Take the settings lock so the PATCH cannot enter the critical section. + srv.settingsMu.Lock() + + type result struct { + code int + body string + } + done := make(chan result, 1) + go func() { + req := httptest.NewRequest(http.MethodPatch, "/api/system/settings", bytes.NewBufferString(`{"proxy_url":"`+oldProxy+`"}`)) + req.Header.Set("Authorization", "Bearer secret") + rec := httptest.NewRecorder() + srv.Handler().ServeHTTP(rec, req) + done <- result{code: rec.Code, body: rec.Body.String()} + }() + + // Give the goroutine time to reach (and block on) the settings lock, then + // change the stored value as a concurrent request would. + time.Sleep(100 * time.Millisecond) + if err := srv.manager.Store().SetSecret(ctx, proxyURLSecret, newProxy); err != nil { + t.Fatal(err) + } + srv.settingsMu.Unlock() + + res := <-done + if res.code != http.StatusOK { + t.Fatalf("patch response: %d %s", res.code, res.body) + } + + // The request submitted old.example while the row held new.example, so it + // must have persisted old.example. If it had compared against a stale read + // taken before the lock, it would have skipped the write and left + // new.example on disk while pointing the runtime at old.example. + stored, ok, err := srv.manager.Store().GetSecret(ctx, proxyURLSecret) + if err != nil || !ok { + t.Fatalf("stored proxy missing: ok=%v err=%v", ok, err) + } + if stored != oldProxy { + t.Fatalf("database/runtime split: database=%q, the request's runtime value=%q", stored, oldProxy) + } +} diff --git a/internal/api/workerproxy.go b/internal/api/workerproxy.go index afe9cde..a44cf25 100644 --- a/internal/api/workerproxy.go +++ b/internal/api/workerproxy.go @@ -242,7 +242,17 @@ func (s *Server) fetchProviderModels(refresh bool, accountID string) ([]map[stri } sawAny = true for _, model := range models { - key := model.NativeModel + "@" + item.Provider + // Dedup on the public model ID (what clients request and what the + // entry exposes as "id"), not the upstream native ID. Two entries + // may legitimately share a native model — e.g. a WorkBuddy alias + // where NativeModel=deep-model and PublicModel=deepseek-v4.1-flash + // alongside the native deep-model entry. Keying on the native ID + // would drop the alias from the merged catalog. + publicKey := strings.TrimSpace(model.PublicModel) + if publicKey == "" { + publicKey = strings.TrimSpace(model.NativeModel) + } + key := publicKey + "@" + item.Provider if _, dup := seen[key]; dup { continue } diff --git a/internal/api/workerproxy_test.go b/internal/api/workerproxy_test.go index d8e5ffb..4646687 100644 --- a/internal/api/workerproxy_test.go +++ b/internal/api/workerproxy_test.go @@ -13,6 +13,7 @@ import ( "github.com/caigee-cmd/cli2api/internal/accounts" "github.com/caigee-cmd/cli2api/internal/config" + "github.com/caigee-cmd/cli2api/internal/providers" ) func TestWaitForWorkerAuthManagerRetriesUntilReady(t *testing.T) { @@ -138,3 +139,37 @@ func TestFetchWorkerModelsForNotFoundAccount(t *testing.T) { t.Fatalf("err = %v", err) } } + +// A WorkBuddy alias shares its native model with the base entry but exposes a +// distinct public model ID. Deduping the merged catalog on the native ID would +// drop the alias; keying on the public ID keeps both the native model and the +// alias visible to clients. +func TestFetchProviderModelsKeepsAliasWithSharedNativeModel(t *testing.T) { + srv := New(config.Config{ + Host: "127.0.0.1", Port: 3010, ProxyAPIKey: "secret", + QoderHome: t.TempDir(), DataDir: t.TempDir(), + }) + defer srv.Close() + srv.pool.Upsert(accounts.Item{ID: "wb-cn", Provider: "workbuddy", Runtime: string(providers.RuntimeInProcess)}) + srv.providers.Register(providers.Adapter{ID: "workbuddy", Models: &countingCatalog{models: []providers.ModelInfo{ + {NativeModel: "deep-model", PublicModel: "deep-model", DisplayName: "Deep"}, + {NativeModel: "deep-model", PublicModel: "deepseek-v4.1-flash", DisplayName: "Deepseek-V4.1-Flash"}, + }}}) + + models, err := srv.fetchWorkerModelsFor(false, "wb-cn") + if err != nil { + t.Fatal(err) + } + ids := map[string]bool{} + for _, model := range models { + if id, _ := model["id"].(string); id != "" { + ids[id] = true + } + } + if !ids["deep-model"] { + t.Fatalf("native model missing from catalog: %v", ids) + } + if !ids["deepseek-v4.1-flash"] { + t.Fatalf("alias dropped from catalog: %v", ids) + } +} diff --git a/internal/config/config.go b/internal/config/config.go index 770db07..342ab38 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,6 +10,7 @@ type Config struct { Host string Port int ProxyAPIKey string + ProxyURL string MaxRetryAccounts int QoderHome string DataDir string @@ -72,6 +73,7 @@ func Load() (Config, error) { Host: host, Port: port, ProxyAPIKey: "", + ProxyURL: strings.TrimSpace(os.Getenv("QODER_PROXY_URL")), MaxRetryAccounts: maxRetryAccounts, QoderHome: home, DataDir: dataDir, diff --git a/internal/providers/trae/client.go b/internal/providers/trae/client.go index 41af576..bcdf833 100644 --- a/internal/providers/trae/client.go +++ b/internal/providers/trae/client.go @@ -16,6 +16,7 @@ import ( "github.com/caigee-cmd/cli2api/internal/accounts" "github.com/caigee-cmd/cli2api/internal/providers" + proxyutil "github.com/caigee-cmd/cli2api/internal/proxy" "github.com/caigee-cmd/cli2api/internal/translate" ) @@ -42,6 +43,8 @@ type Client struct { store Store http *http.Client + transports proxyutil.TransportCache + mu sync.Mutex pending map[string]*loginPending listener net.Listener @@ -63,7 +66,57 @@ func NewClient(store Store) *Client { } } -func (c *Client) do(ctx context.Context, method, rawURL string, body []byte, setHeaders func(http.Header)) ([]byte, int, error) { +func (c *Client) globalProxy(ctx context.Context) (string, error) { + store, ok := c.store.(interface { + GetSecret(context.Context, string) (string, bool, error) + }) + if !ok { + return "", nil + } + + value, found, err := store.GetSecret(ctx, "proxy_url") + if err != nil { + return "", fmt.Errorf("load global proxy setting: %w", err) + } + if !found { + return "", nil + } + return strings.TrimSpace(value), nil +} + +func (c *Client) effectiveProxy(ctx context.Context, accountID string) (string, error) { + account, err := c.store.Get(ctx, accountID) + if err != nil { + return "", err + } + + if value := strings.TrimSpace(account.ProxyURL); value != "" { + return value, nil + } + + return c.globalProxy(ctx) +} + +func (c *Client) httpClient(ctx context.Context, accountID string) (*http.Client, error) { + rawProxy, err := c.effectiveProxy(ctx, accountID) + if err != nil { + return nil, err + } + + client := *c.http + + transport, err := c.transports.Get(rawProxy) + if err != nil { + return nil, err + } + if transport != nil { + client.Transport = transport + } + + return &client, nil +} + +func (c *Client) do(ctx context.Context, accountID, method, rawURL string, body []byte, setHeaders func(http.Header)) ([]byte, int, error) { var reader io.Reader if body != nil { reader = bytes.NewReader(body) @@ -75,7 +128,11 @@ func (c *Client) do(ctx context.Context, method, rawURL string, body []byte, set if setHeaders != nil { setHeaders(req.Header) } - resp, err := c.http.Do(req) + client, err := c.httpClient(ctx, accountID) + if err != nil { + return nil, 0, err + } + resp, err := client.Do(req) if err != nil { return nil, 0, err } @@ -200,14 +257,14 @@ func (c *Client) CompleteLogin(ctx context.Context, accountID, callbackURL strin func (c *Client) finishCredential(ctx context.Context, accountID string, credential Credential) error { if strings.TrimSpace(credential.RefreshToken) != "" { - refreshed, err := c.ExchangeToken(ctx, credential) + refreshed, err := c.ExchangeToken(ctx, accountID, credential) if err != nil { return err } credential = refreshed } if strings.TrimSpace(credential.UID) == "" && strings.TrimSpace(credential.AccessToken) != "" { - info, err := c.GetUserInfo(ctx, credential) + info, err := c.GetUserInfo(ctx, accountID, credential) if err == nil { credential.UID = info.UID if info.Nickname != "" { @@ -384,7 +441,7 @@ func BuildLoginURL(machineID, deviceID, callbackURL, trace string) string { return ConsoleHost + pathAuthorization + "?" + values.Encode() } -func (c *Client) ExchangeToken(ctx context.Context, credential Credential) (Credential, error) { +func (c *Client) ExchangeToken(ctx context.Context, accountID string, credential Credential) (Credential, error) { if strings.TrimSpace(credential.RefreshToken) == "" { return credential, fmt.Errorf("no refreshToken") } @@ -397,7 +454,7 @@ func (c *Client) ExchangeToken(ctx context.Context, credential Credential) (Cred if err != nil { return credential, err } - payload, status, err := c.do(ctx, http.MethodPost, credential.AuthBase()+pathExchange, body, SetOAuthHeaders) + payload, status, err := c.do(ctx, accountID, http.MethodPost, credential.AuthBase()+pathExchange, body, SetOAuthHeaders) if err != nil { return credential, err } @@ -440,7 +497,7 @@ type userInfo struct { EnterpriseID string } -func (c *Client) GetUserInfo(ctx context.Context, credential Credential) (userInfo, error) { +func (c *Client) GetUserInfo(ctx context.Context, accountID string, credential Credential) (userInfo, error) { body, err := json.Marshal(map[string]any{ "ReqSource": "IDE", "IDEVersion": IdeVersion, @@ -448,7 +505,7 @@ func (c *Client) GetUserInfo(ctx context.Context, credential Credential) (userIn if err != nil { return userInfo{}, err } - payload, status, err := c.do(ctx, http.MethodPost, credential.AuthBase()+pathUserInfo, body, func(h http.Header) { + payload, status, err := c.do(ctx, accountID, http.MethodPost, credential.AuthBase()+pathUserInfo, body, func(h http.Header) { SetOAuthHeaders(h) if credential.AccessToken != "" { h.Set("X-Cloudide-Token", credential.AccessToken) @@ -494,7 +551,7 @@ func (c *Client) credential(ctx context.Context, accountID string) (Credential, if !credential.needsRefresh(now) { return credential, nil } - refreshed, err := c.ExchangeToken(ctx, credential) + refreshed, err := c.ExchangeToken(ctx, accountID, credential) if err != nil { _ = c.store.Observe(ctx, accountID, credential.UID, "login_required", err.Error(), accounts.KindAuth) return credential, err @@ -528,7 +585,7 @@ func (c *Client) Models(ctx context.Context, accountID string) ([]providers.Mode if err != nil { return nil, err } - payload, status, err := c.do(ctx, http.MethodPost, credential.ChatBase()+pathModels, body, + payload, status, err := c.do(ctx, accountID, http.MethodPost, credential.ChatBase()+pathModels, body, func(h http.Header) { SetCatalogHeaders(h, credential) }) if err != nil { return nil, err @@ -652,7 +709,12 @@ func (c *Client) ChatNonStream(ctx context.Context, accountID string, req transl if err != nil { return providers.ChatOutcome{}, err } - resp, err := c.http.Do(httpReq) + client, err := c.httpClient(ctx, accountID) + if err != nil { + return providers.ChatOutcome{}, err + } + client.Timeout = 0 + resp, err := client.Do(httpReq) if err != nil { return providers.ChatOutcome{}, err } @@ -677,7 +739,10 @@ func (c *Client) ChatStream(ctx context.Context, accountID string, req translate if err != nil { return nil, err } - client := *c.http + client, err := c.httpClient(ctx, accountID) + if err != nil { + return nil, err + } client.Timeout = 0 resp, err := client.Do(httpReq) if err != nil { @@ -844,7 +909,7 @@ func (c *Client) Quota(ctx context.Context, accountID string) (*providers.QuotaI if err != nil { return nil, err } - remain, used, total, err := c.UserEntUsage(ctx, credential) + remain, used, total, err := c.UserEntUsage(ctx, accountID, credential) if err != nil { return nil, err } @@ -875,8 +940,8 @@ func (c *Client) Quota(ctx context.Context, accountID string) (*providers.QuotaI }, nil } -func (c *Client) UserEntUsage(ctx context.Context, credential Credential) (remain, used, total int64, err error) { - body, status, err := c.do(ctx, http.MethodPost, credential.BillingBase()+pathEntUsage, []byte("{}"), +func (c *Client) UserEntUsage(ctx context.Context, accountID string, credential Credential) (remain, used, total int64, err error) { + body, status, err := c.do(ctx, accountID, http.MethodPost, credential.BillingBase()+pathEntUsage, []byte("{}"), func(h http.Header) { SetUgHeaders(h, credential) }) if err != nil { return 0, 0, 0, err @@ -980,8 +1045,8 @@ func int64FromAny(value any) int64 { } } -func (c *Client) CheckinStatus(ctx context.Context, credential Credential) ([]byte, error) { - body, status, err := c.do(ctx, http.MethodPost, credential.BillingBase()+pathCheckinStatus, []byte("{}"), +func (c *Client) CheckinStatus(ctx context.Context, accountID string, credential Credential) ([]byte, error) { + body, status, err := c.do(ctx, accountID, http.MethodPost, credential.BillingBase()+pathCheckinStatus, []byte("{}"), func(h http.Header) { SetUgHeaders(h, credential) }) if err != nil { return nil, err @@ -992,8 +1057,8 @@ func (c *Client) CheckinStatus(ctx context.Context, credential Credential) ([]by return body, nil } -func (c *Client) CheckinClaim(ctx context.Context, credential Credential) ([]byte, error) { - body, status, err := c.do(ctx, http.MethodPost, credential.BillingBase()+pathCheckinClaim, []byte("{}"), +func (c *Client) CheckinClaim(ctx context.Context, accountID string, credential Credential) ([]byte, error) { + body, status, err := c.do(ctx, accountID, http.MethodPost, credential.BillingBase()+pathCheckinClaim, []byte("{}"), func(h http.Header) { SetUgHeaders(h, credential) }) if err != nil { return nil, err diff --git a/internal/providers/trae/client_test.go b/internal/providers/trae/client_test.go index 1a8b509..7651b71 100644 --- a/internal/providers/trae/client_test.go +++ b/internal/providers/trae/client_test.go @@ -21,6 +21,12 @@ type memStore struct { region string settings map[string]accounts.ProviderModelSetting lookups []string + + accountProxyURL string + secretValue string + secretFound bool + secretErr error + secretCalls int } func (s *memStore) Get(ctx context.Context, id string) (accounts.Account, error) { @@ -28,7 +34,14 @@ func (s *memStore) Get(ctx context.Context, id string) (accounts.Account, error) if region == "" { region = "cn" } - return accounts.Account{ID: id, Provider: "trae", ProviderRegion: region}, nil + return accounts.Account{ID: id, Provider: "trae", ProviderRegion: region, ProxyURL: s.accountProxyURL}, nil +} +func (s *memStore) GetSecret(ctx context.Context, key string) (string, bool, error) { + s.secretCalls++ + if s.secretErr != nil { + return "", false, s.secretErr + } + return s.secretValue, s.secretFound, nil } func (s *memStore) LoadCredentialPayload(ctx context.Context, accountID string) (string, []byte, error) { payload, ok := s.items[accountID] @@ -707,3 +720,99 @@ func TestParseCallbackReadsRefreshAndUserInfo(t *testing.T) { t.Fatalf("info=%+v err=%v", info, err) } } + +func TestHTTPClientFailsClosedOnSecretError(t *testing.T) { + store := &memStore{secretErr: errors.New("db down")} + client := NewClient(store) + if _, err := client.httpClient(context.Background(), "acc1"); err == nil { + t.Fatal("httpClient succeeded despite a global proxy read error") + } +} + +func TestHTTPClientAccountDirectSkipsGlobalRead(t *testing.T) { + store := &memStore{accountProxyURL: "direct", secretErr: errors.New("db down")} + client := NewClient(store) + if _, err := client.httpClient(context.Background(), "acc1"); err != nil { + t.Fatalf("account direct must not read the global proxy: %v", err) + } + if store.secretCalls != 0 { + t.Fatalf("global proxy was read %d times for an account with a proxy override", store.secretCalls) + } +} + +func TestHTTPClientUsesGlobalProxyWhenAccountIsBlank(t *testing.T) { + store := &memStore{secretValue: "http://global.example:8080", secretFound: true} + client := NewClient(store) + httpClient, err := client.httpClient(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + transport, ok := httpClient.Transport.(*http.Transport) + if !ok || transport == nil { + t.Fatalf("transport = %#v", httpClient.Transport) + } + req, _ := http.NewRequest(http.MethodGet, "https://example.com", nil) + proxyURL, err := transport.Proxy(req) + if err != nil || proxyURL == nil || proxyURL.String() != "http://global.example:8080" { + t.Fatalf("transport proxy = %v err=%v", proxyURL, err) + } +} + +func TestHTTPClientKeepsInjectedTransportWhenUnconfigured(t *testing.T) { + store := &memStore{} + client := NewClient(store) + custom := rewriteTransport{} + client.http.Transport = custom + httpClient, err := client.httpClient(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + if httpClient.Transport != custom { + t.Fatalf("unconfigured client replaced the injected transport: %#v", httpClient.Transport) + } +} + +func TestHTTPClientReusesTransportByProxyURL(t *testing.T) { + store := &memStore{secretValue: "http://global.example:8080", secretFound: true} + client := NewClient(store) + + first, err := client.httpClient(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + second, err := client.httpClient(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + if first.Transport == nil || first.Transport != second.Transport { + t.Fatal("transport was not reused across requests") + } +} + +func TestHTTPClientDifferentProxiesUseDifferentTransports(t *testing.T) { + store := &memStore{secretValue: "http://global.example:8080", secretFound: true} + client := NewClient(store) + + inherited, err := client.httpClient(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + + store.accountProxyURL = "socks5://proxy.example:1080" + overridden, err := client.httpClient(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + if inherited.Transport == overridden.Transport { + t.Fatal("different proxy URLs shared a transport") + } + + store.accountProxyURL = "direct" + direct, err := client.httpClient(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + if direct.Transport == inherited.Transport || direct.Transport == overridden.Transport { + t.Fatal("direct did not get its own transport") + } +} diff --git a/internal/providers/workbuddy/catalog.go b/internal/providers/workbuddy/catalog.go index 36943a4..6019dec 100644 --- a/internal/providers/workbuddy/catalog.go +++ b/internal/providers/workbuddy/catalog.go @@ -134,10 +134,15 @@ func containsLevel(options []string, level string) bool { func (c *Client) rememberCatalog(models []providers.ModelInfo) { c.mu.Lock() defer c.mu.Unlock() - c.catalog = make(map[string]providers.ModelInfo, len(models)) + c.catalog = make(map[string]providers.ModelInfo, len(models)*2) for _, model := range models { - c.catalog[model.NativeModel] = model - c.catalog[strings.ToLower(model.NativeModel)] = model + for _, key := range []string{model.NativeModel, model.PublicModel} { + if strings.TrimSpace(key) == "" { + continue + } + c.catalog[key] = model + c.catalog[strings.ToLower(key)] = model + } } } diff --git a/internal/providers/workbuddy/client.go b/internal/providers/workbuddy/client.go index 966c6f0..c8a1fef 100644 --- a/internal/providers/workbuddy/client.go +++ b/internal/providers/workbuddy/client.go @@ -17,6 +17,7 @@ import ( "github.com/caigee-cmd/cli2api/internal/accounts" "github.com/caigee-cmd/cli2api/internal/providers" + proxyutil "github.com/caigee-cmd/cli2api/internal/proxy" "github.com/caigee-cmd/cli2api/internal/translate" ) @@ -33,6 +34,8 @@ type Client struct { store Store http *http.Client + transports proxyutil.TransportCache + mu sync.Mutex loginStates map[string]string catalog map[string]providers.ModelInfo @@ -64,7 +67,57 @@ type envelope struct { Data json.RawMessage `json:"data"` } -func (c *Client) do(ctx context.Context, method, rawURL string, body []byte, setHeaders func(http.Header)) ([]byte, int, error) { +func (c *Client) globalProxy(ctx context.Context) (string, error) { + store, ok := c.store.(interface { + GetSecret(context.Context, string) (string, bool, error) + }) + if !ok { + return "", nil + } + + value, found, err := store.GetSecret(ctx, "proxy_url") + if err != nil { + return "", fmt.Errorf("load global proxy setting: %w", err) + } + if !found { + return "", nil + } + return strings.TrimSpace(value), nil +} + +func (c *Client) effectiveProxy(ctx context.Context, accountID string) (string, error) { + account, err := c.store.Get(ctx, accountID) + if err != nil { + return "", err + } + + if value := strings.TrimSpace(account.ProxyURL); value != "" { + return value, nil + } + + return c.globalProxy(ctx) +} + +func (c *Client) httpClient(ctx context.Context, accountID string) (*http.Client, error) { + rawProxy, err := c.effectiveProxy(ctx, accountID) + if err != nil { + return nil, err + } + + client := *c.http + + transport, err := c.transports.Get(rawProxy) + if err != nil { + return nil, err + } + if transport != nil { + client.Transport = transport + } + + return &client, nil +} + +func (c *Client) do(ctx context.Context, accountID, method, rawURL string, body []byte, setHeaders func(http.Header)) ([]byte, int, error) { var reader io.Reader if body != nil { reader = bytes.NewReader(body) @@ -76,7 +129,11 @@ func (c *Client) do(ctx context.Context, method, rawURL string, body []byte, set if setHeaders != nil { setHeaders(req.Header) } - resp, err := c.http.Do(req) + client, err := c.httpClient(ctx, accountID) + if err != nil { + return nil, 0, err + } + resp, err := client.Do(req) if err != nil { return nil, 0, err } @@ -94,7 +151,7 @@ func (c *Client) StartLogin(ctx context.Context, accountID string) (providers.Lo if account, err := c.store.Get(ctx, accountID); err == nil && account.ProviderRegion == "global" { base = ChatBaseGlobal } - body, status, err := c.do(ctx, http.MethodPost, base+pathAuthState+"?platform=CLI", []byte("{}"), + body, status, err := c.do(ctx, accountID, http.MethodPost, base+pathAuthState+"?platform=CLI", []byte("{}"), func(h http.Header) { setCommonHeaders(h, base == ChatBaseGlobal) }) if err != nil { return providers.LoginSession{}, err @@ -132,7 +189,7 @@ func (c *Client) PollLogin(ctx context.Context, accountID string) (bool, string, if account, err := c.store.Get(ctx, accountID); err == nil && account.ProviderRegion == "global" { base = ChatBaseGlobal } - tokenBody, status, err := c.do(ctx, http.MethodGet, base+pathAuthToken+"?state="+url.QueryEscape(state), nil, + tokenBody, status, err := c.do(ctx, accountID, http.MethodGet, base+pathAuthToken+"?state="+url.QueryEscape(state), nil, func(h http.Header) { setCommonHeaders(h, base == ChatBaseGlobal) }) if err != nil { return false, "", err @@ -154,7 +211,7 @@ func (c *Client) PollLogin(ctx context.Context, accountID string) (bool, string, if err := json.Unmarshal(tokenEnv.Data, &token); err != nil || token.AccessToken == "" { return false, "waiting for authorization", nil } - accountBody, _, err := c.do(ctx, http.MethodGet, base+pathAuthAccount+"?state="+url.QueryEscape(state), nil, + accountBody, _, err := c.do(ctx, accountID, http.MethodGet, base+pathAuthAccount+"?state="+url.QueryEscape(state), nil, func(h http.Header) { setCommonHeaders(h, base == ChatBaseGlobal) h.Set("Authorization", "Bearer "+token.AccessToken) @@ -248,7 +305,7 @@ func (c *Client) resolvedCredential(ctx context.Context, accountID string) (Cred // account by surfacing the auth taxonomy to the manager. func (c *Client) Refresh(ctx context.Context, accountID string, credential Credential) (Credential, error) { credential = c.overlayRegion(ctx, accountID, credential) - body, status, err := c.do(ctx, http.MethodPost, credential.ChatBase()+pathTokenRefresh, []byte("{}"), + body, status, err := c.do(ctx, accountID, http.MethodPost, credential.ChatBase()+pathTokenRefresh, []byte("{}"), func(h http.Header) { SetRefreshHeaders(h, credential) }) if err != nil { return credential, err @@ -304,7 +361,7 @@ func (c *Client) Models(ctx context.Context, accountID string) ([]providers.Mode } ctx, cancel := context.WithTimeout(ctx, catalogTimeout) defer cancel() - body, status, err := c.do(ctx, http.MethodGet, credential.ChatBase()+credential.catalogPath(), nil, + body, status, err := c.do(ctx, accountID, http.MethodGet, credential.ChatBase()+credential.catalogPath(), nil, func(h http.Header) { SetCatalogHeaders(h, credential) }) if err != nil { return nil, err @@ -329,11 +386,26 @@ func (c *Client) Models(ctx context.Context, accountID string) ([]providers.Mode if env.Code != 0 { return nil, fmt.Errorf("models envelope code=%d msg=%s", env.Code, env.Msg) } + cliModels := map[string]struct{}{} + for _, agent := range env.Data.Agents { + if !isCLIAgent(agent.Name) { + continue + } + for _, id := range agent.Models { + cliModels[id] = struct{}{} + } + } + filterCLI := len(cliModels) > 0 var out []providers.ModelInfo for _, model := range env.Data.Models { if model.Disabled { continue } + if filterCLI { + if _, ok := cliModels[model.ID]; !ok { + continue + } + } out = append(out, catalogModel(model)) } out = appendAliasModels(out) @@ -346,7 +418,7 @@ func (c *Client) Models(ctx context.Context, accountID string) ([]providers.Mode func (c *Client) chatRequest(ctx context.Context, accountID string, credential Credential, req translate.ChatRequest) (*http.Request, error) { body := map[string]any{ - "model": req.Model, + "model": upstreamModelID(req.Model), "messages": req.Messages, "max_tokens": req.MaxTokens, "temperature": req.Temperature, @@ -391,7 +463,10 @@ func (c *Client) ChatNonStream(ctx context.Context, accountID string, req transl if err != nil { return providers.ChatOutcome{}, err } - client := *c.http + client, err := c.httpClient(ctx, accountID) + if err != nil { + return providers.ChatOutcome{}, err + } client.Timeout = 0 resp, err := client.Do(httpReq) if err != nil { @@ -421,7 +496,10 @@ func (c *Client) ChatStream(ctx context.Context, accountID string, req translate if err != nil { return nil, err } - client := *c.http + client, err := c.httpClient(ctx, accountID) + if err != nil { + return nil, err + } client.Timeout = 0 resp, err := client.Do(httpReq) if err != nil { @@ -647,7 +725,7 @@ func (c *Client) Quota(ctx context.Context, accountID string) (*providers.QuotaI if err != nil { return nil, err } - remain, used, total, err := c.UserResource(ctx, credential) + remain, used, total, err := c.UserResource(ctx, accountID, credential) if err != nil { return nil, err } @@ -704,7 +782,7 @@ func (c *Client) DailyCheckin(ctx context.Context, accountID string) (string, er var body []byte var status int for attempt := 0; ; attempt++ { - body, status, err = c.do(ctx, http.MethodPost, credential.BillingBase()+pathDailyCheckin, []byte("{}"), + body, status, err = c.do(ctx, accountID, http.MethodPost, credential.BillingBase()+pathDailyCheckin, []byte("{}"), func(h http.Header) { SetBillingHeaders(h, credential) }) if !retryDailyCheckin(ctx, err, status, attempt) { break @@ -794,7 +872,7 @@ func (c *Client) Keepalive(ctx context.Context, accountID string) error { } // UserResource aggregates package remain/used/total from get-user-resource. -func (c *Client) UserResource(ctx context.Context, credential Credential) (remain, used, total int64, err error) { +func (c *Client) UserResource(ctx context.Context, accountID string, credential Credential) (remain, used, total int64, err error) { now := time.Now() payload, err := json.Marshal(map[string]any{ "PageNumber": 1, @@ -807,7 +885,7 @@ func (c *Client) UserResource(ctx context.Context, credential Credential) (remai if err != nil { return 0, 0, 0, err } - body, status, err := c.do(ctx, http.MethodPost, credential.BillingBase()+pathUserResource, payload, + body, status, err := c.do(ctx, accountID, http.MethodPost, credential.BillingBase()+pathUserResource, payload, func(h http.Header) { SetBillingHeaders(h, credential) }) if err != nil { return 0, 0, 0, err @@ -934,6 +1012,21 @@ var workbuddyModelAliases = map[string]string{ "deepseek-v4.1-flash": "deep-model", } +func upstreamModelID(model string) string { + canonical := accounts.CanonicalModelID(model) + for alias, nativeModel := range workbuddyModelAliases { + if accounts.CanonicalModelID(alias) == canonical { + return nativeModel + } + } + return model +} + +// appendAliasModels publishes each workbuddyModelAliases alias alongside the +// CLI-visible model it mirrors. Aliases are derived strictly from out (the +// models that survived the CLI agent filter): an alias must never be invented +// from the unfiltered catalog, or /v1/models would advertise a model the CLI +// agent cannot actually route. func appendAliasModels(out []providers.ModelInfo) []providers.ModelInfo { if len(out) == 0 || len(workbuddyModelAliases) == 0 { return out @@ -941,18 +1034,22 @@ func appendAliasModels(out []providers.ModelInfo) []providers.ModelInfo { seen := make(map[string]struct{}, len(out)) for _, model := range out { seen[model.NativeModel] = struct{}{} + seen[model.PublicModel] = struct{}{} } for alias, nativeModel := range workbuddyModelAliases { if _, ok := seen[alias]; ok { continue } - if base, ok := findModelInfoByNativeModel(out, nativeModel); ok { - clone := base - clone.NativeModel = alias - clone.PublicModel = alias - clone.DisplayName = aliasDisplayName(alias, base.DisplayName) - out = append(out, clone) + base, ok := findModelInfoByNativeModel(out, nativeModel) + if !ok { + continue } + clone := base + clone.NativeModel = nativeModel + clone.PublicModel = alias + clone.DisplayName = aliasDisplayName(alias, base.DisplayName) + out = append(out, clone) + seen[alias] = struct{}{} } return out } diff --git a/internal/providers/workbuddy/client_test.go b/internal/providers/workbuddy/client_test.go index 3a5253c..14e7b39 100644 --- a/internal/providers/workbuddy/client_test.go +++ b/internal/providers/workbuddy/client_test.go @@ -25,6 +25,12 @@ type memStore struct { lastKind string lastStatus string settings map[string]accounts.ProviderModelSetting + + accountProxyURL string + secretValue string + secretFound bool + secretErr error + secretCalls int } func (s *memStore) Get(ctx context.Context, id string) (accounts.Account, error) { @@ -32,7 +38,14 @@ func (s *memStore) Get(ctx context.Context, id string) (accounts.Account, error) if region == "" { region = "cn" } - return accounts.Account{ID: id, Provider: "workbuddy", ProviderRegion: region}, nil + return accounts.Account{ID: id, Provider: "workbuddy", ProviderRegion: region, ProxyURL: s.accountProxyURL}, nil +} +func (s *memStore) GetSecret(ctx context.Context, key string) (string, bool, error) { + s.secretCalls++ + if s.secretErr != nil { + return "", false, s.secretErr + } + return s.secretValue, s.secretFound, nil } func (s *memStore) LoadCredentialPayload(ctx context.Context, accountID string) (string, []byte, error) { payload, ok := s.items[accountID] @@ -383,17 +396,9 @@ func TestModelsFiltersCliAgentAndDisabled(t *testing.T) { if err != nil { t.Fatal(err) } - // Disabled models must still be filtered out, but agent filtering is - // intentionally removed so all non-disabled models are exposed. - if len(models) != 2 { + if len(models) != 1 || models[0].NativeModel != "glm-5.2" || models[0].Capabilities.ContextWindow != 128000 { t.Fatalf("models=%+v", models) } - if models[0].NativeModel != "glm-5.2" || models[0].Capabilities.ContextWindow != 128000 { - t.Fatalf("models[0]=%+v", models[0]) - } - if models[1].NativeModel != "web-model" { - t.Fatalf("models[1]=%+v", models[1]) - } } func TestModelsParsesReasoningOptions(t *testing.T) { @@ -571,6 +576,109 @@ func TestChatRequestFindsStoredReasoningByCanonicalKey(t *testing.T) { } } +func TestModelsAddsDeepseekAliasForDeepModel(t *testing.T) { + payload, _ := Credential{AccessToken: "at", UID: "u1", Domain: "codebuddy.cn", ExpiresAt: 4102444800}.Encode() + store := &memStore{items: map[string][]byte{"acc1": payload}} + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _ = json.NewEncoder(w).Encode(map[string]any{"code": 0, "data": map[string]any{ + "models": []map[string]any{{ + "id": "deep-model", "name": "Deep", "maxInputTokens": 1000000, + "supportsReasoning": true, "reasoning": map[string]any{"defaultEffort": "high", "supportedEfforts": []string{"low", "high"}}, + }}, + "agents": []map[string]any{{"name": "cli", "models": []string{"deep-model"}}}, + }}) + })) + defer server.Close() + client := NewClient(store) + client.http = server.Client() + client.http.Transport = rewriteTransport{server: server.URL, round: server.Client().Transport} + + models, err := client.Models(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + if len(models) != 2 { + t.Fatalf("models=%+v", models) + } + alias := models[1] + if alias.NativeModel != "deep-model" || alias.PublicModel != "deepseek-v4.1-flash" || alias.DisplayName != "Deepseek-V4.1-Flash" { + t.Fatalf("alias=%+v", alias) + } + if alias.Capabilities.ReasoningDefault != "high" { + t.Fatalf("alias capabilities=%+v", alias.Capabilities) + } +} + +// Regression: aliases must be derived from the CLI-filtered model list. When +// the CLI agent does not expose deep-model, the catalog fallback must not +// resurrect deepseek-v4.1-flash and advertise a model chat cannot route. +func TestModelsSkipsAliasWhenNativeModelNotCLIVisible(t *testing.T) { + payload, _ := Credential{AccessToken: "at", UID: "u1", Domain: "codebuddy.cn", ExpiresAt: 4102444800}.Encode() + store := &memStore{items: map[string][]byte{"acc1": payload}} + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _ = json.NewEncoder(w).Encode(map[string]any{"code": 0, "data": map[string]any{ + "models": []map[string]any{ + {"id": "glm-5.2", "name": "GLM"}, + {"id": "deep-model", "name": "Deep", "maxInputTokens": 1000000, "supportsReasoning": true}, + }, + // deep-model exists in the catalog but is not a CLI agent model. + "agents": []map[string]any{{"name": "cli", "models": []string{"glm-5.2"}}}, + }}) + })) + defer server.Close() + client := NewClient(store) + client.http = server.Client() + client.http.Transport = rewriteTransport{server: server.URL, round: server.Client().Transport} + + models, err := client.Models(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + if len(models) != 1 || models[0].NativeModel != "glm-5.2" { + t.Fatalf("alias leaked from unfiltered catalog: models=%+v", models) + } +} + +func TestChatRequestMapsDeepseekAliasToNativeModel(t *testing.T) { + payload, _ := Credential{AccessToken: "at", UID: "u1", Domain: "codebuddy.cn", ExpiresAt: 4102444800}.Encode() + store := &memStore{items: map[string][]byte{"acc1": payload}} + var got map[string]any + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == pathModelsCN { + _ = json.NewEncoder(w).Encode(map[string]any{"code": 0, "data": map[string]any{ + "models": []map[string]any{{ + "id": "deep-model", "name": "Deep", "maxInputTokens": 1000000, + "supportsReasoning": true, "onlyReasoning": true, + "reasoning": map[string]any{"defaultEffort": "high", "supportedEfforts": []string{"low", "high"}}, + }}, + "agents": []map[string]any{{"name": "cli", "models": []string{"deep-model"}}}, + }}) + return + } + if err := json.NewDecoder(r.Body).Decode(&got); err != nil { + t.Fatalf("decode chat body: %v", err) + } + w.Header().Set("Content-Type", "text/event-stream") + _, _ = w.Write([]byte(chatSSE)) + })) + defer server.Close() + client := NewClient(store) + client.http = server.Client() + client.http.Transport = rewriteTransport{server: server.URL, round: server.Client().Transport} + + if _, err := client.ChatNonStream(context.Background(), "acc1", translate.ChatRequest{ + Model: "deepseek-v4.1-flash", Messages: []translate.ChatMessage{{Role: "user", Content: "hi"}}, + }); err != nil { + t.Fatal(err) + } + if got["model"] != "deep-model" { + t.Fatalf("upstream model=%v body=%v", got["model"], got) + } + if got["reasoning_effort"] != "high" || got["reasoning_summary"] != "auto" || got["verbosity"] != "high" { + t.Fatalf("reasoning fields=%v", got) + } +} + func TestModelsAcceptsGlobalCLIAgentNamesAndUsesAccountRegion(t *testing.T) { payload, _ := Credential{AccessToken: "at", UID: "u1", Domain: "codebuddy.cn", ExpiresAt: 4102444800}.Encode() store := &memStore{items: map[string][]byte{"acc1": payload}, region: "global"} @@ -1242,3 +1350,108 @@ func TestRateLimitErrorCarriesResetCooldown(t *testing.T) { t.Fatalf("cooldown=%v want ~30m", out.Cooldown) } } + +func TestHTTPClientFailsClosedOnSecretError(t *testing.T) { + store := &memStore{secretErr: errors.New("db down")} + client := NewClient(store) + if _, err := client.httpClient(context.Background(), "acc1"); err == nil { + t.Fatal("httpClient succeeded despite a global proxy read error") + } +} + +func TestHTTPClientAccountDirectSkipsGlobalRead(t *testing.T) { + store := &memStore{accountProxyURL: "direct", secretErr: errors.New("db down")} + client := NewClient(store) + if _, err := client.httpClient(context.Background(), "acc1"); err != nil { + t.Fatalf("account direct must not read the global proxy: %v", err) + } + if store.secretCalls != 0 { + t.Fatalf("global proxy was read %d times for an account with a proxy override", store.secretCalls) + } +} + +func TestHTTPClientUsesGlobalProxyWhenAccountIsBlank(t *testing.T) { + store := &memStore{secretValue: "http://global.example:8080", secretFound: true} + client := NewClient(store) + httpClient, err := client.httpClient(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + transport, ok := httpClient.Transport.(*http.Transport) + if !ok || transport == nil { + t.Fatalf("transport = %#v", httpClient.Transport) + } + req, _ := http.NewRequest(http.MethodGet, "https://example.com", nil) + proxyURL, err := transport.Proxy(req) + if err != nil || proxyURL == nil || proxyURL.String() != "http://global.example:8080" { + t.Fatalf("transport proxy = %v err=%v", proxyURL, err) + } +} + +func TestHTTPClientKeepsInjectedTransportWhenUnconfigured(t *testing.T) { + store := &memStore{} + client := NewClient(store) + custom := rewriteTransport{} + client.http.Transport = custom + httpClient, err := client.httpClient(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + if httpClient.Transport != custom { + t.Fatalf("unconfigured client replaced the injected transport: %#v", httpClient.Transport) + } +} + +func TestHTTPClientReusesTransportByProxyURL(t *testing.T) { + store := &memStore{secretValue: "http://global.example:8080", secretFound: true} + client := NewClient(store) + + first, err := client.httpClient(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + second, err := client.httpClient(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + if first.Transport == nil || first.Transport != second.Transport { + t.Fatal("transport was not reused across requests") + } + + // Different accounts sharing the same global proxy reuse the transport. + third, err := client.httpClient(context.Background(), "acc2") + if err != nil { + t.Fatal(err) + } + if third.Transport != first.Transport { + t.Fatal("same global proxy did not share a transport across accounts") + } +} + +func TestHTTPClientDifferentProxiesUseDifferentTransports(t *testing.T) { + store := &memStore{secretValue: "http://global.example:8080", secretFound: true} + client := NewClient(store) + + inherited, err := client.httpClient(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + + store.accountProxyURL = "http://account.example:9090" + overridden, err := client.httpClient(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + if inherited.Transport == overridden.Transport { + t.Fatal("different proxy URLs shared a transport") + } + + store.accountProxyURL = "direct" + direct, err := client.httpClient(context.Background(), "acc1") + if err != nil { + t.Fatal(err) + } + if direct.Transport == inherited.Transport || direct.Transport == overridden.Transport { + t.Fatal("direct did not get its own transport") + } +} diff --git a/internal/providers/workbuddy/credential.go b/internal/providers/workbuddy/credential.go index 38b6f19..4c483df 100644 --- a/internal/providers/workbuddy/credential.go +++ b/internal/providers/workbuddy/credential.go @@ -188,3 +188,12 @@ func (c Credential) IsGlobal() bool { } return strings.Contains(domain, DomainGlobal) || strings.Contains(domain, "workbuddy") } + +func isCLIAgent(name string) bool { + switch strings.ToLower(strings.TrimSpace(name)) { + case "cli", "codebuddy", "workbuddy": + return true + default: + return false + } +} diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go new file mode 100644 index 0000000..46486d9 --- /dev/null +++ b/internal/proxy/proxy.go @@ -0,0 +1,289 @@ +package proxy + +import ( + "container/list" + "context" + "fmt" + "net" + "net/http" + "net/url" + "strings" + "sync" + "time" + + socksproxy "golang.org/x/net/proxy" +) + +type Mode int + +const ( + ModeInherit Mode = iota + ModeDirect + ModeProxy +) + +// socksHandshakeTimeout bounds the TCP connect to the SOCKS proxy plus the +// SOCKS5 negotiation. Without it a stalled proxy with no caller deadline would +// leave the dial (and the standard library's fallback goroutine) blocked. +const socksHandshakeTimeout = 30 * time.Second + +// socksDialContext dials through a SOCKS dialer using a context. Dialers that +// implement socksproxy.ContextDialer honour cancellation directly; the fallback +// closes a connection that arrives after the context is cancelled. +func socksDialContext(ctx context.Context, dialer socksproxy.Dialer, network, address string) (net.Conn, error) { + if contextDialer, ok := dialer.(socksproxy.ContextDialer); ok { + return contextDialer.DialContext(ctx, network, address) + } + type result struct { + conn net.Conn + err error + } + done := make(chan result, 1) + go func() { + conn, err := dialer.Dial(network, address) + done <- result{conn: conn, err: err} + }() + select { + case <-ctx.Done(): + go func() { + if r := <-done; r.conn != nil { + _ = r.conn.Close() + } + }() + return nil, ctx.Err() + case r := <-done: + if r.err == nil && ctx.Err() != nil { + _ = r.conn.Close() + return nil, ctx.Err() + } + return r.conn, r.err + } +} + +type Setting struct { + Raw string + Mode Mode + URL *url.URL +} + +func Parse(raw string) (Setting, error) { + raw = strings.TrimSpace(raw) + setting := Setting{Raw: raw} + if raw == "" { + return setting, nil + } + if strings.EqualFold(raw, "direct") || strings.EqualFold(raw, "none") { + setting.Mode = ModeDirect + return setting, nil + } + parsed, err := url.Parse(raw) + if err != nil || parsed.Scheme == "" || parsed.Host == "" { + return Setting{Raw: raw}, fmt.Errorf("proxy URL must include a supported scheme and host") + } + switch strings.ToLower(parsed.Scheme) { + case "http", "https", "socks5", "socks5h": + setting.Mode = ModeProxy + setting.URL = parsed + return setting, nil + default: + return Setting{Raw: raw}, fmt.Errorf("unsupported proxy scheme %q", parsed.Scheme) + } +} + +func Preserve(existing, replacement string) string { + existing = strings.TrimSpace(existing) + replacement = strings.TrimSpace(replacement) + if existing != "" && Redact(existing) == replacement { + return existing + } + return replacement +} + +func Redact(raw string) string { + raw = strings.TrimSpace(raw) + if raw == "" || strings.EqualFold(raw, "direct") || strings.EqualFold(raw, "none") { + return raw + } + parsed, err := url.Parse(raw) + if err != nil || parsed.User == nil { + return raw + } + parsed.User = url.UserPassword(parsed.User.Username(), "******") + return parsed.String() +} + +func Effective(account, global string) string { + if strings.TrimSpace(account) != "" { + return strings.TrimSpace(account) + } + return strings.TrimSpace(global) +} + +// ValidateHTTPOnly rejects SOCKS proxies while still accepting http(s), +// direct, none, and the empty (inherit) value. Child-process providers such +// as Qoder cannot route every cloud request through a SOCKS dialer, so their +// proxy settings must stay within the http(s) boundary. +func ValidateHTTPOnly(raw string) error { + setting, err := Parse(raw) + if err != nil { + return err + } + if setting.Mode != ModeProxy { + return nil + } + switch strings.ToLower(setting.URL.Scheme) { + case "http", "https": + return nil + default: + return fmt.Errorf("this proxy setting only supports http(s), direct, or none") + } +} + +func NewTransport(raw string) (*http.Transport, error) { + setting, err := Parse(raw) + if err != nil { + return nil, err + } + if setting.Mode == ModeInherit { + return nil, nil + } + transport, ok := http.DefaultTransport.(*http.Transport) + if !ok || transport == nil { + transport = &http.Transport{} + } else { + transport = transport.Clone() + } + switch setting.Mode { + case ModeDirect: + transport.Proxy = nil + case ModeProxy: + if setting.URL.Scheme == "socks5" || setting.URL.Scheme == "socks5h" { + var auth *socksproxy.Auth + if setting.URL.User != nil { + password, _ := setting.URL.User.Password() + auth = &socksproxy.Auth{User: setting.URL.User.Username(), Password: password} + } + // The forward dialer dials the SOCKS proxy itself; use a + // context-aware dialer so a dead proxy aborts on cancellation + // instead of hanging in the standard library's goroutine fallback. + dialer, err := socksproxy.SOCKS5("tcp", setting.URL.Host, auth, &net.Dialer{}) + if err != nil { + return nil, fmt.Errorf("create SOCKS5 proxy: %w", err) + } + transport.Proxy = nil + transport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) { + if err := ctx.Err(); err != nil { + return nil, err + } + // Bound the TCP connect + SOCKS handshake even when the caller + // passes a context without a deadline, so a stalled proxy + // cannot hold the dial (and its goroutine) open forever. + handshakeCtx, cancel := context.WithTimeout(ctx, socksHandshakeTimeout) + defer cancel() + return socksDialContext(handshakeCtx, dialer, network, address) + } + } else { + transport.Proxy = http.ProxyURL(setting.URL) + } + } + return transport, nil +} + +func NewClient(timeout time.Duration, redirect func(*http.Request, []*http.Request) error, raw string) (*http.Client, error) { + transport, err := NewTransport(raw) + if err != nil { + return nil, err + } + client := &http.Client{Timeout: timeout, CheckRedirect: redirect} + if transport != nil { + client.Transport = transport + } + return client, nil +} + +// maxCachedTransports bounds the cache. Without a limit, every distinct proxy +// URL (global or per-account) would keep an http.Transport alive forever, +// pinning its idle connections and proxy credentials even after the operator +// stops using that proxy. 32 comfortably covers realistic account counts while +// keeping reuse for the working set. +const maxCachedTransports = 32 + +// transportEntry is one cache slot: the key is stored alongside the transport +// so eviction can delete the map entry without a reverse lookup. +type transportEntry struct { + raw string + transport *http.Transport +} + +// TransportCache memoizes transports by proxy setting so repeated requests +// reuse the same connection pool (and HTTP CONNECT tunnel) instead of dialing +// and handshaking again. http.Transport is safe for concurrent use. It is a +// bounded LRU: the least-recently-used entry is evicted (and its idle +// connections closed) once maxCachedTransports is exceeded. +type TransportCache struct { + mu sync.Mutex + transports map[string]*list.Element + order *list.List // most-recently-used at the front +} + +// Get returns a cached transport for raw, building one lazily. An empty raw +// value yields (nil, nil) so callers keep their existing/default transport. +func (c *TransportCache) Get(raw string) (*http.Transport, error) { + raw = strings.TrimSpace(raw) + if raw == "" { + return nil, nil + } + + c.mu.Lock() + defer c.mu.Unlock() + + if c.transports == nil { + c.transports = make(map[string]*list.Element, maxCachedTransports) + } + if c.order == nil { + c.order = list.New() + } + if element, ok := c.transports[raw]; ok { + c.order.MoveToFront(element) + return element.Value.(*transportEntry).transport, nil + } + + transport, err := NewTransport(raw) + if err != nil { + return nil, err + } + if transport == nil { + return nil, nil + } + c.transports[raw] = c.order.PushFront(&transportEntry{raw: raw, transport: transport}) + c.evictLocked() + return transport, nil +} + +// evictLocked drops least-recently-used entries until the cache is within +// capacity, closing their idle connections so old proxy tunnels do not linger. +func (c *TransportCache) evictLocked() { + for c.order.Len() > maxCachedTransports { + back := c.order.Back() + if back == nil { + return + } + entry := back.Value.(*transportEntry) + c.order.Remove(back) + delete(c.transports, entry.raw) + entry.transport.CloseIdleConnections() + } +} + +// CloseIdleConnections closes idle connections on every cached transport. +func (c *TransportCache) CloseIdleConnections() { + c.mu.Lock() + defer c.mu.Unlock() + + if c.order == nil { + return + } + for element := c.order.Front(); element != nil; element = element.Next() { + element.Value.(*transportEntry).transport.CloseIdleConnections() + } +} diff --git a/internal/proxy/proxy_test.go b/internal/proxy/proxy_test.go new file mode 100644 index 0000000..4c61d99 --- /dev/null +++ b/internal/proxy/proxy_test.go @@ -0,0 +1,282 @@ +package proxy + +import ( + "context" + "fmt" + "net" + "net/http" + "sync" + "testing" + "time" +) + +func TestParseProxyModes(t *testing.T) { + tests := []struct { + name string + raw string + mode Mode + }{ + {name: "inherit", raw: "", mode: ModeInherit}, + {name: "direct", raw: "direct", mode: ModeDirect}, + {name: "http", raw: "http://proxy.example:8080", mode: ModeProxy}, + {name: "socks", raw: "socks5://proxy.example:1080", mode: ModeProxy}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + setting, err := Parse(test.raw) + if err != nil || setting.Mode != test.mode { + t.Fatalf("Parse(%q) = mode=%d err=%v, want mode=%d", test.raw, setting.Mode, err, test.mode) + } + }) + } +} + +func TestParseRejectsUnsupportedProxy(t *testing.T) { + for _, raw := range []string{"proxy.example:8080", "ftp://proxy.example:21", "http://"} { + if _, err := Parse(raw); err == nil { + t.Fatalf("Parse(%q) unexpectedly succeeded", raw) + } + } +} + +func TestEffectiveAndPreserve(t *testing.T) { + if got := Effective("", "http://global:8080"); got != "http://global:8080" { + t.Fatalf("effective inherited proxy = %q", got) + } + if got := Effective("direct", "http://global:8080"); got != "direct" { + t.Fatalf("effective account proxy = %q", got) + } + existing := "http://user:secret@proxy.example:8080" + masked := Redact(existing) + if masked != "http://user:%2A%2A%2A%2A%2A%2A@proxy.example:8080" { + t.Fatalf("redacted proxy = %q", masked) + } + if got := Preserve(existing, masked); got != existing { + t.Fatalf("preserved proxy = %q", got) + } + if got := Preserve(existing, "direct"); got != "direct" { + t.Fatalf("replacement proxy = %q", got) + } +} + +func TestNewTransport(t *testing.T) { + transport, err := NewTransport("direct") + if err != nil || transport == nil || transport.Proxy != nil { + t.Fatalf("direct transport = %#v err=%v", transport, err) + } + transport, err = NewTransport("http://proxy.example:8080") + if err != nil || transport == nil { + t.Fatalf("proxy transport = %#v err=%v", transport, err) + } + req, _ := http.NewRequest(http.MethodGet, "https://example.com", nil) + proxyURL, err := transport.Proxy(req) + if err != nil || proxyURL.String() != "http://proxy.example:8080" { + t.Fatalf("transport proxy = %v err=%v", proxyURL, err) + } +} + +func TestValidateHTTPOnly(t *testing.T) { + for _, raw := range []string{ + "", + "direct", + "none", + "http://proxy.example:8080", + "https://proxy.example:8443", + } { + if err := ValidateHTTPOnly(raw); err != nil { + t.Fatalf("ValidateHTTPOnly(%q): %v", raw, err) + } + } + + for _, raw := range []string{ + "socks5://proxy.example:1080", + "socks5h://proxy.example:1080", + } { + if err := ValidateHTTPOnly(raw); err == nil { + t.Fatalf("ValidateHTTPOnly(%q) unexpectedly succeeded", raw) + } + } + + // Syntax errors still surface. + if err := ValidateHTTPOnly("ftp://proxy.example:21"); err == nil { + t.Fatal("ValidateHTTPOnly accepted an unsupported scheme it should reject at parse time") + } +} + +func TestTransportCache(t *testing.T) { + var cache TransportCache + + first, err := cache.Get("http://proxy.example:8080") + if err != nil || first == nil { + t.Fatalf("first Get = %#v err=%v", first, err) + } + second, err := cache.Get("http://proxy.example:8080") + if err != nil || second != first { + t.Fatalf("same URL was not reused: %#v vs %#v err=%v", first, second, err) + } + // Whitespace is normalized before keying. + third, err := cache.Get(" http://proxy.example:8080 ") + if err != nil || third != first { + t.Fatalf("trimmed URL was not reused: %#v vs %#v err=%v", first, third, err) + } + + other, err := cache.Get("http://other.example:8080") + if err != nil || other == nil || other == first { + t.Fatalf("distinct URL shared a transport: %#v vs %#v err=%v", first, other, err) + } + + // Empty value means "inherit": no transport, and the default stays usable. + empty, err := cache.Get(" ") + if err != nil || empty != nil { + t.Fatalf("empty Get = %#v err=%v, want nil", empty, err) + } +} + +func TestTransportCacheEvictsOldestAtCapacity(t *testing.T) { + var cache TransportCache + + first, err := cache.Get("http://proxy0.example:8080") + if err != nil || first == nil { + t.Fatalf("seed Get = %#v err=%v", first, err) + } + for i := 1; i <= maxCachedTransports; i++ { + raw := fmt.Sprintf("http://proxy%d.example:8080", i) + if _, err := cache.Get(raw); err != nil { + t.Fatalf("Get(%q): %v", raw, err) + } + } + + // The cache filled to capacity, then overflowed by one: the oldest entry + // (proxy0) must be dropped to keep the map bounded. + if len(cache.transports) != maxCachedTransports { + t.Fatalf("cache size = %d, want %d", len(cache.transports), maxCachedTransports) + } + if _, ok := cache.transports["http://proxy0.example:8080"]; ok { + t.Fatal("least-recently-used transport was not evicted") + } + rebuilt, err := cache.Get("http://proxy0.example:8080") + if err != nil || rebuilt == nil || rebuilt == first { + t.Fatalf("evicted transport was not rebuilt: %#v vs %#v err=%v", rebuilt, first, err) + } +} + +func TestTransportCacheGetPromotesRecency(t *testing.T) { + var cache TransportCache + + for i := 0; i < maxCachedTransports; i++ { + raw := fmt.Sprintf("http://proxy%d.example:8080", i) + if _, err := cache.Get(raw); err != nil { + t.Fatalf("Get(%q): %v", raw, err) + } + } + + // Re-reading proxy0 makes it most-recently-used; the next insert must then + // evict proxy1 (the new LRU) instead of proxy0. + if _, err := cache.Get("http://proxy0.example:8080"); err != nil { + t.Fatal(err) + } + if _, err := cache.Get("http://overflow.example:8080"); err != nil { + t.Fatal(err) + } + if _, ok := cache.transports["http://proxy0.example:8080"]; !ok { + t.Fatal("recently used transport was evicted") + } + if _, ok := cache.transports["http://proxy1.example:8080"]; ok { + t.Fatal("least-recently-used transport was not evicted after promotion") + } +} + +func TestTransportCacheGetInvalidProxyDoesNotCache(t *testing.T) { + var cache TransportCache + if _, err := cache.Get("ftp://proxy.example:21"); err == nil { + t.Fatal("Get accepted an unsupported proxy scheme") + } + if len(cache.transports) != 0 { + t.Fatalf("failed Get polluted the cache: %#v", cache.transports) + } +} + +// Regression: the zero-value cache is usable via Get, so CloseIdleConnections +// must not panic before any Get has lazily initialized order/transports. +func TestTransportCacheCloseIdleConnectionsOnZeroValue(t *testing.T) { + var cache TransportCache + cache.CloseIdleConnections() // must not panic + + if _, err := cache.Get("http://proxy.example:8080"); err != nil { + t.Fatalf("Get: %v", err) + } + cache.CloseIdleConnections() // and still safe once populated +} + +func TestTransportCacheConcurrentGet(t *testing.T) { + var cache TransportCache + + const workers = 32 + transports := make([]*http.Transport, workers) + var wg sync.WaitGroup + wg.Add(workers) + for i := 0; i < workers; i++ { + go func(i int) { + defer wg.Done() + transport, err := cache.Get("http://proxy.example:8080") + if err != nil { + t.Errorf("Get: %v", err) + return + } + transports[i] = transport + }(i) + } + wg.Wait() + + for i, transport := range transports { + if transport == nil || transport != transports[0] { + t.Fatalf("concurrent Get[%d] did not reuse the single transport", i) + } + } +} + +func TestSOCKSDialHonorsContextCancellation(t *testing.T) { + // A listener that accepts TCP but never completes the SOCKS5 handshake. + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer listener.Close() + go func() { + for { + conn, err := listener.Accept() + if err != nil { + return + } + // Hold the connection open without replying. + _ = conn + } + }() + + transport, err := NewTransport("socks5://" + listener.Addr().String()) + if err != nil { + t.Fatalf("NewTransport: %v", err) + } + if transport == nil || transport.DialContext == nil { + t.Fatal("SOCKS transport has no DialContext") + } + + ctx, cancel := context.WithTimeout(context.Background(), 150*time.Millisecond) + defer cancel() + + start := time.Now() + _, err = transport.DialContext(ctx, "tcp", "example.com:443") + if err == nil { + t.Fatal("dial through a stalled SOCKS proxy unexpectedly succeeded") + } + if elapsed := time.Since(start); elapsed > 3*time.Second { + t.Fatalf("dial did not abort on context cancellation: took %v", elapsed) + } + + // An already-cancelled context fails fast without dialing. + cancelled, cancelNow := context.WithCancel(context.Background()) + cancelNow() + if _, err := transport.DialContext(cancelled, "tcp", "example.com:443"); err == nil { + t.Fatal("dial with a cancelled context unexpectedly succeeded") + } +} diff --git a/internal/webui/static/assets/TrafficChart-D43-fLy7.js b/internal/webui/static/assets/TrafficChart-BKUjj-xR.js similarity index 99% rename from internal/webui/static/assets/TrafficChart-D43-fLy7.js rename to internal/webui/static/assets/TrafficChart-BKUjj-xR.js index f4981b3..9fdbe9f 100644 --- a/internal/webui/static/assets/TrafficChart-D43-fLy7.js +++ b/internal/webui/static/assets/TrafficChart-BKUjj-xR.js @@ -1,4 +1,4 @@ -import{a as e,c as t,d as n,i as r,l as i,n as a,o,r as s,s as c,t as l,u}from"./index-DyfuUCZL.js";var d=`dangerouslySetInnerHTML.onCopy.onCopyCapture.onCut.onCutCapture.onPaste.onPasteCapture.onCompositionEnd.onCompositionEndCapture.onCompositionStart.onCompositionStartCapture.onCompositionUpdate.onCompositionUpdateCapture.onFocus.onFocusCapture.onBlur.onBlurCapture.onChange.onChangeCapture.onBeforeInput.onBeforeInputCapture.onInput.onInputCapture.onReset.onResetCapture.onSubmit.onSubmitCapture.onInvalid.onInvalidCapture.onLoad.onLoadCapture.onError.onErrorCapture.onKeyDown.onKeyDownCapture.onKeyPress.onKeyPressCapture.onKeyUp.onKeyUpCapture.onAbort.onAbortCapture.onCanPlay.onCanPlayCapture.onCanPlayThrough.onCanPlayThroughCapture.onDurationChange.onDurationChangeCapture.onEmptied.onEmptiedCapture.onEncrypted.onEncryptedCapture.onEnded.onEndedCapture.onLoadedData.onLoadedDataCapture.onLoadedMetadata.onLoadedMetadataCapture.onLoadStart.onLoadStartCapture.onPause.onPauseCapture.onPlay.onPlayCapture.onPlaying.onPlayingCapture.onProgress.onProgressCapture.onRateChange.onRateChangeCapture.onSeeked.onSeekedCapture.onSeeking.onSeekingCapture.onStalled.onStalledCapture.onSuspend.onSuspendCapture.onTimeUpdate.onTimeUpdateCapture.onVolumeChange.onVolumeChangeCapture.onWaiting.onWaitingCapture.onAuxClick.onAuxClickCapture.onClick.onClickCapture.onContextMenu.onContextMenuCapture.onDoubleClick.onDoubleClickCapture.onDrag.onDragCapture.onDragEnd.onDragEndCapture.onDragEnter.onDragEnterCapture.onDragExit.onDragExitCapture.onDragLeave.onDragLeaveCapture.onDragOver.onDragOverCapture.onDragStart.onDragStartCapture.onDrop.onDropCapture.onMouseDown.onMouseDownCapture.onMouseEnter.onMouseLeave.onMouseMove.onMouseMoveCapture.onMouseOut.onMouseOutCapture.onMouseOver.onMouseOverCapture.onMouseUp.onMouseUpCapture.onSelect.onSelectCapture.onTouchCancel.onTouchCancelCapture.onTouchEnd.onTouchEndCapture.onTouchMove.onTouchMoveCapture.onTouchStart.onTouchStartCapture.onPointerDown.onPointerDownCapture.onPointerMove.onPointerMoveCapture.onPointerUp.onPointerUpCapture.onPointerCancel.onPointerCancelCapture.onPointerEnter.onPointerEnterCapture.onPointerLeave.onPointerLeaveCapture.onPointerOver.onPointerOverCapture.onPointerOut.onPointerOutCapture.onGotPointerCapture.onGotPointerCaptureCapture.onLostPointerCapture.onLostPointerCaptureCapture.onScroll.onScrollCapture.onWheel.onWheelCapture.onAnimationStart.onAnimationStartCapture.onAnimationEnd.onAnimationEndCapture.onAnimationIteration.onAnimationIterationCapture.onTransitionEnd.onTransitionEndCapture`.split(`.`);function f(e){return typeof e==`string`&&d.includes(e)}var p=n(t()),m=new Set(`aria-activedescendant.aria-atomic.aria-autocomplete.aria-busy.aria-checked.aria-colcount.aria-colindex.aria-colspan.aria-controls.aria-current.aria-describedby.aria-details.aria-disabled.aria-errormessage.aria-expanded.aria-flowto.aria-haspopup.aria-hidden.aria-invalid.aria-keyshortcuts.aria-label.aria-labelledby.aria-level.aria-live.aria-modal.aria-multiline.aria-multiselectable.aria-orientation.aria-owns.aria-placeholder.aria-posinset.aria-pressed.aria-readonly.aria-relevant.aria-required.aria-roledescription.aria-rowcount.aria-rowindex.aria-rowspan.aria-selected.aria-setsize.aria-sort.aria-valuemax.aria-valuemin.aria-valuenow.aria-valuetext.className.color.height.id.lang.max.media.method.min.name.style.target.width.role.tabIndex.accentHeight.accumulate.additive.alignmentBaseline.allowReorder.alphabetic.amplitude.arabicForm.ascent.attributeName.attributeType.autoReverse.azimuth.baseFrequency.baselineShift.baseProfile.bbox.begin.bias.by.calcMode.capHeight.clip.clipPath.clipPathUnits.clipRule.colorInterpolation.colorInterpolationFilters.colorProfile.colorRendering.contentScriptType.contentStyleType.cursor.cx.cy.d.decelerate.descent.diffuseConstant.direction.display.divisor.dominantBaseline.dur.dx.dy.edgeMode.elevation.enableBackground.end.exponent.externalResourcesRequired.fill.fillOpacity.fillRule.filter.filterRes.filterUnits.floodColor.floodOpacity.focusable.fontFamily.fontSize.fontSizeAdjust.fontStretch.fontStyle.fontVariant.fontWeight.format.from.fx.fy.g1.g2.glyphName.glyphOrientationHorizontal.glyphOrientationVertical.glyphRef.gradientTransform.gradientUnits.hanging.horizAdvX.horizOriginX.href.ideographic.imageRendering.in2.in.intercept.k1.k2.k3.k4.k.kernelMatrix.kernelUnitLength.kerning.keyPoints.keySplines.keyTimes.lengthAdjust.letterSpacing.lightingColor.limitingConeAngle.local.markerEnd.markerHeight.markerMid.markerStart.markerUnits.markerWidth.mask.maskContentUnits.maskUnits.mathematical.mode.numOctaves.offset.opacity.operator.order.orient.orientation.origin.overflow.overlinePosition.overlineThickness.paintOrder.panose1.pathLength.patternContentUnits.patternTransform.patternUnits.pointerEvents.pointsAtX.pointsAtY.pointsAtZ.preserveAlpha.preserveAspectRatio.primitiveUnits.r.radius.refX.refY.renderingIntent.repeatCount.repeatDur.requiredExtensions.requiredFeatures.restart.result.rotate.rx.ry.seed.shapeRendering.slope.spacing.specularConstant.specularExponent.speed.spreadMethod.startOffset.stdDeviation.stemh.stemv.stitchTiles.stopColor.stopOpacity.strikethroughPosition.strikethroughThickness.string.stroke.strokeDasharray.strokeDashoffset.strokeLinecap.strokeLinejoin.strokeMiterlimit.strokeOpacity.strokeWidth.surfaceScale.systemLanguage.tableValues.targetX.targetY.textAnchor.textDecoration.textLength.textRendering.to.transform.u1.u2.underlinePosition.underlineThickness.unicode.unicodeBidi.unicodeRange.unitsPerEm.vAlphabetic.values.vectorEffect.version.vertAdvY.vertOriginX.vertOriginY.vHanging.vIdeographic.viewTarget.visibility.vMathematical.widths.wordSpacing.writingMode.x1.x2.x.xChannelSelector.xHeight.xlinkActuate.xlinkArcrole.xlinkHref.xlinkRole.xlinkShow.xlinkTitle.xlinkType.xmlBase.xmlLang.xmlns.xmlnsXlink.xmlSpace.y1.y2.y.yChannelSelector.z.zoomAndPan.ref.key.angle`.split(`.`));function h(e){return typeof e==`string`&&m.has(e)}function g(e){return typeof e==`string`&&e.startsWith(`data-`)}function _(e){if(typeof e!=`object`||!e)return{};var t={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(h(n)||g(n))&&(t[n]=e[n]);return t}function v(e){if(e==null)return null;if((0,p.isValidElement)(e)&&typeof e.props==`object`&&e.props!==null){var t=e.props;return _(t)}return typeof e==`object`&&!Array.isArray(e)?_(e):null}function y(e){var t={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(h(n)||g(n)||f(n))&&(t[n]=e[n]);return t}function b(e){return e==null?null:(0,p.isValidElement)(e)?y(e.props):typeof e==`object`&&!Array.isArray(e)?y(e):null}var x=[`children`,`width`,`height`,`viewBox`,`className`,`style`,`title`,`desc`];function S(){return S=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var n=e.children,r=e.width,i=e.height,a=e.viewBox,s=e.className,c=e.style,l=e.title,u=e.desc,d=C(e,x),f=a||{width:r,height:i,x:0,y:0},m=o(`recharts-surface`,s);return p.createElement(`svg`,S({},y(d),{className:m,width:r,height:i,style:c,viewBox:`${f.x} ${f.y} ${f.width} ${f.height}`,ref:t}),p.createElement(`title`,null,l),p.createElement(`desc`,null,u),n)}),E=[`children`,`className`];function D(){return D=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var n=e.children,r=e.className,i=O(e,E),a=o(`recharts-layer`,r);return p.createElement(`g`,D({className:a},y(i),{ref:t}),n)});function j(e){return e===`__proto__`}var M=/\.|(\[(?:[^[\]]*|(["'])(?:(?!\2)[^\\]|\\.)*?\2)\])/;function N(e){switch(typeof e){case`number`:case`symbol`:return!1;case`string`:return e===``||e.startsWith(`.`)||e.endsWith(`.`)?!1:M.test(e);default:return!1}}function ee(e){return typeof e==`string`||typeof e==`symbol`?e:Object.is(e?.valueOf?.(),-0)?`-0`:String(e)}function te(e){return typeof e==`symbol`||e instanceof Symbol}function ne(e){return e==null?``:re(e)}function re(e){if(typeof e==`string`)return e;if(Array.isArray(e))return e.map(re).join(`,`);if(te(e))return e.toString();let t=e+``;return t===`0`&&Object.is(Number(e),-0)?`-0`:t}function ie(e){if(Array.isArray(e))return e.map(ee);if(typeof e==`symbol`)return[e];e=ne(e);let t=[],n=e.length;if(n===0)return t;let r=0,i=``,a=``,o=!1,s=!1,c=/^-?\d+(?:\.\d+)?$/;for(e.charCodeAt(0)===46&&t.push(``);r1&&arguments[1]!==void 0?arguments[1]:se),n=Math.round(e*t)/t;return Object.is(n,-0)?0:n}function P(e){var t=[...arguments].slice(1);return e.reduce((e,n,r)=>{var i=t[r-1];return typeof i==`string`?e+i+n:i===void 0?e+n:e+ce(i)+n},``)}var le=e=>e===0?0:e>0?1:-1,ue=e=>typeof e==`number`&&e!=+e,de=e=>typeof e==`string`&&e.length>1&&e.indexOf(`%`)===e.length-1,F=e=>(typeof e==`number`||e instanceof Number)&&!ue(e),fe=e=>F(e)||typeof e==`string`,pe=0,me=e=>{var t=++pe;return`${e||``}${t}`},he=function(e,t){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:0,r=arguments.length>3&&arguments[3]!==void 0&&arguments[3];if(!F(e)&&typeof e!=`string`)return n;var i;if(de(e)){if(t==null)return n;var a=e.indexOf(`%`);i=t*parseFloat(e.slice(0,a))/100}else i=+e;return ue(i)&&(i=n),r&&t!=null&&i>t&&(i=t),i},ge=e=>{if(!Array.isArray(e))return!1;for(var t=e.length,n={},r=0;re&&(typeof t==`function`?t(e):ae(e,t))===n)}var I=e=>e==null,ye=e=>I(e)?e:`${e.charAt(0).toUpperCase()}${e.slice(1)}`;function be(e){return e!=null}function xe(){}function Se(e){if(e)return{x:e.x,y:e.y,upperWidth:`upperWidth`in e?e.upperWidth:e.width,lowerWidth:`lowerWidth`in e?e.lowerWidth:e.width,width:e.width,height:e.height}}function Ce(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function we(e){for(var t=1;t{var t=e.viewBox,n=e.position,r=e.offset,i=r===void 0?0:r,a=e.parentViewBox,o=e.clamp,s=Se(t),c=s.x,l=s.y,u=s.height,d=s.upperWidth,f=s.lowerWidth,p=c,m=c+(d-f)/2,h=(p+m)/2,g=(d+f)/2,_=p+d/2,v=u>=0?1:-1,y=v*i,b=v>0?`end`:`start`,x=v>0?`start`:`end`,S=d>=0?1:-1,C=S*i,w=S>0?`end`:`start`,T=S>0?`start`:`end`,E=a;if(n===`top`){var D={x:p+d/2,y:l-y,horizontalAnchor:`middle`,verticalAnchor:b};return o&&E&&(D.height=Math.max(l-E.y,0),D.width=d),D}if(n===`bottom`){var O={x:m+f/2,y:l+u+y,horizontalAnchor:`middle`,verticalAnchor:x};return o&&E&&(O.height=Math.max(E.y+E.height-(l+u),0),O.width=f),O}if(n===`left`){var k={x:h-C,y:l+u/2,horizontalAnchor:w,verticalAnchor:`middle`};return o&&E&&(k.width=Math.max(k.x-E.x,0),k.height=u),k}if(n===`right`){var A={x:h+g+C,y:l+u/2,horizontalAnchor:T,verticalAnchor:`middle`};return o&&E&&(A.width=Math.max(E.x+E.width-A.x,0),A.height=u),A}var j=o&&E?{width:g,height:u}:{};return n===`insideLeft`?we({x:h+C,y:l+u/2,horizontalAnchor:T,verticalAnchor:`middle`},j):n===`insideRight`?we({x:h+g-C,y:l+u/2,horizontalAnchor:w,verticalAnchor:`middle`},j):n===`insideTop`?we({x:p+d/2,y:l+y,horizontalAnchor:`middle`,verticalAnchor:x},j):n===`insideBottom`?we({x:m+f/2,y:l+u-y,horizontalAnchor:`middle`,verticalAnchor:b},j):n===`insideTopLeft`?we({x:p+C,y:l+y,horizontalAnchor:T,verticalAnchor:x},j):n===`insideTopRight`?we({x:p+d-C,y:l+y,horizontalAnchor:w,verticalAnchor:x},j):n===`insideBottomLeft`?we({x:m+C,y:l+u-y,horizontalAnchor:T,verticalAnchor:b},j):n===`insideBottomRight`?we({x:m+f-C,y:l+u-y,horizontalAnchor:w,verticalAnchor:b},j):n&&typeof n==`object`&&(F(n.x)||de(n.x))&&(F(n.y)||de(n.y))?we({x:c+he(n.x,g),y:l+he(n.y,u),horizontalAnchor:`end`,verticalAnchor:`end`},j):we({x:_,y:l+u/2,horizontalAnchor:`middle`,verticalAnchor:`middle`},j)},ke=[`top`,`left`,`right`,`bottom`];function Ae(e){return e==null?!1:typeof e==`object`||ke.includes(e)}var je=(0,p.createContext)(null);function L(e){return function(){return e}}var Me=Math.PI,Ne=2*Me,Pe=1e-6,Fe=Ne-Pe;function Ie(e){this._+=e[0];for(let t=1,n=e.length;t=0))throw Error(`invalid digits: ${e}`);if(t>15)return Ie;let n=10**t;return function(e){this._+=e[0];for(let t=1,r=e.length;tPe){if(!(Math.abs(u*s-c*l)>Pe)||!i)this._append`L${this._x1=e},${this._y1=t}`;else{let f=n-a,p=r-o,m=s*s+c*c,h=f*f+p*p,g=Math.sqrt(m),_=Math.sqrt(d),v=i*Math.tan((Me-Math.acos((m+d-h)/(2*g*_)))/2),y=v/_,b=v/g;Math.abs(y-1)>Pe&&this._append`L${e+y*l},${t+y*u}`,this._append`A${i},${i},0,0,${+(u*f>l*p)},${this._x1=e+b*s},${this._y1=t+b*c}`}}}arc(e,t,n,r,i,a){if(e=+e,t=+t,n=+n,a=!!a,n<0)throw Error(`negative radius: ${n}`);let o=n*Math.cos(r),s=n*Math.sin(r),c=e+o,l=t+s,u=1^a,d=a?r-i:i-r;this._x1===null?this._append`M${c},${l}`:(Math.abs(this._x1-c)>Pe||Math.abs(this._y1-l)>Pe)&&this._append`L${c},${l}`,n&&(d<0&&(d=d%Ne+Ne),d>Fe?this._append`A${n},${n},0,1,${u},${e-o},${t-s}A${n},${n},0,1,${u},${this._x1=c},${this._y1=l}`:d>Pe&&this._append`A${n},${n},0,${+(d>=Me)},${u},${this._x1=e+n*Math.cos(i)},${this._y1=t+n*Math.sin(i)}`)}rect(e,t,n,r){this._append`M${this._x0=this._x1=+e},${this._y0=this._y1=+t}h${n=+n}v${+r}h${-n}Z`}toString(){return this._}};Re.prototype;function ze(e){let t=3;return e.digits=function(n){if(!arguments.length)return t;if(n==null)t=null;else{let e=Math.floor(n);if(!(e>=0))throw RangeError(`invalid digits: ${n}`);t=e}return e},()=>new Re(t)}Array.prototype.slice;function Be(e){return typeof e==`object`&&`length`in e?e:Array.from(e)}function Ve(e){this._context=e}Ve.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:this._context.lineTo(e,t)}}};function He(e){return new Ve(e)}function Ue(e){return e[0]}function We(e){return e[1]}function Ge(e,t){var n=L(!0),r=null,i=He,a=null,o=ze(s);e=typeof e==`function`?e:e===void 0?Ue:L(e),t=typeof t==`function`?t:t===void 0?We:L(t);function s(s){var c,l=(s=Be(s)).length,u,d=!1,f;for(r??(a=i(f=o())),c=0;c<=l;++c)!(c=d;--f)s.point(_[f],v[f]);s.lineEnd(),s.areaEnd()}}h&&(_[u]=+e(m,u,l),v[u]=+t(m,u,l),s.point(r?+r(m,u,l):_[u],n?+n(m,u,l):v[u]))}if(g)return s=null,g+``||null}function u(){return Ge().defined(i).curve(o).context(a)}return l.x=function(t){return arguments.length?(e=typeof t==`function`?t:L(+t),r=null,l):e},l.x0=function(t){return arguments.length?(e=typeof t==`function`?t:L(+t),l):e},l.x1=function(e){return arguments.length?(r=e==null?null:typeof e==`function`?e:L(+e),l):r},l.y=function(e){return arguments.length?(t=typeof e==`function`?e:L(+e),n=null,l):t},l.y0=function(e){return arguments.length?(t=typeof e==`function`?e:L(+e),l):t},l.y1=function(e){return arguments.length?(n=e==null?null:typeof e==`function`?e:L(+e),l):n},l.lineX0=l.lineY0=function(){return u().x(e).y(t)},l.lineY1=function(){return u().x(e).y(n)},l.lineX1=function(){return u().x(r).y(t)},l.defined=function(e){return arguments.length?(i=typeof e==`function`?e:L(!!e),l):i},l.curve=function(e){return arguments.length?(o=e,a!=null&&(s=o(a)),l):o},l.context=function(e){return arguments.length?(e==null?a=s=null:s=o(a=e),l):a},l}var qe=class{constructor(e,t){this._context=e,this._x=t}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line}point(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:this._x?this._context.bezierCurveTo(this._x0=(this._x0+e)/2,this._y0,this._x0,t,e,t):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+t)/2,e,this._y0,e,t)}this._x0=e,this._y0=t}};function Je(e){return new qe(e,!0)}function Ye(e){return new qe(e,!1)}function Xe(){}function Ze(e,t,n){e._context.bezierCurveTo((2*e._x0+e._x1)/3,(2*e._y0+e._y1)/3,(e._x0+2*e._x1)/3,(e._y0+2*e._y1)/3,(e._x0+4*e._x1+t)/6,(e._y0+4*e._y1+n)/6)}function Qe(e){this._context=e}Qe.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:Ze(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:Ze(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function $e(e){return new Qe(e)}function et(e){this._context=e}et.prototype={areaStart:Xe,areaEnd:Xe,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._x2=e,this._y2=t;break;case 1:this._point=2,this._x3=e,this._y3=t;break;case 2:this._point=3,this._x4=e,this._y4=t,this._context.moveTo((this._x0+4*this._x1+e)/6,(this._y0+4*this._y1+t)/6);break;default:Ze(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function tt(e){return new et(e)}function nt(e){this._context=e}nt.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var n=(this._x0+4*this._x1+e)/6,r=(this._y0+4*this._y1+t)/6;this._line?this._context.lineTo(n,r):this._context.moveTo(n,r);break;case 3:this._point=4;default:Ze(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function rt(e){return new nt(e)}function it(e){this._context=e}it.prototype={areaStart:Xe,areaEnd:Xe,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(e,t){e=+e,t=+t,this._point?this._context.lineTo(e,t):(this._point=1,this._context.moveTo(e,t))}};function at(e){return new it(e)}function ot(e){return e<0?-1:1}function st(e,t,n){var r=e._x1-e._x0,i=t-e._x1,a=(e._y1-e._y0)/(r||i<0&&-0),o=(n-e._y1)/(i||r<0&&-0),s=(a*i+o*r)/(r+i);return(ot(a)+ot(o))*Math.min(Math.abs(a),Math.abs(o),.5*Math.abs(s))||0}function ct(e,t){var n=e._x1-e._x0;return n?(3*(e._y1-e._y0)/n-t)/2:t}function lt(e,t,n){var r=e._x0,i=e._y0,a=e._x1,o=e._y1,s=(a-r)/3;e._context.bezierCurveTo(r+s,i+s*t,a-s,o-s*n,a,o)}function ut(e){this._context=e}ut.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:lt(this,this._t0,ct(this,this._t0))}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){var n=NaN;if(e=+e,t=+t,e!==this._x1||t!==this._y1){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,lt(this,ct(this,n=st(this,e,t)),n);break;default:lt(this,this._t0,n=st(this,e,t))}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t,this._t0=n}}};function dt(e){this._context=new ft(e)}(dt.prototype=Object.create(ut.prototype)).point=function(e,t){ut.prototype.point.call(this,t,e)};function ft(e){this._context=e}ft.prototype={moveTo:function(e,t){this._context.moveTo(t,e)},closePath:function(){this._context.closePath()},lineTo:function(e,t){this._context.lineTo(t,e)},bezierCurveTo:function(e,t,n,r,i,a){this._context.bezierCurveTo(t,e,r,n,a,i)}};function pt(e){return new ut(e)}function mt(e){return new dt(e)}function ht(e){this._context=e}ht.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x=[],this._y=[]},lineEnd:function(){var e=this._x,t=this._y,n=e.length;if(n){if(this._line?this._context.lineTo(e[0],t[0]):this._context.moveTo(e[0],t[0]),n===2)this._context.lineTo(e[1],t[1]);else for(var r=gt(e),i=gt(t),a=0,o=1;o=0;--t)i[t]=(o[t]-i[t+1])/a[t];for(a[n-1]=(e[n]+i[n-1])/2,t=0;t=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,t),this._context.lineTo(e,t);else{var n=this._x*(1-this._t)+e*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,t)}}this._x=e,this._y=t}};function yt(e){return new vt(e,.5)}function bt(e){return new vt(e,0)}function xt(e){return new vt(e,1)}function St(e,t){if((o=e.length)>1)for(var n=1,r,i,a=e[t[0]],o,s=a.length;n=0;)n[t]=t;return n}function wt(e,t){return e[t]}function Tt(e){let t=[];return t.key=e,t}function Et(){var e=L([]),t=Ct,n=St,r=wt;function i(i){var a=Array.from(e.apply(this,arguments),Tt),o,s=a.length,c=-1,l;for(let e of i)for(o=0,++c;o0){for(var n,r,i=0,a=e[0].length,o;i0){for(var n=0,r=e[t[0]],i,a=r.length;n0)||!((a=(i=e[t[0]]).length)>0))){for(var n=0,r=1,i,a,o;r`radius`in e&&`startAngle`in e&&`endAngle`in e,jt=(e,t)=>{if(!e||typeof e==`function`||typeof e==`boolean`)return null;var n=e;if((0,p.isValidElement)(e)&&(n=e.props),typeof n!=`object`&&typeof n!=`function`)return null;var r={};return Object.keys(n).forEach(e=>{f(e)&&typeof n[e]==`function`&&(r[e]=t||(t=>n[e](n,t)))}),r},Mt=(e,t,n)=>r=>(e(t,n,r),null),Nt=(e,t,n)=>{if(e===null||typeof e!=`object`&&typeof e!=`function`)return null;var r=null;return Object.keys(e).forEach(i=>{var a=e[i];f(i)&&typeof a==`function`&&(r||={},r[i]=Mt(a,t,n))}),r};function Pt(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Ft(e){for(var t=1;t(e[t]===void 0&&r[t]!==void 0&&(e[t]=r[t]),e),n)}function Bt(e,t){let n=new Map;for(let r=0;r=0}function Wt(e){return e!=null&&typeof e!=`function`&&Ut(e.length)}function Gt(e){return function(t){return ae(t,e)}}function Kt(e){return e==null||typeof e!=`object`&&typeof e!=`function`}function qt(e){return ArrayBuffer.isView(e)&&!(e instanceof DataView)}function Jt(e){return Object.getOwnPropertySymbols(e).filter(t=>Object.prototype.propertyIsEnumerable.call(e,t))}function Yt(e){return e==null?e===void 0?`[object Undefined]`:`[object Null]`:Object.prototype.toString.call(e)}var Xt=`[object RegExp]`,Zt=`[object String]`,Qt=`[object Number]`,$t=`[object Boolean]`,en=`[object Arguments]`,tn=`[object Symbol]`,nn=`[object Date]`,rn=`[object Map]`,an=`[object Set]`,on=`[object Array]`,sn=`[object Function]`,cn=`[object ArrayBuffer]`,ln=`[object Object]`,un=`[object Error]`,dn=`[object DataView]`,fn=`[object Uint8Array]`,pn=`[object Uint8ClampedArray]`,mn=`[object Uint16Array]`,hn=`[object Uint32Array]`,gn=`[object BigUint64Array]`,_n=`[object Int8Array]`,vn=`[object Int16Array]`,yn=`[object Int32Array]`,bn=`[object BigInt64Array]`,xn=`[object Float32Array]`,Sn=`[object Float64Array]`,Cn=typeof globalThis==`object`&&globalThis||typeof window==`object`&&window||typeof self==`object`&&self||typeof global==`object`&&global||(function(){return this})();function wn(e){return Cn.Buffer!==void 0&&Cn.Buffer.isBuffer(e)}function Tn(e,t){return En(e,void 0,e,new Map,t)}function En(e,t,n,r=new Map,i=void 0){let a=i?.(e,t,n,r);if(a!==void 0)return a;if(Kt(e))return e;if(r.has(e))return r.get(e);if(Array.isArray(e)){let t=Array(e.length);r.set(e,t);for(let a=0;avoid 0)}function Nn(e,t,n,r,i=!1){if(t===e)return!0;switch(typeof t){case`object`:return Pn(e,t,n,r,i);case`function`:return Object.keys(t).length>0?Nn(e,{...t},n,r,i):An(e,t);default:return jn(e)&&i?typeof t!=`string`||t===``:An(e,t)}}function Pn(e,t,n,r,i=!1){if(t==null)return!0;if(Array.isArray(t))return In(e,t,n,r);if(t instanceof Map)return Fn(e,t,n,r);if(t instanceof Set)return Ln(e,t,n,r);let a=Object.keys(t);if(e==null)return i&&a.length===0;if(i)Kt(e)&&(e=Object(e));else{let t=Yt(e);if(t!==`[object Object]`&&t!==`[object Arguments]`)return!1}if(a.length===0)return!0;if(r?.has(t))return r.get(t)===e;r?.set(t,e);try{for(let i=0;ivoid 0)}function zn(e){return e=kn(e),t=>Rn(t,e)}function Bn(e,t){return Tn(e,(n,r,i,a)=>{let o=t?.(n,r,i,a);if(o!==void 0)return o;if(typeof e==`object`){if(Yt(e)===`[object Object]`&&typeof e.constructor!=`function`){let t={};return a.set(e,t),Dn(t,e,i,a),t}switch(Object.prototype.toString.call(e)){case Qt:case Zt:case $t:{let t=new e.constructor(e?.valueOf());return Dn(t,e),t}case en:{let t={};return Dn(t,e),t.length=e.length,t[Symbol.iterator]=e[Symbol.iterator],t}default:return}}})}function Vn(e){return Bn(e)}var Hn=/^(?:0|[1-9]\d*)$/;function Un(e,t=2**53-1){switch(typeof e){case`number`:return Number.isInteger(e)&&e>=0&&e{var n=t(),i=r();function a(e,t){return e===t&&(e!==0||1/e==1/t)||e!==e&&t!==t}var o=typeof Object.is==`function`?Object.is:a,s=i.useSyncExternalStore,c=n.useRef,l=n.useEffect,u=n.useMemo,d=n.useDebugValue;e.useSyncExternalStoreWithSelector=function(e,t,n,r,i){var a=c(null);if(a.current===null){var f={hasValue:!1,value:null};a.current=f}else f=a.current;a=u(function(){function e(e){if(!a){if(a=!0,s=e,e=r(e),i!==void 0&&f.hasValue){var t=f.value;if(i(t,e))return c=t}return c=e}if(t=c,o(s,e))return t;var n=r(e);return i!==void 0&&i(t,n)?(s=e,t):(s=e,c=n)}var a=!1,s,c,l=n===void 0?null:n;return[function(){return e(t())},l===null?void 0:function(){return e(l())}]},[t,n,r,i]);var p=s(e,a[0],a[1]);return l(function(){f.hasValue=!0,f.value=p},[p]),d(p),p}})),Qn=i(((e,t)=>{t.exports=Zn()})),$n=(0,p.createContext)(null),er=Qn(),tr=e=>e,R=()=>{var e=(0,p.useContext)($n);return e?e.store.dispatch:tr},nr=()=>{},rr=()=>nr,ir=(e,t)=>e===t;function z(e){var t=(0,p.useContext)($n),n=(0,p.useMemo)(()=>t?t=>{if(t!=null)return e(t)}:nr,[t,e]);return(0,er.useSyncExternalStoreWithSelector)(t?t.subscription.addNestedSub:rr,t?t.store.getState:nr,t?t.store.getState:nr,n,ir)}function ar(e,t=`expected a function, instead received ${typeof e}`){if(typeof e!=`function`)throw TypeError(t)}function or(e,t=`expected all items to be functions, instead received the following types: `){if(!e.every(e=>typeof e==`function`)){let n=e.map(e=>typeof e==`function`?`function ${e.name||`unnamed`}()`:typeof e).join(`, `);throw TypeError(`${t}[${n}]`)}}var sr=e=>Array.isArray(e)?e:[e];function cr(e){let t=Array.isArray(e[0])?e[0]:e;return or(t,`createSelector expects all input-selectors to be functions, but received the following types: `),t}function lr(e,t){let n=[],{length:r}=e;for(let i=0;i`u`?ur:WeakRef,fr=0,pr=1;function mr(){return{s:fr,v:void 0,o:null,p:null}}function hr(e){return e instanceof dr?e.deref():e}function gr(e,t={}){let n=mr(),{resultEqualityCheck:r}=t,i,a=0;function o(){let t=n,{length:o}=arguments;for(let e=0,n=o;e{n=mr(),o.resetResultsCount()},o.resultsCount=()=>a,o.resetResultsCount=()=>{a=0},o}function _r(e,...t){let n=typeof e==`function`?{memoize:e,memoizeOptions:t}:e,r=(...e)=>{let t=0,r=0,i,a={},o=e.pop();typeof o==`object`&&(a=o,o=e.pop()),ar(o,`createSelector expects an output function after the inputs, but received: [${typeof o}]`);let{memoize:s,memoizeOptions:c=[],argsMemoize:l=gr,argsMemoizeOptions:u=[]}={...n,...a},d=sr(c),f=sr(u),p=cr(e),m=s(function(){return t++,o.apply(null,arguments)},...d),h=l(function(){r++;let e=lr(p,arguments);return i=m.apply(null,e),i},...f);return Object.assign(h,{resultFunc:o,memoizedResultFunc:m,dependencies:p,dependencyRecomputations:()=>r,resetDependencyRecomputations:()=>{r=0},lastResult:()=>i,recomputations:()=>t,resetRecomputations:()=>{t=0},memoize:s,argsMemoize:l})};return Object.assign(r,{withTypes:()=>r}),r}var B=_r(gr);function vr(e,t=1){let n=[],r=Math.floor(t),i=(e,t)=>{for(let a=0;a{if(e!==t){let r=br(e),i=br(t);if(r===i&&r===0){if(et)return n===`desc`?-1:1}return n===`desc`?i-r:r-i}return 0},Sr=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,Cr=/^\w*$/;function wr(e,t){return Array.isArray(e)?!1:typeof e==`number`||typeof e==`boolean`||e==null||te(e)?!0:typeof e==`string`&&(Cr.test(e)||!Sr.test(e))||t!=null&&Object.hasOwn(t,e)}function Tr(e,t,n,r){if(e==null)return[];n=r?void 0:n,Array.isArray(e)||(e=Wt(e)?Array.from(e):Object.values(e)),Array.isArray(t)||(t=t==null?[null]:[t]),t.length===0&&(t=[null]),Array.isArray(n)||(n=n==null?[]:[n]),n=n.map(e=>String(e));let i=(e,t)=>{let n=e,r=0;for(;r0&&r===t.length?n:void 0},a=(e,t)=>{if(e==null)return t;if(t!=null)return typeof e==`object`&&`key`in e?Object.hasOwn(t,e.key)?t[e.key]:i(t,e.path):typeof e==`function`?e(t):Array.isArray(e)?i(t,e):t[e]},o=t.map(e=>(Array.isArray(e)&&e.length===1&&(e=e[0]),e==null||typeof e==`function`||Array.isArray(e)||wr(e)?e:{key:e,path:ie(e)}));return e.map(e=>({original:e,criteria:o.map(t=>a(t,e))})).slice().sort((e,t)=>{for(let r=0;re.original)}function Er(e,...t){let n=t.length;return n>1&&yr(e,t[0],t[1])?t=[]:n>2&&yr(t[0],t[1],t[2])&&(t=[t[0]]),Tr(e,vr(t),[`asc`])}var Dr=e=>e.legend.settings,Or=e=>e.legend.size;B([e=>e.legend.payload,Dr],(e,t)=>{var n=t.itemSorter,r=e.flat(1);return n?Er(r,n):r});function kr(e,t){return Pr(e)||Nr(e,t)||jr(e,t)||Ar()}function Ar(){throw TypeError(`Invalid attempt to destructure non-iterable instance. +import{a as e,c as t,d as n,i as r,l as i,n as a,o,r as s,s as c,t as l,u}from"./index-Nd7wuGyD.js";var d=`dangerouslySetInnerHTML.onCopy.onCopyCapture.onCut.onCutCapture.onPaste.onPasteCapture.onCompositionEnd.onCompositionEndCapture.onCompositionStart.onCompositionStartCapture.onCompositionUpdate.onCompositionUpdateCapture.onFocus.onFocusCapture.onBlur.onBlurCapture.onChange.onChangeCapture.onBeforeInput.onBeforeInputCapture.onInput.onInputCapture.onReset.onResetCapture.onSubmit.onSubmitCapture.onInvalid.onInvalidCapture.onLoad.onLoadCapture.onError.onErrorCapture.onKeyDown.onKeyDownCapture.onKeyPress.onKeyPressCapture.onKeyUp.onKeyUpCapture.onAbort.onAbortCapture.onCanPlay.onCanPlayCapture.onCanPlayThrough.onCanPlayThroughCapture.onDurationChange.onDurationChangeCapture.onEmptied.onEmptiedCapture.onEncrypted.onEncryptedCapture.onEnded.onEndedCapture.onLoadedData.onLoadedDataCapture.onLoadedMetadata.onLoadedMetadataCapture.onLoadStart.onLoadStartCapture.onPause.onPauseCapture.onPlay.onPlayCapture.onPlaying.onPlayingCapture.onProgress.onProgressCapture.onRateChange.onRateChangeCapture.onSeeked.onSeekedCapture.onSeeking.onSeekingCapture.onStalled.onStalledCapture.onSuspend.onSuspendCapture.onTimeUpdate.onTimeUpdateCapture.onVolumeChange.onVolumeChangeCapture.onWaiting.onWaitingCapture.onAuxClick.onAuxClickCapture.onClick.onClickCapture.onContextMenu.onContextMenuCapture.onDoubleClick.onDoubleClickCapture.onDrag.onDragCapture.onDragEnd.onDragEndCapture.onDragEnter.onDragEnterCapture.onDragExit.onDragExitCapture.onDragLeave.onDragLeaveCapture.onDragOver.onDragOverCapture.onDragStart.onDragStartCapture.onDrop.onDropCapture.onMouseDown.onMouseDownCapture.onMouseEnter.onMouseLeave.onMouseMove.onMouseMoveCapture.onMouseOut.onMouseOutCapture.onMouseOver.onMouseOverCapture.onMouseUp.onMouseUpCapture.onSelect.onSelectCapture.onTouchCancel.onTouchCancelCapture.onTouchEnd.onTouchEndCapture.onTouchMove.onTouchMoveCapture.onTouchStart.onTouchStartCapture.onPointerDown.onPointerDownCapture.onPointerMove.onPointerMoveCapture.onPointerUp.onPointerUpCapture.onPointerCancel.onPointerCancelCapture.onPointerEnter.onPointerEnterCapture.onPointerLeave.onPointerLeaveCapture.onPointerOver.onPointerOverCapture.onPointerOut.onPointerOutCapture.onGotPointerCapture.onGotPointerCaptureCapture.onLostPointerCapture.onLostPointerCaptureCapture.onScroll.onScrollCapture.onWheel.onWheelCapture.onAnimationStart.onAnimationStartCapture.onAnimationEnd.onAnimationEndCapture.onAnimationIteration.onAnimationIterationCapture.onTransitionEnd.onTransitionEndCapture`.split(`.`);function f(e){return typeof e==`string`&&d.includes(e)}var p=n(t()),m=new Set(`aria-activedescendant.aria-atomic.aria-autocomplete.aria-busy.aria-checked.aria-colcount.aria-colindex.aria-colspan.aria-controls.aria-current.aria-describedby.aria-details.aria-disabled.aria-errormessage.aria-expanded.aria-flowto.aria-haspopup.aria-hidden.aria-invalid.aria-keyshortcuts.aria-label.aria-labelledby.aria-level.aria-live.aria-modal.aria-multiline.aria-multiselectable.aria-orientation.aria-owns.aria-placeholder.aria-posinset.aria-pressed.aria-readonly.aria-relevant.aria-required.aria-roledescription.aria-rowcount.aria-rowindex.aria-rowspan.aria-selected.aria-setsize.aria-sort.aria-valuemax.aria-valuemin.aria-valuenow.aria-valuetext.className.color.height.id.lang.max.media.method.min.name.style.target.width.role.tabIndex.accentHeight.accumulate.additive.alignmentBaseline.allowReorder.alphabetic.amplitude.arabicForm.ascent.attributeName.attributeType.autoReverse.azimuth.baseFrequency.baselineShift.baseProfile.bbox.begin.bias.by.calcMode.capHeight.clip.clipPath.clipPathUnits.clipRule.colorInterpolation.colorInterpolationFilters.colorProfile.colorRendering.contentScriptType.contentStyleType.cursor.cx.cy.d.decelerate.descent.diffuseConstant.direction.display.divisor.dominantBaseline.dur.dx.dy.edgeMode.elevation.enableBackground.end.exponent.externalResourcesRequired.fill.fillOpacity.fillRule.filter.filterRes.filterUnits.floodColor.floodOpacity.focusable.fontFamily.fontSize.fontSizeAdjust.fontStretch.fontStyle.fontVariant.fontWeight.format.from.fx.fy.g1.g2.glyphName.glyphOrientationHorizontal.glyphOrientationVertical.glyphRef.gradientTransform.gradientUnits.hanging.horizAdvX.horizOriginX.href.ideographic.imageRendering.in2.in.intercept.k1.k2.k3.k4.k.kernelMatrix.kernelUnitLength.kerning.keyPoints.keySplines.keyTimes.lengthAdjust.letterSpacing.lightingColor.limitingConeAngle.local.markerEnd.markerHeight.markerMid.markerStart.markerUnits.markerWidth.mask.maskContentUnits.maskUnits.mathematical.mode.numOctaves.offset.opacity.operator.order.orient.orientation.origin.overflow.overlinePosition.overlineThickness.paintOrder.panose1.pathLength.patternContentUnits.patternTransform.patternUnits.pointerEvents.pointsAtX.pointsAtY.pointsAtZ.preserveAlpha.preserveAspectRatio.primitiveUnits.r.radius.refX.refY.renderingIntent.repeatCount.repeatDur.requiredExtensions.requiredFeatures.restart.result.rotate.rx.ry.seed.shapeRendering.slope.spacing.specularConstant.specularExponent.speed.spreadMethod.startOffset.stdDeviation.stemh.stemv.stitchTiles.stopColor.stopOpacity.strikethroughPosition.strikethroughThickness.string.stroke.strokeDasharray.strokeDashoffset.strokeLinecap.strokeLinejoin.strokeMiterlimit.strokeOpacity.strokeWidth.surfaceScale.systemLanguage.tableValues.targetX.targetY.textAnchor.textDecoration.textLength.textRendering.to.transform.u1.u2.underlinePosition.underlineThickness.unicode.unicodeBidi.unicodeRange.unitsPerEm.vAlphabetic.values.vectorEffect.version.vertAdvY.vertOriginX.vertOriginY.vHanging.vIdeographic.viewTarget.visibility.vMathematical.widths.wordSpacing.writingMode.x1.x2.x.xChannelSelector.xHeight.xlinkActuate.xlinkArcrole.xlinkHref.xlinkRole.xlinkShow.xlinkTitle.xlinkType.xmlBase.xmlLang.xmlns.xmlnsXlink.xmlSpace.y1.y2.y.yChannelSelector.z.zoomAndPan.ref.key.angle`.split(`.`));function h(e){return typeof e==`string`&&m.has(e)}function g(e){return typeof e==`string`&&e.startsWith(`data-`)}function _(e){if(typeof e!=`object`||!e)return{};var t={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(h(n)||g(n))&&(t[n]=e[n]);return t}function v(e){if(e==null)return null;if((0,p.isValidElement)(e)&&typeof e.props==`object`&&e.props!==null){var t=e.props;return _(t)}return typeof e==`object`&&!Array.isArray(e)?_(e):null}function y(e){var t={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(h(n)||g(n)||f(n))&&(t[n]=e[n]);return t}function b(e){return e==null?null:(0,p.isValidElement)(e)?y(e.props):typeof e==`object`&&!Array.isArray(e)?y(e):null}var x=[`children`,`width`,`height`,`viewBox`,`className`,`style`,`title`,`desc`];function S(){return S=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var n=e.children,r=e.width,i=e.height,a=e.viewBox,s=e.className,c=e.style,l=e.title,u=e.desc,d=C(e,x),f=a||{width:r,height:i,x:0,y:0},m=o(`recharts-surface`,s);return p.createElement(`svg`,S({},y(d),{className:m,width:r,height:i,style:c,viewBox:`${f.x} ${f.y} ${f.width} ${f.height}`,ref:t}),p.createElement(`title`,null,l),p.createElement(`desc`,null,u),n)}),E=[`children`,`className`];function D(){return D=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var n=e.children,r=e.className,i=O(e,E),a=o(`recharts-layer`,r);return p.createElement(`g`,D({className:a},y(i),{ref:t}),n)});function j(e){return e===`__proto__`}var M=/\.|(\[(?:[^[\]]*|(["'])(?:(?!\2)[^\\]|\\.)*?\2)\])/;function N(e){switch(typeof e){case`number`:case`symbol`:return!1;case`string`:return e===``||e.startsWith(`.`)||e.endsWith(`.`)?!1:M.test(e);default:return!1}}function ee(e){return typeof e==`string`||typeof e==`symbol`?e:Object.is(e?.valueOf?.(),-0)?`-0`:String(e)}function te(e){return typeof e==`symbol`||e instanceof Symbol}function ne(e){return e==null?``:re(e)}function re(e){if(typeof e==`string`)return e;if(Array.isArray(e))return e.map(re).join(`,`);if(te(e))return e.toString();let t=e+``;return t===`0`&&Object.is(Number(e),-0)?`-0`:t}function ie(e){if(Array.isArray(e))return e.map(ee);if(typeof e==`symbol`)return[e];e=ne(e);let t=[],n=e.length;if(n===0)return t;let r=0,i=``,a=``,o=!1,s=!1,c=/^-?\d+(?:\.\d+)?$/;for(e.charCodeAt(0)===46&&t.push(``);r1&&arguments[1]!==void 0?arguments[1]:se),n=Math.round(e*t)/t;return Object.is(n,-0)?0:n}function P(e){var t=[...arguments].slice(1);return e.reduce((e,n,r)=>{var i=t[r-1];return typeof i==`string`?e+i+n:i===void 0?e+n:e+ce(i)+n},``)}var le=e=>e===0?0:e>0?1:-1,ue=e=>typeof e==`number`&&e!=+e,de=e=>typeof e==`string`&&e.length>1&&e.indexOf(`%`)===e.length-1,F=e=>(typeof e==`number`||e instanceof Number)&&!ue(e),fe=e=>F(e)||typeof e==`string`,pe=0,me=e=>{var t=++pe;return`${e||``}${t}`},he=function(e,t){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:0,r=arguments.length>3&&arguments[3]!==void 0&&arguments[3];if(!F(e)&&typeof e!=`string`)return n;var i;if(de(e)){if(t==null)return n;var a=e.indexOf(`%`);i=t*parseFloat(e.slice(0,a))/100}else i=+e;return ue(i)&&(i=n),r&&t!=null&&i>t&&(i=t),i},ge=e=>{if(!Array.isArray(e))return!1;for(var t=e.length,n={},r=0;re&&(typeof t==`function`?t(e):ae(e,t))===n)}var I=e=>e==null,ye=e=>I(e)?e:`${e.charAt(0).toUpperCase()}${e.slice(1)}`;function be(e){return e!=null}function xe(){}function Se(e){if(e)return{x:e.x,y:e.y,upperWidth:`upperWidth`in e?e.upperWidth:e.width,lowerWidth:`lowerWidth`in e?e.lowerWidth:e.width,width:e.width,height:e.height}}function Ce(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function we(e){for(var t=1;t{var t=e.viewBox,n=e.position,r=e.offset,i=r===void 0?0:r,a=e.parentViewBox,o=e.clamp,s=Se(t),c=s.x,l=s.y,u=s.height,d=s.upperWidth,f=s.lowerWidth,p=c,m=c+(d-f)/2,h=(p+m)/2,g=(d+f)/2,_=p+d/2,v=u>=0?1:-1,y=v*i,b=v>0?`end`:`start`,x=v>0?`start`:`end`,S=d>=0?1:-1,C=S*i,w=S>0?`end`:`start`,T=S>0?`start`:`end`,E=a;if(n===`top`){var D={x:p+d/2,y:l-y,horizontalAnchor:`middle`,verticalAnchor:b};return o&&E&&(D.height=Math.max(l-E.y,0),D.width=d),D}if(n===`bottom`){var O={x:m+f/2,y:l+u+y,horizontalAnchor:`middle`,verticalAnchor:x};return o&&E&&(O.height=Math.max(E.y+E.height-(l+u),0),O.width=f),O}if(n===`left`){var k={x:h-C,y:l+u/2,horizontalAnchor:w,verticalAnchor:`middle`};return o&&E&&(k.width=Math.max(k.x-E.x,0),k.height=u),k}if(n===`right`){var A={x:h+g+C,y:l+u/2,horizontalAnchor:T,verticalAnchor:`middle`};return o&&E&&(A.width=Math.max(E.x+E.width-A.x,0),A.height=u),A}var j=o&&E?{width:g,height:u}:{};return n===`insideLeft`?we({x:h+C,y:l+u/2,horizontalAnchor:T,verticalAnchor:`middle`},j):n===`insideRight`?we({x:h+g-C,y:l+u/2,horizontalAnchor:w,verticalAnchor:`middle`},j):n===`insideTop`?we({x:p+d/2,y:l+y,horizontalAnchor:`middle`,verticalAnchor:x},j):n===`insideBottom`?we({x:m+f/2,y:l+u-y,horizontalAnchor:`middle`,verticalAnchor:b},j):n===`insideTopLeft`?we({x:p+C,y:l+y,horizontalAnchor:T,verticalAnchor:x},j):n===`insideTopRight`?we({x:p+d-C,y:l+y,horizontalAnchor:w,verticalAnchor:x},j):n===`insideBottomLeft`?we({x:m+C,y:l+u-y,horizontalAnchor:T,verticalAnchor:b},j):n===`insideBottomRight`?we({x:m+f-C,y:l+u-y,horizontalAnchor:w,verticalAnchor:b},j):n&&typeof n==`object`&&(F(n.x)||de(n.x))&&(F(n.y)||de(n.y))?we({x:c+he(n.x,g),y:l+he(n.y,u),horizontalAnchor:`end`,verticalAnchor:`end`},j):we({x:_,y:l+u/2,horizontalAnchor:`middle`,verticalAnchor:`middle`},j)},ke=[`top`,`left`,`right`,`bottom`];function Ae(e){return e==null?!1:typeof e==`object`||ke.includes(e)}var je=(0,p.createContext)(null);function L(e){return function(){return e}}var Me=Math.PI,Ne=2*Me,Pe=1e-6,Fe=Ne-Pe;function Ie(e){this._+=e[0];for(let t=1,n=e.length;t=0))throw Error(`invalid digits: ${e}`);if(t>15)return Ie;let n=10**t;return function(e){this._+=e[0];for(let t=1,r=e.length;tPe){if(!(Math.abs(u*s-c*l)>Pe)||!i)this._append`L${this._x1=e},${this._y1=t}`;else{let f=n-a,p=r-o,m=s*s+c*c,h=f*f+p*p,g=Math.sqrt(m),_=Math.sqrt(d),v=i*Math.tan((Me-Math.acos((m+d-h)/(2*g*_)))/2),y=v/_,b=v/g;Math.abs(y-1)>Pe&&this._append`L${e+y*l},${t+y*u}`,this._append`A${i},${i},0,0,${+(u*f>l*p)},${this._x1=e+b*s},${this._y1=t+b*c}`}}}arc(e,t,n,r,i,a){if(e=+e,t=+t,n=+n,a=!!a,n<0)throw Error(`negative radius: ${n}`);let o=n*Math.cos(r),s=n*Math.sin(r),c=e+o,l=t+s,u=1^a,d=a?r-i:i-r;this._x1===null?this._append`M${c},${l}`:(Math.abs(this._x1-c)>Pe||Math.abs(this._y1-l)>Pe)&&this._append`L${c},${l}`,n&&(d<0&&(d=d%Ne+Ne),d>Fe?this._append`A${n},${n},0,1,${u},${e-o},${t-s}A${n},${n},0,1,${u},${this._x1=c},${this._y1=l}`:d>Pe&&this._append`A${n},${n},0,${+(d>=Me)},${u},${this._x1=e+n*Math.cos(i)},${this._y1=t+n*Math.sin(i)}`)}rect(e,t,n,r){this._append`M${this._x0=this._x1=+e},${this._y0=this._y1=+t}h${n=+n}v${+r}h${-n}Z`}toString(){return this._}};Re.prototype;function ze(e){let t=3;return e.digits=function(n){if(!arguments.length)return t;if(n==null)t=null;else{let e=Math.floor(n);if(!(e>=0))throw RangeError(`invalid digits: ${n}`);t=e}return e},()=>new Re(t)}Array.prototype.slice;function Be(e){return typeof e==`object`&&`length`in e?e:Array.from(e)}function Ve(e){this._context=e}Ve.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:this._context.lineTo(e,t)}}};function He(e){return new Ve(e)}function Ue(e){return e[0]}function We(e){return e[1]}function Ge(e,t){var n=L(!0),r=null,i=He,a=null,o=ze(s);e=typeof e==`function`?e:e===void 0?Ue:L(e),t=typeof t==`function`?t:t===void 0?We:L(t);function s(s){var c,l=(s=Be(s)).length,u,d=!1,f;for(r??(a=i(f=o())),c=0;c<=l;++c)!(c=d;--f)s.point(_[f],v[f]);s.lineEnd(),s.areaEnd()}}h&&(_[u]=+e(m,u,l),v[u]=+t(m,u,l),s.point(r?+r(m,u,l):_[u],n?+n(m,u,l):v[u]))}if(g)return s=null,g+``||null}function u(){return Ge().defined(i).curve(o).context(a)}return l.x=function(t){return arguments.length?(e=typeof t==`function`?t:L(+t),r=null,l):e},l.x0=function(t){return arguments.length?(e=typeof t==`function`?t:L(+t),l):e},l.x1=function(e){return arguments.length?(r=e==null?null:typeof e==`function`?e:L(+e),l):r},l.y=function(e){return arguments.length?(t=typeof e==`function`?e:L(+e),n=null,l):t},l.y0=function(e){return arguments.length?(t=typeof e==`function`?e:L(+e),l):t},l.y1=function(e){return arguments.length?(n=e==null?null:typeof e==`function`?e:L(+e),l):n},l.lineX0=l.lineY0=function(){return u().x(e).y(t)},l.lineY1=function(){return u().x(e).y(n)},l.lineX1=function(){return u().x(r).y(t)},l.defined=function(e){return arguments.length?(i=typeof e==`function`?e:L(!!e),l):i},l.curve=function(e){return arguments.length?(o=e,a!=null&&(s=o(a)),l):o},l.context=function(e){return arguments.length?(e==null?a=s=null:s=o(a=e),l):a},l}var qe=class{constructor(e,t){this._context=e,this._x=t}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line}point(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:this._x?this._context.bezierCurveTo(this._x0=(this._x0+e)/2,this._y0,this._x0,t,e,t):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+t)/2,e,this._y0,e,t)}this._x0=e,this._y0=t}};function Je(e){return new qe(e,!0)}function Ye(e){return new qe(e,!1)}function Xe(){}function Ze(e,t,n){e._context.bezierCurveTo((2*e._x0+e._x1)/3,(2*e._y0+e._y1)/3,(e._x0+2*e._x1)/3,(e._y0+2*e._y1)/3,(e._x0+4*e._x1+t)/6,(e._y0+4*e._y1+n)/6)}function Qe(e){this._context=e}Qe.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:Ze(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:Ze(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function $e(e){return new Qe(e)}function et(e){this._context=e}et.prototype={areaStart:Xe,areaEnd:Xe,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._x2=e,this._y2=t;break;case 1:this._point=2,this._x3=e,this._y3=t;break;case 2:this._point=3,this._x4=e,this._y4=t,this._context.moveTo((this._x0+4*this._x1+e)/6,(this._y0+4*this._y1+t)/6);break;default:Ze(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function tt(e){return new et(e)}function nt(e){this._context=e}nt.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var n=(this._x0+4*this._x1+e)/6,r=(this._y0+4*this._y1+t)/6;this._line?this._context.lineTo(n,r):this._context.moveTo(n,r);break;case 3:this._point=4;default:Ze(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function rt(e){return new nt(e)}function it(e){this._context=e}it.prototype={areaStart:Xe,areaEnd:Xe,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(e,t){e=+e,t=+t,this._point?this._context.lineTo(e,t):(this._point=1,this._context.moveTo(e,t))}};function at(e){return new it(e)}function ot(e){return e<0?-1:1}function st(e,t,n){var r=e._x1-e._x0,i=t-e._x1,a=(e._y1-e._y0)/(r||i<0&&-0),o=(n-e._y1)/(i||r<0&&-0),s=(a*i+o*r)/(r+i);return(ot(a)+ot(o))*Math.min(Math.abs(a),Math.abs(o),.5*Math.abs(s))||0}function ct(e,t){var n=e._x1-e._x0;return n?(3*(e._y1-e._y0)/n-t)/2:t}function lt(e,t,n){var r=e._x0,i=e._y0,a=e._x1,o=e._y1,s=(a-r)/3;e._context.bezierCurveTo(r+s,i+s*t,a-s,o-s*n,a,o)}function ut(e){this._context=e}ut.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:lt(this,this._t0,ct(this,this._t0))}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){var n=NaN;if(e=+e,t=+t,e!==this._x1||t!==this._y1){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,lt(this,ct(this,n=st(this,e,t)),n);break;default:lt(this,this._t0,n=st(this,e,t))}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t,this._t0=n}}};function dt(e){this._context=new ft(e)}(dt.prototype=Object.create(ut.prototype)).point=function(e,t){ut.prototype.point.call(this,t,e)};function ft(e){this._context=e}ft.prototype={moveTo:function(e,t){this._context.moveTo(t,e)},closePath:function(){this._context.closePath()},lineTo:function(e,t){this._context.lineTo(t,e)},bezierCurveTo:function(e,t,n,r,i,a){this._context.bezierCurveTo(t,e,r,n,a,i)}};function pt(e){return new ut(e)}function mt(e){return new dt(e)}function ht(e){this._context=e}ht.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x=[],this._y=[]},lineEnd:function(){var e=this._x,t=this._y,n=e.length;if(n){if(this._line?this._context.lineTo(e[0],t[0]):this._context.moveTo(e[0],t[0]),n===2)this._context.lineTo(e[1],t[1]);else for(var r=gt(e),i=gt(t),a=0,o=1;o=0;--t)i[t]=(o[t]-i[t+1])/a[t];for(a[n-1]=(e[n]+i[n-1])/2,t=0;t=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,t),this._context.lineTo(e,t);else{var n=this._x*(1-this._t)+e*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,t)}}this._x=e,this._y=t}};function yt(e){return new vt(e,.5)}function bt(e){return new vt(e,0)}function xt(e){return new vt(e,1)}function St(e,t){if((o=e.length)>1)for(var n=1,r,i,a=e[t[0]],o,s=a.length;n=0;)n[t]=t;return n}function wt(e,t){return e[t]}function Tt(e){let t=[];return t.key=e,t}function Et(){var e=L([]),t=Ct,n=St,r=wt;function i(i){var a=Array.from(e.apply(this,arguments),Tt),o,s=a.length,c=-1,l;for(let e of i)for(o=0,++c;o0){for(var n,r,i=0,a=e[0].length,o;i0){for(var n=0,r=e[t[0]],i,a=r.length;n0)||!((a=(i=e[t[0]]).length)>0))){for(var n=0,r=1,i,a,o;r`radius`in e&&`startAngle`in e&&`endAngle`in e,jt=(e,t)=>{if(!e||typeof e==`function`||typeof e==`boolean`)return null;var n=e;if((0,p.isValidElement)(e)&&(n=e.props),typeof n!=`object`&&typeof n!=`function`)return null;var r={};return Object.keys(n).forEach(e=>{f(e)&&typeof n[e]==`function`&&(r[e]=t||(t=>n[e](n,t)))}),r},Mt=(e,t,n)=>r=>(e(t,n,r),null),Nt=(e,t,n)=>{if(e===null||typeof e!=`object`&&typeof e!=`function`)return null;var r=null;return Object.keys(e).forEach(i=>{var a=e[i];f(i)&&typeof a==`function`&&(r||={},r[i]=Mt(a,t,n))}),r};function Pt(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Ft(e){for(var t=1;t(e[t]===void 0&&r[t]!==void 0&&(e[t]=r[t]),e),n)}function Bt(e,t){let n=new Map;for(let r=0;r=0}function Wt(e){return e!=null&&typeof e!=`function`&&Ut(e.length)}function Gt(e){return function(t){return ae(t,e)}}function Kt(e){return e==null||typeof e!=`object`&&typeof e!=`function`}function qt(e){return ArrayBuffer.isView(e)&&!(e instanceof DataView)}function Jt(e){return Object.getOwnPropertySymbols(e).filter(t=>Object.prototype.propertyIsEnumerable.call(e,t))}function Yt(e){return e==null?e===void 0?`[object Undefined]`:`[object Null]`:Object.prototype.toString.call(e)}var Xt=`[object RegExp]`,Zt=`[object String]`,Qt=`[object Number]`,$t=`[object Boolean]`,en=`[object Arguments]`,tn=`[object Symbol]`,nn=`[object Date]`,rn=`[object Map]`,an=`[object Set]`,on=`[object Array]`,sn=`[object Function]`,cn=`[object ArrayBuffer]`,ln=`[object Object]`,un=`[object Error]`,dn=`[object DataView]`,fn=`[object Uint8Array]`,pn=`[object Uint8ClampedArray]`,mn=`[object Uint16Array]`,hn=`[object Uint32Array]`,gn=`[object BigUint64Array]`,_n=`[object Int8Array]`,vn=`[object Int16Array]`,yn=`[object Int32Array]`,bn=`[object BigInt64Array]`,xn=`[object Float32Array]`,Sn=`[object Float64Array]`,Cn=typeof globalThis==`object`&&globalThis||typeof window==`object`&&window||typeof self==`object`&&self||typeof global==`object`&&global||(function(){return this})();function wn(e){return Cn.Buffer!==void 0&&Cn.Buffer.isBuffer(e)}function Tn(e,t){return En(e,void 0,e,new Map,t)}function En(e,t,n,r=new Map,i=void 0){let a=i?.(e,t,n,r);if(a!==void 0)return a;if(Kt(e))return e;if(r.has(e))return r.get(e);if(Array.isArray(e)){let t=Array(e.length);r.set(e,t);for(let a=0;avoid 0)}function Nn(e,t,n,r,i=!1){if(t===e)return!0;switch(typeof t){case`object`:return Pn(e,t,n,r,i);case`function`:return Object.keys(t).length>0?Nn(e,{...t},n,r,i):An(e,t);default:return jn(e)&&i?typeof t!=`string`||t===``:An(e,t)}}function Pn(e,t,n,r,i=!1){if(t==null)return!0;if(Array.isArray(t))return In(e,t,n,r);if(t instanceof Map)return Fn(e,t,n,r);if(t instanceof Set)return Ln(e,t,n,r);let a=Object.keys(t);if(e==null)return i&&a.length===0;if(i)Kt(e)&&(e=Object(e));else{let t=Yt(e);if(t!==`[object Object]`&&t!==`[object Arguments]`)return!1}if(a.length===0)return!0;if(r?.has(t))return r.get(t)===e;r?.set(t,e);try{for(let i=0;ivoid 0)}function zn(e){return e=kn(e),t=>Rn(t,e)}function Bn(e,t){return Tn(e,(n,r,i,a)=>{let o=t?.(n,r,i,a);if(o!==void 0)return o;if(typeof e==`object`){if(Yt(e)===`[object Object]`&&typeof e.constructor!=`function`){let t={};return a.set(e,t),Dn(t,e,i,a),t}switch(Object.prototype.toString.call(e)){case Qt:case Zt:case $t:{let t=new e.constructor(e?.valueOf());return Dn(t,e),t}case en:{let t={};return Dn(t,e),t.length=e.length,t[Symbol.iterator]=e[Symbol.iterator],t}default:return}}})}function Vn(e){return Bn(e)}var Hn=/^(?:0|[1-9]\d*)$/;function Un(e,t=2**53-1){switch(typeof e){case`number`:return Number.isInteger(e)&&e>=0&&e{var n=t(),i=r();function a(e,t){return e===t&&(e!==0||1/e==1/t)||e!==e&&t!==t}var o=typeof Object.is==`function`?Object.is:a,s=i.useSyncExternalStore,c=n.useRef,l=n.useEffect,u=n.useMemo,d=n.useDebugValue;e.useSyncExternalStoreWithSelector=function(e,t,n,r,i){var a=c(null);if(a.current===null){var f={hasValue:!1,value:null};a.current=f}else f=a.current;a=u(function(){function e(e){if(!a){if(a=!0,s=e,e=r(e),i!==void 0&&f.hasValue){var t=f.value;if(i(t,e))return c=t}return c=e}if(t=c,o(s,e))return t;var n=r(e);return i!==void 0&&i(t,n)?(s=e,t):(s=e,c=n)}var a=!1,s,c,l=n===void 0?null:n;return[function(){return e(t())},l===null?void 0:function(){return e(l())}]},[t,n,r,i]);var p=s(e,a[0],a[1]);return l(function(){f.hasValue=!0,f.value=p},[p]),d(p),p}})),Qn=i(((e,t)=>{t.exports=Zn()})),$n=(0,p.createContext)(null),er=Qn(),tr=e=>e,R=()=>{var e=(0,p.useContext)($n);return e?e.store.dispatch:tr},nr=()=>{},rr=()=>nr,ir=(e,t)=>e===t;function z(e){var t=(0,p.useContext)($n),n=(0,p.useMemo)(()=>t?t=>{if(t!=null)return e(t)}:nr,[t,e]);return(0,er.useSyncExternalStoreWithSelector)(t?t.subscription.addNestedSub:rr,t?t.store.getState:nr,t?t.store.getState:nr,n,ir)}function ar(e,t=`expected a function, instead received ${typeof e}`){if(typeof e!=`function`)throw TypeError(t)}function or(e,t=`expected all items to be functions, instead received the following types: `){if(!e.every(e=>typeof e==`function`)){let n=e.map(e=>typeof e==`function`?`function ${e.name||`unnamed`}()`:typeof e).join(`, `);throw TypeError(`${t}[${n}]`)}}var sr=e=>Array.isArray(e)?e:[e];function cr(e){let t=Array.isArray(e[0])?e[0]:e;return or(t,`createSelector expects all input-selectors to be functions, but received the following types: `),t}function lr(e,t){let n=[],{length:r}=e;for(let i=0;i`u`?ur:WeakRef,fr=0,pr=1;function mr(){return{s:fr,v:void 0,o:null,p:null}}function hr(e){return e instanceof dr?e.deref():e}function gr(e,t={}){let n=mr(),{resultEqualityCheck:r}=t,i,a=0;function o(){let t=n,{length:o}=arguments;for(let e=0,n=o;e{n=mr(),o.resetResultsCount()},o.resultsCount=()=>a,o.resetResultsCount=()=>{a=0},o}function _r(e,...t){let n=typeof e==`function`?{memoize:e,memoizeOptions:t}:e,r=(...e)=>{let t=0,r=0,i,a={},o=e.pop();typeof o==`object`&&(a=o,o=e.pop()),ar(o,`createSelector expects an output function after the inputs, but received: [${typeof o}]`);let{memoize:s,memoizeOptions:c=[],argsMemoize:l=gr,argsMemoizeOptions:u=[]}={...n,...a},d=sr(c),f=sr(u),p=cr(e),m=s(function(){return t++,o.apply(null,arguments)},...d),h=l(function(){r++;let e=lr(p,arguments);return i=m.apply(null,e),i},...f);return Object.assign(h,{resultFunc:o,memoizedResultFunc:m,dependencies:p,dependencyRecomputations:()=>r,resetDependencyRecomputations:()=>{r=0},lastResult:()=>i,recomputations:()=>t,resetRecomputations:()=>{t=0},memoize:s,argsMemoize:l})};return Object.assign(r,{withTypes:()=>r}),r}var B=_r(gr);function vr(e,t=1){let n=[],r=Math.floor(t),i=(e,t)=>{for(let a=0;a{if(e!==t){let r=br(e),i=br(t);if(r===i&&r===0){if(et)return n===`desc`?-1:1}return n===`desc`?i-r:r-i}return 0},Sr=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,Cr=/^\w*$/;function wr(e,t){return Array.isArray(e)?!1:typeof e==`number`||typeof e==`boolean`||e==null||te(e)?!0:typeof e==`string`&&(Cr.test(e)||!Sr.test(e))||t!=null&&Object.hasOwn(t,e)}function Tr(e,t,n,r){if(e==null)return[];n=r?void 0:n,Array.isArray(e)||(e=Wt(e)?Array.from(e):Object.values(e)),Array.isArray(t)||(t=t==null?[null]:[t]),t.length===0&&(t=[null]),Array.isArray(n)||(n=n==null?[]:[n]),n=n.map(e=>String(e));let i=(e,t)=>{let n=e,r=0;for(;r0&&r===t.length?n:void 0},a=(e,t)=>{if(e==null)return t;if(t!=null)return typeof e==`object`&&`key`in e?Object.hasOwn(t,e.key)?t[e.key]:i(t,e.path):typeof e==`function`?e(t):Array.isArray(e)?i(t,e):t[e]},o=t.map(e=>(Array.isArray(e)&&e.length===1&&(e=e[0]),e==null||typeof e==`function`||Array.isArray(e)||wr(e)?e:{key:e,path:ie(e)}));return e.map(e=>({original:e,criteria:o.map(t=>a(t,e))})).slice().sort((e,t)=>{for(let r=0;re.original)}function Er(e,...t){let n=t.length;return n>1&&yr(e,t[0],t[1])?t=[]:n>2&&yr(t[0],t[1],t[2])&&(t=[t[0]]),Tr(e,vr(t),[`asc`])}var Dr=e=>e.legend.settings,Or=e=>e.legend.size;B([e=>e.legend.payload,Dr],(e,t)=>{var n=t.itemSorter,r=e.flat(1);return n?Er(r,n):r});function kr(e,t){return Pr(e)||Nr(e,t)||jr(e,t)||Ar()}function Ar(){throw TypeError(`Invalid attempt to destructure non-iterable instance. In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function jr(e,t){if(e){if(typeof e==`string`)return Mr(e,t);var n={}.toString.call(e).slice(8,-1);return n===`Object`&&e.constructor&&(n=e.constructor.name),n===`Map`||n===`Set`?Array.from(e):n===`Arguments`||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?Mr(e,t):void 0}}function Mr(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);nFr||Math.abs(e.left-t.left)>Fr||Math.abs(e.top-t.top)>Fr||Math.abs(e.width-t.width)>Fr}function Lr(e){var t=e.getBoundingClientRect();return{height:t.height,left:t.left,top:t.top,width:t.width}}function Rr(){var e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[],t=kr((0,p.useState)({height:0,left:0,top:0,width:0}),2),n=t[0],r=t[1],i=(0,p.useRef)(null),a=(0,p.useRef)(n);a.current=n;var o=(0,p.useCallback)(e=>{if(i.current!=null&&(i.current.disconnect(),i.current=null),e!=null){var t=Lr(e);if(Ir(t,a.current)&&r(t),typeof ResizeObserver<`u`){var n=new ResizeObserver(()=>{var t=Lr(e);Ir(t,a.current)&&r(t)});n.observe(e),i.current=n}}},[...e]);return(0,p.useEffect)(()=>()=>{var e;(e=i.current)==null||e.disconnect()},[]),[n,o]}function zr(e){return`Minified Redux error #${e}; visit https://redux.js.org/Errors?code=${e} for the full message or use the non-minified dev environment for full errors. `}var Br=typeof Symbol==`function`&&Symbol.observable||`@@observable`,Vr=()=>Math.random().toString(36).substring(7).split(``).join(`.`),Hr={INIT:`@@redux/INIT${Vr()}`,REPLACE:`@@redux/REPLACE${Vr()}`,PROBE_UNKNOWN_ACTION:()=>`@@redux/PROBE_UNKNOWN_ACTION${Vr()}`};function Ur(e){if(typeof e!=`object`||!e)return!1;let t=e;for(;Object.getPrototypeOf(t)!==null;)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t||Object.getPrototypeOf(e)===null}function Wr(e,t,n){if(typeof e!=`function`)throw Error(zr(2));if(typeof t==`function`&&typeof n==`function`||typeof n==`function`&&typeof arguments[3]==`function`)throw Error(zr(0));if(typeof t==`function`&&n===void 0&&(n=t,t=void 0),n!==void 0){if(typeof n!=`function`)throw Error(zr(1));return n(Wr)(e,t)}let r=e,i=t,a=new Map,o=a,s=0,c=!1;function l(){o===a&&(o=new Map,a.forEach((e,t)=>{o.set(t,e)}))}function u(){if(c)throw Error(zr(3));return i}function d(e){if(typeof e!=`function`)throw Error(zr(4));if(c)throw Error(zr(5));let t=!0;l();let n=s++;return o.set(n,e),function(){if(t){if(c)throw Error(zr(6));t=!1,l(),o.delete(n),a=null}}}function f(e){if(!Ur(e))throw Error(zr(7));if(e.type===void 0)throw Error(zr(8));if(typeof e.type!=`string`)throw Error(zr(17));if(c)throw Error(zr(9));try{c=!0,i=r(i,e)}finally{c=!1}return(a=o).forEach(e=>{e()}),e}function p(e){if(typeof e!=`function`)throw Error(zr(10));r=e,f({type:Hr.REPLACE})}function m(){let e=d;return{subscribe(t){if(typeof t!=`object`||!t)throw Error(zr(11));function n(){let e=t;e.next&&e.next(u())}return n(),{unsubscribe:e(n)}},[Br](){return this}}}return f({type:Hr.INIT}),{dispatch:f,subscribe:d,getState:u,replaceReducer:p,[Br]:m}}function Gr(e){Object.keys(e).forEach(t=>{let n=e[t];if(n(void 0,{type:Hr.INIT})===void 0)throw Error(zr(12));if(n(void 0,{type:Hr.PROBE_UNKNOWN_ACTION()})===void 0)throw Error(zr(13))})}function Kr(e){let t=Object.keys(e),n={};for(let r=0;re:e.length===1?e[0]:e.reduce((e,t)=>(...n)=>e(t(...n)))}function Jr(...e){return t=>(n,r)=>{let i=t(n,r),a=()=>{throw Error(zr(15))},o={getState:i.getState,dispatch:(e,...t)=>a(e,...t)};return a=qr(...e.map(e=>e(o)))(i.dispatch),{...i,dispatch:a}}}function Yr(e){return Ur(e)&&`type`in e&&typeof e.type==`string`}var Xr=Symbol.for(`immer-nothing`),Zr=Symbol.for(`immer-draftable`),Qr=Symbol.for(`immer-state`);function $r(e,...t){throw Error(`[Immer] minified error nr: ${e}. Full error at: https://bit.ly/3cXEKWf`)}var ei=Object,ti=ei.getPrototypeOf,ni=`constructor`,ri=`prototype`,ii=`configurable`,ai=`enumerable`,oi=`writable`,si=`value`,ci=e=>!!e&&!!e[Qr];function li(e){return e?fi(e)||yi(e)||!!e[Zr]||!!e[ni]?.[Zr]||bi(e)||xi(e):!1}var ui=ei[ri][ni].toString(),di=new WeakMap;function fi(e){if(!e||!Si(e))return!1;let t=ti(e);if(t===null||t===ei[ri])return!0;let n=ei.hasOwnProperty.call(t,ni)&&t[ni];if(n===Object)return!0;if(!Ci(n))return!1;let r=di.get(n);return r===void 0&&(r=Function.toString.call(n),di.set(n,r)),r===ui}function pi(e,t,n=!0){mi(e)===0?(n?Reflect.ownKeys(e):ei.keys(e)).forEach(n=>{t(n,e[n],e)}):e.forEach((n,r)=>t(r,n,e))}function mi(e){let t=e[Qr];return t?t.type_:yi(e)?1:bi(e)?2:xi(e)?3:0}var hi=(e,t,n=mi(e))=>n===2?e.has(t):ei[ri].hasOwnProperty.call(e,t),gi=(e,t,n=mi(e))=>n===2?e.get(t):e[t],_i=(e,t,n,r=mi(e))=>{r===2?e.set(t,n):r===3?e.add(n):e[t]=n};function vi(e,t){return e===t?e!==0||1/e==1/t:e!==e&&t!==t}var yi=Array.isArray,bi=e=>e instanceof Map,xi=e=>e instanceof Set,Si=e=>typeof e==`object`,Ci=e=>typeof e==`function`,wi=e=>typeof e==`boolean`;function Ti(e){let t=+e;return Number.isInteger(t)&&String(t)===e}var Ei=e=>e.copy_||e.base_,Di=e=>e.modified_?e.copy_:e.base_;function Oi(e,t){if(bi(e))return new Map(e);if(xi(e))return new Set(e);if(yi(e))return Array[ri].slice.call(e);let n=fi(e);if(t===!0||t===`class_only`&&!n){let t=ei.getOwnPropertyDescriptors(e);delete t[Qr];let n=Reflect.ownKeys(t);for(let r=0;r1&&ei.defineProperties(e,{set:ji,add:ji,clear:ji,delete:ji}),ei.freeze(e),t&&pi(e,(e,t)=>{ki(t,!0)},!1),e)}function Ai(){$r(2)}var ji={[si]:Ai};function Mi(e){return e===null||!Si(e)||ei.isFrozen(e)}var Ni=`MapSet`,Pi=`Patches`,Fi=`ArrayMethods`,Ii={};function Li(e){let t=Ii[e];return t||$r(0,e),t}var Ri=e=>!!Ii[e],zi,Bi=()=>zi,Vi=(e,t)=>({drafts_:[],parent_:e,immer_:t,canAutoFreeze_:!0,unfinalizedDrafts_:0,handledSet_:new Set,processedForPatches_:new Set,mapSetPlugin_:Ri(Ni)?Li(Ni):void 0,arrayMethodsPlugin_:Ri(Fi)?Li(Fi):void 0});function Hi(e,t){t&&(e.patchPlugin_=Li(Pi),e.patches_=[],e.inversePatches_=[],e.patchListener_=t)}function Ui(e){Wi(e),e.drafts_.forEach(Ki),e.drafts_=null}function Wi(e){e===zi&&(zi=e.parent_)}var Gi=e=>zi=Vi(zi,e);function Ki(e){let t=e[Qr];t.type_===0||t.type_===1?t.revoke_():t.revoked_=!0}function qi(e,t){t.unfinalizedDrafts_=t.drafts_.length;let n=t.drafts_[0];if(e!==void 0&&e!==n){n[Qr].modified_&&(Ui(t),$r(4)),li(e)&&(e=Ji(t,e));let{patchPlugin_:r}=t;r&&r.generateReplacementPatches_(n[Qr].base_,e,t)}else e=Ji(t,n);return Yi(t,e,!0),Ui(t),t.patches_&&t.patchListener_(t.patches_,t.inversePatches_),e===Xr?void 0:e}function Ji(e,t){if(Mi(t))return t;let n=t[Qr];if(!n)return ra(t,e.handledSet_,e);if(!Zi(n,e))return t;if(!n.modified_)return n.base_;if(!n.finalized_){let{callbacks_:t}=n;if(t)for(;t.length>0;)t.pop()(e);ta(n,e)}return n.copy_}function Yi(e,t,n=!1){!e.parent_&&e.immer_.autoFreeze_&&e.canAutoFreeze_&&ki(t,n)}function Xi(e){e.finalized_=!0,e.scope_.unfinalizedDrafts_--}var Zi=(e,t)=>e.scope_===t,Qi=[];function $i(e,t,n,r){let i=Ei(e),a=e.type_;if(r!==void 0&&gi(i,r,a)===t){_i(i,r,n,a);return}if(!e.draftLocations_){let t=e.draftLocations_=new Map;pi(i,(e,n)=>{if(ci(n)){let r=t.get(n)||[];r.push(e),t.set(n,r)}})}let o=e.draftLocations_.get(t)??Qi;for(let e of o)_i(i,e,n,a)}function ea(e,t,n){e.callbacks_.push(function(r){let i=t;if(!i||!Zi(i,r))return;r.mapSetPlugin_?.fixSetContents(i);let a=Di(i);$i(e,i.draft_??i,a,n),ta(i,r)})}function ta(e,t){if(e.modified_&&!e.finalized_&&(e.type_===3||e.type_===1&&e.allIndicesReassigned_||(e.assigned_?.size??0)>0)){let{patchPlugin_:n}=t;if(n){let r=n.getPath(e);r&&n.generatePatches_(e,r,t)}Xi(e)}}function na(e,t,n){let{scope_:r}=e;if(ci(n)){let i=n[Qr];Zi(i,r)&&i.callbacks_.push(function(){fa(e),$i(e,n,Di(i),t)})}else li(n)&&e.callbacks_.push(function(){let i=Ei(e);e.type_===3?i.has(n)&&ra(n,r.handledSet_,r):gi(i,t,e.type_)===n&&r.drafts_.length>1&&(e.assigned_.get(t)??!1)===!0&&e.copy_&&ra(gi(e.copy_,t,e.type_),r.handledSet_,r)})}function ra(e,t,n){return!n.immer_.autoFreeze_&&n.unfinalizedDrafts_<1||ci(e)||t.has(e)||!li(e)||Mi(e)?e:(t.add(e),pi(e,(r,i)=>{if(ci(i)){let t=i[Qr];Zi(t,n)&&(_i(e,r,Di(t),e.type_),Xi(t))}else li(i)&&ra(i,t,n)}),e)}function ia(e,t){let n=yi(e),r={type_:+!!n,scope_:t?t.scope_:Bi(),modified_:!1,finalized_:!1,assigned_:void 0,parent_:t,base_:e,draft_:null,copy_:null,revoke_:null,isManual_:!1,callbacks_:void 0},i=r,a=aa;n&&(i=[r],a=oa);let{revoke:o,proxy:s}=Proxy.revocable(i,a);return r.draft_=s,r.revoke_=o,[s,r]}var aa={get(e,t){if(t===Qr)return e;let n=e.scope_.arrayMethodsPlugin_,r=e.type_===1&&typeof t==`string`;if(r&&n?.isArrayOperationMethod(t))return n.createMethodInterceptor(e,t);let i=Ei(e);if(!hi(i,t,e.type_))return la(e,i,t);let a=i[t];if(e.finalized_||!li(a)||r&&e.operationMethod&&n?.isMutatingArrayMethod(e.operationMethod)&&Ti(t))return a;if(a===sa(e.base_,t)||ca(e,t,a)){fa(e);let n=e.type_===1?+t:t,r=ma(e.scope_,a,e,n);return e.copy_[n]=r}return a},has(e,t){return t in Ei(e)},ownKeys(e){return Reflect.ownKeys(Ei(e))},set(e,t,n){let r=ua(Ei(e),t);if(r?.set)return r.set.call(e.draft_,n),!0;if(!e.modified_){let r=sa(Ei(e),t),i=r?.[Qr];if(i&&i.base_===n)return e.copy_[t]=n,e.assigned_.set(t,!1),!0;if(vi(n,r)&&(n!==void 0||hi(e.base_,t,e.type_)))return!0;fa(e),da(e)}return e.copy_[t]===n&&(n!==void 0||hi(e.copy_,t,e.type_))||Number.isNaN(n)&&Number.isNaN(e.copy_[t])?!0:(e.copy_[t]=n,e.assigned_.set(t,!0),na(e,t,n),!0)},deleteProperty(e,t){return fa(e),sa(e.base_,t)!==void 0||t in e.base_?(e.assigned_.set(t,!1),da(e)):e.assigned_.delete(t),e.copy_&&delete e.copy_[t],!0},getOwnPropertyDescriptor(e,t){let n=Ei(e),r=Reflect.getOwnPropertyDescriptor(n,t);return r&&{[oi]:!0,[ii]:e.type_!==1||t!==`length`,[ai]:r[ai],[si]:n[t]}},defineProperty(){$r(11)},getPrototypeOf(e){return ti(e.base_)},setPrototypeOf(){$r(12)}},oa={};for(let e in aa){let t=aa[e];oa[e]=function(){let e=arguments;return e[0]=e[0][0],t.apply(this,e)}}oa.deleteProperty=function(e,t){return oa.set.call(this,e,t,void 0)},oa.set=function(e,t,n){return aa.set.call(this,e[0],t,n,e[0])};function sa(e,t){let n=e[Qr];return(n?Ei(n):e)[t]}function ca(e,t,n){return e.type_!==1||!e.allIndicesReassigned_||e.assigned_?.get(t)||!li(n)||n[Qr]?!1:e.baseRefs_.has(n)}function la(e,t,n){let r=ua(t,n);return r?si in r?r[si]:r.get?.call(e.draft_):void 0}function ua(e,t){if(!(t in e))return;let n=ti(e);for(;n;){let e=Object.getOwnPropertyDescriptor(n,t);if(e)return e;n=ti(n)}}function da(e){e.modified_||(e.modified_=!0,e.parent_&&da(e.parent_))}function fa(e){e.copy_||=(e.assigned_=new Map,Oi(e.base_,e.scope_.immer_.useStrictShallowCopy_))}var pa=class{constructor(e){this.autoFreeze_=!0,this.useStrictShallowCopy_=!1,this.useStrictIteration_=!1,this.produce=(e,t,n)=>{if(Ci(e)&&!Ci(t)){let n=t;t=e;let r=this;return function(e=n,...i){return r.produce(e,e=>t.call(this,e,...i))}}Ci(t)||$r(6),n!==void 0&&!Ci(n)&&$r(7);let r;if(li(e)){let i=Gi(this),a=ma(i,e,void 0),o=!0;try{r=t(a),o=!1}finally{o?Ui(i):Wi(i)}return Hi(i,n),qi(r,i)}if(!e||!Si(e)){if(r=t(e),r===void 0&&(r=e),r===Xr&&(r=void 0),this.autoFreeze_&&ki(r,!0),n){let t=[],i=[];Li(Pi).generateReplacementPatches_(e,r,{patches_:t,inversePatches_:i}),n(t,i)}return r}$r(1,e)},this.produceWithPatches=(e,t)=>{if(Ci(e))return(t,...n)=>this.produceWithPatches(t,t=>e(t,...n));let n,r;return[this.produce(e,t,(e,t)=>{n=e,r=t}),n,r]},wi(e?.autoFreeze)&&this.setAutoFreeze(e.autoFreeze),wi(e?.useStrictShallowCopy)&&this.setUseStrictShallowCopy(e.useStrictShallowCopy),wi(e?.useStrictIteration)&&this.setUseStrictIteration(e.useStrictIteration)}createDraft(e){li(e)||$r(8),ci(e)&&(e=ha(e));let t=Gi(this),n=ma(t,e,void 0);return n[Qr].isManual_=!0,Wi(t),n}finishDraft(e,t){let n=e&&e[Qr];(!n||!n.isManual_)&&$r(9);let{scope_:r}=n;return Hi(r,t),qi(void 0,r)}setAutoFreeze(e){this.autoFreeze_=e}setUseStrictShallowCopy(e){this.useStrictShallowCopy_=e}setUseStrictIteration(e){this.useStrictIteration_=e}shouldUseStrictIteration(){return this.useStrictIteration_}applyPatches(e,t){let n;for(n=t.length-1;n>=0;n--){let r=t[n];if(r.path.length===0&&r.op===`replace`){e=r.value;break}}n>-1&&(t=t.slice(n+1));let r=Li(Pi).applyPatches_;return ci(e)?r(e,t):this.produce(e,e=>r(e,t))}};function ma(e,t,n,r){let[i,a]=bi(t)?Li(Ni).proxyMap_(t,n):xi(t)?Li(Ni).proxySet_(t,n):ia(t,n);return(n?.scope_??Bi()).drafts_.push(i),a.callbacks_=n?.callbacks_??[],a.key_=r,n&&r!==void 0?ea(n,a,r):a.callbacks_.push(function(e){e.mapSetPlugin_?.fixSetContents(a);let{patchPlugin_:t}=e;a.modified_&&t&&t.generatePatches_(a,[],e)}),i}function ha(e){return ci(e)||$r(10,e),ga(e)}function ga(e){if(!li(e)||Mi(e))return e;let t=e[Qr],n,r=!0;if(t){if(!t.modified_)return t.base_;t.finalized_=!0,n=Oi(e,t.scope_.immer_.useStrictShallowCopy_),r=t.scope_.immer_.shouldUseStrictIteration()}else n=Oi(e,!0);return pi(n,(e,t)=>{_i(n,e,ga(t))},r),t&&(t.finalized_=!1),n}globalThis.Iterator?.from;var _a=new pa().produce,V=e=>e;function va(e){return({dispatch:t,getState:n})=>r=>i=>typeof i==`function`?i(t,n,e):r(i)}var ya=va(),ba=va,xa=typeof window<`u`&&window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__:function(){if(arguments.length!==0)return typeof arguments[0]==`object`?qr:qr.apply(null,arguments)};typeof window<`u`&&window.__REDUX_DEVTOOLS_EXTENSION__&&window.__REDUX_DEVTOOLS_EXTENSION__;function Sa(e,t){function n(...n){if(t){let r=t(...n);if(!r)throw Error(jo(0));return{type:e,payload:r.payload,...`meta`in r&&{meta:r.meta},...`error`in r&&{error:r.error}}}return{type:e,payload:n[0]}}return n.toString=()=>`${e}`,n.type=e,n.match=t=>Yr(t)&&t.type===e,n}var Ca=class e extends Array{constructor(...t){super(...t),Object.setPrototypeOf(this,e.prototype)}static get[Symbol.species](){return e}concat(...e){return super.concat.apply(this,e)}prepend(...t){return t.length===1&&Array.isArray(t[0])?new e(...t[0].concat(this)):new e(...t.concat(this))}};function wa(e){return li(e)?_a(e,()=>{}):e}function Ta(e,t,n){return e.has(t)?e.get(t):e.set(t,n(t)).get(t)}function Ea(e){return typeof e==`boolean`}var Da=()=>function(e){let{thunk:t=!0,immutableCheck:n=!0,serializableCheck:r=!0,actionCreatorCheck:i=!0}=e??{},a=new Ca;return t&&(Ea(t)?a.push(ya):a.push(ba(t.extraArgument))),a},Oa=`RTK_autoBatch`,H=()=>e=>({payload:e,meta:{[Oa]:!0}}),ka=e=>t=>{setTimeout(t,e)},Aa=(e,t)=>n=>{let r=!1,i=()=>{r||(r=!0,cancelAnimationFrame(a),clearTimeout(o),n())},a=e(i),o=setTimeout(i,t)},ja=(e={type:`raf`})=>t=>(...n)=>{let r=t(...n),i=!0,a=!1,o=!1,s=new Set,c=e.type===`tick`?queueMicrotask:e.type===`raf`?typeof window<`u`&&window.requestAnimationFrame?Aa(window.requestAnimationFrame,100):ka(10):e.type===`callback`?e.queueNotification:ka(e.timeout),l=()=>{o=!1,a&&(a=!1,s.forEach(e=>e()))};return Object.assign({},r,{subscribe(e){let t=r.subscribe(()=>i&&e());return s.add(e),()=>{t(),s.delete(e)}},dispatch(e){try{return i=!e?.meta?.[Oa],a=!i,a&&(o||(o=!0,c(l))),r.dispatch(e)}finally{i=!0}}})},Ma=e=>function(t){let{autoBatch:n=!0}=t??{},r=new Ca(e);return n&&r.push(ja(typeof n==`object`?n:void 0)),r};function Na(e){let t=Da(),{reducer:n=void 0,middleware:r,devTools:i=!0,duplicateMiddlewareCheck:a=!0,preloadedState:o=void 0,enhancers:s=void 0}=e||{},c;if(typeof n==`function`)c=n;else if(Ur(n))c=Kr(n);else throw Error(jo(1));let l;l=typeof r==`function`?r(t):t();let u=qr;i&&(u=xa({trace:!1,...typeof i==`object`&&i}));let d=Ma(Jr(...l)),f=typeof s==`function`?s(d):d(),p=u(...f);return Wr(c,o,p)}function Pa(e){let t={},n=[],r,i={addCase(e,n){let r=typeof e==`string`?e:e.type;if(!r)throw Error(jo(28));if(r in t)throw Error(jo(29));return t[r]=n,i},addAsyncThunk(e,r){return r.pending&&(t[e.pending.type]=r.pending),r.rejected&&(t[e.rejected.type]=r.rejected),r.fulfilled&&(t[e.fulfilled.type]=r.fulfilled),r.settled&&n.push({matcher:e.settled,reducer:r.settled}),i},addMatcher(e,t){return n.push({matcher:e,reducer:t}),i},addDefaultCase(e){return r=e,i}};return e(i),[t,n,r]}function Fa(e){return typeof e==`function`}function Ia(e,t){let[n,r,i]=Pa(t),a;if(Fa(e))a=()=>wa(e());else{let t=wa(e);a=()=>t}function o(e=a(),t){let o=[n[t.type],...r.filter(({matcher:e})=>e(t)).map(({reducer:e})=>e)];return o.filter(e=>!!e).length===0&&(o=[i]),o.reduce((e,n)=>{if(n){if(ci(e)){let r=n(e,t);return r===void 0?e:r}if(li(e))return _a(e,e=>n(e,t));{let r=n(e,t);if(r===void 0){if(e===null)return e;throw Error(`A case reducer on a non-draftable value must not return undefined`)}return r}}return e},e)}return o.getInitialState=a,o}var La=`ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW`,Ra=(e=21)=>{let t=``,n=e;for(;n--;)t+=La[Math.random()*64|0];return t},za=Symbol.for(`rtk-slice-createasyncthunk`);function Ba(e,t){return`${e}/${t}`}function Va({creators:e}={}){let t=e?.asyncThunk?.[za];return function(e){let{name:n,reducerPath:r=n}=e;if(!n)throw Error(jo(11));let i=(typeof e.reducers==`function`?e.reducers(Wa()):e.reducers)||{},a=Object.keys(i),o={sliceCaseReducersByName:{},sliceCaseReducersByType:{},actionCreators:{},sliceMatchers:[]},s={addCase(e,t){let n=typeof e==`string`?e:e.type;if(!n)throw Error(jo(12));if(n in o.sliceCaseReducersByType)throw Error(jo(13));return o.sliceCaseReducersByType[n]=t,s},addMatcher(e,t){return o.sliceMatchers.push({matcher:e,reducer:t}),s},exposeAction(e,t){return o.actionCreators[e]=t,s},exposeCaseReducer(e,t){return o.sliceCaseReducersByName[e]=t,s}};a.forEach(r=>{let a=i[r],o={reducerName:r,type:Ba(n,r),createNotation:typeof e.reducers==`function`};Ka(a)?Ja(o,a,s,t):Ga(o,a,s)});function c(){let[t={},n=[],r=void 0]=typeof e.extraReducers==`function`?Pa(e.extraReducers):[e.extraReducers],i={...t,...o.sliceCaseReducersByType};return Ia(e.initialState,e=>{for(let t in i)e.addCase(t,i[t]);for(let t of o.sliceMatchers)e.addMatcher(t.matcher,t.reducer);for(let t of n)e.addMatcher(t.matcher,t.reducer);r&&e.addDefaultCase(r)})}let l=e=>e,u=new Map,d=new WeakMap,f;function p(e,t){return f||=c(),f(e,t)}function m(){return f||=c(),f.getInitialState()}function h(t,n=!1){function r(e){let i=e[t];return i===void 0&&n&&(i=Ta(d,r,m)),i}function i(t=l){return Ta(Ta(u,n,()=>new WeakMap),t,()=>{let r={};for(let[i,a]of Object.entries(e.selectors??{}))r[i]=Ha(a,t,()=>Ta(d,t,m),n);return r})}return{reducerPath:t,getSelectors:i,get selectors(){return i(r)},selectSlice:r}}let g={name:n,reducer:p,actions:o.actionCreators,caseReducers:o.sliceCaseReducersByName,getInitialState:m,...h(r),injectInto(e,{reducerPath:t,...n}={}){let i=t??r;return e.inject({reducerPath:i,reducer:p},n),{...g,...h(i,!0)}}};return g}}function Ha(e,t,n,r){function i(i,...a){let o=t(i);return o===void 0&&r&&(o=n()),e(o,...a)}return i.unwrapped=e,i}var Ua=Va();function Wa(){function e(e,t){return{_reducerDefinitionType:`asyncThunk`,payloadCreator:e,...t}}return e.withTypes=()=>e,{reducer(e){return Object.assign({[e.name](...t){return e(...t)}}[e.name],{_reducerDefinitionType:`reducer`})},preparedReducer(e,t){return{_reducerDefinitionType:`reducerWithPrepare`,prepare:e,reducer:t}},asyncThunk:e}}function Ga({type:e,reducerName:t,createNotation:n},r,i){let a,o;if(`reducer`in r){if(n&&!qa(r))throw Error(jo(17));a=r.reducer,o=r.prepare}else a=r;i.addCase(e,a).exposeCaseReducer(t,a).exposeAction(t,o?Sa(e,o):Sa(e))}function Ka(e){return e._reducerDefinitionType===`asyncThunk`}function qa(e){return e._reducerDefinitionType===`reducerWithPrepare`}function Ja({type:e,reducerName:t},n,r,i){if(!i)throw Error(jo(18));let{payloadCreator:a,fulfilled:o,pending:s,rejected:c,settled:l,options:u}=n,d=i(e,a,u);r.exposeAction(t,d),o&&r.addCase(d.fulfilled,o),s&&r.addCase(d.pending,s),c&&r.addCase(d.rejected,c),l&&r.addMatcher(d.settled,l),r.exposeCaseReducer(t,{fulfilled:o||Ya,pending:s||Ya,rejected:c||Ya,settled:l||Ya})}function Ya(){}var Xa=`task`,Za=`listener`,Qa=`completed`,$a=`cancelled`,eo=`task-${$a}`,to=`task-${Qa}`,no=`${Za}-${$a}`,ro=`${Za}-${Qa}`,io=class{constructor(e){this.code=e,this.message=`${Xa} ${$a} (reason: ${e})`}code;name=`TaskAbortError`;message},ao=(e,t)=>{if(typeof e!=`function`)throw TypeError(jo(32))},oo=()=>{},so=(e,t=oo)=>(e.catch(t),e),co=(e,t)=>(e.addEventListener(`abort`,t,{once:!0}),()=>e.removeEventListener(`abort`,t)),lo=e=>{if(e.aborted)throw new io(e.reason)};function uo(e,t){let n=oo;return new Promise((r,i)=>{let a=()=>i(new io(e.reason));if(e.aborted){a();return}n=co(e,a),t.finally(()=>n()).then(r,i)}).finally(()=>{n=oo})}var fo=async(e,t)=>{try{return await Promise.resolve(),{status:`ok`,value:await e()}}catch(e){return{status:e instanceof io?`cancelled`:`rejected`,error:e}}finally{t?.()}},po=e=>t=>so(uo(e,t).then(t=>(lo(e),t))),mo=e=>{let t=po(e);return e=>t(new Promise(t=>setTimeout(t,e)))},{assign:ho}=Object,go={},_o=`listenerMiddleware`,vo=(e,t)=>{let n=t=>co(e,()=>t.abort(e.reason));return(r,i)=>{ao(r,`taskExecutor`);let a=new AbortController;n(a);let o=fo(async()=>{lo(e),lo(a.signal);let t=await r({pause:po(a.signal),delay:mo(a.signal),signal:a.signal});return lo(a.signal),t},()=>a.abort(to));return i?.autoJoin&&t.push(o.catch(oo)),{result:po(e)(o),cancel(){a.abort(eo)}}}},yo=(e,t)=>{let n=async(n,r)=>{lo(t);let i=()=>{},a=[new Promise((t,r)=>{let a=e({predicate:n,effect:(e,n)=>{n.unsubscribe(),t([e,n.getState(),n.getOriginalState()])}});i=()=>{a(),r()}})];r!=null&&a.push(new Promise(e=>setTimeout(e,r,null)));try{let e=await uo(t,Promise.race(a));return lo(t),e}finally{i()}};return((e,t)=>so(n(e,t)))},bo=e=>{let{type:t,actionCreator:n,matcher:r,predicate:i,effect:a}=e;if(t)i=Sa(t).match;else if(n)t=n.type,i=n.match;else if(r)i=r;else if(!i)throw Error(jo(21));return ao(a,`options.listener`),{predicate:i,type:t,effect:a}},xo=ho(e=>{let{type:t,predicate:n,effect:r}=bo(e);return{id:Ra(),effect:r,type:t,predicate:n,pending:new Set,unsubscribe:()=>{throw Error(jo(22))}}},{withTypes:()=>xo}),So=(e,t)=>{let{type:n,effect:r,predicate:i}=bo(t);return Array.from(e.values()).find(e=>(typeof n==`string`?e.type===n:e.predicate===i)&&e.effect===r)},Co=e=>{e.pending.forEach(e=>{e.abort(no)})},wo=(e,t)=>()=>{for(let e of t.keys())Co(e);e.clear()},To=(e,t,n)=>{try{e(t,n)}catch(e){setTimeout(()=>{throw e},0)}},Eo=ho(Sa(`${_o}/add`),{withTypes:()=>Eo}),Do=Sa(`${_o}/removeAll`),Oo=ho(Sa(`${_o}/remove`),{withTypes:()=>Oo}),ko=(...e)=>{console.error(`${_o}/error`,...e)},Ao=(e={})=>{let t=new Map,n=new Map,r=e=>{let t=n.get(e)??0;n.set(e,t+1)},i=e=>{let t=n.get(e)??1;t===1?n.delete(e):n.set(e,t-1)},{extra:a,onError:o=ko}=e;ao(o,`onError`);let s=e=>(e.unsubscribe=()=>t.delete(e.id),t.set(e.id,e),t=>{e.unsubscribe(),t?.cancelActive&&Co(e)}),c=(e=>{let n=So(t,e)??xo(e);return s(n)});ho(c,{withTypes:()=>c});let l=e=>{let n=So(t,e);return n&&(n.unsubscribe(),e.cancelActive&&Co(n)),!!n};ho(l,{withTypes:()=>l});let u=async(e,n,s,l)=>{let u=new AbortController,d=yo(c,u.signal),f=[];try{e.pending.add(u),r(e),await Promise.resolve(e.effect(n,ho({},s,{getOriginalState:l,condition:(e,t)=>d(e,t).then(Boolean),take:d,delay:mo(u.signal),pause:po(u.signal),extra:a,signal:u.signal,fork:vo(u.signal,f),unsubscribe:e.unsubscribe,subscribe:()=>{t.set(e.id,e)},cancelActiveListeners:()=>{e.pending.forEach((e,t,n)=>{e!==u&&(e.abort(no),n.delete(e))})},cancel:()=>{u.abort(no),e.pending.delete(u)},throwIfCancelled:()=>{lo(u.signal)}})))}catch(e){e instanceof io||To(o,e,{raisedBy:`effect`})}finally{await Promise.all(f),u.abort(ro),i(e),e.pending.delete(u)}},d=wo(t,n);return{middleware:e=>n=>r=>{if(!Yr(r))return n(r);if(Eo.match(r))return c(r.payload);if(Do.match(r)){d();return}if(Oo.match(r))return l(r.payload);let i=e.getState(),a=()=>{if(i===go)throw Error(jo(23));return i},s;try{if(s=n(r),t.size>0){let n=e.getState(),s=Array.from(t.values());for(let t of s){let s=!1;try{s=t.predicate(r,n,i)}catch(e){s=!1,To(o,e,{raisedBy:`predicate`})}s&&u(t,r,e,a)}}}finally{i=go}return s},startListening:c,stopListening:l,clearListeners:d}};function jo(e){return`Minified Redux Toolkit error #${e}; visit https://redux-toolkit.js.org/Errors?code=${e} for the full message or use the non-minified dev environment for full errors. `}var Mo=Ua({name:`chartLayout`,initialState:{layoutType:`horizontal`,width:0,height:0,margin:{top:5,right:5,bottom:5,left:5},scale:1},reducers:{setLayout(e,t){e.layoutType=t.payload},setChartSize(e,t){e.width=t.payload.width,e.height=t.payload.height},setMargin(e,t){e.margin.top=t.payload.top??0,e.margin.right=t.payload.right??0,e.margin.bottom=t.payload.bottom??0,e.margin.left=t.payload.left??0},setScale(e,t){e.scale=t.payload}}}),No=Mo.actions,Po=No.setMargin,Fo=No.setLayout,Io=No.setChartSize,Lo=No.setScale,Ro=Mo.reducer;function zo(e,t,n){return Array.isArray(e)&&e&&t+n!==0?e.slice(t,n+1):e}function U(e){return Number.isFinite(e)}function Bo(e){return typeof e==`number`&&e>0&&Number.isFinite(e)}function Vo(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Ho(e){for(var t=1;t{if(t&&n){var r=n.width,i=n.height,a=t.align,o=t.verticalAlign,s=t.layout,c=t.position,l=t.offset,u=l===void 0?0:l;if(c!=null){if(Ae(c)){if(c===`top`&&F(e.top))return Ho(Ho({},e),{},{top:e.top+(i||0)+u});if(c===`bottom`&&F(e.bottom))return Ho(Ho({},e),{},{bottom:e.bottom+(i||0)+u});if(c===`left`&&F(e.left))return Ho(Ho({},e),{},{left:e.left+(r||0)+u});if(c===`right`&&F(e.right))return Ho(Ho({},e),{},{right:e.right+(r||0)+u})}return e}if((s===`vertical`||s===`horizontal`&&o===`middle`)&&a!==`center`&&F(e[a]))return Ho(Ho({},e),{},{[a]:e[a]+(r||0)});if((s===`horizontal`||s===`vertical`&&a===`center`)&&o!==`middle`&&F(e[o]))return Ho(Ho({},e),{},{[o]:e[o]+(i||0)})}return e},qo=(e,t)=>e===`horizontal`&&t===`xAxis`||e===`vertical`&&t===`yAxis`||e===`centric`&&t===`angleAxis`||e===`radial`&&t===`radiusAxis`,Jo=(e,t,n,r)=>{if(r)return e.map(e=>e.coordinate);var i,a,o=e.map(e=>(e.coordinate===t&&(i=!0),e.coordinate===n&&(a=!0),e.coordinate));return i||o.push(t),a||o.push(n),o},Yo=(e,t,n)=>{if(!e)return null;var r=e.duplicateDomain,i=e.type,a=e.range,o=e.scale,s=e.realScaleType,c=e.isCategorical,l=e.categoricalDomain,u=e.tickCount,d=e.ticks,f=e.niceTicks,p=e.axisType;if(!o)return null;var m=s===`scaleBand`&&o.bandwidth?o.bandwidth()/2:2,h=(t||n)&&i===`category`&&o.bandwidth?o.bandwidth()/m:0;return h=p===`angleAxis`&&a&&a.length>=2?le(a[0]-a[1])*2*h:h,t&&(d||f)?(d||f||[]).map((e,t)=>{var n=r?r.indexOf(e):e,i=o.map(n);return U(i)?{coordinate:i+h,value:e,offset:h,index:t}:null}).filter(be):c&&l?l.map((e,t)=>{var n=o.map(e);return U(n)?{coordinate:n+h,value:e,index:t,offset:h}:null}).filter(be):o.ticks&&!n&&u!=null?o.ticks(u).map((e,t)=>{var n=o.map(e);return U(n)?{coordinate:n+h,value:e,index:t,offset:h}:null}).filter(be):o.domain().map((e,t)=>{var n=o.map(e);return U(n)?{coordinate:n+h,value:r?r[e]:e,index:t,offset:h}:null}).filter(be)},Xo={sign:e=>{var t=e.length;if(!(t<=0)){var n=e[0]?.length;if(!(n==null||n<=0))for(var r=0;r=0?(s[0]=i,i+=u,s[1]=i):(s[0]=a,a+=u,s[1]=a)}}}},expand:Dt,none:St,silhouette:Ot,wiggle:kt,positive:e=>{var t=e.length;if(!(t<=0)){var n=e[0]?.length;if(!(n==null||n<=0))for(var r=0;r=0?(o[0]=i,i+=s,o[1]=i):(o[0]=0,o[1]=0)}}}}},Zo=(e,t,n)=>{var r=Xo[n]??St,i=Et().keys(t).value((e,t)=>Number(W(e,t,0))).order(Ct).offset(r)(e);return i.forEach((n,r)=>{n.forEach((n,i)=>{var a=W(e[i],t[r],0);Array.isArray(a)&&a.length===2&&F(a[0])&&F(a[1])&&(n[0]=a[0],n[1]=a[1])})}),i};function Qo(e){return e==null?void 0:String(e)}function $o(e){var t=e.axis,n=e.ticks,r=e.bandSize,i=e.entry,a=e.index,o=e.dataKey;if(t.type===`category`){if(!t.allowDuplicatedCategory&&t.dataKey&&!I(i[t.dataKey])){var s=ve(n,`value`,i[t.dataKey]);if(s)return s.coordinate+r/2}return n!=null&&n[a]?n[a].coordinate+r/2:null}var c=W(i,I(o)?t.dataKey:o),l=t.scale.map(c);return F(l)?l:null}var es=e=>{var t=e.flat(2).filter(F);return[Math.min(...t),Math.max(...t)]},ts=e=>[e[0]===1/0?0:e[0],e[1]===-1/0?0:e[1]],ns=(e,t,n)=>{if(e!=null&&Object.keys(e).length!==0)return ts(Object.keys(e).reduce((r,i)=>{var a=e[i];if(!a)return r;var o=a.stackedData.reduce((e,r)=>{var i=es(zo(r,t,n));return!U(i[0])||!U(i[1])?e:[Math.min(e[0],i[0]),Math.max(e[1],i[1])]},[1/0,-1/0]);return[Math.min(o[0],r[0]),Math.max(o[1],r[1])]},[1/0,-1/0]))},rs=/^dataMin[\s]*-[\s]*([0-9]+([.]{1}[0-9]+){0,1})$/,is=/^dataMax[\s]*\+[\s]*([0-9]+([.]{1}[0-9]+){0,1})$/,as=(e,t,n)=>{if(e&&e.scale&&e.scale.bandwidth){var r=e.scale.bandwidth();if(!n||r>0)return r}if(e&&t&&t.length>=2){for(var i=Er(t,e=>e.coordinate),a=[],o=0,s=1,c=i.length;su&&(d=Math.min(f,d));return d===1/0?0:d}return n?void 0:0};function os(e){var t=e.tooltipEntrySettings,n=e.dataKey,r=e.payload,i=e.value,a=e.name;return Ho(Ho({},t),{},{dataKey:n,payload:r,value:i,name:a})}function ss(e,t){if(e!=null)return String(e);if(typeof t==`string`)return t}var cs=(e,t)=>{if(t===`horizontal`)return e.relativeX;if(t===`vertical`)return e.relativeY},ls=(e,t)=>t===`centric`?e.angle:e.radius,us=e=>e.layout.width,ds=e=>e.layout.height,fs=e=>e.layout.scale,ps=e=>e.layout.margin,ms=B(e=>e.cartesianAxis.xAxis,e=>Object.values(e)),hs=B(e=>e.cartesianAxis.yAxis,e=>Object.values(e)),gs=`data-recharts-item-index`;function _s(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function vs(e){for(var t=1;te.brush.height;function Cs(e){return hs(e).reduce((e,t)=>t.orientation===`left`&&!t.mirror&&!t.hide?e+(typeof t.width==`number`?t.width:60):e,0)}function ws(e){return hs(e).reduce((e,t)=>t.orientation===`right`&&!t.mirror&&!t.hide?e+(typeof t.width==`number`?t.width:60):e,0)}function Ts(e){return ms(e).reduce((e,t)=>t.orientation===`top`&&!t.mirror&&!t.hide?e+(typeof t.height==`number`?t.height:30):e,0)}function Es(e){return ms(e).reduce((e,t)=>t.orientation===`bottom`&&!t.mirror&&!t.hide?e+(typeof t.height==`number`?t.height:30):e,0)}var Ds=B([us,ds,ps,Ss,Cs,ws,Ts,Es,Dr,Or],(e,t,n,r,i,a,o,s,c,l)=>{var u={left:(n.left||0)+i,right:(n.right||0)+a},d=vs(vs({},{top:(n.top||0)+o,bottom:(n.bottom||0)+s}),u),f=d.bottom;d.bottom+=r,d=Ko(d,c,l);var p=e-d.left-d.right,m=t-d.top-d.bottom;return vs(vs({brushBottom:f},d),{},{width:Math.max(p,0),height:Math.max(m,0)})}),Os=B(Ds,e=>({x:e.left,y:e.top,width:e.width,height:e.height})),ks=B(us,ds,(e,t)=>({x:0,y:0,width:e,height:t})),As=(0,p.createContext)(null),js=()=>(0,p.useContext)(As)!=null,Ms=e=>e.brush,Ns=B([Ms,Ds,ps],(e,t,n)=>({height:e.height,x:F(e.x)?e.x:t.left,y:F(e.y)?e.y:t.top+t.height+t.brushBottom-(n?.bottom||0),width:F(e.width)?e.width:t.width}));function Ps(e,t,{signal:n,edges:r}={}){let i,a=null,o=r!=null&&r.includes(`leading`),s=r==null||r.includes(`trailing`),c=()=>{a!==null&&(e.apply(i,a),i=void 0,a=null)},l=()=>{s&&c(),p()},u=null,d=()=>{u!=null&&clearTimeout(u),u=setTimeout(()=>{u=null,l()},t)},f=()=>{u!==null&&(clearTimeout(u),u=null)},p=()=>{f(),i=void 0,a=null},m=()=>{c()},h=function(...e){if(n?.aborted)return;i=this,a=e;let t=u==null;d(),o&&t&&c()};return h.schedule=d,h.cancel=p,h.flush=m,n?.addEventListener(`abort`,p,{once:!0}),h}function Fs(e,t=0,n={}){typeof n!=`object`&&(n={});let{leading:r=!1,trailing:i=!0,maxWait:a}=n,o=[,,];r&&(o[0]=`leading`),i&&(o[1]=`trailing`);let s,c=null,l=Ps(function(...t){s=e.apply(this,t),c=null},t,{edges:o}),u=function(...t){return a!=null&&(c===null&&(c=Date.now()),Date.now()-c>=a)?((r||i)&&(s=e.apply(this,t)),c=Date.now(),l.cancel(),l.schedule(),s):(l.apply(this,t),s)};return u.cancel=l.cancel,u.flush=()=>(l.flush(),s),u}function Is(e,t=0,n={}){let{leading:r=!0,trailing:i=!0}=n;return Fs(e,t,{leading:r,maxWait:t,trailing:i})}var Ls=function(e,t){var n=[...arguments].slice(2);if(typeof console<`u`&&console.warn&&(t===void 0&&console.warn(`LogUtils requires an error message argument`),!e)){if(t===void 0)console.warn(`Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.`);else{var r=0;console.warn(t.replace(/%s/g,()=>n[r++]))}}},Rs={width:`100%`,height:`100%`,debounce:0,minWidth:0,initialDimension:{width:-1,height:-1}},zs=(e,t,n)=>{var r=n.width,i=r===void 0?Rs.width:r,a=n.height,o=a===void 0?Rs.height:a,s=n.aspect,c=n.maxHeight,l=de(i)?e:Number(i),u=de(o)?t:Number(o);return s&&s>0&&(l?u=l/s:u&&(l=u*s),c&&u!=null&&u>c&&(u=c)),{calculatedWidth:l,calculatedHeight:u}},Bs={width:0,height:0,overflow:`visible`},Vs={width:0,overflowX:`visible`},Hs={height:0,overflowY:`visible`},Us={},Ws=e=>{var t=e.width,n=e.height,r=de(t),i=de(n);return r&&i?Bs:r?Vs:i?Hs:Us};function Gs(e){var t=e.width,n=e.height,r=e.aspect,i=t,a=n;return i===void 0&&a===void 0?(i=Rs.width,a=Rs.height):i===void 0?i=r&&r>0?void 0:Rs.width:a===void 0&&(a=r&&r>0?void 0:Rs.height),{width:i,height:a}}var Ks=[`aspect`,`initialDimension`,`width`,`height`,`minWidth`,`minHeight`,`maxHeight`,`children`,`debounce`,`id`,`className`,`onResize`,`style`];function qs(){return qs=Object.assign?Object.assign.bind():function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n({width:n,height:r}),[n,r]);return cc(i)?p.createElement(sc.Provider,{value:i},t):null}var uc=()=>(0,p.useContext)(sc),dc=(0,p.forwardRef)((e,t)=>{var n=e.aspect,r=e.initialDimension,i=r===void 0?Rs.initialDimension:r,a=e.width,s=e.height,c=e.minWidth,l=c===void 0?Rs.minWidth:c,u=e.minHeight,d=e.maxHeight,f=e.children,m=e.debounce,h=m===void 0?Rs.debounce:m,g=e.id,_=e.className,v=e.onResize,y=e.style,b=y===void 0?{}:y,x=ac(e,Ks),S=(0,p.useRef)(null),C=(0,p.useRef)();C.current=v,(0,p.useImperativeHandle)(t,()=>S.current);var w=$s((0,p.useState)({containerWidth:i.width,containerHeight:i.height}),2),T=w[0],E=w[1],D=(0,p.useCallback)((e,t)=>{E(n=>{var r=Math.round(e),i=Math.round(t);return n.containerWidth===r&&n.containerHeight===i?n:{containerWidth:r,containerHeight:i}})},[]);(0,p.useEffect)(()=>{if(S.current==null||typeof ResizeObserver>`u`)return xe;var e=e=>{var t,n=e[0];if(n!=null){var r=n.contentRect,i=r.width,a=r.height;D(i,a),(t=C.current)==null||t.call(C,i,a)}};h>0&&(e=Is(e,h,{trailing:!0,leading:!1}));var t=new ResizeObserver(e),n=S.current.getBoundingClientRect(),r=n.width,i=n.height;return D(r,i),t.observe(S.current),()=>{t.disconnect()}},[D,h]);var O=T.containerWidth,k=T.containerHeight;Ls(!n||n>0,`The aspect(%s) must be greater than zero.`,n);var A=zs(O,k,{width:a,height:s,aspect:n,maxHeight:d}),j=A.calculatedWidth,M=A.calculatedHeight;return Ls(O<0||k<0||j!=null&&j>0||M!=null&&M>0,`The width(%s) and height(%s) of chart should be greater than 0, please check the style of container, or the props width(%s) and height(%s), diff --git a/internal/webui/static/assets/index-DyfuUCZL.js b/internal/webui/static/assets/index-DyfuUCZL.js deleted file mode 100644 index 65514dc..0000000 --- a/internal/webui/static/assets/index-DyfuUCZL.js +++ /dev/null @@ -1,28 +0,0 @@ -var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},c=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},l=(n,r,o)=>(o=n==null?{}:e(i(n)),c(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var u=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function E(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function D(e,t){return E(e.type,t,e.props)}function O(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function k(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var A=/\/+/g;function j(e,t){return typeof e==`object`&&e&&e.key!=null?k(``+e.key):t.toString(36)}function M(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function N(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,N(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+j(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(A,`$&/`)+`/`),N(o,r,i,``,function(e){return e})):o!=null&&(O(o)&&(o=D(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(A,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{t.exports=u()})),f=o((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m){if(n(c)!==null)m=!0,S||(S=!0,O());else{var t=n(l);t!==null&&j(x,t.startTime-e)}}}var S=!1,C=-1,w=5,T=-1;function E(){return g?!0:!(e.unstable_now()-Tt&&E());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&j(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?O():S=!1}}}var O;if(typeof y==`function`)O=function(){y(D)};else if(typeof MessageChannel<`u`){var k=new MessageChannel,A=k.port2;k.port1.onmessage=D,O=function(){A.postMessage(null)}}else O=function(){_(D,0)};function j(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,j(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,O()))),r},e.unstable_shouldYield=E,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),p=o(((e,t)=>{t.exports=f()})),m=o((e=>{var t=d();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=m()})),g=o((e=>{var t=p(),n=d(),r=h();function i(e){var t=`https://react.dev/errors/`+e;if(1te||(e.current=ee[te],ee[te]=null,te--)}function ie(e,t){te++,ee[te]=e.current,e.current=t}var ae=ne(null),oe=ne(null),se=ne(null),ce=ne(null);function le(e,t){switch(ie(se,t),ie(oe,e),ie(ae,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?qd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=qd(t),e=Jd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}re(ae),ie(ae,e)}function ue(){re(ae),re(oe),re(se)}function de(e){e.memoizedState!==null&&ie(ce,e);var t=ae.current,n=Jd(t,e.type);t!==n&&(ie(oe,e),ie(ae,n))}function fe(e){oe.current===e&&(re(ae),re(oe)),ce.current===e&&(re(ce),rp._currentValue=R)}var pe,me;function he(e){if(pe===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);pe=t&&t[1]||``,me=-1)`:-1i||c[r]!==l[i]){var u=` -`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{ge=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?he(n):``}function ve(e,t){switch(e.tag){case 26:case 27:case 5:return he(e.type);case 16:return he(`Lazy`);case 13:return e.child!==t&&t!==null?he(`Suspense Fallback`):he(`Suspense`);case 19:return he(`SuspenseList`);case 0:case 15:return _e(e.type,!1);case 11:return _e(e.type.render,!1);case 1:return _e(e.type,!0);case 31:return he(`Activity`);default:return``}}function ye(e){try{var t=``,n=null;do t+=ve(e,n),n=e,e=e.return;while(e);return t}catch(e){return` -Error generating stack: `+e.message+` -`+e.stack}}var be=Object.prototype.hasOwnProperty,xe=t.unstable_scheduleCallback,Se=t.unstable_cancelCallback,Ce=t.unstable_shouldYield,we=t.unstable_requestPaint,Te=t.unstable_now,Ee=t.unstable_getCurrentPriorityLevel,De=t.unstable_ImmediatePriority,Oe=t.unstable_UserBlockingPriority,ke=t.unstable_NormalPriority,Ae=t.unstable_LowPriority,je=t.unstable_IdlePriority,Me=t.log,Ne=t.unstable_setDisableYieldValue,Pe=null,Fe=null;function Ie(e){if(typeof Me==`function`&&Ne(e),Fe&&typeof Fe.setStrictMode==`function`)try{Fe.setStrictMode(Pe,e)}catch{}}var Le=Math.clz32?Math.clz32:Be,Re=Math.log,ze=Math.LN2;function Be(e){return e>>>=0,e===0?32:31-(Re(e)/ze|0)|0}var Ve=256,He=262144,Ue=4194304;function We(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Ge(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=We(n))):i=We(o):i=We(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=We(n))):i=We(o)):i=We(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function Ke(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function qe(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Je(){var e=Ue;return Ue<<=1,!(Ue&62914560)&&(Ue=4194304),e}function Ye(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function Xe(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Ze(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),un=!1;if(ln)try{var dn={};Object.defineProperty(dn,"passive",{get:function(){un=!0}}),window.addEventListener(`test`,dn,dn),window.removeEventListener(`test`,dn,dn)}catch{un=!1}var fn=null,pn=null,mn=null;function hn(){if(mn)return mn;var e,t=pn,n=t.length,r,i=`value`in fn?fn.value:fn.textContent,a=i.length;for(e=0;e=qn),Xn=` `,Zn=!1;function Qn(e,t){switch(e){case`keyup`:return Gn.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function $n(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var er=!1;function tr(e,t){switch(e){case`compositionend`:return $n(t);case`keypress`:return t.which===32?(Zn=!0,Xn):null;case`textInput`:return e=t.data,e===Xn&&Zn?null:e;default:return null}}function nr(e,t){if(er)return e===`compositionend`||!Kn&&Qn(e,t)?(e=hn(),mn=pn=fn=null,er=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=wr(n)}}function Er(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Er(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Dr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=It(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=It(e.document)}return t}function Or(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var kr=ln&&`documentMode`in document&&11>=document.documentMode,Ar=null,jr=null,Mr=null,Nr=!1;function Pr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Nr||Ar==null||Ar!==It(r)||(r=Ar,`selectionStart`in r&&Or(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Mr&&Cr(Mr,r)||(Mr=r,r=jd(jr,`onSelect`),0>=o,i-=o,Ei=1<<32-Le(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),Fi&&Oi(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),Fi&&Oi(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return Fi&&Oi(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),Fi&&Oi(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===O&&ka(l)===r.type){n(e,r.sibling),c=a(r,o.props),Ia(c,o),c.return=e,e=c;break a}n(e,r);break}t(e,r),r=r.sibling}o.type===y?(c=pi(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=fi(o.type,o.key,o.props,null,e.mode,c),Ia(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l){if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}n(e,r);break}t(e,r),r=r.sibling}c=gi(o,e.mode,c),c.return=e,e=c}return s(e);case O:return o=ka(o),b(e,r,o,c)}if(F(o))return h(e,r,o,c);if(M(o)){if(l=M(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Fa(o),c);if(o.$$typeof===C)return b(e,r,ra(e,o),c);La(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=mi(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{Pa=0;var i=b(e,t,n,r);return Na=null,i}catch(t){if(t===Ca||t===Ta)throw t;var a=ci(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var za=Ra(!0),Ba=Ra(!1),Va=!1;function Ha(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ua(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Wa(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Ga(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,zl&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=ai(e),ii(e,null,n),t}return ti(e,r,t,n),ai(e)}function Ka(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,$e(e,n)}}function qa(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ja=!1;function Ya(){if(Ja){var e=ma;if(e!==null)throw e}}function Xa(e,t,n,r){Ja=!1;var i=e.updateQueue;Va=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(Hl&f)===f:(r&f)===f){f!==0&&f===pa&&(Ja=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var h=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(h=g.payload,typeof h==`function`){d=h.call(_,d,f);break a}d=h;break a;case 3:h.flags=h.flags&-65537|128;case 0:if(h=g.payload,f=typeof h==`function`?h.call(_,d,f):h,f==null)break a;d=m({},d,f);break a;case 2:Va=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Xl|=o,e.lanes=o,e.memoizedState=d}}function Za(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function Qa(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=I.T,s={};I.T=s,Ps(e,!1,t,n);try{var c=i(),l=I.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Ns(e,t,_a(c,r),gu(e)):Ns(e,t,r,gu(e))}catch(n){Ns(e,t,{then:function(){},status:`rejected`,reason:n},gu())}finally{L.p=a,o!==null&&s.types!==null&&(o.types=s.types),I.T=o}}function Cs(){}function ws(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=Ts(e).queue;Ss(e,a,t,R,n===null?Cs:function(){return Es(e),n(r)})}function Ts(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:R,baseState:R,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Io,lastRenderedState:R},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Io,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Es(e){var t=Ts(e);t.next===null&&(t=e.alternate.memoizedState),Ns(e,t.next.queue,{},gu())}function Ds(){return na(rp)}function Os(){return Mo().memoizedState}function ks(){return Mo().memoizedState}function As(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=gu();e=Wa(n);var r=Ga(t,e,n);r!==null&&(vu(r,t,n),Ka(r,t,n)),t={cache:la()},e.payload=t;return}t=t.return}}function js(e,t,n){var r=gu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Fs(e)?Is(t,n):(n=ni(e,t,n,r),n!==null&&(vu(n,e,r),Ls(n,t,r)))}function Ms(e,t,n){Ns(e,t,n,gu())}function Ns(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Fs(e))Is(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Sr(s,o))return ti(e,t,i,0),Bl===null&&ei(),!1}catch{}if(n=ni(e,t,i,r),n!==null)return vu(n,e,r),Ls(n,t,r),!0}return!1}function Ps(e,t,n,r){if(r={lane:2,revertLane:hd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Fs(e)){if(t)throw Error(i(479))}else t=ni(e,n,r,2),t!==null&&vu(t,e,2)}function Fs(e){var t=e.alternate;return e===ho||t!==null&&t===ho}function Is(e,t){vo=_o=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Ls(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,$e(e,n)}}var Rs={readContext:na,use:Po,useCallback:B,useContext:B,useEffect:B,useImperativeHandle:B,useLayoutEffect:B,useInsertionEffect:B,useMemo:B,useReducer:B,useRef:B,useState:B,useDebugValue:B,useDeferredValue:B,useTransition:B,useSyncExternalStore:B,useId:B,useHostTransitionStatus:B,useFormState:B,useActionState:B,useOptimistic:B,useMemoCache:B,useCacheRefresh:B};Rs.useEffectEvent=B;var zs={readContext:na,use:Po,useCallback:function(e,t){return jo().memoizedState=[e,t===void 0?null:t],e},useContext:na,useEffect:ls,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),ss(4194308,4,hs.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ss(4194308,4,e,t)},useInsertionEffect:function(e,t){ss(4,2,e,t)},useMemo:function(e,t){var n=jo();t=t===void 0?null:t;var r=e();if(yo){Ie(!0);try{e()}finally{Ie(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=jo();if(n!==void 0){var i=n(t);if(yo){Ie(!0);try{n(t)}finally{Ie(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=js.bind(null,ho,e),[r.memoizedState,e]},useRef:function(e){var t=jo();return e={current:e},t.memoizedState=e},useState:function(e){e=Go(e);var t=e.queue,n=Ms.bind(null,ho,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:_s,useDeferredValue:function(e,t){return bs(jo(),e,t)},useTransition:function(){var e=Go(!1);return e=Ss.bind(null,ho,e.queue,!0,!1),jo().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=ho,a=jo();if(Fi){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),Bl===null)throw Error(i(349));Hl&127||Bo(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,ls(Ho.bind(null,r,o,e),[e]),r.flags|=2048,as(9,{destroy:void 0},Vo.bind(null,r,o,n,t),null),n},useId:function(){var e=jo(),t=Bl.identifierPrefix;if(Fi){var n=Di,r=Ei;n=(r&~(1<<32-Le(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=bo++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[ot]=t,o[st]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Bd(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&jc(t)}}return Ic(t),Mc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&jc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=se.current,Hi(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=Ni,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[ot]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Ld(e.nodeValue,n)),e||zi(t,!0)}else e=Kd(e).createTextNode(r),e[ot]=t,t.stateNode=e}return Ic(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Hi(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[ot]=t}else Ui(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Ic(t),e=!1}else n=Wi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(uo(t),t):(uo(t),null);if(t.flags&128)throw Error(i(558))}return Ic(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=Hi(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[ot]=t}else Ui(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Ic(t),a=!1}else a=Wi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(uo(t),t):(uo(t),null)}return uo(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Pc(t,t.updateQueue),Ic(t),null);case 4:return ue(),e===null&&Dd(t.stateNode.containerInfo),Ic(t),null;case 10:return Xi(t.type),Ic(t),null;case 19:if(re(fo),r=t.memoizedState,r===null)return Ic(t),null;if(a=!!(t.flags&128),o=r.rendering,o===null){if(a)Fc(r,!1);else{if(Yl!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=po(e),o!==null){for(t.flags|=128,Fc(r,!1),e=o.updateQueue,t.updateQueue=e,Pc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)di(n,e),n=n.sibling;return ie(fo,fo.current&1|2),Fi&&Oi(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Te()>au&&(t.flags|=128,a=!0,Fc(r,!1),t.lanes=4194304)}}else{if(!a){if(e=po(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,Pc(t,e),Fc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!Fi)return Ic(t),null}else 2*Te()-r.renderingStartTime>au&&n!==536870912&&(t.flags|=128,a=!0,Fc(r,!1),t.lanes=4194304)}r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(Ic(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Te(),e.sibling=null,n=fo.current,ie(fo,a?n&1|2:n&1),Fi&&Oi(t,r.treeForkCount),e);case 22:case 23:return uo(t),ro(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(Ic(t),t.subtreeFlags&6&&(t.flags|=8192)):Ic(t),n=t.updateQueue,n!==null&&Pc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&re(ya),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Xi(ca),Ic(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function Rc(e,t){switch(ji(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Xi(ca),ue(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return fe(t),null;case 31:if(t.memoizedState!==null){if(uo(t),t.alternate===null)throw Error(i(340));Ui()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(uo(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));Ui()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return re(fo),null;case 4:return ue(),null;case 10:return Xi(t.type),null;case 22:case 23:return uo(t),ro(),e!==null&&re(ya),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Xi(ca),null;case 25:return null;default:return null}}function zc(e,t){switch(ji(t),t.tag){case 3:Xi(ca),ue();break;case 26:case 27:case 5:fe(t);break;case 4:ue();break;case 31:t.memoizedState!==null&&uo(t);break;case 13:uo(t);break;case 19:re(fo);break;case 10:Xi(t.type);break;case 22:case 23:uo(t),ro(),e!==null&&re(ya);break;case 24:Xi(ca)}}function Bc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Ju(t,t.return,e)}}function Vc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Ju(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Ju(t,t.return,e)}}function Hc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{Qa(t,n)}catch(t){Ju(e,e.return,t)}}}function Uc(e,t,n){n.props=G(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Ju(e,t,n)}}function Wc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Ju(e,t,n)}}function Gc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null){if(typeof r==`function`)try{r()}catch(n){Ju(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Ju(e,t,n)}else n.current=null}}function Kc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Ju(e,e.return,t)}}function qc(e,t,n){try{var r=e.stateNode;Vd(r,e.type,n,t),r[st]=t}catch(t){Ju(e,e.return,t)}}function Jc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&nf(e.type)||e.tag===4}function Yc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Jc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&nf(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Xc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=$t));else if(r!==4&&(r===27&&nf(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Xc(e,t,n),e=e.sibling;e!==null;)Xc(e,t,n),e=e.sibling}function Zc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&nf(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(Zc(e,t,n),e=e.sibling;e!==null;)Zc(e,t,n),e=e.sibling}function Qc(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Bd(t,r,n),t[ot]=e,t[st]=n}catch(t){Ju(e,e.return,t)}}var $c=!1,el=!1,tl=!1,nl=typeof WeakSet==`function`?WeakSet:Set,rl=null;function il(e,t){if(e=e.containerInfo,Wd=fp,e=Dr(e),Or(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(Gd={focusedElem:e,selectionRange:n},fp=!1,rl=t;rl!==null;)if(t=rl,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,rl=e;else for(;rl!==null;){switch(t=rl,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Bd(o,r,n),o[ot]=e,yt(o),r=o;break a;case`link`:var s=Kf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=Tr(s,h),v=Tr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,I.T=null,n=fu,fu=null;var o=lu,s=uu;if(cu=0,q=lu=null,uu=0,zl&6)throw Error(i(331));var c=zl;if(zl|=4,Pl(o.current),El(o,o.current,s,n),zl=c,cd(0,!1),Fe&&typeof Fe.onPostCommitFiberRoot==`function`)try{Fe.onPostCommitFiberRoot(Pe,o)}catch{}return!0}finally{L.p=a,I.T=r,Wu(e,t)}}function qu(e,t,n){t=vi(n,t),t=Xs(e.stateNode,t,2),e=Ga(e,t,2),e!==null&&(Xe(e,2),sd(e))}function Ju(e,t,n){if(e.tag===3)qu(e,e,n);else for(;t!==null;){if(t.tag===3){qu(t,e,n);break}if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(su===null||!su.has(r))){e=vi(n,e),n=Zs(2),r=Ga(t,n,2),r!==null&&(Qs(n,r,t,e),Xe(r,2),sd(r));break}}t=t.return}}function Yu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Rl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(ql=!0,i.add(n),e=Xu.bind(null,e,t,n),t.then(e,e))}function Xu(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,Bl===e&&(Hl&n)===n&&(Yl===4||Yl===3&&(Hl&62914560)===Hl&&300>Te()-ru?!(zl&2)&&Tu(e,0):Ql|=n,eu===Hl&&(eu=0)),sd(e)}function Zu(e,t){t===0&&(t=Je()),e=ri(e,t),e!==null&&(Xe(e,t),sd(e))}function Qu(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Zu(e,n)}function $u(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),Zu(e,n)}function ed(e,t){return xe(e,t)}var td=null,nd=null,rd=!1,id=!1,ad=!1,od=0;function sd(e){e!==nd&&e.next===null&&(nd===null?td=nd=e:nd=nd.next=e),id=!0,rd||(rd=!0,md())}function cd(e,t){if(!ad&&id){ad=!0;do for(var n=!1,r=td;r!==null;){if(!t){if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Le(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,pd(r,a))}else a=Hl,a=Ge(r,r===Bl?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||Ke(r,a)||(n=!0,pd(r,a))}r=r.next}while(n);ad=!1}}function ld(){ud()}function ud(){id=rd=!1;var e=0;od!==0&&Xd()&&(e=od);for(var t=Te(),n=null,r=td;r!==null;){var i=r.next,a=dd(r,t);a===0?(r.next=null,n===null?td=i:n.next=i,i===null&&(nd=n)):(n=r,(e!==0||a&3)&&(id=!0)),r=i}cu!==0&&cu!==5||cd(e,!1),od!==0&&(od=0)}function dd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Hd(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function Ef(e,t,n){var r=Tf;if(r&&typeof t==`string`&&t){var i=Rt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),bf.has(i)||(bf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Bd(t,`link`,e),yt(t),r.head.appendChild(t)))}}function Df(e){Sf.D(e),Ef(`dns-prefetch`,e,null)}function Of(e,t){Sf.C(e,t),Ef(`preconnect`,e,t)}function kf(e,t,n){Sf.L(e,t,n);var r=Tf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Rt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Rt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Rt(n.imageSizes)+`"]`)):i+=`[href="`+Rt(e)+`"]`;var a=i;switch(t){case`style`:a=Ff(e);break;case`script`:a=zf(e)}yf.has(a)||(e=m({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),yf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(If(a))||t===`script`&&r.querySelector(Bf(a))||(t=r.createElement(`link`),Bd(t,`link`,e),yt(t),r.head.appendChild(t)))}}function Af(e,t){Sf.m(e,t);var n=Tf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Rt(r)+`"][href="`+Rt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=zf(e)}if(!yf.has(a)&&(e=m({rel:`modulepreload`,href:e},t),yf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Bf(a)))return}r=n.createElement(`link`),Bd(r,`link`,e),yt(r),n.head.appendChild(r)}}}function jf(e,t,n){Sf.S(e,t,n);var r=Tf;if(r&&e){var i=vt(r).hoistableStyles,a=Ff(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(If(a)))s.loading=5;else{e=m({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=yf.get(a))&&Uf(e,n);var c=o=r.createElement(`link`);yt(c),Bd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Hf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Mf(e,t){Sf.X(e,t);var n=Tf;if(n&&e){var r=vt(n).hoistableScripts,i=zf(e),a=r.get(i);a||(a=n.querySelector(Bf(i)),a||(e=m({src:e,async:!0},t),(t=yf.get(i))&&Wf(e,t),a=n.createElement(`script`),yt(a),Bd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Nf(e,t){Sf.M(e,t);var n=Tf;if(n&&e){var r=vt(n).hoistableScripts,i=zf(e),a=r.get(i);a||(a=n.querySelector(Bf(i)),a||(e=m({src:e,async:!0,type:`module`},t),(t=yf.get(i))&&Wf(e,t),a=n.createElement(`script`),yt(a),Bd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Pf(e,t,n,r){var a=(a=se.current)?xf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Ff(n.href),n=vt(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Ff(n.href);var o=vt(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(If(e)))&&!o._p&&(s.instance=o,s.state.loading=5),yf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},yf.set(e,n),o||Rf(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=zf(n),n=vt(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Ff(e){return`href="`+Rt(e)+`"`}function If(e){return`link[rel="stylesheet"][`+e+`]`}function Lf(e){return m({},e,{"data-precedence":e.precedence,precedence:null})}function Rf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Bd(t,`link`,n),yt(t),e.head.appendChild(t))}function zf(e){return`[src="`+Rt(e)+`"]`}function Bf(e){return`script[async]`+e}function Vf(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Rt(n.href)+`"]`);if(r)return t.instance=r,yt(r),r;var a=m({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),yt(r),Bd(r,`style`,a),Hf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Ff(n.href);var o=e.querySelector(If(a));if(o)return t.state.loading|=4,t.instance=o,yt(o),o;r=Lf(n),(a=yf.get(a))&&Uf(r,a),o=(e.ownerDocument||e).createElement(`link`),yt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Bd(o,`link`,r),t.state.loading|=4,Hf(o,n.precedence,e),t.instance=o;case`script`:return o=zf(n.src),(a=e.querySelector(Bf(o)))?(t.instance=a,yt(a),a):(r=n,(a=yf.get(o))&&(r=m({},n),Wf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),yt(a),Bd(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Hf(r,n.precedence,e));return t.instance}function Hf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Jf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Yf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Xf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Ff(r.href),a=t.querySelector(If(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=$f.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,yt(a);return}a=t.ownerDocument||t,r=Lf(r),(i=yf.get(i))&&Uf(r,i),a=a.createElement(`link`),yt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Bd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=$f.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Zf=0;function Qf(e,t){return e.stylesheets&&e.count===0&&tp(e,e.stylesheets),0Zf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function $f(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)tp(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var ep=null;function tp(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,ep=new Map,t.forEach(np,e),ep=null,$f.call(e))}function np(e,t){if(!(t.state.loading&4)){var n=ep.get(e);if(n)var r=n.get(null);else{n=new Map,ep.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=g()})),v=l(d(),1),y=_(),b=`modulepreload`,x=function(e){return`/`+e},S={},C=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=x(t,n),t=s(t),t in S)return;S[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:b,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},w=/^(?:[a-z][a-z0-9+.-]*:|[\\/]{2})/i,T=/^[\\/]{2}/;function E(e,t){return t+e.replace(/\\/g,`/`)}var D=`popstate`;function O(e){return typeof e==`object`&&!!e&&`pathname`in e&&`search`in e&&`hash`in e&&`state`in e&&`key`in e}function k(e={}){function t(e,t){let n=t.state?.masked,{pathname:r,search:i,hash:a}=n||e.location;return P(``,{pathname:r,search:i,hash:a},t.state&&t.state.usr||null,t.state&&t.state.key||`default`,n?{pathname:e.location.pathname,search:e.location.search,hash:e.location.hash}:void 0)}function n(e,t){return typeof t==`string`?t:F(t)}return L(t,n,null,e)}function A(e,t){if(e===!1||e==null)throw Error(t)}function j(e,t){if(!e){typeof console<`u`&&console.warn(t);try{throw Error(t)}catch{}}}function M(){return Math.random().toString(36).substring(2,10)}function N(e,t){return{usr:e.state,key:e.key,idx:t,masked:e.mask?{pathname:e.pathname,search:e.search,hash:e.hash}:void 0}}function P(e,t,n=null,r,i){return{pathname:typeof e==`string`?e:e.pathname,search:``,hash:``,...typeof t==`string`?I(t):t,state:n,key:t&&t.key||r||M(),mask:i}}function F({pathname:e=`/`,search:t=``,hash:n=``}){return t&&t!==`?`&&(e+=t.charAt(0)===`?`?t:`?`+t),n&&n!==`#`&&(e+=n.charAt(0)===`#`?n:`#`+n),e}function I(e){let t={};if(e){let n=e.indexOf(`#`);n>=0&&(t.hash=e.substring(n),e=e.substring(0,n));let r=e.indexOf(`?`);r>=0&&(t.search=e.substring(r),e=e.substring(0,r)),e&&(t.pathname=e)}return t}function L(e,t,n,r={}){let{window:i=document.defaultView,v5Compat:a=!1}=r,o=i.history,s=`POP`,c=null,l=u();l??(l=0,o.replaceState({...o.state,idx:l},``));function u(){return(o.state||{idx:null}).idx}function d(){s=`POP`;let e=u(),t=e==null?null:e-l;l=e,c&&c({action:s,location:h.location,delta:t})}function f(e,t){s=`PUSH`;let r=O(e)?e:P(h.location,e,t);n&&n(r,e),l=u()+1;let d=N(r,l),f=h.createHref(r.mask||r);try{o.pushState(d,``,f)}catch(e){if(e instanceof DOMException&&e.name===`DataCloneError`)throw e;i.location.assign(f)}a&&c&&c({action:s,location:h.location,delta:1})}function p(e,t){s=`REPLACE`;let r=O(e)?e:P(h.location,e,t);n&&n(r,e),l=u();let i=N(r,l),d=h.createHref(r.mask||r);o.replaceState(i,``,d),a&&c&&c({action:s,location:h.location,delta:0})}function m(e){return R(i,e)}let h={get action(){return s},get location(){return e(i,o)},listen(e){if(c)throw Error(`A history only accepts one active listener`);return i.addEventListener(D,d),c=e,()=>{i.removeEventListener(D,d),c=null}},createHref(e){return t(i,e)},createURL:m,encodeLocation(e){let t=m(e);return{pathname:t.pathname,search:t.search,hash:t.hash}},push:f,replace:p,go(e){return o.go(e)}};return h}function R(e,t,n=!1){let r=`http://localhost`;e&&(r=e.location.origin===`null`?e.location.href:e.location.origin),A(r,`No window.location.(origin|href) available to create URL`);let i=typeof t==`string`?t:F(t);return i=i.replace(/ $/,`%20`),!n&&T.test(i)&&(i=r+i),new URL(i,r)}function ee(e,t,n=`/`){return te(e,t,n,!1)}function te(e,t,n,r,i){let a=be((typeof t==`string`?I(t):t).pathname||`/`,n);if(a==null)return null;let o=i??ne(e),s=null,c=ye(a);for(let e=0;s==null&&e{let c={relativePath:s===void 0?e.path||``:s,caseSensitive:e.caseSensitive===!0,childrenIndex:a,route:e};if(c.relativePath.startsWith(`/`)){if(!c.relativePath.startsWith(r)&&o)return;A(c.relativePath.startsWith(r),`Absolute route path "${c.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),c.relativePath=c.relativePath.slice(r.length)}let l=Oe([r,c.relativePath]),u=n.concat(c);e.children&&e.children.length>0&&(A(e.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${l}".`),re(e.children,t,u,l,o)),!(e.path==null&&!e.index)&&t.push({path:l,score:pe(l,e.index),routesMeta:u.map((e,t)=>{let[n,r]=ve(e.relativePath,e.caseSensitive,t===u.length-1);return{...e,matcher:n,compiledParams:r}})})};return e.forEach((e,t)=>{if(e.path===``||!e.path?.includes(`?`))a(e,t);else for(let n of ie(e.path))a(e,t,!0,n)}),t}function ie(e){let t=e.split(`/`);if(t.length===0)return[];let[n,...r]=t,i=n.endsWith(`?`),a=n.replace(/\?$/,``);if(r.length===0)return i?[a,``]:[a];let o=ie(r.join(`/`)),s=[];return s.push(...o.map(e=>e===``?a:[a,e].join(`/`))),i&&s.push(...o),s.map(t=>e.startsWith(`/`)&&t===``?`/`:t)}function ae(e){e.sort((e,t)=>e.score===t.score?me(e.routesMeta.map(e=>e.childrenIndex),t.routesMeta.map(e=>e.childrenIndex)):t.score-e.score)}var oe=/^:[\w-]+$/,se=3,ce=2,le=1,ue=10,de=-2,fe=e=>e===`*`;function pe(e,t){let n=e.split(`/`),r=n.length;return n.some(fe)&&(r+=de),t&&(r+=ce),n.filter(e=>!fe(e)).reduce((e,t)=>e+(oe.test(t)?se:t===``?le:ue),r)}function me(e,t){return e.length===t.length&&e.slice(0,-1).every((e,n)=>e===t[n])?e[e.length-1]-t[t.length-1]:0}function he(e,t,n=!1){let{routesMeta:r}=e,i={},a=`/`,o=[];for(let e=0;e{if(t===`*`){let e=s[r]||``;o=a.slice(0,a.length-e.length).replace(/(.)\/+$/,`$1`)}let i=s[r];return e[t]=n&&!i?void 0:(i||``).replace(/%2F/g,`/`),e},{}),pathname:a,pathnameBase:o,pattern:e}}function ve(e,t=!1,n=!0){j(e===`*`||!e.endsWith(`*`)||e.endsWith(`/*`),`Route path "${e}" will be treated as if it were "${e.replace(/\*$/,`/*`)}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${e.replace(/\*$/,`/*`)}".`);let r=[],i=`^`+e.replace(/\/*\*?$/,``).replace(/^\/*/,`/`).replace(/[\\.*+^${}|()[\]]/g,`\\$&`).replace(/\/:([\w-]+)(\?)?/g,(e,t,n,i,a)=>{if(r.push({paramName:t,isOptional:n!=null}),n){let t=a.charAt(i+e.length);return t&&t!==`/`?`/([^\\/]*)`:`(?:/([^\\/]*))?`}return`/([^\\/]+)`}).replace(/\/([\w-]+)\?(\/|$)/g,`(/$1)?$2`);return e.endsWith(`*`)?(r.push({paramName:`*`}),i+=e===`*`||e===`/*`?`(.*)$`:`(?:\\/(.+)|\\/*)$`):n?i+=`\\/*$`:e!==``&&e!==`/`&&(i+=`(?:(?=\\/|$))`),[new RegExp(i,t?void 0:`i`),r]}function ye(e){try{return e.split(`/`).map(e=>decodeURIComponent(e).replace(/\//g,`%2F`)).join(`/`)}catch(t){return j(!1,`The URL path "${e}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${t}).`),e}}function be(e,t){if(t===`/`)return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith(`/`)?t.length-1:t.length,r=e.charAt(n);return r&&r!==`/`?null:e.slice(n)||`/`}function xe(e,t=`/`){let{pathname:n,search:r=``,hash:i=``}=typeof e==`string`?I(e):e,a;return n?(n=De(n),a=n.startsWith(`/`)?Se(n.substring(1),`/`):Se(n,t)):a=t,{pathname:a,search:je(r),hash:Me(i)}}function Se(e,t){let n=ke(t).split(`/`);return e.split(`/`).forEach(e=>{e===`..`?n.length>1&&n.pop():e!==`.`&&n.push(e)}),n.length>1?n.join(`/`):`/`}function Ce(e,t,n,r){return`Cannot include a '${e}' character in a manually specified \`to.${t}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${n}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function we(e){return e.filter((e,t)=>t===0||e.route.path&&e.route.path.length>0)}function Te(e){let t=we(e);return t.map((e,n)=>n===t.length-1?e.pathname:e.pathnameBase)}function Ee(e,t,n,r=!1){let i;typeof e==`string`?i=I(e):(i={...e},A(!i.pathname||!i.pathname.includes(`?`),Ce(`?`,`pathname`,`search`,i)),A(!i.pathname||!i.pathname.includes(`#`),Ce(`#`,`pathname`,`hash`,i)),A(!i.search||!i.search.includes(`#`),Ce(`#`,`search`,`hash`,i)));let a=e===``||i.pathname===``,o=a?`/`:i.pathname,s;if(o==null)s=n;else{let e=t.length-1;if(!r&&o.startsWith(`..`)){let t=o.split(`/`);for(;t[0]===`..`;)t.shift(),--e;i.pathname=t.join(`/`)}s=e>=0?t[e]:`/`}let c=xe(i,s),l=o&&o!==`/`&&o.endsWith(`/`),u=(a||o===`.`)&&n.endsWith(`/`);return!c.pathname.endsWith(`/`)&&(l||u)&&(c.pathname+=`/`),c}var De=e=>e.replace(/[\\/]{2,}/g,`/`),Oe=e=>De(e.join(`/`)),ke=e=>e.replace(/\/+$/,``),Ae=e=>ke(e).replace(/^\/*/,`/`),je=e=>!e||e===`?`?``:e.startsWith(`?`)?e:`?`+e,Me=e=>!e||e===`#`?``:e.startsWith(`#`)?e:`#`+e,Ne=class{constructor(e,t,n,r=!1){this.status=e,this.statusText=t||``,this.internal=r,n instanceof Error?(this.data=n.toString(),this.error=n):this.data=n}};function Pe(e){return e!=null&&typeof e.status==`number`&&typeof e.statusText==`string`&&typeof e.internal==`boolean`&&`data`in e}function Fe(e){return Oe(e.map(e=>e.route.path).filter(Boolean))||`/`}var Ie=typeof window<`u`&&window.document!==void 0&&window.document.createElement!==void 0;function Le(e,t){let n=e;if(typeof n!=`string`||!w.test(n))return{absoluteURL:void 0,isExternal:!1,to:n};let r=n,i=!1;if(Ie)try{let e=new URL(window.location.href),r=T.test(n)?new URL(E(n,e.protocol)):new URL(n),a=be(r.pathname,t);r.origin===e.origin&&a!=null?n=a+r.search+r.hash:i=!0}catch{j(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:r,isExternal:i,to:n}}Object.getOwnPropertyNames(Object.prototype).sort().join(`\0`);var Re=[`POST`,`PUT`,`PATCH`,`DELETE`];new Set(Re);var ze=[`GET`,...Re];new Set(ze);var Be=[`about:`,`blob:`,`chrome:`,`chrome-untrusted:`,`content:`,`data:`,`devtools:`,`file:`,`filesystem:`,`javascript:`];function Ve(e){try{return Be.includes(new URL(e).protocol)}catch{return!1}}var He=v.createContext(null);He.displayName=`DataRouter`;var Ue=v.createContext(null);Ue.displayName=`DataRouterState`;var We=v.createContext(!1);function Ge(){return v.useContext(We)}var Ke=v.createContext({isTransitioning:!1});Ke.displayName=`ViewTransition`;var qe=v.createContext(new Map);qe.displayName=`Fetchers`;var Je=v.createContext(null);Je.displayName=`Await`;var Ye=v.createContext(null);Ye.displayName=`Navigation`;var Xe=v.createContext(null);Xe.displayName=`Location`;var Ze=v.createContext({outlet:null,matches:[],isDataRoute:!1});Ze.displayName=`Route`;var Qe=v.createContext(null);Qe.displayName=`RouteError`;var $e=`REACT_ROUTER_ERROR`,et=`REDIRECT`,tt=`ROUTE_ERROR_RESPONSE`;function nt(e){if(e.startsWith(`${$e}:${et}:{`))try{let t=JSON.parse(e.slice(28));if(typeof t==`object`&&t&&typeof t.status==`number`&&typeof t.statusText==`string`&&typeof t.location==`string`&&typeof t.reloadDocument==`boolean`&&typeof t.replace==`boolean`)return t}catch{}}function rt(e){if(e.startsWith(`${$e}:${tt}:{`))try{let t=JSON.parse(e.slice(40));if(typeof t==`object`&&t&&typeof t.status==`number`&&typeof t.statusText==`string`)return new Ne(t.status,t.statusText,t.data)}catch{}}function it(e,{relative:t}={}){A(at(),`useHref() may be used only in the context of a component.`);let{basename:n,navigator:r}=v.useContext(Ye),{hash:i,pathname:a,search:o}=pt(e,{relative:t}),s=a;return n!==`/`&&(s=a===`/`?n:Oe([n,a])),r.createHref({pathname:s,search:o,hash:i})}function at(){return v.useContext(Xe)!=null}function ot(){return A(at(),`useLocation() may be used only in the context of a component.`),v.useContext(Xe).location}var st=`You should call navigate() in a React.useEffect(), not when your component is first rendered.`;function ct(e){v.useContext(Ye).static||v.useLayoutEffect(e)}function lt(){let{isDataRoute:e}=v.useContext(Ze);return e?At():ut()}function ut(){A(at(),`useNavigate() may be used only in the context of a component.`);let e=v.useContext(He),{basename:t,navigator:n}=v.useContext(Ye),{matches:r}=v.useContext(Ze),{pathname:i}=ot(),a=JSON.stringify(Te(r)),o=v.useRef(!1);return ct(()=>{o.current=!0}),v.useCallback((r,s={})=>{if(j(o.current,st),!o.current)return;if(typeof r==`number`){n.go(r);return}let c=Ee(r,JSON.parse(a),i,s.relative===`path`);e==null&&t!==`/`&&(c.pathname=c.pathname===`/`?t:Oe([t,c.pathname])),(s.replace?n.replace:n.push)(c,s.state,s)},[t,n,a,i,e])}var dt=v.createContext(null);function ft(e){let t=v.useContext(Ze).outlet;return v.useMemo(()=>t&&v.createElement(dt.Provider,{value:e},t),[t,e])}function pt(e,{relative:t}={}){let{matches:n}=v.useContext(Ze),{pathname:r}=ot(),i=JSON.stringify(Te(n));return v.useMemo(()=>Ee(e,JSON.parse(i),r,t===`path`),[e,i,r,t])}function mt(e,t){return ht(e,t)}function ht(e,t,n){A(at(),`useRoutes() may be used only in the context of a component.`);let{navigator:r}=v.useContext(Ye),{matches:i}=v.useContext(Ze),a=i[i.length-1],o=a?a.params:{},s=a?a.pathname:`/`,c=a?a.pathnameBase:`/`,l=a&&a.route;{let e=l&&l.path||``;Mt(s,!l||e.endsWith(`*`)||e.endsWith(`*?`),`You rendered descendant (or called \`useRoutes()\`) at "${s}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. - -Please change the parent to .`)}let u=ot(),d;if(t){let e=typeof t==`string`?I(t):t;A(c===`/`||e.pathname?.startsWith(c),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${c}" but pathname "${e.pathname}" was given in the \`location\` prop.`),d=e}else d=u;let f=d.pathname||`/`,p=f;if(c!==`/`){let e=c.replace(/^\//,``).split(`/`);p=`/`+f.replace(/^\//,``).split(`/`).slice(e.length).join(`/`)}let m=n&&n.state.matches.length?n.state.matches.map(e=>Object.assign(e,{route:n.manifest[e.route.id]||e.route})):ee(e,{pathname:p});j(l||m!=null,`No routes matched location "${d.pathname}${d.search}${d.hash}" `),j(m==null||m[m.length-1].route.element!==void 0||m[m.length-1].route.Component!==void 0||m[m.length-1].route.lazy!==void 0,`Matched leaf route at location "${d.pathname}${d.search}${d.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let h=St(m&&m.map(e=>Object.assign({},e,{params:Object.assign({},o,e.params),pathname:Oe([c,r.encodeLocation?r.encodeLocation(e.pathname.replace(/%/g,`%25`).replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathname]),pathnameBase:e.pathnameBase===`/`?c:Oe([c,r.encodeLocation?r.encodeLocation(e.pathnameBase.replace(/%/g,`%25`).replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathnameBase])})),i,n);return t&&h?v.createElement(Xe.Provider,{value:{location:{pathname:`/`,search:``,hash:``,state:null,key:`default`,mask:void 0,...d},navigationType:`POP`}},h):h}function gt(){let e=kt(),t=Pe(e)?`${e.status} ${e.statusText}`:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,r=`rgba(200,200,200, 0.5)`,i={padding:`0.5rem`,backgroundColor:r},a={padding:`2px 4px`,backgroundColor:r},o=null;return console.error(`Error handled by React Router default ErrorBoundary:`,e),o=v.createElement(v.Fragment,null,v.createElement(`p`,null,`💿 Hey developer 👋`),v.createElement(`p`,null,`You can provide a way better UX than this when your app throws errors by providing your own `,v.createElement(`code`,{style:a},`ErrorBoundary`),` or`,` `,v.createElement(`code`,{style:a},`errorElement`),` prop on your route.`)),v.createElement(v.Fragment,null,v.createElement(`h2`,null,`Unexpected Application Error!`),v.createElement(`h3`,{style:{fontStyle:`italic`}},t),n?v.createElement(`pre`,{style:i},n):null,o)}var _t=v.createElement(gt,null),vt=class extends v.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!==`idle`&&e.revalidation===`idle`?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error===void 0?t.error:e.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){this.props.onError?this.props.onError(e,t):console.error(`React Router caught the following error during render`,e)}render(){let e=this.state.error;if(this.context&&typeof e==`object`&&e&&`digest`in e&&typeof e.digest==`string`){let t=rt(e.digest);t&&(e=t)}let t=e===void 0?this.props.children:v.createElement(Ze.Provider,{value:this.props.routeContext},v.createElement(Qe.Provider,{value:e,children:this.props.component}));return this.context?v.createElement(bt,{error:e},t):t}};vt.contextType=We;var yt=new WeakMap;function bt({children:e,error:t}){let{basename:n}=v.useContext(Ye);if(typeof t==`object`&&t&&`digest`in t&&typeof t.digest==`string`){let e=nt(t.digest);if(e){let r=yt.get(t);if(r)throw r;let i=Le(e.location,n),a=i.absoluteURL||i.to;if(Ve(a))throw Error(`Invalid redirect location`);if(Ie&&!yt.get(t)){if(i.isExternal||e.reloadDocument)window.location.href=a;else{let n=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(i.to,{replace:e.replace}));throw yt.set(t,n),n}}return v.createElement(`meta`,{httpEquiv:`refresh`,content:`0;url=${a}`})}}return e}function xt({routeContext:e,match:t,children:n}){let r=v.useContext(He);return r&&r.static&&r.staticContext&&(t.route.errorElement||t.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=t.route.id),v.createElement(Ze.Provider,{value:e},n)}function St(e,t=[],n){let r=n?.state;if(e==null){if(!r)return null;if(r.errors)e=r.matches;else if(t.length===0&&!r.initialized&&r.matches.length>0)e=r.matches;else return null}let i=e,a=r?.errors;if(a!=null){let e=i.findIndex(e=>e.route.id&&a?.[e.route.id]!==void 0);A(e>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(a).join(`,`)}`),i=i.slice(0,Math.min(i.length,e+1))}let o=!1,s=-1;if(n&&r){o=r.renderFallback;for(let e=0;e=0?i.slice(0,s+1):[i[0]];break}}}}let c=n?.onError,l=r&&c?(e,t)=>{c(e,{location:r.location,params:r.matches?.[0]?.params??{},pattern:Fe(r.matches),errorInfo:t})}:void 0;return i.reduceRight((e,n,c)=>{let u,d=!1,f=null,p=null;r&&(u=a&&n.route.id?a[n.route.id]:void 0,f=n.route.errorElement||_t,o&&(s<0&&c===0?(Mt(`route-fallback`,!1,"No `HydrateFallback` element provided to render during initial hydration"),d=!0,p=null):s===c&&(d=!0,p=n.route.hydrateFallbackElement||null)));let m=t.concat(i.slice(0,c+1)),h=()=>{let t;return t=u?f:d?p:n.route.Component?v.createElement(n.route.Component,null):n.route.element?n.route.element:e,v.createElement(xt,{match:n,routeContext:{outlet:e,matches:m,isDataRoute:r!=null},children:t})};return r&&(n.route.ErrorBoundary||n.route.errorElement||c===0)?v.createElement(vt,{location:r.location,revalidation:r.revalidation,component:f,error:u,children:h(),routeContext:{outlet:null,matches:m,isDataRoute:!0},onError:l}):h()},null)}function Ct(e){return`${e} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function wt(e){let t=v.useContext(He);return A(t,Ct(e)),t}function Tt(e){let t=v.useContext(Ue);return A(t,Ct(e)),t}function Et(e){let t=v.useContext(Ze);return A(t,Ct(e)),t}function Dt(e){let t=Et(e),n=t.matches[t.matches.length-1];return A(n.route.id,`${e} can only be used on routes that contain a unique "id"`),n.route.id}function Ot(){return Dt(`useRouteId`)}function kt(){let e=v.useContext(Qe),t=Tt(`useRouteError`),n=Dt(`useRouteError`);return e===void 0?t.errors?.[n]:e}function At(){let{router:e}=wt(`useNavigate`),t=Dt(`useNavigate`),n=v.useRef(!1);return ct(()=>{n.current=!0}),v.useCallback(async(r,i={})=>{j(n.current,st),n.current&&(typeof r==`number`?await e.navigate(r):await e.navigate(r,{fromRouteId:t,...i}))},[e,t])}var jt={};function Mt(e,t,n){!t&&!jt[e]&&(jt[e]=!0,j(!1,n))}v.memo(Nt);function Nt({routes:e,manifest:t,future:n,state:r,isStatic:i,onError:a}){return ht(e,void 0,{manifest:t,state:r,isStatic:i,onError:a,future:n})}function Pt({to:e,replace:t,state:n,relative:r}){A(at(),` may be used only in the context of a component.`);let{static:i}=v.useContext(Ye);j(!i,` must not be used on the initial render in a . This is a no-op, but you should modify your code so the is only ever rendered in response to some user interaction or state change.`);let{matches:a}=v.useContext(Ze),{pathname:o}=ot(),s=lt(),c=Ee(e,Te(a),o,r===`path`),l=JSON.stringify(c);return v.useEffect(()=>{s(JSON.parse(l),{replace:t,state:n,relative:r})},[s,l,r,t,n]),null}function Ft(e){return ft(e.context)}function It(e){A(!1,`A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .`)}function Lt({basename:e=`/`,children:t=null,location:n,navigationType:r=`POP`,navigator:i,static:a=!1,useTransitions:o}){A(!at(),`You cannot render a inside another . You should never have more than one in your app.`);let s=e.replace(/^\/*/,`/`),c=v.useMemo(()=>({basename:s,navigator:i,static:a,useTransitions:o,future:{}}),[s,i,a,o]);typeof n==`string`&&(n=I(n));let{pathname:l=`/`,search:u=``,hash:d=``,state:f=null,key:p=`default`,mask:m}=n,h=v.useMemo(()=>{let e=be(l,s);return e==null?null:{location:{pathname:e,search:u,hash:d,state:f,key:p,mask:m},navigationType:r}},[s,l,u,d,f,p,r,m]);return j(h!=null,` is not able to match the URL "${l}${u}${d}" because it does not start with the basename, so the won't render anything.`),h==null?null:v.createElement(Ye.Provider,{value:c},v.createElement(Xe.Provider,{children:t,value:h}))}function Rt({children:e,location:t}){return mt(zt(e),t)}v.Component;function zt(e,t=[]){let n=[];return v.Children.forEach(e,(e,r)=>{if(!v.isValidElement(e))return;let i=[...t,r];if(e.type===v.Fragment){n.push.apply(n,zt(e.props.children,i));return}A(e.type===It,`[${typeof e.type==`string`?e.type:e.type.name}] is not a component. All component children of must be a or `),A(!e.props.index||!e.props.children,`An index route cannot have child routes.`);let a={id:e.props.id||i.join(`-`),caseSensitive:e.props.caseSensitive,element:e.props.element,Component:e.props.Component,index:e.props.index,path:e.props.path,middleware:e.props.middleware,loader:e.props.loader,action:e.props.action,hydrateFallbackElement:e.props.hydrateFallbackElement,HydrateFallback:e.props.HydrateFallback,errorElement:e.props.errorElement,ErrorBoundary:e.props.ErrorBoundary,hasErrorBoundary:e.props.hasErrorBoundary===!0||e.props.ErrorBoundary!=null||e.props.errorElement!=null,shouldRevalidate:e.props.shouldRevalidate,handle:e.props.handle,lazy:e.props.lazy};e.props.children&&(a.children=zt(e.props.children,i)),n.push(a)}),n}var Bt=`get`,Vt=`application/x-www-form-urlencoded`;function Ht(e){return typeof HTMLElement<`u`&&e instanceof HTMLElement}function Ut(e){return Ht(e)&&e.tagName.toLowerCase()===`button`}function Wt(e){return Ht(e)&&e.tagName.toLowerCase()===`form`}function Gt(e){return Ht(e)&&e.tagName.toLowerCase()===`input`}function Kt(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function qt(e,t){return e.button===0&&(!t||t===`_self`)&&!Kt(e)}var Jt=null;function Yt(){if(Jt===null)try{new FormData(document.createElement(`form`),0),Jt=!1}catch{Jt=!0}return Jt}var Xt=new Set([`application/x-www-form-urlencoded`,`multipart/form-data`,`text/plain`]);function Zt(e){return e!=null&&!Xt.has(e)?(j(!1,`"${e}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${Vt}"`),null):e}function Qt(e,t){let n,r,i,a,o;if(Wt(e)){let o=e.getAttribute(`action`);r=o?be(o,t):null,n=e.getAttribute(`method`)||Bt,i=Zt(e.getAttribute(`enctype`))||Vt,a=new FormData(e)}else if(Ut(e)||Gt(e)&&(e.type===`submit`||e.type===`image`)){let o=e.form;if(o==null)throw Error(`Cannot submit a