@loommii/dsh-provider-usage 0.6.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 ADDED
@@ -0,0 +1,1164 @@
1
+ // dsh-provider-usage — Client half (M2 简易版)。
2
+ // 右下角常驻用量卡片(可拖动、位置记忆)+ 设置页(用量中心):提供方实例管理
3
+ // (OpenCode Go 订阅 / DeepSeek 余额),卡片按结果字段自适应渲染(%+倒计时=订阅卡;
4
+ // 金额+币种=余额卡)。
5
+ // 设置项全部即时生效(localStorage 持久化,无需重启)。
6
+
7
+ window.__ModuleLoader__.load({
8
+ id: 'dsh-provider-usage',
9
+ factory: (require) => {
10
+ var module = { exports: {} };
11
+ var exports = module.exports;
12
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
13
+ const React = require('react');
14
+ const { useState, useEffect, useLayoutEffect, useCallback, useRef } = React;
15
+ // 与 dsh-pet 同构:直接在 document.body 上自建 React root(逃出 shell.overlay
16
+ // 的 z-index:20 层叠上下文,避免被 better-sidebar 等 body 顶层浮层整层盖住)
17
+ const { createRoot } = require('react-dom/client');
18
+
19
+ const inject = ['slots'];
20
+ const SETTINGS_KEY = 'dsh-provider-usage.settings';
21
+ const DEFAULT_SETTINGS = { visible: true, refreshMs: 30000, providerId: 'opencode-go', right: 16, bottom: 16, settingsVersion: 5 };
22
+ // 卡片常驻 body 顶层(z-index 与 dsh-pet 同值,≈int32 上限)
23
+ const Z_INDEX = 2147483000;
24
+ // 适配器元数据(与 host ADAPTERS 对应;logo 与显示名用于设置页/菜单)
25
+ const ADAPTER_META = {
26
+ 'usage-percent': { displayName: 'OpenCode Go(订阅额度)' },
27
+ 'balance-json': { displayName: 'DeepSeek' },
28
+ 'commandcode-credits': { displayName: 'Command Code(订阅+余额)' },
29
+ };
30
+ // 供应商实例清单(用户可配置:名称可改、可增删、可同类型多个;key 引用走 DSH 凭证)
31
+ const PROVIDERS_KEY = 'dsh-provider-usage.providers';
32
+ const DEFAULT_PROVIDERS = [
33
+ { id: 'opencode-go', name: 'OpenCode Go', adapter: 'usage-percent', ref: 'OPENCODE_GO_API_KEY' },
34
+ { id: 'deepseek-balance', name: 'DeepSeek', adapter: 'balance-json', ref: 'DEEPSEEK_API_KEY' },
35
+ ];
36
+ // 官方 logo(资源来源:Rainytoken 整理,见 tmp/Rainytoken 评估)
37
+ // OpenCode Go:opencode.ai/zh/go hero 区官方 SVG(viewBox 54×30,#211E1E + #CFCECD,需浅色衬底)
38
+ // 官方 logo 形状不变;深/浅两色映射为亮色系(透明底,适配暗色卡片):
39
+ // 深 S → 暖白 #EFE0E5,浅 S → 草莓粉 #FFB3C6
40
+ const LOGO_OCGO = React.createElement('svg', { viewBox: '0 0 54 30', width: 27, height: 15, 'aria-hidden': 'true', style: { display: 'block' } },
41
+ React.createElement('path', { fill: '#EFE0E5', d: 'M24 30H0V0H24V6H6V24H18V18H12V12H24V30Z' }),
42
+ React.createElement('path', { fill: '#FFB3C6', d: 'M12 18H18V24H6V12H12V18Z' }),
43
+ React.createElement('path', { fill: '#FFB3C6', d: 'M48 12V24H36V12H48Z' }),
44
+ React.createElement('path', { fill: '#EFE0E5', d: 'M54 30H30V0H54V30ZM36 24H48V6H36V24Z' }));
45
+ // DeepSeek:官方鲸 logo PNG(225×225 RGBA)→ data URI,透明底直接展示
46
+ const LOGO_DEEPSEEK = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAYAAAA+s9J6AAAaRUlEQVR4nO2dXVLcSrLH/1klsN9cZwXorIDeAf044fCxtQN4ZLhEwA483gFEzPXwCDto5kw47mN7B/IKRqzA8ptPt6ryPkiCBndDf+ijpM5fhOMccCMV7v4rszKzMgFBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEISNobYXIPSf6JhDF7gjZjogwDA4tmwvv3zejdtemw8EbS9A6D9W2SFYHQIIGQBAoaadNDrmdHRFSauL8wDV9gKEfhMdcwjSBwDCmW8bAg8t2UE7q/ILsYQdIDpn8/MnjFIwSmWm/D5ZMgDAmlPngtQ5pK9fIx1dUNrSUn/BKjsE1PDp9xkISeu95lfkH7In9JDomMMsyEKyZEjrPWaEAAwIBsymfB0BBgAYSEGUgpECSImQOGTfgiCI2xRkdM7GTvER4PP5r6BrbfFp211SsYQecC86RyGDQkvYI9YhKRhmDlGIDfz45x59yTz7vylBx/YvewlgVOvinyHLsgFBDxb9PYEHltwAQNLUmnxERNgSs8KzhANiHYIQEhCW8uLnL/EcBsAQSsfROY/bsIbROZts4oYgDBa9RlzSHBFhgywSHsBhxbdK8j/urq3YW5ZlA8oDMuaZlxlmDKJjDrfZJRUR1kx0zmYyQahhBzULryQh4hsHNw6yIBn9bzt7QsXBHjOHL0cdeGiV+wrguv5V+cnWiHBRhHEeZaSx/HqdiGN0zKFVdmin2NcKIVgNahQeAICBmMA3KlOjf1/ppM57PUfhiv5OlEdvXyAE6YPomMfbag17Ex0tLY5SmSmjis6538q/J1JvMCfCOJeHSGNJyux+KKW+g5Gysz8s6WQ2JTBr8RgUEqk9gId4nB+rixSMMdjd6Fe6lT3gLH875nBH4yPAR0v+SELENypT19soxE5awvsPPNuwFJud4k1ucbQpo4qPn8T86D/Pwr++iIjAzCkYKSmVakKiCamdIn13Yn883F8NZoMrDRGzspf/+efOuMmbLkKp8mG39I+EzHRotUujc75u+yHSNJ0R4aOgRuHiEavwQWy8ks7WxIBgGACYB+U3iQiz92+YBODbIAjiVu4+B802f29W+7GQQYd26hAd82ibLKLXInw2mtjaZ94rUoBG2tKorQDMPEjpN8xsVt3sEDAAyFiNvXen09u2iw2qpIwRMChUSn2fLabwToQtRBO7TMyU3Y6udpK2F/IIgsHzqYnnCAE+ItYDO3W3fbCK5SkSsDokIGTmR8UU3oiwtHp26gZaqYMmoondh2Kf3NAS59xvS0ZGF2EADAEKrcb++1OOfSjDW4eZY1yHeAjSGeTFFAl8EWG5UOKi2p5FfMvA7H6MLnTa9jpqJAT4qCzDyybu69uTyagr5xAXCPAeQh5XaFWE97k0wgGYhmgmnC/USFG0/abiHbsBMCSigeZg/4+T7FY77XVe8SUBAgBz7rK3IsJH4oMaitvZH37+hNnRa+8HX8KAEAFqYDX2fQ3eLCPAWRotLIzO2bw7nQ6txhlIlcncsMk19AkitRcdb+UDLCyCNx/txEbR+QvFFw2yqgCBBi1hdMyhnbqIoD8g94VNU/fuKz4eBXr9Oi9gaOBWBsCQSRk7dabtKOomW6tGRPj2ZDKwhEOAIojlqwwGQiYatHVcaR6jC0rfndgfeQFD/ZS5RRc4Ex1zo2VvT9Npq26tiPKHVa0ijM7Z2L/sEFCHRR2lqfN+W4gB0Qf7l43R4uFdD8jL3pRNsOFpjLLQH8jL7+YV+5MlQ0q/ySbud63U/rrpNKb8gVGbCEv3k5U6zJ9WQh0QMACpw7cnk6QrofuaWPs0xtOSyPvAEsGAtXn6+qI22RDRy4cBnoNrtIQPFQJ0SOJ+1g9hqLGT+NJCUCn1nZlTNO758NAFfLeMW7rUAesFWRYGqjp/lAI1iHCd6JCwMQbgyGqCD2F7tvaOlEq4eQ/oRbe0FJ+DGxLrgxZLIlOiPKBWaYpCBNgq4X3YfuqO2kxdsOaU0UiEdB4hSO/PS1vMVGZ9LD6jQ7T1OWWklbujIkAvMHiouWztJIJzQaoV0nnnMhuBEE4mCAHEwEOA0Cp88KYyi5A4yu6AiiyhCNA7wjat4u4uEnB7uUtiDjXbELgPEB6x8q04hJIsCxKgAhGKAL3FILeKZy5oVoijC0rB9htaKiIoWyneBwhBZ55F6FNmd/f6dQXuqAiwExStI/CmSfeUFSeUW8Ow7nvNwbDjodUUgj0sEGGkitR/y/dhbUsYnbOx2kUiwE4QNl1rGWRBArSYLiEM/XI/Z5jZDwIbiDDLsgFAH+DjLynMwyCvtTxrYp84uqKECDHai5Ia+FmhlTLz19nD2GuJ8O3JZED5vLlBRQsTGqKotWxkn8jW3pFHxeU+QEBCzI+2BCuLMDrmUNNOWYxtqlue0CDlPvHs7clkUNdNNOuYQXFd1+8iDIo163j2eyuJsNwHAhxBBNh1QoCPNOnahDi6ogRsv0KsYUkCtl+fltStJELZB/YOA1BUpxC102OAxnVcu3MQxRaPrSCwggijYw6Jgw+QfWDfMHUKUazhPSk7921399d/h6VEKG5o7zF1ClGsIQDG2MGO5uVolxKhuKFbgSmF+O50Oqwyl7jt1pCBGOxuFp33fFGE4oZuFQagqI6kvnZ6TMQ32D4hpmC+1a/0eNELnhWhuKFbiUGZ1K9QiKMrSlSmrrfOLX3GDS15VoTihm4vZduM/DNQDaMrSuDsLRdHjPrOS25oyUIRRuds4NQQ4oZuMwNidVhlsEa/0mPCVrilCYFvnnNDSxaKMMuyAREdQNzQbcZUHTUdXVCqrRr1fH+YEPGNtupZN7RkrgjFCgozmMqFWOwPeyrElUd/zxWhWEHhCUaEuBQrCxCYI0KxgsICjAjxWdYSIDBHhJMJQlJqH2IFhV8xdQkR4MuuRk3zdfPlOgIE5ohQsw1JhnQKizF1CFHvqGty7hMYI7R3EHhVEoCuyblPemc9AQJP+ghH52yyiTsnojOIJRSeJwV4ZNleVtl+/+3JZJCfV+UI/uanU+TW71ZbtfE0qEeWUAIywgqYOoq+v3zejbXFpcf7xASgaya7kfWb5ZElfH/KZ8z8D4gIheVJ67CI0TGHVuOsaNZkqrrumiT5n7wQvepR3fciLGaNfwT4vKqLC1tDCsYY7G70K13ZrMT3/8OHzPyPfF5EKyQgisHuK5OLgyxI6hi4c993NMuyAUEPqr6BsBUYECImFdqJNdE5L1Up8iLUSse0BDNWz7KOd3dVMrrQaV03vBehQrDP+RhrQVgLAgZFS8WNx1cXnlmIekWYEpDkw2soYXZ3BE5YcZJbvbxNfd0EQGO/sLAFVDW+ukbPLAFozOzulFLf2do71pzmotNJDfd7kQDIE/RaIVw0FFEQVmTj1vuKgz1mDisaxlmSAjTSFpdtCW4eAVAm6FVYsQZnTP1jCDDMM1a3Hd9fqJewaL0/sBN7s8o+schX/05EptIVMVIixD5MM54lAADSOn/qrEcuNqIEjITZ/QCKkcmFqf/lJywZUvrN/dcEw1y4wwRDzCFD3OMeYJCf0jfZxIXvTqfjZaxinq/WW5OvDjbcDyYAjdjZr5Z0sruLtaNI0Tmbnz9hlILR7ELSes859xuRegPwAIxQLGY3yU/pUwjWB3bqbl8K2tTkinpLsMF+sKgap+sqokjF0zEtvozL70fnbLLMDhSCPcfudyK1V8wYH0AE2SUMHqYI7/9xks1NetfminpMsP5+kMa5AOv1rwtxjoEHaxkENlQI9nMXlgcQQXaJEOAjkBpajYOnYtw2VxQAAlL6DTObVU0/s7trOsI0Yy0TAOMZKymC7B7hrBjfn3IMRsrshtiys6xBl/dZM1ZSBNldQoCPmJGCkSJ3Q027S2qWIA9+dN//nidIODUsToUMsGVvbEFR+/gQtc4DXTAe7qtNYRC2jqCIPq78g0TqTXTOpon556tSCjI65zjLsvEWijEBaPxQ+/gQtZZ9tX8EWP8f3vz8CQOPT0FvqRifjVov2lfDqSGIPlCf92NPZsX7Qr4nXKdUhvKcXtULqoMtEmMK0GiVqPXsv42d2AR0Pwbd1LXI9qAky5opyl4FtXY/GWajVGYqXU3NjC4o/c8/d8bBrrpgsp+Y+RL5PjJtd2UVwUgJ65VljS4o1bt6xGQ/AXQNP0+1b0r6+rV/73XAjJXTE13nqWXMp0553dNkOWiz4Mb9v8sxJy7gH8x0iK7/m8zA7H7UeS5wXVYalz0LAYZst6OqpWXUFpfgznX6modhx8NN5wv2rB9oSaqU+t72IuYRvPyS+TDjcRF2hynct+u3J5NYsf7W6QAFYUisjZ0ifn/KCRgpO/vjobZ3uWj26IqS6JivXeDQC4vISOHpA3ZtEW7q+vjIl8+7cXTOif3LxiB1CMIQ3QtQGABDgIdlApyUSjUhsVMkf5xk35ZtVNQrIXoaGQU2ESFgnHO/VbYSTygsxejtySTpvFUsEuAMAFy0LiGVWI2DP/6e3S7TlKk/QvQzMgpssCfsO18+78bBrrroYFfolwgBPmKllh6J3ZM9opeRUQBQm7iURGovOu5vy/zRBaV//isYWWRdDdsnlB8LS5/+BQEDKP1hMlnOss2Mux7Nu57npETL74ebZkNLyGEWZGElK/GYDnSFfkoKxgjsPrFzn5gXDFthHmjYwbIXHV1RYnl6A86PlnWGsjjcU9Qmi+tDmmJZZqcHwf8Ef8zKXv75Obj+81/BKNhVFwtGVIcgvb9KOiPvsu1u51zLXzwOygAbWkIGQtJ6r6rF+E45PSivKuERvBUixUEQxOVXowtKmVyM+cIpa4CXRjs97pBXAJ+DMsDmgRnDjHCTxHDXKBP8lu2lx/tEgyfCIkuG5qdbVg5YzOwPx+susGG8DcoAVURHCeGym/s+4fM+kcADq+x91Ux0zCGUPuA56YW8lGv1gMXoKm8TD89+9zl4HZQBgIAI6Ub9RpmNUtZUtJ5O4WsOjYEQpA6ziQvfn/J3yxgAPMSvlnCjUi7t9NgFLvTpd59D7JB9a3sRz6HmNeddkZAchRWspZN4mkMzAIZEVIy64yPME8mGUcNuuKV+7weBapL1IWN7RQh4K0QA9fcP8twtTZndnc/7QQBQoM195b4n7ZfBYyHWjrfRUkaqSP3X5/0gsGGe8IHtSNq/ROeEWFER/uiKEgc3hm+/s+f5wRKFanJdW70vnKVjQqwsxRRkQULEnrmlj/OlvqLKVngbEoL0wba7pCWdEmJFKSYPraH3qYkSVYSo080vJS7pLJ0R4or1o88RZEECeDN2zPvURIlia++omg+JuKRP6MjJg8q8GL8ipd1wRQFAsea0glwhIC7pXLpx8oCHLnBHVbx3rDhB+yLsjCsKAMq5IK0iTZEjLuk8vnzejcHuZu5xIj8ox1ufvT2ZDDa50DM1qk3SGVcUAILd3bz3SEXXE5d0AfqVHtupCwEy8LPEKwT4SJM2706nN+vMmY+OObQKB4y2vaHuuKIA8o6j70+5KG+q4glG19rik29zwX0gOubQBe7I81rLFEAM8K1lNV6mQ1suPjsE6YOiRjWsfZWLSYnoH//+J122uIaVCACArb0jpRKuoKERgQeW3ADt7wu8oyz4thpvinpO0/KS5mFQTNTVCgfZxH17dzodl/WXZQnYZIJQqcyQo9ASDgA1ROsWEEDHXFGgFKHmyo7/MxAy0SA65xc7eW0joytK3p1Ob4n1AMCw5eU8RwjmkIiGYH2wo/OHqp3mn5N8xLo2IISeiA8AUmb+Gux0xxUFChE6F6RaIQVvdKipxJBS+0UCOK7ign0jCILYTt0t8sL3sOXlvIRB3sf08Xcr+ahUCwEJmFfey7aNAoDdXSTgCt3HChPAfWR0Qam2auR9Ir9jMFFiqdkR7lWggKLhLdtvqO4DITnDF+hIIr9LpOzct93d7j3U7s8TVp9k5aFVdljd9frH6IoSpuwW4rZXQQzlOhmHuBdhDXV/Yg2XIM9ncbdaCPpHHpDpUG5wlnsR1lP3J9bwJWR/WAmdtYLAk/YWNdT9iTVcgm70avGWTltB4IkI6zmKItZwGfw6gdApOm0FgSciHF1RQjR/gMgGiDVcEm97tfhL560gMKfbWoXnC2evKtZwCcQtXZnOW0Fgjgg165hBccX3EWu4JOKWLk0vrCAwR4T1fQiqOzjad8QtXYpeWEFgQfPfmk5H5wdHxS19EXFLXyQB+LYPVhBYIMIiShqj+nIqcUuXRNzS56CxtmrUBysILBDh6IoSOPu1+gANIG7p8ohb+isMxHD2tk+HxhfOoqgpQAOIW7o04pb+QgrmW/1Kj9teSJUsFGHN7pC4pUsibukjehOMmeXZqUza6XF9T2FxS5dF3NLCDWV305dgzCzPirB2a8j0wZIc/n0JOXuIhMA3elf3Jhgzy4vzCeu0hgyEUOKWLsMWnz1MARr1KRr6lBdFWLM1NABH4pYux5aePYyZsl5FQ5+y1KTeeveGebRUhPgyW3j2MOlTUn4RtOwL/zjJjkDqI+rrDpYAfKl31LVvbkd0zqYcH+Yc0tevkba5xuiYQ6vxsehd2lcSIr5RmbrusxUEVhBh8caf1dm0loGY2F36tAHPf28XgdQBAIDzOeiK1H8dZXfrtIuvggYeim2SFp3cL/suQKDoO7oMTTStJWDApM7sXzYFMKrjHquSFxWoM/CDq0xEKTOnBJ1kE/f13el03LQYtdNjF7jQ85b668EYW0xv/rzaTdpeShMstScsaSIwQMAApA43nQ5UBdE5G5Dex68f8qLzNIZEdEasP2YTd/7udDqsYvT0MnRmCOmKlPnAL59347bX0hQribAMDNReRkUYato5bDtQ8/MnDF52vQ2eiLGpB0jfytrutyM9K0t7iZVECDRWRmV8SF0Uw0/SJV9uUIhRI/j4x9+zqAmrWBTb33o8+3BZep2Qf46VRQg0VkZV2eDKdRldUMrsfqz4YwaEiJVqzEXVr/SY0Gm3NCHimz4n5J9jLRE26AaFxeDKsyb3W7MQ1jvgTMCgdFHttF6L3vH84dakIhaxlgiBRt0gA1BErD/aiW3ExZtlwy4DBvmsvzOrUeuDpKOBmhSg0TYLENhAhECjbpABMGRSZ3VbladU1Is1BPiobqvYsULvFOCR5enNNgsQWCFZv4gWRkA37r5UnBhPABoxZbd15RbfnkwGmvQZQBH8nAYMMEYW2adtSkUsYiNLCLTy9G281rTi2tnw3irW5F5/+bwbW7aXYIyrvnYVbGMu8Dk2FiGQC9Hy9KbBN73RyGkNaRmDmt3rL593Y7C78S11sa25wOeoRIRAK296CPBRUzm5OtIyBAzqDNroV3pM7C59EiIRJRa6cyOt62TjPeEs0TkbO3VHAJ2hwXrG3L3hWwc7qtPFeXvCA63wEcxRxZdOAcQA32qrRlXudaNzNnZiIyZ1lou+dZJyT5xlQdL2iRQfqFSEQCuBmpIUjDHY3ehXupZmQA08ZBKARpanle6XPBRiCiAGKGF2d1Cu8QJ4n6hchECrQqzdKr47nQ6J9UfUdJIEReieKW9qVNUH00MhlqTIBRkTIXHIvm2bIGsRIdCuEFGjVazRJZ0lRQ3uqcdCLEkBxMz8dZusY20iBFoX4r1VrPINbcASzlJ5TrQUIkgdIheiqeK6FZNii6xjrSIE2hciKny6NtFdYA5J1cn96JxNlmUDYnXodUI/J0XPrWPtIgS8ECKwwdO17DETKBe15l4XCe4qj/p0orLmgRQ9FWMjIgS8EWJJihlBgpGysz9Yc+pckDqHVCkYpTJDlgwTDUipfTAP0OLaGYgJxZGfitzTXIg7hwBHaP99WYYUPRNjYyIEvBNiSQpGSoSUgRREKRgpCAbMhgDD+VpNy+ssSSp3T/18X14iRU251aZpVITAQ/cyBh16GqHrAikqtgb3XeVAH+BvwGYeCdh9+vNzcN32QtalcRECnQiVd4UUFYqxYwGbGehC7+BTV93SVkQIdCZU3hVSPAk6bVIS9vZkMlDQEYg+dOMhSddTi0//11GXtDURArNP3uBDhwIDvpOiKAkDkM4GnizpxLmH42ZPRRqdsyk6zEEpGE04bDgdsyZiCTemo4GBrvAQeCJKwI/OfD5qZEWk3uCR4DiE/16K7AmrosOBgW0hAVEMZgN/3p+0D+3yvREh0OXAQO9JiPgmc2qkVGbg1JCIDsAIQUs1SK5lTXWcOGkDr0RY0sEEcl9JMScXVz4sFQd7jt3vRGoP4LABUSb5n7zTgXZ63GULWOKlCAFxTz0gWaYooAzmBEEWKg72QDDMM8UNBEPMIfPD11j8Xj4unABQFE8kzO4HgRNWnARZkPRBfCXeihB4eOLCqWF3wuW9YKPTG79EWdmGpPQbAADBOOd+m/dzSqnvsyWEAOBckO7uIulq5HMZvBZhSXTOxv5lh1D6A8BDiItaF0XwxX3teilYl+iECEvERa2V++BL3y2Pb3RKhMBjF5WIDiBi3JQEoHGfAh1do3MiLBExVsLWD2Pxgc6KsETEuBaJWD9/6LwIS0SMS5GI+PyjNyIsETH+QkpAwqBYxOcnvRNhyX1VB4L9PHnMA2yXIBMQxezcN2KONetYxOcnvRXhLL9Yx3ZrHuskwUxZl4WOJd3gP1shwpKFNY/dtZCFq4m0FF4fy7r6zlaJcJZHNY+ly0oIiTn0rLHTLI9ER4SYrb1jzakIr7tsrQifUvYW1WxD0nrPOffbwyHXRk4IlMzr/pYwux9Kqe8iuv4hInyGvx1zGAQI4dzsXjKs9aaMBIQE4IQId+w4mTo97mr/FEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEFojP8HNZ+Z+RnaKewAAAAASUVORK5CYII=';
47
+
48
+ const zh = {
49
+ loading: '查询中…',
50
+ refresh: '刷新',
51
+ stale: '未更新',
52
+ remaining: '剩余',
53
+ rolling: '5小时滚动',
54
+ weekly: '7天(每周)',
55
+ monthly: '每月',
56
+ used: '已用',
57
+ errorTitle: '无法获取用量',
58
+ nav: '用量中心',
59
+ };
60
+
61
+ var styleId = 'dsh-provider-usage/styles.css';
62
+ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css="' + styleId + '"]') === null) {
63
+ var css = [
64
+ // 官方「模型」设置页完整样式(1:1 注入,dsh-client-ui-settings-models 同款)
65
+ ".zGbnIq_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}.zGbnIq_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}.zGbnIq_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.zGbnIq_notice{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}.zGbnIq_savedNotice{color:var(--dsw-alias-state-success-primary);margin:0;font-size:12px;line-height:18px}.zGbnIq_rows{flex-direction:column;gap:8px;margin:12px 0 0;padding:0;list-style:none;display:flex}.zGbnIq_rowCard{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;flex-direction:column;gap:12px;padding:12px 14px;display:flex}.zGbnIq_rowHead{align-items:center;gap:10px;display:flex}.zGbnIq_rowIdentity{align-items:center;gap:6px;min-width:0;display:inline-flex}.zGbnIq_rowName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.zGbnIq_rowTag{border:1px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:1px 6px;font-size:11px;line-height:16px}.zGbnIq_credentialDot{box-sizing:border-box;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.zGbnIq_credentialDotConfigured{background:var(--dsw-alias-state-success-primary)}.zGbnIq_credentialDotMissing{background:var(--dsw-alias-state-error-primary)}.zGbnIq_rowActions{align-items:center;gap:4px;margin-left:auto;display:inline-flex}.zGbnIq_primaryButton,.zGbnIq_secondaryButton,.zGbnIq_addButton{box-sizing:border-box;height:36px;font:inherit;cursor:pointer;border:none;border-radius:18px;justify-content:center;align-items:center;gap:4px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.zGbnIq_primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.zGbnIq_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.zGbnIq_secondaryButton,.zGbnIq_addButton{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0}.zGbnIq_secondaryButton:hover:not(:disabled),.zGbnIq_addButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.zGbnIq_secondaryButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.zGbnIq_dangerButton{box-sizing:border-box;height:36px;color:var(--dsw-alias-state-error-primary);font:inherit;cursor:pointer;background:0 0;border:none;border-radius:18px;justify-content:center;align-items:center;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.zGbnIq_dangerButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.zGbnIq_rowActions .zGbnIq_secondaryButton,.zGbnIq_rowActions .zGbnIq_dangerButton{border-radius:14px;height:28px;padding:0 10px;font-size:12px;line-height:18px}.zGbnIq_primaryButton:disabled,.zGbnIq_secondaryButton:disabled,.zGbnIq_dangerButton:disabled,.zGbnIq_addButton:disabled,.zGbnIq_linkButton:disabled,.zGbnIq_addModelButton:disabled{opacity:.4;cursor:default}.zGbnIq_primaryButton:focus-visible,.zGbnIq_secondaryButton:focus-visible,.zGbnIq_dangerButton:focus-visible,.zGbnIq_addButton:focus-visible,.zGbnIq_linkButton:focus-visible,.zGbnIq_addModelButton:focus-visible,.zGbnIq_iconButton:focus-visible,.zGbnIq_customizedSummary:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}.zGbnIq_editor{background:var(--dsw-alias-bg-module-platform);border-radius:12px;flex-direction:column;gap:14px;padding:14px 16px;display:flex}.zGbnIq_editorHeader{align-items:baseline;gap:8px;display:flex}.zGbnIq_editorTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.zGbnIq_editorRoute{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.zGbnIq_field{flex-direction:column;gap:6px;display:flex}.zGbnIq_fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:inline-flex}.zGbnIq_linkButton{box-sizing:border-box;height:28px;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:none;border-radius:14px;align-items:center;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex}.zGbnIq_linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.zGbnIq_advancedHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.zGbnIq_editorActions{justify-content:flex-end;gap:8px;display:flex}.zGbnIq_addBlock{flex-direction:column;gap:12px;display:flex}.zGbnIq_addActions{flex-wrap:wrap;gap:10px;display:flex}.zGbnIq_addButton{border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;flex:1 1 0;gap:6px;min-width:180px;height:44px}.zGbnIq_addCard,.zGbnIq_setupCard{background:var(--dsw-alias-bg-module-platform);border-radius:12px;flex-direction:column;gap:14px;padding:14px 16px;list-style:none;display:flex}.zGbnIq_addCard .zGbnIq_editor,.zGbnIq_setupCard .zGbnIq_editor{background:0 0;padding:0}.zGbnIq_customized{border-top:1px solid var(--dsw-alias-border-l2);padding-top:10px}.zGbnIq_customizedSummary{cursor:pointer;width:fit-content;color:var(--dsw-alias-label-secondary);border-radius:6px;align-items:center;gap:6px;margin-left:-4px;padding:2px 4px;font-size:12px;font-weight:500;line-height:18px;list-style:none;display:flex}.zGbnIq_customizedSummary::-webkit-details-marker{display:none}.zGbnIq_customizedSummary:before{content:\"\";border-bottom:1.5px solid;border-right:1.5px solid;width:5px;height:5px;transition:transform .12s;transform:rotate(-45deg)translate(-1px,-1px)}.zGbnIq_customized[open]>.zGbnIq_customizedSummary:before{transform:rotate(45deg)translate(-1px,-1px)}.zGbnIq_customizedSummary:hover{color:var(--dsw-alias-label-primary)}.zGbnIq_customizedBody{flex-direction:column;gap:12px;padding-top:12px;display:flex}.zGbnIq_modelCatalog{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:10px;padding-top:12px;display:flex}.zGbnIq_modelCatalogHeading{flex-direction:column;gap:2px;display:flex}.zGbnIq_modelCatalogTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.zGbnIq_modelListHead{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.zGbnIq_modelEntry{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px}.zGbnIq_iconButton{box-sizing:border-box;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;display:inline-flex}.zGbnIq_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.zGbnIq_iconButton:disabled{cursor:default;opacity:.4}.zGbnIq_iconButtonDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}.zGbnIq_modelAdvanced{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:8px;padding:8px 4px 2px;display:grid}.zGbnIq_modelField{flex-direction:column;gap:4px;display:flex}.zGbnIq_modelFieldLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.zGbnIq_modelEmpty{border:1px dashed var(--dsw-alias-border-l3);text-align:center;border-radius:8px;padding:12px}.zGbnIq_addModelButton{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);height:28px;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;background:0 0;border-radius:14px;align-self:flex-start;align-items:center;gap:4px;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex}.zGbnIq_addModelButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.zGbnIq_input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:14px;line-height:22px}select.zGbnIq_input{cursor:pointer;max-width:240px}.zGbnIq_input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.zGbnIq_input::placeholder{color:var(--dsw-alias-label-dimmed)}.zGbnIq_input:disabled{opacity:.6;cursor:default}.zGbnIq_selectInput{appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");background-position:right 12px center;background-repeat:no-repeat;background-size:12px 12px;padding-right:32px}.zGbnIq_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}.zGbnIq_deleteDialog{width:min(480px,100%)}.zGbnIq_deleteConfirm:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.zGbnIq_deleteConfirm:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.zGbnIq_hiddenLabel{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}@media (prefers-reduced-motion:reduce){.zGbnIq_customizedSummary:before{transition:none}}.zGbnIq_fetchDialog{--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);max-width:520px}.zGbnIq_candidateActions{justify-content:flex-end;margin-bottom:6px;display:flex}.zGbnIq_candidateList{flex-direction:column;gap:2px;max-height:320px;margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}.zGbnIq_candidate{border-radius:6px}.zGbnIq_candidateLabel{cursor:pointer;align-items:center;gap:8px;padding:6px 8px;display:flex}.zGbnIq_candidateId{font-family:var(--ds-font-family-code);overflow-wrap:anywhere;flex:auto;font-size:13px}",
66
+ // 官方「插件」设置页二级菜单样式(1:1 注入,dsh-client-ui-settings-plugins PluginsSettingsSection 同款)
67
+ ".pbvGtq_section{max-width:760px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}.pbvGtq_heading{margin:0;font-size:18px;font-weight:600}.pbvGtq_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px}.pbvGtq_tabs{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:flex-end;gap:22px;margin-top:2px;display:flex}.pbvGtq_tab{color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:0;padding:7px 1px 9px;font-size:13px;line-height:20px;position:relative}.pbvGtq_tab:hover,.pbvGtq_tab[data-active=true]{color:var(--dsw-alias-label-primary)}.pbvGtq_tab[data-active=true]:after,.pbvGtq_tab:focus-visible:after{background:var(--dsw-alias-label-primary);content:\"\";border-radius:2px 2px 0 0;height:2px;position:absolute;bottom:-1px;left:0;right:0}.pbvGtq_tab:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;color:var(--dsw-alias-label-primary);border-radius:2px}.pbvGtq_panel{min-width:0;padding-top:2px}",
68
+ // 用量统计页样式
69
+ ".zGbnIq_statsGrid{grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin:12px 0;display:grid}.zGbnIq_stat{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);border-radius:10px;flex-direction:column;gap:3px;padding:10px 12px;display:flex}.zGbnIq_statValue{color:var(--dsw-alias-label-primary);font-size:17px;font-weight:600;font-variant-numeric:tabular-nums;line-height:24px;white-space:nowrap}.zGbnIq_cntCard{cursor:pointer;transition:border-color .15s,background .15s}.zGbnIq_cntCard:hover{border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-interactive-bg-hover-solid)}.zGbnIq_cntCard:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}.zGbnIq_statLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}.zGbnIq_chips{flex-wrap:wrap;gap:6px;margin:10px 0 2px;display:flex}.zGbnIq_chip{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border-radius:999px;padding:2px 12px;font-size:12px;line-height:18px}.zGbnIq_chip:hover{color:var(--dsw-alias-label-primary)}.zGbnIq_chip[data-active=true]{border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 10%,transparent)}",
70
+ ".zGbnIq_json{max-height:260px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;margin:10px 0 0;padding:8px 10px;color:var(--dsw-alias-label-secondary);font-family:var(--ds-font-family-code);font-size:12px;line-height:18px;white-space:pre-wrap;word-break:break-all}",
71
+ ".zGbnIq_progress{height:3px;border-radius:999px;background:rgba(255,255,255,.08);overflow:hidden;position:relative;margin:8px 0 2px}.zGbnIq_progress i{position:absolute;top:0;bottom:0;width:36%;border-radius:999px;background:var(--dsw-alias-brand-primary,#6c8ef5);animation:zGbnIq-progress 1.2s ease-in-out infinite}@keyframes zGbnIq-progress{0%{left:-36%}100%{left:100%}}",
72
+ ".zGbnIq_modelTable{width:100%;border-collapse:collapse;font-size:12px;line-height:18px}.zGbnIq_modelTable th,.zGbnIq_modelTable td{text-align:left;padding:5px 8px;vertical-align:middle;white-space:nowrap}.zGbnIq_modelTable thead th{color:var(--dsw-alias-label-tertiary);font-weight:500;border-bottom:1px solid var(--dsw-alias-border-l2)}.zGbnIq_modelTable tbody tr{border-bottom:1px solid var(--dsw-alias-border-l2)}.zGbnIq_modelTable tbody tr:last-child{border-bottom:none}.zGbnIq_modelTable tbody tr:hover{background:var(--dsw-alias-interactive-bg-hover)}.zGbnIq_modelTable .zGbnIq_num{text-align:right;font-variant-numeric:tabular-nums;overflow:visible}.zGbnIq_modelTable .zGbnIq_numText{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.zGbnIq_modelTable th.zGbnIq_candidateId{max-width:280px;overflow:visible;font-family:var(--ds-font-family-code);font-weight:400;font-size:13px}.zGbnIq_modelTable .zGbnIq_modelName{display:inline-block;max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.dsh-pu-numCell{cursor:pointer;display:inline-block;max-width:100%;vertical-align:bottom}",
73
+ // ── 常驻用量卡片(Rainytoken 暗色视觉:暖深底/草莓粉/三色窗口条)──
74
+ '.dsh-pu-card{position:fixed;right:16px;bottom:16px;width:304px;box-sizing:border-box;border-radius:16px;pointer-events:auto;background:linear-gradient(180deg,#2A1F25 0%,#241A20 100%);border:1px solid #4D3A42;box-shadow:0 10px 28px rgba(31,20,25,.5),0 2px 6px rgba(31,20,25,.4);padding:14px 14px 12px;font-family:inherit;color:#EFE0E5;font-size:13px;line-height:1.5;touch-action:none;user-select:none;cursor:grab;transition:border-color .2s,transform .2s,box-shadow .2s}',
75
+ '.dsh-pu-card:hover{border-color:#6b5560;transform:translateY(-1px);box-shadow:0 12px 32px rgba(31,20,25,.55),0 2px 8px rgba(31,20,25,.45)}',
76
+ '.dsh-pu-card-head{display:flex;align-items:center;gap:8px;margin-bottom:10px}',
77
+ '.dsh-pu-logo{width:30px;height:30px;border-radius:8px;flex:none;display:flex;align-items:center;justify-content:center;overflow:hidden;background:transparent}',
78
+ '.dsh-pu-logo.go svg,.dsh-pu-logo.deepseek img{display:block}',
79
+ '.dsh-pu-cc-logo{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;background:linear-gradient(135deg,#5B3A9E,#8E5BD8);color:#fff;font-size:13px;font-weight:800;letter-spacing:.5px;font-family:inherit;flex:none}',
80
+ '.dsh-pu-cc-logo-sm{width:22px;height:22px;border-radius:6px;font-size:10px}',
81
+ '.dsh-pu-cc-sub{display:flex;align-items:center;gap:10px;margin:-6px 0 6px;font-size:11px;color:#C9B8BE;white-space:nowrap}',
82
+ '.dsh-pu-cc-sub .dsh-pu-cc-used{color:#EFE0E5;font-weight:600}',
83
+ '.dsh-pu-cc-sub .dsh-pu-cc-remain{color:#FFB3C6;font-weight:600}',
84
+ '.dsh-pu-title-btn{display:flex;align-items:center;gap:7px;cursor:pointer;border:none;background:none;padding:0;margin:0;font:inherit;flex:1;min-width:0;text-align:left;color:inherit;border-radius:8px}',
85
+ '.dsh-pu-title-btn:hover{background:rgba(255,179,198,.07)}',
86
+ '.dsh-pu-title-btn .caret{font-size:9px;color:#A9949D;flex:none;transition:transform .15s,color .15s}',
87
+ '.dsh-pu-title-btn:hover .caret{color:#FFB3C6}',
88
+ '.dsh-pu-menu{position:absolute;left:10px;top:46px;width:232px;box-sizing:border-box;background:#2A1F25;border:1px solid #4D3A42;border-radius:12px;box-shadow:0 14px 36px rgba(31,20,25,.65);padding:6px;z-index:5;display:flex;flex-direction:column;gap:2px}',
89
+ '.dsh-pu-menu-item{display:flex;align-items:center;gap:9px;width:100%;border:none;background:transparent;color:#EFE0E5;border-radius:8px;padding:8px 10px;font:inherit;font-size:12px;font-weight:600;cursor:pointer;text-align:left}',
90
+ '.dsh-pu-menu-item:hover{background:rgba(255,179,198,.1)}',
91
+ '.dsh-pu-menu-item.active{background:rgba(255,179,198,.14);color:#FFB3C6}',
92
+ '.dsh-pu-menu-item .mini-logo{width:22px;height:22px;border-radius:6px;flex:none;display:flex;align-items:center;justify-content:center;overflow:hidden}',
93
+ '.dsh-pu-menu-item .check{margin-left:auto;color:#FFB3C6;font-size:12px}',
94
+ '.dsh-pu-card-title{font-size:14px;font-weight:600;color:#EFE0E5;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
95
+ '.dsh-pu-chip{display:inline-flex;align-items:center;gap:5px;border-radius:999px;padding:2px 8px;font-size:10px;font-weight:600;flex:none}',
96
+ '.dsh-pu-chip.ok{background:rgba(102,187,106,.14);color:#7ED68A}',
97
+ '.dsh-pu-chip.warn{background:rgba(255,167,38,.15);color:#FFC46B}',
98
+ '.dsh-pu-chip.err{background:rgba(233,30,99,.16);color:#F06292}',
99
+ '.dsh-pu-chip i{width:6px;height:6px;border-radius:50%;background:currentColor;font-style:normal}',
100
+ '.dsh-pu-chip.dsh-pu-chip-loading i{animation:dsh-pu-ms-breathe 1.2s ease-in-out infinite}',
101
+ '.dsh-pu-meta{font-size:10px;color:#C9B8BE;white-space:nowrap}',
102
+ '.dsh-pu-refresh{width:24px;height:24px;border-radius:50%;border:1px solid rgba(255,179,198,.25);background:rgba(255,179,198,.12);color:#FFB3C6;font-size:13px;line-height:1;cursor:pointer;flex:none;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}',
103
+ '.dsh-pu-refresh:hover{background:rgba(255,179,198,.22);color:#FFD1DC}',
104
+ '.dsh-pu-remain{display:flex;align-items:baseline;gap:8px;margin:2px 0 10px}',
105
+ '.dsh-pu-remain-big{font-size:28px;font-weight:700;font-variant-numeric:tabular-nums;color:#EFE0E5;letter-spacing:.5px}',
106
+ '.dsh-pu-remain-label{font-size:11px;color:#C9B8BE}',
107
+ '.dsh-pu-row{display:grid;grid-template-columns:44px 42px 1fr 58px;align-items:center;gap:8px;margin:7px 0}',
108
+ '.dsh-pu-row-label{font-size:11px;color:#C9B8BE;white-space:nowrap}',
109
+ '.dsh-pu-row-used{font-size:11px;font-weight:600;color:#EFE0E5;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}',
110
+ '.dsh-pu-bar{height:5px;border-radius:999px;background:rgba(255,255,255,.07);overflow:hidden}',
111
+ '.dsh-pu-bar-fill{height:100%;border-radius:999px;transition:width .3s}',
112
+ '.dsh-pu-bar-fill.pink{background:#FF85A2}.dsh-pu-bar-fill.orange{background:#FFA726}.dsh-pu-bar-fill.magenta{background:#E91E63}',
113
+ '.dsh-pu-reset{font-size:10px;color:#B9A7AE;text-align:right;white-space:nowrap;font-variant-numeric:tabular-nums}',
114
+ '.dsh-pu-error{border:1px solid rgba(233,30,99,.4);border-radius:10px;padding:8px 10px;font-size:11px;color:#C9B8BE}',
115
+ '.dsh-pu-error b{color:#F06292}',
116
+ '.dsh-pu-card-foot{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:10px;padding-top:9px;border-top:1px solid rgba(255,255,255,.06)}',
117
+ // ── 官方样式补充:状态点三态(官方只有 configured/missing)与添加按钮加号 ──
118
+ '.zGbnIq_credentialDot.dsh-pu-dot-loading{background:var(--dsw-alias-state-success-primary,#22c55e);animation:dsh-pu-ms-breathe 1.2s ease-in-out infinite}',
119
+ '.zGbnIq_credentialDot.dsh-pu-dot-gray{background:var(--dsw-alias-label-tertiary,#8b93a7)}',
120
+ '@keyframes dsh-pu-ms-breathe{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.72)}}',
121
+ '.ms-plus{color:var(--dsw-alias-brand-primary);font-size:15px;line-height:1}',
122
+ ].join('');
123
+ var tag = document.createElement('style');
124
+ tag.dataset.plugin = 'dsh-provider-usage';
125
+ tag.dataset.pluginCss = styleId;
126
+ tag.textContent = css;
127
+ document.head.appendChild(tag);
128
+ }
129
+
130
+ // ── 设置存储(localStorage,即时生效)───────────────────────────────
131
+ function loadSettings() {
132
+ try {
133
+ var s = JSON.parse(window.localStorage.getItem(SETTINGS_KEY) || 'null');
134
+ if (s && typeof s === 'object' &&
135
+ (s.settingsVersion === DEFAULT_SETTINGS.settingsVersion || s.settingsVersion === 3 || s.settingsVersion === 4)) {
136
+ // v3→v4 新增 providerId(默认 opencode-go),其余偏好保留
137
+ return Object.assign({}, DEFAULT_SETTINGS, s, {
138
+ providerId: typeof s.providerId === 'string' ? s.providerId : DEFAULT_SETTINGS.providerId,
139
+ });
140
+ }
141
+ // 设置结构升级:v2→v3 移除用户可配置的 zIndex(固定与 dsh-pet 一致),其余偏好保留
142
+ if (s && typeof s === 'object') {
143
+ return Object.assign({}, DEFAULT_SETTINGS, {
144
+ visible: typeof s.visible === 'boolean' ? s.visible : true,
145
+ refreshMs: typeof s.refreshMs === 'number' ? s.refreshMs : DEFAULT_SETTINGS.refreshMs,
146
+ });
147
+ }
148
+ } catch (e) { /* ignore */ }
149
+ return Object.assign({}, DEFAULT_SETTINGS);
150
+ }
151
+ var settingsState = loadSettings();
152
+ var settingsListeners = new Set();
153
+ function saveSettings() {
154
+ try { window.localStorage.setItem(SETTINGS_KEY, JSON.stringify(settingsState)); } catch (e) { /* ignore */ }
155
+ }
156
+ function getSettings() { return settingsState; }
157
+ function setSettings(patch) {
158
+ settingsState = Object.assign({}, settingsState, patch);
159
+ saveSettings();
160
+ settingsListeners.forEach(function (fn) { fn(); });
161
+ }
162
+ function subscribeSettings(fn) {
163
+ settingsListeners.add(fn);
164
+ return function () { settingsListeners.delete(fn); };
165
+ }
166
+ function useSettings() {
167
+ var [s, setS] = useState(getSettings);
168
+ useEffect(function () { return subscribeSettings(function () { setS(getSettings()); }); }, []);
169
+ return s;
170
+ }
171
+
172
+ // ── 供应商实例清单(localStorage,即时生效)──────────────────────
173
+ function loadProviders() {
174
+ try {
175
+ var arr = JSON.parse(window.localStorage.getItem(PROVIDERS_KEY) || 'null');
176
+ if (Array.isArray(arr)) {
177
+ if (arr.length === 0) return []; // 用户删光后保持空清单,不复活默认实例
178
+ return arr.filter(function (p) {
179
+ return p && typeof p === 'object' && typeof p.adapter === 'string' && ADAPTER_META[p.adapter];
180
+ }).map(function (p) {
181
+ var name = typeof p.name === 'string' && p.name ? p.name : (ADAPTER_META[p.adapter] || {}).displayName;
182
+ if (String(p.id || '') === 'deepseek-balance' && name === 'DeepSeek 余额') name = 'DeepSeek'; // 0.4.0 更名
183
+ // source:Key 解析位置(vault=插件私有库直取;dsh=DSH 凭证链);type:创建方式(import=导入 / manual=自定义)
184
+ var source = p.source === 'vault' ? 'vault' : 'dsh';
185
+ // 显式 type 优先;旧数据无 type 时由 source 推导(vault→manual,其余→import)
186
+ var type = p.type === 'manual' || p.type === 'import' ? p.type : (source === 'vault' ? 'manual' : 'import');
187
+ return { id: String(p.id || ''), name: name, adapter: p.adapter, ref: typeof p.ref === 'string' ? p.ref : '', source: source, type: type };
188
+ });
189
+ }
190
+ } catch (e) { /* ignore */ }
191
+ return DEFAULT_PROVIDERS.slice();
192
+ }
193
+ var providersState = loadProviders();
194
+ var providersListeners = new Set();
195
+ function saveProviders() {
196
+ try { window.localStorage.setItem(PROVIDERS_KEY, JSON.stringify(providersState)); } catch (e) { /* ignore */ }
197
+ }
198
+ function getProviders() { return providersState; }
199
+ function setProviders(list) {
200
+ providersState = list;
201
+ saveProviders();
202
+ providersListeners.forEach(function (fn) { fn(); });
203
+ }
204
+ function subscribeProviders(fn) {
205
+ providersListeners.add(fn);
206
+ return function () { providersListeners.delete(fn); };
207
+ }
208
+ function useProviders() {
209
+ var [p, setP] = useState(getProviders);
210
+ useEffect(function () { return subscribeProviders(function () { setP(getProviders()); }); }, []);
211
+ return p;
212
+ }
213
+ /** 当前选中实例;若被删除则回落到第一个。 */
214
+ function currentProvider(providers, providerId) {
215
+ for (var i = 0; i < providers.length; i++) if (providers[i].id === providerId) return providers[i];
216
+ return providers[0] || null;
217
+ }
218
+
219
+ // ── 工具 ──────────────────────────────────────────────────────────
220
+ function fmtTime(ms) {
221
+ if (!ms) return '-';
222
+ var d = new Date(ms);
223
+ var hh = String(d.getHours()).padStart(2, '0');
224
+ var mm = String(d.getMinutes()).padStart(2, '0');
225
+ var ss = String(d.getSeconds()).padStart(2, '0');
226
+ return hh + ':' + mm + ':' + ss;
227
+ }
228
+ /** Rainytoken 小组件 formatReset 同款:>1 天 → “3d4h”/“3d”,>1 时 → “5h32m”/“5h”,否则 “42m”/“<1m”;无效返回空串 */
229
+ function fmtResetIn(ms) {
230
+ if (!ms || ms <= 0) return '';
231
+ var sec = Math.floor(ms / 1000);
232
+ var days = Math.floor(sec / 86400);
233
+ var hours = Math.floor((sec % 86400) / 3600);
234
+ var minutes = Math.floor((sec % 3600) / 60);
235
+ if (days > 0) return days + 'd' + (hours > 0 ? hours + 'h' : '');
236
+ if (hours > 0) return hours + 'h' + (minutes > 0 ? minutes + 'm' : '');
237
+ if (minutes > 0) return minutes + 'm';
238
+ return '<1m';
239
+ }
240
+ function WindowRow(props) {
241
+ // Rainytoken 小组件同款行:标签 | 已用% | 进度条(窗口固定色)| 重置倒计时(紧凑格式,如 3d4h / 5h32m / 42m)
242
+ var label = props.label, win = props.win || {}, bar = props.bar || 'pink';
243
+ var used = win.usedPct;
244
+ // 兼容读:resetsAt 为统一命名(parseUsage 与 Command Code 均输出);resetAt 兜底旧缓存载荷
245
+ var resetsAt = win.resetsAt || win.resetAt;
246
+ var remain = resetsAt ? new Date(resetsAt).getTime() - Date.now() : 0;
247
+ var resetText = fmtResetIn(remain);
248
+ return React.createElement('div', { className: 'dsh-pu-row' },
249
+ React.createElement('span', { className: 'dsh-pu-row-label' }, label),
250
+ React.createElement('span', { className: 'dsh-pu-row-used' }, used === null ? '--' : Math.round(used) + '%'),
251
+ React.createElement('div', { className: 'dsh-pu-bar' },
252
+ React.createElement('div', { className: 'dsh-pu-bar-fill ' + bar, style: { width: Math.min(100, used === null ? 0 : used) + '%' } })),
253
+ React.createElement('span', { className: 'dsh-pu-reset', title: resetText ? '约 ' + resetText + ' 后重置' : '' }, resetText));
254
+ }
255
+
256
+ // ── 用量卡片(布局借鉴 Rainytoken 小组件卡片,常驻右下角)──────────
257
+ function UsageCard() {
258
+ var s = useSettings();
259
+ var providers = useProviders();
260
+ var [data, setData] = useState(null);
261
+ var [loading, setLoading] = useState(false);
262
+ var [loadError, setLoadError] = useState(null);
263
+ var [menuOpen, setMenuOpen] = useState(false);
264
+ var menuRef = useRef(null);
265
+ var cardRef = useRef(null);
266
+ // dsh-pet 同款拖拽:pointer 事件 + right/bottom 位置记忆(存 localStorage)
267
+ var [dragPos, setDragPos] = useState(null);
268
+ var dragRef = useRef(null);
269
+ var draggedRef = useRef(false);
270
+ // 整卡入屏钳制:右/下距离 ≤ 视口 − 卡片自身宽/高 → 左/上边缘永不为负
271
+ function clampCard(right, bottom, w, h) {
272
+ return {
273
+ right: Math.max(0, Math.min(right, Math.max(0, window.innerWidth - w))),
274
+ bottom: Math.max(0, Math.min(bottom, Math.max(0, window.innerHeight - h))),
275
+ };
276
+ }
277
+ function onPointerDown(e) {
278
+ if (e.button && e.button !== 0) return;
279
+ e.preventDefault();
280
+ if (e.target && e.target.setPointerCapture) e.target.setPointerCapture(e.pointerId);
281
+ var start = dragPos || { right: s.right, bottom: s.bottom };
282
+ dragRef.current = { startX: e.clientX, startY: e.clientY, right: start.right, bottom: start.bottom };
283
+ draggedRef.current = false;
284
+ }
285
+ function onPointerMove(e) {
286
+ var d = dragRef.current;
287
+ if (!d) return;
288
+ var dx = e.clientX - d.startX;
289
+ var dy = e.clientY - d.startY;
290
+ if (Math.abs(dx) > 4 || Math.abs(dy) > 4) draggedRef.current = true;
291
+ var el = cardRef.current;
292
+ var w = el && el.offsetWidth ? el.offsetWidth : 304;
293
+ var h = el && el.offsetHeight ? el.offsetHeight : 200;
294
+ setDragPos(clampCard(d.right - dx, d.bottom - dy, w, h));
295
+ }
296
+ function clearDrag() {
297
+ dragRef.current = null;
298
+ if (dragPos) setSettings({ right: dragPos.right, bottom: dragPos.bottom });
299
+ // 关键:拖完必须清空 dragPos,否则渲染永远用拖拽时固化的坐标,
300
+ // resize/挂载钳制只改 settings,pos 却停留在旧的越界值上。
301
+ setDragPos(null);
302
+ }
303
+ function onPointerUp() {
304
+ if (!dragRef.current) return;
305
+ clearDrag();
306
+ }
307
+ // 挂载恢复 / 内容高度变化时,把持久化位置拉回视口内;拖动中跳过
308
+ useLayoutEffect(function () {
309
+ if (dragRef.current || !s.visible) return;
310
+ var el = cardRef.current;
311
+ if (!el || !el.offsetWidth || !el.offsetHeight) return;
312
+ var c = clampCard(s.right, s.bottom, el.offsetWidth, el.offsetHeight);
313
+ if (c.right !== s.right || c.bottom !== s.bottom) setSettings(c);
314
+ }, [s.right, s.bottom, s.visible, data, loading, loadError]);
315
+ // 窗口缩放后同样拉回(防止 resize 后越界)
316
+ useLayoutEffect(function () {
317
+ if (!s.visible) return undefined;
318
+ function onResize() {
319
+ if (dragRef.current) return;
320
+ var el = cardRef.current;
321
+ if (!el || !el.offsetWidth || !el.offsetHeight) return;
322
+ var c = clampCard(s.right, s.bottom, el.offsetWidth, el.offsetHeight);
323
+ if (c.right !== s.right || c.bottom !== s.bottom) setSettings(c);
324
+ }
325
+ window.addEventListener('resize', onResize);
326
+ return function () { window.removeEventListener('resize', onResize); };
327
+ }, [s.right, s.bottom, s.visible, data]);
328
+ var current = currentProvider(providers, s.providerId);
329
+ var multiple = providers.length > 1;
330
+ var curKey = current ? current.id + '|' + current.adapter + '|' + current.ref : 'none';
331
+
332
+ var load = useCallback(function (force) {
333
+ if (!current) return;
334
+ setLoading(true);
335
+ // force=true 时绕过 host 30s 缓存(noCache=1),手动点刷新/改 Key 后走真实上游
336
+ var qs = 'adapter=' + encodeURIComponent(current.adapter) + (current.ref ? '&ref=' + encodeURIComponent(current.ref) : '') + (current.source === 'vault' ? '&source=vault' : '') + (force ? '&noCache=1' : '');
337
+ fetch('/api/provider-usage/query?' + qs, { headers: { accept: 'application/json' } })
338
+ .then(function (res) { return res.json().catch(function () { return null; }); })
339
+ .then(function (json) { setData(json); setLoadError(null); })
340
+ .catch(function (e) { setLoadError(String((e && e.message) || e)); })
341
+ .finally(function () { setLoading(false); });
342
+ }, [curKey]);
343
+
344
+ useEffect(function () {
345
+ load();
346
+ var timer = window.setInterval(load, s.refreshMs || 30000);
347
+ return function () { window.clearInterval(timer); };
348
+ }, [load, s.refreshMs]);
349
+
350
+ // 点击卡片外部关闭菜单
351
+ useEffect(function () {
352
+ if (!menuOpen) return undefined;
353
+ function onDoc(e) {
354
+ var card = cardRef.current;
355
+ if (card && !card.contains(e.target)) setMenuOpen(false);
356
+ }
357
+ document.addEventListener('mousedown', onDoc);
358
+ return function () { document.removeEventListener('mousedown', onDoc); };
359
+ }, [menuOpen]);
360
+
361
+ if (!s.visible || !current) return null;
362
+
363
+ // 状态语义:请求中=呼吸绿“查询中”(优先于旧数据状态);正常/注意(stale 或余额不足)/错误
364
+ var loadingCls = loading ? ' dsh-pu-chip-loading' : '';
365
+ var level = loading ? 'ok' : 'err';
366
+ var chipText = loading ? zh.loading : '错误';
367
+ if (!loading && data && data.ok && data.isValid !== false) { level = 'ok'; chipText = '正常'; }
368
+ else if (!loading && data && data.error) { level = 'err'; chipText = '查询失败'; }
369
+ else if (!loading && data && data.stale) { level = 'warn'; chipText = '未更新'; }
370
+ else if (!loading && data && data.ok && data.isValid === false) { level = 'warn'; chipText = '注意'; }
371
+ var title = current.name;
372
+ var meta = data && data.fetchedAt ? fmtTime(data.fetchedAt) + (data.stale ? ' · ' + zh.stale : '') : zh.loading;
373
+ var isBalance = current.adapter === 'balance-json';
374
+ var isCommandCode = current.adapter === 'commandcode-credits';
375
+ var logoClass = isBalance ? 'deepseek' : (isCommandCode ? 'commandcode' : 'go');
376
+ var logoEl = isBalance
377
+ ? React.createElement('img', { src: LOGO_DEEPSEEK, width: 24, height: 24, alt: '' })
378
+ : (isCommandCode
379
+ ? React.createElement('span', { className: 'dsh-pu-cc-logo', 'aria-hidden': 'true' }, 'CC')
380
+ : LOGO_OCGO);
381
+
382
+ var body = null;
383
+ if (loading && !data) {
384
+ body = React.createElement('div', null, zh.loading);
385
+ } else if (!data && loadError) {
386
+ body = React.createElement('div', { className: 'dsh-pu-error' }, React.createElement('b', null, zh.errorTitle + ':'), String(loadError));
387
+ } else if (data && !data.ok && !data.stale) {
388
+ var em = (data.error && data.error.message) || '未知错误';
389
+ body = React.createElement('div', { className: 'dsh-pu-error' }, React.createElement('b', null, zh.errorTitle + ':'), ' ' + em);
390
+ } else if (data) {
391
+ var isPct = data.unit === '%';
392
+ var main = data.remaining === null ? '--' : (isPct ? Math.round(data.remaining) + '%' : Number(data.remaining).toFixed(2) + ' ' + data.unit);
393
+ var mainLabel = zh.remaining + (isPct ? '(' + zh.monthly + ')' : '');
394
+ var winRows = data.windows
395
+ ? (isCommandCode
396
+ ? [React.createElement(WindowRow, { key: 'r', label: zh.rolling, win: data.windows.fiveHour, bar: 'pink' }),
397
+ React.createElement(WindowRow, { key: 'w', label: zh.weekly, win: data.windows.weekly, bar: 'orange' }),
398
+ // 订阅失败/未知计划 → 月% 推算不出(null):隐藏整行,避免「0% 还没用」的误导
399
+ (data.monthly && data.monthly.usedPct !== null
400
+ ? React.createElement(WindowRow, { key: 'm', label: zh.monthly, win: data.monthly, bar: 'magenta' })
401
+ : null)].filter(Boolean)
402
+ : [React.createElement(WindowRow, { key: 'r', label: zh.rolling, win: data.windows.rolling, bar: 'pink' }),
403
+ React.createElement(WindowRow, { key: 'w', label: zh.weekly, win: data.windows.weekly, bar: 'orange' }),
404
+ React.createElement(WindowRow, { key: 'm', label: zh.monthly, win: data.windows.monthly, bar: 'magenta' })])
405
+ : null;
406
+ var warn = data.isValid === false && data.invalidMessage
407
+ ? React.createElement('div', { className: 'dsh-pu-error' }, data.invalidMessage)
408
+ : null;
409
+ // Command Code 混合卡:主数字 = 月已用百分比,副行 = 已用/总额 + 剩余额度
410
+ var cc = null;
411
+ if (isCommandCode) {
412
+ var usedPct = data.monthly && data.monthly.usedPct;
413
+ var usedStr = data.used === null ? '--' : Number(data.used).toFixed(2);
414
+ var quotaStr = data.totalQuota === null ? '--' : Number(data.totalQuota).toFixed(2);
415
+ var remainStr = data.remaining === null ? '--' : Number(data.remaining).toFixed(2);
416
+ cc = React.createElement('div', { className: 'dsh-pu-cc-sub' },
417
+ React.createElement('span', { className: 'dsh-pu-cc-used' }, '已用 $' + usedStr + ' / $' + quotaStr),
418
+ React.createElement('span', { className: 'dsh-pu-cc-remain' }, '剩余 $' + remainStr));
419
+ }
420
+ // 窗口行/主值/警告条已覆盖全部信息,extra 全文不再展示(保持卡片干净)
421
+ var extra = null;
422
+ body = React.createElement('div', null,
423
+ React.createElement('div', { className: 'dsh-pu-remain' },
424
+ React.createElement('span', { className: 'dsh-pu-remain-big' }, isCommandCode ? (usedPct === null ? '--' : Math.round(usedPct) + '%') : main),
425
+ React.createElement('span', { className: 'dsh-pu-remain-label' }, isCommandCode ? ' ' + zh.used + '(' + zh.monthly + ')' : ' ' + mainLabel)),
426
+ warn,
427
+ cc,
428
+ winRows,
429
+ extra);
430
+ }
431
+
432
+ var menu = null;
433
+ if (multiple && menuOpen) {
434
+ menu = React.createElement('div', { ref: menuRef, className: 'dsh-pu-menu' },
435
+ providers.map(function (p) {
436
+ var active = p.id === current.id;
437
+ var mini = p.adapter === 'balance-json'
438
+ ? React.createElement('img', { src: LOGO_DEEPSEEK, width: 16, height: 16, alt: '' })
439
+ : (p.adapter === 'commandcode-credits'
440
+ ? React.createElement('span', { className: 'dsh-pu-cc-logo dsh-pu-cc-logo-sm', 'aria-hidden': 'true' }, 'CC')
441
+ : LOGO_OCGO);
442
+ return React.createElement('button', {
443
+ key: p.id, type: 'button', className: 'dsh-pu-menu-item' + (active ? ' active' : ''),
444
+ onClick: function (e) { if (draggedRef.current) { draggedRef.current = false; return; } e.stopPropagation(); setSettings({ providerId: p.id }); setMenuOpen(false); },
445
+ },
446
+ React.createElement('span', { className: 'mini-logo' }, mini),
447
+ p.name,
448
+ active ? React.createElement('span', { className: 'check' }, '✓') : null);
449
+ }));
450
+ }
451
+
452
+ var pos = dragPos || { right: s.right, bottom: s.bottom };
453
+ return React.createElement('div', { ref: cardRef, className: 'dsh-pu-card',
454
+ style: { zIndex: Z_INDEX, right: pos.right, bottom: pos.bottom, cursor: dragRef.current ? 'grabbing' : 'grab' },
455
+ onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp,
456
+ onPointerCancel: clearDrag },
457
+ React.createElement('div', { className: 'dsh-pu-card-head' },
458
+ React.createElement('button', {
459
+ type: 'button', className: 'dsh-pu-title-btn',
460
+ title: multiple ? '选择提供方' : '',
461
+ onClick: function (e) { if (draggedRef.current) { draggedRef.current = false; return; } e.stopPropagation(); if (multiple) setMenuOpen(!menuOpen); },
462
+ },
463
+ React.createElement('span', { className: 'dsh-pu-logo ' + logoClass }, logoEl),
464
+ React.createElement('span', { className: 'dsh-pu-card-title' }, title),
465
+ multiple ? React.createElement('span', { className: 'caret' }, menuOpen ? '▴' : '▾') : null),
466
+ React.createElement('span', { className: 'dsh-pu-chip ' + level + loadingCls }, React.createElement('i', null), chipText)),
467
+ menu,
468
+ body,
469
+ React.createElement('div', { className: 'dsh-pu-card-foot' },
470
+ React.createElement('span', { className: 'dsh-pu-meta' }, meta + ' · 每 ' + Math.round((s.refreshMs || 30000) / 1000) + 's'),
471
+ React.createElement('button', { type: 'button', className: 'dsh-pu-refresh', title: zh.refresh, onClick: function () { if (draggedRef.current) { draggedRef.current = false; return; } load(true); } }, '↻')));
472
+ }
473
+
474
+ // ── 用量统计页(本地 Token 统计:DSH 会话日志折叠;只画可获取指标)──
475
+ function fmtCnt(n) {
476
+ n = Number(n) || 0;
477
+ if (n >= 1000000000000) return (n / 1000000000000).toFixed(1).replace(/\.0$/, '') + 'T';
478
+ if (n >= 1000000000) return (n / 1000000000).toFixed(1).replace(/\.0$/, '') + 'B';
479
+ if (n >= 1000000) return (n / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
480
+ if (n >= 1000) return (n / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
481
+ return String(n);
482
+ }
483
+ // 完整数字(千分位)
484
+ function fmtFull(n) {
485
+ try { return (Number(n) || 0).toLocaleString('en-US'); }
486
+ catch (e) { return String(Number(n) || 0); }
487
+ }
488
+ // 缩略数字单元格:点击在「缩写 ↔ 完整数字」之间切换
489
+ function NumCell(props) {
490
+ var value = Number(props.value) || 0;
491
+ var short = fmtCnt(value);
492
+ var long = fmtFull(value);
493
+ var [full, setFull] = useState(false);
494
+ if (short === long) return React.createElement('span', { className: props.className }, short);
495
+ return React.createElement('span', {
496
+ className: props.className + ' dsh-pu-numCell',
497
+ role: 'button',
498
+ tabIndex: 0,
499
+ 'aria-label': full ? '收起完整数值' : '展开完整数值(' + long + ')',
500
+ onClick: function () { setFull(!full); },
501
+ onKeyDown: function (e) {
502
+ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setFull(!full); }
503
+ },
504
+ }, full ? long : short);
505
+ }
506
+ // 缩略数字卡片:点击整张卡片在「缩写 ↔ 完整数字」之间切换(未缩略时不可点击)
507
+ function CntStatCard(props) {
508
+ var value = Number(props.value) || 0;
509
+ var short = fmtCnt(value);
510
+ var long = fmtFull(value);
511
+ var [full, setFull] = useState(false);
512
+ function renderBody() {
513
+ return [
514
+ React.createElement('span', { className: 'zGbnIq_statValue' }, full ? long : short),
515
+ React.createElement('span', { className: 'zGbnIq_statLabel' }, props.label),
516
+ ];
517
+ }
518
+ if (short === long) return React.createElement('div', { key: props.label, className: 'zGbnIq_stat' }, renderBody());
519
+ return React.createElement('div', {
520
+ key: props.label,
521
+ className: 'zGbnIq_stat zGbnIq_cntCard',
522
+ role: 'button',
523
+ tabIndex: 0,
524
+ 'aria-label': full ? '收起完整数值' : '展开完整数值(' + long + ')',
525
+ onClick: function () { setFull(!full); },
526
+ onKeyDown: function (e) {
527
+ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setFull(!full); }
528
+ },
529
+ }, renderBody());
530
+ }
531
+ // 按模型汇总表列宽(auto 布局下作为期望宽度提示;数字列内容自适应,长值不会截断)
532
+ var MODEL_COL_WIDTHS = ['34%', '12%', '16%', '18%', '12%', '8%'];
533
+ function fmtPct(rate) {
534
+ if (!(rate >= 0) || rate === null || rate === undefined) return '--';
535
+ return (rate * 100).toFixed(1) + '%';
536
+ }
537
+ function UsageStatsPage(props) {
538
+ var active = !!props.active;
539
+ var [data, setData] = useState(null);
540
+ var [loading, setLoading] = useState(false);
541
+ var [err, setErr] = useState(null);
542
+ var [provider, setProvider] = useState(''); // '' = 全部
543
+ var [range, setRange] = useState('1'); // 默认近 1 天;'0'=全部 / '1'=近1天 / '7'=近7天 / '30'=近30天
544
+ var dataRef = useRef(null); // 轮询静默刷新判定用(避免闭包陈旧)
545
+ function setDataBoth(v) { dataRef.current = v; setData(v); }
546
+ function load(force) {
547
+ // 自然日语义:近 1 天 = 今天 00:00 起;近 7/30 天 = 今天 + 前 N-1 个完整自然日(0 点边界)
548
+ var nowD = new Date(Date.now());
549
+ var todayStart = new Date(nowD.getFullYear(), nowD.getMonth(), nowD.getDate()).getTime();
550
+ var since = range === '1' ? todayStart : (range === '7' ? todayStart - 6 * 86400000 : (range === '30' ? todayStart - 29 * 86400000 : 0));
551
+ var q = '/api/provider-usage/local-usage?provider=' + encodeURIComponent(provider) + '&since=' + since + (force ? '&noCache=1' : '');
552
+ // 有旧数据时静默刷新(不闪进度条/查询中文案);首查或手动刷新才显示加载态
553
+ if (!dataRef.current || force) setLoading(true);
554
+ setErr(null);
555
+ fetch(q, { headers: { accept: 'application/json' } })
556
+ .then(function (r) { return r.json().catch(function () { return null; }); })
557
+ .then(function (j) {
558
+ if (j && j.ok && j.detected !== false) { setDataBoth(j); if (j.error) setErr(j.error.message); }
559
+ else if (j && j.error) { setErr(j.error.message || '无法读取本地用量统计'); }
560
+ else { setErr('无法读取本地用量统计'); }
561
+ })
562
+ .catch(function () { setErr('读取失败'); })
563
+ .finally(function () { setLoading(false); });
564
+ }
565
+ useEffect(function () { if (active) load(false); }, [active, provider, range]);
566
+ useEffect(function () {
567
+ if (!active) return undefined;
568
+ var t = window.setInterval(function () { load(false); }, 30000);
569
+ return function () { window.clearInterval(t); };
570
+ }, [active, provider, range]);
571
+
572
+ var total = data && data.totals ? data.totals : null;
573
+ // host 按天物化聚合后直接给出 providers / byModel(不再有会话级数据)
574
+ var provs = data && Array.isArray(data.providers) ? data.providers.slice() : [];
575
+ provs.sort();
576
+ var byModel = (data && data.byModel && typeof data.byModel === 'object') ? data.byModel : {};
577
+ var modelNames = Object.keys(byModel).sort();
578
+ // ── 按模型汇总表 ──
579
+ function ModelNameCell(props) {
580
+ var name = props.name;
581
+ return React.createElement('span', {
582
+ className: 'zGbnIq_modelName',
583
+ tabIndex: 0,
584
+ title: name,
585
+ }, name);
586
+ }
587
+ function ModelSummaryTable(props) {
588
+ var rows = props.rows;
589
+ return React.createElement('table', { className: 'zGbnIq_modelTable' },
590
+ React.createElement('colgroup', null,
591
+ MODEL_COL_WIDTHS.map(function (w, i) { return React.createElement('col', { key: i, style: { width: w } }); })),
592
+ React.createElement('thead', null,
593
+ React.createElement('tr', null,
594
+ React.createElement('th', { scope: 'col' }, '模型'),
595
+ React.createElement('th', { scope: 'col', className: 'zGbnIq_num' }, '请求'),
596
+ React.createElement('th', { scope: 'col', className: 'zGbnIq_num' }, '输入'),
597
+ React.createElement('th', { scope: 'col', className: 'zGbnIq_num' }, '输出'),
598
+ React.createElement('th', { scope: 'col', className: 'zGbnIq_num' }, '缓存读取'),
599
+ React.createElement('th', { scope: 'col', className: 'zGbnIq_num' }, '命中率'))),
600
+ React.createElement('tbody', null,
601
+ rows.map(function (r) {
602
+ return React.createElement('tr', { key: r.name },
603
+ React.createElement('th', { scope: 'row', className: 'zGbnIq_candidateId' }, React.createElement(ModelNameCell, { name: r.name })),
604
+ React.createElement('td', { className: 'zGbnIq_num' }, React.createElement('span', { className: 'zGbnIq_numText' }, String(r.requests))),
605
+ React.createElement('td', { className: 'zGbnIq_num' }, React.createElement(NumCell, { value: r.inputTokens })),
606
+ React.createElement('td', { className: 'zGbnIq_num' }, React.createElement(NumCell, { value: r.outputTokens })),
607
+ React.createElement('td', { className: 'zGbnIq_num' }, React.createElement(NumCell, { value: r.cacheReadTokens })),
608
+ React.createElement('td', { className: 'zGbnIq_num' }, React.createElement('span', { className: 'zGbnIq_numText' }, r.hitRate)));
609
+ })));
610
+ }
611
+
612
+ var chips = [React.createElement("button", { key: "", type: "button", className: "zGbnIq_chip", "data-active": provider === "" ? "true" : undefined, onClick: function () { setProvider(""); } }, "全部")];
613
+ for (var ci = 0; ci < provs.length; ci++) {
614
+ (function (pv) {
615
+ chips.push(React.createElement("button", { key: pv, type: "button", className: "zGbnIq_chip", "data-active": provider === pv ? "true" : undefined, onClick: function () { setProvider(pv); } }, pv));
616
+ })(provs[ci]);
617
+ }
618
+
619
+ var stats = null;
620
+ if (total) {
621
+ var statCells = [
622
+ ["请求数", "text", String(total.requests)],
623
+ ["输入", "cnt", total.inputTokens],
624
+ ["输出", "cnt", total.outputTokens],
625
+ ["缓存读取", "cnt", total.cacheReadTokens],
626
+ ["命中率", "text", fmtPct(total.cacheHitRate)],
627
+ ["真实总 token", "cnt", total.realTotalTokens],
628
+ ].map(function (pair) {
629
+ if (pair[1] === "cnt") {
630
+ return React.createElement(CntStatCard, { key: pair[0], label: pair[0], value: pair[2] });
631
+ }
632
+ return React.createElement("div", { key: pair[0], className: "zGbnIq_stat" },
633
+ React.createElement("span", { className: "zGbnIq_statValue" }, pair[2]),
634
+ React.createElement("span", { className: "zGbnIq_statLabel" }, pair[0]));
635
+ });
636
+ stats = React.createElement("div", { className: "zGbnIq_statsGrid" }, statCells);
637
+ }
638
+
639
+ var modelRows = modelNames.map(function (mn) {
640
+ var md = byModel[mn];
641
+ var hit = (md.inputTokens + md.cacheReadTokens) > 0 ? md.cacheReadTokens / (md.inputTokens + md.cacheReadTokens) : 0;
642
+ return { name: mn, requests: md.requests, inputTokens: md.inputTokens, outputTokens: md.outputTokens, cacheReadTokens: md.cacheReadTokens, hitRate: fmtPct(hit) };
643
+ });
644
+
645
+ return React.createElement("div", null,
646
+ React.createElement("div", null,
647
+ React.createElement("p", { className: "zGbnIq_intro", style: { margin: "0 0 8px" } }, "统计 DSH 会话日志中的 Token 用量(本机解析;历史按天存储,只算当天)。" + (data && data.days ? " 已聚合 " + data.days + " 天数据。" : "")),
648
+ React.createElement("div", { className: "zGbnIq_rowHead", style: { marginBottom: "4px" } },
649
+ React.createElement("select", { className: "zGbnIq_input zGbnIq_selectInput", style: { width: "auto" }, value: range, onChange: function (e) { setRange(e.target.value); } },
650
+ React.createElement("option", { value: "1" }, "近 1 天"),
651
+ React.createElement("option", { value: "7" }, "近 7 天"),
652
+ React.createElement("option", { value: "30" }, "近 30 天"),
653
+ React.createElement("option", { value: "0" }, "全部时间")),
654
+ React.createElement("button", { type: "button", className: "zGbnIq_linkButton", onClick: function () { load(true); } }, "刷新"))),
655
+ loading ? React.createElement("div", null,
656
+ React.createElement("div", { className: "zGbnIq_progress" }, React.createElement("i", null)),
657
+ React.createElement("p", { className: "zGbnIq_advancedHint" }, "查询中…")) : null,
658
+ err ? React.createElement("p", { className: "zGbnIq_error" }, err) : null,
659
+ total ? React.createElement("div", null,
660
+ React.createElement("div", { className: "zGbnIq_chips" }, chips),
661
+ stats,
662
+ React.createElement("div", { className: "zGbnIq_modelCatalog" },
663
+ React.createElement("div", { className: "zGbnIq_modelCatalogHeading" },
664
+ React.createElement("span", { className: "zGbnIq_modelCatalogTitle" }, "按模型汇总")),
665
+ React.createElement(ModelSummaryTable, { rows: modelRows }))) : null,
666
+ !total && !loading && !err && data ? React.createElement("div", { className: "zGbnIq_modelEmpty" }, "没有匹配的 DSH 会话。") : null);
667
+ }
668
+ // 提供方状态归一:dot 颜色/标签/可点击性/展开内容都从这里出,6 个 if-else 收敛为 1 个表
669
+ // level: 'ok' 绿 / 'err' 红 / 'loading' 脉冲灰 / 'unknown' 静默灰
670
+ // detailKind: 'err' 渲染红字错误, 'resp' 渲染 JSON 响应, null 不可展开
671
+ // detailText: detailKind 非空时,errOpen 展开的纯文本(可序列化)
672
+ function statusOf(refKnown, refCheckFailed, configured, qs) {
673
+ if (!refKnown && refCheckFailed) {
674
+ return { level: 'err', label: '状态检查失败', clickable: true, detailKind: 'err', dotCls: ' zGbnIq_credentialDotMissing', detailText: '状态检查失败:无法读取 Key 配置状态。' };
675
+ }
676
+ if (!refKnown) {
677
+ return { level: 'loading', label: '检查 Key…', clickable: false, detailKind: null, dotCls: ' dsh-pu-dot-loading', detailText: null };
678
+ }
679
+ if (!configured) {
680
+ return { level: 'unknown', label: 'Key 未配置', clickable: false, detailKind: null, dotCls: ' dsh-pu-dot-gray', detailText: null };
681
+ }
682
+ if (!qs || qs.loading) {
683
+ return { level: 'loading', label: '查询中…', clickable: false, detailKind: null, dotCls: ' dsh-pu-dot-loading', detailText: null };
684
+ }
685
+ if (qs.ok) {
686
+ return { level: 'ok', label: '查询正常', clickable: true, detailKind: 'resp', dotCls: ' zGbnIq_credentialDotConfigured', detailText: typeof qs.resp === 'string' ? qs.resp : JSON.stringify(qs.resp || {}, null, 2) };
687
+ }
688
+ return { level: 'err', label: '查询失败' + (qs.err ? ':' + qs.err : ''), clickable: true, detailKind: 'err', dotCls: ' zGbnIq_credentialDotMissing', detailText: '查询失败:' + (qs.err || '未知错误') };
689
+ }
690
+ function SettingsCard() {
691
+ var s = useSettings();
692
+ var providers = useProviders();
693
+ var [refStatus, setRefStatus] = useState({});
694
+ var [refCheckFailed, setRefCheckFailed] = useState(false);
695
+ var [queryStatus, setQueryStatus] = useState({});
696
+ var [errOpen, setErrOpen] = useState({}); // 展开的错误详情(按实例 id)
697
+ var refsKey = providers.map(function (p) { return p.ref || ''; }).join(',');
698
+ // 添加卡:mode = null | 'import' | 'manual';编辑卡:edit = null | { id, kind, adapter, ref }
699
+ var [mode, setMode] = useState(null);
700
+ var [edit, setEdit] = useState(null);
701
+ var [nonce, setNonce] = useState(0); // 改 Key 后 +1 → 全量强制刷新(noCache)
702
+ var [dshItems, setDshItems] = useState([]);
703
+ var [form, setForm] = useState({ provider: '', name: '', adapter: 'usage-percent', key: '', ref: '' });
704
+ var [busy, setBusy] = useState(false);
705
+ var [formError, setFormError] = useState(null);
706
+
707
+ function removeProvider(p) {
708
+ var next = providers.filter(function (x) { return x.id !== p.id; });
709
+ if (p.source === 'vault' && p.ref) {
710
+ // 删除自定义实例时同步清理插件私有库中的 Key(fire-and-forget)
711
+ fetch('/api/provider-usage/credentials?ref=' + encodeURIComponent(p.ref), { headers: { accept: 'application/json' } })
712
+ .catch(function () { /* ignore */ });
713
+ }
714
+ setProviders(next);
715
+ if ((s.providerId || '') === p.id) setSettings({ providerId: next.length > 0 ? next[0].id : '' });
716
+ }
717
+
718
+ function openImport() {
719
+ setMode('import');
720
+ setEdit(null);
721
+ setFormError(null);
722
+ setBusy(true);
723
+ fetch('/api/provider-usage/dsh-providers', { headers: { accept: 'application/json' } })
724
+ .then(function (r) { return r.json().catch(function () { return null; }); })
725
+ .then(function (j) {
726
+ var items = j && Array.isArray(j.items) ? j.items : [];
727
+ setDshItems(items);
728
+ var first = items[0] || null;
729
+ setForm({ provider: first ? first.route : '', name: first ? first.displayName : '', adapter: first ? first.adapter : 'usage-percent', key: '', ref: '', nameTouched: false });
730
+ })
731
+ .catch(function () { setDshItems([]); })
732
+ .finally(function () { setBusy(false); });
733
+ }
734
+ function openManual() {
735
+ setMode('manual');
736
+ setEdit(null);
737
+ setFormError(null);
738
+ setForm({ provider: '', name: 'Command Code', adapter: 'commandcode-credits', key: '', nameTouched: false });
739
+ }
740
+ function cancelAdd() { setMode(null); setEdit(null); setFormError(null); }
741
+
742
+ async function saveImport() {
743
+ if (!form.provider) { setFormError('请选择要导入的提供方'); return; }
744
+ var name = form.name.trim();
745
+ if (!name) { setFormError('请输入名称'); return; }
746
+ var item = null;
747
+ for (var i = 0; i < dshItems.length; i++) if (dshItems[i].route === form.provider) item = dshItems[i];
748
+ if (!item) { setFormError('导入项不存在'); return; }
749
+ var inst = { id: item.route + '-' + Date.now(), name: name, adapter: item.adapter, ref: item.ref, source: 'dsh', type: 'import' };
750
+ setProviders(providers.concat([inst]));
751
+ setSettings({ providerId: inst.id });
752
+ setMode(null);
753
+ }
754
+
755
+ async function saveManual() {
756
+ var name = form.name.trim();
757
+ if (!name) { setFormError('请输入名称'); return; }
758
+ if (!form.key) { setFormError('请输入 API 密钥值'); return; }
759
+ var instId = form.adapter + '-' + Date.now();
760
+ setBusy(true);
761
+ setFormError(null);
762
+ try {
763
+ // 私有库键 = 实例 id(与 DSH 凭证完全隔离,无需递增/防重名)
764
+ var resp = await fetch('/api/provider-usage/credentials', {
765
+ method: 'POST',
766
+ headers: { 'content-type': 'application/json' },
767
+ body: JSON.stringify({ ref: instId, value: form.key }),
768
+ });
769
+ var j = await resp.json().catch(function () { return null; });
770
+ if (!j || !j.ok) { setFormError((j && j.error && j.error.message) || '保存 Key 失败'); return; }
771
+ var inst = { id: instId, name: name, adapter: form.adapter, ref: instId, source: 'vault', type: 'manual' };
772
+ setProviders(providers.concat([inst]));
773
+ setSettings({ providerId: inst.id });
774
+ setMode(null);
775
+ } catch (e) {
776
+ setFormError('保存 Key 失败:' + String((e && e.message) || e));
777
+ } finally {
778
+ setBusy(false);
779
+ }
780
+ }
781
+
782
+ // ── 编辑:导入的只改名称;自定义(vault)可改名称 + 覆盖 Key ──
783
+ function startEdit(p) {
784
+ setMode(null);
785
+ setEdit({ id: p.id, kind: p.type === 'manual' ? 'manual' : 'import', adapter: p.adapter, ref: p.ref });
786
+ setForm({ provider: '', name: p.name, adapter: p.adapter, key: '', ref: p.ref, nameTouched: true });
787
+ setFormError(null);
788
+ }
789
+
790
+ async function saveEdit() {
791
+ if (!edit) return;
792
+ var name = form.name.trim();
793
+ if (!name) { setFormError('请输入名称'); return; }
794
+ var keyChanged = edit.kind === 'manual' && form.key;
795
+ if (keyChanged) {
796
+ setBusy(true);
797
+ setFormError(null);
798
+ try {
799
+ // 覆盖私有库中同一 ref 的值(不新建条目)
800
+ var resp = await fetch('/api/provider-usage/credentials', {
801
+ method: 'POST',
802
+ headers: { 'content-type': 'application/json' },
803
+ body: JSON.stringify({ ref: edit.ref, value: form.key }),
804
+ });
805
+ var j = await resp.json().catch(function () { return null; });
806
+ if (!j || !j.ok) { setFormError((j && j.error && j.error.message) || '保存 Key 失败'); return; }
807
+ } catch (e) {
808
+ setFormError('保存 Key 失败:' + String((e && e.message) || e));
809
+ return;
810
+ } finally {
811
+ setBusy(false);
812
+ }
813
+ }
814
+ setProviders(providers.map(function (p) { return p.id === edit.id ? Object.assign({}, p, { name: name }) : p; }));
815
+ if (keyChanged) setNonce(function (n) { return n + 1; });
816
+ setEdit(null);
817
+ setForm({ provider: '', name: '', adapter: 'usage-percent', key: '', ref: '', nameTouched: false });
818
+ }
819
+
820
+ // ── 状态查询(打开设置页时:describe + 每实例实际查询)──
821
+ useEffect(function () {
822
+ var alive = true;
823
+ var refs = [];
824
+ providers.forEach(function (p) { if (p.ref && refs.indexOf(p.ref) < 0) refs.push(p.ref); });
825
+ if (refs.length === 0) { setRefStatus({}); setRefCheckFailed(false); setQueryStatus({}); return undefined; }
826
+
827
+ function startQueries(configuredMap, force) {
828
+ var jobs = [];
829
+ providers.forEach(function (p) {
830
+ if (!p.ref) return;
831
+ if (configuredMap && !configuredMap[p.ref]) return;
832
+ var key = p.adapter + '|' + p.ref;
833
+ setQueryStatus(function (prev) {
834
+ var next = Object.assign({}, prev);
835
+ next[key] = { loading: true };
836
+ return next;
837
+ });
838
+ var qs = 'adapter=' + encodeURIComponent(p.adapter) + '&ref=' + encodeURIComponent(p.ref) + (p.source === 'vault' ? '&source=vault' : '') + (force ? '&noCache=1' : '');
839
+ jobs.push(fetch('/api/provider-usage/query?' + qs, { headers: { accept: 'application/json' } })
840
+ .then(function (r) { return r.json().catch(function () { return null; }); })
841
+ .then(function (res) {
842
+ if (!alive) return;
843
+ setQueryStatus(function (prev) {
844
+ var next = Object.assign({}, prev);
845
+ next[key] = { ok: !!(res && res.ok), err: (res && res.error && res.error.message) || '', resp: res || null };
846
+ return next;
847
+ });
848
+ })
849
+ .catch(function () {
850
+ if (!alive) return;
851
+ setQueryStatus(function (prev) {
852
+ var next = Object.assign({}, prev);
853
+ next[key] = { ok: false, err: '网络请求失败' };
854
+ return next;
855
+ });
856
+ }));
857
+ });
858
+ return Promise.all(jobs);
859
+ }
860
+
861
+ setRefCheckFailed(false);
862
+ fetch('/api/provider-usage/credential-refs?refs=' + encodeURIComponent(refs.join(',')), { headers: { accept: 'application/json' } })
863
+ .then(function (r) { return r.json().catch(function () { return null; }); })
864
+ .then(function (j) {
865
+ if (!alive) return;
866
+ if (j && Array.isArray(j.refs)) {
867
+ var map = {};
868
+ var storeMap = {};
869
+ j.refs.forEach(function (x) { map[x.name] = !!x.configured; storeMap[x.name] = x.store; });
870
+ setRefStatus(map);
871
+ // 旧数据自愈:source 非 vault 但 Key 只存在插件私有库 → 升级为手动实例(可改名称+Key)
872
+ var changed = false;
873
+ var next = providers.map(function (p) {
874
+ if (p.source === 'vault' || !p.ref) return p;
875
+ if (storeMap[p.ref] === 'vault') { changed = true; return Object.assign({}, p, { source: 'vault' }); }
876
+ return p;
877
+ });
878
+ if (changed) setProviders(next);
879
+ return startQueries(map, nonce > 0);
880
+ }
881
+ return startQueries(null, nonce > 0);
882
+ })
883
+ .catch(function () {
884
+ if (!alive) return;
885
+ setRefCheckFailed(true);
886
+ startQueries(null, nonce > 0);
887
+ });
888
+ return function () { alive = false; };
889
+ }, [refsKey, nonce]);
890
+
891
+ var adapterOptions = Object.keys(ADAPTER_META).map(function (a) {
892
+ return React.createElement('option', { key: a, value: a }, ADAPTER_META[a].displayName);
893
+ });
894
+
895
+ var addCard = null;
896
+ if (mode === 'import') {
897
+ addCard = React.createElement('div', { className: 'zGbnIq_editor' },
898
+ React.createElement('div', { className: 'zGbnIq_editorHeader' },
899
+ React.createElement('span', { className: 'zGbnIq_editorTitle' }, '从 DSH 导入提供方')),
900
+ busy ? React.createElement('p', { className: 'zGbnIq_advancedHint' }, '加载中…') : null,
901
+ React.createElement('div', { className: 'zGbnIq_field' },
902
+ React.createElement('span', { className: 'zGbnIq_fieldLabel' }, '提供方'),
903
+ React.createElement('select', {
904
+ className: 'zGbnIq_input zGbnIq_selectInput', value: form.provider,
905
+ onChange: function (e) {
906
+ var item = null;
907
+ for (var i = 0; i < dshItems.length; i++) if (dshItems[i].route === e.target.value) item = dshItems[i];
908
+ setForm(Object.assign({}, form, { provider: e.target.value, adapter: item ? item.adapter : form.adapter, name: !form.nameTouched && item ? item.displayName : form.name }));
909
+ },
910
+ }, dshItems.map(function (it) {
911
+ return React.createElement('option', { key: it.route, value: it.route }, it.displayName + (it.configured ? '(已配置)' : '(未配置)'));
912
+ }))),
913
+ React.createElement('div', { className: 'zGbnIq_field' },
914
+ React.createElement('span', { className: 'zGbnIq_fieldLabel' }, '名称'),
915
+ React.createElement('input', {
916
+ className: 'zGbnIq_input', value: form.name,
917
+ onChange: function (e) { setForm(Object.assign({}, form, { name: e.target.value, nameTouched: true })); },
918
+ })),
919
+ (function () {
920
+ var sel = null;
921
+ for (var i = 0; i < dshItems.length; i++) if (dshItems[i].route === form.provider) sel = dshItems[i];
922
+ return sel ? React.createElement('div', { className: 'zGbnIq_field' },
923
+ React.createElement('span', { className: 'zGbnIq_advancedHint' }, 'Key 将从 DSH 获取(' + sel.ref + ')')) : null;
924
+ })(),
925
+ formError ? React.createElement('p', { className: 'zGbnIq_error' }, formError) : null,
926
+ React.createElement('div', { className: 'zGbnIq_editorActions' },
927
+ React.createElement('button', { type: 'button', className: 'zGbnIq_secondaryButton', onClick: cancelAdd }, '取消'),
928
+ React.createElement('button', { type: 'button', className: 'zGbnIq_primaryButton', disabled: busy, onClick: saveImport }, '保存')));
929
+ } else if (mode === 'manual') {
930
+ addCard = React.createElement('div', { className: 'zGbnIq_editor' },
931
+ React.createElement('div', { className: 'zGbnIq_editorHeader' },
932
+ React.createElement('span', { className: 'zGbnIq_editorTitle' }, '添加自定义提供方(手动 Key)')),
933
+ React.createElement('p', { className: 'zGbnIq_advancedHint' }, 'Key 将加密保存到插件私有库(本机),查询时直接使用,不经过 DSH 凭证'),
934
+ React.createElement('div', { className: 'zGbnIq_field' },
935
+ React.createElement('span', { className: 'zGbnIq_fieldLabel' }, '提供方'),
936
+ React.createElement('select', {
937
+ className: 'zGbnIq_input zGbnIq_selectInput', value: form.adapter,
938
+ onChange: function (e) {
939
+ var next = Object.assign({}, form, { adapter: e.target.value });
940
+ if (!form.nameTouched) next.name = e.target.value === 'balance-json' ? 'DeepSeek' : (e.target.value === 'commandcode-credits' ? 'Command Code' : 'OpenCode Go');
941
+ setForm(next);
942
+ },
943
+ }, adapterOptions)),
944
+ React.createElement('div', { className: 'zGbnIq_field' },
945
+ React.createElement('span', { className: 'zGbnIq_fieldLabel' }, '名称'),
946
+ React.createElement('input', {
947
+ className: 'zGbnIq_input', value: form.name,
948
+ onChange: function (e) { setForm(Object.assign({}, form, { name: e.target.value, nameTouched: true })); },
949
+ })),
950
+ React.createElement('div', { className: 'zGbnIq_field' },
951
+ React.createElement('span', { className: 'zGbnIq_fieldLabel' }, 'API 密钥'),
952
+ React.createElement('input', {
953
+ type: 'password', className: 'zGbnIq_input', value: form.key,
954
+ placeholder: '输入 API 密钥',
955
+ autoComplete: 'off',
956
+ onChange: function (e) { setForm(Object.assign({}, form, { key: e.target.value })); },
957
+ })),
958
+ formError ? React.createElement('p', { className: 'zGbnIq_error' }, formError) : null,
959
+ React.createElement('div', { className: 'zGbnIq_editorActions' },
960
+ React.createElement('button', { type: 'button', className: 'zGbnIq_secondaryButton', onClick: cancelAdd }, '取消'),
961
+ React.createElement('button', { type: 'button', className: 'zGbnIq_primaryButton', disabled: busy, onClick: saveManual }, '保存')));
962
+ } else if (edit) {
963
+ var editManual = edit.kind === 'manual';
964
+ addCard = React.createElement('div', { className: 'zGbnIq_editor' },
965
+ React.createElement('div', { className: 'zGbnIq_editorHeader' },
966
+ React.createElement('span', { className: 'zGbnIq_editorTitle' }, editManual ? '编辑自定义提供方' : '编辑提供方')),
967
+ editManual
968
+ ? React.createElement('p', { className: 'zGbnIq_advancedHint' }, 'Key 加密保存在插件私有库(本机);输入新 Key 即覆盖,留空表示不修改')
969
+ : React.createElement('p', { className: 'zGbnIq_advancedHint' }, 'Key 继续从 DSH 获取(' + edit.ref + '),这里只改名称'),
970
+ React.createElement('div', { className: 'zGbnIq_field' },
971
+ React.createElement('span', { className: 'zGbnIq_fieldLabel' }, '提供方'),
972
+ React.createElement('select', { className: 'zGbnIq_input zGbnIq_selectInput', value: edit.adapter, disabled: true }, adapterOptions)),
973
+ React.createElement('div', { className: 'zGbnIq_field' },
974
+ React.createElement('span', { className: 'zGbnIq_fieldLabel' }, '名称'),
975
+ React.createElement('input', {
976
+ className: 'zGbnIq_input', value: form.name,
977
+ onChange: function (e) { setForm(Object.assign({}, form, { name: e.target.value, nameTouched: true })); },
978
+ })),
979
+ editManual ? React.createElement('div', { className: 'zGbnIq_field' },
980
+ React.createElement('span', { className: 'zGbnIq_fieldLabel' }, 'API 密钥'),
981
+ React.createElement('input', {
982
+ type: 'password', className: 'zGbnIq_input', value: form.key,
983
+ placeholder: '留空则不修改', autoComplete: 'off',
984
+ onChange: function (e) { setForm(Object.assign({}, form, { key: e.target.value })); },
985
+ })) : null,
986
+ formError ? React.createElement('p', { className: 'zGbnIq_error' }, formError) : null,
987
+ React.createElement('div', { className: 'zGbnIq_editorActions' },
988
+ React.createElement('button', { type: 'button', className: 'zGbnIq_secondaryButton', onClick: cancelAdd }, '取消'),
989
+ React.createElement('button', { type: 'button', className: 'zGbnIq_primaryButton', disabled: busy, onClick: saveEdit }, '保存')));
990
+ }
991
+
992
+ // 插件页同款二级菜单(官方 dsh-client-ui-settings-plugins 结构):提供方设置 / 用量统计 两个互不影响的分页
993
+ var tabsList = [
994
+ { id: 'providers', label: '提供方设置' },
995
+ { id: 'usage', label: '用量统计' },
996
+ ];
997
+ var [tab, setTab] = useState('providers');
998
+ var tabsRef = useRef([]);
999
+ function onTabKey(e, idx) {
1000
+ var len = tabsList.length;
1001
+ var next = null;
1002
+ if (e.key === 'ArrowRight') next = (idx + 1) % len;
1003
+ else if (e.key === 'ArrowLeft') next = (idx - 1 + len) % len;
1004
+ else if (e.key === 'Home') next = 0;
1005
+ else if (e.key === 'End') next = len - 1;
1006
+ else return;
1007
+ e.preventDefault();
1008
+ var t = tabsList[next];
1009
+ setTab(t.id);
1010
+ var el = tabsRef.current[next];
1011
+ if (el && el.focus) el.focus();
1012
+ }
1013
+
1014
+ // 页面 1:提供方设置(原设置页内容原样保留,两页互不影响)
1015
+ var providersPanel = React.createElement('div', { className: 'zGbnIq_section' },
1016
+ React.createElement('h2', { className: 'zGbnIq_title' }, '提供方'),
1017
+ React.createElement('p', { className: 'zGbnIq_intro' }, '填入各供应方的 Key 引用即可查询其用量与余额。'),
1018
+ React.createElement('ul', { className: 'zGbnIq_rows' },
1019
+ providers.length === 0
1020
+ ? React.createElement('li', { className: 'zGbnIq_rowCard' },
1021
+ React.createElement('span', { className: 'zGbnIq_rowName' }, '暂无提供方,点击下方按钮添加'))
1022
+ : providers.map(function (pl) {
1023
+ var qs = pl.ref ? queryStatus[pl.adapter + '|' + pl.ref] : null;
1024
+ var refKnownRow = pl.ref ? Object.prototype.hasOwnProperty.call(refStatus, pl.ref) : true;
1025
+ var status = pl.ref ? statusOf(refKnownRow, refCheckFailed, !!refStatus[pl.ref], qs) : null;
1026
+ return React.createElement(ProviderRow, {
1027
+ key: pl.id,
1028
+ provider: pl,
1029
+ status: status,
1030
+ expanded: !!errOpen[pl.id],
1031
+ onToggle: function () { setErrOpen(function (prev) { var n = Object.assign({}, prev); n[pl.id] = !prev[pl.id]; return n; }); },
1032
+ onEdit: function () { startEdit(pl); },
1033
+ onRemove: function () { removeProvider(pl); },
1034
+ });
1035
+ })),
1036
+ (mode || edit) ? addCard : React.createElement('div', { className: 'zGbnIq_addBlock' },
1037
+ React.createElement('div', { className: 'zGbnIq_addActions' },
1038
+ React.createElement('button', { type: 'button', className: 'zGbnIq_addButton', onClick: openImport },
1039
+ React.createElement('span', { className: 'ms-plus' }, '+'), '从 DSH 导入提供方'),
1040
+ React.createElement('button', { type: 'button', className: 'zGbnIq_addButton', onClick: openManual },
1041
+ React.createElement('span', { className: 'ms-plus' }, '+'), '添加自定义提供方'))));
1042
+
1043
+ // 页面 2:用量统计(本地 Token 统计;active 时才加载/轮询,切页不互相干扰)
1044
+ var usagePanel = React.createElement('div', { className: 'pbvGtq_panel', role: 'tabpanel', id: 'pu-tab-usage', 'aria-labelledby': 'pu-tab-btn-usage', hidden: tab !== 'usage' },
1045
+ React.createElement(UsageStatsPage, { active: tab === 'usage' }));
1046
+
1047
+ // 二级菜单(插件页同款:下划线高亮 + 方向键切换)
1048
+ var tabsBar = React.createElement('div', { className: 'pbvGtq_tabs', role: 'tablist', 'aria-label': '用量中心视图' },
1049
+ tabsList.map(function (t, idx) {
1050
+ var active = t.id === tab;
1051
+ return React.createElement('button', {
1052
+ key: t.id,
1053
+ ref: function (el) { tabsRef.current[idx] = el; },
1054
+ id: 'pu-tab-btn-' + t.id, type: 'button', role: 'tab',
1055
+ className: 'pbvGtq_tab',
1056
+ 'aria-selected': active ? 'true' : 'false',
1057
+ 'aria-controls': 'pu-tab-' + t.id,
1058
+ tabIndex: active ? 0 : -1,
1059
+ 'data-active': active ? 'true' : undefined,
1060
+ onClick: function () { setTab(t.id); },
1061
+ onKeyDown: function (e) { onTabKey(e, idx); },
1062
+ }, t.label);
1063
+ }));
1064
+
1065
+ return React.createElement('div', { className: 'pbvGtq_section' },
1066
+ React.createElement('h2', { className: 'pbvGtq_heading' }, '用量中心'),
1067
+ React.createElement('p', { className: 'pbvGtq_intro' }, '管理提供方并查看本地用量统计。'),
1068
+ tabsBar,
1069
+ React.createElement('div', { className: 'pbvGtq_panel', role: 'tabpanel', id: 'pu-tab-providers', 'aria-labelledby': 'pu-tab-btn-providers', hidden: tab !== 'providers' }, providersPanel),
1070
+ usagePanel);
1071
+ }
1072
+ // 提供方单行:状态点 + 名称 + 编辑/删除 + 展开详情
1073
+ // 状态判定全部交给 statusOf;本组件只做渲染
1074
+ function ProviderRow(props) {
1075
+ var pl = props.provider;
1076
+ var status = props.status;
1077
+ var expanded = props.expanded;
1078
+ var onToggle = props.onToggle;
1079
+ var onEdit = props.onEdit;
1080
+ var onRemove = props.onRemove;
1081
+ var dotEl = status.dotCls ? React.createElement('span', {
1082
+ className: 'zGbnIq_credentialDot' + status.dotCls,
1083
+ role: 'img', 'aria-label': status.label,
1084
+ style: status.clickable ? { cursor: 'pointer' } : undefined,
1085
+ onClick: status.clickable ? onToggle : undefined,
1086
+ }) : null;
1087
+ var detailEl = null;
1088
+ if (expanded && status.detailKind) {
1089
+ if (status.detailKind === 'err') {
1090
+ detailEl = React.createElement('p', { className: 'zGbnIq_error', style: { margin: '10px 0 0' } }, status.detailText);
1091
+ } else if (status.detailKind === 'resp') {
1092
+ detailEl = React.createElement('div', { className: 'zGbnIq_json' }, status.detailText);
1093
+ }
1094
+ }
1095
+ return React.createElement('li', { key: pl.id, className: 'zGbnIq_rowCard' },
1096
+ React.createElement('div', { className: 'zGbnIq_rowHead' },
1097
+ React.createElement('span', { className: 'zGbnIq_rowIdentity' },
1098
+ React.createElement('span', { className: 'zGbnIq_rowName' }, pl.name),
1099
+ dotEl),
1100
+ React.createElement('span', { className: 'zGbnIq_rowActions' },
1101
+ React.createElement('button', { type: 'button', className: 'zGbnIq_secondaryButton', onClick: onEdit }, '编辑'),
1102
+ React.createElement('button', { type: 'button', className: 'zGbnIq_dangerButton', onClick: onRemove }, '删除'))),
1103
+ detailEl);
1104
+ }
1105
+ // ── 挂载:用量卡片走 body 顶层 React root(与 dsh-pet 同构),设置页走插槽 ──
1106
+ function apply(ctx) {
1107
+ var rootEl = null;
1108
+ var reactRoot = null;
1109
+
1110
+ // 与 dsh-pet 完全一致的挂载方式:document.body 上自建 root。
1111
+ // 这样用量卡片与 better-sidebar / pet 同处 body 顶层(z-index: 2147483000),
1112
+ // 不再被 shell.overlay 的 z-index:20 层叠上下文困住。
1113
+ function mount() {
1114
+ if (reactRoot) { try { reactRoot.unmount(); } catch (e) { /* ignore */ } reactRoot = null; }
1115
+ if (rootEl && rootEl.isConnected) { try { rootEl.remove(); } catch (e) { /* ignore */ } }
1116
+ rootEl = document.createElement('div');
1117
+ rootEl.setAttribute('data-provider-usage-root', '');
1118
+ document.body.appendChild(rootEl);
1119
+ reactRoot = createRoot(rootEl);
1120
+ reactRoot.render(React.createElement(UsageCard));
1121
+ }
1122
+
1123
+ function ensureMounted() {
1124
+ try {
1125
+ if (!getSettings().visible) return; // 用户主动隐藏,不自动复活
1126
+ if (getProviders().length === 0) return; // 无提供方实例:卡片本就渲染 null,避免 10s 空转重挂
1127
+ if (rootEl && rootEl.isConnected && document.querySelector('.dsh-pu-card')) return;
1128
+ mount();
1129
+ } catch (e) {
1130
+ console.warn('[dsh-provider-usage] re-mount failed:', e);
1131
+ }
1132
+ }
1133
+
1134
+ try { mount(); } catch (e) {
1135
+ console.warn('[dsh-provider-usage] body mount failed:', e);
1136
+ }
1137
+
1138
+ var disposers = [];
1139
+ try {
1140
+ disposers.push(ctx.slots.inject('settings.section', function () {
1141
+ return ctx.slots.register(
1142
+ { name: 'settings.section', id: 'provider-usage-settings', order: 40, label: function () { return zh.nav; } },
1143
+ function () { return React.createElement(SettingsCard); });
1144
+ }));
1145
+ } catch (e) {
1146
+ console.warn('[dsh-provider-usage] settings section mount failed:', e);
1147
+ }
1148
+
1149
+ ctx.effect(function () {
1150
+ var timer = window.setInterval(ensureMounted, 10000);
1151
+ return function () {
1152
+ window.clearInterval(timer);
1153
+ if (reactRoot) { try { reactRoot.unmount(); } catch (e) { /* ignore */ } reactRoot = null; }
1154
+ if (rootEl && rootEl.isConnected) { try { rootEl.remove(); } catch (e) { /* ignore */ } rootEl = null; }
1155
+ for (var i = 0; i < disposers.length; i++) { try { disposers[i](); } catch (e) { /* ignore */ } }
1156
+ };
1157
+ }, 'dsh-provider-usage: ui mounts');
1158
+ }
1159
+
1160
+ exports.apply = apply;
1161
+ exports.inject = inject;
1162
+ return module.exports;
1163
+ },
1164
+ });