@mars-sea/dsh-commandcode-provider 0.12.9 → 0.13.0

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/lib/client.js CHANGED
@@ -2766,6 +2766,7 @@ window.__ModuleLoader__.load({
2766
2766
  //#endregion
2767
2767
  //#region src/client/locales.ts
2768
2768
  const zh = {
2769
+ openAnalytics: "查看模型用量、成本与性能分析",
2769
2770
  nav: "Command Code",
2770
2771
  title: "Command Code",
2771
2772
  intro: "账户操作立即生效;其余设置修改后点击底部「保存」。API 密钥仅保存在本机凭据服务中,不会回显。",
@@ -2872,7 +2873,7 @@ window.__ModuleLoader__.load({
2872
2873
  usageNetworkError: "无法连接 Command Code 服务",
2873
2874
  usageNetworkHint: "所有请求都没有到达服务端。请检查网络连接或 API 地址设置。",
2874
2875
  usageInvalidResponse: "Command Code 响应无法读取",
2875
- usageInvalidResponseHint: "请求已收到响应,但内容无法解析。请检查宿主的 HTTP 代理或响应解压配置。",
2876
+ usageInvalidResponseHint: "已收到响应,但正文读取或解析失败。下方详情可区分读取超时、中断与格式异常;可先点击刷新重试。",
2876
2877
  usageUpdated: "更新于",
2877
2878
  usagePeriodEnd: "账期截止",
2878
2879
  usageActive: "当前使用",
@@ -2940,6 +2941,7 @@ window.__ModuleLoader__.load({
2940
2941
  integrationsTitle: "集成与显示"
2941
2942
  };
2942
2943
  const en = {
2944
+ openAnalytics: "View model usage, cost and performance",
2943
2945
  nav: "Command Code",
2944
2946
  title: "Command Code",
2945
2947
  intro: "Account actions apply immediately; other settings are written by Save at the bottom. The API key stays in the local credential service and is never echoed.",
@@ -3046,7 +3048,7 @@ window.__ModuleLoader__.load({
3046
3048
  usageNetworkError: "Could not reach the Command Code service",
3047
3049
  usageNetworkHint: "No request reached the server. Check your network connection or the API base setting.",
3048
3050
  usageInvalidResponse: "Could not read the Command Code response",
3049
- usageInvalidResponseHint: "The requests received responses, but their bodies could not be parsed. Check the host HTTP proxy or response decoding.",
3051
+ usageInvalidResponseHint: "Responses arrived, but reading or parsing their bodies failed. Details below distinguish timeouts, interrupted reads, and invalid formats. Try Refresh again.",
3050
3052
  usageUpdated: "Updated",
3051
3053
  usagePeriodEnd: "Period ends",
3052
3054
  usageActive: "Active",
@@ -3273,7 +3275,7 @@ window.__ModuleLoader__.load({
3273
3275
  }
3274
3276
  //#endregion
3275
3277
  //#region package.json
3276
- var version = "0.12.9";
3278
+ var version = "0.13.0";
3277
3279
  var repository = {
3278
3280
  "type": "git",
3279
3281
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -5232,6 +5234,12 @@ window.__ModuleLoader__.load({
5232
5234
  className: "cc-intro",
5233
5235
  children: t("intro")
5234
5236
  }),
5237
+ props.openAnalytics ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5238
+ variant: "ghost",
5239
+ size: "sm",
5240
+ onClick: props.openAnalytics,
5241
+ children: t("openAnalytics")
5242
+ }) : null,
5235
5243
  !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5236
5244
  className: "cc-readOnly",
5237
5245
  role: "status",
@@ -5544,11 +5552,193 @@ window.__ModuleLoader__.load({
5544
5552
  });
5545
5553
  }
5546
5554
  //#endregion
5555
+ //#region src/client/analytics-copy.ts
5556
+ /** 模型分析沿用宿主语言;每个数值有独立标签,缺失样本与试算前提必须可见。 */
5557
+ const ANALYTICS_COPY_ZH = {
5558
+ analyticsNav: "模型分析",
5559
+ quotaNav: "账号额度",
5560
+ analysisRecent: "最近会话",
5561
+ analysisRange: "指定时间",
5562
+ analysisLimit: "会话数量",
5563
+ analysisFrom: "开始日期",
5564
+ analysisTo: "结束日期",
5565
+ analysisApply: "读取统计",
5566
+ analysisLoading: "正在读取历史记录…",
5567
+ analysisUnavailable: "宿主未提供历史查询服务,无法统计关闭后的会话。",
5568
+ analysisEmpty: "所选范围内没有 Command Code 请求记录。",
5569
+ analysisInvalid: "请输入 1–100 个会话,或不超过 366 天的有效日期范围。",
5570
+ analysisScope: "统计范围",
5571
+ analysisScanned: "已读取会话",
5572
+ analysisMatched: "含请求会话",
5573
+ analysisUnreadable: "读取失败",
5574
+ analysisSkipped: "已跳过",
5575
+ analysisPartial: "结果不完整:历史超过读取上限,或部分记录不可读。金额只覆盖已读取且可计价的请求。",
5576
+ analysisCatalog: "模型目录暂不可用,历史统计仍可查看。",
5577
+ analysisUnpriced: "部分请求缺少用量或价格,金额与词元仅为已知小计。展开明细可查看样本覆盖。",
5578
+ analysisOverview: "用量总览",
5579
+ analysisSections: "分析内容",
5580
+ analysisHistory: "历史用量",
5581
+ analysisSimulation: "价格试算",
5582
+ analysisByModel: "按模型查看",
5583
+ analysisModelHint: "展开模型,查看词元、请求状态与样本。",
5584
+ analysisCost: "已知成本 · 美元",
5585
+ analysisRequests: "模型请求",
5586
+ analysisSessionCount: "会话数",
5587
+ analysisResponse: "平均响应",
5588
+ analysisFirstToken: "平均首词元",
5589
+ analysisSpeed: "输出速度",
5590
+ analysisSeconds: "秒",
5591
+ analysisSpeedUnit: "词元/秒",
5592
+ analysisPriced: "计价请求",
5593
+ analysisTimed: "响应样本",
5594
+ analysisFirstTokenSamples: "首词元样本",
5595
+ analysisDecodeSamples: "解码样本",
5596
+ analysisUsageSamples: "用量样本",
5597
+ analysisAverage: "每会话均摊",
5598
+ analysisMissing: "无数据",
5599
+ analysisDetails: "查看汇总明细",
5600
+ analysisTokens: "词元与缓存",
5601
+ analysisInput: "未缓存输入",
5602
+ analysisOutput: "输出",
5603
+ analysisCacheRead: "缓存读取",
5604
+ analysisCacheWrite: "缓存写入",
5605
+ analysisOutcome: "请求状态",
5606
+ analysisCompleted: "完成",
5607
+ analysisFailed: "失败",
5608
+ analysisRetries: "宿主重试",
5609
+ analysisCancelled: "取消",
5610
+ analysisUnknown: "未知状态",
5611
+ analysisSamples: "统计样本",
5612
+ analysisSessionCost: "会话与成本",
5613
+ analysisMonthly: "按该时间段折算 30 天",
5614
+ analysisMonthlyHint: "仅完整、全部计价且跨度至少一天的日期范围提供折算。",
5615
+ analysisRules: "统计口径与试算说明",
5616
+ analysisShortBasis: "按当前价格估算,非历史账单。缺失用量与耗时不会补零。",
5617
+ analysisBasis: "按当前价格快照重算;不是历史账单,不含订阅费和充值,词元用量不等于账号积分。仅有请求数量、没有任务边界;不计算任务质量或智能评分。旧记录缺失的用量和耗时不会补零。",
5618
+ analysisAssumption: "沿用原请求的词元与时段,对比不同模型的价格。换模型后的生成长度、缓存命中、重试和质量可能变化;不会调用模型或自动推荐。",
5619
+ analysisContext: "上下文词元",
5620
+ analysisCapabilities: "模型能力",
5621
+ analysisEfforts: "推理力度",
5622
+ analysisExcluded: "超出上下文",
5623
+ analysisUnknownPrice: "未知用量或价格",
5624
+ analysisSearch: "筛选试算模型",
5625
+ analysisSearchPlaceholder: "输入模型名称或标识",
5626
+ analysisNoMatches: "没有匹配的模型,请调整筛选条件。",
5627
+ analysisImages: "支持图片",
5628
+ analysisTextOnly: "仅文本",
5629
+ analysisNoEfforts: "未提供推理力度",
5630
+ analysisCoverageHint: "耗时从步骤或重试开始计至模型结算;包含前置处理,不含后续工具执行。首词元包含推理与工具参数。速度按有解码计时的输出词元总量计算。试算未校验图片及工具兼容性,也不代表账号已获授权。",
5631
+ effortNone: "关闭 (none)",
5632
+ effortLow: "低 (low)",
5633
+ effortMedium: "中 (medium)",
5634
+ effortHigh: "高 (high)",
5635
+ effortXhigh: "极高 (xhigh)",
5636
+ effortMax: "最高 (max)"
5637
+ };
5638
+ /** 按项目约定,错误、缺失与读取状态在各语言环境中统一为中文。 */
5639
+ const ANALYTICS_COPY_EN = {
5640
+ analyticsNav: "Model analysis",
5641
+ quotaNav: "Account quota",
5642
+ analysisRecent: "Recent sessions",
5643
+ analysisRange: "Date range",
5644
+ analysisLimit: "Session count",
5645
+ analysisFrom: "Start date",
5646
+ analysisTo: "End date",
5647
+ analysisApply: "Read statistics",
5648
+ analysisLoading: ANALYTICS_COPY_ZH.analysisLoading,
5649
+ analysisUnavailable: ANALYTICS_COPY_ZH.analysisUnavailable,
5650
+ analysisEmpty: ANALYTICS_COPY_ZH.analysisEmpty,
5651
+ analysisInvalid: ANALYTICS_COPY_ZH.analysisInvalid,
5652
+ analysisScope: "Scope",
5653
+ analysisScanned: "Sessions read",
5654
+ analysisMatched: "Sessions with requests",
5655
+ analysisUnreadable: "Read failures",
5656
+ analysisSkipped: "Skipped",
5657
+ analysisPartial: ANALYTICS_COPY_ZH.analysisPartial,
5658
+ analysisCatalog: ANALYTICS_COPY_ZH.analysisCatalog,
5659
+ analysisUnpriced: ANALYTICS_COPY_ZH.analysisUnpriced,
5660
+ analysisOverview: "Usage overview",
5661
+ analysisSections: "Analysis sections",
5662
+ analysisHistory: "Recorded usage",
5663
+ analysisSimulation: "Price simulation",
5664
+ analysisByModel: "Usage by model",
5665
+ analysisModelHint: "Expand a model for tokens, request outcomes and samples.",
5666
+ analysisCost: "Known cost · USD",
5667
+ analysisRequests: "Model requests",
5668
+ analysisSessionCount: "Sessions",
5669
+ analysisResponse: "Mean response",
5670
+ analysisFirstToken: "Mean first token",
5671
+ analysisSpeed: "Output speed",
5672
+ analysisSeconds: "s",
5673
+ analysisSpeedUnit: "tokens/s",
5674
+ analysisPriced: "Priced requests",
5675
+ analysisTimed: "Response samples",
5676
+ analysisFirstTokenSamples: "First-token samples",
5677
+ analysisDecodeSamples: "Decode samples",
5678
+ analysisUsageSamples: "Usage samples",
5679
+ analysisAverage: "Per session",
5680
+ analysisMissing: ANALYTICS_COPY_ZH.analysisMissing,
5681
+ analysisDetails: "View aggregate details",
5682
+ analysisTokens: "Tokens and cache",
5683
+ analysisInput: "Uncached input",
5684
+ analysisOutput: "Output",
5685
+ analysisCacheRead: "Cache read",
5686
+ analysisCacheWrite: "Cache write",
5687
+ analysisOutcome: "Request outcomes",
5688
+ analysisCompleted: "Completed",
5689
+ analysisFailed: "Failed",
5690
+ analysisRetries: "Host retries",
5691
+ analysisCancelled: "Cancelled",
5692
+ analysisUnknown: "Unknown outcome",
5693
+ analysisSamples: "Samples",
5694
+ analysisSessionCost: "Sessions and cost",
5695
+ analysisMonthly: "30-day extrapolation from this range",
5696
+ analysisMonthlyHint: "Requires a complete, fully priced date range spanning at least one day.",
5697
+ analysisRules: "Statistics and simulation notes",
5698
+ analysisShortBasis: "Estimated at current prices, not an invoice. Missing usage and timing stay unknown.",
5699
+ analysisBasis: "Repriced with the current snapshot; not an invoice. Excludes subscriptions and top-ups. Tokens are not account credits. Requests have no task boundaries; no task quality or intelligence scores. Missing usage and timing stay unknown.",
5700
+ analysisAssumption: "Compares model prices using original tokens and request times. Output length, cache hits, retries and quality may change with another model. Makes no model calls or automatic recommendations.",
5701
+ analysisContext: "Context tokens",
5702
+ analysisCapabilities: "Model capabilities",
5703
+ analysisEfforts: "Reasoning efforts",
5704
+ analysisExcluded: "Over context limit",
5705
+ analysisUnknownPrice: "Unknown usage or price",
5706
+ analysisSearch: "Filter simulation models",
5707
+ analysisSearchPlaceholder: "Model name or ID",
5708
+ analysisNoMatches: ANALYTICS_COPY_ZH.analysisNoMatches,
5709
+ analysisImages: "Images supported",
5710
+ analysisTextOnly: "Text only",
5711
+ analysisNoEfforts: "No reasoning efforts listed",
5712
+ analysisCoverageHint: "Response time runs from step/retry start to model settlement, includes preparation and excludes subsequent tools. First token includes reasoning and tool arguments. Speed uses output tokens with decode timing. Simulation does not validate image/tool compatibility or prove account entitlement.",
5713
+ effortNone: "None",
5714
+ effortLow: "Low",
5715
+ effortMedium: "Medium",
5716
+ effortHigh: "High",
5717
+ effortXhigh: "Extra high",
5718
+ effortMax: "Max"
5719
+ };
5720
+ //#endregion
5547
5721
  //#region src/client/panel-copy.ts
5722
+ /**
5723
+ * Bilingual copy for the Command Code **plans & quota panel** (the sidebar
5724
+ * footer card and the center-column dashboard it opens).
5725
+ *
5726
+ * The panel follows the harness's active language: `PANEL_LOCALE_NS` is a
5727
+ * registered locale namespace (`ctx.locale.register`), both panel slot
5728
+ * registrations declare it, and the renderer hands the component a `t` seat
5729
+ * whose identity changes on a language switch (so a memoized panel re-renders).
5730
+ * {@link buildPanelView} takes that translator as an input and builds every
5731
+ * string through it, so the projection stays React-free and testable and no
5732
+ * component carries copy of its own. The settings page keeps its own
5733
+ * `settings.commandcode` namespace (see `./locales.ts`); the two never mix.
5734
+ *
5735
+ * @module dsh-commandcode-provider/client/panel-copy
5736
+ */
5548
5737
  /** The locale namespace both panel slots bind their `t` seat to. */
5549
5738
  const PANEL_LOCALE_NS = "panel.commandcode";
5550
5739
  /** The panel's Simplified Chinese string table (the key-set source of truth). */
5551
5740
  const PANEL_COPY_ZH = {
5741
+ ...ANALYTICS_COPY_ZH,
5552
5742
  nav: "Command Code",
5553
5743
  subtitle: "套餐、额度与配额窗口",
5554
5744
  refresh: "刷新",
@@ -5598,11 +5788,12 @@ window.__ModuleLoader__.load({
5598
5788
  errorNetwork: "无法连接 Command Code 服务",
5599
5789
  errorNetworkHint: "所有请求都没有到达服务端。请检查网络连接或 API 地址设置。",
5600
5790
  errorInvalidResponse: "Command Code 响应无法读取",
5601
- errorInvalidResponseHint: "请求已收到响应,但内容无法解析。请检查宿主的 HTTP 代理或响应解压配置。",
5791
+ errorInvalidResponseHint: "已收到响应,但正文读取或解析失败。下方详情可区分读取超时、中断与格式异常;可先点击刷新重试。",
5602
5792
  errorGeneric: "用量获取失败"
5603
5793
  };
5604
5794
  /** The panel's English string table (must mirror {@link PANEL_COPY_ZH}). */
5605
5795
  const PANEL_COPY_EN = {
5796
+ ...ANALYTICS_COPY_EN,
5606
5797
  nav: "Command Code",
5607
5798
  subtitle: "Plans, credits and quota windows",
5608
5799
  refresh: "Refresh",
@@ -5652,7 +5843,7 @@ window.__ModuleLoader__.load({
5652
5843
  errorNetwork: "Could not reach the Command Code service",
5653
5844
  errorNetworkHint: "No request reached the server. Check your network connection or the API base setting.",
5654
5845
  errorInvalidResponse: "Could not read the Command Code response",
5655
- errorInvalidResponseHint: "The requests received responses, but their bodies could not be parsed. Check the host HTTP proxy or response decoding.",
5846
+ errorInvalidResponseHint: "Responses arrived, but reading or parsing their bodies failed. Details below distinguish timeouts, interrupted reads, and invalid formats. Try Refresh again.",
5656
5847
  errorGeneric: "Could not fetch account usage"
5657
5848
  };
5658
5849
  /** Every panel key, in the English table's declaration order. */
@@ -5668,7 +5859,7 @@ window.__ModuleLoader__.load({
5668
5859
  //#endregion
5669
5860
  //#region src/client/panel.ts
5670
5861
  /** `$1.23`, and `$0.0123` only when the amount is too small for cents to show it. */
5671
- function money(value) {
5862
+ function money$1(value) {
5672
5863
  if (value === 0) return formatMoney(0);
5673
5864
  return Math.abs(value) < .01 ? formatMoneyExact(value) : formatMoney(value);
5674
5865
  }
@@ -5711,7 +5902,7 @@ window.__ModuleLoader__.load({
5711
5902
  const capped = limit.cap > 0;
5712
5903
  return {
5713
5904
  label,
5714
- value: capped ? `${money(limit.used)} / ${money(limit.cap)}` : money(limit.used),
5905
+ value: capped ? `${money$1(limit.used)} / ${money$1(limit.cap)}` : money$1(limit.used),
5715
5906
  capped,
5716
5907
  percent,
5717
5908
  barPercent: barPercent(percent),
@@ -5747,11 +5938,11 @@ window.__ModuleLoader__.load({
5747
5938
  const percent = known ? rawPercent(used, limitValue) : 0;
5748
5939
  return {
5749
5940
  known,
5750
- limit: limitValue !== null ? money(limitValue) : UNREPORTED,
5751
- used: known ? money(used) : UNREPORTED,
5752
- remaining: remainingReported ? money(remaining) : UNREPORTED,
5753
- purchased: credits !== void 0 && credits.purchasedReported !== false ? money(Math.max(0, credits.purchasedCredits)) : UNREPORTED,
5754
- free: credits !== void 0 && credits.freeReported !== false ? money(Math.max(0, credits.freeCredits)) : UNREPORTED,
5941
+ limit: limitValue !== null ? money$1(limitValue) : UNREPORTED,
5942
+ used: known ? money$1(used) : UNREPORTED,
5943
+ remaining: remainingReported ? money$1(remaining) : UNREPORTED,
5944
+ purchased: credits !== void 0 && credits.purchasedReported !== false ? money$1(Math.max(0, credits.purchasedCredits)) : UNREPORTED,
5945
+ free: credits !== void 0 && credits.freeReported !== false ? money$1(Math.max(0, credits.freeCredits)) : UNREPORTED,
5755
5946
  percent,
5756
5947
  barPercent: barPercent(percent),
5757
5948
  exhausted: known && remaining <= 0,
@@ -5881,11 +6072,11 @@ window.__ModuleLoader__.load({
5881
6072
  percent: `${percent}%`,
5882
6073
  barPercent: barPercent(percent),
5883
6074
  warn: window.exceeded,
5884
- detail: `${money(window.used)} / ${money(window.cap)}`
6075
+ detail: `${money$1(window.used)} / ${money$1(window.cap)}`
5885
6076
  });
5886
6077
  }
5887
6078
  const totalCost = selectedEntry?.report.usage?.totalCost;
5888
- const cost = totalCost === void 0 ? "" : money(totalCost);
6079
+ const cost = totalCost === void 0 ? "" : money$1(totalCost);
5889
6080
  let status = "";
5890
6081
  if (entries.length > 0 && !entries.some((entry) => entry.configured)) status = t("unconfigured");
5891
6082
  else if (selectedView?.mark !== void 0 && selectedView.mark !== "active") status = t(selectedView.mark);
@@ -5975,6 +6166,690 @@ window.__ModuleLoader__.load({
5975
6166
  };
5976
6167
  }
5977
6168
  //#endregion
6169
+ //#region src/analytics-wire.ts
6170
+ const g = makeBoundaryValidator("commandcode/analytics");
6171
+ const nonnegative = (s, k) => {
6172
+ const n = g.numberField(s, k, k);
6173
+ return n >= 0 ? n : g.reject(k);
6174
+ };
6175
+ const count$1 = (s, k) => {
6176
+ const n = nonnegative(s, k);
6177
+ return Number.isSafeInteger(n) ? n : g.reject(k);
6178
+ };
6179
+ function parseAnalyticsQuery(value) {
6180
+ const s = g.record(value, "query");
6181
+ if (s.mode === "recent") {
6182
+ const limit = count$1(s, "limit");
6183
+ if (limit < 1 || limit > 100) return g.reject("limit");
6184
+ return {
6185
+ mode: "recent",
6186
+ limit
6187
+ };
6188
+ }
6189
+ if (s.mode !== "range") return g.reject("mode");
6190
+ const from = count$1(s, "from"), to = count$1(s, "to");
6191
+ if (to <= from || to - from > 316224e5 || to > 864e13) return g.reject("range");
6192
+ return {
6193
+ mode: "range",
6194
+ from,
6195
+ to
6196
+ };
6197
+ }
6198
+ const counts = [
6199
+ "sessions",
6200
+ "requests",
6201
+ "completed",
6202
+ "failed",
6203
+ "cancelled",
6204
+ "unknown",
6205
+ "retries",
6206
+ "usageSamples",
6207
+ "pricedRequests",
6208
+ "latencySamples",
6209
+ "ttftSamples",
6210
+ "decodeSamples"
6211
+ ];
6212
+ const quantities = [
6213
+ "cost",
6214
+ "latencyMs",
6215
+ "ttftMs",
6216
+ "decodeMs",
6217
+ "decodeTokens"
6218
+ ];
6219
+ const tokenKeys = [
6220
+ "uncachedInputTokens",
6221
+ "outputTokens",
6222
+ "cacheReadTokens",
6223
+ "cacheWriteTokens"
6224
+ ];
6225
+ function metrics(value) {
6226
+ const s = g.record(value, "metrics"), result = {};
6227
+ for (const k of counts) result[k] = count$1(s, k);
6228
+ for (const k of quantities) result[k] = nonnegative(s, k);
6229
+ const tokens = g.record(s.tokens, "tokens");
6230
+ result.tokens = Object.fromEntries(tokenKeys.map((k) => [k, nonnegative(tokens, k)]));
6231
+ if (result.completed + result.failed + result.cancelled + result.unknown !== result.requests || result.pricedRequests > result.usageSamples || result.usageSamples > result.requests || result.retries > result.requests || result.ttftSamples > result.requests || result.latencySamples > result.requests || result.decodeSamples > result.requests) return g.reject("coverage");
6232
+ return result;
6233
+ }
6234
+ const nullable = (s, k) => s[k] === null ? null : nonnegative(s, k);
6235
+ function parseAnalyticsReport(value) {
6236
+ const s = g.record(value, "report");
6237
+ if (s.status !== "ready" && s.status !== "unavailable") return g.reject("status");
6238
+ if (!Array.isArray(s.models) || !Array.isArray(s.scenarios) || s.models.length > 1e3 || s.scenarios.length > 1e3) return g.reject("models");
6239
+ return {
6240
+ generatedAt: count$1(s, "generatedAt"),
6241
+ pricingKey: count$1(s, "pricingKey"),
6242
+ query: parseAnalyticsQuery(s.query),
6243
+ status: s.status,
6244
+ scannedSessions: count$1(s, "scannedSessions"),
6245
+ matchedSessions: count$1(s, "matchedSessions"),
6246
+ unreadableSessions: count$1(s, "unreadableSessions"),
6247
+ skippedSessions: count$1(s, "skippedSessions"),
6248
+ truncated: g.booleanField(s, "truncated", "truncated"),
6249
+ catalogUnavailable: g.booleanField(s, "catalogUnavailable", "catalogUnavailable"),
6250
+ totals: metrics(s.totals),
6251
+ monthlyEstimate: nullable(s, "monthlyEstimate"),
6252
+ models: s.models.map((value) => {
6253
+ const m = g.record(value, "model");
6254
+ return {
6255
+ ...metrics(m),
6256
+ id: g.stringField(m, "id", "id")
6257
+ };
6258
+ }),
6259
+ scenarios: s.scenarios.map((value) => {
6260
+ const m = g.record(value, "scenario");
6261
+ if (!Array.isArray(m.efforts) || !m.efforts.every((v) => typeof v === "string")) return g.reject("efforts");
6262
+ return {
6263
+ id: g.stringField(m, "id", "id"),
6264
+ name: g.stringField(m, "name", "name"),
6265
+ contextWindow: nullable(m, "contextWindow"),
6266
+ images: g.booleanField(m, "images", "images"),
6267
+ efforts: m.efforts,
6268
+ cost: nonnegative(m, "cost"),
6269
+ pricedRequests: count$1(m, "pricedRequests"),
6270
+ excludedRequests: count$1(m, "excludedRequests"),
6271
+ unknownRequests: count$1(m, "unknownRequests")
6272
+ };
6273
+ })
6274
+ };
6275
+ }
6276
+ const ANALYTICS_REMOTE_CONTRIBUTION = {
6277
+ package: REMOTE_PACKAGE,
6278
+ descriptors: [{
6279
+ ...makeRemoteDescriptor("commandcode/analytics", "analytics", `${REMOTE_PACKAGE}#AnalyticsReport`, { parse: parseAnalyticsReport }),
6280
+ parameters: [{
6281
+ name: "query",
6282
+ wire: "query",
6283
+ source: "json",
6284
+ codec: makeStrictCodec(`${REMOTE_PACKAGE}#AnalyticsQuery`, { parse: parseAnalyticsQuery })
6285
+ }]
6286
+ }]
6287
+ };
6288
+ //#endregion
6289
+ //#region src/client/analytics-view.tsx
6290
+ /** 只展示数字快照;筛选提交后才查询,页签、展开与模型筛选不触发历史读取。 */
6291
+ function localDate(time) {
6292
+ const d = new Date(time);
6293
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
6294
+ }
6295
+ /** 日期按用户本地日历展开为半开区间;截止日包括当日全部时间,夏令时不固定加 24 小时。 */
6296
+ function dateRange(from, to) {
6297
+ const start = /* @__PURE__ */ new Date(`${from}T00:00:00`), end = /* @__PURE__ */ new Date(`${to}T00:00:00`);
6298
+ if (localDate(start.getTime()) !== from || localDate(end.getTime()) !== to) throw new TypeError("无效日期");
6299
+ end.setDate(end.getDate() + 1);
6300
+ return parseAnalyticsQuery({
6301
+ mode: "range",
6302
+ from: start.getTime(),
6303
+ to: end.getTime()
6304
+ });
6305
+ }
6306
+ const money = (n) => `$${n.toFixed(4)}`;
6307
+ const digits = (n) => n.toLocaleString();
6308
+ const meanSeconds = (ms, samples, text) => samples ? `${(ms / samples / 1e3).toFixed(2)} ${text("analysisSeconds")}` : text("analysisMissing");
6309
+ const speed = (m, text) => m.decodeSamples && m.decodeMs ? `${(m.decodeTokens * 1e3 / m.decodeMs).toFixed(1)} ${text("analysisSpeedUnit")}` : text("analysisMissing");
6310
+ function effortName(effort, text) {
6311
+ const keys = {
6312
+ none: "effortNone",
6313
+ low: "effortLow",
6314
+ medium: "effortMedium",
6315
+ high: "effortHigh",
6316
+ xhigh: "effortXhigh",
6317
+ max: "effortMax"
6318
+ };
6319
+ return keys[effort] ? text(keys[effort]) : effort;
6320
+ }
6321
+ function Chevron() {
6322
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
6323
+ className: "ccp-analysisChevron",
6324
+ width: "16",
6325
+ height: "16",
6326
+ viewBox: "0 0 24 24",
6327
+ fill: "none",
6328
+ stroke: "currentColor",
6329
+ strokeWidth: "1.6",
6330
+ strokeLinecap: "round",
6331
+ strokeLinejoin: "round",
6332
+ "aria-hidden": "true",
6333
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m9 5 7 7-7 7" })
6334
+ });
6335
+ }
6336
+ function Metric({ label, value, sub }) {
6337
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6338
+ className: "ccp-analysisMetric",
6339
+ children: [
6340
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6341
+ className: "ccp-analysisLabel",
6342
+ children: label
6343
+ }),
6344
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6345
+ className: "ccp-analysisValue",
6346
+ children: value
6347
+ }),
6348
+ sub ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6349
+ className: "ccp-analysisSub",
6350
+ children: sub
6351
+ }) : null
6352
+ ]
6353
+ });
6354
+ }
6355
+ function DetailGroup({ title, values, text }) {
6356
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6357
+ className: "ccp-analysisDetailGroup",
6358
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
6359
+ className: "ccp-analysisDetailTitle",
6360
+ children: text(title)
6361
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
6362
+ className: "ccp-analysisFacts",
6363
+ children: values.map(([key, value]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6364
+ className: "ccp-analysisFact",
6365
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
6366
+ className: "ccp-analysisLabel",
6367
+ children: text(key)
6368
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
6369
+ className: "ccp-analysisFactValue",
6370
+ children: value
6371
+ })]
6372
+ }, key))
6373
+ })]
6374
+ });
6375
+ }
6376
+ /** 样本为零时词元仍为未知;汇总和单模型复用相同口径,展开只改变展示。 */
6377
+ function MetricsDetails({ m, text }) {
6378
+ const tokens = m.tokens, tokenValue = (n) => m.usageSamples ? digits(n) : text("analysisMissing");
6379
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6380
+ className: "ccp-analysisDetails",
6381
+ children: [
6382
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailGroup, {
6383
+ title: "analysisTokens",
6384
+ text,
6385
+ values: [
6386
+ ["analysisInput", tokenValue(tokens.uncachedInputTokens)],
6387
+ ["analysisOutput", tokenValue(tokens.outputTokens)],
6388
+ ["analysisCacheRead", tokenValue(tokens.cacheReadTokens)],
6389
+ ["analysisCacheWrite", tokenValue(tokens.cacheWriteTokens)]
6390
+ ]
6391
+ }),
6392
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailGroup, {
6393
+ title: "analysisOutcome",
6394
+ text,
6395
+ values: [
6396
+ ["analysisCompleted", digits(m.completed)],
6397
+ ["analysisFailed", digits(m.failed)],
6398
+ ["analysisRetries", digits(m.retries)],
6399
+ ["analysisCancelled", digits(m.cancelled)],
6400
+ ["analysisUnknown", digits(m.unknown)]
6401
+ ]
6402
+ }),
6403
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailGroup, {
6404
+ title: "analysisSamples",
6405
+ text,
6406
+ values: [
6407
+ ["analysisUsageSamples", digits(m.usageSamples)],
6408
+ ["analysisPriced", digits(m.pricedRequests)],
6409
+ ["analysisTimed", digits(m.latencySamples)],
6410
+ ["analysisFirstTokenSamples", digits(m.ttftSamples)],
6411
+ ["analysisDecodeSamples", digits(m.decodeSamples)]
6412
+ ]
6413
+ }),
6414
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailGroup, {
6415
+ title: "analysisSessionCost",
6416
+ text,
6417
+ values: [
6418
+ ["analysisSessionCount", digits(m.sessions)],
6419
+ ["analysisRequests", digits(m.requests)],
6420
+ ["analysisAverage", m.sessions && m.pricedRequests ? money(m.cost / m.sessions) : text("analysisMissing")],
6421
+ ["analysisSpeed", speed(m, text)]
6422
+ ]
6423
+ })
6424
+ ]
6425
+ });
6426
+ }
6427
+ function ModelRow({ m, text }) {
6428
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6429
+ className: "ccp-analysisModelRow",
6430
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
6431
+ className: "ccp-analysisModelSummary",
6432
+ children: [
6433
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6434
+ className: "ccp-analysisIdentity",
6435
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6436
+ className: "ccp-analysisModelName",
6437
+ children: m.id
6438
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6439
+ className: "ccp-analysisSub",
6440
+ children: [
6441
+ text("analysisRequests"),
6442
+ " ",
6443
+ digits(m.requests),
6444
+ " · ",
6445
+ text("analysisSessionCount"),
6446
+ " ",
6447
+ digits(m.sessions)
6448
+ ]
6449
+ })]
6450
+ }),
6451
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
6452
+ label: text("analysisCost"),
6453
+ value: m.pricedRequests ? money(m.cost) : text("analysisMissing"),
6454
+ sub: `${text("analysisPriced")} ${m.pricedRequests}/${m.requests}`
6455
+ }),
6456
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
6457
+ label: text("analysisResponse"),
6458
+ value: meanSeconds(m.latencyMs, m.latencySamples, text)
6459
+ }),
6460
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
6461
+ label: text("analysisFirstToken"),
6462
+ value: meanSeconds(m.ttftMs, m.ttftSamples, text)
6463
+ }),
6464
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
6465
+ label: text("analysisSpeed"),
6466
+ value: speed(m, text)
6467
+ }),
6468
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {})
6469
+ ]
6470
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MetricsDetails, {
6471
+ m,
6472
+ text
6473
+ })]
6474
+ });
6475
+ }
6476
+ function ScenarioRow({ m, text }) {
6477
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6478
+ className: "ccp-analysisModelRow",
6479
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
6480
+ className: "ccp-analysisScenarioSummary",
6481
+ children: [
6482
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6483
+ className: "ccp-analysisIdentity",
6484
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6485
+ className: "ccp-analysisModelName",
6486
+ children: m.name
6487
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6488
+ className: "ccp-analysisSub",
6489
+ children: m.id
6490
+ })]
6491
+ }),
6492
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
6493
+ label: text("analysisCost"),
6494
+ value: m.pricedRequests ? money(m.cost) : text("analysisMissing"),
6495
+ sub: `${text("analysisPriced")} ${m.pricedRequests}/${m.pricedRequests + m.excludedRequests + m.unknownRequests}`
6496
+ }),
6497
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
6498
+ label: text("analysisContext"),
6499
+ value: m.contextWindow === null ? text("analysisMissing") : digits(m.contextWindow)
6500
+ }),
6501
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6502
+ className: "ccp-analysisCapability",
6503
+ children: text(m.images ? "analysisImages" : "analysisTextOnly")
6504
+ }),
6505
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {})
6506
+ ]
6507
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6508
+ className: "ccp-analysisDetails ccp-analysisScenarioDetails",
6509
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailGroup, {
6510
+ title: "analysisSamples",
6511
+ text,
6512
+ values: [
6513
+ ["analysisPriced", digits(m.pricedRequests)],
6514
+ ["analysisExcluded", digits(m.excludedRequests)],
6515
+ ["analysisUnknownPrice", digits(m.unknownRequests)]
6516
+ ]
6517
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailGroup, {
6518
+ title: "analysisCapabilities",
6519
+ text,
6520
+ values: [["analysisEfforts", m.efforts.length ? m.efforts.map((e) => effortName(e, text)).join(", ") : text("analysisNoEfforts")]]
6521
+ })]
6522
+ })]
6523
+ });
6524
+ }
6525
+ function AnalyticsView(props) {
6526
+ const { text } = props, state = props.useCommandCodeAnalytics((s) => s), report = state.report;
6527
+ const [mode, setMode] = (0, react.useState)(report?.query.mode ?? "recent");
6528
+ const [limit, setLimit] = (0, react.useState)(report?.query.mode === "recent" ? String(report.query.limit) : "20");
6529
+ const [from, setFrom] = (0, react.useState)(localDate(report?.query.mode === "range" ? report.query.from : Date.now() - 25056e5));
6530
+ const [to, setTo] = (0, react.useState)(localDate(report?.query.mode === "range" ? report.query.to - 1 : Date.now()));
6531
+ const [invalid, setInvalid] = (0, react.useState)(false), [filter, setFilter] = (0, react.useState)("");
6532
+ const [page, setPage] = (0, react.useState)("history"), id = (0, react.useId)();
6533
+ const refresh = props.refreshAnalytics;
6534
+ (0, react.useEffect)(() => {
6535
+ if (state.status === "idle") refresh();
6536
+ }, [state.status, refresh]);
6537
+ const busy = state.status === "loading";
6538
+ const apply = () => {
6539
+ try {
6540
+ const query = mode === "recent" ? parseAnalyticsQuery({
6541
+ mode,
6542
+ limit: Number(limit)
6543
+ }) : dateRange(from, to);
6544
+ setInvalid(false);
6545
+ refresh(query);
6546
+ } catch {
6547
+ setInvalid(true);
6548
+ }
6549
+ };
6550
+ const navigateTab = (event) => {
6551
+ if (![
6552
+ "ArrowLeft",
6553
+ "ArrowRight",
6554
+ "Home",
6555
+ "End"
6556
+ ].includes(event.key)) return;
6557
+ event.preventDefault();
6558
+ const next = event.key === "Home" ? "history" : event.key === "End" ? "simulation" : page === "history" ? "simulation" : "history";
6559
+ setPage(next);
6560
+ event.currentTarget.querySelector(`[data-page="${next}"]`)?.focus();
6561
+ };
6562
+ const scenarios = report?.scenarios.filter((m) => `${m.id} ${m.name}`.toLowerCase().includes(filter.trim().toLowerCase())) ?? [];
6563
+ const m = report?.totals;
6564
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
6565
+ className: "ccp-analysis",
6566
+ "aria-label": text("analyticsNav"),
6567
+ "aria-busy": busy,
6568
+ children: [
6569
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
6570
+ className: "ccp-analysisFilters",
6571
+ noValidate: true,
6572
+ onSubmit: (e) => {
6573
+ e.preventDefault();
6574
+ if (!busy) apply();
6575
+ },
6576
+ children: [
6577
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6578
+ className: "ccp-analysisField ccp-analysisScopeField",
6579
+ children: [text("analysisScope"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6580
+ className: "ccp-analysisSelectShell",
6581
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
6582
+ className: "ccp-analysisInput ccp-analysisSelect",
6583
+ value: mode,
6584
+ disabled: busy,
6585
+ onChange: (e) => setMode(e.target.value),
6586
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
6587
+ value: "recent",
6588
+ children: text("analysisRecent")
6589
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
6590
+ value: "range",
6591
+ children: text("analysisRange")
6592
+ })]
6593
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {})]
6594
+ })]
6595
+ }),
6596
+ mode === "recent" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6597
+ className: "ccp-analysisField",
6598
+ children: [text("analysisLimit"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6599
+ className: "ccp-analysisInput",
6600
+ type: "number",
6601
+ min: "1",
6602
+ max: "100",
6603
+ required: true,
6604
+ value: limit,
6605
+ disabled: busy,
6606
+ onChange: (e) => setLimit(e.target.value)
6607
+ })]
6608
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6609
+ className: "ccp-analysisField",
6610
+ children: [text("analysisFrom"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6611
+ className: "ccp-analysisInput",
6612
+ type: "date",
6613
+ required: true,
6614
+ value: from,
6615
+ disabled: busy,
6616
+ onChange: (e) => setFrom(e.target.value)
6617
+ })]
6618
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6619
+ className: "ccp-analysisField",
6620
+ children: [text("analysisTo"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6621
+ className: "ccp-analysisInput",
6622
+ type: "date",
6623
+ required: true,
6624
+ value: to,
6625
+ disabled: busy,
6626
+ onChange: (e) => setTo(e.target.value)
6627
+ })]
6628
+ })] }),
6629
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6630
+ className: "ccp-analysisApply",
6631
+ type: "submit",
6632
+ disabled: busy,
6633
+ children: text("analysisApply")
6634
+ })
6635
+ ]
6636
+ }),
6637
+ invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6638
+ className: "ccp-analysisMessage ccp-analysisError",
6639
+ role: "alert",
6640
+ children: text("analysisInvalid")
6641
+ }) : null,
6642
+ busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6643
+ className: "ccp-analysisMessage",
6644
+ role: "status",
6645
+ children: text("analysisLoading")
6646
+ }) : null,
6647
+ state.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6648
+ className: "ccp-analysisMessage ccp-analysisError",
6649
+ role: "alert",
6650
+ children: state.error
6651
+ }) : null,
6652
+ report?.status === "unavailable" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6653
+ className: "ccp-analysisMessage",
6654
+ role: "status",
6655
+ children: text("analysisUnavailable")
6656
+ }) : null,
6657
+ report?.status === "ready" && m ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6658
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6659
+ className: "ccp-analysisScope",
6660
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
6661
+ text("analysisScope"),
6662
+ ": ",
6663
+ report.query.mode === "recent" ? `${text("analysisRecent")} ${report.query.limit}` : `${localDate(report.query.from)} — ${localDate(report.query.to - 1)}`
6664
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
6665
+ text("analysisScanned"),
6666
+ " ",
6667
+ report.scannedSessions,
6668
+ " · ",
6669
+ text("analysisMatched"),
6670
+ " ",
6671
+ report.matchedSessions,
6672
+ " · ",
6673
+ text("analysisUnreadable"),
6674
+ " ",
6675
+ report.unreadableSessions,
6676
+ " · ",
6677
+ text("analysisSkipped"),
6678
+ " ",
6679
+ report.skippedSessions
6680
+ ] })]
6681
+ }),
6682
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6683
+ className: "ccp-analysisOverview",
6684
+ role: "group",
6685
+ "aria-label": text("analysisOverview"),
6686
+ children: [
6687
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
6688
+ label: text("analysisCost"),
6689
+ value: m.pricedRequests ? money(m.cost) : text("analysisMissing"),
6690
+ sub: `${text("analysisPriced")} ${m.pricedRequests}/${m.requests}`
6691
+ }),
6692
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
6693
+ label: text("analysisRequests"),
6694
+ value: digits(m.requests),
6695
+ sub: `${text("analysisSessionCount")} ${digits(m.sessions)}`
6696
+ }),
6697
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
6698
+ label: text("analysisResponse"),
6699
+ value: meanSeconds(m.latencyMs, m.latencySamples, text),
6700
+ sub: `${text("analysisTimed")} ${digits(m.latencySamples)}`
6701
+ }),
6702
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
6703
+ label: text("analysisFirstToken"),
6704
+ value: meanSeconds(m.ttftMs, m.ttftSamples, text),
6705
+ sub: `${text("analysisFirstTokenSamples")} ${digits(m.ttftSamples)}`
6706
+ })
6707
+ ]
6708
+ }),
6709
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6710
+ className: "ccp-analysisNote",
6711
+ children: text("analysisShortBasis")
6712
+ }),
6713
+ report.truncated || report.unreadableSessions > 0 || report.skippedSessions > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6714
+ className: "ccp-analysisMessage ccp-analysisWarning",
6715
+ role: "status",
6716
+ children: text("analysisPartial")
6717
+ }) : null,
6718
+ m.pricedRequests < m.requests ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6719
+ className: "ccp-analysisMessage ccp-analysisWarning",
6720
+ role: "status",
6721
+ children: text("analysisUnpriced")
6722
+ }) : null,
6723
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6724
+ className: "ccp-analysisTabs",
6725
+ role: "tablist",
6726
+ "aria-label": text("analysisSections"),
6727
+ onKeyDown: navigateTab,
6728
+ children: ["history", "simulation"].map((tab) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6729
+ className: "ccp-analysisTab",
6730
+ id: `${id}-${tab}-tab`,
6731
+ "data-page": tab,
6732
+ type: "button",
6733
+ role: "tab",
6734
+ "aria-selected": page === tab,
6735
+ "aria-controls": `${id}-${tab}`,
6736
+ tabIndex: page === tab ? 0 : -1,
6737
+ onClick: () => setPage(tab),
6738
+ children: text(tab === "history" ? "analysisHistory" : "analysisSimulation")
6739
+ }, tab))
6740
+ }),
6741
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
6742
+ className: "ccp-analysisPanel",
6743
+ id: `${id}-history`,
6744
+ role: "tabpanel",
6745
+ "aria-labelledby": `${id}-history-tab`,
6746
+ tabIndex: 0,
6747
+ hidden: page !== "history",
6748
+ children: [
6749
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6750
+ className: "ccp-analysisSectionHead",
6751
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
6752
+ className: "ccp-analysisHeading",
6753
+ children: text("analysisByModel")
6754
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6755
+ className: "ccp-analysisNote",
6756
+ children: text("analysisModelHint")
6757
+ })]
6758
+ }),
6759
+ m.requests === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6760
+ className: "ccp-analysisEmpty",
6761
+ children: text("analysisEmpty")
6762
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6763
+ className: "ccp-analysisModels",
6764
+ children: report.models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelRow, {
6765
+ m: model,
6766
+ text
6767
+ }, model.id))
6768
+ }),
6769
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6770
+ className: "ccp-analysisAggregate",
6771
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
6772
+ className: "ccp-analysisDisclosure",
6773
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {}), text("analysisDetails")]
6774
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MetricsDetails, {
6775
+ m,
6776
+ text
6777
+ })]
6778
+ }),
6779
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6780
+ className: "ccp-analysisMonthly",
6781
+ children: [
6782
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text("analysisMonthly") }),
6783
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
6784
+ className: "ccp-analysisMonthlyValue",
6785
+ children: report.monthlyEstimate === null || report.truncated || report.unreadableSessions || report.skippedSessions ? text("analysisMissing") : money(report.monthlyEstimate)
6786
+ }),
6787
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6788
+ className: "ccp-analysisNote",
6789
+ children: text("analysisMonthlyHint")
6790
+ })
6791
+ ]
6792
+ })
6793
+ ]
6794
+ }),
6795
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
6796
+ className: "ccp-analysisPanel",
6797
+ id: `${id}-simulation`,
6798
+ role: "tabpanel",
6799
+ "aria-labelledby": `${id}-simulation-tab`,
6800
+ tabIndex: 0,
6801
+ hidden: page !== "simulation",
6802
+ children: [
6803
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6804
+ className: "ccp-analysisAssumption",
6805
+ children: text("analysisAssumption")
6806
+ }),
6807
+ report.catalogUnavailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6808
+ className: "ccp-analysisMessage ccp-analysisWarning",
6809
+ role: "status",
6810
+ children: text("analysisCatalog")
6811
+ }) : null,
6812
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6813
+ className: "ccp-analysisField ccp-analysisSearch",
6814
+ children: [text("analysisSearch"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6815
+ className: "ccp-analysisInput",
6816
+ type: "search",
6817
+ placeholder: text("analysisSearchPlaceholder"),
6818
+ value: filter,
6819
+ onChange: (e) => setFilter(e.target.value)
6820
+ })]
6821
+ }),
6822
+ m.requests === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6823
+ className: "ccp-analysisEmpty",
6824
+ children: text("analysisEmpty")
6825
+ }) : scenarios.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6826
+ className: "ccp-analysisModels",
6827
+ children: scenarios.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScenarioRow, {
6828
+ m: model,
6829
+ text
6830
+ }, model.id))
6831
+ }) : !report.catalogUnavailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6832
+ className: "ccp-analysisEmpty",
6833
+ role: "status",
6834
+ children: text("analysisNoMatches")
6835
+ }) : null
6836
+ ]
6837
+ }),
6838
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6839
+ className: "ccp-analysisRules",
6840
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
6841
+ className: "ccp-analysisDisclosure",
6842
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {}), text("analysisRules")]
6843
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6844
+ className: "ccp-analysisRulesBody",
6845
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: text("analysisBasis") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: text("analysisCoverageHint") })]
6846
+ })]
6847
+ })
6848
+ ] }) : null
6849
+ ]
6850
+ });
6851
+ }
6852
+ //#endregion
5978
6853
  //#region src/client/panel-view.tsx
