@cassius0924/dsh-usage-dashboard 0.4.0 → 0.5.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
@@ -190,6 +190,26 @@ async function fetchUsage(force = false) {
190
190
  return task;
191
191
  }
192
192
  var sessionUsageInflight = /* @__PURE__ */ new Map();
193
+ var sessionUsageCache = /* @__PURE__ */ new Map();
194
+ var sessionUsageListeners = /* @__PURE__ */ new Map();
195
+ var getCachedSessionUsage = (sessionId) => sessionUsageCache.get(sessionId) ?? null;
196
+ var subscribeSessionUsage = (sessionId, fn) => {
197
+ let listeners = sessionUsageListeners.get(sessionId);
198
+ if (listeners === void 0) {
199
+ listeners = /* @__PURE__ */ new Set();
200
+ sessionUsageListeners.set(sessionId, listeners);
201
+ }
202
+ listeners.add(fn);
203
+ return () => {
204
+ listeners?.delete(fn);
205
+ if (listeners?.size === 0) sessionUsageListeners.delete(sessionId);
206
+ };
207
+ };
208
+ var publishSessionUsage = (sessionId, response) => {
209
+ if (!response.ok) return;
210
+ sessionUsageCache.set(sessionId, response);
211
+ for (const listener of [...sessionUsageListeners.get(sessionId) ?? []]) listener();
212
+ };
193
213
  async function fetchSessionUsage(sessionId, force = false) {
194
214
  if (typeof sessionId !== "string" || sessionId === "") {
195
215
  return { ok: false, error: "\u7F3A\u5C11\u4F1A\u8BDD id" };
@@ -198,7 +218,10 @@ async function fetchSessionUsage(sessionId, force = false) {
198
218
  const inflight = sessionUsageInflight.get(sessionId);
199
219
  if (inflight !== void 0) return inflight;
200
220
  }
201
- const task = getJson(`/api/dsh-usage-dashboard/session?id=${encodeURIComponent(sessionId)}`, "no-store");
221
+ const task = getJson(`/api/dsh-usage-dashboard/session?id=${encodeURIComponent(sessionId)}`, "no-store").then((response) => {
222
+ publishSessionUsage(sessionId, response);
223
+ return response;
224
+ });
202
225
  if (force) return task;
203
226
  const tracked = task.finally(() => {
204
227
  if (sessionUsageInflight.get(sessionId) === tracked) sessionUsageInflight.delete(sessionId);
@@ -484,7 +507,10 @@ var zh = {
484
507
  "links.status": "\u670D\u52A1\u72B6\u6001",
485
508
  "settings.title": "\u8BBE\u7F6E",
486
509
  "settings.widget": "\u5728\u5176\u4ED6\u9875\u9762\u663E\u793A\u53F3\u4E0B\u89D2\u60AC\u6D6E\u989D\u5EA6\u7A97\u53E3",
487
- "settings.widgetHint": "\u5F53\u524D\u989D\u5EA6\u9875\u5DF2\u5C55\u793A\u5B8C\u6574\u6570\u636E\uFF0C\u60AC\u6D6E\u7A97\u4F1A\u81EA\u52A8\u9690\u85CF\uFF1B\u5207\u56DE Chat \u6216 Trajectory \u540E\u6062\u590D\u3002",
510
+ "settings.widgetHint": "\u989D\u5EA6\u9875\u4F1A\u81EA\u52A8\u9690\u85CF\u6D6E\u7A97\uFF1B\u5176\u4ED6\u9875\u9762\u662F\u5426\u5C55\u793A\u7531\u4E0B\u65B9\u9009\u62E9\u51B3\u5B9A\u3002",
511
+ "settings.widgetTabs": "\u5C55\u793A\u6D6E\u7A97\u7684\u9875\u9762",
512
+ "settings.widgetTabsHint": "\u9875\u9762\u5217\u8868\u6765\u81EA DSH \u7684\u5B9E\u65F6 Tab \u6CE8\u518C\u8868\uFF1B\u5176\u4ED6\u63D2\u4EF6\u65B0\u589E\u7684 Tab \u4E5F\u4F1A\u81EA\u52A8\u51FA\u73B0\u5728\u8FD9\u91CC\u3002",
513
+ "settings.widgetTabsEmpty": "\u5F53\u524D\u6CA1\u6709\u53EF\u914D\u7F6E\u7684\u4F1A\u8BDD\u9875\u9762\u3002",
488
514
  "settings.lowBalance": "\u4F59\u989D\u9884\u8B66\u7EBF",
489
515
  "settings.lowBalanceOn": "\u4F59\u989D\u4F4E\u4E8E {amount} {currency} \u65F6\uFF0C\u8FD9\u91CC\u548C\u60AC\u6D6E\u7A97\u90FD\u4F1A\u8F6C\u4E3A\u8B66\u793A\u8272\u3002\u586B 0 \u5173\u95ED\u3002",
490
516
  "settings.lowBalanceOff": "\u5DF2\u5173\u95ED\u4F59\u989D\u9884\u8B66\u3002\u586B\u4E00\u4E2A\u5927\u4E8E 0 \u7684\u6570\u5F00\u542F\u3002",
@@ -497,6 +523,8 @@ var zh = {
497
523
  "widget.lowTitle": "\u4F59\u989D\u4F4E\u4E8E\u9884\u8B66\u7EBF {amount}",
498
524
  "widget.todayCostTitle": "\u4ECA\u65E5\u6D88\u8017\uFF08\u4F30\u7B97\uFF09\xB7 {age}",
499
525
  "widget.todayCost": "\u4ECA\u65E5\u6D88\u8017",
526
+ "widget.sessionCostTitle": "\u5F53\u524D\u4F1A\u8BDD\u7D2F\u8BA1\u6D88\u8017\uFF08\u4F30\u7B97\uFF09",
527
+ "widget.sessionCost": "\u5F53\u524D\u4F1A\u8BDD",
500
528
  "widget.noBalance": "\u6682\u65E0\u4F59\u989D\u6570\u636E",
501
529
  "widget.dragCard": "\u62D6\u52A8\u5230\u56DB\u4E2A\u89D2\u843D",
502
530
  "widget.dragGrip": "\u62D6\u52A8\u4EE5\u79FB\u52A8",
@@ -505,6 +533,9 @@ var zh = {
505
533
  "widget.collapse": "\u6536\u8D77",
506
534
  "widget.expandAria": "\u5C55\u5F00\u989D\u5EA6\u7A97\u53E3",
507
535
  "widget.collapseAria": "\u6536\u8D77\u989D\u5EA6\u7A97\u53E3",
536
+ "messageCost.label": "\u672C\u8F6E\u8D39\u7528",
537
+ "messageCost.tooltip": "\u7531 dsh-usage-dashboard \u63D0\u4F9B \xB7 \u672C\u8F6E\u5BF9\u8BDD\u8D39\u7528\u4E3A\u4F30\u7B97\u503C",
538
+ "messageCost.aria": "{amount}\uFF0C\u672C\u8F6E\u5BF9\u8BDD\u4F30\u7B97\u8D39\u7528\uFF0C\u7531 dsh-usage-dashboard \u63D0\u4F9B",
508
539
  "boundary.title": "\u989D\u5EA6\u9762\u677F\u51FA\u9519\u4E86",
509
540
  "boundary.body": "\u6E32\u67D3\u65F6\u629B\u4E86\u5F02\u5E38\uFF0C\u901A\u5E38\u662F\u6D4F\u89C8\u5668\u91CC\u5B58\u7740\u65E7\u7248\u672C\u7684\u7F13\u5B58\u6570\u636E\u3002\u6E05\u6389\u672C\u63D2\u4EF6\u7684\u672C\u5730\u6570\u636E\u518D\u91CD\u65B0\u52A0\u8F7D\u5C31\u80FD\u6062\u590D\u3002",
510
541
  "boundary.reset": "\u6E05\u9664\u672C\u5730\u6570\u636E\u5E76\u91CD\u65B0\u52A0\u8F7D"
@@ -725,7 +756,10 @@ var en = {
725
756
  "links.status": "Service status",
726
757
  "settings.title": "Settings",
727
758
  "settings.widget": "Show the floating balance widget on other views",
728
- "settings.widgetHint": "The widget hides while this full dashboard is open, then returns on Chat or Trajectory.",
759
+ "settings.widgetHint": "The widget always hides on Usage; choose its other views below.",
760
+ "settings.widgetTabs": "Show widget on",
761
+ "settings.widgetTabsHint": "This live list comes from DSH\u2019s tab registry, including tabs added by other plugins.",
762
+ "settings.widgetTabsEmpty": "There are no configurable conversation views right now.",
729
763
  "settings.lowBalance": "Balance warning",
730
764
  "settings.lowBalanceOn": "This page and the widget turn amber below {amount} {currency}. Enter 0 to disable.",
731
765
  "settings.lowBalanceOff": "Balance warnings are off. Enter an amount above 0 to enable them.",
@@ -738,6 +772,8 @@ var en = {
738
772
  "widget.lowTitle": "Balance below warning threshold {amount}",
739
773
  "widget.todayCostTitle": "Today\u2019s spend (estimated) \xB7 {age}",
740
774
  "widget.todayCost": "Today\u2019s spend",
775
+ "widget.sessionCostTitle": "Estimated spend for the current session",
776
+ "widget.sessionCost": "Current session",
741
777
  "widget.noBalance": "No balance data",
742
778
  "widget.dragCard": "Drag to any corner",
743
779
  "widget.dragGrip": "Drag to move",
@@ -746,6 +782,9 @@ var en = {
746
782
  "widget.collapse": "Collapse",
747
783
  "widget.expandAria": "Expand balance widget",
748
784
  "widget.collapseAria": "Collapse balance widget",
785
+ "messageCost.label": "Turn cost",
786
+ "messageCost.tooltip": "Provided by dsh-usage-dashboard \xB7 estimated cost for this turn",
787
+ "messageCost.aria": "{amount}, estimated cost for this turn, provided by dsh-usage-dashboard",
749
788
  "boundary.title": "The usage dashboard crashed",
750
789
  "boundary.body": "Rendering failed, usually because this browser still has cached data from an older version. Clear this plugin\u2019s local data and reload to recover.",
751
790
  "boundary.reset": "Clear local data and reload"
@@ -1101,6 +1140,15 @@ function getComposerElement(frame) {
1101
1140
  if (frame === null) return null;
1102
1141
  return slotElement(frame, "conversation.composer.dock", "ancestor");
1103
1142
  }
1143
+ function getActiveConversationViewId(frame, viewIds) {
1144
+ if (viewIds.length === 0) return null;
1145
+ const header = frame === null ? null : slotElement(frame, "conversation.session.header", "child");
1146
+ const tablist = header?.querySelector('[role="tablist"]') ?? null;
1147
+ if (tablist === null) return viewIds[0] ?? null;
1148
+ const tabs = [...tablist.querySelectorAll('[role="tab"]')];
1149
+ const activeIndex = tabs.findIndex((tab) => tab.getAttribute("aria-selected") === "true");
1150
+ return activeIndex < 0 ? viewIds[0] ?? null : viewIds[activeIndex] ?? null;
1151
+ }
1104
1152
 
1105
1153
  // src/client/export.ts
1106
1154
  var BEIJING_OFFSET_MS2 = 8 * 60 * 60 * 1e3;
@@ -1240,6 +1288,8 @@ function createStore(key, isValid, fallback) {
1240
1288
  }
1241
1289
  var isThreshold = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
1242
1290
  var widgetVisibleStore = createStore("widget.visible", isBoolean, true);
1291
+ var isWidgetTabIds = (value) => value === null || Array.isArray(value) && value.every((id) => typeof id === "string" && id !== "") && new Set(value).size === value.length;
1292
+ var widgetTabIdsStore = createStore("widget.tabIds", isWidgetTabIds, null);
1243
1293
  var lowBalanceStore = createStore("alert.lowBalance", isThreshold, 10);
1244
1294
  var monthlyBudgetStore = createStore("budget.monthly", isThreshold, 0);
1245
1295
  var isUsageWindowDays = (value) => typeof value === "number" && USAGE_WINDOW_DAYS.some((days) => days === value);
@@ -1978,6 +2028,9 @@ function BalanceDashboard(props) {
1978
2028
  const [freshnessNow, setFreshnessNow] = (0, import_react4.useState)(Date.now());
1979
2029
  const hadDataRef = (0, import_react4.useRef)(cachedBalance !== null || cachedUsage !== null);
1980
2030
  const [widgetOn, setWidgetOn] = (0, import_react4.useState)(widgetVisibleStore.get());
2031
+ const [widgetTabIds, setWidgetTabIds] = (0, import_react4.useState)(widgetTabIdsStore.get());
2032
+ const [viewTabs, setViewTabs] = (0, import_react4.useState)(() => props.views.list());
2033
+ (0, import_react4.useEffect)(() => props.views.subscribe(() => setViewTabs(props.views.list())), [props.views]);
1981
2034
  const [lowBalance, setLowBalance] = (0, import_react4.useState)(lowBalanceStore.get());
1982
2035
  const [monthlyBudget, setMonthlyBudget] = (0, import_react4.useState)(monthlyBudgetStore.get());
1983
2036
  const [barMode, setBarMode] = (0, import_react4.useState)("daily");
@@ -1986,6 +2039,8 @@ function BalanceDashboard(props) {
1986
2039
  const [chartMetric, setChartMetric] = (0, import_react4.useState)(chartMetricStore.get());
1987
2040
  const [runwayHovering, setRunwayHovering] = (0, import_react4.useState)(false);
1988
2041
  const [runwayManuallyOpened, setRunwayManuallyOpened] = (0, import_react4.useState)(false);
2042
+ const [remainingHovering, setRemainingHovering] = (0, import_react4.useState)(false);
2043
+ const [remainingManuallyOpened, setRemainingManuallyOpened] = (0, import_react4.useState)(false);
1989
2044
  const budgetInputRef = (0, import_react4.useRef)(null);
1990
2045
  const rootRef = (0, import_react4.useRef)(null);
1991
2046
  const [composerHeight, setComposerHeight] = (0, import_react4.useState)(null);
@@ -2118,6 +2173,13 @@ function BalanceDashboard(props) {
2118
2173
  setWidgetOn(next);
2119
2174
  widgetVisibleStore.set(next);
2120
2175
  };
2176
+ const widgetTabs = viewTabs.filter((tab) => tab.id !== "balance");
2177
+ const toggleWidgetTab = (id) => {
2178
+ const selected = widgetTabIds ?? widgetTabs.map((tab) => tab.id);
2179
+ const next = selected.includes(id) ? selected.filter((tabId) => tabId !== id) : [...selected, id];
2180
+ setWidgetTabIds(next);
2181
+ widgetTabIdsStore.set(next);
2182
+ };
2121
2183
  const changeLowBalance = (value) => {
2122
2184
  setLowBalance(value);
2123
2185
  lowBalanceStore.set(value);
@@ -2273,17 +2335,36 @@ function BalanceDashboard(props) {
2273
2335
  primary !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-balance-grid", children: [
2274
2336
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
2275
2337
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: t("balance.remaining") }),
2276
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("details", { className: "dq-stat-value dq-remaining", children: [
2277
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("summary", { title: t("balance.breakdownTitle"), children: [
2278
- fmt(primary.total),
2279
- " ",
2280
- primary.currency
2281
- ] }),
2282
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-remaining-breakdown", children: [
2283
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("balance.toppedUp", { amount: fmt(primary.toppedUp) }) }),
2284
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-remaining-granted", children: t("balance.granted", { amount: fmt(primary.granted) }) })
2285
- ] })
2286
- ] })
2338
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2339
+ "details",
2340
+ {
2341
+ className: "dq-stat-value dq-remaining",
2342
+ open: remainingHovering || remainingManuallyOpened,
2343
+ onPointerEnter: () => setRemainingHovering(true),
2344
+ onPointerLeave: () => setRemainingHovering(false),
2345
+ children: [
2346
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2347
+ "summary",
2348
+ {
2349
+ title: t("balance.breakdownTitle"),
2350
+ onClick: (e) => {
2351
+ e.preventDefault();
2352
+ setRemainingManuallyOpened((v) => !v);
2353
+ },
2354
+ children: [
2355
+ fmt(primary.total),
2356
+ " ",
2357
+ primary.currency
2358
+ ]
2359
+ }
2360
+ ),
2361
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-remaining-breakdown", children: [
2362
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("balance.toppedUp", { amount: fmt(primary.toppedUp) }) }),
2363
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-remaining-granted", children: t("balance.granted", { amount: fmt(primary.granted) }) })
2364
+ ] })
2365
+ ]
2366
+ }
2367
+ )
2287
2368
  ] }),
