@goodandready/dsh-clinebot 0.6.29 → 0.6.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to `@goodandready/dsh-clinebot` will be documented in this f
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.30] - 2026-11-06
9
+
10
+ ### Fixed
11
+ - **Stream transport idle timer (#296)**: removed premature `timer.unref()` in `readChunkWithIdle()` that caused Node 22 event loops to exit early with pending promises during stream idle waits. Timer cleanup is reliably handled in `finally` via `clearTimeout`.
12
+
13
+ ### Changed
14
+ - **Default on-screen indicators disabled (#297)**: `showSidebarQuota` and `showComposerCost` are now disabled (`false`) by default upon clean installs, ensuring an uncluttered UI out-of-the-box. Users can enable either indicator explicitly in Settings -> ClineBot -> Integrations.
15
+
8
16
  ## [0.6.29] - 2026-11-06
9
17
 
10
18
  ### Fixed
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h3>Native ClineBot / ClinePass Provider Companion for DeepSeek Harness</h3>
6
6
 
7
- <p><b>0.6.29</b> fixes DSH Desktop key verification when the host strips <code>Origin</code>/<code>Referer</code> but admits the request through <code>connection.requestRejection()</code>. It keeps the complete security boundary from 0.6.28, DSH <code>0.2.2-rc.1</code> support, automatic model/free/dated catalog backfill, model-scoped credit cooldowns and 402/413 errors. Chat runs on the native <code>ctx.llm</code> adapter; the legacy <code>llm-pi-ai</code>, loopback proxy and <code>llm/stream</code> path are removed.</p>
7
+ <p><b>0.6.30</b> fixes a stream idle timer race in Node 22 (removing <code>timer.unref()</code> to prevent premature event loop termination) and defaults on-screen UI badges (sidebar quota meter and composer session cost) to disabled so users can enable them explicitly in Settings -> ClineBot -> Integrations. It keeps the complete security boundary, DSH <code>0.2.2-rc.1</code> support, automatic model/free/dated catalog backfill, model-scoped credit cooldowns and 402/413 errors. Chat runs on the native <code>ctx.llm</code> adapter; the legacy <code>llm-pi-ai</code>, loopback proxy and <code>llm/stream</code> path are removed.</p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/@goodandready/dsh-clinebot"><img src="https://img.shields.io/npm/v/@goodandready/dsh-clinebot.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
package/README.ru.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h3>Нативное подключение провайдера ClineBot / ClinePass для DeepSeek Harness</h3>
6
6
 
7
- <p><b>0.6.29</b> исправляет проверку ключа в DSH Desktop, когда host убирает <code>Origin</code>/<code>Referer</code>, но принимает запрос через <code>connection.requestRejection()</code>. Сохраняются полная security-граница из 0.6.28, поддержка DSH <code>0.2.2-rc.1</code>, автодобор каталога моделей/FREE/dated, cooldown недостатка кредитов для конкретной модели и ошибки 402/413. Чат работает через нативный адаптер <code>ctx.llm</code>; старые <code>llm-pi-ai</code>, loopback-прокси и <code>llm/stream</code> удалены.</p>
7
+ <p><b>0.6.30</b> исправляет гонку idle-таймера стрима в Node 22 (удален вызов <code>timer.unref()</code> для предотвращения преждевременного завершения цикла событий) и переводит экранные значки (квоту в сайдбаре и индикатор стоимости в композере) в состояние выключено по умолчанию для явного включения пользователем в «Настройки → ClineBot → Интеграции». Сохраняются полная security-граница, поддержка DSH <code>0.2.2-rc.1</code>, автодобор каталога моделей/FREE/dated, cooldown недостатка кредитов для конкретной модели и ошибки 402/413. Чат работает через нативный адаптер <code>ctx.llm</code>; старые <code>llm-pi-ai</code>, loopback-прокси и <code>llm/stream</code> удалены.</p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/@goodandready/dsh-clinebot"><img src="https://img.shields.io/npm/v/@goodandready/dsh-clinebot.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
package/README.zh.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h3>适用于 DeepSeek Harness 的 ClineBot / ClinePass 原生模型提供商伴侣插件</h3>
6
6
 
7
- <p><b>0.6.29</b> 修复 DSH Desktop 在移除 <code>Origin</code>/<code>Referer</code> 后通过 <code>connection.requestRejection()</code> 认证时密钥验证失败的问题。保留 0.6.28 的完整安全边界、DSH <code>0.2.2-rc.1</code> 支持、模型/免费/日期变体自动补齐、按模型隔离的余额不足冷却和明确的 402/413 错误。聊天使用原生 <code>ctx.llm</code> 适配器;旧的 <code>llm-pi-ai</code>、回环代理和 <code>llm/stream</code> 已删除。</p>
7
+ <p><b>0.6.30</b> 修复 Node 22 中的流式传输空闲定时器竞争问题(移除 <code>timer.unref()</code> 以防止事件循环提前终止),并将屏幕徽标/指示器(侧边栏配额表和编辑器会话费用)默认设为禁用,供用户在“设置 -> ClineBot -> 集成”中显式启用。保留完整的安全边界、DSH <code>0.2.2-rc.1</code> 支持、模型/免费/日期变体自动补齐、按模型隔离的余额不足冷却和明确的 402/413 错误。聊天使用原生 <code>ctx.llm</code> 适配器;旧的 <code>llm-pi-ai</code>、回环代理和 <code>llm/stream</code> 已删除。</p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/@goodandready/dsh-clinebot"><img src="https://img.shields.io/npm/v/@goodandready/dsh-clinebot.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
package/lib/client.js CHANGED
@@ -403,7 +403,7 @@ function formatResetCountdown(isoString){if(!isoString)return"";try{const d=new
403
403
  function SettingsGroup({title,description,divided=false,action,children}){return React.createElement("section",{className:divided?"cb-settings-group cb-settings-group-divided":"cb-settings-group","aria-label":title},React.createElement("div",{className:"cb-settings-group-head"},React.createElement("h3",{className:"cb-settings-group-title"},title),action?React.createElement("span",{className:"cb-settings-group-action"},action):null),description?React.createElement("div",{className:"cb-settings-group-desc"},description):null,React.createElement("div",{className:"cb-settings-group-body"},children))}function CollapsibleGroup({title,description,defaultOpen=false,divided=false,badge,children}){const[open,setOpen]=React.useState(Boolean(defaultOpen));return React.createElement("section",{className:divided?"cb-settings-group cb-settings-group-divided":"cb-settings-group","aria-label":title},React.createElement("button",{type:"button",className:"cb-settings-group-head cb-settings-disclosure","aria-expanded":open,onClick:()=>setOpen(value=>!value)},React.createElement("span",{className:"cb-settings-group-title"},title),badge!==void 0&&badge!==""?React.createElement("span",{className:"cb-badge"},badge):null,React.createElement("span",{className:"cb-settings-spacer"}),React.createElement("span",{className:open?"cb-settings-chevron cb-settings-chevron-open":"cb-settings-chevron","aria-hidden":"true"},"\u203A")),open&&description?React.createElement("div",{className:"cb-settings-group-desc"},description):null,open?React.createElement("div",{className:"cb-settings-group-body"},children):null)}function SettingRow({title,description,control,error,htmlFor}){return React.createElement("div",{className:"cb-settings-row"},React.createElement("div",{className:"cb-settings-row-text"},htmlFor?React.createElement("label",{className:"cb-settings-row-title",htmlFor},title):React.createElement("span",{className:"cb-settings-row-title"},title),error?React.createElement("div",{className:"cb-settings-row-error"},error):description?React.createElement("div",{className:"cb-settings-row-desc"},description):null),control?React.createElement("div",{className:"cb-settings-row-control"},control):null)}function SaveBar({pendingCount,saving,saveState,saveError,invalid,validationError,onSave,onDiscard,t}){const visible=pendingCount>0||saving||saveState==="saved"||saveState==="error";if(!visible)return null;const message=saveError||validationError||(saving?t("settings.saving"):saveState==="saved"?t("settings.saved"):t("settings.unsaved",{count:pendingCount}));return React.createElement("div",{className:"cb-savebar",role:"status"},React.createElement("div",{className:"cb-savebar-inner"},React.createElement("span",{className:"cb-savebar-text"},message),React.createElement("span",{className:"cb-settings-spacer"}),React.createElement("button",{type:"button",className:"cb-btn",disabled:saving||pendingCount===0,onClick:onDiscard},t("settings.discard")),React.createElement("button",{type:"button",className:"cb-btn cb-btn-primary",disabled:saving||pendingCount===0||invalid,onClick:onSave},saving?t("settings.saving"):t("settings.save"))))}
404
404
 
405
405
  // --- src/client/components/integrations-section.js ---
406
- function IntegrationToggle({id,label,hint,checked,onChange}){return React.createElement(SettingRow,{title:label,description:hint,htmlFor:id,control:React.createElement(BooleanControl,{checked:!!checked,label,onChange:next=>onChange(next)})})}function IntegrationsSection({draft,onStage,t}){const current=draft||{};return React.createElement(SettingsGroup,{title:t("settings.integrations"),description:t("settings.integrations_desc"),divided:true},React.createElement(IntegrationToggle,{id:"cb-web-search",label:t("settings.web_search"),hint:t("settings.web_search_hint"),checked:current.webSearch!==false,onChange:value=>onStage({webSearch:value})}),React.createElement(IntegrationToggle,{id:"cb-sidebar-quota",label:t("settings.sidebar_quota"),hint:t("settings.sidebar_quota_hint"),checked:current.showSidebarQuota!==false,onChange:value=>onStage({showSidebarQuota:value})}),React.createElement(IntegrationToggle,{id:"cb-composer-cost",label:t("settings.composer_cost"),hint:t("settings.composer_cost_hint"),checked:current.showComposerCost!==false,onChange:value=>onStage({showComposerCost:value})}))}
406
+ function IntegrationToggle({id,label,hint,checked,onChange}){return React.createElement(SettingRow,{title:label,description:hint,htmlFor:id,control:React.createElement(BooleanControl,{checked:!!checked,label,onChange:next=>onChange(next)})})}function IntegrationsSection({draft,onStage,t}){const current=draft||{};return React.createElement(SettingsGroup,{title:t("settings.integrations"),description:t("settings.integrations_desc"),divided:true},React.createElement(IntegrationToggle,{id:"cb-web-search",label:t("settings.web_search"),hint:t("settings.web_search_hint"),checked:current.webSearch!==false,onChange:value=>onStage({webSearch:value})}),React.createElement(IntegrationToggle,{id:"cb-sidebar-quota",label:t("settings.sidebar_quota"),hint:t("settings.sidebar_quota_hint"),checked:Boolean(current.showSidebarQuota),onChange:value=>onStage({showSidebarQuota:value})}),React.createElement(IntegrationToggle,{id:"cb-composer-cost",label:t("settings.composer_cost"),hint:t("settings.composer_cost_hint"),checked:Boolean(current.showComposerCost),onChange:value=>onStage({showComposerCost:value})}))}
407
407
 
408
408
  // --- src/client/components/advanced-section.js ---
409
409
  function AdvancedField({id,label,hint,value,type,onChange}){return React.createElement(SettingRow,{title:label,description:hint,htmlFor:id,control:React.createElement("input",{id,className:"cb-input cb-settings-input",type:type||"text",value:value===void 0||value===null?"":String(value),onChange:event=>onChange(event.target.value)})})}function AdvancedSection({draft,onStage,t}){const current=draft||{};const numeric=(key,minimum)=>raw=>{if(raw==="")return;const value=Number(raw);if(Number.isFinite(value)&&value>minimum)onStage({[key]:value})};return React.createElement(CollapsibleGroup,{title:t("settings.advanced"),description:t("settings.advanced_desc"),divided:true,defaultOpen:false},React.createElement(AdvancedField,{id:"cb-base-url",label:t("settings.base_url"),hint:t("settings.base_url_hint"),value:current.baseUrl,onChange:value=>onStage({baseUrl:value})}),React.createElement(AdvancedField,{id:"cb-timeout",label:t("settings.timeout"),hint:t("settings.timeout_hint"),value:current.timeoutMs,type:"number",onChange:numeric("timeoutMs",0)}),React.createElement(AdvancedField,{id:"cb-smoke-timeout",label:t("settings.smoke_timeout"),hint:t("settings.smoke_timeout_hint"),value:current.smokeTimeoutMs,type:"number",onChange:numeric("smokeTimeoutMs",0)}),React.createElement(AdvancedField,{id:"cb-stream-idle",label:t("settings.stream_idle"),hint:t("settings.stream_idle_hint"),value:current.streamIdleTimeoutMs,type:"number",onChange:numeric("streamIdleTimeoutMs",0)}),React.createElement(AdvancedField,{id:"cb-max-payload",label:t("settings.max_payload"),hint:t("settings.max_payload_hint"),value:current.maxPayloadMb,type:"number",onChange:numeric("maxPayloadMb",0)}),React.createElement(AdvancedField,{id:"cb-retention",label:t("settings.stats_retention"),hint:t("settings.stats_retention_hint"),value:current.statsRetentionDays,type:"number",onChange:numeric("statsRetentionDays",-1)}),React.createElement(AdvancedField,{id:"cb-daily-budget",label:t("settings.daily_budget"),hint:t("settings.daily_budget_hint"),value:current.dailyBudgetUsd,type:"number",onChange:numeric("dailyBudgetUsd",-1)}),React.createElement(AdvancedField,{id:"cb-monthly-budget",label:t("settings.monthly_budget"),hint:t("settings.monthly_budget_hint"),value:current.monthlyBudgetUsd,type:"number",onChange:numeric("monthlyBudgetUsd",-1)}),React.createElement(AdvancedField,{id:"cb-stats-path",label:t("settings.stats_path"),hint:t("settings.stats_path_hint"),value:current.statsPath,onChange:value=>onStage({statsPath:value})}),React.createElement(AdvancedField,{id:"cb-models-cache-path",label:t("settings.models_cache_path"),hint:t("settings.models_cache_path_hint"),value:current.modelsCachePath,onChange:value=>onStage({modelsCachePath:value})}))}
@@ -424,7 +424,7 @@ function AccountRoutes({accounts=[],modelRoutes={},onUpdateModelRoutes,busy,t}){
424
424
  function AccountsSection({status,busy,handlePinAccount,handleAddAccount,handleDeleteAccount,handleRenameAccount,handleClearKey,accountMode,handleUpdateAccountMode,modelRoutes={},onUpdateModelRoutes,t}){const[showAdd,setShowAdd]=React.useState(false);const[newLabel,setNewLabel]=React.useState("");const[newEnv,setNewEnv]=React.useState("");const[newKey,setNewKey]=React.useState("");const[showKey,setShowKey]=React.useState(false);const[discoveredKeys,setDiscoveredKeys]=React.useState([]);const[usageByAccount,setUsageByAccount]=React.useState({});const[menuFor,setMenuFor]=React.useState("");const[renameFor,setRenameFor]=React.useState("");const[renameValue,setRenameValue]=React.useState("");const[confirmAction,setConfirmAction]=React.useState(null);const[historyOpen,setHistoryOpen]=React.useState(false);const[history,setHistory]=React.useState([]);const[historyLoading,setHistoryLoading]=React.useState(false);React.useEffect(()=>{let active=true;fetchWithTimeout(`${ROUTE_PREFIX}/keys/discovered`,{cache:"no-store"}).then(response=>response.json()).then(data=>{if(active&&data?.ok&&Array.isArray(data.discovered))setDiscoveredKeys(data.discovered)}).catch(()=>{});return()=>{active=false}},[]);const accountSignature=(status?.accounts||[]).map(account=>account.apiKeyEnv||account.id).join("|");React.useEffect(()=>{let active=true;fetchWithTimeout(`${ROUTE_PREFIX}/accounts/usage`,{cache:"no-store"}).then(response=>response.json()).then(data=>{if(!active||!data?.ok||!Array.isArray(data.accounts))return;const next={};for(const entry of data.accounts)next[entry.apiKeyEnv]=entry;setUsageByAccount(next)}).catch(()=>{});return()=>{active=false}},[accountSignature,status?.activeAccount,busy]);function toggleHistory(){if(historyOpen){setHistoryOpen(false);return}setHistoryOpen(true);setHistoryLoading(true);fetchWithTimeout(`${ROUTE_PREFIX}/accounts/history?limit=50`,{cache:"no-store"}).then(response=>response.json()).then(data=>{if(data?.ok&&Array.isArray(data.events))setHistory(data.events)}).catch(()=>{}).finally(()=>setHistoryLoading(false))}function nextEnvName(){let max=1;for(const account of status.accounts||[]){const match=String(account.apiKeyEnv||"").match(/^CLINEBOT_API_KEY_(\d+)$/);if(match)max=Math.max(max,Number(match[1])+1)}return`CLINEBOT_API_KEY_${max}`}function openAdd(){setNewEnv(nextEnvName());setShowAdd(true)}async function submitAdd(event){if(event?.preventDefault)event.preventDefault();if(!newKey.trim()||typeof handleAddAccount!=="function")return;await handleAddAccount({label:newLabel.trim()||newEnv.trim(),apiKeyEnv:newEnv.trim(),apiKey:newKey.trim()});setNewLabel("");setNewEnv("");setNewKey("");setShowAdd(false)}async function importDiscovered(item){if(typeof handleAddAccount!=="function")return;await handleAddAccount({label:item.source||nextEnvName(),apiKeyEnv:nextEnvName(),id:item.id});setDiscoveredKeys(previous=>previous.filter(entry=>entry.id!==item.id))}function openRename(account){setMenuFor("");setRenameFor(account.apiKeyEnv);setRenameValue(account.label||"")}async function submitRename(account){if(typeof handleRenameAccount==="function"){await handleRenameAccount(account.apiKeyEnv,renameValue.trim()||account.apiKeyEnv)}setRenameFor("");setRenameValue("")}async function runConfirm(){if(!confirmAction)return;if(confirmAction.type==="delete"&&typeof handleDeleteAccount==="function"){await handleDeleteAccount(confirmAction.env,true)}if(confirmAction.type==="clear"&&typeof handleClearKey==="function"){await handleClearKey(confirmAction.env)}setConfirmAction(null)}function dotClass(account,live){if(!account.present)return"cb-status-dot cb-status-dot-warn";if(live?.error)return"cb-status-dot cb-status-dot-bad";const percent=live?.usage?.windows?.fiveHour?.percentUsed;if(typeof percent==="number"&&percent>=95)return"cb-status-dot cb-status-dot-bad";if(account.isPinned||status.activeAccount===account.apiKeyEnv)return"cb-status-dot cb-status-dot-ok";return"cb-status-dot cb-status-dot-idle"}const accounts=status.accounts||[];return React.createElement("section",{className:"cb-section-card cb-accounts"},React.createElement("div",{className:"cb-section-head"},React.createElement("div",{className:"cb-section-title"},t("accounts.title")),React.createElement("button",{type:"button",className:"cb-btn",onClick:toggleHistory},historyOpen?t("accounts.history_hide"):t("accounts.history_show")),!showAdd?React.createElement("button",{type:"button",className:"cb-btn cb-btn-primary",disabled:!!busy,onClick:openAdd},React.createElement(CbIcon,{name:"plus",size:13}),` ${t("accounts.add_btn")}`):null),React.createElement("div",{className:"cb-section-desc"},t("accounts.desc")),discoveredKeys.length>0?React.createElement("div",{className:"cb-discovered"},React.createElement("div",{className:"cb-discovered-head"},React.createElement(CbIcon,{name:"key",size:14}),` ${t("discovery.title")}`),discoveredKeys.map(item=>React.createElement("div",{className:"cb-discovered-row",key:item.id},React.createElement("span",{className:"cb-mono"},item.maskedKey||item.source),React.createElement("span",{className:"cb-muted"},item.source||""),React.createElement("button",{type:"button",className:"cb-btn",disabled:!!busy,onClick:()=>importDiscovered(item)},React.createElement(CbIcon,{name:"download",size:12}),` ${t("discovery.import_btn",{source:item.source||""})}`)))):null,React.createElement("div",{className:"cb-account-mode"},React.createElement("span",{className:"cb-muted"},t("accounts.mode_label")),React.createElement("div",{className:"cb-seg"},React.createElement("button",{type:"button",className:(accountMode||"least_used")==="least_used"?"cb-seg-item cb-seg-item-active":"cb-seg-item",disabled:!!busy,onClick:()=>handleUpdateAccountMode&&handleUpdateAccountMode("least_used")},t("accounts.mode_least_used")),React.createElement("button",{type:"button",className:accountMode==="round_robin"?"cb-seg-item cb-seg-item-active":"cb-seg-item",disabled:!!busy,onClick:()=>handleUpdateAccountMode&&handleUpdateAccountMode("round_robin")},t("accounts.mode_round_robin")))),showAdd?React.createElement("form",{className:"cb-account-add",onSubmit:submitAdd},React.createElement("div",{className:"cb-account-add-row"},React.createElement("input",{type:"text",className:"cb-input",placeholder:t("accounts.label_placeholder"),value:newLabel,onChange:event=>setNewLabel(event.target.value)}),React.createElement("input",{type:"text",className:"cb-input",placeholder:t("accounts.env_placeholder"),value:newEnv,onChange:event=>setNewEnv(event.target.value)})),React.createElement("div",{className:"cb-account-add-row"},React.createElement("input",{type:showKey?"text":"password",className:"cb-input",placeholder:t("key.placeholder_empty"),value:newKey,onChange:event=>setNewKey(event.target.value)}),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setShowKey(value=>!value)},showKey?t("key.hide"):t("key.show"))),React.createElement("div",{className:"cb-account-add-actions"},React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setShowAdd(false)},t("accounts.cancel_btn")),React.createElement("button",{type:"submit",className:"cb-btn cb-btn-primary",disabled:!newKey.trim()||!!busy},busy==="add-account"?t("key.saving"):t("accounts.save_btn")))):null,accounts.length===0?React.createElement("div",{className:"cb-empty"},t("accounts.no_accounts")):React.createElement("div",{className:"cb-account-list"},accounts.map(account=>{const envName=account.apiKeyEnv||account.id||"";const live=usageByAccount[envName];const isActive=status.activeAccount===envName||!status.activeAccount&&account.id==="default";const isPrimary=account.id==="default"||envName===status.config?.apiKeyEnv;const five=live?.usage?.windows?.fiveHour;const percent=typeof five?.percentUsed==="number"?five.percentUsed:typeof account.percentUsed==="number"?account.percentUsed:null;const plan=live?.usage?.plan||"";const email=live?.usage?.user?.email||"";return React.createElement("div",{className:"cb-account-item",key:envName||account.id},React.createElement("div",{className:"cb-account-row"},React.createElement("span",{className:dotClass(account,live),"aria-hidden":"true"}),React.createElement("div",{className:"cb-account-main"},React.createElement("div",{className:"cb-account-name"},account.label||envName,React.createElement("span",{className:"cb-account-env cb-mono"},envName)),React.createElement("div",{className:"cb-account-meta"},[plan,email].filter(Boolean).join(" \xB7 ")||t("accounts.source",{source:account.source||"\u2014"}))),React.createElement("div",{className:"cb-account-badges"},isActive?React.createElement("span",{className:"cb-badge cb-badge-ok"},t("accounts.active_badge")):null,account.isPinned?React.createElement("span",{className:"cb-badge"},t("accounts.pinned_badge")):null,account.present?React.createElement("span",{className:"cb-badge"},t("accounts.configured")):React.createElement("span",{className:"cb-badge cb-badge-warn"},t("accounts.missing_key"))),React.createElement("div",{className:"cb-account-quota"},percent===null?null:React.createElement(React.Fragment,null,React.createElement("span",{className:"cb-muted"},t("accounts.quota_5h",{pct:Math.round(percent)})),React.createElement("span",{className:"cb-meter"},React.createElement("span",{className:percent>=95?"cb-meter-fill cb-meter-fill-warn":"cb-meter-fill",style:{width:`${Math.max(0,Math.min(100,percent))}%`}})))),React.createElement("button",{type:"button",className:"cb-icon-btn","aria-label":t("accounts.actions"),"aria-expanded":menuFor===envName,disabled:!!busy,onClick:()=>setMenuFor(current=>current===envName?"":envName)},React.createElement(CbIcon,{name:"kebab",size:16}))),menuFor===envName?React.createElement("div",{className:"cb-account-menu"},isActive?React.createElement("button",{type:"button",className:"cb-menu-item",onClick:()=>{setMenuFor("");handlePinAccount("")}},t("accounts.unpin")):account.present?React.createElement("button",{type:"button",className:"cb-menu-item",onClick:()=>{setMenuFor("");handlePinAccount(envName)}},t("accounts.pin_btn")):null,!isPrimary?React.createElement("button",{type:"button",className:"cb-menu-item",onClick:()=>openRename(account)},t("accounts.rename")):null,account.present?React.createElement("button",{type:"button",className:"cb-menu-item",onClick:()=>{setMenuFor("");setConfirmAction({type:"clear",env:envName})}},t("accounts.clear_key")):null,!isPrimary?React.createElement("button",{type:"button",className:"cb-menu-item cb-menu-item-danger",onClick:()=>{setMenuFor("");setConfirmAction({type:"delete",env:envName})}},t("accounts.delete_btn")):null):null,renameFor===envName?React.createElement("div",{className:"cb-account-inline"},React.createElement("input",{type:"text",className:"cb-input",value:renameValue,onChange:event=>setRenameValue(event.target.value),placeholder:t("accounts.rename_placeholder")}),React.createElement("button",{type:"button",className:"cb-btn cb-btn-primary",disabled:!!busy,onClick:()=>submitRename(account)},t("accounts.rename_save")),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setRenameFor("")},t("accounts.cancel"))):null,confirmAction&&confirmAction.env===envName?React.createElement("div",{className:"cb-account-inline cb-account-danger"},React.createElement("span",{className:"cb-account-danger-text"},confirmAction.type==="delete"?t("accounts.remove_confirm",{name:account.label||envName}):t("accounts.clear_confirm",{name:account.label||envName})),React.createElement("button",{type:"button",className:"cb-btn cb-btn-danger",disabled:!!busy,onClick:runConfirm},t("accounts.confirm")),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setConfirmAction(null)},t("accounts.cancel"))):null)})),React.createElement(AccountRoutes,{accounts,modelRoutes,onUpdateModelRoutes,busy,t}),React.createElement(AccountHistory,{open:historyOpen,events:history,loading:historyLoading,accounts,t}))}