5979
6854
  /**
5980
6855
  * React components for the Command Code plans & quota panel: the sidebar footer
@@ -6277,10 +7152,17 @@ window.__ModuleLoader__.load({
6277
7152
  */
6278
7153
  function CommandCodePanel(props) {
6279
7154
  const view = usePanelView(props);
7155
+ const [fallbackMode, setFallbackMode] = (0, react.useState)("quota");
7156
+ const mode = props.useCommandCodePanelMode?.((s) => s) ?? fallbackMode;
7157
+ const analyticsBusy = props.useCommandCodeAnalytics?.((s) => s.status === "loading") ?? false;
7158
+ const selectMode = (next) => {
7159
+ setFallbackMode(next);
7160
+ props.selectPanelMode?.(next);
7161
+ };
6280
7162
  const text = (key) => view.text[key] ?? key;
6281
7163
  const [selectedId, setSelectedId] = (0, react.useState)(void 0);
6282
7164
  const startAutoRefresh = props.startAutoRefresh;
6283
- (0, react.useEffect)(() => startAutoRefresh(), [startAutoRefresh]);
7165
+ (0, react.useEffect)(() => mode === "quota" ? startAutoRefresh() : void 0, [startAutoRefresh, mode]);
6284
7166
  const accounts = view.accounts;
6285
7167
  const selected = accounts.find((account) => account.id === selectedId) ?? view.selected ?? accounts[0];
6286
7168
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -6288,7 +7170,7 @@ window.__ModuleLoader__.load({
6288
7170
  role: "region",
6289
7171
  "aria-label": text("nav"),
6290
7172
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6291
- className: "ccp-mainInner",
7173
+ className: `ccp-mainInner${mode === "analysis" ? " ccp-mainInnerAnalysis" : ""}`,
6292
7174
  children: [
6293
7175
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
6294
7176
  className: "ccp-header",
@@ -6297,14 +7179,14 @@ window.__ModuleLoader__.load({
6297
7179
  className: "ccp-headerText",
6298
7180
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
6299
7181
  className: "ccp-title",
6300
- children: text("nav")
6301
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7182
+ children: mode === "analysis" ? `Command Code · ${text("analyticsNav")}` : text("nav")
7183
+ }), mode === "quota" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6302
7184
  className: "ccp-subtitle",
6303
7185
  children: text("subtitle")
6304
- })]
7186
+ }) : null]
6305
7187
  }),