2288
2369
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
2289
2370
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: t("balance.runway") }),
@@ -2465,6 +2546,23 @@ function BalanceDashboard(props) {
2465
2546
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("settings.widget") })
2466
2547
  ] }),
2467
2548
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-toggle-hint", children: t("settings.widgetHint") }),
2549
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting dq-setting--tabs", children: [
2550
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-label", id: "dq-widget-tabs-label", children: t("settings.widgetTabs") }),
2551
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control dq-widget-tabs", role: "group", "aria-labelledby": "dq-widget-tabs-label", children: [
2552
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-widget-tab-list", children: widgetTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "dq-widget-tab", children: [
2553
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2554
+ "input",
2555
+ {
2556
+ type: "checkbox",
2557
+ checked: widgetTabIds === null || widgetTabIds.includes(tab.id),
2558
+ onChange: () => toggleWidgetTab(tab.id)
2559
+ }
2560
+ ),
2561
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: tab.label })
2562
+ ] }, tab.id)) }),
2563
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-setting-hint", children: widgetTabs.length === 0 ? t("settings.widgetTabsEmpty") : t("settings.widgetTabsHint") })
2564
+ ] })
2565
+ ] }),
2468
2566
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
2469
2567
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-low-balance", children: t("settings.lowBalance") }),
2470
2568
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
@@ -2507,10 +2605,50 @@ function BalanceDashboard(props) {
2507
2605
  ] });
