@goodandready/dsh-clinebot 0.6.7 → 0.6.8
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 +6 -0
- package/README.md +1 -1
- package/README.ru.md +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +1 -1
- package/lib/transport/cline-transport.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ 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.8] - 2026-10-21
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Analytics export**: JSON and CSV download links are available directly from the analytics panel, using the existing `/stats/export` endpoint.
|
|
12
|
+
- **Locale audit regression test**: checks that both dictionaries stay aligned and that no unused locale keys accumulate.
|
|
13
|
+
|
|
8
14
|
## [0.6.7] - 2026-10-21
|
|
9
15
|
|
|
10
16
|
### Added
|
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.
|
|
7
|
+
<p><b>0.6.8</b> adds JSON/CSV export links to the analytics panel and a locale dead-key regression test on top of the compressed bundle. Chat runs on the native <code>ctx.llm</code> adapter and requires DSH <code>0.2.1-rc.2</code>; 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.
|
|
7
|
+
<p><b>0.6.8</b> добавляет ссылки JSON/CSV-экспорта в analytics-панель и regression-тест на мёртвые locale-ключи поверх сжатого бандла. Чат работает через нативный адаптер <code>ctx.llm</code>, требуется DSH <code>0.2.1-rc.2</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.
|
|
7
|
+
<p><b>0.6.8</b> 在压缩包之上为分析面板添加 JSON/CSV 导出链接,并加入 locale 死键回归测试。聊天使用原生 <code>ctx.llm</code> 适配器,需要 DSH <code>0.2.1-rc.2</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
|
@@ -450,7 +450,7 @@ function heatWeekdayLabels(){return Array.from({length:7},(_,index)=>{const date
|
|
|
450
450
|
function QuotaPanelView({accountTabs,selectedUsage,accountName,accountEmail,accountPlan,accountBadges=[],activeAcc,routingMode,enabledCount,t}){return React.createElement(React.Fragment,null,accountTabs,selectedUsage?.ok===false?React.createElement("div",{className:"cb-alert-bad"},selectedUsage.error||t("analytics.no_data")):null,React.createElement("div",{className:"cb-dialogCard"},React.createElement("div",{className:"cb-dialogRow"},React.createElement("div",{className:"cb-dialogIdentity"},React.createElement("div",{className:"cb-row-title"},accountName),React.createElement("div",{className:"cb-row-desc"},[accountEmail,accountPlan].filter(Boolean).join(" \xB7 "))),React.createElement("div",{className:"cb-dialogBadges"},accountBadges.map((badge,index)=>React.createElement("span",{className:badge.className,key:`account-badge-${index}`},badge.label)),React.createElement("span",{className:"cb-badge cb-badge-ok"},t("badge.registered",{count:enabledCount})))),React.createElement("div",{className:"cb-dialogTiles"},React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-lbl"},t("dashboard.account_pool")),React.createElement("div",{className:"cb-stat-val"},activeAcc)),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-lbl"},t("accounts.mode_label")),React.createElement("div",{className:"cb-stat-val"},routingMode)))),React.createElement("div",{className:"cb-dialogCard"},React.createElement("div",{className:"cb-section-title"},t("quota.title")),React.createElement("div",{className:"cb-dialogWindows"},selectedUsage?.windows?.fiveHour?React.createElement(ProgressBar,{label:t("quota.window_5h"),percentUsed:selectedUsage.windows.fiveHour.percentUsed||0,remainingPercent:selectedUsage.windows.fiveHour.remainingPercent||100,resetsAt:selectedUsage.windows.fiveHour.resetsAt,windowMs:5*60*60*1e3,t}):null,selectedUsage?.windows?.weekly?React.createElement(ProgressBar,{label:t("quota.window_weekly"),percentUsed:selectedUsage.windows.weekly.percentUsed||0,remainingPercent:selectedUsage.windows.weekly.remainingPercent||100,resetsAt:selectedUsage.windows.weekly.resetsAt,windowMs:7*24*60*60*1e3,t}):null,selectedUsage?.windows?.monthly?React.createElement(ProgressBar,{label:t("quota.window_monthly"),percentUsed:selectedUsage.windows.monthly.percentUsed||0,remainingPercent:selectedUsage.windows.monthly.remainingPercent||100,resetsAt:selectedUsage.windows.monthly.resetsAt,windowMs:30*24*60*60*1e3,t}):null,!selectedUsage?.windows||!selectedUsage.windows.fiveHour&&!selectedUsage.windows.weekly&&!selectedUsage.windows.monthly?React.createElement("div",{className:"cb-hint"},t("quota.no_windows")):null)))}
|
|
451
451
|
|
|
452
452
|
// --- src/client/components/dashboard-dialog.js ---
|
|
453
|
-
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[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 from=Date.now()-days*864e5;const query=[`groupBy=${encodeURIComponent(group)}`,"limit=20",`from=${from}`,`heatmapDays=${days}`,...model?[`heatmapModel=${encodeURIComponent(model)}`]:[]].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]);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.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("div",{className:"cb-analytics-controls"},React.createElement("select",{className:"cb-heat-select",value:String(analyticsDays),title:t("analytics.range"),onChange:event=>setAnalyticsDays(Number(event.target.value))},React.createElement("option",{value:"7"},t("analytics.range_7")),React.createElement("option",{value:"30"},t("analytics.range_30")),React.createElement("option",{value:"90"},t("analytics.range_90"))),React.createElement("div",{className:"cb-seg"},["model","day","session"].map(group=>React.createElement("button",{key:group,type:"button",className:groupBy===group?"cb-seg-item cb-seg-item-active":"cb-seg-item",onClick:()=>setGroupBy(group)},t(`analytics.group_${group}`)))))),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&¤t.day===cell.day&¤t.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))}
|
|
453
|
+
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[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 from=Date.now()-days*864e5;const query=[`groupBy=${encodeURIComponent(group)}`,"limit=20",`from=${from}`,`heatmapDays=${days}`,...model?[`heatmapModel=${encodeURIComponent(model)}`]:[]].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]);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.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("div",{className:"cb-analytics-controls"},React.createElement("select",{className:"cb-heat-select",value:String(analyticsDays),title:t("analytics.range"),onChange:event=>setAnalyticsDays(Number(event.target.value))},React.createElement("option",{value:"7"},t("analytics.range_7")),React.createElement("option",{value:"30"},t("analytics.range_30")),React.createElement("option",{value:"90"},t("analytics.range_90"))),React.createElement("div",{className:"cb-seg"},["model","day","session"].map(group=>React.createElement("button",{key:group,type:"button",className:groupBy===group?"cb-seg-item cb-seg-item-active":"cb-seg-item",onClick:()=>setGroupBy(group)},t(`analytics.group_${group}`)))))),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&¤t.day===cell.day&¤t.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))}
|
|
454
454
|
|
|
455
455
|
// --- src/client/components/composer-cost.js ---
|
|
456
456
|
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==="estimate"&&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"):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":""})}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-clinebot",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"description": "DeepSeek Harness companion for ClineBot / ClinePass: dynamic subscription models sync, quota exhaustion warnings, session metrics, dedicated settings page, live usage limits, and /cline slash-command.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|