6306
7188
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "ccp-spacer" }),
6307
- view.updatedAt !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7189
+ mode === "quota" && view.updatedAt !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6308
7190
  className: "ccp-meta",
6309
7191
  children: [
6310
7192
  text("updated"),
@@ -6315,9 +7197,9 @@ window.__ModuleLoader__.load({
6315
7197
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6316
7198
  variant: "ghost",
6317
7199
  size: "sm",
6318
- disabled: view.loading,
6319
- onClick: () => props.refresh(),
6320
- children: view.loading ? text("refreshing") : text("refresh")
7200
+ disabled: mode === "quota" ? view.loading : analyticsBusy,
7201
+ onClick: () => mode === "quota" ? props.refresh() : props.refreshAnalytics?.(),
7202
+ children: mode === "quota" && view.loading ? text("refreshing") : text("refresh")
6321
7203
  }),
6322
7204
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6323
7205
  variant: "ghost",
@@ -6326,75 +7208,108 @@ window.__ModuleLoader__.load({
6326
7208
  "aria-label": text("close"),
6327
7209
  title: text("closeHint"),
6328
7210
  onClick: () => props.close(),
6329
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7211
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
7212
+ width: "16",
7213
+ height: "16",
7214
+ viewBox: "0 0 24 24",
7215
+ fill: "none",
7216
+ stroke: "currentColor",
7217
+ strokeWidth: "1.6",
7218
+ strokeLinecap: "round",
6330
7219
  "aria-hidden": "true",
6331
- children: "×"
7220
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 6 12 12M18 6 6 18" })
6332
7221
  })
6333
7222
  })