425
425
 
426
426
  // --- src/client/components/quota-section.js ---
427
- function QuotaSection({keyPresent,status,usage,busy,handleRefreshQuota,showSidebarQuota,onToggleSidebarQuota,t}){const isLowQuota=Boolean(usage?.windows?.weekly&&typeof usage.windows.weekly.remainingPercent==="number"&&usage.windows.weekly.remainingPercent<=10||usage?.windows?.monthly&&typeof usage.windows.monthly.remainingPercent==="number"&&usage.windows.monthly.remainingPercent<=10||usage?.windows?.fiveHour&&typeof usage.windows.fiveHour.remainingPercent==="number"&&usage.windows.fiveHour.remainingPercent<=10);return React.createElement(React.Fragment,null,status.quotaWarning?React.createElement("div",{className:status.quotaWarning.level==="exhausted"?"cb-banner-exhausted":"cb-banner-warning"},React.createElement("span",{className:"cb-banner-icon"},React.createElement(CbIcon,{name:status.quotaWarning.level==="exhausted"?"error":"warning",size:16})),React.createElement("div",{className:"cb-banner-body"},React.createElement("div",null,status.quotaWarning.code&&t(status.quotaWarning.code,status.quotaWarning.params)||status.quotaWarning.message),status.quotaWarning.resetsAt?React.createElement("div",{className:"cb-banner-sub"},t("quota.reset_at",{time:new Date(status.quotaWarning.resetsAt).toLocaleTimeString()})):null)):isLowQuota?React.createElement("div",{className:"cb-banner-warning"},React.createElement("span",{className:"cb-banner-icon"},React.createElement(CbIcon,{name:"warning",size:16})),React.createElement("div",{className:"cb-banner-body"},t("quota.warning_low"))):null,keyPresent?React.createElement("section",{className:"cb-section-card"},React.createElement("div",{className:"cb-section-head"},React.createElement("div",{className:"cb-section-title"},t("quota.title")),React.createElement("button",{type:"button",className:"cb-btn",disabled:!!busy,onClick:handleRefreshQuota},busy==="refresh-quota"?t("quota.refreshing"):t("quota.refresh"))),React.createElement("div",{className:"cb-section-desc"},usage?.user?.email?t("quota.account",{email:usage.user.email,plan:usage.plan||"ClinePass"}):t("quota.desc")),React.createElement("div",{className:"cb-quota-grid"},usage?.windows?.fiveHour?React.createElement(ProgressBar,{label:t("quota.window_5h"),percentUsed:usage.windows.fiveHour.percentUsed||0,remainingPercent:usage.windows.fiveHour.remainingPercent||100,resetsAt:usage.windows.fiveHour.resetsAt,windowMs:5*60*60*1e3,t}):null,React.createElement(ProgressBar,{label:t("quota.window_weekly"),percentUsed:usage?.windows?.weekly?.percentUsed||0,remainingPercent:usage?.windows?.weekly?.remainingPercent||100,resetsAt:usage?.windows?.weekly?.resetsAt,windowMs:7*24*60*60*1e3,t}),usage?.windows?.monthly?React.createElement(ProgressBar,{label:t("quota.window_monthly"),percentUsed:usage.windows.monthly.percentUsed||0,remainingPercent:usage.windows.monthly.remainingPercent||100,resetsAt:usage.windows.monthly.resetsAt,windowMs:30*24*60*60*1e3,t}):null),React.createElement(SettingRow,{title:t("sidebar.show_quota"),description:t("sidebar.show_quota_hint"),control:React.createElement(BooleanControl,{checked:showSidebarQuota!==false,label:t("sidebar.show_quota"),onChange:next=>onToggleSidebarQuota&&onToggleSidebarQuota(next)})})):null)}
427
+ function QuotaSection({keyPresent,status,usage,busy,handleRefreshQuota,showSidebarQuota,onToggleSidebarQuota,t}){const isLowQuota=Boolean(usage?.windows?.weekly&&typeof usage.windows.weekly.remainingPercent==="number"&&usage.windows.weekly.remainingPercent<=10||usage?.windows?.monthly&&typeof usage.windows.monthly.remainingPercent==="number"&&usage.windows.monthly.remainingPercent<=10||usage?.windows?.fiveHour&&typeof usage.windows.fiveHour.remainingPercent==="number"&&usage.windows.fiveHour.remainingPercent<=10);return React.createElement(React.Fragment,null,status.quotaWarning?React.createElement("div",{className:status.quotaWarning.level==="exhausted"?"cb-banner-exhausted":"cb-banner-warning"},React.createElement("span",{className:"cb-banner-icon"},React.createElement(CbIcon,{name:status.quotaWarning.level==="exhausted"?"error":"warning",size:16})),React.createElement("div",{className:"cb-banner-body"},React.createElement("div",null,status.quotaWarning.code&&t(status.quotaWarning.code,status.quotaWarning.params)||status.quotaWarning.message),status.quotaWarning.resetsAt?React.createElement("div",{className:"cb-banner-sub"},t("quota.reset_at",{time:new Date(status.quotaWarning.resetsAt).toLocaleTimeString()})):null)):isLowQuota?React.createElement("div",{className:"cb-banner-warning"},React.createElement("span",{className:"cb-banner-icon"},React.createElement(CbIcon,{name:"warning",size:16})),React.createElement("div",{className:"cb-banner-body"},t("quota.warning_low"))):null,keyPresent?React.createElement("section",{className:"cb-section-card"},React.createElement("div",{className:"cb-section-head"},React.createElement("div",{className:"cb-section-title"},t("quota.title")),React.createElement("button",{type:"button",className:"cb-btn",disabled:!!busy,onClick:handleRefreshQuota},busy==="refresh-quota"?t("quota.refreshing"):t("quota.refresh"))),React.createElement("div",{className:"cb-section-desc"},usage?.user?.email?t("quota.account",{email:usage.user.email,plan:usage.plan||"ClinePass"}):t("quota.desc")),React.createElement("div",{className:"cb-quota-grid"},usage?.windows?.fiveHour?React.createElement(ProgressBar,{label:t("quota.window_5h"),percentUsed:usage.windows.fiveHour.percentUsed||0,remainingPercent:usage.windows.fiveHour.remainingPercent||100,resetsAt:usage.windows.fiveHour.resetsAt,windowMs:5*60*60*1e3,t}):null,React.createElement(ProgressBar,{label:t("quota.window_weekly"),percentUsed:usage?.windows?.weekly?.percentUsed||0,remainingPercent:usage?.windows?.weekly?.remainingPercent||100,resetsAt:usage?.windows?.weekly?.resetsAt,windowMs:7*24*60*60*1e3,t}),usage?.windows?.monthly?React.createElement(ProgressBar,{label:t("quota.window_monthly"),percentUsed:usage.windows.monthly.percentUsed||0,remainingPercent:usage.windows.monthly.remainingPercent||100,resetsAt:usage.windows.monthly.resetsAt,windowMs:30*24*60*60*1e3,t}):null),React.createElement(SettingRow,{title:t("sidebar.show_quota"),description:t("sidebar.show_quota_hint"),control:React.createElement(BooleanControl,{checked:Boolean(showSidebarQuota),label:t("sidebar.show_quota"),onChange:next=>onToggleSidebarQuota&&onToggleSidebarQuota(next)})})):null)}
428
428
 