2508
2606
  }
2509
2607
 
2608
+ // src/client/message-cost.tsx
2609
+ var import_react5 = require("react");
2610
+ var import_jsx_runtime5 = require("react/jsx-runtime");
2611
+ var fmtTurnCost = (cost) => cost.toLocaleString(void 0, {
2612
+ minimumFractionDigits: cost > 0 && cost < 0.01 ? 4 : 2,
2613
+ maximumFractionDigits: cost > 0 && cost < 0.01 ? 6 : 4
2614
+ });
2615
+ var cachedData = (sessionId) => {
2616
+ const response = getCachedSessionUsage(sessionId);
2617
+ return response?.ok === true ? response.data ?? null : null;
2618
+ };
2619
+ function MessageCost({ sessionId, messageId }) {
2620
+ const { t } = useI18n();
2621
+ const [data, setData] = (0, import_react5.useState)(() => cachedData(sessionId));
2622
+ (0, import_react5.useEffect)(() => {
2623
+ setData(cachedData(sessionId));
2624
+ const unsubscribe = subscribeSessionUsage(sessionId, () => setData(cachedData(sessionId)));
2625
+ const hit = cachedData(sessionId)?.turns.some((turn2) => turn2.messageId === messageId) ?? false;
2626
+ if (!hit) void fetchSessionUsage(sessionId);
2627
+ return unsubscribe;
2628
+ }, [messageId, sessionId]);
2629
+ const turn = data?.turns.find((candidate) => candidate.messageId === messageId);
2630
+ if (turn === void 0) return null;
2631
+ const amount = `\xA5 ${fmtTurnCost(turn.cost)}`;
2632
+ const tooltip = t("messageCost.tooltip");
2633
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2634
+ "span",
2635
+ {
2636
+ className: "dq-message-cost",
2637
+ tabIndex: 0,
2638
+ "data-dq-tooltip": tooltip,
2639
+ "aria-label": t("messageCost.aria", { amount }),
2640
+ children: [
2641
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dq-message-cost-separator", "aria-hidden": true, children: "\xB7" }),
2642
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dq-message-cost-amount", children: amount })
2643
+ ]
2644
+ }
2645
+ );
2646
+ }
2647
+
2510
2648
  // src/client/styles.ts