6334
7223
  ]
6335
7224
  }),
6336
- view.noKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6337
- className: "ccp-notice",
6338
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6339
- className: "ccp-noticeTitle",
6340
- children: text("noKey")
6341
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6342
- className: "ccp-noticeHint",
6343
- children: text("noKeyHint")
6344
- })]
6345
- }) : null,
6346
- view.failure !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6347
- className: "ccp-notice ccp-noticeError",
6348
- role: "alert",
6349
- children: [
6350
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6351
- className: "ccp-noticeTitle",
6352
- children: text(view.failure.title)
6353
- }),
6354
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6355
- className: "ccp-noticeHint",
6356
- children: text(view.failure.hint)
6357
- }),
6358
- view.failure.detail !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6359
- className: "ccp-noticeDetail",
6360
- children: view.failure.detail
6361
- }) : null
6362
- ]
6363
- }) : null,
6364
- !view.noKey && view.failure === void 0 && accounts.length === 0 && view.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6365
- className: "ccp-hint",
6366
- children: text("loading")
6367
- }) : null,
6368
- view.staleError !== void 0 && view.staleError !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
6369
- className: "ccp-hint",
6370
- role: "status",
6371
- children: [
6372
- text("errorGeneric"),
6373
- " — ",
6374
- view.staleError
6375
- ]
6376
- }) : null,
6377
- accounts.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7225
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
6378
7226
  className: "ccp-tabs",