429
429
  // --- src/client/components/custom-models.js ---
430
430
  function CustomModelsSub({customModels=[],onConfigUpdate,busy,t}){const[newId,setNewId]=React.useState("");const[newName,setNewName]=React.useState("");const[newCtx,setNewCtx]=React.useState("");const[newMax,setNewMax]=React.useState("");const[isExpanded,setIsExpanded]=React.useState(false);function handleAdd(){const trimmedId=newId.trim();const trimmedName=newName.trim()||trimmedId;if(!trimmedId)return;const parsedCtx=parseInt(newCtx,10);const parsedMax=parseInt(newMax,10);const nextList=[...customModels.filter(m=>m.id!==trimmedId),{id:trimmedId,name:trimmedName,contextLength:parsedCtx>0?parsedCtx:128e3,maxTokens:parsedMax>0?parsedMax:8192,category:"general",isCustom:true}];if(typeof onConfigUpdate==="function"){onConfigUpdate({customModels:nextList})}setNewId("");setNewName("");setNewCtx("");setNewMax("")}function handleDelete(id){const nextList=customModels.filter(m=>m.id!==id);if(typeof onConfigUpdate==="function"){onConfigUpdate({customModels:nextList})}}return React.createElement("div",{style:{marginTop:"16px",paddingTop:"14px",borderTop:"1px solid var(--dsw-alias-border-l2)"}},React.createElement("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",cursor:"pointer"},onClick:()=>setIsExpanded(!isExpanded)},React.createElement("span",{style:{fontWeight:500,fontSize:"13px",color:"var(--dsw-alias-label-primary)"}},React.createElement(CbIcon,{name:"plus",size:13}),` ${t("models.custom_add_title")} (${customModels.length})`),React.createElement("span",{style:{fontSize:"12px",color:"var(--dsw-alias-label-secondary)"}},React.createElement(CbIcon,{name:"chevron",size:13,className:isExpanded?"cb-icon cb-icon-chevron-open":"cb-icon"}))),isExpanded?React.createElement("div",{style:{marginTop:"12px",display:"flex",flexDirection:"column",gap:"10px"}},React.createElement("div",{style:{display:"flex",gap:"8px",flexWrap:"wrap"}},React.createElement("input",{type:"text",className:"cb-input",style:{flex:"2 1 140px"},placeholder:t("models.custom_id"),value:newId,disabled:!!busy,onChange:e=>setNewId(e.target.value)}),React.createElement("input",{type:"text",className:"cb-input",style:{flex:"2 1 140px"},placeholder:t("models.custom_name"),value:newName,disabled:!!busy,onChange:e=>setNewName(e.target.value)}),React.createElement("input",{type:"number",className:"cb-input",style:{flex:"1 1 90px"},placeholder:t("models.custom_ctx_label"),title:t("models.custom_ctx_label"),value:newCtx,disabled:!!busy,onChange:e=>setNewCtx(e.target.value)}),React.createElement("input",{type:"number",className:"cb-input",style:{flex:"1 1 80px"},placeholder:t("models.custom_max_label"),title:t("models.custom_max_label"),value:newMax,disabled:!!busy,onChange:e=>setNewMax(e.target.value)}),React.createElement("button",{type:"button",className:"cb-btn cb-btn-primary",disabled:!newId.trim()||!!busy,onClick:handleAdd},t("models.custom_btn_add"))),customModels.length>0?React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:"6px",marginTop:"6px"}},customModels.map(cm=>React.createElement("div",{key:cm.id,style:{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"6px 10px",background:"var(--dsw-alias-bg-layer-2)",border:"1px solid var(--dsw-alias-border-l2)",borderRadius:"6px",fontSize:"12px"}},React.createElement("div",null,React.createElement("span",{style:{fontWeight:500}},cm.name||cm.id),React.createElement("span",{style:{color:"var(--dsw-alias-label-secondary)",marginLeft:"8px"}},`(${cm.id})`),React.createElement("span",{style:{color:"var(--dsw-alias-label-secondary)",marginLeft:"8px",fontSize:"11px"}},`[${cm.contextLength||128e3} / ${cm.maxTokens||8192}]`)),React.createElement("button",{type:"button",className:"cb-btn",style:{padding:"2px 8px",fontSize:"11px"},disabled:!!busy,onClick:()=>handleDelete(cm.id)},t("models.custom_btn_del"))))):null):null)}
@@ -448,7 +448,7 @@ function StatsSection({status,onResetStats,busy,t}){const[confirmReset,setConfir
448
448
  function DiagSection({keyPresent,busy,smokeResult,handleSmoke,t}){return React.createElement("section",{className:"cb-section-card"},React.createElement("div",{className:"cb-section-title"},t("diag.title")),React.createElement("div",{className:"cb-section-desc"},t("diag.desc")),React.createElement("div",{className:"cb-row cb-row-actions"},React.createElement("button",{type:"button",className:"cb-btn cb-btn-primary",disabled:!!busy||!keyPresent,onClick:handleSmoke},busy==="smoke"?t("diag.smoke_testing"):t("diag.smoke_btn"))),smokeResult?React.createElement("div",{className:"cb-preview"},t("diag.smoke_result",{latency:smokeResult.latencyMs,model:smokeResult.model,preview:smokeResult.preview||t("diag.empty")})):null)}
449
449
 
450
450
  // --- src/client/components/sidebar-quota.js ---
451
- function MiniRing({percent,size=18,warn=false}){const r=7;const c=2*Math.PI*r;const pct=Math.max(0,Math.min(100,Number(percent)||0));const offset=c-pct/100*c;const strokeColor=warn?"var(--dsw-alias-state-error-primary)":"var(--dsw-alias-brand-primary)";return React.createElement("svg",{className:"cb-glyph",width:size,height:size,viewBox:"0 0 20 20",fill:"none"},React.createElement("circle",{cx:10,cy:10,r,stroke:"var(--dsw-alias-bg-layer-2)",strokeWidth:2.5}),React.createElement("circle",{cx:10,cy:10,r,stroke:strokeColor,strokeWidth:2.5,strokeDasharray:c,strokeDashoffset:offset,strokeLinecap:"round",transform:"rotate(-90 10 10)",style:{transition:"stroke-dashoffset 0.3s ease"}}))}function SidebarQuota({ctx,wide,t:propT,open:openPanel}){const t=propT||makeT(en,en);const[data,setData]=React.useState(null);const[open,setOpen]=React.useState(false);const configSnapshot=useConfigFormsSnapshot(ctx);const show=configSnapshot?.value?.showSidebarQuota!==false;const loadQuota=React.useCallback(async()=>{try{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/usage`,{cache:"no-store"});if(!res.ok)return;const json=await res.json().catch(()=>null);if(json&&json.ok)setData(json)}catch{}},[]);React.useEffect(()=>{if(!show)return void 0;let timer=null;const scheduleNext=()=>{timer=setTimeout(async()=>{await loadQuota();scheduleNext()},6e4)};loadQuota();scheduleNext();return()=>{if(timer)clearTimeout(timer)}},[loadQuota,show]);if(!show)return null;const windowEntries=[{key:"fiveHour",label:t("quota.window_5h"),value:data?.windows?.fiveHour},{key:"weekly",label:t("quota.window_weekly"),value:data?.windows?.weekly},{key:"monthly",label:t("quota.window_monthly"),value:data?.windows?.monthly}];const windowPct=window=>{const value=Number(window.value?.percentUsed);return Number.isFinite(value)?Math.max(0,Math.min(100,Math.round(value))):null};const measured=windowEntries.map(window=>({...window,pct:windowPct(window)})).filter(window=>window.pct!==null);const pct=measured.length?Math.max(...measured.map(window=>window.pct)):0;const isWarn=measured.some(window=>window.pct>=85);const planName=String(data?.plan||t("title")).replace(/\s*\(\$[^)]*\)\s*$/,"");const accountLabel=data?.account?` \xB7 ${data.account}`:"";const tooltip=`${planName}${accountLabel} \xB7 ${windowEntries.map(window=>{const value=windowPct(window);return`${window.label} ${value===null?"\u2014":`${value}% ${t("dashboard.used")}`}`}).join(" \xB7 ")}`;const handleOpen=()=>{if(typeof openPanel==="function"){try{if(openPanel()!==false)return}catch{}}setOpen(true)};const rows=windowEntries;const dialog=React.createElement(DashboardDialog,{t,open,onClose:()=>setOpen(false)});if(!wide){return React.createElement(React.Fragment,null,React.createElement("button",{type:"button",className:"cb-railButton","aria-label":tooltip,"aria-expanded":open,title:tooltip,onClick:handleOpen},React.createElement(MiniRing,{percent:pct,warn:isWarn,size:20})),dialog)}return React.createElement(React.Fragment,null,React.createElement("button",{type:"button",className:"cb-foot","aria-label":tooltip,"aria-expanded":open,title:tooltip,onClick:handleOpen},React.createElement("div",{className:"cb-footTop"},React.createElement(MiniRing,{percent:pct,warn:isWarn,size:16}),React.createElement("span",{className:"cb-footName"},planName),React.createElement("span",{className:isWarn?"cb-footPct cb-footPctWarn":"cb-footPct"},`${pct}%`)),rows.map(window=>{const value=windowPct(window);const hasData=value!==null;const warn=hasData&&value>=85;const reset=hasData&&typeof formatResetCountdown==="function"?formatResetCountdown(window.value?.resetsAt):"";const resetText=reset?` \xB7 ${reset==="resetting"?t("quota.resetting"):reset}`:"";return React.createElement("div",{className:hasData?"cb-footRow":"cb-footRow cb-footRowMissing",key:window.key},React.createElement("div",{className:"cb-footHead"},React.createElement("span",{className:"cb-footLabel"},window.label),React.createElement("span",{className:hasData?"cb-footAmount":"cb-footAmount cb-footAmountMissing"},hasData?`${100-value}% ${t("dashboard.remaining")}${resetText}`:"\u2014")),React.createElement("span",{className:"cb-footBar"},React.createElement("span",{className:warn?"cb-footFill cb-footFillWarn":"cb-footFill",style:{width:hasData?`${Math.min(100,Math.max(2,value))}%`:"0%"}})),hasData?React.createElement("span",{className:"cb-footForecast"},quotaForecast(window.value,window.key==="fiveHour"?5*60*60*1e3:window.key==="weekly"?7*24*60*60*1e3:30*24*60*60*1e3,t)):null)}),rows.some(window=>windowPct(window)!==null)?null:React.createElement("span",{className:"cb-footMessage"},data?.error||t("quota.no_windows"))),dialog)}
451
+ function MiniRing({percent,size=18,warn=false}){const r=7;const c=2*Math.PI*r;const pct=Math.max(0,Math.min(100,Number(percent)||0));const offset=c-pct/100*c;const strokeColor=warn?"var(--dsw-alias-state-error-primary)":"var(--dsw-alias-brand-primary)";return React.createElement("svg",{className:"cb-glyph",width:size,height:size,viewBox:"0 0 20 20",fill:"none"},React.createElement("circle",{cx:10,cy:10,r,stroke:"var(--dsw-alias-bg-layer-2)",strokeWidth:2.5}),React.createElement("circle",{cx:10,cy:10,r,stroke:strokeColor,strokeWidth:2.5,strokeDasharray:c,strokeDashoffset:offset,strokeLinecap:"round",transform:"rotate(-90 10 10)",style:{transition:"stroke-dashoffset 0.3s ease"}}))}function SidebarQuota({ctx,wide,t:propT,open:openPanel}){const t=propT||makeT(en,en);const[data,setData]=React.useState(null);const[open,setOpen]=React.useState(false);const configSnapshot=useConfigFormsSnapshot(ctx);const show=Boolean(configSnapshot?.value?.showSidebarQuota);const loadQuota=React.useCallback(async()=>{try{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/usage`,{cache:"no-store"});if(!res.ok)return;const json=await res.json().catch(()=>null);if(json&&json.ok)setData(json)}catch{}},[]);React.useEffect(()=>{if(!show)return void 0;let timer=null;const scheduleNext=()=>{timer=setTimeout(async()=>{await loadQuota();scheduleNext()},6e4)};loadQuota();scheduleNext();return()=>{if(timer)clearTimeout(timer)}},[loadQuota,show]);if(!show)return null;const windowEntries=[{key:"fiveHour",label:t("quota.window_5h"),value:data?.windows?.fiveHour},{key:"weekly",label:t("quota.window_weekly"),value:data?.windows?.weekly},{key:"monthly",label:t("quota.window_monthly"),value:data?.windows?.monthly}];const windowPct=window=>{const value=Number(window.value?.percentUsed);return Number.isFinite(value)?Math.max(0,Math.min(100,Math.round(value))):null};const measured=windowEntries.map(window=>({...window,pct:windowPct(window)})).filter(window=>window.pct!==null);const pct=measured.length?Math.max(...measured.map(window=>window.pct)):0;const isWarn=measured.some(window=>window.pct>=85);const planName=String(data?.plan||t("title")).replace(/\s*\(\$[^)]*\)\s*$/,"");const accountLabel=data?.account?` \xB7 ${data.account}`:"";const tooltip=`${planName}${accountLabel} \xB7 ${windowEntries.map(window=>{const value=windowPct(window);return`${window.label} ${value===null?"\u2014":`${value}% ${t("dashboard.used")}`}`}).join(" \xB7 ")}`;const handleOpen=()=>{if(typeof openPanel==="function"){try{if(openPanel()!==false)return}catch{}}setOpen(true)};const rows=windowEntries;const dialog=React.createElement(DashboardDialog,{t,open,onClose:()=>setOpen(false)});if(!wide){return React.createElement(React.Fragment,null,React.createElement("button",{type:"button",className:"cb-railButton","aria-label":tooltip,"aria-expanded":open,title:tooltip,onClick:handleOpen},React.createElement(MiniRing,{percent:pct,warn:isWarn,size:20})),dialog)}return React.createElement(React.Fragment,null,React.createElement("button",{type:"button",className:"cb-foot","aria-label":tooltip,"aria-expanded":open,title:tooltip,onClick:handleOpen},React.createElement("div",{className:"cb-footTop"},React.createElement(MiniRing,{percent:pct,warn:isWarn,size:16}),React.createElement("span",{className:"cb-footName"},planName),React.createElement("span",{className:isWarn?"cb-footPct cb-footPctWarn":"cb-footPct"},`${pct}%`)),rows.map(window=>{const value=windowPct(window);const hasData=value!==null;const warn=hasData&&value>=85;const reset=hasData&&typeof formatResetCountdown==="function"?formatResetCountdown(window.value?.resetsAt):"";const resetText=reset?` \xB7 ${reset==="resetting"?t("quota.resetting"):reset}`:"";return React.createElement("div",{className:hasData?"cb-footRow":"cb-footRow cb-footRowMissing",key:window.key},React.createElement("div",{className:"cb-footHead"},React.createElement("span",{className:"cb-footLabel"},window.label),React.createElement("span",{className:hasData?"cb-footAmount":"cb-footAmount cb-footAmountMissing"},hasData?`${100-value}% ${t("dashboard.remaining")}${resetText}`:"\u2014")),React.createElement("span",{className:"cb-footBar"},React.createElement("span",{className:warn?"cb-footFill cb-footFillWarn":"cb-footFill",style:{width:hasData?`${Math.min(100,Math.max(2,value))}%`:"0%"}})),hasData?React.createElement("span",{className:"cb-footForecast"},quotaForecast(window.value,window.key==="fiveHour"?5*60*60*1e3:window.key==="weekly"?7*24*60*60*1e3:30*24*60*60*1e3,t)):null)}),rows.some(window=>windowPct(window)!==null)?null:React.createElement("span",{className:"cb-footMessage"},data?.error||t("quota.no_windows"))),dialog)}
452
452
 