2511
2649
  var css = `
2512
2650
  .dsh-quota-root{position:absolute;right:16px;bottom:16px;z-index:2147483000;pointer-events:auto;opacity:1;visibility:visible;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:13px;line-height:1.45;color:var(--dsw-alias-label-primary,#1f2328);transition:left .28s cubic-bezier(.22,1,.36,1),top .28s cubic-bezier(.22,1,.36,1),opacity .16s ease-out,visibility 0s linear}
2513
- .dsh-quota-root.dsh-quota-root--dashboard{opacity:0;visibility:hidden;pointer-events:none;transition:opacity .14s ease-out,visibility 0s linear .14s}
2651
+ .dsh-quota-root.dsh-quota-root--hidden{opacity:0;visibility:hidden;pointer-events:none;transition:opacity .14s ease-out,visibility 0s linear .14s}
2514
2652
  .dsh-quota-root.dsh-quota-dragging{transition:none}
2515
2653
  .dsh-quota-card,.dsh-quota-card *{box-sizing:border-box}
2516
2654
  .dsh-quota-card{background:var(--dsw-alias-bg-overlay,#ffffff);border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.12));border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,.18);padding:12px 14px;min-width:208px;max-width:270px;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);cursor:grab;touch-action:none;user-select:none;-webkit-user-select:none}
@@ -2540,6 +2678,17 @@ var css = `
2540
2678
  .dsh-quota-label{color:var(--dsw-alias-label-secondary,#59636e)}
2541
2679
  .dsh-quota-value{color:var(--dsw-alias-label-primary,#1f2328);font-variant-numeric:tabular-nums}
2542
2680
  .dsh-quota-error{color:var(--dsw-alias-state-error-primary,#cf222e)}
2681
+ .dq-message-cost{position:relative;display:inline-flex;align-items:center;order:10;gap:10px;height:28px;color:var(--dsw-alias-label-tertiary,#59636e);font-size:12px;line-height:20px;font-variant-numeric:tabular-nums;white-space:nowrap;cursor:help}
2682
+ .dq-message-cost-separator{cursor:default}
2683
+ .dq-message-cost-amount{text-decoration-line:underline;text-decoration-style:dotted;text-decoration-color:currentColor;text-underline-offset:4px}
2684
+ .dq-message-cost:hover{color:var(--dsw-alias-label-secondary,#3f4852)}
2685
+ .dq-message-cost:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px;border-radius:4px}
2686
+ .dq-message-cost::after{content:attr(data-dq-tooltip);position:absolute;left:0;top:calc(100% + 6px);z-index:2147483001;width:max-content;max-width:min(240px,calc(100vw - 24px));padding:6px 9px;border-radius:8px;background:var(--dsw-alias-bg-inverse,#1f2328);color:var(--dsw-alias-label-inverse,#fff);box-shadow:0 6px 18px rgba(0,0,0,.22);font-size:12px;font-weight:400;line-height:1.45;text-align:left;white-space:normal;pointer-events:none;opacity:0;visibility:hidden;transform:translateY(-3px);transition:opacity .12s ease-out,transform .12s cubic-bezier(.22,1,.36,1),visibility 0s linear .12s}
2687
+ .dq-message-cost:hover::after,.dq-message-cost:focus-visible::after{opacity:1;visibility:visible;transform:translateY(0);transition:opacity .12s ease-out,transform .12s cubic-bezier(.22,1,.36,1),visibility 0s linear}
2688
+ @media (hover:hover){
2689
+ [data-time-hover-root] .dq-message-cost{opacity:0;transition:opacity 80ms ease}
2690
+ [data-time-hover-root]:hover .dq-message-cost,[data-time-hover-root]:focus-within .dq-message-cost{opacity:1}
2691
+ }
2543
2692
  /* \u5361\u7247\u95F4\u8DDD\uFF08\u6B64\u5904 gap\uFF09\u523B\u610F\u5927\u4E8E\u5361\u5185\u5C0F\u8282\u95F4\u8DDD\uFF08margin-top:12/14/16px \u90A3\u4E00\u6863\uFF09\uFF0C\u5236\u9020"\u5361\u7247\u5916\u758F\u3001\u5361\u7247\u5185\u5BC6"\u7684
2544
2693
  \u758F\u5BC6\u5BF9\u6BD4\uFF1A24px \u76F8\u5BF9\u5361\u5185\u6700\u5927\u6863 16px \u4ECD\u6709 1.5 \u500D\u7EA7\u5DEE\uFF0C\u76F8\u5BF9\u5E38\u89C1\u6863 12px \u6709 2 \u500D\u7EA7\u5DEE\uFF0C\u4E0D\u4E0E .dq-card \u7684
2545
2694
  padding\uFF0816px/20px\uFF0C\u5361\u7247\u5185\u90E8\u547C\u5438\u611F\uFF0C\u8F6E\u6B21 36 \u5DF2\u5B9A\uFF09\u6DF7\u540C\u3002 */
@@ -2606,12 +2755,15 @@ var css = `
2606
2755
  .dq-runway[open] .dq-runway-detail{display:block}
2607
2756
  /* hero number for the balance, matching .dq-period-cost; relies on cascade order (declared after
2608
2757
  .dq-stat-value, same specificity) to win over the .dq-stat-value it's paired with in JSX \u2014 keep it below */
2758
+ /* \u8F6E\u6B21 47: .dq-remaining is a controlled <details> too now (see dashboard.tsx), same reason as
2759
+ .dq-runway above \u2014 an unopened <details>'s non-summary children don't actually render in current
2760
+ Chromium regardless of author display value, so the old :hover{display:flex} branch was dead code. */
2609
2761
  .dq-remaining{position:relative;font-size:22px;font-weight:680;letter-spacing:-.01em;line-height:1.25}
2610
2762
  .dq-remaining>summary{display:inline-block;list-style:none;border-radius:4px;cursor:pointer;text-decoration:underline dotted var(--dsw-alias-border-l2,rgba(0,0,0,.25));text-underline-offset:4px}
2611
2763
  .dq-remaining>summary::-webkit-details-marker{display:none}
2612
2764
  .dq-remaining>summary:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:3px}
2613
2765
  .dq-remaining-breakdown{display:none;position:absolute;top:calc(100% + 6px);left:0;z-index:20;flex-direction:column;gap:4px;padding:8px 10px;background:var(--dsw-alias-bg-overlay,#ffffff);border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.12));border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.15);white-space:nowrap;font-size:12px;font-weight:500;color:var(--dsw-alias-label-primary,#1f2328)}
2614
- .dq-remaining:hover .dq-remaining-breakdown,.dq-remaining[open] .dq-remaining-breakdown{display:flex}
2766
+ .dq-remaining[open] .dq-remaining-breakdown{display:flex}
2615
2767
  .dq-remaining-granted{color:var(--dsw-alias-label-tertiary,#59636e);font-weight:400;font-size:11px}
2616
2768
  /* narrow screens: .dq-balance-grid wraps its 3 stats onto separate lines, so the absolutely-positioned
2617
2769
  .dq-remaining-breakdown/.dq-runway-detail popovers can float down over whichever stat now sits in the
@@ -2772,7 +2924,11 @@ p.dq-session-sub{margin:8px 0}
2772
2924
  .dq-toggle{display:flex;align-items:center;gap:10px;cursor:pointer;font-size:13px;margin-top:14px;padding-top:14px;border-top:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08))}
2773
2925
  .dq-toggle input{width:16px;height:16px;accent-color:var(--dsw-alias-state-business-primary,#0969da);cursor:pointer}
2774
2926
  .dq-toggle-hint{margin:5px 0 0 26px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary,#59636e)}
2775
- .dq-toggle input:focus-visible,.dq-model-item input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
2927
+ .dq-widget-tabs{align-items:flex-start;flex-direction:column;gap:7px}
2928
+ .dq-widget-tab-list{display:flex;align-items:center;flex-wrap:wrap;gap:6px 14px}
2929
+ .dq-widget-tab{display:inline-flex;align-items:center;gap:7px;min-height:28px;color:var(--dsw-alias-label-primary,#1f2328);font-size:12px;cursor:pointer}
2930
+ .dq-widget-tab input{width:14px;height:14px;margin:0;accent-color:var(--dsw-alias-state-business-primary,#0969da);cursor:pointer;flex:none}
2931
+ .dq-toggle input:focus-visible,.dq-model-item input:focus-visible,.dq-widget-tab input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
2776
2932
  .dq-link:focus-visible,.dq-model-btn:focus-visible,.dq-chart-switch-btn:focus-visible,.dq-export-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
2777
2933
  .dq-usage-totals{display:flex;flex-wrap:wrap;gap:12px 24px;margin-bottom:16px}
2778
2934
  /* value itself (number+unit, e.g. "9999.9\u4E07") must never break across lines inside its own column \u2014
@@ -2882,14 +3038,15 @@ p.dq-session-sub{margin:8px 0}
2882
3038
  @media (prefers-reduced-motion:reduce){
2883
3039
  .dsh-quota-root,.dsh-quota-root *,
2884
3040
  .dsh-quota-root *::before,.dsh-quota-root *::after,
3041
+ .dq-message-cost,.dq-message-cost::after,
2885
3042
  .dq-balance,.dq-balance *,
2886
3043
  .dq-balance *::before,.dq-balance *::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}
2887
3044
  }
2888
3045
  `;
2889
3046
 
2890
3047
  // src/client/widget.tsx
2891
- var import_react5 = require("react");
2892
- var import_jsx_runtime5 = require("react/jsx-runtime");
3048
+ var import_react6 = require("react");
3049
+ var import_jsx_runtime6 = require("react/jsx-runtime");
2893
3050
  var MARGIN = 16;
2894
3051
  var CORNER_KEY = "widget.corner";
2895
3052
  var COLLAPSED_KEY = "widget.collapsed";
@@ -2937,30 +3094,68 @@ function cornerPos(corner, node, bounds) {
2937
3094
  if (corner === "bl") return { x: xLeft, y: yBottom };
2938
3095
  return { x: xRight, y: yBottom };
2939
3096
  }
2940
- function QuotaWidget() {
3097
+ function QuotaWidget(props) {
2941
3098
  const { t } = useI18n();
2942
- const [visible, setVisible] = (0, import_react5.useState)(widgetVisibleStore.get());
2943
- (0, import_react5.useEffect)(() => widgetVisibleStore.subscribe(() => setVisible(widgetVisibleStore.get())), []);
2944
- const [quotaViewActive2, setQuotaViewActive] = (0, import_react5.useState)(quotaViewActiveStore.get());
2945
- (0, import_react5.useEffect)(() => quotaViewActiveStore.subscribe(() => setQuotaViewActive(quotaViewActiveStore.get())), []);
2946
- const [lowBalance, setLowBalance] = (0, import_react5.useState)(lowBalanceStore.get());
2947
- (0, import_react5.useEffect)(() => lowBalanceStore.subscribe(() => setLowBalance(lowBalanceStore.get())), []);
3099
+ const sessionId = props.useSessions((state) => state.current);
3100
+ const [visible, setVisible] = (0, import_react6.useState)(widgetVisibleStore.get());
3101
+ (0, import_react6.useEffect)(() => widgetVisibleStore.subscribe(() => setVisible(widgetVisibleStore.get())), []);
3102
+ const [quotaViewActive2, setQuotaViewActive] = (0, import_react6.useState)(quotaViewActiveStore.get());
3103
+ (0, import_react6.useEffect)(() => quotaViewActiveStore.subscribe(() => setQuotaViewActive(quotaViewActiveStore.get())), []);
3104
+ const [lowBalance, setLowBalance] = (0, import_react6.useState)(lowBalanceStore.get());
3105
+ (0, import_react6.useEffect)(() => lowBalanceStore.subscribe(() => setLowBalance(lowBalanceStore.get())), []);
3106
+ const [selectedTabIds, setSelectedTabIds] = (0, import_react6.useState)(widgetTabIdsStore.get());
3107
+ (0, import_react6.useEffect)(() => widgetTabIdsStore.subscribe(() => setSelectedTabIds(widgetTabIdsStore.get())), []);
3108
+ const [viewTabs, setViewTabs] = (0, import_react6.useState)(() => props.views.list());
3109
+ (0, import_react6.useEffect)(() => props.views.subscribe(() => setViewTabs(props.views.list())), [props.views]);
3110
+ const [activeViewId, setActiveViewId] = (0, import_react6.useState)(null);
2948
3111
  const cachedBalance = getCachedBalance();
2949
- const [data, setData] = (0, import_react5.useState)(cachedBalance?.data ?? null);
2950
- const [error, setError] = (0, import_react5.useState)(null);
2951
- const [loading, setLoading] = (0, import_react5.useState)(cachedBalance === null);
2952
- const [collapsed, setCollapsed] = (0, import_react5.useState)(() => loadPref(COLLAPSED_KEY, isBoolean, false));
2953
- const [pos, setPos] = (0, import_react5.useState)(null);
2954
- const [corner, setCorner] = (0, import_react5.useState)(() => loadPref(CORNER_KEY, isCorner, "br"));
2955
- const [dragging, setDragging] = (0, import_react5.useState)(false);
2956
- const rootRef = (0, import_react5.useRef)(null);
2957
- const dragRef = (0, import_react5.useRef)(null);
3112
+ const [data, setData] = (0, import_react6.useState)(cachedBalance?.data ?? null);
3113
+ const [error, setError] = (0, import_react6.useState)(null);
3114
+ const [loading, setLoading] = (0, import_react6.useState)(cachedBalance === null);
3115
+ const [collapsed, setCollapsed] = (0, import_react6.useState)(() => loadPref(COLLAPSED_KEY, isBoolean, false));
3116
+ const [pos, setPos] = (0, import_react6.useState)(null);
3117
+ const [corner, setCorner] = (0, import_react6.useState)(() => loadPref(CORNER_KEY, isCorner, "br"));
3118
+ const [dragging, setDragging] = (0, import_react6.useState)(false);
3119
+ const rootRef = (0, import_react6.useRef)(null);
3120
+ const dragRef = (0, import_react6.useRef)(null);
2958
3121
  const readUsage = () => {
2959
3122
  const cached = getCachedUsage();
2960
3123
  return { data: cached?.ok === true ? cached.data ?? null : null, at: getCachedUsageAt() };
2961
3124
  };
2962
- const [usage, setUsage] = (0, import_react5.useState)(readUsage);
2963
- (0, import_react5.useEffect)(() => subscribeUsage(() => setUsage(readUsage())), []);
3125
+ const [usage, setUsage] = (0, import_react6.useState)(readUsage);
3126
+ (0, import_react6.useEffect)(() => subscribeUsage(() => setUsage(readUsage())), []);
3127
+ const readSessionUsage = (id) => {
3128
+ if (id === void 0) return null;
3129
+ const response = getCachedSessionUsage(id);
3130
+ return response?.ok === true ? response.data ?? null : null;
3131
+ };
3132
+ const [sessionUsage, setSessionUsage] = (0, import_react6.useState)(() => readSessionUsage(sessionId));
3133
+ (0, import_react6.useEffect)(() => {
3134
+ setSessionUsage(readSessionUsage(sessionId));
3135
+ if (sessionId === void 0) return;
3136
+ const unsubscribe = subscribeSessionUsage(sessionId, () => setSessionUsage(readSessionUsage(sessionId)));
3137
+ void fetchSessionUsage(sessionId);
3138
+ return unsubscribe;
3139
+ }, [sessionId]);
3140
+ (0, import_react6.useLayoutEffect)(() => {
3141
+ const viewIds = viewTabs.map((tab) => tab.id);
3142
+ const read = () => {
3143
+ const next = getActiveConversationViewId(getShellFrame(rootRef.current), viewIds);
3144
+ setActiveViewId((previous) => previous === next ? previous : next);
3145
+ };
3146
+ read();
3147
+ const frame = getShellFrame(rootRef.current);
3148
+ let observer = null;
3149
+ if (frame !== null && typeof MutationObserver === "function") {
3150
+ observer = new MutationObserver(read);
3151
+ observer.observe(frame, { subtree: true, childList: true, attributes: true, attributeFilter: ["aria-selected"] });
3152
+ }
3153
+ const poll = setInterval(read, 1e3);
3154
+ return () => {
3155
+ observer?.disconnect();
3156
+ clearInterval(poll);
3157
+ };
3158
+ }, [sessionId, viewTabs]);
2964
3159
  const load = async (showLoading, force = false) => {
2965
3160
  if (showLoading) setLoading(true);
2966
3161
  try {
@@ -2979,14 +3174,14 @@ function QuotaWidget() {
2979
3174
  setLoading(false);
2980
3175
  }
2981
3176
  };
2982
- (0, import_react5.useEffect)(() => {
3177
+ (0, import_react6.useEffect)(() => {
2983
3178
  void load(true);
2984
3179
  const id = setInterval(() => {
2985
3180
  void load(false);
2986
3181
  }, 6e4);
2987
3182
  return () => clearInterval(id);
2988
3183
  }, []);
2989
- (0, import_react5.useLayoutEffect)(() => {
3184
+ (0, import_react6.useLayoutEffect)(() => {
2990
3185
  if (!visible || quotaViewActive2) return;
2991
3186
  const node = rootRef.current;
2992
3187
  if (node === null) return;
@@ -3106,49 +3301,68 @@ function QuotaWidget() {
3106
3301
  endDrag(true);
3107
3302
  };
3108
3303
  if (!visible) return null;
3304
+ const allowedOnActiveView = activeViewId === null ? sessionId === void 0 : activeViewId !== "balance" && (selectedTabIds === null || selectedTabIds.includes(activeViewId));
3305
+ const hidden = quotaViewActive2 || !allowedOnActiveView;
3109
3306
  const primary = data !== null && data.balances.length > 0 ? data.balances[0] : null;
3110
3307
  const balanceValue = primary !== null ? Number(primary.total) : Number.NaN;
3111
3308
  const low = lowBalance > 0 && Number.isFinite(balanceValue) && balanceValue < lowBalance;
3112
3309
  const dotClass = primary === null ? "dsh-quota-dot dsh-quota-dot--idle" : data?.isAvailable === false ? "dsh-quota-dot dsh-quota-dot--error" : low ? "dsh-quota-dot dsh-quota-dot--warn" : "dsh-quota-dot";
3113
3310
  const ageMinutes = usage.at === null ? null : Math.floor((Date.now() - usage.at) / 6e4);
3114
3311
  const usageAgeText = ageMinutes === null || ageMinutes < 1 ? t("widget.updatedNow") : t("widget.stale", { minutes: ageMinutes });
3312
+ const sessionCostRow = sessionUsage === null ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-row", title: t("widget.sessionCostTitle"), children: [
3313
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-quota-label", children: t("widget.sessionCost") }),
3314
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "dsh-quota-value", children: [
3315
+ "\xA5 ",
3316
+ fmtTurnCost(sessionUsage.cost)
3317
+ ] })
3318
+ ] });
3115
3319
  let body = null;
3116
3320
  if (!collapsed) {
3117
3321
  if (loading && data === null && error === null) {
3118
- body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-quota-body", children: t("widget.loading") });
3322
+ body = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-body", children: [
3323
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { children: t("widget.loading") }),
3324
+ sessionCostRow
3325
+ ] });
3119
3326
  } else if (error !== null && data === null) {
3120
- body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-quota-body dsh-quota-error", children: localizeApiError(error, t, "error.query") });
3327
+ body = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-body", children: [
3328
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dsh-quota-error", children: localizeApiError(error, t, "error.query") }),
3329
+ sessionCostRow
3330
+ ] });
3121
3331
  } else if (primary !== null) {
3122
- body = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-quota-body", children: [
3123
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-quota-remaining-label", children: t("balance.remaining") }),
3124
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { title: low ? t("widget.lowTitle", { amount: fmt(lowBalance) }) : void 0, children: [
3125
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: `dsh-quota-total${low ? " dsh-quota-total--low" : ""}`, children: fmt(primary.total) }),
3126
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-quota-currency", children: primary.currency })
3332
+ body = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-body", children: [
3333
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dsh-quota-remaining-label", children: t("balance.remaining") }),
3334
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { title: low ? t("widget.lowTitle", { amount: fmt(lowBalance) }) : void 0, children: [
3335
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: `dsh-quota-total${low ? " dsh-quota-total--low" : ""}`, children: fmt(primary.total) }),
3336
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-quota-currency", children: primary.currency })
3127
3337
  ] }),
3128
- usage.data !== null && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-quota-row", title: t("widget.todayCostTitle", { age: usageAgeText }), children: [
3129
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-quota-label", children: t("widget.todayCost") }),
3130
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "dsh-quota-value", children: [
3338
+ usage.data !== null && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-row", title: t("widget.todayCostTitle", { age: usageAgeText }), children: [
3339
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-quota-label", children: t("widget.todayCost") }),
3340
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "dsh-quota-value", children: [
3131
3341
  "\xA5 ",
3132
3342
  fmt(usage.data.summary.today.cost)
3133
3343
  ] })
3134
3344
  ] }),
3135
- data?.isAvailable === false && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-quota-row", children: [
3136
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-quota-label", children: t("common.status") }),
3137
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-quota-value dsh-quota-error", children: t("common.unavailable") })
3345
+ sessionCostRow,
3346
+ data?.isAvailable === false && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-row", children: [
3347
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-quota-label", children: t("common.status") }),
3348
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-quota-value dsh-quota-error", children: t("common.unavailable") })
3138
3349
  ] })