6379
- role: "tablist",
6380
7227
  "aria-label": text("nav"),
6381
- children: accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7228
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7229
+ className: mode === "quota" ? "ccp-tab ccp-tabActive" : "ccp-tab",
6382
7230
  type: "button",
6383
- role: "tab",
6384
- "aria-selected": selected?.id === account.id,
6385
- className: selected?.id === account.id ? "ccp-tab ccp-tabActive" : "ccp-tab",
6386
- onClick: () => setSelectedId(account.id),
6387
- children: account.label
6388
- }, account.id))
6389
- }) : null,
6390
- selected !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountCard, {
6391
- account: selected,
6392
- view
6393
- }) : null,
6394
- view.partial !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7231
+ "aria-pressed": mode === "quota",
7232
+ onClick: () => selectMode("quota"),
7233
+ children: text("quotaNav")
7234
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7235
+ className: mode === "analysis" ? "ccp-tab ccp-tabActive" : "ccp-tab",
7236
+ type: "button",
7237
+ "aria-pressed": mode === "analysis",
7238
+ onClick: () => selectMode("analysis"),
7239
+ children: text("analyticsNav")
7240
+ })]
7241
+ }),
7242
+ mode === "analysis" ? props.useCommandCodeAnalytics && props.refreshAnalytics ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnalyticsView, {
7243
+ useCommandCodeAnalytics: props.useCommandCodeAnalytics,
7244
+ refreshAnalytics: props.refreshAnalytics,
7245
+ text
7246
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6395
7247
  className: "ccp-hint",
6396
- children: text(view.partial)
6397
- }) : null
7248
+ children: text("analysisUnavailable")
7249
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
7250
+ view.noKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7251
+ className: "ccp-notice",
7252
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7253
+ className: "ccp-noticeTitle",
7254
+ children: text("noKey")
7255
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7256
+ className: "ccp-noticeHint",
7257
+ children: text("noKeyHint")
7258
+ })]
7259
+ }) : null,
7260
+ view.failure !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7261
+ className: "ccp-notice ccp-noticeError",
7262
+ role: "alert",
7263
+ children: [
7264
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7265
+ className: "ccp-noticeTitle",
7266
+ children: text(view.failure.title)
7267
+ }),
7268
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7269
+ className: "ccp-noticeHint",
7270
+ children: text(view.failure.hint)
7271
+ }),
7272
+ view.failure.detail !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7273
+ className: "ccp-noticeDetail",
7274
+ children: view.failure.detail
7275
+ }) : null
7276
+ ]
7277
+ }) : null,
7278
+ !view.noKey && view.failure === void 0 && accounts.length === 0 && view.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7279
+ className: "ccp-hint",
7280
+ children: text("loading")
7281
+ }) : null,
7282
+ view.staleError !== void 0 && view.staleError !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
7283
+ className: "ccp-hint",
7284
+ role: "status",
7285
+ children: [
7286
+ text("errorGeneric"),
7287
+ " — ",
7288
+ view.staleError
7289
+ ]
7290
+ }) : null,
7291
+ accounts.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7292
+ className: "ccp-tabs",
7293
+ role: "tablist",
7294
+ "aria-label": text("nav"),
7295
+ children: accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7296
+ type: "button",
7297
+ role: "tab",
7298
+ "aria-selected": selected?.id === account.id,
7299
+ className: selected?.id === account.id ? "ccp-tab ccp-tabActive" : "ccp-tab",
7300
+ onClick: () => setSelectedId(account.id),
7301
+ children: account.label
7302
+ }, account.id))
7303
+ }) : null,
7304
+ selected !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountCard, {
7305
+ account: selected,
7306
+ view
7307
+ }) : null,
7308
+ view.partial !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7309
+ className: "ccp-hint",
7310
+ children: text(view.partial)
7311
+ }) : null
7312
+ ] })
6398
7313
  ]