453
453
  // --- src/client/components/price-section.js ---
454
454
  function PriceSection({modelsList=[],priceOverrides={},priceAutoSync=true,onConfigUpdate,t}){const[expanded,setExpanded]=React.useState(false);const[syncState,setSyncState]=React.useState({busy:false,message:""});const models=Array.isArray(modelsList)?modelsList:[];const syncPrices=async()=>{setSyncState({busy:true,message:""});try{const response=await fetchWithTimeout(`${ROUTE_PREFIX}/prices/sync`,{method:"POST"});const data=await response.json().catch(()=>({}));if(!response.ok||!data.ok)throw new Error(data.error||`HTTP ${response.status}`);setSyncState({busy:false,message:t("prices.synced",{count:data.count})})}catch(err){setSyncState({busy:false,message:t("prices.sync_failed",{error:String(err?.message||err)})})}};const update=(modelId,field,raw)=>{const next={...priceOverrides||{}};const current={...next[modelId]||{}};const value=String(raw??"").trim();const numeric=Number(value);if(value===""||!Number.isFinite(numeric)||numeric<0)delete current[field];else current[field]=numeric;if(Object.keys(current).length===0)delete next[modelId];else next[modelId]=current;onConfigUpdate({priceOverrides:next})};return React.createElement("div",{className:"cb-price-section"},React.createElement("div",{className:"cb-price-head"},React.createElement("div",{className:"cb-price-copy"},React.createElement("div",{className:"cb-section-title"},t("prices.title")),React.createElement("div",{className:"cb-section-desc"},t("prices.desc")),React.createElement(BooleanControl,{checked:priceAutoSync!==false,label:t("prices.auto_sync"),onChange:next=>onConfigUpdate&&onConfigUpdate({priceAutoSync:next})}),React.createElement("div",{className:"cb-hint"},t("prices.auto_sync_hint"))),React.createElement("div",{className:"cb-row",style:{gap:"6px"}},React.createElement("button",{type:"button",className:"cb-btn",disabled:syncState.busy,onClick:syncPrices},syncState.busy?t("prices.syncing"):t("prices.sync")),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setExpanded(value=>!value)},expanded?t("prices.hide"):t("prices.show")))),expanded?React.createElement("div",{className:"cb-price-table"},React.createElement("div",{className:"cb-price-head-row"},React.createElement("span",null,t("prices.model")),React.createElement("span",null,t("prices.input")),React.createElement("span",null,t("prices.output")),React.createElement("span",null,t("prices.cache_read")),React.createElement("span",null,t("prices.cache_write"))),models.length===0?React.createElement("div",{className:"cb-hint"},t("prices.empty")):models.map(model=>{const override=priceOverrides?.[model.id]||{};return React.createElement("div",{className:"cb-price-row",key:model.id},React.createElement("span",{className:"cb-mono",title:model.id},model.name||model.id),["input","output","cacheRead","cacheWrite"].map(field=>React.createElement("input",{key:field,className:"cb-input cb-price-input",type:"number",min:"0",step:"0.001",value:override[field]===void 0?"":String(override[field]),placeholder:t("prices.auto"),onChange:event=>update(model.id,field,event.target.value)})))}),React.createElement("div",{className:"cb-hint"},t("prices.public_hint")),React.createElement("div",{className:"cb-hint"},t("prices.hint")),syncState.message?React.createElement("div",{className:"cb-hint"},syncState.message):null):null)}