3139
3350
  ] });
3140
3351
  } else {
3141
- body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-quota-body dsh-quota-error", children: t("widget.noBalance") });
3352
+ body = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-body", children: [
3353
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dsh-quota-error", children: t("widget.noBalance") }),
3354
+ sessionCostRow
3355
+ ] });
3142
3356
  }
3143
3357
  }
3144
3358
  const style = pos !== null ? { left: `${pos.x}px`, top: `${pos.y}px`, right: "auto", bottom: "auto" } : void 0;
3145
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3359
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3146
3360
  "div",
3147
3361
  {
3148
- className: `dsh-quota-root${dragging ? " dsh-quota-dragging" : ""}${quotaViewActive2 ? " dsh-quota-root--dashboard" : ""}`,
3362
+ className: `dsh-quota-root${dragging ? " dsh-quota-dragging" : ""}${hidden ? " dsh-quota-root--hidden" : ""}`,
3149
3363
  style,
3150
- "aria-hidden": quotaViewActive2,
3151
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3364
+ "aria-hidden": hidden,
3365
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3152
3366
  "div",
3153
3367
  {
3154
3368
  ref: rootRef,
@@ -3160,14 +3374,14 @@ function QuotaWidget() {
3160
3374
  onPointerCancel: () => endDrag(false),
3161
3375
  onLostPointerCapture: () => endDrag(false),
3162
3376
  children: [
3163
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-quota-header", children: [
3164
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-quota-title", children: [
3165
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-quota-grip", title: t("widget.dragGrip"), children: "\u2807" }),
3166
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: dotClass }),
3167
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-quota-name", children: t("widget.title") })
3377
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-header", children: [
3378
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-title", children: [
3379
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-quota-grip", title: t("widget.dragGrip"), children: "\u2807" }),
3380
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: dotClass }),
3381
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-quota-name", children: t("widget.title") })
3168
3382
  ] }),