6399
7314
  })
6400
7315
  });
@@ -7657,10 +8572,8 @@ window.__ModuleLoader__.load({
7657
8572
  .ccp-subtitle{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
7658
8573
  .ccp-spacer{flex:1}
7659
8574
  .ccp-meta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;font-variant-numeric:tabular-nums}
7660
- /* The dashboard's exit: the primitive's own size="sm" geometry is kept; only the
7661
- glyph is enlarged and optically centred, so it reads as a dismiss control. */
8575
+ /* 关闭图标沿用原语义与按钮尺寸,线性 SVG 随宿主文字颜色变化。 */
7662
8576
  .ccp-close{min-width:28px;justify-content:center;padding-left:0;padding-right:0}
7663
- .ccp-close span{font-size:16px;line-height:1}
7664
8577
  .ccp-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
7665
8578
 
7666
8579
  .ccp-notice{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:12px 14px;flex-direction:column;gap:4px;display:flex}
@@ -7714,7 +8627,182 @@ window.__ModuleLoader__.load({
7714
8627
  .ccp-tabActive{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-brand-primary)}
7715
8628
 
7716
8629
  @media (prefers-reduced-motion:reduce){.ccp-footFill,.ccp-barFill{transition:none}}
8630
+ /* 分析页使用独立宽度与私有选择器;数字总览和明细不影响原额度面板。 */
8631
+ .ccp-mainInnerAnalysis{max-width:1040px}
8632
+ .ccp-analysis{font-size:14px;line-height:1.6;min-width:0;color:var(--dsw-alias-label-primary)}
8633
+ .ccp-analysisFilters{display:flex;gap:12px;align-items:end;flex-wrap:wrap;margin:10px 0 18px}
8634
+ .ccp-analysisField{display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary);min-width:0}
8635
+ .ccp-analysisFilters .ccp-analysisField{min-width:136px}
8636
+ .ccp-analysisFilters .ccp-analysisScopeField{flex:0 1 180px}
8637
+ /* 明确边框盒高度与行高,避免原生下拉、日期输入和按钮按各自外观计算尺寸。 */
8638
+ .ccp-analysisInput{box-sizing:border-box;width:100%;min-width:0;height:40px;padding:8px 11px;font:inherit;font-size:14px;line-height:20px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:7px}
8639
+ /* 原生选择行为保留;SVG 的位置与文字留白独立控制,点击箭头仍交给下拉框。 */
8640
+ .ccp-analysisSelectShell{position:relative;display:block;width:100%;min-width:0}
8641
+ .ccp-analysisSelect{appearance:none;-webkit-appearance:none;padding-inline-end:40px;text-overflow:ellipsis;cursor:pointer}
8642
+ .ccp-analysisSelectShell > .ccp-analysisChevron{position:absolute;inset-inline-end:12px;top:50%;transform:translateY(-50%) rotate(90deg);pointer-events:none}
8643
+ .ccp-analysisSelect:disabled + .ccp-analysisChevron{opacity:.65}
8644
+ .ccp-analysisInput::placeholder{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary))}
8645
+ .ccp-analysisApply{box-sizing:border-box;height:40px;font:inherit;font-size:14px;font-weight:500;line-height:20px;white-space:nowrap;padding:8px 18px;border:1px solid var(--dsw-alias-label-primary);border-radius:7px;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-1);cursor:pointer}
8646
+ .ccp-analysisApply:disabled{opacity:.5;cursor:wait}
8647
+ .ccp-analysisInput:disabled{opacity:.65}
8648
+ .ccp-analysisInput:focus-visible,.ccp-analysisApply:focus-visible,.ccp-analysisTab:focus-visible,.ccp-analysisPanel:focus-visible,.ccp-analysisModelSummary:focus-visible,.ccp-analysisScenarioSummary:focus-visible,.ccp-analysisDisclosure:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}
8649
+ /* 模型列表会裁切圆角,内收焦点框以保持第一行和最后一行也清晰可见。 */
8650
+ .ccp-analysisModelSummary:focus-visible,.ccp-analysisScenarioSummary:focus-visible,.ccp-analysisAggregate > .ccp-analysisDisclosure:focus-visible{outline-offset:-3px}
8651
+ .ccp-analysisScope{display:flex;justify-content:space-between;gap:6px 18px;flex-wrap:wrap;color:var(--dsw-alias-label-secondary);font-size:12px;margin-bottom:12px}
8652
+ .ccp-analysisOverview{display:grid;grid-template-columns:1.25fr repeat(3,1fr);padding:22px 0;border-top:1px solid var(--dsw-alias-border-l2);border-bottom:1px solid var(--dsw-alias-border-l2);font-variant-numeric:tabular-nums}
8653
+ .ccp-analysisMetric{display:flex;flex-direction:column;gap:4px;min-width:0}
8654
+ .ccp-analysisLabel{font-size:12px;color:var(--dsw-alias-label-secondary);font-weight:400}
8655
+ .ccp-analysisValue{font-size:16px;line-height:1.4;font-weight:500;overflow-wrap:anywhere}
8656
+ .ccp-analysisSub{display:block;color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:400;overflow-wrap:anywhere}
8657
+ .ccp-analysisOverview .ccp-analysisMetric{padding:0 24px;border-left:1px solid var(--dsw-alias-border-l2)}
8658
+ .ccp-analysisOverview .ccp-analysisMetric:first-child{padding-left:0;border-left:0}
8659
+ .ccp-analysisOverview .ccp-analysisMetric:last-child{padding-right:0}
8660
+ .ccp-analysisOverview .ccp-analysisValue{font-size:26px;font-weight:600;letter-spacing:-.025em;margin:3px 0}
8661
+ .ccp-analysisNote{font-size:12px;color:var(--dsw-alias-label-secondary);margin:8px 0 0}
8662
+ .ccp-analysisMessage{margin:10px 0;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:var(--dsw-alias-bg-layer-2);font-size:14px}
8663
+ .ccp-analysisWarning{border-left:3px solid var(--dsw-alias-state-warn-primary,var(--dsw-alias-label-secondary))}
8664
+ .ccp-analysisError{border-left:3px solid var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
8665
+ .ccp-analysisTabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2);margin:24px 0 0}
8666
+ .ccp-analysisTab{position:relative;border:0;background:transparent;font:inherit;font-size:14px;color:var(--dsw-alias-label-secondary);padding:10px 0 12px;cursor:pointer}
8667
+ .ccp-analysisTab[aria-selected="true"]{color:var(--dsw-alias-label-primary);font-weight:600}
8668
+ .ccp-analysisTab[aria-selected="true"]:after{content:"";position:absolute;bottom:-1px;left:0;right:0;height:2px;background:var(--dsw-alias-brand-primary);border-radius:2px}
8669
+ .ccp-analysisPanel{padding-top:18px;min-width:0}
8670
+ .ccp-analysisPanel[hidden]{display:none}
8671
+ .ccp-analysisSectionHead{display:flex;align-items:baseline;justify-content:space-between;gap:8px;flex-wrap:wrap;margin-bottom:12px}
8672
+ .ccp-analysisHeading{font-size:14px;font-weight:600;margin:0}
8673
+ .ccp-analysisSectionHead .ccp-analysisNote{margin:0}
8674
+ .ccp-analysisModels{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;overflow:hidden}
8675
+ .ccp-analysisModelRow{border-top:1px solid var(--dsw-alias-border-l2)}
8676
+ .ccp-analysisModelRow:first-child{border-top:0}
8677
+ .ccp-analysisModelSummary,.ccp-analysisScenarioSummary{position:relative;display:grid;grid-template-columns:minmax(180px,1.65fr) repeat(4,minmax(90px,1fr)) 16px;gap:16px;align-items:center;padding:18px 16px;cursor:pointer;list-style:none;font-variant-numeric:tabular-nums}
8678
+ .ccp-analysisScenarioSummary{grid-template-columns:minmax(180px,1.65fr) repeat(2,minmax(90px,1fr)) minmax(90px,.8fr) 16px}
8679
+ .ccp-analysisModelSummary::-webkit-details-marker,.ccp-analysisScenarioSummary::-webkit-details-marker,.ccp-analysisDisclosure::-webkit-details-marker{display:none}
8680
+ .ccp-analysisModelSummary:hover,.ccp-analysisScenarioSummary:hover{background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}
8681
+ .ccp-analysisIdentity{display:flex;flex-direction:column;gap:5px;min-width:0}
8682
+ .ccp-analysisModelName{font-size:14px;font-weight:600;overflow-wrap:anywhere}
8683
+ .ccp-analysisChevron{flex:none;color:var(--dsw-alias-label-secondary)}
8684
+ .ccp-analysisModelRow[open] > .ccp-analysisModelSummary > .ccp-analysisChevron,.ccp-analysisModelRow[open] > .ccp-analysisScenarioSummary > .ccp-analysisChevron,.ccp-analysisAggregate[open] > .ccp-analysisDisclosure > .ccp-analysisChevron,.ccp-analysisRules[open] > .ccp-analysisDisclosure > .ccp-analysisChevron{transform:rotate(90deg)}
8685
+ .ccp-analysisDetails{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:24px;padding:18px 16px 22px;background:var(--dsw-alias-bg-layer-2);border-top:1px solid var(--dsw-alias-border-l2)}
8686
+ .ccp-analysisDetailTitle{font-size:12px;font-weight:600;margin:0 0 10px}
8687
+ .ccp-analysisFacts{margin:0;display:flex;flex-direction:column;gap:8px}
8688
+ .ccp-analysisFact{display:flex;align-items:baseline;justify-content:space-between;gap:12px;min-width:0}
8689
+ .ccp-analysisFactValue{font-size:14px;margin:0;text-align:right;font-variant-numeric:tabular-nums;overflow-wrap:anywhere;min-width:0}
8690
+ .ccp-analysisFact .ccp-analysisLabel{flex-shrink:0}
8691
+ .ccp-analysisAggregate{margin-top:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;overflow:hidden}
8692
+ .ccp-analysisDisclosure{display:flex;gap:8px;align-items:center;cursor:pointer;list-style:none;font-size:12px;color:var(--dsw-alias-label-secondary);padding:11px 14px}
8693
+ .ccp-analysisMonthly{margin:16px 0 0;display:flex;flex-wrap:wrap;gap:4px 12px;align-items:baseline;font-size:12px;color:var(--dsw-alias-label-secondary)}
8694
+ .ccp-analysisMonthlyValue{font-size:14px;font-weight:500;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums}
8695
+ .ccp-analysisMonthly .ccp-analysisNote{width:100%;margin:0}
8696
+ .ccp-analysisAssumption{font-size:14px;line-height:1.6;color:var(--dsw-alias-label-secondary);margin:0 0 16px}
8697
+ .ccp-analysisSearch{max-width:360px;margin-bottom:16px}
8698
+ .ccp-analysisCapability{font-size:12px;color:var(--dsw-alias-label-secondary)}
8699
+ .ccp-analysisScenarioDetails{grid-template-columns:minmax(0,1fr) minmax(0,2fr)}
8700
+ .ccp-analysisScenarioDetails .ccp-analysisFactValue{max-width:75%}
8701
+ .ccp-analysisEmpty{font-size:14px;color:var(--dsw-alias-label-secondary);text-align:center;padding:32px 16px;margin:0;border:1px dashed var(--dsw-alias-border-l2);border-radius:8px}
8702
+ .ccp-analysisRules{margin-top:22px;border-top:1px solid var(--dsw-alias-border-l2)}
8703
+ .ccp-analysisRules .ccp-analysisDisclosure{padding:14px 0}
8704
+ .ccp-analysisRulesBody{color:var(--dsw-alias-label-secondary);font-size:14px;padding-bottom:4px}
8705
+ .ccp-analysisRulesBody p{margin:0 0 12px}
8706
+ @media (max-width:860px){
8707
+ .ccp-analysisOverview .ccp-analysisMetric{padding:0 16px}
8708
+ .ccp-analysisOverview .ccp-analysisValue{font-size:22px}
8709
+ .ccp-analysisModelSummary{grid-template-columns:repeat(4,minmax(0,1fr)) 16px;gap:12px 16px}
8710
+ .ccp-analysisScenarioSummary{grid-template-columns:repeat(3,minmax(0,1fr)) 16px;gap:12px 16px}
8711
+ .ccp-analysisModelSummary > .ccp-analysisIdentity,.ccp-analysisScenarioSummary > .ccp-analysisIdentity{grid-column:1/-2}
8712
+ .ccp-analysisModelSummary > .ccp-analysisChevron{grid-column:5;grid-row:1}
8713
+ .ccp-analysisScenarioSummary > .ccp-analysisChevron{grid-column:4;grid-row:1}
8714
+ .ccp-analysisDetails{grid-template-columns:repeat(2,minmax(0,1fr))}
8715
+ }
8716
+ @media (max-width:640px){
8717
+ .ccp-mainInnerAnalysis{padding:20px 16px 32px}
8718
+ .ccp-analysisFilters{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
8719
+ .ccp-analysisFilters .ccp-analysisField{min-width:0}
8720
+ .ccp-analysisApply{grid-column:1/-1}
8721
+ .ccp-analysisOverview{grid-template-columns:repeat(2,minmax(0,1fr));gap:20px 0;padding:20px 0}
8722
+ .ccp-analysisOverview .ccp-analysisMetric{padding:0 12px}
8723
+ .ccp-analysisOverview .ccp-analysisMetric:nth-child(odd){border-left:0;padding-left:0}
8724
+ .ccp-analysisOverview .ccp-analysisValue{font-size:22px}
8725
+ .ccp-analysisModelSummary,.ccp-analysisScenarioSummary{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;padding:16px}
8726
+ .ccp-analysisModelSummary > .ccp-analysisIdentity,.ccp-analysisScenarioSummary > .ccp-analysisIdentity{grid-column:1/-1;padding-right:24px}
8727
+ .ccp-analysisModelSummary > .ccp-analysisChevron,.ccp-analysisScenarioSummary > .ccp-analysisChevron{position:absolute;top:19px;right:16px;grid-column:auto;grid-row:auto}
8728
+ .ccp-analysisDetails{gap:20px;grid-template-columns:1fr}
8729
+ .ccp-analysisFactValue{max-width:65%}
8730
+ .ccp-analysisSearch{max-width:none}
8731
+ }
7717
8732
  `;
8733
+ //#endregion
8734
+ //#region src/client/analytics.ts
8735
+ /** 手动读取历史,旧宿主可降级;查询切换和卸载后不接受过期结果。 */
8736
+ var AnalyticsController = class {
8737
+ remote;
8738
+ current = { status: "idle" };
8739
+ listeners = /* @__PURE__ */ new Set();
8740
+ generation = 0;
8741
+ disposed = false;
8742
+ query = {
8743
+ mode: "recent",
8744
+ limit: 20
8745
+ };
8746
+ constructor(remote) {
8747
+ this.remote = remote;
8748
+ }
8749
+ state() {
8750
+ return this.current;
8751
+ }
8752
+ subscribe(listener) {
8753
+ this.listeners.add(listener);
8754
+ return () => {
8755
+ this.listeners.delete(listener);
8756
+ };
8757
+ }
8758
+ dispose() {
8759
+ this.disposed = true;
8760
+ this.generation++;
8761
+ this.listeners.clear();
8762
+ }
8763
+ reload() {
8764
+ this.generation++;
8765
+ this.set({ status: "idle" });
8766
+ }
8767
+ refresh(query = this.query) {
8768
+ if (this.disposed) return;
8769
+ const requested = parseAnalyticsQuery(query);
8770
+ this.query = requested;
8771
+ const generation = ++this.generation;
8772
+ this.set({ status: "loading" });
8773
+ const call = this.remote.analytics;
8774
+ if (!call) {
8775
+ this.set({
8776
+ status: "error",
8777
+ error: "宿主不支持模型分析,请更新插件"
8778
+ });
8779
+ return;
8780
+ }
8781
+ Promise.resolve().then(() => call(requested)).then((result) => {
8782
+ if (this.disposed || generation !== this.generation) return;
8783
+ if (!result.ok) {
8784
+ this.set({
8785
+ status: "error",
8786
+ error: result.error.message === "宿主不支持模型分析,请更新插件" ? result.error.message : "模型分析读取失败,请稍后刷新或检查宿主日志"
8787
+ });
8788
+ return;
8789
+ }
8790
+ this.set({
8791
+ status: "ready",
8792
+ report: parseAnalyticsReport(result.value)
8793
+ });
8794
+ }).catch(() => {
8795
+ if (!this.disposed && generation === this.generation) this.set({
8796
+ status: "error",
8797
+ error: "模型分析读取失败,请稍后刷新或检查宿主日志"
8798
+ });
8799
+ });
8800
+ }
8801
+ set(state) {
8802
+ this.current = state;
8803
+ for (const listener of this.listeners) listener();
8804
+ }
8805
+ };
7718
8806
  //#endregion
7719
8807
  //#region src/client/index.ts
7720
8808
  /** Install one stylesheet by id and return a disposer for its owning effect. */
@@ -7808,12 +8896,14 @@ window.__ModuleLoader__.load({
7808
8896
  let usageMountError;
7809
8897
  let pricesController;
7810
8898
  let refreshUsage;
8899
+ let analyticsController;
7811
8900
  const contribution = {
7812
8901
  package: USAGE_REMOTE_CONTRIBUTION.package,
7813
8902
  descriptors: [
7814
8903
  ...USAGE_REMOTE_CONTRIBUTION.descriptors,
7815
8904
  ...MODELS_REMOTE_CONTRIBUTION.descriptors,
7816
8905
  ...PRICES_REMOTE_CONTRIBUTION.descriptors,
8906
+ ...ANALYTICS_REMOTE_CONTRIBUTION.descriptors,
7817
8907
  ...LOGIN_REMOTE_CONTRIBUTION.descriptors,
7818
8908
  ...ENROLLMENT_REMOTE_CONTRIBUTION.descriptors
7819
8909
  ]
@@ -7831,9 +8921,11 @@ window.__ModuleLoader__.load({
7831
8921
  usageNamespace = namespaceCtx.remote.commandcode;
7832
8922
  controller.refreshCatalog();
7833
8923
  pricesController?.reload();
8924
+ analyticsController?.reload();
7834
8925
  refreshUsage?.();
7835
8926
  namespaceCtx.effect(() => () => {
7836
8927
  usageNamespace = void 0;
8928
+ analyticsController?.reload();
7837
8929
  }, "dsh-commandcode-provider: usage namespace");
7838
8930
  });
7839
8931
  }, (error) => {
@@ -7889,6 +8981,22 @@ window.__ModuleLoader__.load({
7889
8981
  ctx.effect(() => () => pricesController?.dispose(), "dsh-commandcode-provider: price table");
7890
8982
  const pricesStore = createSnapshotStore(pricesController.state());
7891
8983
  pricesController.subscribe(() => pricesStore.set(pricesController.state()));
8984
+ analyticsController = new AnalyticsController({ analytics: async (query) => {
8985
+ const namespace = usageNamespace;
8986
+ if (typeof namespace?.analytics !== "function") return {
8987
+ ok: false,
8988
+ error: { message: "宿主不支持模型分析,请更新插件" }
8989
+ };
8990
+ return namespace.analytics(query);
8991
+ } });
8992
+ const analyticsStore = createSnapshotStore(analyticsController.state());
8993
+ analyticsController.subscribe(() => analyticsStore.set(analyticsController.state()));
8994
+ ctx.effect(() => () => analyticsController?.dispose(), "commandcode analytics controller");
8995
+ const panelMode = createSnapshotStore("quota");
8996
+ const openAnalytics = () => {
8997
+ panelMode.set("analysis");
8998
+ ctx.get("layout")?.selectPanel(PANEL_ID);
8999
+ };
7892
9000
  const loginRemote = {
7893
9001
  loginBegin: async (targetRef) => {
7894
9002
  if (usageNamespace === void 0) return {
@@ -7952,6 +9060,7 @@ window.__ModuleLoader__.load({
7952
9060
  discard: () => controller.discard(),
7953
9061
  refreshCredentials: () => void controller.refreshCredentials(),
7954
9062
  refreshUsage: () => void usageController.refresh(),
9063
+ ...typeof ctx.get("layout")?.selectPanel === "function" ? { openAnalytics } : {},
7955
9064
  beginLogin: (targetRef) => void loginController.begin(targetRef),
7956
9065
  cancelLogin: () => void loginController.cancel(),
7957
9066
  renameAccount: (ref, label) => controller.renameAccount(ref, label),
@@ -7997,14 +9106,19 @@ window.__ModuleLoader__.load({
7997
9106
  const panelFace = () => ({
7998
9107
  hooks: {
7999
9108
  commandCodeUsage: usageStore,
8000
- commandCodeSettings: store
9109
+ commandCodeSettings: store,
9110
+ commandCodeAnalytics: analyticsStore,
9111
+ commandCodePanelMode: panelMode
8001
9112
  },
9113
+ selectPanelMode: (mode) => panelMode.set(mode),
9114
+ refreshAnalytics: (query) => analyticsController?.refresh(query),
8002
9115
  refresh: () => {
8003
9116
  pricesController?.ensure();
8004
9117
  usageController.refresh();
8005
9118
  },
8006
9119
  startAutoRefresh: () => startPanelAutoRefresh(usageController, () => true),
8007
9120
  open: () => {
9121
+ panelMode.set("quota");
8008
9122
  ctx.get("layout")?.selectPanel(PANEL_ID);
8009
9123
  },
8010
9124
  close: () => {