@@ -466,10 +466,10 @@ function AnalyticsControls({analyticsDays,setAnalyticsDays,groupBy,setGroupBy,mo
466
466
  function DashboardDialog(props){const t=props?.t||makeT(en,en);const panel=props?.panel===true;const open=panel||props?.open!==false;const onClose=typeof props?.onClose==="function"?props.onClose:()=>{};const[data,setData]=React.useState(null);const[status,setStatus]=React.useState(null);const[analytics,setAnalytics]=React.useState(null);const[tab,setTab]=React.useState("quota");const[groupBy,setGroupBy]=React.useState("model");const[loading,setLoading]=React.useState(false);const[analyticsLoading,setAnalyticsLoading]=React.useState(false);const[updatedAt,setUpdatedAt]=React.useState("");const[err,setErr]=React.useState("");const[accountsUsage,setAccountsUsage]=React.useState(null);const[selectedAccount,setSelectedAccount]=React.useState("");const[analyticsDays,setAnalyticsDays]=React.useState(7);const[analyticsModel,setAnalyticsModel]=React.useState("");const[analyticsAccount,setAnalyticsAccount]=React.useState("");const[analyticsErrorsOnly,setAnalyticsErrorsOnly]=React.useState(false);const[analyticsFrom,setAnalyticsFrom]=React.useState("");const[analyticsTo,setAnalyticsTo]=React.useState("");const[heatmapModel,setHeatmapModel]=React.useState("");const[selectedHeat,setSelectedHeat]=React.useState(null);const loadData=React.useCallback(async()=>{setLoading(true);setErr("");try{const[uRes,sRes,aRes]=await Promise.all([fetchWithTimeout(`${ROUTE_PREFIX}/usage`,{cache:"no-store"}),fetchWithTimeout(`${ROUTE_PREFIX}/status`,{cache:"no-store"}),fetchWithTimeout(`${ROUTE_PREFIX}/accounts/usage`,{cache:"no-store"})]);const uJson=await uRes.json().catch(()=>({}));const sJson=await sRes.json().catch(()=>({}));const aJson=await aRes.json().catch(()=>({}));if(uJson?.ok)setData(uJson);if(sJson?.ok)setStatus(sJson);if(aJson?.ok){setAccountsUsage(aJson);setSelectedAccount(current=>{const accounts=Array.isArray(aJson.accounts)?aJson.accounts:[];if(accounts.some(entry=>entry.apiKeyEnv===current))return current;return aJson.activeAccount||accounts[0]?.apiKeyEnv||""})}setUpdatedAt(new Date().toLocaleTimeString())}catch(e){setErr(String(e?.message||e))}finally{setLoading(false)}},[]);const loadAnalytics=React.useCallback(async(group=groupBy,days=analyticsDays,model=heatmapModel)=>{setAnalyticsLoading(true);setErr("");try{const now=Date.now();const parsedFrom=analyticsFrom?Date.parse(analyticsFrom):NaN;const parsedTo=analyticsTo?Date.parse(analyticsTo):NaN;const from=Number.isFinite(parsedFrom)?parsedFrom:now-days*864e5;const to=Number.isFinite(parsedTo)?parsedTo:now;const query=[`groupBy=${encodeURIComponent(group)}`,"limit=20",`from=${from}`,`to=${to}`,`heatmapDays=${days}`,...model?[`heatmapModel=${encodeURIComponent(model)}`]:[],...analyticsModel?[`model=${encodeURIComponent(analyticsModel)}`]:[],...analyticsAccount?[`account=${encodeURIComponent(analyticsAccount)}`]:[],...analyticsErrorsOnly?["errorsOnly=1"]:[]].join("&");const res=await fetchWithTimeout(`${ROUTE_PREFIX}/analytics?${query}`,{cache:"no-store"});const json=await res.json().catch(()=>({}));if(!res.ok||!json.ok)throw new Error(json.error||`HTTP ${res.status}`);setAnalytics(json)}catch(e){setErr(String(e?.message||e))}finally{setAnalyticsLoading(false)}},[groupBy,analyticsDays,heatmapModel,analyticsModel,analyticsAccount,analyticsErrorsOnly,analyticsFrom,analyticsTo]);React.useEffect(()=>{if(open)loadData()},[open,loadData]);React.useEffect(()=>{if(open&&(panel||tab==="analytics"))loadAnalytics(groupBy,analyticsDays,heatmapModel)},[open,panel,tab,groupBy,analyticsDays,heatmapModel,loadAnalytics]);React.useEffect(()=>{if(!open)return void 0;let timer=null;const tick=()=>{loadData();if(panel||tab==="analytics")loadAnalytics(groupBy,analyticsDays,heatmapModel);timer=setTimeout(tick,12e4)};timer=setTimeout(tick,12e4);return()=>{if(timer)clearTimeout(timer)}},[open,panel,tab,groupBy,analyticsDays,heatmapModel,loadData,loadAnalytics]);if(!open)return null;function fmtTokens(value){const n=Number(value)||0;if(n>=1e6)return`${(n/1e6).toFixed(1)}M`;if(n>=1e3)return`${(n/1e3).toFixed(1)}k`;return String(n)}function fmtCost(value){const n=Number(value)||0;if(n===0)return"$0";if(n<1e-4)return"< $0.0001";return`$${n.toFixed(4)}`}function fmtMs(value){const n=Number(value)||0;return n>0?`${Math.round(n)} ms`:"\u2014"}const plan=data?.plan||status?.config?.defaultModel||"ClinePass";const email=data?.user?.email||"";const activeAcc=status?.config?.activeAccount||t("dashboard.auto_account");const enabledCount=status?.config?.enabledModels?.length||0;const routingMode=status?.config?.accountMode==="round_robin"?t("accounts.mode_round_robin"):t("accounts.mode_least_used");const accountEntries=Array.isArray(accountsUsage?.accounts)?accountsUsage.accounts:[];const selectedEntry=accountEntries.find(entry=>entry.apiKeyEnv===selectedAccount)||accountEntries.find(entry=>entry.active)||accountEntries[0];const selectedUsage=selectedEntry?.usage||data;const accountPlan=selectedUsage?.plan||plan;const accountEmail=selectedUsage?.user?.email||email;const accountName=selectedEntry?.label||selectedEntry?.apiKeyEnv||accountPlan;const accountBadges=[];if(selectedEntry){accountBadges.push(selectedEntry.configured===false?{className:"cb-badge cb-badge-warn",label:t("accounts.missing_key")}:{className:"cb-badge cb-badge-ok",label:t("accounts.configured")});if(selectedEntry.active)accountBadges.push({className:"cb-badge cb-badge-ok",label:t("accounts.active_badge")});if(selectedEntry.isPinned)accountBadges.push({className:"cb-badge",label:t("accounts.pinned_badge")})}const accountTabs=accountEntries.length>1?React.createElement("div",{className:"cb-account-tabs"},accountEntries.map(entry=>React.createElement("button",{type:"button",key:entry.apiKeyEnv||entry.id,className:entry.apiKeyEnv===selectedEntry?.apiKeyEnv?"cb-account-tab cb-account-tab-active":"cb-account-tab",onClick:()=>setSelectedAccount(entry.apiKeyEnv)},entry.label||entry.apiKeyEnv||entry.id))):null;const noticeItems=[];if(status?.key&&status.key.present===false){noticeItems.push(React.createElement("div",{className:"cb-notice",key:"no-key"},React.createElement("div",{className:"cb-notice-title"},t("analytics.no_key")),React.createElement("div",{className:"cb-notice-hint"},t("analytics.no_key_hint"))))}if(status?.health&&status.health.ok===false){noticeItems.push(React.createElement("div",{className:"cb-notice cb-notice-error",key:"health"},React.createElement("div",{className:"cb-notice-title"},t("analytics.health_error")),React.createElement("div",{className:"cb-notice-hint"},status.health.error||"")))}if(status&&status.isRegistered===false){noticeItems.push(React.createElement("div",{className:"cb-notice",key:"not-registered"},React.createElement("div",{className:"cb-notice-title"},t("analytics.not_registered")),React.createElement("div",{className:"cb-notice-hint"},t("analytics.not_registered_hint"))))}const notices=noticeItems.length?React.createElement(React.Fragment,null,...noticeItems):null;const errorNotice=err?React.createElement("div",{className:data?"cb-hint":"cb-alert-bad"},data?`${t("analytics.stale")} ${err}`:err):null;const totals=analytics?.totals;const secondaryTiles=totals?[[t("analytics.input"),fmtTokens(totals.promptTokens)],[t("analytics.output"),fmtTokens(totals.completionTokens)],[t("analytics.reasoning"),fmtTokens(totals.reasoningTokens)],[t("analytics.cache_read"),fmtTokens(totals.cacheReadTokens)],[t("analytics.cache_write"),fmtTokens(totals.cacheWriteTokens)],[t("analytics.cache_saved"),`\u2248 ${fmtCost(totals.cacheSavingsUsd)}`],[t("analytics.avg_ttft"),fmtMs(totals.avgTtftMs)],[t("analytics.avg_latency"),fmtMs(totals.avgLatencyMs)],[t("analytics.p50_latency"),fmtMs(totals.p50LatencyMs)],[t("analytics.p95_latency"),fmtMs(totals.p95LatencyMs)],[t("analytics.errors"),String(Math.round(Number(totals.failed)||0))]]:[];const tabs=React.createElement("div",{className:"cb-tabs"},React.createElement("button",{type:"button",className:tab==="quota"?"cb-tab cb-tab-active":"cb-tab",onClick:()=>setTab("quota")},t("dashboard.tab_quota")),React.createElement("button",{type:"button",className:tab==="analytics"?"cb-tab cb-tab-active":"cb-tab",onClick:()=>setTab("analytics")},t("dashboard.tab_analytics")));const quotaBody=React.createElement(QuotaPanelView,{accountTabs,selectedUsage,accountName,accountEmail,accountPlan,accountBadges,activeAcc,routingMode,enabledCount,t});const analyticsBody=React.createElement("div",{className:"cb-dialogCard"},React.createElement("div",{className:"cb-dialogRow"},React.createElement("div",{className:"cb-section-title"},t("analytics.title")),React.createElement(AnalyticsControls,{analyticsDays,setAnalyticsDays,groupBy,setGroupBy,model:analyticsModel,setModel:setAnalyticsModel,account:analyticsAccount,setAccount:setAnalyticsAccount,errorsOnly:analyticsErrorsOnly,setErrorsOnly:setAnalyticsErrorsOnly,from:analyticsFrom,setFrom:setAnalyticsFrom,to:analyticsTo,setTo:setAnalyticsTo,models:status?.availableModels||[],accounts:status?.accounts||[],t})),React.createElement("div",{className:"cb-row",style:{gap:"6px",marginBottom:"8px"}},React.createElement("a",{href:"/dsh-clinebot/stats/export?format=json",download:"clinebot-stats.json",className:"cb-btn cb-btn-link"},t("stats.export_json")),React.createElement("a",{href:"/dsh-clinebot/stats/export",download:"clinebot-stats.csv",className:"cb-btn cb-btn-link"},t("stats.export_csv"))),analytics?.totals?React.createElement("div",{className:"cb-dialogTiles"},React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},String(analytics.totals.requests||0)),React.createElement("div",{className:"cb-stat-lbl"},t("analytics.requests"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},fmtTokens(analytics.totals.totalTokens)),React.createElement("div",{className:"cb-stat-lbl"},t("analytics.tokens"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},`\u2248 ${fmtCost(analytics.totals.costUsd)}`),React.createElement("div",{className:"cb-stat-lbl"},t("analytics.cost"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},`${Math.round(analytics.totals.successRate||0)}%`),React.createElement("div",{className:"cb-stat-lbl"},t("analytics.success")))):null,secondaryTiles.length?React.createElement("div",{className:"cb-dialogTiles"},secondaryTiles.map(([label,value])=>React.createElement("div",{className:"cb-stat-box",key:label},React.createElement("div",{className:"cb-stat-val"},value),React.createElement("div",{className:"cb-stat-lbl"},label)))):null,analyticsLoading?React.createElement("div",{className:"cb-hint"},t("analytics.loading")):analytics?.groups?.length?React.createElement("div",{className:"cb-analytics-list"},React.createElement("div",{className:"cb-analytics-head"},React.createElement("span",null,analytics.groupBy||groupBy),React.createElement("span",null,t("analytics.requests")),React.createElement("span",null,t("analytics.tokens")),React.createElement("span",null,t("analytics.cost")),React.createElement("span",null,t("analytics.ttft"))),analytics.groups.map(group=>React.createElement("div",{className:"cb-analytics-row",key:group.key},React.createElement("span",{className:"cb-mono"},group.label||group.key),React.createElement("span",null,String(group.requests||0)),React.createElement("span",null,fmtTokens(group.totalTokens)),React.createElement("span",null,`\u2248 ${fmtCost(group.costUsd)}`),React.createElement("span",null,group.avgTtftMs?`${Math.round(group.avgTtftMs)} ms`:"\u2014")))):React.createElement("div",{className:"cb-empty"},t("analytics.no_data")),analytics?.unpriced>0?React.createElement("div",{className:"cb-hint"},t("analytics.unpriced",{count:analytics.unpriced})):null);const heatmapCard=React.createElement("div",{className:"cb-dialogCard"},React.createElement("div",{className:"cb-section-title"},t("analytics.heatmap")),React.createElement("div",{className:"cb-section-desc"},t("analytics.heatmap_hint")),React.createElement(HeatmapView,{heatmap:analytics?.heatmap,model:heatmapModel,onModelChange:setHeatmapModel,selected:selectedHeat,onSelect:cell=>setSelectedHeat(current=>current&&current.day===cell.day&&current.hour===cell.hour?null:cell),t}),selectedHeat?React.createElement("div",{className:"cb-hint"},t("analytics.heatmap_selected",{day:selectedHeat.label,hour:String(selectedHeat.hour).padStart(2,"0"),count:selectedHeat.value})):null);const panelBody=React.createElement("div",{className:"cb-panel"},React.createElement("div",{className:"cb-panel-head"},React.createElement("div",{className:"cb-panel-identity"},React.createElement("div",{className:"cb-panel-title"},t("dashboard.title")),React.createElement("div",{className:"cb-panel-sub"},t("dashboard.subtitle"))),React.createElement("div",{className:"cb-panel-actions"},updatedAt?React.createElement("span",{className:"cb-panel-updated"},`${t("dashboard.updated_at")} ${updatedAt}`):null,React.createElement("button",{type:"button",className:"cb-btn",disabled:loading||analyticsLoading,onClick:()=>{loadData();loadAnalytics(groupBy)}},loading||analyticsLoading?t("dashboard.refreshing"):t("dashboard.refresh")),React.createElement("button",{type:"button",className:"cb-panel-close","aria-label":t("dashboard.close"),title:t("dashboard.close"),onClick:onClose},"\xD7"))),notices,errorNotice,quotaBody,analyticsBody,heatmapCard);const body=React.createElement("div",{className:"cb-dialog"},React.createElement("div",{className:"cb-dialogMeta"},updatedAt?React.createElement("span",null,`${t("dashboard.updated_at")} ${updatedAt}`):null,React.createElement("span",{className:"cb-settings-spacer"}),React.createElement("button",{type:"button",className:"cb-btn",disabled:loading||analyticsLoading,onClick:()=>tab==="quota"?loadData():loadAnalytics(groupBy)},loading||analyticsLoading?t("dashboard.refreshing"):t("dashboard.refresh"))),tabs,notices,errorNotice,tab==="quota"?quotaBody:React.createElement(React.Fragment,null,analyticsBody,heatmapCard));if(panel)return panelBody;if(Modal){return React.createElement(Modal,{open:true,onClose,title:t("dashboard.title"),description:t("dashboard.subtitle"),closeLabel:t("dashboard.close"),children:body})}return React.createElement("div",{className:"cb-dialogFallback",role:"dialog","aria-label":t("dashboard.title"),onClick:onClose},React.createElement("div",{className:"cb-dialogFallbackPanel",onClick:event=>event.stopPropagation()},React.createElement("div",{className:"cb-dialogMeta"},React.createElement("span",{className:"cb-row-title"},t("dashboard.title")),React.createElement("span",{className:"cb-settings-spacer"}),React.createElement("button",{type:"button",className:"cb-btn","aria-label":t("dashboard.close"),onClick:onClose},t("dashboard.close"))),body))}
467
467
 
468
468
  // --- src/client/components/composer-cost.js ---