3169
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-quota-actions", children: [
3170
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3383
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-actions", children: [
3384
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3171
3385
  "button",
3172
3386
  {
3173
3387
  className: "dsh-quota-btn",
@@ -3178,11 +3392,12 @@ function QuotaWidget() {
3178
3392
  onClick: () => {
3179
3393
  void load(true, true);
3180
3394
  void fetchUsage(true);
3395
+ if (sessionId !== void 0) void fetchSessionUsage(sessionId, true);
3181
3396
  },
3182
3397
  children: "\u21BB"
3183
3398
  }
3184
3399
  ),
3185
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3400
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3186
3401
  "button",
3187
3402
  {
3188
3403
  className: "dsh-quota-btn",
@@ -3200,7 +3415,7 @@ function QuotaWidget() {
3200
3415
  )
3201
3416
  ] })
3202
3417
  ] }),
3203
- collapsed && primary !== null && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-quota-collapsed-total-row", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: `dsh-quota-collapsed-total${low ? " dsh-quota-total--low" : ""}`, children: [
3418
+ collapsed && primary !== null && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dsh-quota-collapsed-total-row", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: `dsh-quota-collapsed-total${low ? " dsh-quota-total--low" : ""}`, children: [
3204
3419
  fmt(primary.total),
3205
3420
  " ",
