@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/README.md +63 -0
- package/cordis.patch.yml +7 -0
- package/docs/CHANGELOG.md +120 -0
- package/lib/client.js +1164 -0
- package/lib/daily-stats.js +324 -0
- package/lib/index.js +1104 -0
- package/lib/secure-store.js +136 -0
- package/package.json +66 -0
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,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';
|
|
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
|
+
});
|