469
- function formatTokens(count){const n=Number(count)||0;if(n>=1e6)return(n/1e6).toFixed(1)+"M";if(n>=1e3)return(n/1e3).toFixed(1)+"k";return String(n)}function findPrice(modelId,prices){if(!modelId||!Array.isArray(prices))return null;const exact=prices.find(entry=>entry&&entry.id===modelId);if(exact)return exact;return prices.find(entry=>entry&&entry.id&&(modelId===entry.id||modelId.endsWith(`/${entry.id}`)||modelId.includes(entry.id)))||null}function calculateCost(tokens,modelId,prices){const pTok=Number(tokens?.promptTokens||tokens?.inputTokens||0);const cTok=Number(tokens?.completionTokens||tokens?.outputTokens||0);const rTok=Number(tokens?.reasoningTokens||0);const cacheRead=Number(tokens?.cacheReadTokens||tokens?.cacheRead||0);const cacheWrite=Number(tokens?.cacheWriteTokens||tokens?.cacheWrite||0);const total=pTok+cTok+rTok;const price=findPrice(modelId,prices);if(!price)return{cost:0,priced:false,source:null,pTok,cTok,rTok,cacheRead,cacheWrite,total};const uncachedInput=Math.max(0,pTok-cacheRead-cacheWrite);const cost=(uncachedInput*Number(price.input||0)+cTok*Number(price.output||0)+cacheRead*Number(price.cacheRead||0)+cacheWrite*Number(price.cacheWrite||0))/1e6;return{cost,priced:true,source:price.source||"estimate",pTok,cTok,rTok,cacheRead,cacheWrite,total}}const COST_ANCHOR='[data-slot="conversation.composer.dock"]';const STATS_ROOT="[data-composer-stats]";const STATS_USAGE='[data-composer-stat="usage"]';const STATS_ANY="[data-composer-stat]";const DIALOG_TRIGGER='button[aria-haspopup="dialog"]';const MISSES_BEFORE_WARNING=3;function ComposerCostDock(props){if(typeof props?.useProjection!=="function")return null;return React.createElement(ComposerCostEntry,props)}function ComposerCostEntry(props){const t=props?.t||makeT(en,en);const configSnapshot=useConfigFormsSnapshot(props?.ctx);const[stats,setStats]=React.useState(null);const[prices,setPrices]=React.useState([]);const[sessionStats,setSessionStats]=React.useState(null);const markerRef=React.useRef(null);const projectedTokens=props.useProjection("tokenUsage");const selection=props.useProjection("modelSelection");const sessionId=String(props?.sessionId||"");const priceOverridesKey=JSON.stringify(configSnapshot?.value?.priceOverrides||{});React.useEffect(()=>{let active=true;fetchWithTimeout(`${ROUTE_PREFIX}/prices`,{cache:"no-store"}).then(response=>response.json()).then(data=>{if(active&&data?.ok&&Array.isArray(data.models))setPrices(data.models)}).catch(()=>{});return()=>{active=false}},[priceOverridesKey]);React.useEffect(()=>{if(!sessionId){setSessionStats(null);return void 0}let active=true;const loadSession=async()=>{try{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/stats/session?sessionId=${encodeURIComponent(sessionId)}`,{cache:"no-store"});const data=await res.json().catch(()=>null);if(active&&data?.ok)setSessionStats(data.session||null)}catch{}};loadSession();let timer=null;const schedule=()=>{timer=setTimeout(async()=>{await loadSession();if(active)schedule()},1e4)};schedule();return()=>{active=false;if(timer)clearTimeout(timer)}},[sessionId,projectedTokens]);React.useEffect(()=>{if(projectedTokens||sessionId)return void 0;let active=true;const loadStats=async()=>{try{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/stats`,{cache:"no-store"});if(!res.ok)return;const data=await res.json().catch(()=>null);const totals=data?.stats?.totals||data?.stats?.summary||null;if(active&&totals)setStats(totals)}catch{}};loadStats();const timer=setTimeout(loadStats,3e4);return()=>{active=false;clearTimeout(timer)}},[projectedTokens,sessionId]);const selected=selection?.lastUsed??selection?.next??(selection?.provider||selection?.model?selection:null);const provider=String(selected?.provider||"");const modelId=String(selected?.model||selected?.id||"");const isClineBot=(provider===""||provider==="clinebot"||provider==="llm-clinebot")&&(provider==="clinebot"||provider==="llm-clinebot"||modelId.startsWith("cline-pass/"));const session=sessionStats&&Number(sessionStats.requests||0)>0?sessionStats:null;const sessionTokens=session?{promptTokens:Number(session.promptTokens||0),completionTokens:Number(session.completionTokens||0),reasoningTokens:Number(session.reasoningTokens||0),cacheReadTokens:Number(session.cacheReadTokens||0),cacheWriteTokens:Number(session.cacheWriteTokens||0)}:null;const rawTokens=sessionTokens||(sessionId?null:projectedTokens||{promptTokens:stats?.promptTokens||0,completionTokens:stats?.completionTokens||0,reasoningTokens:stats?.reasoningTokens||0,cacheReadTokens:stats?.cacheReadTokens||0,cacheWriteTokens:stats?.cacheWriteTokens||0});const cacheRead=Number(rawTokens?.cacheReadTokens||0);const cacheWrite=Number(rawTokens?.cacheWriteTokens||0);const uncachedInput=Number(rawTokens?.uncachedInputTokens);const promptTokens=rawTokens?Number.isFinite(uncachedInput)?uncachedInput+cacheRead+cacheWrite:Number(rawTokens.promptTokens||rawTokens.inputTokens||0):0;const tokens={promptTokens,completionTokens:Number(rawTokens?.completionTokens||rawTokens?.outputTokens||0),reasoningTokens:Number(rawTokens?.reasoningTokens||0),cacheReadTokens:cacheRead,cacheWriteTokens:cacheWrite};const calculated=calculateCost(tokens,session?.model||modelId,prices);const cost=session?Number(session.costUsd||0):calculated.cost;const priced=session?Number(session.pricedRequests||0)>0:calculated.priced;const source=session?findPrice(session.model||modelId,prices)?.source||"estimate":calculated.source;const{pTok,cTok,rTok}=calculated;const total=session?Number(session.totalTokens||pTok+cTok):calculated.total;const last=session?.last||null;const lastCost=last?Number(last.costUsd||0):0;const lastPriced=Boolean(last?.priced);const costValue=priced?cost===0?t("composer.free"):cost<1e-4?"< $0.0001":`$${cost.toFixed(4)}`:t("composer.unpriced");const costDisplay=priced&&source!=="override"&&cost>0?`\u2248 ${costValue}`:costValue;const lastCostValue=last?lastPriced?lastCost===0?t("composer.free"):lastCost<1e-4?"< $0.0001":`$${lastCost.toFixed(4)}`:t("composer.unpriced"):"";const title=[`${t("composer.cost_title")} ${costDisplay}`,...last?[`${t("composer.last_request")} ${lastCostValue}`]:[],`${t("composer.prompt_tok")} ${formatTokens(pTok)}`,`${t("composer.completion_tok")} ${formatTokens(cTok)}`,...rTok>0?[`${t("composer.reasoning_tok")} ${formatTokens(rTok)}`]:[],...cacheRead>0?[`${t("composer.cache_read")} ${formatTokens(cacheRead)}`]:[],...cacheWrite>0?[`${t("composer.cache_write")} ${formatTokens(cacheWrite)}`]:[],...priced?[`${t("composer.price_source")} ${source==="override"?t("composer.price_override"):source==="public"?t("composer.price_public"):t("composer.price_estimate")}`]:[]].join(" \xB7 ");const inject=isClineBot&&priced&&total>0;React.useEffect(()=>{if(!inject||typeof document==="undefined")return void 0;const marker=markerRef.current;if(!marker)return void 0;const scope=marker.closest(COST_ANCHOR);if(!scope)return void 0;let host=null;let root=null;let valueNode=null;let misses=0;const apply=()=>{if(!root||root.parentNode===null){const usageAnchor=scope.querySelector(STATS_USAGE)||scope.querySelector(STATS_ANY);const statsRoot=usageAnchor||scope.querySelector(STATS_ROOT)||scope;const triggers=statsRoot.querySelectorAll(DIALOG_TRIGGER);const button=triggers[triggers.length-1]||usageAnchor;if(!button){misses+=1;if(misses===MISSES_BEFORE_WARNING){console.warn("[dsh-clinebot] no native token-usage pill found for the session cost")}return}misses=0;host=button;root=document.createElement("span");root.setAttribute("data-clinebot-session-cost","");root.style.display="inline-flex";root.style.alignItems="center";const separator=document.createElement("span");separator.textContent="\xB7";separator.setAttribute("aria-hidden","true");separator.style.color="var(--dsw-alias-separator-primary)";separator.style.margin="0 6px 0 0";valueNode=document.createElement("span");valueNode.style.fontWeight="500";root.appendChild(separator);root.appendChild(valueNode);button.appendChild(root)}if(root.parentNode!==host)host.appendChild(root);if(valueNode.textContent!==costDisplay)valueNode.textContent=costDisplay;if(root.title!==title)root.title=title};apply();const observer=typeof window!=="undefined"&&typeof window.MutationObserver==="function"?new window.MutationObserver(apply):null;observer?.observe(document.body,{childList:true});let timer=null;const tick=()=>{apply();timer=setTimeout(tick,2e3)};timer=setTimeout(tick,2e3);return()=>{observer?.disconnect();if(timer)clearTimeout(timer);if(root?.parentNode)root.parentNode.removeChild(root)}},[inject,costDisplay,title]);if(configSnapshot?.value?.showComposerCost===false)return null;if(total===0&&!stats)return null;if(!inject)return null;return React.createElement("span",{ref:markerRef,style:{display:"none"},"data-clinebot-cost-anchor":""})}
469
+ function formatTokens(count){const n=Number(count)||0;if(n>=1e6)return(n/1e6).toFixed(1)+"M";if(n>=1e3)return(n/1e3).toFixed(1)+"k";return String(n)}function findPrice(modelId,prices){if(!modelId||!Array.isArray(prices))return null;const exact=prices.find(entry=>entry&&entry.id===modelId);if(exact)return exact;return prices.find(entry=>entry&&entry.id&&(modelId===entry.id||modelId.endsWith(`/${entry.id}`)||modelId.includes(entry.id)))||null}function calculateCost(tokens,modelId,prices){const pTok=Number(tokens?.promptTokens||tokens?.inputTokens||0);const cTok=Number(tokens?.completionTokens||tokens?.outputTokens||0);const rTok=Number(tokens?.reasoningTokens||0);const cacheRead=Number(tokens?.cacheReadTokens||tokens?.cacheRead||0);const cacheWrite=Number(tokens?.cacheWriteTokens||tokens?.cacheWrite||0);const total=pTok+cTok+rTok;const price=findPrice(modelId,prices);if(!price)return{cost:0,priced:false,source:null,pTok,cTok,rTok,cacheRead,cacheWrite,total};const uncachedInput=Math.max(0,pTok-cacheRead-cacheWrite);const cost=(uncachedInput*Number(price.input||0)+cTok*Number(price.output||0)+cacheRead*Number(price.cacheRead||0)+cacheWrite*Number(price.cacheWrite||0))/1e6;return{cost,priced:true,source:price.source||"estimate",pTok,cTok,rTok,cacheRead,cacheWrite,total}}const COST_ANCHOR='[data-slot="conversation.composer.dock"]';const STATS_ROOT="[data-composer-stats]";const STATS_USAGE='[data-composer-stat="usage"]';const STATS_ANY="[data-composer-stat]";const DIALOG_TRIGGER='button[aria-haspopup="dialog"]';const MISSES_BEFORE_WARNING=3;function ComposerCostDock(props){if(typeof props?.useProjection!=="function")return null;return React.createElement(ComposerCostEntry,props)}function ComposerCostEntry(props){const t=props?.t||makeT(en,en);const configSnapshot=useConfigFormsSnapshot(props?.ctx);const[stats,setStats]=React.useState(null);const[prices,setPrices]=React.useState([]);const[sessionStats,setSessionStats]=React.useState(null);const markerRef=React.useRef(null);const projectedTokens=props.useProjection("tokenUsage");const selection=props.useProjection("modelSelection");const sessionId=String(props?.sessionId||"");const priceOverridesKey=JSON.stringify(configSnapshot?.value?.priceOverrides||{});React.useEffect(()=>{let active=true;fetchWithTimeout(`${ROUTE_PREFIX}/prices`,{cache:"no-store"}).then(response=>response.json()).then(data=>{if(active&&data?.ok&&Array.isArray(data.models))setPrices(data.models)}).catch(()=>{});return()=>{active=false}},[priceOverridesKey]);React.useEffect(()=>{if(!sessionId){setSessionStats(null);return void 0}let active=true;const loadSession=async()=>{try{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/stats/session?sessionId=${encodeURIComponent(sessionId)}`,{cache:"no-store"});const data=await res.json().catch(()=>null);if(active&&data?.ok)setSessionStats(data.session||null)}catch{}};loadSession();let timer=null;const schedule=()=>{timer=setTimeout(async()=>{await loadSession();if(active)schedule()},1e4)};schedule();return()=>{active=false;if(timer)clearTimeout(timer)}},[sessionId,projectedTokens]);React.useEffect(()=>{if(projectedTokens||sessionId)return void 0;let active=true;const loadStats=async()=>{try{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/stats`,{cache:"no-store"});if(!res.ok)return;const data=await res.json().catch(()=>null);const totals=data?.stats?.totals||data?.stats?.summary||null;if(active&&totals)setStats(totals)}catch{}};loadStats();const timer=setTimeout(loadStats,3e4);return()=>{active=false;clearTimeout(timer)}},[projectedTokens,sessionId]);const selected=selection?.lastUsed??selection?.next??(selection?.provider||selection?.model?selection:null);const provider=String(selected?.provider||"");const modelId=String(selected?.model||selected?.id||"");const isClineBot=(provider===""||provider==="clinebot"||provider==="llm-clinebot")&&(provider==="clinebot"||provider==="llm-clinebot"||modelId.startsWith("cline-pass/"));const session=sessionStats&&Number(sessionStats.requests||0)>0?sessionStats:null;const sessionTokens=session?{promptTokens:Number(session.promptTokens||0),completionTokens:Number(session.completionTokens||0),reasoningTokens:Number(session.reasoningTokens||0),cacheReadTokens:Number(session.cacheReadTokens||0),cacheWriteTokens:Number(session.cacheWriteTokens||0)}:null;const rawTokens=sessionTokens||(sessionId?null:projectedTokens||{promptTokens:stats?.promptTokens||0,completionTokens:stats?.completionTokens||0,reasoningTokens:stats?.reasoningTokens||0,cacheReadTokens:stats?.cacheReadTokens||0,cacheWriteTokens:stats?.cacheWriteTokens||0});const cacheRead=Number(rawTokens?.cacheReadTokens||0);const cacheWrite=Number(rawTokens?.cacheWriteTokens||0);const uncachedInput=Number(rawTokens?.uncachedInputTokens);const promptTokens=rawTokens?Number.isFinite(uncachedInput)?uncachedInput+cacheRead+cacheWrite:Number(rawTokens.promptTokens||rawTokens.inputTokens||0):0;const tokens={promptTokens,completionTokens:Number(rawTokens?.completionTokens||rawTokens?.outputTokens||0),reasoningTokens:Number(rawTokens?.reasoningTokens||0),cacheReadTokens:cacheRead,cacheWriteTokens:cacheWrite};const calculated=calculateCost(tokens,session?.model||modelId,prices);const cost=session?Number(session.costUsd||0):calculated.cost;const priced=session?Number(session.pricedRequests||0)>0:calculated.priced;const source=session?findPrice(session.model||modelId,prices)?.source||"estimate":calculated.source;const{pTok,cTok,rTok}=calculated;const total=session?Number(session.totalTokens||pTok+cTok):calculated.total;const last=session?.last||null;const lastCost=last?Number(last.costUsd||0):0;const lastPriced=Boolean(last?.priced);const costValue=priced?cost===0?t("composer.free"):cost<1e-4?"< $0.0001":`$${cost.toFixed(4)}`:t("composer.unpriced");const costDisplay=priced&&source!=="override"&&cost>0?`\u2248 ${costValue}`:costValue;const lastCostValue=last?lastPriced?lastCost===0?t("composer.free"):lastCost<1e-4?"< $0.0001":`$${lastCost.toFixed(4)}`:t("composer.unpriced"):"";const title=[`${t("composer.cost_title")} ${costDisplay}`,...last?[`${t("composer.last_request")} ${lastCostValue}`]:[],`${t("composer.prompt_tok")} ${formatTokens(pTok)}`,`${t("composer.completion_tok")} ${formatTokens(cTok)}`,...rTok>0?[`${t("composer.reasoning_tok")} ${formatTokens(rTok)}`]:[],...cacheRead>0?[`${t("composer.cache_read")} ${formatTokens(cacheRead)}`]:[],...cacheWrite>0?[`${t("composer.cache_write")} ${formatTokens(cacheWrite)}`]:[],...priced?[`${t("composer.price_source")} ${source==="override"?t("composer.price_override"):source==="public"?t("composer.price_public"):t("composer.price_estimate")}`]:[]].join(" \xB7 ");const inject=isClineBot&&priced&&total>0;React.useEffect(()=>{if(!inject||typeof document==="undefined")return void 0;const marker=markerRef.current;if(!marker)return void 0;const scope=marker.closest(COST_ANCHOR);if(!scope)return void 0;let host=null;let root=null;let valueNode=null;let misses=0;const apply=()=>{if(!root||root.parentNode===null){const usageAnchor=scope.querySelector(STATS_USAGE)||scope.querySelector(STATS_ANY);const statsRoot=usageAnchor||scope.querySelector(STATS_ROOT)||scope;const triggers=statsRoot.querySelectorAll(DIALOG_TRIGGER);const button=triggers[triggers.length-1]||usageAnchor;if(!button){misses+=1;if(misses===MISSES_BEFORE_WARNING){console.warn("[dsh-clinebot] no native token-usage pill found for the session cost")}return}misses=0;host=button;root=document.createElement("span");root.setAttribute("data-clinebot-session-cost","");root.style.display="inline-flex";root.style.alignItems="center";const separator=document.createElement("span");separator.textContent="\xB7";separator.setAttribute("aria-hidden","true");separator.style.color="var(--dsw-alias-separator-primary)";separator.style.margin="0 6px 0 0";valueNode=document.createElement("span");valueNode.style.fontWeight="500";root.appendChild(separator);root.appendChild(valueNode);button.appendChild(root)}if(root.parentNode!==host)host.appendChild(root);if(valueNode.textContent!==costDisplay)valueNode.textContent=costDisplay;if(root.title!==title)root.title=title};apply();const observer=typeof window!=="undefined"&&typeof window.MutationObserver==="function"?new window.MutationObserver(apply):null;observer?.observe(document.body,{childList:true});let timer=null;const tick=()=>{apply();timer=setTimeout(tick,2e3)};timer=setTimeout(tick,2e3);return()=>{observer?.disconnect();if(timer)clearTimeout(timer);if(root?.parentNode)root.parentNode.removeChild(root)}},[inject,costDisplay,title]);if(!configSnapshot?.value?.showComposerCost)return null;if(total===0&&!stats)return null;if(!inject)return null;return React.createElement("span",{ref:markerRef,style:{display:"none"},"data-clinebot-cost-anchor":""})}
470
470
 
471
471
  // --- src/client/settings-page.js ---
