@chenpu17/cc-gw 0.10.3 → 0.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/package.json +5 -5
  2. package/src/web/dist/assets/{About-BlRwok0C.js → About-DVONGhC8.js} +1 -1
  3. package/src/web/dist/assets/{ApiKeys-__yoww1B.js → ApiKeys-C3UcR3Xt.js} +1 -1
  4. package/src/web/dist/assets/{ConfirmDialog-MfZNB6Df.js → ConfirmDialog-Ci29hhDD.js} +1 -1
  5. package/src/web/dist/assets/{Dashboard-DW5yhjz2.js → Dashboard-C5pBcbjh.js} +1 -1
  6. package/src/web/dist/assets/{Events-DIHxXK2c.js → Events-DXuVlVli.js} +1 -1
  7. package/src/web/dist/assets/{Help-53lFT60j.js → Help-DoBQVllq.js} +1 -1
  8. package/src/web/dist/assets/{Login-wXXmJSKA.js → Login-BKBhBrF_.js} +1 -1
  9. package/src/web/dist/assets/{Logs-DlUZYPnC.js → Logs-Cl14ySgm.js} +1 -1
  10. package/src/web/dist/assets/{ProvidersWorkbench-B30mrfQ5.js → ProvidersWorkbench-CMXcjnjA.js} +1 -1
  11. package/src/web/dist/assets/{Settings-DRW9xcJ_.js → Settings-BxtiQXA0.js} +1 -1
  12. package/src/web/dist/assets/{Setup-Du0PQb1-.js → Setup-r8hTQIkT.js} +1 -1
  13. package/src/web/dist/assets/{apiKeys-CQ7yz9uu.js → apiKeys-C7NN8p2H.js} +1 -1
  14. package/src/web/dist/assets/{app-CDdTBXQ0.js → app-WMvqYU5W.js} +2 -2
  15. package/src/web/dist/assets/{gateway-CbmElcXM.js → gateway-T6-6vX59.js} +1 -1
  16. package/src/web/dist/assets/{landing-Cra6nfAd.js → landing-CQeQu2bM.js} +1 -1
  17. package/src/web/dist/assets/package-B6sWB4v8.js +1 -0
  18. package/src/web/dist/assets/{shared-ByllZUE0.js → shared-lc9s0nIr.js} +1 -1
  19. package/src/web/dist/assets/{useAppMutation-BebVa-9_.js → useAppMutation-aXncU1Ie.js} +1 -1
  20. package/src/web/dist/assets/{useProviderForm-Y_sOoFFu.js → useProviderForm-BRhWbili.js} +1 -1
  21. package/src/web/dist/index.html +1 -1
  22. package/src/web/dist/landing.html +2 -2
  23. package/src/web/dist/assets/package-B8-r92OZ.js +0 -1
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/Dashboard-DW5yhjz2.js","assets/vendor-CITafuNQ.js","assets/PageToolbar-xVyzEX46.js","assets/global-D5dyWHqO.js","assets/global-CjRN4Kqi.css","assets/PageState-BzdQq00f.js","assets/select-BMTHg2Ox.js","assets/radix-Bux_1C5J.js","assets/router-BoBrmro7.js","assets/table-Cmf9Rq18.js","assets/charts-react-BQP6-BFp.js","assets/charts-core-BTTtAoo-.js","assets/charts-engine-CFhx8nba.js","assets/PageSection-BE4JZURK.js","assets/card-kh1_vqEo.js","assets/utils-C0ba36BP.js","assets/badge-DMgopDMC.js","assets/disclosure-Dpus5Sms.js","assets/metric-card-DMOLvKiC.js","assets/types-DYz12j32.js","assets/i18n-PeX9kBVO.js","assets/useEventStream-BYg6oKvQ.js","assets/apiKeys-CQ7yz9uu.js","assets/gateway-CbmElcXM.js","assets/query-DZF7K6hv.js","assets/Logs-DlUZYPnC.js","assets/label-Dg8bOG83.js","assets/DialogShell-iCyqEFRA.js","assets/tabs-kDNBpHZ8.js","assets/clipboard-BnAnBk_-.js","assets/popover-BPqySDlA.js","assets/segmented-control-C1wXb26c.js","assets/switch-BjJJ5nhB.js","assets/Events-DIHxXK2c.js","assets/ProvidersWorkbench-B30mrfQ5.js","assets/ConfirmDialog-MfZNB6Df.js","assets/useProviderForm-Y_sOoFFu.js","assets/useAppMutation-BebVa-9_.js","assets/ApiKeys-__yoww1B.js","assets/shared-ByllZUE0.js","assets/Settings-DRW9xcJ_.js","assets/Setup-Du0PQb1-.js","assets/About-BlRwok0C.js","assets/package-B8-r92OZ.js","assets/Help-53lFT60j.js","assets/Login-wXXmJSKA.js"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/Dashboard-C5pBcbjh.js","assets/vendor-CITafuNQ.js","assets/PageToolbar-xVyzEX46.js","assets/global-D5dyWHqO.js","assets/global-CjRN4Kqi.css","assets/PageState-BzdQq00f.js","assets/select-BMTHg2Ox.js","assets/radix-Bux_1C5J.js","assets/router-BoBrmro7.js","assets/table-Cmf9Rq18.js","assets/charts-react-BQP6-BFp.js","assets/charts-core-BTTtAoo-.js","assets/charts-engine-CFhx8nba.js","assets/PageSection-BE4JZURK.js","assets/card-kh1_vqEo.js","assets/utils-C0ba36BP.js","assets/badge-DMgopDMC.js","assets/disclosure-Dpus5Sms.js","assets/metric-card-DMOLvKiC.js","assets/types-DYz12j32.js","assets/i18n-PeX9kBVO.js","assets/useEventStream-BYg6oKvQ.js","assets/apiKeys-C7NN8p2H.js","assets/gateway-T6-6vX59.js","assets/query-DZF7K6hv.js","assets/Logs-Cl14ySgm.js","assets/label-Dg8bOG83.js","assets/DialogShell-iCyqEFRA.js","assets/tabs-kDNBpHZ8.js","assets/clipboard-BnAnBk_-.js","assets/popover-BPqySDlA.js","assets/segmented-control-C1wXb26c.js","assets/switch-BjJJ5nhB.js","assets/Events-DXuVlVli.js","assets/ProvidersWorkbench-CMXcjnjA.js","assets/ConfirmDialog-Ci29hhDD.js","assets/useProviderForm-BRhWbili.js","assets/useAppMutation-aXncU1Ie.js","assets/ApiKeys-C3UcR3Xt.js","assets/shared-lc9s0nIr.js","assets/Settings-BxtiQXA0.js","assets/Setup-r8hTQIkT.js","assets/About-DVONGhC8.js","assets/package-B6sWB4v8.js","assets/Help-DoBQVllq.js","assets/Login-BKBhBrF_.js"])))=>i.map(i=>d[i]);
2
2
  import{c as h,B as Re}from"./global-D5dyWHqO.js";import{J as Ee,r as n,j as t,K as ee,L as Le,N as De,O as Ne,P as qe,S as He,T as je,U as Me,V as Fe,W as Oe,X as Ke,Y as Ue,Z as _e,$ as ze,a0 as Be,a1 as _,a2 as z,a3 as We,a4 as te,a5 as oe,a6 as Ve,a7 as Ge,a8 as Qe,q as $e}from"./vendor-CITafuNQ.js";import{Q as Ye,u as Je}from"./query-DZF7K6hv.js";import{i as k,a as Xe,I as Ze,u as C}from"./i18n-PeX9kBVO.js";import{N as D,u as et,a as N,O as tt,b as O,B as ot,R as at,c as I}from"./router-BoBrmro7.js";import{S as st,a as ae,b as se,P as nt,C as ne,I as re,c as ie,d as le,R as de,L as ce,e as ue,f as rt,T as it,g as lt,h as dt,i as ct,j as ut,k as pe,_ as y,l as pt,m as mt,O as ht,n as gt,o as ft}from"./radix-Bux_1C5J.js";const q={themeMode:"cc-gw-theme",language:"cc-gw-language",dashboard:{endpointFilter:"cc-gw.dashboard.endpoint-filter"},logs:{visibleColumns:"cc-gw.logs.visible-columns",density:"cc-gw.logs.density",pageSize:"cc-gw.logs.page-size",providerFilter:"cc-gw.logs.provider-filter",endpointFilter:"cc-gw.logs.endpoint-filter",modelFilter:"cc-gw.logs.model-filter",statusFilter:"cc-gw.logs.status-filter",fromDate:"cc-gw.logs.from-date",toDate:"cc-gw.logs.to-date",selectedApiKeys:"cc-gw.logs.selected-api-keys",autoRefresh:"cc-gw.logs.auto-refresh"},apiKeys:{rangeDays:"cc-gw.api-keys.range-days",search:"cc-gw.api-keys.search",statusFilter:"cc-gw.api-keys.status-filter",viewMode:"cc-gw.api-keys.inventory-view-mode",activeTab:"cc-gw.api-keys.active-tab",quickStartDismissed:"cc-gw.api-keys.quick-start-dismissed"},modelManagement:{recentRouteTargets:"cc-gw.model-management.recent-route-targets"}},yt={title:"cc-gw 控制台",skipToContent:"跳转到主要内容",consoleSubtitle:"网关控制平面",environmentLabel:"运行状态",online:"服务在线",liveRpm:"实时 {{count}} RPM",commandPalette:{title:"命令面板",trigger:"搜索 / 跳转",placeholder:"搜索页面或操作…",empty:"没有匹配项",actions:"快速操作",toggleTheme:"切换浅色 / 深色主题",switchLanguage:"切换中 / 英文"}},vt={dashboard:"仪表盘",logs:"请求日志",events:"事件",models:"模型供应商",routing:"路由管理",providers:"模型与路由",apiKeys:"API 密钥",settings:"设置",help:"使用指南",about:"关于",group:{overview:"概览",configure:"配置",system:"系统"},more:"更多"},bt={zh:"简体中文",en:"English"},wt={loading:"加载中...",loadingShort:"加载中...",noData:"暂无数据",languageSelector:"语言选择",yes:"是",edit:"编辑",delete:"删除",create:"创建",save:"保存",saving:"保存中...",cancel:"取消",actions:{loading:"处理中...",refresh:"刷新",refreshing:"刷新中...",manualRefresh:"手动刷新",reset:"重置",previous:"上一步",next:"下一步",close:"关闭",openNavigation:"打开导航",closeNavigation:"关闭导航",save:"保存设置",saving:"保存中...",cancel:"取消",copy:"复制",download:"下载",testConnection:"测试连接",testingConnection:"测试中...",cleanup:"清理历史日志",cleaning:"清理中...",compact:"释放数据库空间",compacting:"整理中...",checkUpdates:"检查更新",logout:"退出登录"},theme:{label:"主题",light:"亮色",dark:"暗色",system:"跟随系统"},status:{success:"成功",pending:"进行中",error:"失败",enabled:"已启用",disabled:"已禁用"},notifications:{featureInProgress:"功能开发中,敬请期待。"},units:{request:"次",ms:"ms",token:"Tokens",msPerToken:"ms/Token"},noMatches:"无匹配项",unknownError:"未知错误",requestFailed:"请求失败,请稍后再试",filters:{activeCount:"{{count}} 个筛选条件",collapse:"收起筛选",expand:"展开筛选",allRequests:"全部请求"}},Pt={title:"登录 cc-gw 控制台",description:"启用 Web UI 访问控制后,请输入账号与密码继续。",fields:{username:"用户名",usernamePlaceholder:"请输入用户名",password:"密码",passwordPlaceholder:"请输入密码"},actions:{submit:"登录"},validation:{required:"请填写用户名和密码",failed:"登录失败,请检查账号或密码后重试"},hint:"如果忘记密码,可在服务器上通过 CLI 或编辑配置重置 Web 登录设置。",status:"已登录:{{username}}"},xt={description:"快速了解请求规模与实时运行状态。",labels:{providers:"Provider 数量",activeClientAddresses:"活跃来源地址",activeClientSessions:"活跃会话",uniqueClientAddressesLastHour:"1小时活跃客户端 IP",uniqueClientSessionsLastHour:"1小时会话",todayRequests:"今日请求",activeRequests:"活跃转发连接",throughput:"实时吞吐",requestsPerMinute:"RPM",outputTokensPerMinute:"TPM",cpu:"CPU 占用率",bandwidth:"网络带宽",networkIngress:"入口带宽",networkEgress:"出口带宽",database:"数据库",memory:"内存占用"},filters:{endpoint:"端点筛选",endpointAll:"全部端点",endpointAnthropic:"anthropic",endpointOpenAI:"openai"},status:{listeningLabel:"监听地址",listening:"监听:{{host}}:{{port}}",providers:"Provider 数量:{{value}}",todayRequests:"今日请求:{{value}}",active:"活动请求:{{value}}",dbSize:"数据库:{{value}}",memory:"内存占用:{{value}}"},toast:{overviewError:"统计数据获取失败",dailyError:"趋势数据获取失败",modelError:"模型统计获取失败",statusError:"状态信息获取失败",dbError:"数据库信息获取失败",recentError:"最近请求获取失败"},cards:{todayRequests:"今日请求数",todayInput:"今日输入 Tokens",todayCacheRead:"今日缓存读取",todayCacheCreation:"今日缓存写入",todayOutput:"今日输出 Tokens",todayCached:"今日缓存 Tokens",todayErrorRate:"今日错误率",cacheHitHint:"命中率 {{value}}%",errorCountHint:"{{value}} 次错误",yesterdayHint:"昨日同期 {{value}}",avgLatency:"平均响应耗时",systemResources:"系统资源"},charts:{trendTitle:"请求趋势",trendDesc:"最近 14 天请求量与平均延迟走势",latencyLabel:"平均延迟 (ms)",requestsTitle:"请求趋势",requestsDesc:"最近 14 天请求与 Token 走势",modelTitle:"模型调用分布",modelDesc:"近 7 天不同模型的调用次数与 Token 走势",barRequests:"请求数",lineInput:"输入 Tokens",lineOutput:"输出 Tokens",lineCached:"缓存 Tokens",lineCacheRead:"缓存读取",lineCacheCreation:"缓存写入",axisTokens:"Tokens",ttftLabel:"TTFT(ms)",tpotLabel:"TPOT(ms/Token)",ttftTitle:"TTFT 模型对比",ttftDesc:"比较不同模型的首 Token 耗时 (TTFT)",ttftEmpty:"暂无 TTFT 数据。",tpotTitle:"TPOT 模型对比",tpotDesc:"比较不同模型的平均 Token 耗时 (TPOT)",tpotEmpty:"暂无 TPOT 数据。",ttftAxis:"TTFT (ms)",tpotAxis:"TPOT (ms/Token)",empty:"暂无数据"},insights:{totalRequests:"趋势期总请求",totalRequestsHint:"最近 14 天累计请求量",busiestDay:"最忙的一天",busiestDayHint:"{{value}} 次请求",topModel:"最高频模型",topModelHint:"{{value}} 次调用",fastestTtft:"最快 TTFT 模型",fastestTtftHint:"{{value}} ms 首 Token"},recent:{title:"最新请求",subtitle:"仅展示最近 {{count}} 条记录",loading:"加载中...",empty:"暂无请求记录",routePlaceholder:"未指定",columns:{time:"时间",endpoint:"端点",provider:"Provider",route:"路由",latency:"耗时(ms)",status:"状态"}},attention:{title:"需要关注",subtitle:"实时推送的警告与错误事件",live:"实时",reconnecting:"重连中…",failed:"连接失败 — 刷新以重试",allClear:"最近无异常事件",viewAll:"查看全部事件"},sections:{performance:"性能详情"},setupProgress:{label:"初始化 {{done}}/{{total}}",cta:"继续设置 →"},guide:{title:"先走通这三步",subtitle:"把 Provider、路由和 API Key 配好后发起一条请求,仪表盘就会开始有数据。",startWizard:"开始引导设置",step1Title:"先配置 Provider",step1Desc:"先在模型供应商里接入至少 1 个上游模型服务。",step1DescDone:"当前已检测到 {{count}} 个 Provider,可直接继续下一步。",step1Cta:"去模型与路由",step2Title:"确认默认路由入口",step2Desc:"把一个端点或默认路由配置清楚,后续客户端就能稳定接入。",step2DescDone:"当前已有 {{count}} 个自定义端点,可继续检查默认映射是否合理。",step2Cta:"去模型与路由",step3Title:"发起第一条真实请求",step3Desc:"创建 API Key,然后从常用客户端打进来一条请求,让日志、路由和延迟开始有数据。",step3Cta:"去 API 密钥"},modelTable:{title:"模型性能摘要",description:"统计每个后端模型的请求数、平均耗时、TTFT 与 TPOT。",empty:"暂无模型统计数据。",columns:{model:"Provider/模型",requests:"请求数",latency:"平均耗时",ttft:"TTFT",tpot:"TPOT"}}},Tt={title:"请求日志",description:"查看近期请求,支持筛选 Provider、模型、成功状态及时间范围。",filtersTitle:"筛选条件",filtersDescription:"组合多种条件精准定位请求记录。",summary:{total:"记录总数:{{value}}"},filters:{provider:"Provider",providerAll:"全部 Provider",endpoint:"请求端点",endpointAll:"全部端点",endpointAnthropic:"anthropic",endpointOpenAI:"openai",apiKey:"API Key",apiKeyHint:"可多选,不选择时将展示全部密钥。",modelId:"模型 ID",modelPlaceholder:"如 deepseek-chat",status:"状态",statusAll:"全部",statusSuccess:"成功",statusError:"失败",startDate:"起始日期",endDate:"结束日期",apiKeyAll:"全部密钥",apiKeySelected:"{{count}} 个已选"},actions:{columns:"列设置",visibleCount:"已显示 {{count}} 列",manualRefresh:"手动刷新",autoRefresh:"自动刷新",refreshing:"刷新中...",export:"导出 ZIP 日志",exporting:"导出中...",detail:"详情"},quickViews:{all:"全部流量",errors:"仅看失败",today:"今天",anthropic:"Anthropic",openai:"OpenAI"},table:{loading:"正在加载日志...",empty:"未找到符合条件的日志记录。",density:{comfortable:"标准",compact:"紧凑"},requestedModelFallback:"未指定",apiKeyUnknown:"未知密钥",columns:{time:"时间",endpoint:"端点",provider:"Provider",requestedModel:"请求模型",routedModel:"路由模型",apiKey:"API Key",tokens:"Tokens",duration:"耗时",tokenIn:"输入",tokenOut:"输出",tokenCache:"缓存",latencyTtft:"TTFT",latencyTpot:"TPOT",status:"状态",error:"错误信息",actions:"操作"},pagination:{perPage:"每页",unit:"条",previous:"上一页",next:"下一页",pageLabel:"第 {{page}} / {{total}} 页"}},empty:{title:"日志还没有开始积累",subtitle:"发起一条真实请求后,这里会开始显示路由结果、耗时和状态。",filteredTitle:"当前筛选条件下没有匹配记录",filteredSubtitle:"可以重置筛选条件,或放宽时间范围、端点和状态。",actions:{reset:"重置筛选",apiKeys:"去 API 密钥"}},endpointAnthropic:"anthropic",endpointOpenAI:"openai",toast:{listError:{title:"日志获取失败",desc:"错误信息:{{message}}"},providerError:{title:"Provider 列表获取失败",desc:"错误信息:{{message}}"},exportSuccess:{title:"导出完成",desc:"ZIP 压缩包已开始下载,包内包含 `logs.json`。"},exportError:{title:"导出失败",desc:"错误信息:{{message}}"}},stream:{streaming:"流式",single:"单次"},detail:{title:"日志详情",id:"ID #{{id}}",infoSection:"基本信息",info:{time:"时间",sessionId:"Session ID",endpoint:"端点",provider:"Provider",requestedModel:"请求模型",noRequestedModel:"未指定",model:"路由模型",stream:"Stream",latency:"耗时",status:"状态",inputTokens:"输入 Tokens",cacheReadTokens:"缓存读取",cacheCreationTokens:"缓存写入",outputTokens:"输出 Tokens",ttft:"TTFT (首 Token 耗时)",tpot:"TPOT (平均 ms/Token)",error:"错误信息",errorSource:"错误来源"},errorSource:{none:"无",client:"客户端中断",gateway:"网关错误",upstream:"后端服务错误",unknown:"未分类"},summary:{route:"{{from}} → {{to}}",latency:"耗时:{{value}}",ttft:"TTFT:{{value}}",tpot:"TPOT:{{value}}",stream:"Stream:{{value}}"},payload:{title:"Payloads",helperWithUpstream:"客户端与上游链路内容已分开展示。",helperClientOnly:"当前请求未发生额外链路改写,仅展示客户端侧内容。",clientRequest:"客户端请求体",upstreamRequest:"上游请求体",upstreamResponse:"上游响应体",clientResponse:"客户端响应体",emptyRequest:"暂无请求内容",emptyResponse:"暂无响应内容",truncated:"仅显示前 {{shown}} / {{total}} 个字符。复制按钮仍会复制完整内容。"},apiKey:{title:"密钥信息",name:"密钥名称",identifier:"密钥 ID",masked:"掩码展示",maskedUnavailable:"暂无掩码信息",raw:"原始密钥",rawUnavailable:"未记录原始密钥",rawMasked:"原始密钥(已脱敏)",rawMaskedHint:"出于安全考虑,仅展示部分前后缀。如需完整值,请在上游服务中重新生成。",missing:"未记录",lastUsed:"最后使用"},copy:{requestSuccess:"请求体已复制到剪贴板。",responseSuccess:"响应体已复制到剪贴板。",keySuccess:"API 密钥已复制到剪贴板。",empty:"{{label}}为空,无法复制。",failure:"复制失败",failureFallback:"无法复制内容,请稍后再试。"},trace:{title:"请求链路",arrived:"客户端请求到达",ttft:"上游首 Token (TTFT)",complete:"请求完成",pending:"请求处理中…",streamOn:"stream",streamOff:"single",tpot:"TPOT",degraded:"未记录逐步耗时,按总耗时与 TTFT 推算。"},inline:{copyRequest:"复制请求体",copySession:"复制 Session ID",viewRule:"查看命中规则",sessionCopied:"Session ID 已复制到剪贴板。",expand:"展开行内预览",collapse:"收起行内预览"},loadError:"无法加载日志详情。"}},Ct={title:"模型提供商",description:"管理集成的模型服务,查看默认模型及支持能力。",emptyState:"暂无 Provider,请点击“新增提供商”以开始配置。",emptyStateSub:"点击上方按钮添加您的第一个提供商。",emptyFiltered:"当前筛选条件下没有匹配的 Provider。",count:"已配置:{{count}} 个 Provider",groupCount:"{{count}} 个提供商",filters:{searchPlaceholder:"按名称、ID 或 Base URL 搜索",typeAll:"全部类型"},list:{sortLabel:"排序方式",sortUsage:"按用量",sortName:"按名称",requests7d:"近 7 天 {{count}} 次请求",testOk:"最近一次测试成功",testFailed:"最近一次测试失败",untested:"未测试"},table:{status:"状态",name:"名称",type:"类型",baseUrl:"Base URL",models:"模型数",defaultModel:"默认模型",requests7d:"近 7 天请求",viewDetail:"查看详情"},status:{ready:"已就绪",needsDefault:"待设默认模型"},toast:{createSuccess:"已添加 Provider:{{name}}",updateSuccess:"已更新 Provider:{{name}}",testSuccess:"Provider 连通性检查通过。",testSuccessDesc:"状态:{{status}} · 耗时:{{duration}}",testFailure:"Provider 连通性检查失败:{{message}}",loadFailure:"获取配置失败:{{message}}",deleteSuccess:"已删除 Provider:{{name}}",deleteFailure:"删除 Provider 失败:{{message}}"},actions:{add:"新增提供商",refresh:"刷新",refreshing:"刷新中...",edit:"编辑",delete:"删除",test:"测试连接"},quickAddHuawei:{button:"一键添加华为云模型",title:"一键添加华为云模型",description:"输入 API Key 即可快速添加华为云 DeepSeek V3.1、KIMI-K2 与 Qwen3-235B-A22B 模型。",apiKeyLabel:"API Key",apiKeyPlaceholder:"请输入华为云 API Key",note:"完成后可在提供商列表中查看并进一步调整配置。",submit:"添加",providerLabel:"华为云",validation:{apiKey:"请填写 API Key"},toast:{success:"已添加华为云模型",added:"已添加 {{name}}",failure:"添加失败,请稍后重试"}},testDialog:{title:"连接测试选项",subtitle:"针对 {{name}} 的测试请求",description:"部分 Claude 兼容服务需要额外 Header 才能通过诊断。请选择需要附加的 Header,不勾选则保持最简请求。",headerValue:"Header 值:{{value}}",presetLabel:"模拟 Claude Code 请求(推荐)",presetDescription:"附加 Claude CLI 常用的 Header(anthropic-beta、x-app、user-agent 等)以提升兼容性。",presetPreviewSummary:"查看将附加的 Header 列表",preservedInfo:"以下 Header 将自动附加(来自当前配置):",cancel:"取消",primary:"开始测试",options:{beta:{label:"`anthropic-beta` 头",description:"启用 Claude Code 的实验特性(如工具流式);fox code_cc 等服务通常要求此头。"},browser:{label:"`anthropic-dangerous-direct-browser-access` 头",description:"标记请求来自受信客户端,Claude Code 默认会携带此头。"},xApp:{label:"`x-app` 头",description:"标识请求来源,Claude CLI 默认发送为 cli。"},userAgent:{label:"`user-agent` 头",description:"模拟 Claude CLI 的 User-Agent 值。"},accept:{label:"`accept` 头",description:"声明客户端接受 JSON 响应格式。"},acceptLanguage:{label:"`accept-language` 头",description:"兼容要求语言信息的服务。"},secFetchMode:{label:"`sec-fetch-mode` 头",description:"与浏览器/CLI 保持一致的访问信息。"},acceptEncoding:{label:"`accept-encoding` 头",description:"允许 gzip/deflate 压缩响应内容。"},stainlessHelper:{label:"`x-stainless-helper-method` 头",description:"表明请求使用 Claude CLI 的 stream helper。"},stainlessRetry:{label:"`x-stainless-retry-count` 头",description:"Claude CLI 当前的重试计数。"},stainlessTimeout:{label:"`x-stainless-timeout` 头",description:"Claude CLI 设定的超时时间(秒)。"},stainlessLang:{label:"`x-stainless-lang` 头",description:"Claude CLI 所使用的语言标识。"},stainlessPackage:{label:"`x-stainless-package-version` 头",description:"Claude CLI 的包版本号。"},stainlessOs:{label:"`x-stainless-os` 头",description:"调用方所在的操作系统。"},stainlessArch:{label:"`x-stainless-arch` 头",description:"调用方 CPU 架构信息。"},stainlessRuntime:{label:"`x-stainless-runtime` 头",description:"运行时环境标识,例如 node。"},stainlessRuntimeVersion:{label:"`x-stainless-runtime-version` 头",description:"运行时环境的版本号。"}}},noModelDialog:{title:"未配置模型",subtitle:"Provider「{{name}}」还没有可用于测试的模型",description:"连接测试需要先知道要请求哪个模型。请先为该 Provider 添加至少一个模型,或设置默认模型后再测试。",hint:"如果你只是想使用透传模式,也需要在实际请求或路由映射里指定模型;诊断测试不能自动猜测上游模型 ID。",primary:"去配置模型"},card:{baseUrl:"Base URL",defaultModelLabel:"默认模型",defaultModel:"默认模型:{{model}}",noDefault:"未设置默认模型",modelsTitle:"支持模型",noModels:"尚未配置模型。",authMode:"认证方式",providerDefault:"Provider 默认",modelCount:"{{count}} 个模型",passthrough:"透传模式",statModels:"模型",statRequests24h:"24h 请求",statAvgLatency:"平均延迟",routeCount:"被 {{count}} 条路由规则引用",health:{ok:"正常",fail:"异常",untested:"未测"}},drawer:{createTitle:"新增 Provider",editTitle:"编辑 Provider",quickStart:"快速配置",description:"配置基础信息与模型列表。",formSummary:"当前草稿",modelsDescription:"维护支持的模型列表。",defaultHint:"当前默认模型:{{model}}",summary:{type:"Provider 类型",auth:"认证方式",models:"模型数量",untitled:"未命名 Provider"},sections:{type:"选择 Provider 类型",basic:"填写基础信息",auth:"设置认证",checklist:"提交前检查"},steps:{basics:"基础与认证",modelsVerify:"模型与验证"},hints:{type:"先选择 Provider 模板,可自动填入推荐 Base URL。",basic:"ID 用于路由映射;显示名称用于界面展示。",auth:"根据上游接口要求选择 Header 认证方式。",customProvider:"自定义兼容服务",checkUrl:"确认 Base URL 指向上游 API 根路径。",checkAuth:"确认密钥与认证 Header 类型匹配。",checkModels:"如需路由提示和默认模型,请补充模型列表。",advancedTitle:"高级模式说明",advancedBody:"开启后可单独维护显示名称与模型别名;如果只是快速接入,保留默认同步即可。"},fields:{id:"Provider ID",idPlaceholder:"如 openai",label:"显示名称",labelPlaceholder:"如 官方主账号",baseUrl:"Base URL",baseUrlPlaceholder:"https://api.example.com/v1",type:"Provider 类型",apiKey:"API Key(可选)",apiKeyPlaceholder:"可留空以从环境变量读取",authMode:"认证方式",authModeHint:"选择 API 认证方式,填写对应的密钥值。",authModeApiKey:"X-API-Key",authModeProviderDefault:"Provider 默认",authModeAuthToken:"Authorization: Bearer",authModeXAuthToken:"X-Auth-Token",nonStreamViaStream:"非流式请求转上游流式",nonStreamViaStreamHint:"仅在客户端请求非流式时生效。网关向上游发送流式请求,读取完整 SSE 后一次性返回 JSON。",useAbsoluteUrl:"使用绝对路径",useAbsoluteUrlHint:"勾选后,Base URL 将原样作为上游请求地址(需填完整端点,如 https://x/internal/v1/messages),网关不再自动追加 /v1/messages、/v1/chat/completions 等后缀。协议(请求体格式)仍由 Provider 类型决定,互不影响。",streamUsage:"流式请求携带 usage 统计(默认关闭)",streamUsageHint:"⚠️ 兼容性风险:开启后网关会向上游附加 stream_options.include_usage 参数。部分 OpenAI 兼容模型服务不认识该参数,可能导致报错或流式返回为空。默认关闭;若开启后请求异常,请关闭此开关。多数上游本身就会返回 usage,无需开启。",models:"模型配置",showAdvanced:"显示高级选项",hideAdvanced:"隐藏高级选项",advancedOptions:"高级选项",addModel:"新增模型",modelId:"模型 ID",modelIdPlaceholder:"如 claude-sonnet-4-5-20250929",modelLabel:"显示名称(可选)",modelLabelPlaceholder:"如 GPT-4 旗舰",modelNonStreamViaStream:"非流式转流式",modelNonStreamViaStreamInherit:"使用 Provider 默认",modelNonStreamViaStreamEnabled:"启用",modelNonStreamViaStreamDisabled:"禁用",setDefault:"设为默认模型",removeModel:"删除模型",extraHeaders:"自定义请求头",extraHeadersHint:"附加到发往该 Provider 的上游请求,并覆盖客户端发送的同名头。认证类请求头(Authorization、x-api-key 等)由网关根据 API Key 统一设置,无法在此覆盖。",addHeader:"新增请求头",headerName:"名称",headerValue:"取值",headerNamePlaceholder:"如 X-App-Id",headerValuePlaceholder:"如 my-app",removeHeader:"删除"},probe:{button:"探测模型",needsBaseUrl:"请先填写 Base URL",title:"探测可用模型",subtitle:"从「{{name}}」的上游接口获取模型列表,勾选后导入。",loading:"正在向上游探测模型列表…",failedTitle:"探测失败",failedHint:"该供应商可能不支持模型列表接口,请手动添加模型。",failedFallback:"上游未返回可用的模型列表",searchPlaceholder:"搜索模型 ID 或名称",totalCount:"共 {{count}} 个模型",selectAll:"全选",clearSelection:"清空",selectedCount:"已选 {{count}} 个",emptyResult:"没有匹配的模型",imported:"已导入",importAction:"导入 {{count}} 个模型",importSuccess:"已导入 {{count}} 个模型"},errors:{idRequired:"请填写 Provider ID",idDuplicate:"该 Provider ID 已存在",baseUrlInvalid:"Base URL 格式无效",modelsRequired:"请至少配置一个模型",modelInvalid:"模型 ID 不可为空或重复",defaultInvalid:"默认模型必须在模型列表中",headerNameInvalid:"请求头名称只能包含字母、数字及 - _ . * + 等字符(不可含空格或冒号)",headerNameDuplicate:"请求头名称不可重复"},toast:{saveFailure:"保存失败:{{message}}"},noModelsTitle:"透传模式已启用",noModelsHint:'当前未配置模型列表。该 Provider 将以"透传"模式使用,可在模型路由中映射,或在请求中直接指定模型。',routeExample:"路由映射示例:"},confirm:{delete:"确认删除 Provider「{{name}}」?",deleteImpact:"将同时清理 {{count}} 条引用该 Provider 的路由规则。"}},kt={title:"模型与路由管理",description:"统一维护模型提供商配置、模型路由映射与自定义端点。",providersEyebrow:"供应池",header:{providersHelper:"先维护上游 Provider 供应池,再进入路由管理为内置端点和自定义端点配置映射规则。",routingHelper:"当前工作区正在编辑「{{name}}」的路由规则,保持单工作区上下文以减少切换混淆。"},guide:{title:"配置导览",subtitle:"将资源池与流量入口拆开维护,减少信息混排。",description:"先把上游 Provider 配完整,再去路由管理为 /anthropic、/openai 与自定义端点分配模型映射。",endpointBadge:"{{count}} 个路由工作区",providersDescription:"维护 Base URL、鉴权方式、默认模型与模型别名。这一层只定义上游资源。",providersMeta:"当前已配置 {{count}} 个 Provider",routingDescription:"分别管理内置端点与自定义端点的映射、模板和兼容策略。这一层决定对外入口如何分流。",routingMeta:"{{count}} 个工作区,{{dirty}} 个未保存",current:"当前步骤",currentAction:"当前页",openAction:"打开"},overview:{synced:"工作区已同步",unsavedCount:"未保存 {{count}}",providersStat:"Providers",providersStatHint:"已接入的上游供应池",routeWorkspacesStat:"路由工作区",routeWorkspacesStatHint:"内置端点 + 自定义端点",customEndpointsStat:"自定义端点",customEndpointsStatHint:"额外暴露的接入入口",activeWorkspace:"当前工作区",activeWorkspaceProvider:"Provider 清单",activeWorkspaceRouting:"路由编辑区",providerAndSystem:"资源与内置路由",providerPoolTag:"Provider 池",systemEndpointTag:"内置端点",customEndpoints:"自定义端点",customEndpointsHint:"自定义端点保留紧凑横向卡片,便于快速切换、编辑和删除。",headerWithCustom:"优先在此处切换和维护自定义端点,避免主工作区被多列布局挤压。",headerWithoutCustom:"先配置提供商与内置路由,需要时再补充自定义端点。",endpointPaths:"访问路径",protocols:"协议路径",endpointEnabled:"已启用",endpointDisabled:"已停用",endpointProtocols:"{{count}} 条协议路径",endpointNoProtocol:"尚未配置协议路径",endpointMorePaths:"另有 {{count}} 条路径",endpointManagedExternally:"该端点不可删除,仅可调整配置。",suggestionHint:"一键补充该端点常见的源模型写法,减少手工录入。",resourceCardTitle:"Provider 供应池",resourceCardDescription:"Provider 只表示上游模型资源与鉴权配置,不直接决定外部请求入口。",entryCardTitle:"Endpoint 入口层",entryCardDescription:"Anthropic、OpenAI 与自定义端点才是网关对外暴露的入口;路由与模板都按 endpoint 作用域管理。"},providersSemantics:{title:"先维护 Provider 资源池,再维护 Endpoint 路由",description:"这里配置的是上游供应商、认证方式和模型清单;真正对客户端暴露的仍是内置端点与自定义端点。"},targetPicker:{helper:"可输入 Provider、模型名或 ID 快速筛选。",searchPlaceholder:"搜索 Provider、模型名或 ID",matchCount:"找到 {{count}} 个匹配目标",recent:"最近使用",custom:"自定义 / 已保存",model:"模型",passthrough:"透传",customValue:"自定义",default:"默认"},tabs:{providers:"模型提供商",providersDesc:"配置上游模型提供商以及认证信息。",anthropic:"Anthropic 路由",anthropicDesc:"管理 /anthropic 端点的模型映射和默认配置。",openai:"OpenAI 路由",openaiDesc:"管理 /openai 端点的模型映射和默认配置。",customEndpoint:"自定义端点"},addEndpoint:"添加端点",createEndpoint:"创建端点",editEndpoint:"编辑端点",deleteEndpointConfirm:'确定要删除端点 "{{label}}" 吗?此操作无法撤销。',deleteEndpointSuccess:"端点删除成功",deleteEndpointError:"删除失败:{{error}}",createEndpointSuccess:"端点创建成功",createEndpointError:"创建失败:{{error}}",updateEndpointSuccess:"端点更新成功",updateEndpointError:"更新失败:{{error}}",endpointValidationError:"请填写所有必填字段",pathValidationError:"请填写所有路径信息",atLeastOnePath:"至少需要一个路径",endpointId:"端点 ID",endpointIdPlaceholder:"如 custom-api",endpointIdHint:"ID 创建后不可修改,用于内部标识。",endpointLabel:"显示名称",endpointLabelPlaceholder:"如 我的自定义 API",endpointPath:"访问路径",endpointPaths:"访问路径",endpointPathPlaceholder:"如 /custom/api",endpointPathHint:"路径需以 / 开头,修改后立即生效。",endpointProtocol:"协议类型",endpointEnabled:"启用此端点",endpointRoutingHint:"创建后,在路由视图的端点表格中点击该端点,即可设置模型路由规则。",addPath:"添加路径",removePath:"删除路径",protocolAnthropic:"Anthropic 协议",protocolOpenAI:"OpenAI",protocolOpenAIChat:"OpenAI Chat",protocolOpenAIResponses:"OpenAI Responses",protocolHints:{anthropic:"Anthropic Messages API 协议(/v1/messages)","openai-auto":"OpenAI 协议(支持 Chat Completions 和 Responses API)。请确保路径以 /v1/chat/completions 或 /v1/responses 结尾。","openai-chat":"OpenAI Chat Completions API 协议(/v1/chat/completions)","openai-responses":"OpenAI Responses API 协议(/v1/responses)"},actions:{saveRoutes:"保存路由",unsaved:"有未保存修改",footerTitle:"路由操作",footerDirtyHint:"已修改当前路由规则,请在此处完成保存。",footerSavedHint:"当前路由已与服务器保持同步。"},routing:{selectTarget:"请选择目标 Provider:模型"},openaiCompatibility:{title:"OpenAI 兼容降级",description:"仅在跨协议转发时启用 OpenAI 兼容降级与重试。同协议 /openai 请求始终原样透传,不会因为这里的设置被改写。",toggleLabel:"兼容降级",enabledHint:"已启用:跨协议失败时允许剥离 metadata/tool 历史并重试。",disabledHint:"已关闭:跨协议请求不会自动降级或重试。"},toast:{routesSaved:"模型路由已更新。",routesSaveFailure:"保存模型路由失败:{{message}}",presetSaved:'已保存模板 "{{name}}"。',presetSaveFailure:"保存模板失败:{{message}}",presetApplySuccess:'已应用模板 "{{name}}"。',presetApplyFailure:"应用模板失败:{{message}}",presetDeleteSuccess:'模板 "{{name}}" 已删除。',presetDeleteFailure:"删除模板失败:{{message}}",compatibilitySaved:"OpenAI 兼容降级已更新为:{{state}}。",compatibilitySaveFailure:"更新 OpenAI 兼容降级失败:{{message}}",endpointNotFound:"所选端点已不存在。"},presets:{title:"路由模板",description:"保存当前 Anthropic 路由映射,便于在不同 Provider 方案之间快速切换。",namePlaceholder:"输入模板名称,例如 fox",save:"保存模板",saving:"保存中...",empty:"尚未保存任何模板。",apply:"应用",applying:"应用中...",delete:"删除",deleting:"删除中...",rulesCount:"{{count}} 条规则",noRules:"空模板",previewTooltip:"悬停查看路由规则",diffTitle:"应用模板确认",diffDescription:'以下路由将被替换为模板 "{{name}}" 的配置:',diffAdded:"新增",diffRemoved:"移除",diffChanged:"变更",diffConfirm:"确认应用",diffEmpty:"模板与当前配置相同,无需变更。"},validation:{presetName:"请输入模板名称。",presetDuplicate:"模板 {{name}} 已存在,请使用其他名称。"},confirm:{deletePreset:'确定要删除模板 "{{name}}" 吗?'}},At={title:"路由管理",description:"按端点管理模型映射、模板与自定义入口,避免和 Provider 资源池混在一起。",eyebrow:"流量入口"},It={title:"安全事件",description:"查看校验防护与系统检测记录,及时发现异常访问。",filters:{title:"筛选条件",allLevels:"全部级别",allTypes:"全部类型",typePlaceholder:"按事件类型过滤(可留空)"},live:"实时更新中",reconnecting:"连接中断,重连中…",failed:"连接失败 — 切换标签页或刷新以重试",actions:{newest:"最新",older:"更早"},levels:{info:"提示",warn:"警告",error:"错误"},stats:{total:"总计"},empty:{title:"暂无事件记录",subtitle:"当前没有异常或告警事件,这通常意味着系统运行平稳。",filteredTitle:"当前筛选条件下没有匹配事件",filteredSubtitle:"可以重置筛选条件,或去请求日志中查看更完整的调用链。",actions:{logs:"查看请求日志"}},details:"查看详情",defaultTitle:"未命名事件",defaultMessage:"未提供详细描述。",toast:{loadFailure:"加载事件失败:{{message}}",statsLoadFailure:"加载事件统计失败:{{message}}"}},St={title:"设置",description:"调整网关端口、日志策略及其他运行参数。",toast:{loadFailure:"配置加载失败:{{message}}",saveSuccess:"系统配置已更新。",saveFailure:"保存失败:{{message}}",protocolRestartRequired:"配置已保存!请执行 cc-gw restart --daemon 重启服务使协议配置生效",copySuccess:"配置文件路径已复制到剪贴板。",copyFailure:"复制失败:{{message}}",cleanupSuccess:"已删除 {{count}} 条历史日志。",cleanupNone:"没有需要删除的日志。",cleanupFailure:"清理失败:{{message}}",clearAllSuccess:"日志已清空(请求 {{logs}} 条,统计 {{metrics}} 条)。",clearAllFailure:"清空失败:{{message}}",compactSuccess:{title:"数据库整理完成",desc:"空闲页已回收,建议稍后刷新确认容量变化。"},compactFailure:"数据库整理失败:{{message}}",missingConfig:"未能加载配置,请刷新或稍后再试。",authLoadFailure:"安全配置加载失败:{{message}}"},sections:{basics:"基础配置",routing:"模型路由",configFile:"配置文件",cleanup:"日志清理",security:"访问安全",protocol:"协议配置",jump:"跳转到"},overview:{title:"当前运行概览",description:"先确认当前监听方式、访问保护和配置文件位置,再进入具体调优。",unsavedCount:"待保存 {{count}} 项",cards:{protocols:"协议入口",security:"控制台访问",configFile:"配置文件"},values:{authEnabled:"已启用登录保护",authDisabled:"未启用登录保护",httpOnly:"仅 HTTP",httpsOnly:"仅 HTTPS",httpAndHttps:"HTTP + HTTPS"}},fields:{port:"监听端口",host:"监听地址(可选)",hostPlaceholder:"默认 127.0.0.1",retention:"日志保留天数",logExportTimeout:"日志导出超时 (秒)",logExportTimeoutHint:"默认 60 秒;导出量较大时可调高,范围 5-600 秒。",bodyLimit:"请求体大小上限 (MB)",bodyLimitHint:"默认 10 MB;如 Claude Code 的 /compact 遇到 413,可适当调大。",defaults:"默认模型配置",storeRequestPayloads:"保存请求内容",storeRequestPayloadsHint:"开启后会在日志数据库中保留客户端请求;如发生协议转换,也会额外保存发往上游的请求体。",storeResponsePayloads:"保存响应内容",storeResponsePayloadsHint:"开启后会保留客户端响应;如发生协议转换,也会保存上游原始响应。流式响应会整理为完整消息而不是 chunk 片段。",logLevel:"日志级别",logLevelOption:{fatal:"致命 (fatal)",error:"错误 (error)",warn:"警告 (warn)",info:"信息 (info)",debug:"调试 (debug)",trace:"跟踪 (trace)"},enableRoutingFallback:"启用模型回退策略",enableRoutingFallbackHint:"无匹配模型时自动落到首个可用模型。默认关闭,建议仅在明确需要时开启。"},auth:{description:"开启 Web UI 登录后,所有管理接口仅对已登录用户开放,模型代理端点仍保持兼容。",enable:"启用 Web UI 登录保护",enableHint:"推荐在多人共用或生产环境中开启,访问 /ui 与 /api/* 将需要先登录。",username:"登录用户名",usernamePlaceholder:"设置用于登录的用户名",password:"登录密码",passwordPlaceholder:"至少 6 位字符",confirmPassword:"确认密码",confirmPasswordPlaceholder:"再次输入登录密码",status:"当前状态",statusEnabled:"已启用登录保护",statusDisabled:"未启用登录保护",passwordHintRequired:"首次启用或修改用户名时必须设置新密码(不少于 6 位)。",passwordHintOptional:"如需更新密码可填写新值,留空则沿用旧密码。",actions:{save:"保存安全设置"},toast:{success:"安全设置已更新。",failure:"保存失败:{{message}}"},validation:{username:"请填写用户名",minLength:"密码至少需要 6 位字符",passwordRequired:"请设置登录密码",confirmMismatch:"两次输入的密码不一致"}},protocol:{description:"配置 HTTP 和 HTTPS 服务端口,默认同时启用两个协议",restartWarning:"⚠️ 修改协议配置后需要重启服务才能生效",restartHint:"保存配置后,请执行以下命令重启服务:",restartTip:"💡 提示:端口、协议启用状态、证书路径需要重启;Provider 和路由配置支持热加载无需重启",http:{enable:"启用 HTTP",hint:"标准 HTTP 协议,适用于本地开发和内网环境",port:"HTTP 端口",host:"HTTP 主机地址"},https:{enable:"启用 HTTPS",hint:"HTTPS 加密协议",port:"HTTPS 端口",host:"HTTPS 主机地址",keyPath:"证书私钥路径",certPath:"证书文件路径",caPath:"CA 证书路径 (可选)",warning:"⚠️ 关于 HTTPS 证书",invalidCert:"自签名证书无效:",invalidCertDetail:"Claude Code 和大多数 AI 工具无法信任自签名证书,会导致连接失败。",recommended:"推荐方案:",recommendedDetail:"本地开发环境建议使用 HTTP 协议(127.0.0.1 本地访问非常安全)。",tip:"💡 如需 HTTPS,请使用受信任 CA(如 Let's Encrypt)签发的正式证书,或配置反向代理(如 Nginx/Caddy)处理 HTTPS。"}},validation:{port:"请输入 1-65535 之间的端口号",retention:"日志保留天数需为 1-365 之间的数字",logExportTimeout:"日志导出超时需在 5-600 秒之间",bodyLimit:"请求体大小需在 1-2048 MB 之间",protocolRequired:"至少需要启用 HTTP 或 HTTPS 协议",httpPort:"HTTP 端口必须在 1-65535 之间",httpsPort:"HTTPS 端口必须在 1-65535 之间",httpsCertificate:"HTTPS 已启用但缺少证书路径,请手动配置受信任的证书",routePair:"请填写完整的来源模型与目标模型配置。",routeDuplicate:"模型 {{model}} 已存在映射,请勿重复配置。"},defaults:{completion:"对话:{{model}}",reasoning:"推理:{{model}}",background:"后台:{{model}}",none:"未设置默认模型"},routing:{title:"模型路由映射",description:"为 Claude Code 发起的模型请求指定实际 Provider 与模型 ID(如将 claude 系列映射至 Kimi)。如需禁用映射,可留空或移除。",titleByEndpoint:"{{endpoint}} 路由配置",descriptionByEndpoint:{anthropic:"当 Claude Code 通过 /anthropic 端点请求特定模型时,将根据此映射选择目标 Provider 与模型。",openai:"当 Codex 通过 /openai 端点请求特定模型时,将根据此映射选择目标 Provider 与模型。"},add:"新增映射",sourcePlaceholder:"如 claude-sonnet-4-5-20250929",targetPlaceholder:"如 kimi:kimi-k2-0905-preview",customTargetOption:"自定义目标…",providerPassthroughOption:"{{provider}} · 透传原始模型 (*)",remove:"移除",suggested:"常用 Anthropic 模型"},file:{description:"当前配置存储在本地文件,可通过编辑该文件进行离线修改。",unknown:"未知路径"},cleanup:{description:"立即清理早于当前保留天数的日志记录。",softLabel:"轻度操作",softTitle:"清理过期日志",softDescription:"仅删除超过保留天数的历史日志,适合日常维护。",confirmTitle:"清理历史日志",confirmDescription:"该操作会删除超过保留天数的历史日志,但不会影响当前较新的记录。",hardLabel:"高风险操作",hardTitle:"彻底清空日志",clearAllTitle:"彻底清空日志",clearAll:"彻底清空",clearingAll:"清空中...",confirmCleanup:"该操作会删除超过保留天数的历史日志,但不会影响当前较新的记录。",confirmClearAll:"此操作会删除全部请求日志和日统计数据,且无法恢复。",clearAllWarning:"该操作会删除所有日志记录及日统计数据,请谨慎操作。",dbSizeLabel:"数据库大小",compactTitle:"释放数据库空间",compactDescription:"回收已删除日志留下的空闲页,缩小数据库文件体积。不删除任何数据。"}},Rt={title:"使用指南",intro:"完整的 cc-gw 配置和使用指南,帮助您从零开始搭建 AI 模型网关。",note:"所有配置变更都会实时生效。建议通过 Web UI 进行配置管理,CLI 主要用于服务启动和重启。",helper:"推荐顺序:先启动服务,再配置模型供应商和模型,验证连接,然后配置路由,按需创建 API 密钥,最后接入 Claude Code 或 Codex。",meta:{breadcrumb:"网关 / 使用指南",guides:"{{count}} 个指南",faqCount:"{{count}} 个问题",recommendedFlow:"推荐流程",claudeWorkflow:"IDE / 桌面工作流",codexWorkflow:"终端工作流",tocTitle:"本页索引"},clientConfig:{title:"客户端配置指南",subtitle:"选择您的客户端工具,按照步骤进行配置"},advancedGuide:{title:"高级使用指南",subtitle:"日常使用技巧与最佳实践"},sections:{consoleTour:{title:"🧭 控制台导航",items:["**仪表盘 / Dashboard**:实时查看请求量、Token 消耗、缓存命中率与响应耗时(TTFT / TPOT)。可按端点 (`/anthropic`、`/openai` 或自定义) 过滤。",'**请求日志 / Logs**:分页浏览每一次请求的元数据;详情抽屉按客户端 ↔ 上游分别展示 payload,便于排查协议改写问题。需要在"设置"开启"保存请求/响应内容"才能看到完整 payload。',"**模型供应商 / Models**:管理 Provider(上游接入信息:Base URL、认证、模型清单)。这是上游资源池,不直接对外暴露。","**路由管理 / Routing**:把客户端请求的模型名映射到具体 `providerId:modelId`。每个端点(`/anthropic`、`/openai`、自定义)都有独立的路由空间,互不影响。",'**API 密钥 / API Keys**:为不同客户端、环境、自动化任务发放专属密钥,便于审计、限权和吊销。默认存在通配符密钥,启用即代表"任意密钥都允许"。',"**事件 / Events**:网关层面的系统事件流(启动、配置变更、安全告警等),供审计和故障回溯使用。","**设置 / Settings**:调整端口、日志保留、payload 存储、清理策略、Web UI 登录保护等运行参数。"]},configuration:{title:"🚀 基础配置流程",items:["📦 **安装并启动服务**:运行 `npm install -g @chenpu17/cc-gw && cc-gw start --daemon --port 4100`,然后访问 http://127.0.0.1:4100/ui",'🔧 **配置模型供应商与模型**:在"模型供应商"页面添加至少一个 Provider,配置 Base URL、认证方式 / API Key,并在该 Provider 下添加至少一个模型或设置默认模型。Provider 只是上游资源池,真正对外接入还需要路由配置。','✅ **测试供应商连接**:模型列表或默认模型配置完成后,使用"测试"按钮验证 Base URL、API Key 与模型是否可用。如果提示没有配置模型,请先在该 Provider 下新增模型。','🔀 **配置模型路由**:进入"路由管理",选择 `/anthropic`、`/openai` 或自定义接入点,把客户端请求的模型名映射到目标模型 `providerId:modelId`;如果希望透传客户端传入的模型名,可选择 `providerId:*`,保存后只影响当前端点。','🔑 **生成网关 API Key(可选)**:在"API 密钥"页面创建 API Key,用于审计、区分客户端和后续吊销访问。默认情况下,所有请求都可以通过网关访问。']},claudeCodeConfig:{title:"⚡ Claude Code 配置",items:["🎯 **设置环境变量**(推荐方式):\n```bash\nexport ANTHROPIC_BASE_URL=http://127.0.0.1:4100/anthropic\nexport ANTHROPIC_API_KEY=sk-ant-oat01-8HEmUDacamV1...\n```\n写入 `~/.bashrc` 或 `~/.zshrc` 后执行 `source ~/.bashrc` / `source ~/.zshrc` 让变量生效。Claude Code 启动时会自动读取这两个变量并指向 cc-gw。",'💡 **API Key 怎么填**:如果在 cc-gw 里启用了 API Key 限制,使用你在"API 密钥"页面创建的密钥;如未启用(保留默认通配符密钥),可以填任意非空字符串,仅用于占位。','✅ **快速验证**:\n```bash\nclaude "你好,请简短回应"\n```\n输出正常即代表配置成功,可在"请求日志"页看到对应记录;如果想观察完整的 Agent ↔ LLM ↔ Tools 交互时序,进入"性能分析"开启录制后再发起请求。']},codexConfig:{title:"🛠️ Codex CLI 配置",items:[`📝 **编辑配置文件**:
3
3
  在 \`~/.codex/config.toml\` 进行配置:
4
4
  \`\`\`toml
@@ -58,4 +58,4 @@ env_key = "cc_gw_key"
58
58
  "protocol": "anthropic"
59
59
  }
60
60
  \`\`\`
61
- After configuration, clients access via \`http://127.0.0.1:4100/claude/v1/messages\` (path auto-expansion).`,'Enable "Store request bodies" / "Store response bodies" to inspect and copy client-side and upstream payloads from the log drawer when troubleshooting.',"If you do not need payload-level troubleshooting, turn off payload storage to reduce local disk usage and privacy exposure.",'Use **routing presets** to save common mapping schemes, such as "Claude models through Anthropic" or "GPT models through an OpenAI-compatible provider", then apply them per endpoint when switching providers.',"If you edit `~/.cc-gw/config.json` manually, refresh the Settings page or restart cc-gw so the UI reflects the latest configuration.","🗃️ **Data backup**: Regularly back up the `~/.cc-gw/` directory — it holds config.json, the SQLite database, and logs.",'🧹 **Log & database maintenance**: Adjust the log retention days to your needs. Under "Settings → Log cleanup" you can clean up expired logs, clear everything, or run "Compact database" (VACUUM) to reclaim disk space. For best results, clean logs before compacting.']}},faq:{title:"Frequently asked questions",items:[{q:"How can I change the default model for each endpoint?",a:'Go to "Routing" and edit the routing workspace for /anthropic or /openai. Saving applies the change right away.'},{q:"How do I use custom endpoints?",a:'Create a custom endpoint in the "Routing" page by configuring a base path (e.g., `/my-endpoint`) and protocol type. The system automatically registers full API paths based on the protocol. For example, after configuring `/claude` + `anthropic` protocol, clients access via `http://127.0.0.1:4100/claude/v1/messages`.\n\nIf you encounter 404 errors, check:\n1) Is the endpoint enabled?\n2) Are clients using the complete path (including protocol subpath)?\n3) Check server logs to confirm route registration'},{q:"Why does provider testing say no model is configured?",a:"Provider settings store Base URL and authentication, but the test request still needs a concrete model. Add at least one model under that Provider, or set a default model, then test again."},{q:"When do I need model routing?",a:"Configure routing when the client model name differs from the upstream model name, when `/anthropic` and `/openai` should go to different providers, or when different clients need different provider mappings. Target models use `providerId:modelId`; `providerId:*` passes the original client model name through to that provider."},{q:"Why are cached token numbers missing?",a:"Upstream providers must return `cached_tokens` or `input_tokens_details.cached_tokens`. Enable cache metrics on the provider if supported."},{q:"How can I use different models for different clients?",a:"The recommended approach is to create dedicated custom endpoints for different clients and maintain independent routing rules per endpoint. You can also point clients at `/anthropic`, `/openai`, or custom Base URLs. API keys are mainly for auditing, source separation, and revocation; they do not carry separate routing rules by themselves."},{q:"How do I enable login protection for the Web UI?",a:'Go to "Settings → Security", turn on login protection, and set a username and password. After saving, the next visit to `/ui` will require credentials. When login is disabled, the console is open to anyone who can reach the port — enable it for shared or public deployments.'},{q:"How does Codex CLI connect to cc-gw?",a:'Edit `~/.codex/config.toml`: set `model_provider` to "cc_gw", point `base_url` at the cc-gw OpenAI-compatible endpoint, and configure the matching environment variable. See the Codex CLI Configuration section above for the full example.'},{q:"How do I back up and migrate my configuration?",a:"Back up the entire `~/.cc-gw/` directory, which contains config.json, the database, and log files. To migrate, restore the directory on the new machine and restart the service."},{q:"What should I do if the Web UI shows a 404 error?",a:"Make sure the Web UI has been built with `pnpm --filter @cc-gw/web build`, or use the npm-installed global version. Also check the static asset path reported in the service startup logs."}]}},Qt={title:"About",description:"Review cc-gw version details, build metadata, and current runtime status.",app:{title:"Application",subtitle:"Gateway build metadata at a glance.",labels:{name:"Name",version:"Version",buildTime:"Build time",runtime:"Backend runtime",backendVersion:"Backend version"},hint:{buildTime:"Timestamps are recorded in UTC so you can trace deployments easily."}},status:{title:"Runtime status",subtitle:"Live metrics reported by the running gateway.",loading:"Fetching status...",empty:"Unable to retrieve status information.",labels:{host:"Listen host",port:"Listen port",providers:"Providers configured",active:"Active requests",platform:"Platform",pid:"Process PID"},hint:{active:"Active request totals refresh roughly every minute."}},support:{title:"Operational notes",subtitle:"Maintenance guidance",description:"Manage providers, routing, and logs in the Web UI; advanced settings live in ~/.cc-gw/config.json.",tip:"Consider keeping ~/.cc-gw/config.json under version control or managing it via automation scripts.",actions:{checkUpdates:"Check for updates",checkingUpdates:"Checking..."}},update:{available:"Update available: v{{version}}",current:"You are on the latest version: v{{version}}",channel:"Channel: {{channel}}"},toast:{statusError:{title:"Failed to load status"},upToDate:{title:"You are on the latest version: v{{version}}",description:"No newer release was found on npm."},updateAvailable:{title:"Update available: v{{version}}",description:"Upgrade with npm install -g {{packageName}}."},updateError:{title:"Failed to check for updates"}}},$t={title:"API Keys Management",description:"Create and manage API keys for gateway access",helper:"Use separate keys for each client, environment, or automation task so you can audit, restrict, and revoke access cleanly.",createNew:"Create New Key",createAction:"Create",createDescription:"Create a new API key for authentication and optionally add a description.",descriptionLabel:"Key description (optional)",keyDescriptionPlaceholder:"e.g. Internal staging access only",keyNamePlaceholder:"Enter key name",keyCreated:"API Key Created",saveKeyWarning:"Keep this key secure. You can also reveal the full key anytime from the key list.",wildcard:"Any Key",wildcardHint:"When enabled, any custom key — including an empty key — is accepted. Disable this key to enforce strict authentication.",status:{enabled:"Enabled",disabled:"Disabled"},actions:{enable:"Enable",disable:"Disable",delete:"Delete",reveal:"Reveal key",hide:"Hide key"},created:"Created",lastUsed:"Last Used",requestCount:"Requests",totalTokens:"Total Tokens",deleteDialogTitle:"Delete API key",confirmDelete:"Are you sure you want to delete this API key? This action cannot be undone.",errors:{nameRequired:"Key name is required",maxConcurrencyInvalid:"Max concurrency must be a positive integer"},analytics:{title:"Key Usage Analytics",description:"Highlights for the past {{days}} days of API key activity",range:{today:"Today",week:"Last 7 days",month:"Last 30 days"},cards:{total:"Total keys",enabled:"Enabled keys",active:"Active keys ({{days}} days)"},charts:{requests:"Top 10 keys by request count",tokens:"Top 10 keys by token usage"},tokens:{input:"Input tokens",output:"Output tokens"},requestsSeries:"Requests",empty:"No activity for the selected range.",emptyHint:"Create and use at least one key, then charts will fill in as real traffic arrives.",actions:{logs:"Open request logs"},unknownKey:"Unknown key"},quickStart:{title:"Recommended workflow",description:"Start with separate keys per client, then tighten endpoint access as needed.",create:{title:"Split keys by client",description:"Create different keys for Claude Code, Codex, CI, or staging so logs stay easy to trace."},restrict:{title:"Restrict endpoint access",description:"Limit keys to Anthropic, OpenAI, or custom endpoints when you want tighter isolation."},wildcard:{title:"Use wildcard sparingly",description:"Wildcard access is convenient for migration, but named keys are safer for production."}},list:{title:"Key Inventory",emptyTitle:"Create your first API key",empty:"No API keys found. Use the button above to create one.",emptyFilteredTitle:"No keys match the current filters",emptyFiltered:"No API keys match the current filters."},filters:{searchPlaceholder:"Search by name, description, or endpoint",all:"All",enabled:"Enabled",disabled:"Disabled"},summary:{totalCount:"{{count}} keys",wildcard:"Wildcard keys: {{count}}",restricted:"Restricted keys: {{count}}",unrestricted:"Unrestricted keys: {{count}}"},views:{cards:"Cards",compact:"Compact list"},tabs:{inventory:"Keys",analytics:"Usage"},table:{name:"Key",access:"Access",actions:"Actions",accessWildcard:"Wildcard access",accessOpen:"All endpoints"},toast:{keyCreated:"API key created successfully",keyUpdated:"API key updated successfully",keyDeleted:"API key deleted successfully",keyCopied:"Key copied to clipboard",createFailure:"Failed to create: {{message}}",updateFailure:"Failed to update: {{message}}",deleteFailure:"Failed to delete: {{message}}",revealFailure:"Failed to reveal key",copyFailure:"Failed to copy"},allowedEndpoints:"Allowed Endpoints",allEndpoints:"All endpoints (unrestricted)",editEndpoints:"Edit Endpoint Access",endpointRestricted:"Restricted",selectEndpoints:"Select which endpoints this key can access. Leave empty to allow all.",maxConcurrency:"Max Concurrency",maxConcurrencyPlaceholder:"Leave empty for unlimited",maxConcurrencyHelper:"Set the maximum number of simultaneous requests for this key. Leave empty or set to 0 for unlimited."},Yt={title:"Custom Endpoints",description:"Manage custom API endpoints with multiple protocol support.",createButton:"Add Endpoint",createTitle:"Create Endpoint",editTitle:"Edit Endpoint",emptyTitle:"No custom endpoints",emptyDescription:'Click "Add Endpoint" to create your first custom endpoint.',loadError:"Failed to load endpoints",id:"ID",path:"Path",disabled:"Disabled",hasRouting:"Routing configured",protocols:{anthropic:"Anthropic Protocol","openai-chat":"OpenAI Chat","openai-responses":"OpenAI Responses"},protocolHints:{anthropic:"Anthropic Messages API protocol (/v1/messages)","openai-chat":"OpenAI Chat Completions API protocol (/v1/chat/completions)","openai-responses":"OpenAI Responses API protocol (/v1/responses)"},form:{id:"Endpoint ID",idPlaceholder:"e.g. custom-api",idHint:"ID cannot be changed after creation, used for internal identification.",label:"Display Name",labelPlaceholder:"e.g. My Custom API",path:"Access Path",pathPlaceholder:"e.g. /custom/api",pathHint:"Path must start with /. Changes take effect immediately.",protocol:"Protocol Type",enabled:"Enable this endpoint"},routing:{title:"Routing Configuration (Optional)",modelRoutes:"Model Routing Rules",addRoute:"Add Rule",noRoutes:"No routing rules",sourceModelPlaceholder:"Source model (e.g. claude-3-5-sonnet-20241022)",targetPlaceholder:"Target (e.g. anthropic:claude-3-5-sonnet-20241022)",modelRoutesHint:"Format: source model → provider:model, wildcards supported (e.g. gpt-* → openai:*)",defaults:"Default Model Configuration",defaultCompletion:"Default for completion tasks",defaultReasoning:"Default for reasoning tasks",defaultBackground:"Default for background tasks",longContextThreshold:"Long context threshold (tokens)",defaultPlaceholder:"e.g. anthropic:claude-3-5-sonnet-20241022"},createSuccess:"Endpoint created successfully",createError:"Failed to create: {{error}}",updateSuccess:"Endpoint updated successfully",updateError:"Failed to update: {{error}}",deleteSuccess:"Endpoint deleted successfully",deleteError:"Failed to delete: {{error}}",deleteConfirm:'Are you sure you want to delete endpoint "{{label}}"? This action cannot be undone.',validationError:"Please fill in all required fields"},Jt={title:"Providers & Routing Workbench",description:"Manage model providers, endpoint routing rules and custom endpoints in one place.",viewSwitch:{label:"Workbench view",providers:"Providers",providersDesc:"Manage upstream model services",routing:"Routing",routingDesc:"Decide where requests are forwarded"},routingGuide:{flow:"Client-requested model (e.g. claude-sonnet-4) → matched route rule → forwarded to a provider's model",hint:"Clients request by model name: rules below are matched first, and anything unmatched falls through to the default."},flow:{title:"Request flow",clientStep:"Client model",endpointStep:"Endpoint",matchStep:"Route rule match",targetStep:"Forward target",fallbackNote:"Unmatched requests use the default"},endpointRail:{title:"Endpoints",subtitle:"Each endpoint has its own rule set",add:"New endpoint",ruleCount:"{{count}}"},endpointConfig:{title:"Endpoint config"},hitSim:{title:"Routing Hit Simulator",subtitle:"Enter a client model name to preview which rule it hits, in real time",endpointLabel:"Endpoint",modelPlaceholder:"e.g. claude-sonnet-4-5-20250929",thinking:"Thinking",anyModel:"(any model)",empty:"Enter a model name to see which route it hits",errorPrefix:"Could not resolve route",reasonModelRoute:"Hit a model route rule",reasonModelRouteAlias:"Hit a model route rule (via alias)",reasonDirectMatch:"Direct provider match",reasonThinkingDefault:"Hit thinking default",reasonCompletionDefault:"Hit completion default",reasonLongContext:"Hit long-context default",longContextDetail:"Estimated ~{{estimate}} tokens, over threshold {{threshold}}",reasonFallback:"No rule matched · global fallback",fallbackHint:"No rule matched this model; it will use the global fallback."},defaults:{title:"Default forwarding",description:"Requests that match no rule below are forwarded here. With a single provider, setting this alone is usually all you need.",cardSubtitle:"Requests that match no rule go here",completionLabel:"Forward to (provider:model)",moreLabel:"More default settings",reasoningLabel:"Reasoning model",backgroundLabel:"Long-context model",thresholdLabel:"Long-context threshold (tokens)",targetPlaceholder:"e.g. openai:gpt-4o",save:"Save defaults",saveSuccess:"Default forwarding saved.",saveFailure:"Failed to save default forwarding: {{message}}"},specific:{title:"Route specific models (optional)",description:"Only requests for these model names hit the rules below — they take priority over default forwarding."},advanced:{title:"Advanced",wildcardHint:"Rules support * wildcards; the most specific match wins."},routing:{rulesTitle:"Route rules",rulesSubtitle:"Matched top-to-bottom, * wildcards supported; drag the left handle to set priority",dragHandle:"Drag to reorder",sourceLabel:"Client-requested model",targetLabel:"Forward to (provider:model)",emptyTitle:"No route rules yet",emptyDescription:`Clients send requests by model name — configure here which provider's model each should be forwarded to. For example, map claude-sonnet-4 to the provider you just added; model names not listed here use "Default forwarding" above.`,addFirst:"Add your first rule"},detail:{routesTitle:"Routing rules using this provider",routesEmptyHint:"No route rules reference it yet — client requests can't reach this provider.",addRuleCta:"Add a rule in Routing view",viewRoute:"Open this rule in the routing view"},list:{emptyHint:"Add an upstream model service first (Anthropic, OpenAI, or a compatible endpoint), then map client models to it in the Routing view."},endpoints:{create:"New endpoint",editRoute:"Routes",defaultUnset:"Not set",table:{name:"Name",protocol:"Protocol",paths:"Paths",rules:"Rules",defaultTarget:"Default",status:"Status",actions:"Actions"}},testResult:{title:"Latest test",success:"Connected",failure:"Test failed",status:"Status {{status}}",duration:"{{duration}}",never:"Not tested yet"},drawer:{verifyTitle:"Connection check",verifyHint:"Runs a real request against the current config — including unsaved drafts — to confirm auth and connectivity.",verifySaveFirst:"Save the provider before running a connection test.",verifyRun:"Test connection"}},Xt={title:"Guided setup",description:"Finish cold-start configuration in four steps: connect a provider, configure routing, create an API key, then verify connectivity.",steps:{provider:{navLabel:"Add provider",title:"Add a model provider",description:"Connect an upstream model service. Routing and client requests will point to it.",existingTitle:"{{count}} provider(s) already configured",existingHint:"Pick an existing provider and continue, or add another one.",chooseLabel:"Provider used by this wizard",addAnother:"Add another provider",backToList:"Back to existing providers",saveAndContinue:"Save and continue"},routing:{navLabel:"Configure routing",title:"Configure a default route",description:"Add a route on the Anthropic endpoint that maps a common client model name to the new provider model.",existingTitle:"Current Anthropic endpoint routes",existingHint:"Routes already exist — review them and continue to the next step.",emptyHint:"No routes yet. Save the default route below to continue.",sourceLabel:"Source model (client request name)",sourceHint:"When a client switches to a new model name later, add a matching rule under Providers & Routing → Routing.",targetLabel:"Target (provider:model)",save:"Save route",saving:"Saving..."},apiKey:{navLabel:"Create API key",title:"Create an API key",description:"Clients use this key to access the gateway. Scoped to the Anthropic endpoint by default.",create:"Create key",creating:"Creating...",createFailed:"Failed to create API key: {{message}}"},verify:{navLabel:"Verify & finish",title:"Verify and finish",description:"Test provider connectivity, then send your first request from a client with the baseUrl and key.",noProvider:"No provider available — go back to step 1.",testingProvider:'Testing provider "{{name}}"',envTitle:"Send your first request from a client",envHint:"Set the following variables in your client environment (Claude Code example), then send a request:"}},actions:{finish:"Finish"}},Zt={zh:{translation:{app:yt,nav:vt,language:bt,common:wt,login:Pt,dashboard:xt,logs:Tt,providers:Ct,modelManagement:kt,routingManagement:At,events:It,settings:St,help:Rt,about:Et,apiKeys:Lt,endpoints:Dt,workbench:Nt,setup:qt}},en:{translation:{app:Ht,nav:jt,language:Mt,common:Ft,login:Ot,dashboard:Kt,logs:Ut,providers:_t,modelManagement:zt,routingManagement:Bt,events:Wt,settings:Vt,help:Gt,about:Qt,apiKeys:$t,endpoints:Yt,workbench:Jt,setup:Xt}}};function eo(){if(typeof window>"u")return"zh";const e=window.localStorage.getItem(q.language);return e==="zh"||e==="en"?e:"zh"}function Q(e){if(typeof window>"u")return;const o=e.toLowerCase().startsWith("zh")?"zh":"en";window.localStorage.setItem(q.language,o)}k.isInitialized||(k.use(Xe).init({resources:Zt,lng:eo(),fallbackLng:"en",interpolation:{escapeValue:!1}}),k.on("languageChanged",Q),Q(k.language));function to(e,o){const a=o,s=a==null?void 0:a.status;return s===401||s===403||s===404?!1:e<2}function oo(){return new Ee({defaultOptions:{queries:{retry:to,staleTime:3e4,refetchOnWindowFocus:!1},mutations:{retry:!1}}})}function ao(e){return typeof e=="function"?e():e}function so(e,o,a={}){const{serialize:s=JSON.stringify,deserialize:i=JSON.parse}=a,[l,d]=n.useState(()=>{const c=ao(o);if(typeof window>"u")return c;const p=window.localStorage.getItem(e);if(p===null)return c;try{return i(p)}catch{return c}});return n.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(e,s(l))}catch{}},[e,l]),[l,d]}const me=n.createContext(void 0);function no(e){return e==="system"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e}function j(e){const o=document.documentElement;o.dataset.theme=e,e==="dark"?o.classList.add("dark"):o.classList.remove("dark")}function ro({children:e}){const[o,a]=so(q.themeMode,"system",{serialize:l=>l,deserialize:l=>l==="light"||l==="dark"||l==="system"?l:"system"}),s=n.useMemo(()=>typeof window<"u"?no(o):"light",[o]);typeof window<"u"&&j(s),n.useEffect(()=>{if(typeof window>"u")return;const l=window.matchMedia("(prefers-color-scheme: dark)"),d=c=>{o==="system"&&j(c.matches?"dark":"light")};return l.addEventListener("change",d),()=>l.removeEventListener("change",d)},[o]),n.useEffect(()=>{typeof window>"u"||j(s)},[s]);const i=n.useMemo(()=>({mode:o,resolved:s,setMode:a}),[o,s]);return t.jsx(me.Provider,{value:i,children:e})}function he(){const e=n.useContext(me);if(!e)throw new Error("useTheme must be used within ThemeProvider");return e}const ge=n.createContext(void 0);function io(){return`toast_${Math.random().toString(36).slice(2)}`}function lo({children:e}){const[o,a]=n.useState([]),s=n.useRef([]),i=n.useRef({});n.useEffect(()=>{s.current=o},[o]);const l=n.useCallback(r=>{const m=i.current[r];m&&(clearTimeout(m),delete i.current[r])},[]),d=n.useCallback(r=>{l(r),a(m=>m.filter(f=>f.id!==r))},[l]),c=n.useCallback(r=>{a(m=>m.map(f=>f.id===r?{...f,dismissing:!0}:f)),setTimeout(()=>d(r),200)},[d]),p=n.useCallback((r,m)=>{l(r),!(m<=0)&&(i.current[r]=setTimeout(()=>c(r),m))},[l,c]);n.useEffect(()=>()=>{Object.values(i.current).forEach(r=>clearTimeout(r)),i.current={}},[]);const u=n.useCallback(r=>{const m=r.durationMs??3e3,f=s.current.find(v=>r.id?v.id===r.id:v.title===r.title&&v.description===r.description&&v.variant===r.variant),P=(f==null?void 0:f.id)??r.id??io();a(v=>f?v.map(x=>x.id===P?{...x,...r,id:P,dismissing:!1}:x):[...v,{...r,id:P}]),p(P,m)},[p]),g=n.useMemo(()=>({toasts:o,pushToast:u,dismissToast:c}),[o,c,u]);return t.jsxs(ge.Provider,{value:g,children:[e,t.jsx("div",{"aria-live":"polite","aria-atomic":"false",className:"fixed right-6 top-6 z-50 flex w-80 flex-col gap-3",children:o.map(r=>t.jsx("div",{role:"alert",className:`rounded-md border border-border bg-card p-4 text-card-foreground shadow-[var(--surface-shadow-lg)] ${r.dismissing?"animate-toast-out":"animate-toast-in"} ${r.variant==="error"?"border-destructive/20 bg-destructive/10 text-destructive":r.variant==="success"?"border-success/20 bg-success-bg text-success":""}`,children:t.jsxs("div",{className:"flex items-start justify-between gap-4",children:[t.jsxs("div",{children:[t.jsx("p",{className:"text-sm font-semibold",children:r.title}),r.description?t.jsx("p",{className:"mt-1 text-sm opacity-75",children:r.description}):null]}),t.jsx("button",{type:"button","aria-label":"Dismiss",className:"text-sm text-muted-foreground hover:text-foreground",onClick:()=>r.id&&c(r.id),children:"×"})]})},r.id))})]})}function ia(){const e=n.useContext(ge);if(!e)throw new Error("useToast must be used within ToastProvider");return e}const b=ee.create({baseURL:"/",timeout:15e3,withCredentials:!0});b.interceptors.request.use(e=>{const o=(e.method??"get").toLowerCase();if(o==="get"||o==="head"){const a=Le.from(e.headers);a.set("Cache-Control","no-cache"),a.set("Pragma","no-cache"),e.headers=a}return e});b.interceptors.response.use(e=>e,e=>Promise.reject(e));function E(e){var o,a;if(ee.isAxiosError(e)){const s=(o=e.response)==null?void 0:o.status,i=(a=e.response)==null?void 0:a.data;return{message:(i==null?void 0:i.error)||(i==null?void 0:i.statusText)||e.message||k.t("common.requestFailed"),status:s,code:String(s??"unknown")}}return{message:e instanceof Error?e.message:k.t("common.requestFailed")}}async function fe(e){return(await b.request(e)).data}async function A(e){return(await e).data}const la={list:async()=>A(b.get("/api/custom-endpoints")),create:async e=>A(b.post("/api/custom-endpoints",e)),update:async(e,o)=>A(b.put(`/api/custom-endpoints/${e}`,o)),delete:async e=>A(b.delete(`/api/custom-endpoints/${e}`))},M={session:async()=>fe({url:"/auth/session",method:"GET"}),login:async(e,o)=>A(b.post("/auth/login",{username:e,password:o})),logout:async()=>A(b.post("/auth/logout"))},ye=n.createContext(void 0);function co({children:e}){const o=n.useRef(!0),[a,s]=n.useState({loading:!0,authEnabled:!1,isAuthenticated:!0,username:void 0,error:null}),i=n.useCallback(u=>{o.current&&s(u)},[]),l=n.useCallback(async()=>{i(u=>({...u,loading:!0,error:null}));try{const u=await M.session();i(()=>({loading:!1,authEnabled:!!u.authEnabled,isAuthenticated:!u.authEnabled||!!u.authenticated,username:u.username??void 0,error:null}))}catch(u){const g=E(u);i(()=>({loading:!1,authEnabled:!1,isAuthenticated:!0,username:void 0,error:g.message}))}},[i]),d=n.useCallback(async(u,g)=>{i(r=>({...r,loading:!0,error:null}));try{await M.login(u,g),await l()}catch(r){const m=E(r);throw i(f=>({...f,loading:!1,error:m.message,isAuthenticated:!1})),m}},[l,i]),c=n.useCallback(async()=>{i(u=>({...u,loading:!0}));try{await M.logout()}catch(u){const g=E(u);i(r=>({...r,error:g.message}))}finally{await l()}},[l,i]);n.useEffect(()=>{o.current=!0,l();const u=b.interceptors.response.use(g=>g,async g=>{var r;return((r=g==null?void 0:g.response)==null?void 0:r.status)===401&&await l(),Promise.reject(g)});return()=>{o.current=!1,b.interceptors.response.eject(u)}},[l]);const p=n.useMemo(()=>({...a,refresh:l,login:d,logout:c}),[a,l,d,c]);return t.jsx(ye.Provider,{value:p,children:e})}function B(){const e=n.useContext(ye);if(!e)throw new Error("useAuth must be used within an AuthProvider");return e}function uo({children:e}){const[o]=n.useState(()=>oo());return t.jsx(Ye,{client:o,children:t.jsx(Ze,{i18n:k,children:t.jsx(ro,{children:t.jsx(lo,{children:t.jsx(co,{children:e})})})})})}function ve({label:e}){const{t:o}=C();return t.jsx("div",{className:"flex h-full items-center justify-center p-12",role:"status","aria-live":"polite",children:t.jsxs("div",{className:"flex flex-col items-center gap-4 text-center",children:[t.jsx("div",{className:"flex h-14 w-14 items-center justify-center rounded-xl bg-secondary text-primary",children:t.jsx(De,{className:"h-6 w-6 animate-spin","aria-hidden":"true"})}),t.jsx("p",{className:"text-sm text-muted-foreground",children:e??o("common.loading")})]})})}const po="modulepreload",mo=function(e){return"/"+e},$={},w=function(o,a,s){let i=Promise.resolve();if(a&&a.length>0){document.getElementsByTagName("link");const d=document.querySelector("meta[property=csp-nonce]"),c=(d==null?void 0:d.nonce)||(d==null?void 0:d.getAttribute("nonce"));i=Promise.allSettled(a.map(p=>{if(p=mo(p),p in $)return;$[p]=!0;const u=p.endsWith(".css"),g=u?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${p}"]${g}`))return;const r=document.createElement("link");if(r.rel=u?"stylesheet":po,u||(r.as="script"),r.crossOrigin="",r.href=p,c&&r.setAttribute("nonce",c),document.head.appendChild(r),u)return new Promise((m,f)=>{r.addEventListener("load",m),r.addEventListener("error",()=>f(new Error(`Unable to preload CSS for ${p}`)))})}))}function l(d){const c=new Event("vite:preloadError",{cancelable:!0});if(c.payload=d,window.dispatchEvent(c),!c.defaultPrevented)throw d}return i.then(d=>{for(const c of d||[])c.status==="rejected"&&l(c.reason);return o().catch(l)})},ho=n.lazy(()=>w(()=>import("./Dashboard-DW5yhjz2.js"),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]))),go=n.lazy(()=>w(()=>import("./Logs-DlUZYPnC.js"),__vite__mapDeps([25,1,2,3,4,16,14,26,6,7,20,27,5,28,29,15,30,31,32,8,24,23]))),fo=n.lazy(()=>w(()=>import("./Events-DIHxXK2c.js"),__vite__mapDeps([33,1,8,2,3,4,13,14,5,21,20,16,17,18,31,6,7,24]))),yo=n.lazy(()=>w(()=>import("./ProvidersWorkbench-B30mrfQ5.js"),__vite__mapDeps([34,1,8,35,27,7,3,4,2,16,36,24,23,20,17,14,26,6,32,37,5,19,30]))),vo=n.lazy(()=>w(()=>import("./ApiKeys-__yoww1B.js"),__vite__mapDeps([38,1,5,3,4,2,28,7,35,27,26,39,22,20,8,9,10,11,12,13,14,16,18,31,37,24,29]))),bo=n.lazy(()=>w(()=>import("./Settings-DRW9xcJ_.js"),__vite__mapDeps([40,1,35,27,7,3,4,5,2,14,16,26,32,6,20,37,24,23,29,19,8]))),wo=n.lazy(()=>w(()=>import("./Setup-Du0PQb1-.js"),__vite__mapDeps([41,1,14,3,4,36,24,23,20,17,26,6,7,32,16,39,22,29,8]))),Po=n.lazy(()=>w(()=>import("./About-BlRwok0C.js"),__vite__mapDeps([42,1,2,3,4,13,14,5,37,24,16,43,20,8,7]))),xo=n.lazy(()=>w(()=>import("./Help-53lFT60j.js"),__vite__mapDeps([44,1,2,3,4,13,14,29,20,24,8,7]))),To=n.lazy(()=>w(()=>import("./Login-wXXmJSKA.js"),__vite__mapDeps([45,1,14,3,4,26,20,8,24,7])));function Co(){return t.jsx(D,{to:"/providers",replace:!0})}function ko(){return t.jsx(D,{to:"/providers?tab=routing",replace:!0})}const be=[{path:"/",index:!0,element:ho,nav:{icon:Ne,labelKey:"nav.dashboard",titleKey:"nav.dashboard",descriptionKey:"dashboard.description",matchPaths:["/"]}},{path:"/logs",element:go,nav:{icon:qe,labelKey:"nav.logs",titleKey:"logs.title",descriptionKey:"logs.description"}},{path:"/providers",element:yo,nav:{icon:He,labelKey:"nav.providers",titleKey:"workbench.title",descriptionKey:"workbench.description",matchPaths:["/providers","/models","/routing"]}},{path:"/models",element:Co},{path:"/routing",element:ko},{path:"/events",element:fo,nav:{icon:je,labelKey:"nav.events",titleKey:"events.title",descriptionKey:"events.description"}},{path:"/api-keys",element:vo,nav:{icon:Me,labelKey:"nav.apiKeys",titleKey:"apiKeys.title",descriptionKey:"apiKeys.description"}},{path:"/setup",element:wo},{path:"/settings",element:bo,nav:{icon:Fe,labelKey:"nav.settings",titleKey:"settings.title",descriptionKey:"settings.description"}},{path:"/help",element:xo,nav:{icon:Oe,labelKey:"nav.help",titleKey:"help.title",descriptionKey:"help.intro"}},{path:"/about",element:Po,nav:{icon:Ke,labelKey:"nav.about",titleKey:"about.title",descriptionKey:"about.description"}}],Ao=[{path:"/login",element:To}],T=be.filter(e=>!!e.nav),S=[{labelKey:"nav.group.overview",paths:["/","/logs","/events"]},{labelKey:"nav.group.configure",paths:["/providers","/api-keys"]},{labelKey:"nav.group.system",paths:["/settings","/about"]}];function Io(e){return e==="/"?T[0]:T.find(o=>(o.nav.matchPaths??[o.path]).some(s=>s!=="/"&&e.startsWith(s)))??T[0]}function So(){return typeof window>"u"?"/":window.location.pathname.startsWith("/ui")?"/ui":"/"}const Ro=Ue("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/90",destructive:"bg-destructive text-destructive-foreground hover:bg-destructive/90",outline:"border-0 bg-card text-foreground shadow-[var(--surface-shadow)] hover:bg-secondary hover:text-foreground",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80",ghost:"text-muted-foreground hover:bg-secondary hover:text-foreground",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-9 px-4 py-2",sm:"h-8 px-3 text-[13px]",lg:"h-10 px-6",icon:"h-9 w-9"}},defaultVariants:{variant:"default",size:"default"}}),H=n.forwardRef(({className:e,variant:o,size:a,asChild:s=!1,...i},l)=>{const d=s?st:"button";return t.jsx(d,{className:h(Ro({variant:o,size:a,className:e})),ref:l,...i})});H.displayName="Button";const we=rt,Pe=it,Eo=n.forwardRef(({className:e,inset:o,children:a,...s},i)=>t.jsxs(ae,{ref:i,className:h("flex cursor-default select-none items-center gap-2 rounded-xl px-3 py-2 text-sm outline-none transition-colors focus:bg-accent/80 data-[state=open]:bg-accent/80 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",o&&"pl-8",e),...s,children:[a,t.jsx(_e,{className:"ml-auto"})]}));Eo.displayName=ae.displayName;const Lo=n.forwardRef(({className:e,...o},a)=>t.jsx(se,{ref:a,className:h("z-50 min-w-[8rem] overflow-hidden rounded-xl border border-[color:var(--surface-border)] bg-popover p-2 text-popover-foreground shadow-[var(--surface-shadow-lg)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",e),...o}));Lo.displayName=se.displayName;const W=n.forwardRef(({className:e,sideOffset:o=4,...a},s)=>t.jsx(nt,{children:t.jsx(ne,{ref:s,sideOffset:o,className:h("z-50 min-w-[8rem] overflow-hidden rounded-xl border border-[color:var(--surface-border)] bg-popover p-2 text-popover-foreground shadow-[var(--surface-shadow-lg)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",e),...a})}));W.displayName=ne.displayName;const V=n.forwardRef(({className:e,inset:o,...a},s)=>t.jsx(re,{ref:s,className:h("relative flex cursor-default select-none items-center gap-2 rounded-xl px-3 py-2 text-sm outline-none transition-colors focus:bg-accent/80 focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",o&&"pl-8",e),...a}));V.displayName=re.displayName;const Do=n.forwardRef(({className:e,children:o,checked:a,...s},i)=>t.jsxs(ie,{ref:i,className:h("relative flex cursor-default select-none items-center rounded-xl py-2 pl-8 pr-3 text-sm outline-none transition-colors focus:bg-accent/80 focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",e),checked:a,...s,children:[t.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:t.jsx(le,{children:t.jsx(ze,{className:"h-4 w-4"})})}),o]}));Do.displayName=ie.displayName;const No=n.forwardRef(({className:e,children:o,...a},s)=>t.jsxs(de,{ref:s,className:h("relative flex cursor-default select-none items-center rounded-xl py-2 pl-8 pr-3 text-sm outline-none transition-colors focus:bg-accent/80 focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",e),...a,children:[t.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:t.jsx(le,{children:t.jsx(Be,{className:"h-2 w-2 fill-current"})})}),o]}));No.displayName=de.displayName;const qo=n.forwardRef(({className:e,inset:o,...a},s)=>t.jsx(ce,{ref:s,className:h("px-3 py-2 text-sm font-semibold",o&&"pl-8",e),...a}));qo.displayName=ce.displayName;const Ho=n.forwardRef(({className:e,...o},a)=>t.jsx(ue,{ref:a,className:h("-mx-1 my-1 h-px bg-border/55",e),...o}));Ho.displayName=ue.displayName;const Y=[{mode:"light",labelKey:"common.theme.light",icon:_},{mode:"dark",labelKey:"common.theme.dark",icon:z},{mode:"system",labelKey:"common.theme.system",icon:We}];function jo(){const{mode:e,setMode:o,resolved:a}=he(),{t:s}=C(),i=Y.find(d=>d.mode===e),l=(i==null?void 0:i.icon)||(a==="dark"?z:_);return t.jsxs(we,{children:[t.jsx(Pe,{asChild:!0,children:t.jsxs(H,{variant:"ghost",size:"sm","aria-label":s("common.theme.label"),"data-testid":"theme-switcher-trigger",children:[t.jsx(l,{className:"mr-2 h-4 w-4","aria-hidden":"true"}),t.jsx("span",{className:"hidden sm:inline",children:s((i==null?void 0:i.labelKey)??"common.theme.label")})]})}),t.jsx(W,{align:"end",children:Y.map(d=>{const c=d.icon,p=d.mode===e;return t.jsxs(V,{onClick:()=>o(d.mode),className:p?"bg-accent":"",children:[t.jsx(c,{className:"mr-2 h-4 w-4","aria-hidden":"true"}),s(d.labelKey)]},d.mode)})})]})}const J=[{code:"zh",labelKey:"language.zh",nativeName:"中文"},{code:"en",labelKey:"language.en",nativeName:"English"}];function Mo(){const{i18n:e,t:o}=C(),a=e.language.startsWith("zh")?"zh":"en",s=J.find(l=>l.code===a),i=l=>{typeof window<"u"&&window.localStorage.setItem(q.language,l),e.changeLanguage(l)};return t.jsxs(we,{children:[t.jsx(Pe,{asChild:!0,children:t.jsxs(H,{variant:"ghost",size:"sm","aria-label":o("common.languageSelector"),"data-testid":"language-switcher-trigger",children:[t.jsx(te,{className:"mr-2 h-4 w-4","aria-hidden":"true"}),t.jsx("span",{className:"hidden sm:inline",children:(s==null?void 0:s.nativeName)??"Language"})]})}),t.jsx(W,{align:"end",children:J.map(l=>t.jsx(V,{onClick:()=>i(l.code),className:l.code===a?"bg-accent":"",children:l.nativeName},l.code))})]})}const Fo=lt,Oo=dt,Ko=ct,xe=n.forwardRef(({className:e,sideOffset:o=4,...a},s)=>t.jsx(ut,{children:t.jsx(pe,{ref:s,sideOffset:o,className:h("z-50 overflow-hidden rounded-lg border border-border bg-popover px-3 py-2 text-sm text-popover-foreground shadow-[var(--surface-shadow)] animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",e),...a})}));xe.displayName=pe.displayName;const Te=n.forwardRef(({className:e,...o},a)=>t.jsx(y,{ref:a,className:h("flex h-full w-full flex-col overflow-hidden bg-card text-foreground",e),...o}));Te.displayName=y.displayName;const Uo=({open:e,onOpenChange:o,title:a,children:s})=>t.jsx(pt,{open:e,onOpenChange:o,children:t.jsxs(mt,{children:[t.jsx(ht,{className:"fixed inset-0 z-50 bg-background/95 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0"}),t.jsxs(gt,{"aria-describedby":void 0,className:h("fixed left-[50%] top-[18%] z-50 flex max-h-[62vh] w-[calc(100%-2rem)] max-w-xl translate-x-[-50%] flex-col overflow-hidden rounded-lg border border-border bg-card shadow-[var(--surface-shadow-lg)]","data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95 data-[state=open]:slide-in-from-top-[12%] data-[state=closed]:slide-out-to-top-[12%]"),children:[t.jsx(ft,{className:"sr-only",children:a}),t.jsx(Te,{className:h("[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-[10px] [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-wider [&_[cmdk-group-heading]]:text-muted-foreground","[&_[cmdk-group]]:px-1.5 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0","[&_[cmdk-input-wrapper]_svg]:h-4 [&_[cmdk-input-wrapper]_svg]:w-4 [&_[cmdk-input]]:h-12","[&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-2 [&_[cmdk-item]_svg]:h-4 [&_[cmdk-item]_svg]:w-4"),children:s})]})]})}),Ce=n.forwardRef(({className:e,...o},a)=>t.jsxs("div",{className:"flex items-center border-b border-border px-3","cmdk-input-wrapper":"",children:[t.jsx(oe,{className:"mr-2 h-4 w-4 shrink-0 text-muted-foreground","aria-hidden":"true"}),t.jsx(y.Input,{ref:a,className:h("flex h-12 w-full bg-transparent py-3 text-sm text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",e),...o})]}));Ce.displayName=y.Input.displayName;const ke=n.forwardRef(({className:e,...o},a)=>t.jsx(y.List,{ref:a,className:h("max-h-[52vh] overflow-y-auto overflow-x-hidden p-1.5",e),...o}));ke.displayName=y.List.displayName;const Ae=n.forwardRef(({className:e,...o},a)=>t.jsx(y.Empty,{ref:a,className:h("py-8 text-center text-sm text-muted-foreground",e),...o}));Ae.displayName=y.Empty.displayName;const K=n.forwardRef(({className:e,...o},a)=>t.jsx(y.Group,{ref:a,className:h("overflow-hidden text-foreground [&_[cmdk-group-heading]]:text-muted-foreground",e),...o}));K.displayName=y.Group.displayName;const U=n.forwardRef(({className:e,...o},a)=>t.jsx(y.Separator,{ref:a,className:h("-mx-1 h-px bg-border",e),...o}));U.displayName=y.Separator.displayName;const L=n.forwardRef(({className:e,...o},a)=>t.jsx(y.Item,{ref:a,className:h("relative flex cursor-pointer select-none items-center rounded-md px-2 py-2 text-sm text-foreground outline-none","data-[selected=true]:bg-secondary data-[selected=true]:text-foreground","data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50",e),...o}));L.displayName=y.Item.displayName;function _o({open:e,onOpenChange:o}){const{t:a,i18n:s}=C(),i=et(),{resolved:l,setMode:d}=he(),c=n.useMemo(()=>S.map(r=>({label:a(r.labelKey),items:T.filter(m=>r.paths.includes(m.path)).map(m=>({path:m.path,label:a(m.nav.labelKey),description:a(m.nav.descriptionKey),Icon:m.nav.icon}))})),[a]),p=r=>{o(!1),r()},u=()=>d(l==="dark"?"light":"dark"),g=()=>{const r=s.language.startsWith("zh")?"en":"zh";s.changeLanguage(r)};return t.jsxs(Uo,{open:e,onOpenChange:o,title:a("app.commandPalette.title"),children:[t.jsx(Ce,{placeholder:a("app.commandPalette.placeholder")}),t.jsxs(ke,{children:[t.jsx(Ae,{children:a("app.commandPalette.empty")}),c.map((r,m)=>r.items.length===0?null:t.jsxs(n.Fragment,{children:[m>0&&t.jsx(U,{}),t.jsx(K,{heading:r.label,children:r.items.map(({path:f,label:P,description:v,Icon:x})=>t.jsxs(L,{value:`${P} ${f} ${v}`,onSelect:()=>p(()=>i(f)),children:[t.jsx(x,{className:"mr-2 h-4 w-4 shrink-0 text-muted-foreground","aria-hidden":"true"}),t.jsx("span",{className:"flex-1",children:P})]},f))})]},r.label)),t.jsx(U,{}),t.jsxs(K,{heading:a("app.commandPalette.actions"),children:[t.jsxs(L,{value:a("app.commandPalette.toggleTheme"),onSelect:()=>p(u),children:[l==="dark"?t.jsx(_,{className:"mr-2 h-4 w-4 shrink-0 text-muted-foreground","aria-hidden":"true"}):t.jsx(z,{className:"mr-2 h-4 w-4 shrink-0 text-muted-foreground","aria-hidden":"true"}),t.jsx("span",{className:"flex-1",children:a("app.commandPalette.toggleTheme")})]}),t.jsxs(L,{value:a("app.commandPalette.switchLanguage"),onSelect:()=>p(g),children:[t.jsx(te,{className:"mr-2 h-4 w-4 shrink-0 text-muted-foreground","aria-hidden":"true"}),t.jsx("span",{className:"flex-1",children:a("app.commandPalette.switchLanguage")})]})]})]})]})}function zo(e,o,a){return Je({queryKey:e,placeholderData:Ve,...a,queryFn:async()=>{try{return await fe(o)}catch(s){throw E(s)}}})}const Bo={auth:{session:()=>["auth","session"],web:()=>["auth","web"]},config:{full:()=>["config","full"],info:()=>["config","info"],exportTimeout:()=>["config","export-timeout"]},customEndpoints:{all:()=>["custom-endpoints"]},providers:{all:()=>["providers","all"]},stats:{overview:(e="all")=>["stats","overview",e],daily:(e,o="all")=>["stats","daily",e,o],model:(e,o,a="all")=>["stats","model",e,o,a]},status:{byEndpoint:(e="all")=>["status",e],gateway:()=>["status","gateway"]},db:{info:()=>["db","info"]},dashboard:{summary:(e="all")=>["dashboard","summary",e]},logs:{list:e=>["logs",e],recent:(e="all")=>["logs","recent",e],detail:e=>["logs","detail",e]},events:{list:e=>["events",e],stats:()=>["events","stats"]},apiKeys:{all:()=>["api-keys"],overview:e=>["api-keys","overview",e],usage:e=>["api-keys","usage",e]}},X=15e3;function Wo(){var o;const{data:e}=zo(Bo.dashboard.summary(),{url:"/api/dashboard/summary",method:"GET"},{refetchInterval:X,refetchOnWindowFocus:!0,staleTime:X,retry:!1});return((o=e==null?void 0:e.status)==null?void 0:o.requestsPerMinute)??0}function G(e,o){return(o.nav.matchPaths??[o.path]).some(s=>s==="/"?e==="/":e.startsWith(s))}const Vo=["/","/logs","/providers","/api-keys"];function Go(e){for(const o of S)if(T.some(s=>o.paths.includes(s.path)&&G(e,s)))return o.labelKey;return S[0].labelKey}function Qo({compact:e}){return t.jsx(Re,{className:h(e?"h-8 w-8":"h-9 w-9"),title:"cc-gw"})}function Z({label:e,items:o,pathname:a,compact:s,onNavigate:i}){const{t:l}=C();return o.length===0?null:t.jsxs("div",{className:"space-y-0.5",children:[!s&&t.jsx("p",{className:"mb-1 px-3 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/50",children:e}),o.map(d=>{const c=d.nav.icon,p=G(a,d),u=l(d.nav.labelKey);return s?t.jsxs(Oo,{children:[t.jsx(Ko,{asChild:!0,children:t.jsxs(O,{to:d.path,onClick:i,end:d.path==="/",className:h("relative mx-1 flex items-center justify-center rounded-lg p-2 transition-colors",p?"bg-secondary text-foreground":"text-muted-foreground hover:bg-secondary hover:text-foreground"),"aria-label":u,children:[t.jsx("span",{"aria-hidden":!0,className:h("absolute left-0 top-1/2 h-5 w-0.5 -translate-y-1/2 origin-left rounded-full bg-primary motion-surface",p?"scale-x-100":"scale-x-0")}),t.jsx(c,{className:"h-5 w-5","aria-hidden":"true"})]})}),t.jsx(xe,{side:"right",children:u})]},d.path):t.jsxs(O,{to:d.path,onClick:i,end:d.path==="/",className:h("relative mx-2 flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors",p?"bg-secondary text-foreground":"text-muted-foreground hover:bg-secondary hover:text-foreground"),children:[t.jsx("span",{"aria-hidden":!0,className:h("absolute left-0 top-1/2 h-5 w-0.5 -translate-y-1/2 origin-left rounded-full bg-primary motion-surface",p?"scale-x-100":"scale-x-0")}),t.jsx(c,{className:"h-4 w-4 shrink-0","aria-hidden":"true"}),t.jsx("span",{className:"flex-1 truncate",children:u})]},d.path)})]})}function F({compact:e,onNavigate:o}){const{t:a}=C(),s=N(),{authEnabled:i,username:l}=B();return t.jsxs("div",{className:h("flex h-full flex-col",e?"items-center":""),children:[t.jsxs("div",{className:h("flex shrink-0 items-center border-b border-[color:var(--surface-border)]",e?"h-12 w-full justify-center":"h-12 gap-3 px-4"),children:[t.jsx(Qo,{compact:e}),!e&&t.jsx("div",{className:"min-w-0 flex-1",children:t.jsxs("div",{className:"flex min-w-0 items-center gap-2",children:[t.jsx("p",{className:"shrink-0 whitespace-nowrap text-sm font-semibold text-foreground",title:a("app.title"),children:"cc-gw"}),t.jsx("span",{className:"ml-auto inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded-md bg-success-bg px-1.5 py-0.5 text-[10px] font-medium text-success before:h-1.5 before:w-1.5 before:rounded-full before:bg-success before:animate-live-pulse",children:a("app.online")})]})})]}),t.jsx("div",{className:h("flex-1 overflow-y-auto",e?"w-full p-2":"space-y-4 p-3"),children:e?t.jsx(Fo,{delayDuration:0,children:t.jsx("div",{className:"space-y-1",children:S.map((d,c)=>t.jsxs("div",{children:[c>0&&t.jsx("div",{className:"my-2 border-t border-border"}),t.jsx(Z,{label:"",items:T.filter(p=>d.paths.includes(p.path)),pathname:s.pathname,compact:!0,onNavigate:o})]},d.labelKey))})}):t.jsx(t.Fragment,{children:S.map(d=>t.jsx(Z,{label:a(d.labelKey),items:T.filter(c=>d.paths.includes(c.path)),pathname:s.pathname,onNavigate:o},d.labelKey))})}),i&&l&&!e&&t.jsx("div",{className:"shrink-0 border-t border-border p-3",children:t.jsxs("div",{className:"flex items-center gap-2 rounded-lg px-2 py-2",children:[t.jsx("div",{className:"flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-secondary text-xs font-medium text-muted-foreground",children:l.slice(0,2).toUpperCase()}),t.jsx("span",{className:"flex-1 truncate text-xs text-muted-foreground",children:l})]})})]})}function $o({onMore:e}){const{t:o}=C(),a=N(),s=T.filter(i=>Vo.includes(i.path));return t.jsxs("nav",{className:"fixed inset-x-0 bottom-0 z-30 flex h-14 items-stretch border-t border-border bg-card pb-[env(safe-area-inset-bottom)] md:hidden","aria-label":o("nav.more"),children:[s.map(i=>{const l=i.nav.icon,d=G(a.pathname,i);return t.jsxs(O,{to:i.path,end:i.path==="/",className:h("flex flex-1 flex-col items-center justify-center gap-0.5 text-[10px] font-medium transition-colors",d?"text-primary":"text-muted-foreground"),"aria-current":d?"page":void 0,children:[t.jsx(l,{className:"h-5 w-5","aria-hidden":"true"}),t.jsx("span",{className:"truncate",children:o(i.nav.labelKey)})]},i.path)}),t.jsxs("button",{type:"button",onClick:e,className:"flex flex-1 flex-col items-center justify-center gap-0.5 text-[10px] font-medium text-muted-foreground transition-colors",children:[t.jsx(Ge,{className:"h-5 w-5","aria-hidden":"true"}),t.jsx("span",{children:o("nav.more")})]})]})}function Yo(){const{t:e}=C(),o=N(),[a,s]=n.useState(!1),[i,l]=n.useState(!1),{authEnabled:d,username:c,logout:p}=B(),[u,g]=n.useState(!1),r=Wo();n.useEffect(()=>{s(!1)},[o.pathname]),n.useEffect(()=>{const x=R=>{(R.metaKey||R.ctrlKey)&&R.key.toLowerCase()==="k"&&(R.preventDefault(),l(Se=>!Se))};return window.addEventListener("keydown",x),()=>window.removeEventListener("keydown",x)},[]);const m=n.useMemo(()=>Io(o.pathname),[o.pathname]),f=e(m.nav.titleKey??m.nav.labelKey),P=e(Go(o.pathname)),v=async()=>{if(!u){g(!0);try{await p()}finally{g(!1)}}};return t.jsxs("div",{className:"flex h-screen overflow-hidden bg-background",children:[t.jsx("a",{href:"#main-content",className:"sr-only focus:not-sr-only focus:absolute focus:z-50 focus:m-4 focus:rounded-md focus:bg-primary focus:px-4 focus:py-2 focus:text-primary-foreground",children:e("app.skipToContent")}),t.jsx("aside",{className:"hidden w-14 shrink-0 flex-col border-r border-border bg-card md:flex lg:hidden",children:t.jsx(F,{compact:!0})}),t.jsx("aside",{className:"hidden w-[236px] shrink-0 flex-col border-r border-border bg-card lg:flex",children:t.jsx(F,{})}),t.jsxs("div",{className:"flex min-w-0 flex-1 flex-col",children:[t.jsxs("header",{className:"sticky top-0 z-30 flex h-14 shrink-0 items-center gap-3 border-b border-border bg-card px-4 lg:px-6",children:[t.jsxs("div",{className:"flex min-w-0 flex-1 animate-fade-in items-baseline gap-2",children:[t.jsx("span",{className:"hidden shrink-0 text-xs text-muted-foreground/60 sm:inline",children:P}),t.jsx("span",{"aria-hidden":!0,className:"hidden shrink-0 text-xs text-muted-foreground/40 sm:inline",children:"/"}),t.jsx("h1",{className:"truncate text-sm font-semibold text-foreground",children:f})]},o.pathname),t.jsxs("button",{type:"button",onClick:()=>l(!0),"data-testid":"command-palette-trigger","aria-label":e("app.commandPalette.trigger"),className:"hidden h-8 shrink-0 items-center gap-2 rounded-md border border-border bg-secondary px-2.5 text-xs text-muted-foreground transition-colors hover:border-primary hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring md:inline-flex",children:[t.jsx(oe,{className:"h-3.5 w-3.5","aria-hidden":"true"}),t.jsx("span",{children:e("app.commandPalette.trigger")}),t.jsx("kbd",{className:"rounded-[3px] border border-border bg-card px-1.5 py-px text-[10px] leading-none text-muted-foreground",children:"⌘K"})]}),t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx("span",{"data-testid":"header-rpm-badge",className:"hidden items-center gap-1.5 rounded-md bg-success-bg px-2 py-1.5 text-[11px] font-semibold text-success before:h-1.5 before:w-1.5 before:rounded-full before:bg-success before:animate-live-pulse sm:inline-flex",children:e("app.liveRpm",{count:r})}),d&&c&&t.jsx("span",{className:"hidden text-xs text-muted-foreground sm:inline",children:e("login.status",{username:c})}),d&&t.jsx(H,{variant:"ghost",size:"sm",onClick:()=>void v(),disabled:u,children:e(u?"common.actions.loading":"common.actions.logout")}),t.jsx(Mo,{}),t.jsx(jo,{})]})]}),t.jsx("main",{id:"main-content",role:"main",tabIndex:-1,className:"flex min-h-0 flex-1 flex-col",children:t.jsx("div",{className:"flex min-h-0 flex-1 flex-col overflow-y-auto px-4 pb-20 pt-4 md:pb-4 lg:px-6",children:t.jsx("div",{className:"mx-auto flex w-full max-w-[1280px] flex-1 flex-col",children:t.jsx(tt,{})})})})]}),t.jsx($o,{onMore:()=>s(!0)}),a&&t.jsxs("div",{className:"fixed inset-0 z-40 md:hidden",role:"dialog","aria-modal":"true",children:[t.jsx("div",{className:"fixed inset-0 bg-background/95",onClick:()=>s(!1)}),t.jsx("div",{id:"mobile-nav",className:"fixed inset-y-0 left-0 w-[236px] bg-card shadow-[var(--surface-shadow-lg)] animate-in slide-in-from-left duration-200",children:t.jsx(F,{onNavigate:()=>s(!1)})})]}),t.jsx(_o,{open:i,onOpenChange:l})]})}function Jo({children:e}){const{authEnabled:o,isAuthenticated:a,loading:s}=B(),i=N();return s?t.jsx(ve,{}):!o||a?e:t.jsx(D,{to:"/login",replace:!0,state:{from:i}})}function Xo(){return t.jsx(Jo,{children:t.jsx(Yo,{})})}function Zo(){return t.jsx(ot,{basename:So(),children:t.jsx(n.Suspense,{fallback:t.jsx(ve,{}),children:t.jsxs(at,{children:[t.jsx(I,{path:"/",element:t.jsx(Xo,{}),children:be.map(e=>{const o=e.element;return e.index?t.jsx(I,{index:!0,element:t.jsx(o,{})},"index"):t.jsx(I,{path:e.path.slice(1),element:t.jsx(o,{})},e.path)})}),Ao.map(e=>{const o=e.element;return t.jsx(I,{path:e.path,element:t.jsx(o,{})},e.path)}),t.jsx(I,{path:"*",element:t.jsx(D,{to:"/",replace:!0})})]})})})}function ea(){return t.jsx(uo,{children:t.jsx(Zo,{})})}const Ie=document.getElementById("root");if(!Ie)throw new Error("Root element #root not found");Qe.createRoot(Ie).render(t.jsx($e.StrictMode,{children:t.jsx(ea,{})}));export{H as B,ve as L,Fo as T,ia as a,so as b,zo as c,b as d,la as e,Oo as f,Ko as g,xe as h,A as i,B as j,Bo as q,fe as r,q as s,E as t,he as u};
61
+ After configuration, clients access via \`http://127.0.0.1:4100/claude/v1/messages\` (path auto-expansion).`,'Enable "Store request bodies" / "Store response bodies" to inspect and copy client-side and upstream payloads from the log drawer when troubleshooting.',"If you do not need payload-level troubleshooting, turn off payload storage to reduce local disk usage and privacy exposure.",'Use **routing presets** to save common mapping schemes, such as "Claude models through Anthropic" or "GPT models through an OpenAI-compatible provider", then apply them per endpoint when switching providers.',"If you edit `~/.cc-gw/config.json` manually, refresh the Settings page or restart cc-gw so the UI reflects the latest configuration.","🗃️ **Data backup**: Regularly back up the `~/.cc-gw/` directory — it holds config.json, the SQLite database, and logs.",'🧹 **Log & database maintenance**: Adjust the log retention days to your needs. Under "Settings → Log cleanup" you can clean up expired logs, clear everything, or run "Compact database" (VACUUM) to reclaim disk space. For best results, clean logs before compacting.']}},faq:{title:"Frequently asked questions",items:[{q:"How can I change the default model for each endpoint?",a:'Go to "Routing" and edit the routing workspace for /anthropic or /openai. Saving applies the change right away.'},{q:"How do I use custom endpoints?",a:'Create a custom endpoint in the "Routing" page by configuring a base path (e.g., `/my-endpoint`) and protocol type. The system automatically registers full API paths based on the protocol. For example, after configuring `/claude` + `anthropic` protocol, clients access via `http://127.0.0.1:4100/claude/v1/messages`.\n\nIf you encounter 404 errors, check:\n1) Is the endpoint enabled?\n2) Are clients using the complete path (including protocol subpath)?\n3) Check server logs to confirm route registration'},{q:"Why does provider testing say no model is configured?",a:"Provider settings store Base URL and authentication, but the test request still needs a concrete model. Add at least one model under that Provider, or set a default model, then test again."},{q:"When do I need model routing?",a:"Configure routing when the client model name differs from the upstream model name, when `/anthropic` and `/openai` should go to different providers, or when different clients need different provider mappings. Target models use `providerId:modelId`; `providerId:*` passes the original client model name through to that provider."},{q:"Why are cached token numbers missing?",a:"Upstream providers must return `cached_tokens` or `input_tokens_details.cached_tokens`. Enable cache metrics on the provider if supported."},{q:"How can I use different models for different clients?",a:"The recommended approach is to create dedicated custom endpoints for different clients and maintain independent routing rules per endpoint. You can also point clients at `/anthropic`, `/openai`, or custom Base URLs. API keys are mainly for auditing, source separation, and revocation; they do not carry separate routing rules by themselves."},{q:"How do I enable login protection for the Web UI?",a:'Go to "Settings → Security", turn on login protection, and set a username and password. After saving, the next visit to `/ui` will require credentials. When login is disabled, the console is open to anyone who can reach the port — enable it for shared or public deployments.'},{q:"How does Codex CLI connect to cc-gw?",a:'Edit `~/.codex/config.toml`: set `model_provider` to "cc_gw", point `base_url` at the cc-gw OpenAI-compatible endpoint, and configure the matching environment variable. See the Codex CLI Configuration section above for the full example.'},{q:"How do I back up and migrate my configuration?",a:"Back up the entire `~/.cc-gw/` directory, which contains config.json, the database, and log files. To migrate, restore the directory on the new machine and restart the service."},{q:"What should I do if the Web UI shows a 404 error?",a:"Make sure the Web UI has been built with `pnpm --filter @cc-gw/web build`, or use the npm-installed global version. Also check the static asset path reported in the service startup logs."}]}},Qt={title:"About",description:"Review cc-gw version details, build metadata, and current runtime status.",app:{title:"Application",subtitle:"Gateway build metadata at a glance.",labels:{name:"Name",version:"Version",buildTime:"Build time",runtime:"Backend runtime",backendVersion:"Backend version"},hint:{buildTime:"Timestamps are recorded in UTC so you can trace deployments easily."}},status:{title:"Runtime status",subtitle:"Live metrics reported by the running gateway.",loading:"Fetching status...",empty:"Unable to retrieve status information.",labels:{host:"Listen host",port:"Listen port",providers:"Providers configured",active:"Active requests",platform:"Platform",pid:"Process PID"},hint:{active:"Active request totals refresh roughly every minute."}},support:{title:"Operational notes",subtitle:"Maintenance guidance",description:"Manage providers, routing, and logs in the Web UI; advanced settings live in ~/.cc-gw/config.json.",tip:"Consider keeping ~/.cc-gw/config.json under version control or managing it via automation scripts.",actions:{checkUpdates:"Check for updates",checkingUpdates:"Checking..."}},update:{available:"Update available: v{{version}}",current:"You are on the latest version: v{{version}}",channel:"Channel: {{channel}}"},toast:{statusError:{title:"Failed to load status"},upToDate:{title:"You are on the latest version: v{{version}}",description:"No newer release was found on npm."},updateAvailable:{title:"Update available: v{{version}}",description:"Upgrade with npm install -g {{packageName}}."},updateError:{title:"Failed to check for updates"}}},$t={title:"API Keys Management",description:"Create and manage API keys for gateway access",helper:"Use separate keys for each client, environment, or automation task so you can audit, restrict, and revoke access cleanly.",createNew:"Create New Key",createAction:"Create",createDescription:"Create a new API key for authentication and optionally add a description.",descriptionLabel:"Key description (optional)",keyDescriptionPlaceholder:"e.g. Internal staging access only",keyNamePlaceholder:"Enter key name",keyCreated:"API Key Created",saveKeyWarning:"Keep this key secure. You can also reveal the full key anytime from the key list.",wildcard:"Any Key",wildcardHint:"When enabled, any custom key — including an empty key — is accepted. Disable this key to enforce strict authentication.",status:{enabled:"Enabled",disabled:"Disabled"},actions:{enable:"Enable",disable:"Disable",delete:"Delete",reveal:"Reveal key",hide:"Hide key"},created:"Created",lastUsed:"Last Used",requestCount:"Requests",totalTokens:"Total Tokens",deleteDialogTitle:"Delete API key",confirmDelete:"Are you sure you want to delete this API key? This action cannot be undone.",errors:{nameRequired:"Key name is required",maxConcurrencyInvalid:"Max concurrency must be a positive integer"},analytics:{title:"Key Usage Analytics",description:"Highlights for the past {{days}} days of API key activity",range:{today:"Today",week:"Last 7 days",month:"Last 30 days"},cards:{total:"Total keys",enabled:"Enabled keys",active:"Active keys ({{days}} days)"},charts:{requests:"Top 10 keys by request count",tokens:"Top 10 keys by token usage"},tokens:{input:"Input tokens",output:"Output tokens"},requestsSeries:"Requests",empty:"No activity for the selected range.",emptyHint:"Create and use at least one key, then charts will fill in as real traffic arrives.",actions:{logs:"Open request logs"},unknownKey:"Unknown key"},quickStart:{title:"Recommended workflow",description:"Start with separate keys per client, then tighten endpoint access as needed.",create:{title:"Split keys by client",description:"Create different keys for Claude Code, Codex, CI, or staging so logs stay easy to trace."},restrict:{title:"Restrict endpoint access",description:"Limit keys to Anthropic, OpenAI, or custom endpoints when you want tighter isolation."},wildcard:{title:"Use wildcard sparingly",description:"Wildcard access is convenient for migration, but named keys are safer for production."}},list:{title:"Key Inventory",emptyTitle:"Create your first API key",empty:"No API keys found. Use the button above to create one.",emptyFilteredTitle:"No keys match the current filters",emptyFiltered:"No API keys match the current filters."},filters:{searchPlaceholder:"Search by name, description, or endpoint",all:"All",enabled:"Enabled",disabled:"Disabled"},summary:{totalCount:"{{count}} keys",wildcard:"Wildcard keys: {{count}}",restricted:"Restricted keys: {{count}}",unrestricted:"Unrestricted keys: {{count}}"},views:{cards:"Cards",compact:"Compact list"},tabs:{inventory:"Keys",analytics:"Usage"},table:{name:"Key",access:"Access",actions:"Actions",accessWildcard:"Wildcard access",accessOpen:"All endpoints"},toast:{keyCreated:"API key created successfully",keyUpdated:"API key updated successfully",keyDeleted:"API key deleted successfully",keyCopied:"Key copied to clipboard",createFailure:"Failed to create: {{message}}",updateFailure:"Failed to update: {{message}}",deleteFailure:"Failed to delete: {{message}}",revealFailure:"Failed to reveal key",copyFailure:"Failed to copy"},allowedEndpoints:"Allowed Endpoints",allEndpoints:"All endpoints (unrestricted)",editEndpoints:"Edit Endpoint Access",endpointRestricted:"Restricted",selectEndpoints:"Select which endpoints this key can access. Leave empty to allow all.",maxConcurrency:"Max Concurrency",maxConcurrencyPlaceholder:"Leave empty for unlimited",maxConcurrencyHelper:"Set the maximum number of simultaneous requests for this key. Leave empty or set to 0 for unlimited."},Yt={title:"Custom Endpoints",description:"Manage custom API endpoints with multiple protocol support.",createButton:"Add Endpoint",createTitle:"Create Endpoint",editTitle:"Edit Endpoint",emptyTitle:"No custom endpoints",emptyDescription:'Click "Add Endpoint" to create your first custom endpoint.',loadError:"Failed to load endpoints",id:"ID",path:"Path",disabled:"Disabled",hasRouting:"Routing configured",protocols:{anthropic:"Anthropic Protocol","openai-chat":"OpenAI Chat","openai-responses":"OpenAI Responses"},protocolHints:{anthropic:"Anthropic Messages API protocol (/v1/messages)","openai-chat":"OpenAI Chat Completions API protocol (/v1/chat/completions)","openai-responses":"OpenAI Responses API protocol (/v1/responses)"},form:{id:"Endpoint ID",idPlaceholder:"e.g. custom-api",idHint:"ID cannot be changed after creation, used for internal identification.",label:"Display Name",labelPlaceholder:"e.g. My Custom API",path:"Access Path",pathPlaceholder:"e.g. /custom/api",pathHint:"Path must start with /. Changes take effect immediately.",protocol:"Protocol Type",enabled:"Enable this endpoint"},routing:{title:"Routing Configuration (Optional)",modelRoutes:"Model Routing Rules",addRoute:"Add Rule",noRoutes:"No routing rules",sourceModelPlaceholder:"Source model (e.g. claude-3-5-sonnet-20241022)",targetPlaceholder:"Target (e.g. anthropic:claude-3-5-sonnet-20241022)",modelRoutesHint:"Format: source model → provider:model, wildcards supported (e.g. gpt-* → openai:*)",defaults:"Default Model Configuration",defaultCompletion:"Default for completion tasks",defaultReasoning:"Default for reasoning tasks",defaultBackground:"Default for background tasks",longContextThreshold:"Long context threshold (tokens)",defaultPlaceholder:"e.g. anthropic:claude-3-5-sonnet-20241022"},createSuccess:"Endpoint created successfully",createError:"Failed to create: {{error}}",updateSuccess:"Endpoint updated successfully",updateError:"Failed to update: {{error}}",deleteSuccess:"Endpoint deleted successfully",deleteError:"Failed to delete: {{error}}",deleteConfirm:'Are you sure you want to delete endpoint "{{label}}"? This action cannot be undone.',validationError:"Please fill in all required fields"},Jt={title:"Providers & Routing Workbench",description:"Manage model providers, endpoint routing rules and custom endpoints in one place.",viewSwitch:{label:"Workbench view",providers:"Providers",providersDesc:"Manage upstream model services",routing:"Routing",routingDesc:"Decide where requests are forwarded"},routingGuide:{flow:"Client-requested model (e.g. claude-sonnet-4) → matched route rule → forwarded to a provider's model",hint:"Clients request by model name: rules below are matched first, and anything unmatched falls through to the default."},flow:{title:"Request flow",clientStep:"Client model",endpointStep:"Endpoint",matchStep:"Route rule match",targetStep:"Forward target",fallbackNote:"Unmatched requests use the default"},endpointRail:{title:"Endpoints",subtitle:"Each endpoint has its own rule set",add:"New endpoint",ruleCount:"{{count}}"},endpointConfig:{title:"Endpoint config"},hitSim:{title:"Routing Hit Simulator",subtitle:"Enter a client model name to preview which rule it hits, in real time",endpointLabel:"Endpoint",modelPlaceholder:"e.g. claude-sonnet-4-5-20250929",thinking:"Thinking",anyModel:"(any model)",empty:"Enter a model name to see which route it hits",errorPrefix:"Could not resolve route",reasonModelRoute:"Hit a model route rule",reasonModelRouteAlias:"Hit a model route rule (via alias)",reasonDirectMatch:"Direct provider match",reasonThinkingDefault:"Hit thinking default",reasonCompletionDefault:"Hit completion default",reasonLongContext:"Hit long-context default",longContextDetail:"Estimated ~{{estimate}} tokens, over threshold {{threshold}}",reasonFallback:"No rule matched · global fallback",fallbackHint:"No rule matched this model; it will use the global fallback."},defaults:{title:"Default forwarding",description:"Requests that match no rule below are forwarded here. With a single provider, setting this alone is usually all you need.",cardSubtitle:"Requests that match no rule go here",completionLabel:"Forward to (provider:model)",moreLabel:"More default settings",reasoningLabel:"Reasoning model",backgroundLabel:"Long-context model",thresholdLabel:"Long-context threshold (tokens)",targetPlaceholder:"e.g. openai:gpt-4o",save:"Save defaults",saveSuccess:"Default forwarding saved.",saveFailure:"Failed to save default forwarding: {{message}}"},specific:{title:"Route specific models (optional)",description:"Only requests for these model names hit the rules below — they take priority over default forwarding."},advanced:{title:"Advanced",wildcardHint:"Rules support * wildcards; the most specific match wins."},routing:{rulesTitle:"Route rules",rulesSubtitle:"Matched top-to-bottom, * wildcards supported; drag the left handle to set priority",dragHandle:"Drag to reorder",sourceLabel:"Client-requested model",targetLabel:"Forward to (provider:model)",emptyTitle:"No route rules yet",emptyDescription:`Clients send requests by model name — configure here which provider's model each should be forwarded to. For example, map claude-sonnet-4 to the provider you just added; model names not listed here use "Default forwarding" above.`,addFirst:"Add your first rule"},detail:{routesTitle:"Routing rules using this provider",routesEmptyHint:"No route rules reference it yet — client requests can't reach this provider.",addRuleCta:"Add a rule in Routing view",viewRoute:"Open this rule in the routing view"},list:{emptyHint:"Add an upstream model service first (Anthropic, OpenAI, or a compatible endpoint), then map client models to it in the Routing view."},endpoints:{create:"New endpoint",editRoute:"Routes",defaultUnset:"Not set",table:{name:"Name",protocol:"Protocol",paths:"Paths",rules:"Rules",defaultTarget:"Default",status:"Status",actions:"Actions"}},testResult:{title:"Latest test",success:"Connected",failure:"Test failed",status:"Status {{status}}",duration:"{{duration}}",never:"Not tested yet"},drawer:{verifyTitle:"Connection check",verifyHint:"Runs a real request against the current config — including unsaved drafts — to confirm auth and connectivity.",verifySaveFirst:"Save the provider before running a connection test.",verifyRun:"Test connection"}},Xt={title:"Guided setup",description:"Finish cold-start configuration in four steps: connect a provider, configure routing, create an API key, then verify connectivity.",steps:{provider:{navLabel:"Add provider",title:"Add a model provider",description:"Connect an upstream model service. Routing and client requests will point to it.",existingTitle:"{{count}} provider(s) already configured",existingHint:"Pick an existing provider and continue, or add another one.",chooseLabel:"Provider used by this wizard",addAnother:"Add another provider",backToList:"Back to existing providers",saveAndContinue:"Save and continue"},routing:{navLabel:"Configure routing",title:"Configure a default route",description:"Add a route on the Anthropic endpoint that maps a common client model name to the new provider model.",existingTitle:"Current Anthropic endpoint routes",existingHint:"Routes already exist — review them and continue to the next step.",emptyHint:"No routes yet. Save the default route below to continue.",sourceLabel:"Source model (client request name)",sourceHint:"When a client switches to a new model name later, add a matching rule under Providers & Routing → Routing.",targetLabel:"Target (provider:model)",save:"Save route",saving:"Saving..."},apiKey:{navLabel:"Create API key",title:"Create an API key",description:"Clients use this key to access the gateway. Scoped to the Anthropic endpoint by default.",create:"Create key",creating:"Creating...",createFailed:"Failed to create API key: {{message}}"},verify:{navLabel:"Verify & finish",title:"Verify and finish",description:"Test provider connectivity, then send your first request from a client with the baseUrl and key.",noProvider:"No provider available — go back to step 1.",testingProvider:'Testing provider "{{name}}"',envTitle:"Send your first request from a client",envHint:"Set the following variables in your client environment (Claude Code example), then send a request:"}},actions:{finish:"Finish"}},Zt={zh:{translation:{app:yt,nav:vt,language:bt,common:wt,login:Pt,dashboard:xt,logs:Tt,providers:Ct,modelManagement:kt,routingManagement:At,events:It,settings:St,help:Rt,about:Et,apiKeys:Lt,endpoints:Dt,workbench:Nt,setup:qt}},en:{translation:{app:Ht,nav:jt,language:Mt,common:Ft,login:Ot,dashboard:Kt,logs:Ut,providers:_t,modelManagement:zt,routingManagement:Bt,events:Wt,settings:Vt,help:Gt,about:Qt,apiKeys:$t,endpoints:Yt,workbench:Jt,setup:Xt}}};function eo(){if(typeof window>"u")return"zh";const e=window.localStorage.getItem(q.language);return e==="zh"||e==="en"?e:"zh"}function Q(e){if(typeof window>"u")return;const o=e.toLowerCase().startsWith("zh")?"zh":"en";window.localStorage.setItem(q.language,o)}k.isInitialized||(k.use(Xe).init({resources:Zt,lng:eo(),fallbackLng:"en",interpolation:{escapeValue:!1}}),k.on("languageChanged",Q),Q(k.language));function to(e,o){const a=o,s=a==null?void 0:a.status;return s===401||s===403||s===404?!1:e<2}function oo(){return new Ee({defaultOptions:{queries:{retry:to,staleTime:3e4,refetchOnWindowFocus:!1},mutations:{retry:!1}}})}function ao(e){return typeof e=="function"?e():e}function so(e,o,a={}){const{serialize:s=JSON.stringify,deserialize:i=JSON.parse}=a,[l,d]=n.useState(()=>{const c=ao(o);if(typeof window>"u")return c;const p=window.localStorage.getItem(e);if(p===null)return c;try{return i(p)}catch{return c}});return n.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(e,s(l))}catch{}},[e,l]),[l,d]}const me=n.createContext(void 0);function no(e){return e==="system"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e}function j(e){const o=document.documentElement;o.dataset.theme=e,e==="dark"?o.classList.add("dark"):o.classList.remove("dark")}function ro({children:e}){const[o,a]=so(q.themeMode,"system",{serialize:l=>l,deserialize:l=>l==="light"||l==="dark"||l==="system"?l:"system"}),s=n.useMemo(()=>typeof window<"u"?no(o):"light",[o]);typeof window<"u"&&j(s),n.useEffect(()=>{if(typeof window>"u")return;const l=window.matchMedia("(prefers-color-scheme: dark)"),d=c=>{o==="system"&&j(c.matches?"dark":"light")};return l.addEventListener("change",d),()=>l.removeEventListener("change",d)},[o]),n.useEffect(()=>{typeof window>"u"||j(s)},[s]);const i=n.useMemo(()=>({mode:o,resolved:s,setMode:a}),[o,s]);return t.jsx(me.Provider,{value:i,children:e})}function he(){const e=n.useContext(me);if(!e)throw new Error("useTheme must be used within ThemeProvider");return e}const ge=n.createContext(void 0);function io(){return`toast_${Math.random().toString(36).slice(2)}`}function lo({children:e}){const[o,a]=n.useState([]),s=n.useRef([]),i=n.useRef({});n.useEffect(()=>{s.current=o},[o]);const l=n.useCallback(r=>{const m=i.current[r];m&&(clearTimeout(m),delete i.current[r])},[]),d=n.useCallback(r=>{l(r),a(m=>m.filter(f=>f.id!==r))},[l]),c=n.useCallback(r=>{a(m=>m.map(f=>f.id===r?{...f,dismissing:!0}:f)),setTimeout(()=>d(r),200)},[d]),p=n.useCallback((r,m)=>{l(r),!(m<=0)&&(i.current[r]=setTimeout(()=>c(r),m))},[l,c]);n.useEffect(()=>()=>{Object.values(i.current).forEach(r=>clearTimeout(r)),i.current={}},[]);const u=n.useCallback(r=>{const m=r.durationMs??3e3,f=s.current.find(v=>r.id?v.id===r.id:v.title===r.title&&v.description===r.description&&v.variant===r.variant),P=(f==null?void 0:f.id)??r.id??io();a(v=>f?v.map(x=>x.id===P?{...x,...r,id:P,dismissing:!1}:x):[...v,{...r,id:P}]),p(P,m)},[p]),g=n.useMemo(()=>({toasts:o,pushToast:u,dismissToast:c}),[o,c,u]);return t.jsxs(ge.Provider,{value:g,children:[e,t.jsx("div",{"aria-live":"polite","aria-atomic":"false",className:"fixed right-6 top-6 z-50 flex w-80 flex-col gap-3",children:o.map(r=>t.jsx("div",{role:"alert",className:`rounded-md border border-border bg-card p-4 text-card-foreground shadow-[var(--surface-shadow-lg)] ${r.dismissing?"animate-toast-out":"animate-toast-in"} ${r.variant==="error"?"border-destructive/20 bg-destructive/10 text-destructive":r.variant==="success"?"border-success/20 bg-success-bg text-success":""}`,children:t.jsxs("div",{className:"flex items-start justify-between gap-4",children:[t.jsxs("div",{children:[t.jsx("p",{className:"text-sm font-semibold",children:r.title}),r.description?t.jsx("p",{className:"mt-1 text-sm opacity-75",children:r.description}):null]}),t.jsx("button",{type:"button","aria-label":"Dismiss",className:"text-sm text-muted-foreground hover:text-foreground",onClick:()=>r.id&&c(r.id),children:"×"})]})},r.id))})]})}function ia(){const e=n.useContext(ge);if(!e)throw new Error("useToast must be used within ToastProvider");return e}const b=ee.create({baseURL:"/",timeout:15e3,withCredentials:!0});b.interceptors.request.use(e=>{const o=(e.method??"get").toLowerCase();if(o==="get"||o==="head"){const a=Le.from(e.headers);a.set("Cache-Control","no-cache"),a.set("Pragma","no-cache"),e.headers=a}return e});b.interceptors.response.use(e=>e,e=>Promise.reject(e));function E(e){var o,a;if(ee.isAxiosError(e)){const s=(o=e.response)==null?void 0:o.status,i=(a=e.response)==null?void 0:a.data;return{message:(i==null?void 0:i.error)||(i==null?void 0:i.statusText)||e.message||k.t("common.requestFailed"),status:s,code:String(s??"unknown")}}return{message:e instanceof Error?e.message:k.t("common.requestFailed")}}async function fe(e){return(await b.request(e)).data}async function A(e){return(await e).data}const la={list:async()=>A(b.get("/api/custom-endpoints")),create:async e=>A(b.post("/api/custom-endpoints",e)),update:async(e,o)=>A(b.put(`/api/custom-endpoints/${e}`,o)),delete:async e=>A(b.delete(`/api/custom-endpoints/${e}`))},M={session:async()=>fe({url:"/auth/session",method:"GET"}),login:async(e,o)=>A(b.post("/auth/login",{username:e,password:o})),logout:async()=>A(b.post("/auth/logout"))},ye=n.createContext(void 0);function co({children:e}){const o=n.useRef(!0),[a,s]=n.useState({loading:!0,authEnabled:!1,isAuthenticated:!0,username:void 0,error:null}),i=n.useCallback(u=>{o.current&&s(u)},[]),l=n.useCallback(async()=>{i(u=>({...u,loading:!0,error:null}));try{const u=await M.session();i(()=>({loading:!1,authEnabled:!!u.authEnabled,isAuthenticated:!u.authEnabled||!!u.authenticated,username:u.username??void 0,error:null}))}catch(u){const g=E(u);i(()=>({loading:!1,authEnabled:!1,isAuthenticated:!0,username:void 0,error:g.message}))}},[i]),d=n.useCallback(async(u,g)=>{i(r=>({...r,loading:!0,error:null}));try{await M.login(u,g),await l()}catch(r){const m=E(r);throw i(f=>({...f,loading:!1,error:m.message,isAuthenticated:!1})),m}},[l,i]),c=n.useCallback(async()=>{i(u=>({...u,loading:!0}));try{await M.logout()}catch(u){const g=E(u);i(r=>({...r,error:g.message}))}finally{await l()}},[l,i]);n.useEffect(()=>{o.current=!0,l();const u=b.interceptors.response.use(g=>g,async g=>{var r;return((r=g==null?void 0:g.response)==null?void 0:r.status)===401&&await l(),Promise.reject(g)});return()=>{o.current=!1,b.interceptors.response.eject(u)}},[l]);const p=n.useMemo(()=>({...a,refresh:l,login:d,logout:c}),[a,l,d,c]);return t.jsx(ye.Provider,{value:p,children:e})}function B(){const e=n.useContext(ye);if(!e)throw new Error("useAuth must be used within an AuthProvider");return e}function uo({children:e}){const[o]=n.useState(()=>oo());return t.jsx(Ye,{client:o,children:t.jsx(Ze,{i18n:k,children:t.jsx(ro,{children:t.jsx(lo,{children:t.jsx(co,{children:e})})})})})}function ve({label:e}){const{t:o}=C();return t.jsx("div",{className:"flex h-full items-center justify-center p-12",role:"status","aria-live":"polite",children:t.jsxs("div",{className:"flex flex-col items-center gap-4 text-center",children:[t.jsx("div",{className:"flex h-14 w-14 items-center justify-center rounded-xl bg-secondary text-primary",children:t.jsx(De,{className:"h-6 w-6 animate-spin","aria-hidden":"true"})}),t.jsx("p",{className:"text-sm text-muted-foreground",children:e??o("common.loading")})]})})}const po="modulepreload",mo=function(e){return"/"+e},$={},w=function(o,a,s){let i=Promise.resolve();if(a&&a.length>0){document.getElementsByTagName("link");const d=document.querySelector("meta[property=csp-nonce]"),c=(d==null?void 0:d.nonce)||(d==null?void 0:d.getAttribute("nonce"));i=Promise.allSettled(a.map(p=>{if(p=mo(p),p in $)return;$[p]=!0;const u=p.endsWith(".css"),g=u?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${p}"]${g}`))return;const r=document.createElement("link");if(r.rel=u?"stylesheet":po,u||(r.as="script"),r.crossOrigin="",r.href=p,c&&r.setAttribute("nonce",c),document.head.appendChild(r),u)return new Promise((m,f)=>{r.addEventListener("load",m),r.addEventListener("error",()=>f(new Error(`Unable to preload CSS for ${p}`)))})}))}function l(d){const c=new Event("vite:preloadError",{cancelable:!0});if(c.payload=d,window.dispatchEvent(c),!c.defaultPrevented)throw d}return i.then(d=>{for(const c of d||[])c.status==="rejected"&&l(c.reason);return o().catch(l)})},ho=n.lazy(()=>w(()=>import("./Dashboard-C5pBcbjh.js"),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]))),go=n.lazy(()=>w(()=>import("./Logs-Cl14ySgm.js"),__vite__mapDeps([25,1,2,3,4,16,14,26,6,7,20,27,5,28,29,15,30,31,32,8,24,23]))),fo=n.lazy(()=>w(()=>import("./Events-DXuVlVli.js"),__vite__mapDeps([33,1,8,2,3,4,13,14,5,21,20,16,17,18,31,6,7,24]))),yo=n.lazy(()=>w(()=>import("./ProvidersWorkbench-CMXcjnjA.js"),__vite__mapDeps([34,1,8,35,27,7,3,4,2,16,36,24,23,20,17,14,26,6,32,37,5,19,30]))),vo=n.lazy(()=>w(()=>import("./ApiKeys-C3UcR3Xt.js"),__vite__mapDeps([38,1,5,3,4,2,28,7,35,27,26,39,22,20,8,9,10,11,12,13,14,16,18,31,37,24,29]))),bo=n.lazy(()=>w(()=>import("./Settings-BxtiQXA0.js"),__vite__mapDeps([40,1,35,27,7,3,4,5,2,14,16,26,32,6,20,37,24,23,29,19,8]))),wo=n.lazy(()=>w(()=>import("./Setup-r8hTQIkT.js"),__vite__mapDeps([41,1,14,3,4,36,24,23,20,17,26,6,7,32,16,39,22,29,8]))),Po=n.lazy(()=>w(()=>import("./About-DVONGhC8.js"),__vite__mapDeps([42,1,2,3,4,13,14,5,37,24,16,43,20,8,7]))),xo=n.lazy(()=>w(()=>import("./Help-DoBQVllq.js"),__vite__mapDeps([44,1,2,3,4,13,14,29,20,24,8,7]))),To=n.lazy(()=>w(()=>import("./Login-BKBhBrF_.js"),__vite__mapDeps([45,1,14,3,4,26,20,8,24,7])));function Co(){return t.jsx(D,{to:"/providers",replace:!0})}function ko(){return t.jsx(D,{to:"/providers?tab=routing",replace:!0})}const be=[{path:"/",index:!0,element:ho,nav:{icon:Ne,labelKey:"nav.dashboard",titleKey:"nav.dashboard",descriptionKey:"dashboard.description",matchPaths:["/"]}},{path:"/logs",element:go,nav:{icon:qe,labelKey:"nav.logs",titleKey:"logs.title",descriptionKey:"logs.description"}},{path:"/providers",element:yo,nav:{icon:He,labelKey:"nav.providers",titleKey:"workbench.title",descriptionKey:"workbench.description",matchPaths:["/providers","/models","/routing"]}},{path:"/models",element:Co},{path:"/routing",element:ko},{path:"/events",element:fo,nav:{icon:je,labelKey:"nav.events",titleKey:"events.title",descriptionKey:"events.description"}},{path:"/api-keys",element:vo,nav:{icon:Me,labelKey:"nav.apiKeys",titleKey:"apiKeys.title",descriptionKey:"apiKeys.description"}},{path:"/setup",element:wo},{path:"/settings",element:bo,nav:{icon:Fe,labelKey:"nav.settings",titleKey:"settings.title",descriptionKey:"settings.description"}},{path:"/help",element:xo,nav:{icon:Oe,labelKey:"nav.help",titleKey:"help.title",descriptionKey:"help.intro"}},{path:"/about",element:Po,nav:{icon:Ke,labelKey:"nav.about",titleKey:"about.title",descriptionKey:"about.description"}}],Ao=[{path:"/login",element:To}],T=be.filter(e=>!!e.nav),S=[{labelKey:"nav.group.overview",paths:["/","/logs","/events"]},{labelKey:"nav.group.configure",paths:["/providers","/api-keys"]},{labelKey:"nav.group.system",paths:["/settings","/about"]}];function Io(e){return e==="/"?T[0]:T.find(o=>(o.nav.matchPaths??[o.path]).some(s=>s!=="/"&&e.startsWith(s)))??T[0]}function So(){return typeof window>"u"?"/":window.location.pathname.startsWith("/ui")?"/ui":"/"}const Ro=Ue("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/90",destructive:"bg-destructive text-destructive-foreground hover:bg-destructive/90",outline:"border-0 bg-card text-foreground shadow-[var(--surface-shadow)] hover:bg-secondary hover:text-foreground",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80",ghost:"text-muted-foreground hover:bg-secondary hover:text-foreground",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-9 px-4 py-2",sm:"h-8 px-3 text-[13px]",lg:"h-10 px-6",icon:"h-9 w-9"}},defaultVariants:{variant:"default",size:"default"}}),H=n.forwardRef(({className:e,variant:o,size:a,asChild:s=!1,...i},l)=>{const d=s?st:"button";return t.jsx(d,{className:h(Ro({variant:o,size:a,className:e})),ref:l,...i})});H.displayName="Button";const we=rt,Pe=it,Eo=n.forwardRef(({className:e,inset:o,children:a,...s},i)=>t.jsxs(ae,{ref:i,className:h("flex cursor-default select-none items-center gap-2 rounded-xl px-3 py-2 text-sm outline-none transition-colors focus:bg-accent/80 data-[state=open]:bg-accent/80 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",o&&"pl-8",e),...s,children:[a,t.jsx(_e,{className:"ml-auto"})]}));Eo.displayName=ae.displayName;const Lo=n.forwardRef(({className:e,...o},a)=>t.jsx(se,{ref:a,className:h("z-50 min-w-[8rem] overflow-hidden rounded-xl border border-[color:var(--surface-border)] bg-popover p-2 text-popover-foreground shadow-[var(--surface-shadow-lg)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",e),...o}));Lo.displayName=se.displayName;const W=n.forwardRef(({className:e,sideOffset:o=4,...a},s)=>t.jsx(nt,{children:t.jsx(ne,{ref:s,sideOffset:o,className:h("z-50 min-w-[8rem] overflow-hidden rounded-xl border border-[color:var(--surface-border)] bg-popover p-2 text-popover-foreground shadow-[var(--surface-shadow-lg)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",e),...a})}));W.displayName=ne.displayName;const V=n.forwardRef(({className:e,inset:o,...a},s)=>t.jsx(re,{ref:s,className:h("relative flex cursor-default select-none items-center gap-2 rounded-xl px-3 py-2 text-sm outline-none transition-colors focus:bg-accent/80 focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",o&&"pl-8",e),...a}));V.displayName=re.displayName;const Do=n.forwardRef(({className:e,children:o,checked:a,...s},i)=>t.jsxs(ie,{ref:i,className:h("relative flex cursor-default select-none items-center rounded-xl py-2 pl-8 pr-3 text-sm outline-none transition-colors focus:bg-accent/80 focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",e),checked:a,...s,children:[t.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:t.jsx(le,{children:t.jsx(ze,{className:"h-4 w-4"})})}),o]}));Do.displayName=ie.displayName;const No=n.forwardRef(({className:e,children:o,...a},s)=>t.jsxs(de,{ref:s,className:h("relative flex cursor-default select-none items-center rounded-xl py-2 pl-8 pr-3 text-sm outline-none transition-colors focus:bg-accent/80 focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",e),...a,children:[t.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:t.jsx(le,{children:t.jsx(Be,{className:"h-2 w-2 fill-current"})})}),o]}));No.displayName=de.displayName;const qo=n.forwardRef(({className:e,inset:o,...a},s)=>t.jsx(ce,{ref:s,className:h("px-3 py-2 text-sm font-semibold",o&&"pl-8",e),...a}));qo.displayName=ce.displayName;const Ho=n.forwardRef(({className:e,...o},a)=>t.jsx(ue,{ref:a,className:h("-mx-1 my-1 h-px bg-border/55",e),...o}));Ho.displayName=ue.displayName;const Y=[{mode:"light",labelKey:"common.theme.light",icon:_},{mode:"dark",labelKey:"common.theme.dark",icon:z},{mode:"system",labelKey:"common.theme.system",icon:We}];function jo(){const{mode:e,setMode:o,resolved:a}=he(),{t:s}=C(),i=Y.find(d=>d.mode===e),l=(i==null?void 0:i.icon)||(a==="dark"?z:_);return t.jsxs(we,{children:[t.jsx(Pe,{asChild:!0,children:t.jsxs(H,{variant:"ghost",size:"sm","aria-label":s("common.theme.label"),"data-testid":"theme-switcher-trigger",children:[t.jsx(l,{className:"mr-2 h-4 w-4","aria-hidden":"true"}),t.jsx("span",{className:"hidden sm:inline",children:s((i==null?void 0:i.labelKey)??"common.theme.label")})]})}),t.jsx(W,{align:"end",children:Y.map(d=>{const c=d.icon,p=d.mode===e;return t.jsxs(V,{onClick:()=>o(d.mode),className:p?"bg-accent":"",children:[t.jsx(c,{className:"mr-2 h-4 w-4","aria-hidden":"true"}),s(d.labelKey)]},d.mode)})})]})}const J=[{code:"zh",labelKey:"language.zh",nativeName:"中文"},{code:"en",labelKey:"language.en",nativeName:"English"}];function Mo(){const{i18n:e,t:o}=C(),a=e.language.startsWith("zh")?"zh":"en",s=J.find(l=>l.code===a),i=l=>{typeof window<"u"&&window.localStorage.setItem(q.language,l),e.changeLanguage(l)};return t.jsxs(we,{children:[t.jsx(Pe,{asChild:!0,children:t.jsxs(H,{variant:"ghost",size:"sm","aria-label":o("common.languageSelector"),"data-testid":"language-switcher-trigger",children:[t.jsx(te,{className:"mr-2 h-4 w-4","aria-hidden":"true"}),t.jsx("span",{className:"hidden sm:inline",children:(s==null?void 0:s.nativeName)??"Language"})]})}),t.jsx(W,{align:"end",children:J.map(l=>t.jsx(V,{onClick:()=>i(l.code),className:l.code===a?"bg-accent":"",children:l.nativeName},l.code))})]})}const Fo=lt,Oo=dt,Ko=ct,xe=n.forwardRef(({className:e,sideOffset:o=4,...a},s)=>t.jsx(ut,{children:t.jsx(pe,{ref:s,sideOffset:o,className:h("z-50 overflow-hidden rounded-lg border border-border bg-popover px-3 py-2 text-sm text-popover-foreground shadow-[var(--surface-shadow)] animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",e),...a})}));xe.displayName=pe.displayName;const Te=n.forwardRef(({className:e,...o},a)=>t.jsx(y,{ref:a,className:h("flex h-full w-full flex-col overflow-hidden bg-card text-foreground",e),...o}));Te.displayName=y.displayName;const Uo=({open:e,onOpenChange:o,title:a,children:s})=>t.jsx(pt,{open:e,onOpenChange:o,children:t.jsxs(mt,{children:[t.jsx(ht,{className:"fixed inset-0 z-50 bg-background/95 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0"}),t.jsxs(gt,{"aria-describedby":void 0,className:h("fixed left-[50%] top-[18%] z-50 flex max-h-[62vh] w-[calc(100%-2rem)] max-w-xl translate-x-[-50%] flex-col overflow-hidden rounded-lg border border-border bg-card shadow-[var(--surface-shadow-lg)]","data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95 data-[state=open]:slide-in-from-top-[12%] data-[state=closed]:slide-out-to-top-[12%]"),children:[t.jsx(ft,{className:"sr-only",children:a}),t.jsx(Te,{className:h("[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-[10px] [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-wider [&_[cmdk-group-heading]]:text-muted-foreground","[&_[cmdk-group]]:px-1.5 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0","[&_[cmdk-input-wrapper]_svg]:h-4 [&_[cmdk-input-wrapper]_svg]:w-4 [&_[cmdk-input]]:h-12","[&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-2 [&_[cmdk-item]_svg]:h-4 [&_[cmdk-item]_svg]:w-4"),children:s})]})]})}),Ce=n.forwardRef(({className:e,...o},a)=>t.jsxs("div",{className:"flex items-center border-b border-border px-3","cmdk-input-wrapper":"",children:[t.jsx(oe,{className:"mr-2 h-4 w-4 shrink-0 text-muted-foreground","aria-hidden":"true"}),t.jsx(y.Input,{ref:a,className:h("flex h-12 w-full bg-transparent py-3 text-sm text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",e),...o})]}));Ce.displayName=y.Input.displayName;const ke=n.forwardRef(({className:e,...o},a)=>t.jsx(y.List,{ref:a,className:h("max-h-[52vh] overflow-y-auto overflow-x-hidden p-1.5",e),...o}));ke.displayName=y.List.displayName;const Ae=n.forwardRef(({className:e,...o},a)=>t.jsx(y.Empty,{ref:a,className:h("py-8 text-center text-sm text-muted-foreground",e),...o}));Ae.displayName=y.Empty.displayName;const K=n.forwardRef(({className:e,...o},a)=>t.jsx(y.Group,{ref:a,className:h("overflow-hidden text-foreground [&_[cmdk-group-heading]]:text-muted-foreground",e),...o}));K.displayName=y.Group.displayName;const U=n.forwardRef(({className:e,...o},a)=>t.jsx(y.Separator,{ref:a,className:h("-mx-1 h-px bg-border",e),...o}));U.displayName=y.Separator.displayName;const L=n.forwardRef(({className:e,...o},a)=>t.jsx(y.Item,{ref:a,className:h("relative flex cursor-pointer select-none items-center rounded-md px-2 py-2 text-sm text-foreground outline-none","data-[selected=true]:bg-secondary data-[selected=true]:text-foreground","data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50",e),...o}));L.displayName=y.Item.displayName;function _o({open:e,onOpenChange:o}){const{t:a,i18n:s}=C(),i=et(),{resolved:l,setMode:d}=he(),c=n.useMemo(()=>S.map(r=>({label:a(r.labelKey),items:T.filter(m=>r.paths.includes(m.path)).map(m=>({path:m.path,label:a(m.nav.labelKey),description:a(m.nav.descriptionKey),Icon:m.nav.icon}))})),[a]),p=r=>{o(!1),r()},u=()=>d(l==="dark"?"light":"dark"),g=()=>{const r=s.language.startsWith("zh")?"en":"zh";s.changeLanguage(r)};return t.jsxs(Uo,{open:e,onOpenChange:o,title:a("app.commandPalette.title"),children:[t.jsx(Ce,{placeholder:a("app.commandPalette.placeholder")}),t.jsxs(ke,{children:[t.jsx(Ae,{children:a("app.commandPalette.empty")}),c.map((r,m)=>r.items.length===0?null:t.jsxs(n.Fragment,{children:[m>0&&t.jsx(U,{}),t.jsx(K,{heading:r.label,children:r.items.map(({path:f,label:P,description:v,Icon:x})=>t.jsxs(L,{value:`${P} ${f} ${v}`,onSelect:()=>p(()=>i(f)),children:[t.jsx(x,{className:"mr-2 h-4 w-4 shrink-0 text-muted-foreground","aria-hidden":"true"}),t.jsx("span",{className:"flex-1",children:P})]},f))})]},r.label)),t.jsx(U,{}),t.jsxs(K,{heading:a("app.commandPalette.actions"),children:[t.jsxs(L,{value:a("app.commandPalette.toggleTheme"),onSelect:()=>p(u),children:[l==="dark"?t.jsx(_,{className:"mr-2 h-4 w-4 shrink-0 text-muted-foreground","aria-hidden":"true"}):t.jsx(z,{className:"mr-2 h-4 w-4 shrink-0 text-muted-foreground","aria-hidden":"true"}),t.jsx("span",{className:"flex-1",children:a("app.commandPalette.toggleTheme")})]}),t.jsxs(L,{value:a("app.commandPalette.switchLanguage"),onSelect:()=>p(g),children:[t.jsx(te,{className:"mr-2 h-4 w-4 shrink-0 text-muted-foreground","aria-hidden":"true"}),t.jsx("span",{className:"flex-1",children:a("app.commandPalette.switchLanguage")})]})]})]})]})}function zo(e,o,a){return Je({queryKey:e,placeholderData:Ve,...a,queryFn:async()=>{try{return await fe(o)}catch(s){throw E(s)}}})}const Bo={auth:{session:()=>["auth","session"],web:()=>["auth","web"]},config:{full:()=>["config","full"],info:()=>["config","info"],exportTimeout:()=>["config","export-timeout"]},customEndpoints:{all:()=>["custom-endpoints"]},providers:{all:()=>["providers","all"]},stats:{overview:(e="all")=>["stats","overview",e],daily:(e,o="all")=>["stats","daily",e,o],model:(e,o,a="all")=>["stats","model",e,o,a]},status:{byEndpoint:(e="all")=>["status",e],gateway:()=>["status","gateway"]},db:{info:()=>["db","info"]},dashboard:{summary:(e="all")=>["dashboard","summary",e]},logs:{list:e=>["logs",e],recent:(e="all")=>["logs","recent",e],detail:e=>["logs","detail",e]},events:{list:e=>["events",e],stats:()=>["events","stats"]},apiKeys:{all:()=>["api-keys"],overview:e=>["api-keys","overview",e],usage:e=>["api-keys","usage",e]}},X=15e3;function Wo(){var o;const{data:e}=zo(Bo.dashboard.summary(),{url:"/api/dashboard/summary",method:"GET"},{refetchInterval:X,refetchOnWindowFocus:!0,staleTime:X,retry:!1});return((o=e==null?void 0:e.status)==null?void 0:o.requestsPerMinute)??0}function G(e,o){return(o.nav.matchPaths??[o.path]).some(s=>s==="/"?e==="/":e.startsWith(s))}const Vo=["/","/logs","/providers","/api-keys"];function Go(e){for(const o of S)if(T.some(s=>o.paths.includes(s.path)&&G(e,s)))return o.labelKey;return S[0].labelKey}function Qo({compact:e}){return t.jsx(Re,{className:h(e?"h-8 w-8":"h-9 w-9"),title:"cc-gw"})}function Z({label:e,items:o,pathname:a,compact:s,onNavigate:i}){const{t:l}=C();return o.length===0?null:t.jsxs("div",{className:"space-y-0.5",children:[!s&&t.jsx("p",{className:"mb-1 px-3 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/50",children:e}),o.map(d=>{const c=d.nav.icon,p=G(a,d),u=l(d.nav.labelKey);return s?t.jsxs(Oo,{children:[t.jsx(Ko,{asChild:!0,children:t.jsxs(O,{to:d.path,onClick:i,end:d.path==="/",className:h("relative mx-1 flex items-center justify-center rounded-lg p-2 transition-colors",p?"bg-secondary text-foreground":"text-muted-foreground hover:bg-secondary hover:text-foreground"),"aria-label":u,children:[t.jsx("span",{"aria-hidden":!0,className:h("absolute left-0 top-1/2 h-5 w-0.5 -translate-y-1/2 origin-left rounded-full bg-primary motion-surface",p?"scale-x-100":"scale-x-0")}),t.jsx(c,{className:"h-5 w-5","aria-hidden":"true"})]})}),t.jsx(xe,{side:"right",children:u})]},d.path):t.jsxs(O,{to:d.path,onClick:i,end:d.path==="/",className:h("relative mx-2 flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors",p?"bg-secondary text-foreground":"text-muted-foreground hover:bg-secondary hover:text-foreground"),children:[t.jsx("span",{"aria-hidden":!0,className:h("absolute left-0 top-1/2 h-5 w-0.5 -translate-y-1/2 origin-left rounded-full bg-primary motion-surface",p?"scale-x-100":"scale-x-0")}),t.jsx(c,{className:"h-4 w-4 shrink-0","aria-hidden":"true"}),t.jsx("span",{className:"flex-1 truncate",children:u})]},d.path)})]})}function F({compact:e,onNavigate:o}){const{t:a}=C(),s=N(),{authEnabled:i,username:l}=B();return t.jsxs("div",{className:h("flex h-full flex-col",e?"items-center":""),children:[t.jsxs("div",{className:h("flex shrink-0 items-center border-b border-[color:var(--surface-border)]",e?"h-12 w-full justify-center":"h-12 gap-3 px-4"),children:[t.jsx(Qo,{compact:e}),!e&&t.jsx("div",{className:"min-w-0 flex-1",children:t.jsxs("div",{className:"flex min-w-0 items-center gap-2",children:[t.jsx("p",{className:"shrink-0 whitespace-nowrap text-sm font-semibold text-foreground",title:a("app.title"),children:"cc-gw"}),t.jsx("span",{className:"ml-auto inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded-md bg-success-bg px-1.5 py-0.5 text-[10px] font-medium text-success before:h-1.5 before:w-1.5 before:rounded-full before:bg-success before:animate-live-pulse",children:a("app.online")})]})})]}),t.jsx("div",{className:h("flex-1 overflow-y-auto",e?"w-full p-2":"space-y-4 p-3"),children:e?t.jsx(Fo,{delayDuration:0,children:t.jsx("div",{className:"space-y-1",children:S.map((d,c)=>t.jsxs("div",{children:[c>0&&t.jsx("div",{className:"my-2 border-t border-border"}),t.jsx(Z,{label:"",items:T.filter(p=>d.paths.includes(p.path)),pathname:s.pathname,compact:!0,onNavigate:o})]},d.labelKey))})}):t.jsx(t.Fragment,{children:S.map(d=>t.jsx(Z,{label:a(d.labelKey),items:T.filter(c=>d.paths.includes(c.path)),pathname:s.pathname,onNavigate:o},d.labelKey))})}),i&&l&&!e&&t.jsx("div",{className:"shrink-0 border-t border-border p-3",children:t.jsxs("div",{className:"flex items-center gap-2 rounded-lg px-2 py-2",children:[t.jsx("div",{className:"flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-secondary text-xs font-medium text-muted-foreground",children:l.slice(0,2).toUpperCase()}),t.jsx("span",{className:"flex-1 truncate text-xs text-muted-foreground",children:l})]})})]})}function $o({onMore:e}){const{t:o}=C(),a=N(),s=T.filter(i=>Vo.includes(i.path));return t.jsxs("nav",{className:"fixed inset-x-0 bottom-0 z-30 flex h-14 items-stretch border-t border-border bg-card pb-[env(safe-area-inset-bottom)] md:hidden","aria-label":o("nav.more"),children:[s.map(i=>{const l=i.nav.icon,d=G(a.pathname,i);return t.jsxs(O,{to:i.path,end:i.path==="/",className:h("flex flex-1 flex-col items-center justify-center gap-0.5 text-[10px] font-medium transition-colors",d?"text-primary":"text-muted-foreground"),"aria-current":d?"page":void 0,children:[t.jsx(l,{className:"h-5 w-5","aria-hidden":"true"}),t.jsx("span",{className:"truncate",children:o(i.nav.labelKey)})]},i.path)}),t.jsxs("button",{type:"button",onClick:e,className:"flex flex-1 flex-col items-center justify-center gap-0.5 text-[10px] font-medium text-muted-foreground transition-colors",children:[t.jsx(Ge,{className:"h-5 w-5","aria-hidden":"true"}),t.jsx("span",{children:o("nav.more")})]})]})}function Yo(){const{t:e}=C(),o=N(),[a,s]=n.useState(!1),[i,l]=n.useState(!1),{authEnabled:d,username:c,logout:p}=B(),[u,g]=n.useState(!1),r=Wo();n.useEffect(()=>{s(!1)},[o.pathname]),n.useEffect(()=>{const x=R=>{(R.metaKey||R.ctrlKey)&&R.key.toLowerCase()==="k"&&(R.preventDefault(),l(Se=>!Se))};return window.addEventListener("keydown",x),()=>window.removeEventListener("keydown",x)},[]);const m=n.useMemo(()=>Io(o.pathname),[o.pathname]),f=e(m.nav.titleKey??m.nav.labelKey),P=e(Go(o.pathname)),v=async()=>{if(!u){g(!0);try{await p()}finally{g(!1)}}};return t.jsxs("div",{className:"flex h-screen overflow-hidden bg-background",children:[t.jsx("a",{href:"#main-content",className:"sr-only focus:not-sr-only focus:absolute focus:z-50 focus:m-4 focus:rounded-md focus:bg-primary focus:px-4 focus:py-2 focus:text-primary-foreground",children:e("app.skipToContent")}),t.jsx("aside",{className:"hidden w-14 shrink-0 flex-col border-r border-border bg-card md:flex lg:hidden",children:t.jsx(F,{compact:!0})}),t.jsx("aside",{className:"hidden w-[236px] shrink-0 flex-col border-r border-border bg-card lg:flex",children:t.jsx(F,{})}),t.jsxs("div",{className:"flex min-w-0 flex-1 flex-col",children:[t.jsxs("header",{className:"sticky top-0 z-30 flex h-14 shrink-0 items-center gap-3 border-b border-border bg-card px-4 lg:px-6",children:[t.jsxs("div",{className:"flex min-w-0 flex-1 animate-fade-in items-baseline gap-2",children:[t.jsx("span",{className:"hidden shrink-0 text-xs text-muted-foreground/60 sm:inline",children:P}),t.jsx("span",{"aria-hidden":!0,className:"hidden shrink-0 text-xs text-muted-foreground/40 sm:inline",children:"/"}),t.jsx("h1",{className:"truncate text-sm font-semibold text-foreground",children:f})]},o.pathname),t.jsxs("button",{type:"button",onClick:()=>l(!0),"data-testid":"command-palette-trigger","aria-label":e("app.commandPalette.trigger"),className:"hidden h-8 shrink-0 items-center gap-2 rounded-md border border-border bg-secondary px-2.5 text-xs text-muted-foreground transition-colors hover:border-primary hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring md:inline-flex",children:[t.jsx(oe,{className:"h-3.5 w-3.5","aria-hidden":"true"}),t.jsx("span",{children:e("app.commandPalette.trigger")}),t.jsx("kbd",{className:"rounded-[3px] border border-border bg-card px-1.5 py-px text-[10px] leading-none text-muted-foreground",children:"⌘K"})]}),t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx("span",{"data-testid":"header-rpm-badge",className:"hidden items-center gap-1.5 rounded-md bg-success-bg px-2 py-1.5 text-[11px] font-semibold text-success before:h-1.5 before:w-1.5 before:rounded-full before:bg-success before:animate-live-pulse sm:inline-flex",children:e("app.liveRpm",{count:r})}),d&&c&&t.jsx("span",{className:"hidden text-xs text-muted-foreground sm:inline",children:e("login.status",{username:c})}),d&&t.jsx(H,{variant:"ghost",size:"sm",onClick:()=>void v(),disabled:u,children:e(u?"common.actions.loading":"common.actions.logout")}),t.jsx(Mo,{}),t.jsx(jo,{})]})]}),t.jsx("main",{id:"main-content",role:"main",tabIndex:-1,className:"flex min-h-0 flex-1 flex-col",children:t.jsx("div",{className:"flex min-h-0 flex-1 flex-col overflow-y-auto px-4 pb-20 pt-4 md:pb-4 lg:px-6",children:t.jsx("div",{className:"mx-auto flex w-full max-w-[1280px] flex-1 flex-col",children:t.jsx(tt,{})})})})]}),t.jsx($o,{onMore:()=>s(!0)}),a&&t.jsxs("div",{className:"fixed inset-0 z-40 md:hidden",role:"dialog","aria-modal":"true",children:[t.jsx("div",{className:"fixed inset-0 bg-background/95",onClick:()=>s(!1)}),t.jsx("div",{id:"mobile-nav",className:"fixed inset-y-0 left-0 w-[236px] bg-card shadow-[var(--surface-shadow-lg)] animate-in slide-in-from-left duration-200",children:t.jsx(F,{onNavigate:()=>s(!1)})})]}),t.jsx(_o,{open:i,onOpenChange:l})]})}function Jo({children:e}){const{authEnabled:o,isAuthenticated:a,loading:s}=B(),i=N();return s?t.jsx(ve,{}):!o||a?e:t.jsx(D,{to:"/login",replace:!0,state:{from:i}})}function Xo(){return t.jsx(Jo,{children:t.jsx(Yo,{})})}function Zo(){return t.jsx(ot,{basename:So(),children:t.jsx(n.Suspense,{fallback:t.jsx(ve,{}),children:t.jsxs(at,{children:[t.jsx(I,{path:"/",element:t.jsx(Xo,{}),children:be.map(e=>{const o=e.element;return e.index?t.jsx(I,{index:!0,element:t.jsx(o,{})},"index"):t.jsx(I,{path:e.path.slice(1),element:t.jsx(o,{})},e.path)})}),Ao.map(e=>{const o=e.element;return t.jsx(I,{path:e.path,element:t.jsx(o,{})},e.path)}),t.jsx(I,{path:"*",element:t.jsx(D,{to:"/",replace:!0})})]})})})}function ea(){return t.jsx(uo,{children:t.jsx(Zo,{})})}const Ie=document.getElementById("root");if(!Ie)throw new Error("Root element #root not found");Qe.createRoot(Ie).render(t.jsx($e.StrictMode,{children:t.jsx(ea,{})}));export{H as B,ve as L,Fo as T,ia as a,so as b,zo as c,b as d,la as e,Oo as f,Ko as g,xe as h,A as i,B as j,Bo as q,fe as r,q as s,E as t,he as u};
@@ -1 +1 @@
1
- import{i as o,d as i,r as e}from"./app-CDdTBXQ0.js";const r={configRequest(){return{url:"/api/config",method:"GET"}},configInfoRequest(){return{url:"/api/config/info",method:"GET"}},providersRequest(){return{url:"/api/providers",method:"GET"}},configInfo:async()=>e(r.configInfoRequest()),providers:async()=>e(r.providersRequest()),saveConfig:async n=>{await o(i.put("/api/config",n))}};export{r as g};
1
+ import{i as o,d as i,r as e}from"./app-WMvqYU5W.js";const r={configRequest(){return{url:"/api/config",method:"GET"}},configInfoRequest(){return{url:"/api/config/info",method:"GET"}},providersRequest(){return{url:"/api/providers",method:"GET"}},configInfo:async()=>e(r.configInfoRequest()),providers:async()=>e(r.providersRequest()),saveConfig:async n=>{await o(i.put("/api/config",n))}};export{r as g};
@@ -1,4 +1,4 @@
1
- import{B as v,c as g}from"./global-D5dyWHqO.js";import{r as u,j as e,a4 as O,a9 as L,aa as N,ab as p,ac as b,ad as j,P as K,ae as T,af as P,ag as R,ah as E,ai as D,aj as q,ak as k,a8 as U,q as B}from"./vendor-CITafuNQ.js";import{c as A}from"./clipboard-BnAnBk_-.js";import{p as G}from"./package-B8-r92OZ.js";const S="/assets/live-dashboard-B7vLOY_S.png",_="/assets/live-logs-PRpgTV6S.png",F="/assets/live-model-management-xkBDeuLk.png",W="/assets/live-api-keys-CJUuaCev.png",C="/assets/live-en-dashboard-CJoX7IQT.png",Y="/assets/live-en-logs-BGGL53ab.png",H="/assets/live-en-model-management-BpjNblfR.png",$="/assets/live-en-api-keys-Bx8N-eoh.png",w=G.version,Q=(o,a)=>a[o],t={nav:{why:{zh:"现状",en:"The mess"},how:{zh:"怎么用",en:"How"},debug:{zh:"排查",en:"Debug"},local:{zh:"本地优先",en:"Local-first"},start:{zh:"开始",en:"Start"},faq:{zh:"FAQ",en:"FAQ"}},ctaConsole:{zh:"打开控制台",en:"Open console"},ctaInstall:{zh:"立即安装",en:"Install now"},ctaPreview:{zh:"看看控制台长什么样",en:"Preview the console"},hero:{badge:{zh:"v"+w+" · Rust 内核 · 完全开源",en:"v"+w+" · Rust core · Open source"},h1Top:{zh:"你的 API Key",en:"Your API key"},h1Bottom:{zh:"不该躺在 30 个 .env 里",en:"shouldn’t live in 30 .env files"},lead:{zh:"cc-gw 在你的机器上跑一个本地网关。Claude Code、OpenAI SDK、Anthropic SDK 全部指向同一个地址。换模型、换 provider、查日志,再也不用翻业务代码。",en:"cc-gw runs a small gateway right on your machine. Point Claude Code, the OpenAI SDK, and the Anthropic SDK at one address — then swap models, switch providers, and trace requests without touching your app code."},note:{zh:"不需要本机有 Rust 环境,npm 自动拉对应平台的预编译二进制。",en:"No Rust toolchain needed — npm pulls the right prebuilt binary for your platform."},proof:[{zh:"一条命令装好,本机就能跑",en:"One command to install, runs on your laptop"},{zh:"客户端只改 baseURL,不动业务逻辑",en:"Clients only change baseURL — your code stays put"},{zh:"所有数据都在 ~/.cc-gw 下,不走云",en:"Everything lives under ~/.cc-gw — nothing leaves your box"}],works:{zh:"已经在以下客户端里跑通",en:"Tested with"}},why:{eyebrow:{zh:"现状",en:"The mess"},title:{zh:"AI 接进来不难,难的是接进来之后",en:"Wiring up AI is easy. Living with it isn’t."},description:{zh:"你大概率已经踩过下面这三件事里的至少两件。这就是为什么有了 cc-gw。",en:"You’ve probably hit at least two of these. That’s why cc-gw exists."},cards:[{title:{zh:"Key 散在各个 .env 里",en:"Keys scattered across .env files"},body:{zh:"每个项目自己一份 baseURL、自己一份 key、自己一份模型名。时间一长,没人敢动,也没人记得清。",en:"Every project keeps its own baseURL, its own key, its own model name. Six months in, nobody dares touch it."},sample:`OPENAI_BASE_URL=https://...
1
+ import{B as v,c as g}from"./global-D5dyWHqO.js";import{r as u,j as e,a4 as O,a9 as L,aa as N,ab as p,ac as b,ad as j,P as K,ae as T,af as P,ag as R,ah as E,ai as D,aj as q,ak as k,a8 as U,q as B}from"./vendor-CITafuNQ.js";import{c as A}from"./clipboard-BnAnBk_-.js";import{p as G}from"./package-B6sWB4v8.js";const S="/assets/live-dashboard-B7vLOY_S.png",_="/assets/live-logs-PRpgTV6S.png",F="/assets/live-model-management-xkBDeuLk.png",W="/assets/live-api-keys-CJUuaCev.png",C="/assets/live-en-dashboard-CJoX7IQT.png",Y="/assets/live-en-logs-BGGL53ab.png",H="/assets/live-en-model-management-BpjNblfR.png",$="/assets/live-en-api-keys-Bx8N-eoh.png",w=G.version,Q=(o,a)=>a[o],t={nav:{why:{zh:"现状",en:"The mess"},how:{zh:"怎么用",en:"How"},debug:{zh:"排查",en:"Debug"},local:{zh:"本地优先",en:"Local-first"},start:{zh:"开始",en:"Start"},faq:{zh:"FAQ",en:"FAQ"}},ctaConsole:{zh:"打开控制台",en:"Open console"},ctaInstall:{zh:"立即安装",en:"Install now"},ctaPreview:{zh:"看看控制台长什么样",en:"Preview the console"},hero:{badge:{zh:"v"+w+" · Rust 内核 · 完全开源",en:"v"+w+" · Rust core · Open source"},h1Top:{zh:"你的 API Key",en:"Your API key"},h1Bottom:{zh:"不该躺在 30 个 .env 里",en:"shouldn’t live in 30 .env files"},lead:{zh:"cc-gw 在你的机器上跑一个本地网关。Claude Code、OpenAI SDK、Anthropic SDK 全部指向同一个地址。换模型、换 provider、查日志,再也不用翻业务代码。",en:"cc-gw runs a small gateway right on your machine. Point Claude Code, the OpenAI SDK, and the Anthropic SDK at one address — then swap models, switch providers, and trace requests without touching your app code."},note:{zh:"不需要本机有 Rust 环境,npm 自动拉对应平台的预编译二进制。",en:"No Rust toolchain needed — npm pulls the right prebuilt binary for your platform."},proof:[{zh:"一条命令装好,本机就能跑",en:"One command to install, runs on your laptop"},{zh:"客户端只改 baseURL,不动业务逻辑",en:"Clients only change baseURL — your code stays put"},{zh:"所有数据都在 ~/.cc-gw 下,不走云",en:"Everything lives under ~/.cc-gw — nothing leaves your box"}],works:{zh:"已经在以下客户端里跑通",en:"Tested with"}},why:{eyebrow:{zh:"现状",en:"The mess"},title:{zh:"AI 接进来不难,难的是接进来之后",en:"Wiring up AI is easy. Living with it isn’t."},description:{zh:"你大概率已经踩过下面这三件事里的至少两件。这就是为什么有了 cc-gw。",en:"You’ve probably hit at least two of these. That’s why cc-gw exists."},cards:[{title:{zh:"Key 散在各个 .env 里",en:"Keys scattered across .env files"},body:{zh:"每个项目自己一份 baseURL、自己一份 key、自己一份模型名。时间一长,没人敢动,也没人记得清。",en:"Every project keeps its own baseURL, its own key, its own model name. Six months in, nobody dares touch it."},sample:`OPENAI_BASE_URL=https://...
2
2
  ANTHROPIC_API_KEY=sk-ant-...
3
3
  MODEL=gpt-4o-2024-08-06`},{title:{zh:"出错只能猜",en:"Debugging is just guessing"},body:{zh:"是 payload 错?协议不对?上游挂了?key 被收回了?没有统一日志,只能 curl 一遍试一遍。",en:"Bad payload? Wrong protocol? Upstream down? Key revoked? Without one log to look at, you curl your way through every theory."},sample:`$ curl ... 401
4
4
  # 是 key 过期?还是 endpoint 写错了?
@@ -0,0 +1 @@
1
+ const o="0.10.4",s={version:o};export{s as p};
@@ -1 +1 @@
1
- import{r as p,j as e}from"./vendor-CITafuNQ.js";import{c as u,q as m}from"./app-CDdTBXQ0.js";import{a as b}from"./apiKeys-CQ7yz9uu.js";import{u as x}from"./i18n-PeX9kBVO.js";const j=[{value:1,labelKey:"apiKeys.analytics.range.today"},{value:7,labelKey:"apiKeys.analytics.range.week"},{value:30,labelKey:"apiKeys.analytics.range.month"}];function v(){var s;const l=[{id:"anthropic",label:"Anthropic"},{id:"openai",label:"OpenAI"}],t=u(m.customEndpoints.all(),b.customEndpointOptionsRequest());return p.useMemo(()=>{var n;const a=(((n=t.data)==null?void 0:n.endpoints)??[]).map(o=>({id:o.id,label:o.label||o.id}));return[...l,...a]},[(s=t.data)==null?void 0:s.endpoints])}function N({available:l,hint:t,onChange:s,selected:a}){const{t:n}=x(),o=a.length===0,i=()=>{s([])},c=r=>{if(a.includes(r)){s(a.filter(d=>d!==r));return}s([...a,r])};return e.jsxs("div",{className:"space-y-2",children:[e.jsx("div",{className:"rounded-lg border border-border bg-secondary px-3 py-3 text-xs text-muted-foreground",children:t}),e.jsxs("label",{className:"flex cursor-pointer items-center gap-2 rounded-lg border border-border bg-card px-3 py-2.5 transition-colors hover:border-primary/20 hover:bg-accent/50",children:[e.jsx("input",{type:"checkbox",checked:o,onChange:i,className:"h-4 w-4 rounded border-input accent-primary"}),e.jsx("span",{className:"text-sm font-medium",children:n("apiKeys.allEndpoints")})]}),e.jsx("div",{className:"grid max-h-40 gap-1 overflow-y-auto",children:l.map(r=>e.jsxs("label",{className:"flex cursor-pointer items-center gap-2 rounded-lg border border-border bg-card px-3 py-2.5 transition-colors hover:border-primary/20 hover:bg-accent/50",children:[e.jsx("input",{type:"checkbox",checked:!o&&a.includes(r.id),onChange:()=>c(r.id),className:"h-4 w-4 rounded border-input accent-primary"}),e.jsx("span",{className:"text-sm",children:r.label}),e.jsxs("span",{className:"text-xs text-muted-foreground",children:["(",r.id,")"]})]},r.id))})]})}export{N as E,j as R,v as u};
1
+ import{r as p,j as e}from"./vendor-CITafuNQ.js";import{c as u,q as m}from"./app-WMvqYU5W.js";import{a as b}from"./apiKeys-C7NN8p2H.js";import{u as x}from"./i18n-PeX9kBVO.js";const j=[{value:1,labelKey:"apiKeys.analytics.range.today"},{value:7,labelKey:"apiKeys.analytics.range.week"},{value:30,labelKey:"apiKeys.analytics.range.month"}];function v(){var s;const l=[{id:"anthropic",label:"Anthropic"},{id:"openai",label:"OpenAI"}],t=u(m.customEndpoints.all(),b.customEndpointOptionsRequest());return p.useMemo(()=>{var n;const a=(((n=t.data)==null?void 0:n.endpoints)??[]).map(o=>({id:o.id,label:o.label||o.id}));return[...l,...a]},[(s=t.data)==null?void 0:s.endpoints])}function N({available:l,hint:t,onChange:s,selected:a}){const{t:n}=x(),o=a.length===0,i=()=>{s([])},c=r=>{if(a.includes(r)){s(a.filter(d=>d!==r));return}s([...a,r])};return e.jsxs("div",{className:"space-y-2",children:[e.jsx("div",{className:"rounded-lg border border-border bg-secondary px-3 py-3 text-xs text-muted-foreground",children:t}),e.jsxs("label",{className:"flex cursor-pointer items-center gap-2 rounded-lg border border-border bg-card px-3 py-2.5 transition-colors hover:border-primary/20 hover:bg-accent/50",children:[e.jsx("input",{type:"checkbox",checked:o,onChange:i,className:"h-4 w-4 rounded border-input accent-primary"}),e.jsx("span",{className:"text-sm font-medium",children:n("apiKeys.allEndpoints")})]}),e.jsx("div",{className:"grid max-h-40 gap-1 overflow-y-auto",children:l.map(r=>e.jsxs("label",{className:"flex cursor-pointer items-center gap-2 rounded-lg border border-border bg-card px-3 py-2.5 transition-colors hover:border-primary/20 hover:bg-accent/50",children:[e.jsx("input",{type:"checkbox",checked:!o&&a.includes(r.id),onChange:()=>c(r.id),className:"h-4 w-4 rounded border-input accent-primary"}),e.jsx("span",{className:"text-sm",children:r.label}),e.jsxs("span",{className:"text-xs text-muted-foreground",children:["(",r.id,")"]})]},r.id))})]})}export{N as E,j as R,v as u};
@@ -1 +1 @@
1
- import{a as o,b as q}from"./query-DZF7K6hv.js";import{a as x,t as y}from"./app-CDdTBXQ0.js";function j({mutationFn:C,successToast:u,errorToast:p,invalidateKeys:n,onSuccess:m,onError:f,...M}){const Q=o(),{pushToast:A}=x();return q({...M,mutationFn:async t=>{try{return await C(t)}catch(a){throw y(a)}},onSuccess:async(t,a,w,h)=>{n!=null&&n.length&&await Promise.all(n.map(g=>Q.invalidateQueries({queryKey:g})));const i=u==null?void 0:u(t,a);i&&A({...i,variant:"success"}),await(m==null?void 0:m(t,a,w,h))},onError:async(t,a,w,h)=>{const i=p==null?void 0:p(t,a);i&&A({...i,variant:"error"}),await(f==null?void 0:f(t,a,w,h))}})}export{j as u};
1
+ import{a as o,b as q}from"./query-DZF7K6hv.js";import{a as x,t as y}from"./app-WMvqYU5W.js";function j({mutationFn:C,successToast:u,errorToast:p,invalidateKeys:n,onSuccess:m,onError:f,...M}){const Q=o(),{pushToast:A}=x();return q({...M,mutationFn:async t=>{try{return await C(t)}catch(a){throw y(a)}},onSuccess:async(t,a,w,h)=>{n!=null&&n.length&&await Promise.all(n.map(g=>Q.invalidateQueries({queryKey:g})));const i=u==null?void 0:u(t,a);i&&A({...i,variant:"success"}),await(m==null?void 0:m(t,a,w,h))},onError:async(t,a,w,h)=>{const i=p==null?void 0:p(t,a);i&&A({...i,variant:"error"}),await(f==null?void 0:f(t,a,w,h))}})}export{j as u};