3206
3421
  primary.currency
@@ -3213,13 +3428,31 @@ function QuotaWidget() {
3213
3428
  );
3214
3429
  }
3215
3430
 
3431
+ // src/client/views.ts
3432
+ var labelOf = (entry) => {
3433
+ const { id = "", label } = entry.options;
3434
+ try {
3435
+ const resolved = typeof label === "function" ? label() : label;
3436
+ return typeof resolved === "string" && resolved !== "" ? resolved : id;
3437
+ } catch {
3438
+ return id;
3439
+ }
3440
+ };
3441
+ function conversationViewsSource(slots) {
3442
+ return {
3443
+ list: () => slots.entries("conversation.view").flatMap((entry) => entry.options.id === void 0 ? [] : [{ id: entry.options.id, label: labelOf(entry) }]),
3444
+ subscribe: (fn) => slots.subscribe("conversation.view", fn)
3445
+ };
3446
+ }
3447
+
3216
3448
  // src/client/index.tsx
3217
- var import_jsx_runtime6 = require("react/jsx-runtime");
3449
+ var import_jsx_runtime7 = require("react/jsx-runtime");
3218
3450
  var name = "dsh-usage-dashboard";
3219
3451
  var inject = ["slots", "locale"];
3220
3452
  function apply(ctx) {
3221
3453
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-usage-dashboard: browser dictionaries");
3222
3454
  const t = ctx.locale.bind(NS);
3455
+ const views = conversationViewsSource(ctx.slots);
3223
3456
  if (typeof document !== "undefined") {
3224
3457
  const tag = document.createElement("style");
3225
3458
  tag.dataset.plugin = "dsh-usage-dashboard";
@@ -3232,7 +3465,17 @@ function apply(ctx) {
3232
3465
  });
3233
3466
  ctx.slots.inject("shell.overlay", () => ctx.slots.register(
3234
3467
  { name: "shell.overlay", id: "deepseek-quota", order: 1e3, locale: NS, label: () => t("widget.title") },
3235
- ({ t: slotT }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(QuotaWidget, {}) }) })
3468
+ ({ t: slotT, useSessions }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(QuotaWidget, { useSessions, views }) }) })
3469
+ ));
3470
+ ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register(
3471
+ {
3472
+ name: "conversation.chat.assistant-actions",
3473
+ id: "dsh-usage-turn-cost",
3474
+ order: 5,
3475
+ locale: NS,
3476
+ label: () => t("messageCost.label")
3477
+ },
3478
+ ({ t: slotT, sessionId, messageId }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MessageCost, { sessionId, messageId }) }) })
3236
3479
  ));
3237
3480
  ctx.slots.inject("conversation.view", () => ctx.slots.register(
3238
3481
  { name: "conversation.view", id: "balance", order: 20, locale: NS, label: () => t("nav.quota") },
@@ -3243,7 +3486,7 @@ function apply(ctx) {
3243
3486
  // `sessionId` is typed optional defensively even though the host is
3244
3487
  // expected to always supply it: BalanceDashboard renders the current-
3245
3488
  // session card only when it actually has one, never throws either way.
3246
- ({ t: slotT, sessionId }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ErrorBoundary, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BalanceDashboard, { sessionId }) }) })
3489
+ ({ t: slotT, sessionId }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ErrorBoundary, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BalanceDashboard, { sessionId, views }) }) })
3247
3490
  ));
3248
3491
  }
3249
3492
  return module.exports; } });