472
- function SettingsPage(props){const ctx=props?.ctx;const t=props?.t||makeT(en,en);const configSnapshot=useConfigFormsSnapshot(ctx);const[status,setStatus]=React.useState(null);const[draft,setDraft]=React.useState(null);const[busy,setBusy]=React.useState("");const[err,setErr]=React.useState(""),[msg,setMsg]=React.useState("");const[smokeResult,setSmokeResult]=React.useState(null);const[updateState,setUpdateState]=React.useState({status:"idle",checking:false,updating:false,currentVersion:"",latestVersion:"",updateAvailable:false,canAutoUpdate:true,latestCheckFailed:false,lastCheckedAt:null,error:"",notice:""});const[apiKeyInput,setApiKeyInput]=React.useState(""),[showKey,setShowKey]=React.useState(false);React.useEffect(()=>{ensureCss()},[]);const pendingRef=React.useRef({});const load=React.useCallback(async()=>{setErr("");const[res,statsRes]=await Promise.all([fetchWithTimeout(`${ROUTE_PREFIX}/status`,{cache:"no-store"}),fetchWithTimeout(`${ROUTE_PREFIX}/stats`,{cache:"no-store"}).catch(()=>null)]);const data=await res.json().catch(()=>({}));if(!res.ok)throw new Error(data.error||`HTTP ${res.status}`);const rawStats=statsRes&&statsRes.ok?await statsRes.json().catch(()=>null):null;const statsObj=rawStats&&typeof rawStats==="object"&&rawStats.stats?rawStats.stats:rawStats||data.statsSummary||{};setStatus({...data,statsSummary:statsObj});setDraft({...data.config||{},...pendingRef.current||{}})},[]);React.useEffect(()=>{load().catch(e=>setErr(String(e.message||e)))},[load]);const staged=useStagedConfig({status,draft,setDraft,load,t,pendingRef});const modelSelection=useModelSelection({status,draft,setDraft,load,setErr});const checkUpdate=React.useCallback(async()=>{setUpdateState(s=>({...s,checking:true,status:"checking",error:""}));try{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/update`);if(!res.ok)throw new Error(`HTTP ${res.status}`);const data=await res.json().catch(()=>({}));if(data.latestCheckFailed){setUpdateState(s=>({...s,checking:false,status:"error",latestCheckFailed:true,currentVersion:data.currentVersion||s.currentVersion,error:t("update.check_failed")}));return}const isAvailable=Boolean(data.updateAvailable);setUpdateState(s=>({...s,checking:false,status:isAvailable?"available":"current",currentVersion:data.currentVersion||s.currentVersion,latestVersion:data.latestVersion||"",updateAvailable:isAvailable,latestCheckFailed:false,lastCheckedAt:Date.now(),canAutoUpdate:data.canAutoUpdate!==false,error:""}))}catch(err2){setUpdateState(s=>({...s,checking:false,status:"error",latestCheckFailed:true,error:String(err2?.message||err2||t("update.check_failed"))}))}},[t]);React.useEffect(()=>{checkUpdate()},[checkUpdate]);async function handleTriggerUpdate(){if(updateState.updating)return;setUpdateState(s=>({...s,updating:true,error:"",notice:""}));try{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/update`,{method:"POST",headers:{"x-dsh-plugin-update":"1"}});const data=await res.json().catch(()=>({}));if(!res.ok||data.ok===false||data.error)throw new Error(data.error||`HTTP ${res.status}`);const newVer=data.updatedVersion||updateState.latestVersion||updateState.currentVersion;setUpdateState(s=>({...s,updating:false,status:"current",updateAvailable:false,currentVersion:newVer,lastCheckedAt:Date.now(),notice:t("update.done",{version:newVer})}));setTimeout(()=>checkUpdate(),2e3)}catch(err2){setUpdateState(s=>({...s,updating:false,status:s.updateAvailable?"available":"error",error:t("update.failed",{error:String(err2?.message||err2)})}))}}async function performAction(busyKey,fn){setBusy(busyKey);setErr("");setMsg("");try{await fn()}catch(e){setErr(String(e.message||e))}finally{setBusy("")}}const{handlePinAccount,handleAddAccount,handleDeleteAccount,handleRenameAccount,handleClearKey}=createAccountActions({performAction,load,t,setMsg});async function handleSaveKey(){const keyVal=String(apiKeyInput||"").trim();if(!keyVal){setErr(t("key.empty_err"));return}await performAction("save-key",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/save-key`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({apiKey:keyVal,apiKeyEnv:draft?.apiKeyEnv})});const data=await res.json().catch(()=>({}));if(!res.ok||!data.ok)throw new Error(data.error||`HTTP ${res.status}`);setApiKeyInput("");setMsg(t("key.saved_msg",{status:data.validated?"OK":"Notice (check console)"}));await load()})}async function handleRefreshQuota(){await performAction("refresh-quota",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/usage`);const data=await res.json().catch(()=>({}));if(!res.ok||!data.ok)throw new Error(data.error||`HTTP ${res.status}`);setStatus(prev=>prev?{...prev,usage:data}:prev);setMsg(t("quota.refreshed_msg"))})}async function handleUpdateModelContext(payload){await performAction("update-context",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/models/context`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload)});const data=await res.json().catch(()=>({}));if(!res.ok||!data.ok)throw new Error(data.error||`HTTP ${res.status}`);setMsg(t("models.context_updated_msg"));await load()})}async function handleSyncPlanModels(){await performAction("sync-models",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/models/sync`,{method:"POST"});const data=await res.json().catch(()=>({}));if(!res.ok||!data.ok)throw new Error(data.error||`HTTP ${res.status}`);setMsg(t("models.synced_msg",{total:data.totalModelsCount,discovered:data.discoveredCount}));await load()})}async function handleApplyVisible(payload){await performAction("visible-models",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/models/visible`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload||{})});const data=await res.json().catch(()=>({}));if(!res.ok||data.ok===false)throw new Error(data.error||`HTTP ${res.status}`);await load();setMsg(t("models.visible_applied"))})}async function handleSmoke(){setSmokeResult(null);await performAction("smoke",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/smoke`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:draft?.defaultModel})});const data=await res.json().catch(()=>({}));if(!res.ok||!data.ok)throw new Error(data.error||`HTTP ${res.status}`);setSmokeResult(data)})}const[verifyStatus,setVerifyStatus]=React.useState({state:"idle",email:"",plan:"",error:""});const verifyTimerRef=typeof React.useRef==="function"?React.useRef(null):{current:null};React.useEffect(()=>{const raw=String(apiKeyInput||"").trim();if(!raw||raw.length<10){setVerifyStatus({state:"idle",email:"",plan:"",error:""});return}if(verifyTimerRef.current)clearTimeout(verifyTimerRef.current);verifyTimerRef.current=setTimeout(async()=>{setVerifyStatus({state:"verifying",email:"",plan:"",error:""});try{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/key/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:raw})});const data=await res.json().catch(()=>({}));if(data.ok&&data.valid){setVerifyStatus({state:"valid",email:data.email,plan:data.plan,error:""})}else{setVerifyStatus({state:"invalid",email:"",plan:"",error:data.error||"Verification failed"})}}catch(err2){setVerifyStatus({state:"invalid",email:"",plan:"",error:String(err2?.message||err2)})}},500);return()=>{if(verifyTimerRef.current)clearTimeout(verifyTimerRef.current)}},[apiKeyInput]);function handleFastLogin(){window.open("https://app.cline.bot/settings/api-keys","_blank")}async function handleConfigPatch(patch){setDraft(d=>({...d||{},...patch||{}}));await performAction("patch-config",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/config`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({config:patch})});const data=await res.json().catch(()=>({}));if(!res.ok||data.ok===false)throw new Error(data.error||`HTTP ${res.status}`);await load()})}async function handleResetStats(){await performAction("reset-stats",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/stats/reset`,{method:"POST"});const data=await res.json().catch(()=>({}));if(!res.ok||!data.ok)throw new Error(data.error||`HTTP ${res.status}`);await load()})}if(!status||!draft){if(err){return React.createElement("div",{className:"cb-page"},React.createElement("div",{className:"cb-alert cb-alert-err"},`${t("settings.loading")}: ${err}`),React.createElement("button",{type:"button",className:"cb-btn",style:{marginTop:"12px"},onClick:()=>{setErr("");load().catch(e=>setErr(String(e.message||e)))}},t("settings.retry")))}return React.createElement("div",{className:"cb-page"},t("settings.loading"))}const healthOk=!!status.health?.ok;const keyPresent=!!status.key?.present;const isRegistered=!!status.isRegistered;const modelsList=status.availableModels||[];const disabledSet=new Set(draft.disabledModels||[]);const enabledCount=modelsList.filter(m=>!disabledSet.has(m.id)).length;const usage=status.usage;return React.createElement("div",{className:"cb-page"},React.createElement("div",{className:"cb-header"},React.createElement("div",{className:"cb-page-title"},t("header.title"),React.createElement("span",{className:`cb-badge ${healthOk?"cb-badge-ok":"cb-badge-bad"}`},healthOk?t("badge.online",{latency:status.health?.latencyMs}):t("badge.offline")),React.createElement("span",{className:`cb-badge ${keyPresent?"cb-badge-ok":"cb-badge-warn"}`},keyPresent?t("badge.key_ok",{source:status.key?.source}):t("badge.key_missing")),React.createElement("span",{className:`cb-badge ${isRegistered?"cb-badge-ok":"cb-badge-warn"}`},isRegistered?t("badge.registered",{count:enabledCount}):t("badge.not_registered"))),React.createElement("div",{className:"cb-page-sub"},t("header.sub"))),React.createElement(UpdateBanner,{updateState,handleTriggerUpdate,handleCheckUpdate:checkUpdate,t}),configSnapshot?.status==="unavailable"?React.createElement("div",{className:"cb-alert-bad",style:{marginBottom:"12px"}},t("settings.unavailable")):null,err?React.createElement("div",{className:"cb-alert-bad"},err):null,msg?React.createElement("div",{className:"cb-alert-ok"},msg):null,React.createElement(KeySection,{keyPresent,apiKeyInput,setApiKeyInput,showKey,setShowKey,busy,draft,handleSaveKey,handleFastLogin,verifyStatus,t}),React.createElement(AccountsSection,{status,busy,handlePinAccount,handleAddAccount,handleDeleteAccount,handleRenameAccount,handleClearKey,accountMode:draft?.accountMode||status?.config?.accountMode||"least_used",handleUpdateAccountMode:mode=>handleConfigPatch({accountMode:mode}),modelRoutes:draft?.modelRoutes||status?.config?.modelRoutes||{},onUpdateModelRoutes:routes=>handleConfigPatch({modelRoutes:routes}),t}),React.createElement(QuotaSection,{keyPresent,status,usage,busy,handleRefreshQuota,showSidebarQuota:draft?.showSidebarQuota!==false,onToggleSidebarQuota:value=>staged.stage({showSidebarQuota:value}),t}),React.createElement(CollapsibleGroup,{title:t("settings.models_group"),description:t("settings.models_group_hint"),badge:`${enabledCount}/${modelsList.length}`,divided:true,defaultOpen:false},React.createElement(ModelsSection,{modelsList,disabledSet,enabledCount,keyPresent,busy,handleSyncPlanModels,handleUpdateModelContext,handleSetModelsFilter:modelSelection.handleSetModelsFilter,handleToggleModel:modelSelection.handleToggleModel,planSynced:!!(status?.config?.planSynced||status?.config?.dynamicModels&&status.config.dynamicModels.length>0),planSyncedAt:status?.config?.planSyncedAt||0,defaultModelWarning:status?.config?.defaultModelWarning||"",customModels:draft?.customModels||[],modelReasoningDefaults:draft?.modelReasoningDefaults||{},visibleModels:draft?.visibleModels||[],filterModelsByPlan:draft?.filterModelsByPlan!==false,onConfigUpdate:staged.stage,onApplyVisible:handleApplyVisible,onUpdateReasoningEffort:(modelId,effort)=>{const next={...draft?.modelReasoningDefaults||{}};if(!effort)delete next[modelId];else next[modelId]=effort;staged.stage({modelReasoningDefaults:next})},t}),React.createElement(PriceSection,{modelsList,priceOverrides:draft?.priceOverrides||{},priceAutoSync:draft?.priceAutoSync!==false,onConfigUpdate:staged.stage,t})),React.createElement(StatsSection,{status,onResetStats:handleResetStats,busy,t}),React.createElement(IntegrationsSection,{draft,onStage:staged.stage,t}),React.createElement(DiagSection,{keyPresent,busy,smokeResult,handleSmoke,t}),React.createElement(AdvancedSection,{draft,onStage:staged.stage,t}),React.createElement(SaveBar,{pendingCount:staged.pendingCount,saving:staged.saving,saveState:staged.saveState,saveError:staged.saveError,invalid:staged.invalid,validationError:staged.validationError,onSave:staged.save,onDiscard:staged.discard,t}))}
472
+ function SettingsPage(props){const ctx=props?.ctx;const t=props?.t||makeT(en,en);const configSnapshot=useConfigFormsSnapshot(ctx);const[status,setStatus]=React.useState(null);const[draft,setDraft]=React.useState(null);const[busy,setBusy]=React.useState("");const[err,setErr]=React.useState(""),[msg,setMsg]=React.useState("");const[smokeResult,setSmokeResult]=React.useState(null);const[updateState,setUpdateState]=React.useState({status:"idle",checking:false,updating:false,currentVersion:"",latestVersion:"",updateAvailable:false,canAutoUpdate:true,latestCheckFailed:false,lastCheckedAt:null,error:"",notice:""});const[apiKeyInput,setApiKeyInput]=React.useState(""),[showKey,setShowKey]=React.useState(false);React.useEffect(()=>{ensureCss()},[]);const pendingRef=React.useRef({});const load=React.useCallback(async()=>{setErr("");const[res,statsRes]=await Promise.all([fetchWithTimeout(`${ROUTE_PREFIX}/status`,{cache:"no-store"}),fetchWithTimeout(`${ROUTE_PREFIX}/stats`,{cache:"no-store"}).catch(()=>null)]);const data=await res.json().catch(()=>({}));if(!res.ok)throw new Error(data.error||`HTTP ${res.status}`);const rawStats=statsRes&&statsRes.ok?await statsRes.json().catch(()=>null):null;const statsObj=rawStats&&typeof rawStats==="object"&&rawStats.stats?rawStats.stats:rawStats||data.statsSummary||{};setStatus({...data,statsSummary:statsObj});setDraft({...data.config||{},...pendingRef.current||{}})},[]);React.useEffect(()=>{load().catch(e=>setErr(String(e.message||e)))},[load]);const staged=useStagedConfig({status,draft,setDraft,load,t,pendingRef});const modelSelection=useModelSelection({status,draft,setDraft,load,setErr});const checkUpdate=React.useCallback(async()=>{setUpdateState(s=>({...s,checking:true,status:"checking",error:""}));try{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/update`);if(!res.ok)throw new Error(`HTTP ${res.status}`);const data=await res.json().catch(()=>({}));if(data.latestCheckFailed){setUpdateState(s=>({...s,checking:false,status:"error",latestCheckFailed:true,currentVersion:data.currentVersion||s.currentVersion,error:t("update.check_failed")}));return}const isAvailable=Boolean(data.updateAvailable);setUpdateState(s=>({...s,checking:false,status:isAvailable?"available":"current",currentVersion:data.currentVersion||s.currentVersion,latestVersion:data.latestVersion||"",updateAvailable:isAvailable,latestCheckFailed:false,lastCheckedAt:Date.now(),canAutoUpdate:data.canAutoUpdate!==false,error:""}))}catch(err2){setUpdateState(s=>({...s,checking:false,status:"error",latestCheckFailed:true,error:String(err2?.message||err2||t("update.check_failed"))}))}},[t]);React.useEffect(()=>{checkUpdate()},[checkUpdate]);async function handleTriggerUpdate(){if(updateState.updating)return;setUpdateState(s=>({...s,updating:true,error:"",notice:""}));try{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/update`,{method:"POST",headers:{"x-dsh-plugin-update":"1"}});const data=await res.json().catch(()=>({}));if(!res.ok||data.ok===false||data.error)throw new Error(data.error||`HTTP ${res.status}`);const newVer=data.updatedVersion||updateState.latestVersion||updateState.currentVersion;setUpdateState(s=>({...s,updating:false,status:"current",updateAvailable:false,currentVersion:newVer,lastCheckedAt:Date.now(),notice:t("update.done",{version:newVer})}));setTimeout(()=>checkUpdate(),2e3)}catch(err2){setUpdateState(s=>({...s,updating:false,status:s.updateAvailable?"available":"error",error:t("update.failed",{error:String(err2?.message||err2)})}))}}async function performAction(busyKey,fn){setBusy(busyKey);setErr("");setMsg("");try{await fn()}catch(e){setErr(String(e.message||e))}finally{setBusy("")}}const{handlePinAccount,handleAddAccount,handleDeleteAccount,handleRenameAccount,handleClearKey}=createAccountActions({performAction,load,t,setMsg});async function handleSaveKey(){const keyVal=String(apiKeyInput||"").trim();if(!keyVal){setErr(t("key.empty_err"));return}await performAction("save-key",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/save-key`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({apiKey:keyVal,apiKeyEnv:draft?.apiKeyEnv})});const data=await res.json().catch(()=>({}));if(!res.ok||!data.ok)throw new Error(data.error||`HTTP ${res.status}`);setApiKeyInput("");setMsg(t("key.saved_msg",{status:data.validated?"OK":"Notice (check console)"}));await load()})}async function handleRefreshQuota(){await performAction("refresh-quota",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/usage`);const data=await res.json().catch(()=>({}));if(!res.ok||!data.ok)throw new Error(data.error||`HTTP ${res.status}`);setStatus(prev=>prev?{...prev,usage:data}:prev);setMsg(t("quota.refreshed_msg"))})}async function handleUpdateModelContext(payload){await performAction("update-context",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/models/context`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload)});const data=await res.json().catch(()=>({}));if(!res.ok||!data.ok)throw new Error(data.error||`HTTP ${res.status}`);setMsg(t("models.context_updated_msg"));await load()})}async function handleSyncPlanModels(){await performAction("sync-models",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/models/sync`,{method:"POST"});const data=await res.json().catch(()=>({}));if(!res.ok||!data.ok)throw new Error(data.error||`HTTP ${res.status}`);setMsg(t("models.synced_msg",{total:data.totalModelsCount,discovered:data.discoveredCount}));await load()})}async function handleApplyVisible(payload){await performAction("visible-models",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/models/visible`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload||{})});const data=await res.json().catch(()=>({}));if(!res.ok||data.ok===false)throw new Error(data.error||`HTTP ${res.status}`);await load();setMsg(t("models.visible_applied"))})}async function handleSmoke(){setSmokeResult(null);await performAction("smoke",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/smoke`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:draft?.defaultModel})});const data=await res.json().catch(()=>({}));if(!res.ok||!data.ok)throw new Error(data.error||`HTTP ${res.status}`);setSmokeResult(data)})}const[verifyStatus,setVerifyStatus]=React.useState({state:"idle",email:"",plan:"",error:""});const verifyTimerRef=typeof React.useRef==="function"?React.useRef(null):{current:null};React.useEffect(()=>{const raw=String(apiKeyInput||"").trim();if(!raw||raw.length<10){setVerifyStatus({state:"idle",email:"",plan:"",error:""});return}if(verifyTimerRef.current)clearTimeout(verifyTimerRef.current);verifyTimerRef.current=setTimeout(async()=>{setVerifyStatus({state:"verifying",email:"",plan:"",error:""});try{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/key/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:raw})});const data=await res.json().catch(()=>({}));if(data.ok&&data.valid){setVerifyStatus({state:"valid",email:data.email,plan:data.plan,error:""})}else{setVerifyStatus({state:"invalid",email:"",plan:"",error:data.error||"Verification failed"})}}catch(err2){setVerifyStatus({state:"invalid",email:"",plan:"",error:String(err2?.message||err2)})}},500);return()=>{if(verifyTimerRef.current)clearTimeout(verifyTimerRef.current)}},[apiKeyInput]);function handleFastLogin(){window.open("https://app.cline.bot/settings/api-keys","_blank")}async function handleConfigPatch(patch){setDraft(d=>({...d||{},...patch||{}}));await performAction("patch-config",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/config`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({config:patch})});const data=await res.json().catch(()=>({}));if(!res.ok||data.ok===false)throw new Error(data.error||`HTTP ${res.status}`);await load()})}async function handleResetStats(){await performAction("reset-stats",async()=>{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/stats/reset`,{method:"POST"});const data=await res.json().catch(()=>({}));if(!res.ok||!data.ok)throw new Error(data.error||`HTTP ${res.status}`);await load()})}if(!status||!draft){if(err){return React.createElement("div",{className:"cb-page"},React.createElement("div",{className:"cb-alert cb-alert-err"},`${t("settings.loading")}: ${err}`),React.createElement("button",{type:"button",className:"cb-btn",style:{marginTop:"12px"},onClick:()=>{setErr("");load().catch(e=>setErr(String(e.message||e)))}},t("settings.retry")))}return React.createElement("div",{className:"cb-page"},t("settings.loading"))}const healthOk=!!status.health?.ok;const keyPresent=!!status.key?.present;const isRegistered=!!status.isRegistered;const modelsList=status.availableModels||[];const disabledSet=new Set(draft.disabledModels||[]);const enabledCount=modelsList.filter(m=>!disabledSet.has(m.id)).length;const usage=status.usage;return React.createElement("div",{className:"cb-page"},React.createElement("div",{className:"cb-header"},React.createElement("div",{className:"cb-page-title"},t("header.title"),React.createElement("span",{className:`cb-badge ${healthOk?"cb-badge-ok":"cb-badge-bad"}`},healthOk?t("badge.online",{latency:status.health?.latencyMs}):t("badge.offline")),React.createElement("span",{className:`cb-badge ${keyPresent?"cb-badge-ok":"cb-badge-warn"}`},keyPresent?t("badge.key_ok",{source:status.key?.source}):t("badge.key_missing")),React.createElement("span",{className:`cb-badge ${isRegistered?"cb-badge-ok":"cb-badge-warn"}`},isRegistered?t("badge.registered",{count:enabledCount}):t("badge.not_registered"))),React.createElement("div",{className:"cb-page-sub"},t("header.sub"))),React.createElement(UpdateBanner,{updateState,handleTriggerUpdate,handleCheckUpdate:checkUpdate,t}),configSnapshot?.status==="unavailable"?React.createElement("div",{className:"cb-alert-bad",style:{marginBottom:"12px"}},t("settings.unavailable")):null,err?React.createElement("div",{className:"cb-alert-bad"},err):null,msg?React.createElement("div",{className:"cb-alert-ok"},msg):null,React.createElement(KeySection,{keyPresent,apiKeyInput,setApiKeyInput,showKey,setShowKey,busy,draft,handleSaveKey,handleFastLogin,verifyStatus,t}),React.createElement(AccountsSection,{status,busy,handlePinAccount,handleAddAccount,handleDeleteAccount,handleRenameAccount,handleClearKey,accountMode:draft?.accountMode||status?.config?.accountMode||"least_used",handleUpdateAccountMode:mode=>handleConfigPatch({accountMode:mode}),modelRoutes:draft?.modelRoutes||status?.config?.modelRoutes||{},onUpdateModelRoutes:routes=>handleConfigPatch({modelRoutes:routes}),t}),React.createElement(QuotaSection,{keyPresent,status,usage,busy,handleRefreshQuota,showSidebarQuota:Boolean(draft?.showSidebarQuota),onToggleSidebarQuota:value=>staged.stage({showSidebarQuota:value}),t}),React.createElement(CollapsibleGroup,{title:t("settings.models_group"),description:t("settings.models_group_hint"),badge:`${enabledCount}/${modelsList.length}`,divided:true,defaultOpen:false},React.createElement(ModelsSection,{modelsList,disabledSet,enabledCount,keyPresent,busy,handleSyncPlanModels,handleUpdateModelContext,handleSetModelsFilter:modelSelection.handleSetModelsFilter,handleToggleModel:modelSelection.handleToggleModel,planSynced:!!(status?.config?.planSynced||status?.config?.dynamicModels&&status.config.dynamicModels.length>0),planSyncedAt:status?.config?.planSyncedAt||0,defaultModelWarning:status?.config?.defaultModelWarning||"",customModels:draft?.customModels||[],modelReasoningDefaults:draft?.modelReasoningDefaults||{},visibleModels:draft?.visibleModels||[],filterModelsByPlan:draft?.filterModelsByPlan!==false,onConfigUpdate:staged.stage,onApplyVisible:handleApplyVisible,onUpdateReasoningEffort:(modelId,effort)=>{const next={...draft?.modelReasoningDefaults||{}};if(!effort)delete next[modelId];else next[modelId]=effort;staged.stage({modelReasoningDefaults:next})},t}),React.createElement(PriceSection,{modelsList,priceOverrides:draft?.priceOverrides||{},priceAutoSync:draft?.priceAutoSync!==false,onConfigUpdate:staged.stage,t})),React.createElement(StatsSection,{status,onResetStats:handleResetStats,busy,t}),React.createElement(IntegrationsSection,{draft,onStage:staged.stage,t}),React.createElement(DiagSection,{keyPresent,busy,smokeResult,handleSmoke,t}),React.createElement(AdvancedSection,{draft,onStage:staged.stage,t}),React.createElement(SaveBar,{pendingCount:staged.pendingCount,saving:staged.saving,saveState:staged.saveState,saveError:staged.saveError,invalid:staged.invalid,validationError:staged.validationError,onSave:staged.save,onDiscard:staged.discard,t}))}
473
473
 
474
474
  // --- src/client/entry.js ---
475
475
  function ensureSettingsForm(ctx){const svc=readConfigForms(ctx);if(!svc||typeof svc.get!=="function")return;try{svc.get(NS)}catch(err){console.warn("[dsh-clinebot] configForms.get failed:",err)}}function apply(ctx){ensureCss();const addLocale=(locale,dictionary)=>{try{return ctx.locale.register(NS,locale,dictionary)}catch(err){console.warn(`[dsh-clinebot] Failed to register locale dictionary for ns "${NS}", locale "${locale}":`,err);return()=>{}}};if(ctx.locale&&ctx.locale.register){if(typeof ctx.effect==="function"){ctx.effect(()=>{const undo=[addLocale("en",en),addLocale("zh",zh)];return()=>{for(const off of undo)off()}},"dsh-clinebot: dictionaries")}else{addLocale("en",en);addLocale("zh",zh)}}if(typeof ctx.effect==="function"){ctx.effect(()=>{ensureSettingsForm(ctx)},"dsh-clinebot: open the settings form for this namespace")}function registerSlotWhenReady(slotName,registerFn){if(!ctx.slots)return;if(typeof ctx.slots.inject==="function"){try{ctx.slots.inject(slotName,()=>{try{return registerFn()}catch(err){console.warn("[dsh-clinebot] Error registering slot "+slotName+":",err)}});return}catch(err){console.warn("[dsh-clinebot] Failed to inject slot "+slotName+":",err)}}if(typeof ctx.slots.register==="function"){try{registerFn()}catch(err){console.warn("[dsh-clinebot] Failed direct registration for "+slotName+":",err)}}}function selectLayoutPanel(id){try{const layout=typeof ctx.get==="function"?ctx.get("layout"):ctx.layout;if(!layout||typeof layout.selectPanel!=="function")return false;layout.selectPanel(id);return true}catch(err){console.warn("[dsh-clinebot] layout.selectPanel failed:",err);return false}}const openPanel=()=>selectLayoutPanel("clinebot-panel");const closePanel=()=>{if(selectLayoutPanel(null))return;selectLayoutPanel("conversation")};registerSlotWhenReady("settings.section",()=>ctx.slots.register({name:"settings.section",id:NS,order:30,label:()=>{try{const t=ctx.locale&&typeof ctx.locale.bind==="function"?ctx.locale.bind(NS):null;const label=t?t("nav"):"";return label&&label!=="nav"?label:"ClineBot"}catch(_){return"ClineBot"}},locale:NS,inject:()=>({ctx})},props=>React.createElement(ErrorBoundary,{t:props&&props.t},React.createElement(SettingsPage,{...props,ctx:props&&props.ctx||ctx}))));registerSlotWhenReady("sidebar.footer.action",()=>ctx.slots.register({name:"sidebar.footer.action",id:"clinebot-quota",order:20,locale:NS,inject:()=>({ctx,open:openPanel})},props=>React.createElement(ErrorBoundary,{t:props&&props.t},React.createElement(SidebarQuota,{...props,ctx:props&&props.ctx||ctx}))));registerSlotWhenReady("main",()=>ctx.slots.register({name:"main",key:"clinebot-panel",locale:NS,inject:()=>({ctx,open:openPanel,close:closePanel})},props=>React.createElement(ErrorBoundary,{t:props&&props.t},React.createElement(DashboardDialog,{...props,panel:true,ctx:props&&props.ctx||ctx,onClose:props&&props.close||closePanel}))));registerSlotWhenReady("settings.models.provider-card",()=>ctx.slots.register({name:"settings.models.provider-card",key:NS,locale:NS,inject:()=>({ctx})},props=>React.createElement(ErrorBoundary,{t:props&&props.t},React.createElement(ProviderCard,{...props,ctx:props&&props.ctx||ctx}))));registerSlotWhenReady("conversation.composer.dock",()=>ctx.slots.register({name:"conversation.composer.dock",id:"clinebot-cost",order:10,locale:NS,inject:()=>({ctx})},props=>React.createElement(ErrorBoundary,{t:props&&props.t},React.createElement(ComposerCostDock,{...props,ctx:props&&props.ctx||ctx}))))}module.exports={apply,inject:["slots","locale","configForms"]};