@loommii/dsh-provider-usage 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -3,9 +3,11 @@
3
3
  // (OpenCode Go 订阅 / DeepSeek 余额),卡片按结果字段自适应渲染(%+倒计时=订阅卡;
4
4
  // 金额+币种=余额卡)。
5
5
  // 设置项全部即时生效(localStorage 持久化,无需重启)。
6
+ // v0.8.0: 注册 id 必须等于包名 @loommii/dsh-provider-usage —— dsh 0.1.2-alpha 的
7
+ // boot graph 行 id = 包名,serveBundle 按 id 应答 bundle,不一致即客户端静默失联。
6
8
 
7
9
  window.__ModuleLoader__.load({
8
- id: 'dsh-provider-usage',
10
+ id: '@loommii/dsh-provider-usage',
9
11
  factory: (require) => {
10
12
  var module = { exports: {} };
11
13
  var exports = module.exports;
@@ -62,7 +64,7 @@ window.__ModuleLoader__.load({
62
64
  if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css="' + styleId + '"]') === null) {
63
65
  var css = [
64
66
  // 官方「模型」设置页完整样式(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}",
67
+ ".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_dangerButton{box-sizing:border-box;height:28px;color:var(--dsw-alias-state-warn-label);font:inherit;cursor:pointer;background:0 0;border:1px solid transparent;border-radius:14px;align-items:center;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex;transition:border-color .15s,color .15s,background .15s}.zGbnIq_dangerButton:hover:not(:disabled){color:var(--dsw-alias-state-danger-primary,var(--dsw-alias-state-warn-label))}.zGbnIq_dangerButton:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}.zGbnIq_dangerButton:disabled{opacity:.6;cursor:default}.zGbnIq_dangerButton[data-arm=true]{color:var(--dsw-alias-state-danger-primary,#e5484d);border-color:var(--dsw-alias-state-danger-primary,#e5484d);background:color-mix(in srgb,var(--dsw-alias-state-danger-primary,#e5484d) 8%,transparent)}.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
68
  // 官方「插件」设置页二级菜单样式(1:1 注入,dsh-client-ui-settings-plugins PluginsSettingsSection 同款)
67
69
  ".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
70
  // 用量统计页样式
@@ -119,6 +121,20 @@ window.__ModuleLoader__.load({
119
121
  '.zGbnIq_credentialDot.dsh-pu-dot-gray{background:var(--dsw-alias-label-tertiary,#8b93a7)}',
120
122
  '@keyframes dsh-pu-ms-breathe{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.72)}}@media (prefers-reduced-motion:reduce){.dsh-pu-chip.dsh-pu-chip-loading i,.zGbnIq_credentialDot.dsh-pu-dot-loading{animation:none}}',
121
123
  '.ms-plus{color:var(--dsw-alias-brand-primary);font-size:15px;line-height:1}',
124
+ // ── 提供方「启用/暂停」开关(官方 switch 视觉;开=启用=绿 / 关=暂停=灰)──
125
+ '.dsh-pu-switch{box-sizing:border-box;width:36px;height:20px;background:var(--dsw-alias-border-l3);cursor:pointer;border:0;border-radius:10px;flex:none;padding:2px;position:relative}',
126
+ '.dsh-pu-switch:hover:not(:disabled){background:var(--dsw-alias-border-l4)}',
127
+ '.dsh-pu-switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}',
128
+ '.dsh-pu-switch-on{background:var(--dsw-alias-state-success-primary)}',
129
+ '.dsh-pu-switch-on:hover:not(:disabled){background:var(--dsw-alias-state-success-primary)}',
130
+ '.dsh-pu-switch-thumb{box-sizing:border-box;background:var(--dsw-alias-label-primary-foreground);border-radius:50%;width:16px;height:16px;display:block;transition:transform .12s}',
131
+ '.dsh-pu-switch-on .dsh-pu-switch-thumb{transform:translate(16px)}',
132
+ '.zGbnIq_editorSwitchRow{align-items:center;gap:10px;display:flex}',
133
+ '.dsh-pu-switchText{color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}',
134
+ // ── 提供方列表:暂停行(灰点 + 名称变灰,编辑/删除保持可用)──
135
+ '.zGbnIq_rowNameDim{color:var(--dsw-alias-label-tertiary)}',
136
+ '.zGbnIq_rowCard .zGbnIq_credentialDot.dsh-pu-dot-paused{background:var(--dsw-alias-label-tertiary,#8b93a7)}',
137
+ '.zGbnIq_rowPaused .zGbnIq_rowIdentity .zGbnIq_rowName{color:var(--dsw-alias-label-tertiary)}',
122
138
  ].join('');
123
139
  var tag = document.createElement('style');
124
140
  tag.dataset.plugin = 'dsh-provider-usage';
@@ -169,35 +185,67 @@ window.__ModuleLoader__.load({
169
185
  return s;
170
186
  }
171
187
 
172
- // ── 供应商实例清单(localStorage,即时生效)──────────────────────
173
- function loadProviders() {
188
+ // ── 供应商实例清单(v0.8.0:host 端 providers.json 为单一事实源,localStorage 只是写穿透缓存)──
189
+ // 修复「重启后提供方清单被静默重建」:此前清单只存 localStorage,存储 key 一旦丢失
190
+ // (换 origin/清站点数据/浏览器驱逐临时存储),启动就走 DEFAULT_PROVIDERS 兜底 →
191
+ // 已删除的默认实例(OpenCode Go/DeepSeek)复活、自定义实例(如 Command Code)永久消失。
192
+ // 现在启动时从 host 恢复;host 无数据时才一次性迁移本地缓存/默认清单并立即写回 host。
193
+ function normalizeProviders(arr) {
194
+ if (!Array.isArray(arr)) return null;
195
+ return arr.filter(function (p) {
196
+ return p && typeof p === 'object' && typeof p.adapter === 'string' && ADAPTER_META[p.adapter];
197
+ }).map(function (p) {
198
+ var name = typeof p.name === 'string' && p.name ? p.name : (ADAPTER_META[p.adapter] || {}).displayName;
199
+ if (String(p.id || '') === 'deepseek-balance' && name === 'DeepSeek 余额') name = 'DeepSeek'; // 0.4.0 更名
200
+ // source:Key 解析位置(vault=插件私有库直取;dsh=DSH 凭证链);type:创建方式(import=导入 / manual=自定义)
201
+ var source = p.source === 'vault' ? 'vault' : 'dsh';
202
+ // 显式 type 优先;旧数据无 type 时由 source 推导(vault→manual,其余→import)
203
+ var type = p.type === 'manual' || p.type === 'import' ? p.type : (source === 'vault' ? 'manual' : 'import');
204
+ // paused:true=暂停(不轮询查询、卡片菜单不可选);旧数据无该字段 = false(启用)
205
+ return { id: String(p.id || ''), name: name, adapter: p.adapter, ref: typeof p.ref === 'string' ? p.ref : '', source: source, type: type, paused: p.paused === true };
206
+ });
207
+ }
208
+ function readProvidersCache() {
174
209
  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
- }
210
+ return normalizeProviders(JSON.parse(window.localStorage.getItem(PROVIDERS_KEY) || 'null'));
190
211
  } catch (e) { /* ignore */ }
191
- return DEFAULT_PROVIDERS.slice();
212
+ return null;
213
+ }
214
+ function saveProvidersCache(list) {
215
+ try { window.localStorage.setItem(PROVIDERS_KEY, JSON.stringify(list)); } catch (e) { /* ignore */ }
192
216
  }
193
- var providersState = loadProviders();
217
+ // host 恢复完成前的乐观缓存:优先用 localStorage 旧数据(多数场景与 host 一致),否则默认清单
218
+ var providersState = readProvidersCache() || DEFAULT_PROVIDERS.slice();
194
219
  var providersListeners = new Set();
220
+ // 本次页面会话内的写入计数:恢复响应迟到时,若期间用户已写入则放弃应用 host 过期数据(防复活)
221
+ var providersMutations = 0;
195
222
  function saveProviders() {
196
- try { window.localStorage.setItem(PROVIDERS_KEY, JSON.stringify(providersState)); } catch (e) { /* ignore */ }
223
+ // 写穿透:localStorage 缓存 + host 落盘(fire-and-forget;host 失联时本地缓存仍可用)
224
+ saveProvidersCache(providersState);
225
+ try {
226
+ fetch('/api/provider-usage/providers', {
227
+ method: 'POST',
228
+ headers: { 'content-type': 'application/json', accept: 'application/json' },
229
+ body: JSON.stringify({ providers: providersState }),
230
+ }).catch(function () { /* ignore */ });
231
+ } catch (e) { /* ignore */ }
232
+ }
233
+ // 内部应用(不记写入数):host 恢复/一次性迁移专用
234
+ function applyProvidersUpdate(update) {
235
+ var next = typeof update === 'function' ? update(providersState) : update;
236
+ providersState = next;
237
+ saveProviders();
238
+ providersListeners.forEach(function (fn) { fn(); });
197
239
  }
198
240
  function getProviders() { return providersState; }
199
- function setProviders(list) {
200
- providersState = list;
241
+ // v0.8.0 修复(丢更新竞态):所有写方共享同一份 providersState 快照,整体覆盖式写入
242
+ // 在写入交错时会把先写的整单覆盖掉(如连删两个实例会复活第一个)。
243
+ // 现支持函数式更新:setProviders(function (prev) { return next; }) —— next 基于写入时刻的最新状态计算;
244
+ // 兼容旧用法:传数组视为恒等函数。
245
+ function setProviders(update) {
246
+ providersMutations += 1;
247
+ var next = typeof update === 'function' ? update(providersState) : update;
248
+ providersState = next;
201
249
  saveProviders();
202
250
  providersListeners.forEach(function (fn) { fn(); });
203
251
  }
@@ -205,15 +253,43 @@ window.__ModuleLoader__.load({
205
253
  providersListeners.add(fn);
206
254
  return function () { providersListeners.delete(fn); };
207
255
  }
256
+ // host 恢复只执行一次(首个订阅者触发;fetch 去重)
257
+ var hostRestoreStarted = false;
208
258
  function useProviders() {
209
259
  var [p, setP] = useState(getProviders);
210
- useEffect(function () { return subscribeProviders(function () { setP(getProviders()); }); }, []);
260
+ useEffect(function () {
261
+ var unsub = subscribeProviders(function () { setP(getProviders()); });
262
+ if (!hostRestoreStarted) {
263
+ hostRestoreStarted = true;
264
+ fetch('/api/provider-usage/providers', { headers: { accept: 'application/json' } })
265
+ .then(function (r) { return r.json().catch(function () { return null; }); })
266
+ .then(function (j) {
267
+ if (!j || !j.ok) return;
268
+ // 恢复期间用户已做过修改(增删改实例)→ 放弃 host 过期数据,避免把用户刚删的实例复活
269
+ if (providersMutations > 0) return;
270
+ if (j.exists && Array.isArray(j.providers)) {
271
+ // 主路径:host 有清单 → 恢复(host 为单一事实源;本地缓存仅作 host 失联时的乐观值)
272
+ applyProvidersUpdate(j.providers);
273
+ } else {
274
+ // 一次性迁移:host 无文件(首次升级到 0.8.0)→ 把当前缓存清单写回 host 固化
275
+ applyProvidersUpdate(getProviders());
276
+ }
277
+ })
278
+ .catch(function () { /* host 失联:继续用本地缓存,后续写操作会重试落盘 */ });
279
+ }
280
+ return unsub;
281
+ }, []);
211
282
  return p;
212
283
  }
213
- /** 当前选中实例;若被删除则回落到第一个。 */
284
+ /** 可用(未暂停)的实例清单:卡片轮询与切换只在这些实例里进行。 */
285
+ function activeProviders(providers) {
286
+ return providers.filter(function (p) { return !p.paused; });
287
+ }
288
+ /** 当前选中实例;若被删除或已暂停则回落到第一个可用实例(全暂停/无实例 → null)。 */
214
289
  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;
290
+ var actives = activeProviders(providers);
291
+ for (var i = 0; i < actives.length; i++) if (actives[i].id === providerId) return actives[i];
292
+ return actives[0] || null;
217
293
  }
218
294
 
219
295
  // ── 工具 ──────────────────────────────────────────────────────────
@@ -326,7 +402,7 @@ window.__ModuleLoader__.load({
326
402
  return function () { window.removeEventListener('resize', onResize); };
327
403
  }, [s.right, s.bottom, s.visible, data]);
328
404
  var current = currentProvider(providers, s.providerId);
329
- var multiple = providers.length > 1;
405
+ var multiple = activeProviders(providers).length > 1;
330
406
  var curKey = current ? current.id + '|' + current.adapter + '|' + current.ref : 'none';
331
407
 
332
408
  var load = useCallback(function (force) {
@@ -406,10 +482,18 @@ window.__ModuleLoader__.load({
406
482
  var warn = data.isValid === false && data.invalidMessage
407
483
  ? React.createElement('div', { className: 'dsh-pu-error' }, data.invalidMessage)
408
484
  : null;
409
- // Command Code 混合卡:主数字 = 月已用百分比,副行 = 已用/总额 + 剩余额度
485
+ // Command Code 混合卡:主数字 = 月剩余百分比(3 位小数向下取整),副行 = 已用/总额 + 剩余额度
410
486
  var cc = null;
411
487
  if (isCommandCode) {
412
- var usedPct = data.monthly && data.monthly.usedPct;
488
+ var monthlyInfo = data.monthly || null;
489
+ // 新服务端直接给 remainingPct;旧服务端只有 usedPct → 按 100−usedPct 兜底(与 host 公式数学等价)
490
+ var remainPct = monthlyInfo
491
+ ? (monthlyInfo.remainingPct !== undefined && monthlyInfo.remainingPct !== null
492
+ ? monthlyInfo.remainingPct
493
+ : (monthlyInfo.usedPct === undefined || monthlyInfo.usedPct === null
494
+ ? null
495
+ : Math.floor(Math.max(0, Math.min(100, 100 - monthlyInfo.usedPct)) * 1000) / 1000))
496
+ : null;
413
497
  var usedStr = data.used === null ? '--' : Number(data.used).toFixed(2);
414
498
  var quotaStr = data.totalQuota === null ? '--' : Number(data.totalQuota).toFixed(2);
415
499
  var remainStr = data.remaining === null ? '--' : Number(data.remaining).toFixed(2);
@@ -421,8 +505,8 @@ window.__ModuleLoader__.load({
421
505
  var extra = null;
422
506
  body = React.createElement('div', null,
423
507
  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)),
508
+ React.createElement('span', { className: 'dsh-pu-remain-big' }, isCommandCode ? (remainPct === null ? '--' : remainPct.toFixed(3) + '%') : main),
509
+ React.createElement('span', { className: 'dsh-pu-remain-label' }, isCommandCode ? ' ' + zh.remaining + '(' + zh.monthly + ')' : ' ' + mainLabel)),
426
510
  warn,
427
511
  cc,
428
512
  winRows,
@@ -432,7 +516,7 @@ window.__ModuleLoader__.load({
432
516
  var menu = null;
433
517
  if (multiple && menuOpen) {
434
518
  menu = React.createElement('div', { ref: menuRef, className: 'dsh-pu-menu' },
435
- providers.map(function (p) {
519
+ activeProviders(providers).map(function (p) {
436
520
  var active = p.id === current.id;
437
521
  var mini = p.adapter === 'balance-json'
438
522
  ? React.createElement('img', { src: LOGO_DEEPSEEK, width: 16, height: 16, alt: '' })
@@ -542,6 +626,9 @@ window.__ModuleLoader__.load({
542
626
  var [provider, setProvider] = useState(''); // '' = 全部
543
627
  var [range, setRange] = useState('1'); // 默认近 1 天;'0'=全部 / '1'=近1天 / '7'=近7天 / '30'=近30天
544
628
  var dataRef = useRef(null); // 轮询静默刷新判定用(避免闭包陈旧)
629
+ var [resetState, setResetState] = useState('idle'); // 重置统计:idle → arm(待确认)→ busy → idle
630
+ var [resetMsg, setResetMsg] = useState(null);
631
+ var resetTimerRef = useRef(null); // 两段式确认的 4s 自动解除
545
632
  function setDataBoth(v) { dataRef.current = v; setData(v); }
546
633
  function load(force) {
547
634
  // 自然日语义:近 1 天 = 今天 00:00 起;近 7/30 天 = 今天 + 前 N-1 个完整自然日(0 点边界)
@@ -560,7 +647,20 @@ window.__ModuleLoader__.load({
560
647
  else { setErr('无法读取本地用量统计'); }
561
648
  })
562
649
  .catch(function () { setErr('读取失败'); })
563
- .finally(function () { setLoading(false); });
650
+ .finally(function () { setLoading(false); setResetMsg(null); });
651
+ }
652
+ function doReset() {
653
+ if (resetState === 'busy') return;
654
+ setResetState('busy'); setResetMsg(null);
655
+ setDataBoth(null); setLoading(true); // 重置语义:立即清空旧数据展示,进入「查询中」重建态
656
+ fetch('/api/provider-usage/reset-local-stats', { method: 'POST', headers: { accept: 'application/json' } })
657
+ .then(function (r) { return r.json().catch(function () { return null; }); })
658
+ .then(function (j) {
659
+ if (j && j.ok) setResetMsg('已重置(清除 ' + (j.removed || 0) + ' 个缓存文件),正在重新统计…');
660
+ else setResetMsg('重置失败' + (j && j.error && j.error.message ? ':' + j.error.message : ''));
661
+ })
662
+ .catch(function () { setResetMsg('重置失败:网络错误'); })
663
+ .finally(function () { setResetState('idle'); load(true); });
564
664
  }
565
665
  useEffect(function () { if (active) load(false); }, [active, provider, range]);
566
666
  useEffect(function () {
@@ -574,7 +674,42 @@ window.__ModuleLoader__.load({
574
674
  var provs = data && Array.isArray(data.providers) ? data.providers.slice() : [];
575
675
  provs.sort();
576
676
  var byModel = (data && data.byModel && typeof data.byModel === 'object') ? data.byModel : {};
577
- var modelNames = Object.keys(byModel).sort();
677
+ // 形态判别:v0.7.0 = 嵌套 { 提供方: { 模型: 计数 } }(顶层值无 requests 数字);
678
+ // v0.6.1 旧服务端 = 扁平 { 模型: 计数 }(顶层值直接是计数条目)。
679
+ function byModelShape(m) {
680
+ for (var k in m) {
681
+ if (!Object.prototype.hasOwnProperty.call(m, k)) continue;
682
+ var v = m[k];
683
+ if (v && typeof v === 'object') return (typeof v.requests === 'number') ? 'flat' : 'nested';
684
+ }
685
+ return 'flat'; // 空对象:无模型行,形态无影响
686
+ }
687
+ var modelShape = byModelShape(byModel);
688
+ var modelData = {}; // 模型名 -> 计数条目(已按当前提供方选择过滤/合并)
689
+ if (modelShape === 'nested') {
690
+ if (provider === '') {
691
+ // 全部:同名模型跨提供方合并求和(unknown 桶=旧归属缺失事件,一并计入)
692
+ var merged = {};
693
+ for (var pk in byModel) {
694
+ if (!Object.prototype.hasOwnProperty.call(byModel, pk)) continue;
695
+ var bucket = byModel[pk];
696
+ if (!bucket || typeof bucket !== 'object' || typeof bucket.requests === 'number') continue;
697
+ for (var mk in bucket) {
698
+ if (!Object.prototype.hasOwnProperty.call(bucket, mk)) continue;
699
+ var e = bucket[mk], g = merged[mk];
700
+ if (!g) g = merged[mk] = { requests: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0 };
701
+ g.requests += e.requests || 0; g.inputTokens += e.inputTokens || 0;
702
+ g.outputTokens += e.outputTokens || 0; g.cacheReadTokens += e.cacheReadTokens || 0;
703
+ }
704
+ }
705
+ modelData = merged;
706
+ } else {
707
+ modelData = (byModel[provider] && typeof byModel[provider] === 'object' && typeof byModel[provider].requests !== 'number') ? byModel[provider] : {};
708
+ }
709
+ } else {
710
+ modelData = byModel; // v0.6.1 旧服务端:无提供方维度,行为同旧版
711
+ }
712
+ var modelNames = Object.keys(modelData).sort();
578
713
  // ── 按模型汇总表 ──
579
714
  function ModelNameCell(props) {
580
715
  var name = props.name;
@@ -637,25 +772,37 @@ window.__ModuleLoader__.load({
637
772
  }
638
773
 
639
774
  var modelRows = modelNames.map(function (mn) {
640
- var md = byModel[mn];
775
+ var md = modelData[mn] || { requests: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0 };
641
776
  var hit = (md.inputTokens + md.cacheReadTokens) > 0 ? md.cacheReadTokens / (md.inputTokens + md.cacheReadTokens) : 0;
642
777
  return { name: mn, requests: md.requests, inputTokens: md.inputTokens, outputTokens: md.outputTokens, cacheReadTokens: md.cacheReadTokens, hitRate: fmtPct(hit) };
643
778
  });
644
779
 
645
780
  return React.createElement("div", null,
646
781
  React.createElement("div", null,
647
- React.createElement("p", { className: "zGbnIq_intro", style: { margin: "0 0 8px" } }, "统计 DSH 会话日志中的 Token 用量(本机解析;历史按天存储,只算当天)。" + (data && data.days ? " 已聚合 " + data.days + " 天数据。" : "")),
782
+ React.createElement("p", { className: "zGbnIq_intro", style: { margin: "0 0 8px" } }, "统计 DSH 会话日志中的 Token 用量(本机解析;历史按天存储,只算当天;下方汇总跟随所选提供方)。" + (data && data.days ? " 已聚合 " + data.days + " 天数据。" : "")),
648
783
  React.createElement("div", { className: "zGbnIq_rowHead", style: { marginBottom: "4px" } },
649
784
  React.createElement("select", { className: "zGbnIq_input zGbnIq_selectInput", style: { width: "auto" }, value: range, onChange: function (e) { setRange(e.target.value); } },
650
785
  React.createElement("option", { value: "1" }, "近 1 天"),
651
786
  React.createElement("option", { value: "7" }, "近 7 天"),
652
787
  React.createElement("option", { value: "30" }, "近 30 天"),
653
788
  React.createElement("option", { value: "0" }, "全部时间")),
654
- React.createElement("button", { type: "button", className: "zGbnIq_linkButton", onClick: function () { load(true); } }, "刷新"))),
789
+ React.createElement("button", { type: "button", className: "zGbnIq_linkButton", onClick: function () { load(true); } }, "刷新"),
790
+ React.createElement("button", {
791
+ type: "button",
792
+ className: "zGbnIq_dangerButton",
793
+ "data-arm": resetState === 'arm' ? "true" : undefined,
794
+ disabled: resetState === 'busy',
795
+ "aria-label": resetState === 'arm' ? "再次点击确认重置本地统计" : "重置本地统计(清除按天缓存并重新统计,不影响会话记录)",
796
+ onClick: function () {
797
+ if (resetState === 'arm') { doReset(); }
798
+ else { setResetState('arm'); setResetMsg(null); window.clearTimeout(resetTimerRef.current); resetTimerRef.current = window.setTimeout(function () { setResetState('idle'); }, 4000); }
799
+ },
800
+ }, resetState === 'arm' ? "确认重置?" : resetState === 'busy' ? "重置中…" : "重置统计"))),
655
801
  loading ? React.createElement("div", null,
656
802
  React.createElement("div", { className: "zGbnIq_progress" }, React.createElement("i", null)),
657
803
  React.createElement("p", { className: "zGbnIq_advancedHint" }, "查询中…")) : null,
658
804
  err ? React.createElement("p", { className: "zGbnIq_error" }, err) : null,
805
+ resetMsg ? React.createElement("p", { className: "zGbnIq_advancedHint" }, resetMsg) : null,
659
806
  total ? React.createElement("div", null,
660
807
  React.createElement("div", { className: "zGbnIq_chips" }, chips),
661
808
  stats,
@@ -663,7 +810,8 @@ window.__ModuleLoader__.load({
663
810
  React.createElement("div", { className: "zGbnIq_modelCatalogHeading" },
664
811
  React.createElement("span", { className: "zGbnIq_modelCatalogTitle" }, "按模型汇总")),
665
812
  React.createElement(ModelSummaryTable, { rows: modelRows }))) : null,
666
- !total && !loading && !err && data ? React.createElement("div", { className: "zGbnIq_modelEmpty" }, "没有匹配的 DSH 会话。") : null);
813
+ !total && !loading && !err && data ? React.createElement("div", { className: "zGbnIq_modelEmpty" }, "没有匹配的 DSH 会话。") : null,
814
+ total && modelRows.length === 0 ? React.createElement("div", { className: "zGbnIq_modelEmpty" }, total.requests > 0 ? "旧版本统计的用量暂无模型归属明细。" : "该时间范围内没有此提供方的模型用量。") : null);
667
815
  }
668
816
  // 提供方状态归一:dot 颜色/标签/可点击性/展开内容都从这里出,6 个 if-else 收敛为 1 个表
669
817
  // level: 'ok' 绿 / 'err' 红 / 'loading' 脉冲灰 / 'unknown' 静默灰
@@ -695,6 +843,8 @@ window.__ModuleLoader__.load({
695
843
  var [queryStatus, setQueryStatus] = useState({});
696
844
  var [errOpen, setErrOpen] = useState({}); // 展开的错误详情(按实例 id)
697
845
  var refsKey = providers.map(function (p) { return p.ref || ''; }).join(',');
846
+ // 暂停态也进依赖:切「恢复/暂停」后让设置页状态查询重新跑(暂停的不查、恢复的马上查)
847
+ var refsPauseKey = providers.map(function (p) { return (p.paused ? '1' : '0') + ':' + (p.ref || ''); }).join(',');
698
848
  // 添加卡:mode = null | 'import' | 'manual';编辑卡:edit = null | { id, kind, adapter, ref }
699
849
  var [mode, setMode] = useState(null);
700
850
  var [edit, setEdit] = useState(null);
@@ -705,14 +855,22 @@ window.__ModuleLoader__.load({
705
855
  var [formError, setFormError] = useState(null);
706
856
 
707
857
  function removeProvider(p) {
708
- var next = providers.filter(function (x) { return x.id !== p.id; });
709
858
  if (p.source === 'vault' && p.ref) {
710
859
  // 删除自定义实例时同步清理插件私有库中的 Key(fire-and-forget)
711
860
  fetch('/api/provider-usage/credentials?ref=' + encodeURIComponent(p.ref), { headers: { accept: 'application/json' } })
712
861
  .catch(function () { /* ignore */ });
713
862
  }
714
- setProviders(next);
715
- if ((s.providerId || '') === p.id) setSettings({ providerId: next.length > 0 ? next[0].id : '' });
863
+ // v0.8.0:函数式更新 —— 基于写入时刻的最新清单计算,连删/交错写入不再互相覆盖
864
+ var next = null;
865
+ setProviders(function (prev) {
866
+ next = prev.filter(function (x) { return x.id !== p.id; });
867
+ return next;
868
+ });
869
+ // 若当前卡片实例被删除,回落到第一个可用实例(跳过已暂停的)
870
+ if (currentProvider(next, s.providerId || '') === null) {
871
+ var first = activeProviders(next)[0];
872
+ setSettings({ providerId: first ? first.id : '' });
873
+ }
716
874
  }
717
875
 
718
876
  function openImport() {
@@ -746,8 +904,8 @@ window.__ModuleLoader__.load({
746
904
  var item = null;
747
905
  for (var i = 0; i < dshItems.length; i++) if (dshItems[i].route === form.provider) item = dshItems[i];
748
906
  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]));
907
+ var inst = { id: item.route + '-' + Date.now(), name: name, adapter: item.adapter, ref: item.ref, source: 'dsh', type: 'import', paused: false };
908
+ setProviders(function (prev) { return prev.concat([inst]); });
751
909
  setSettings({ providerId: inst.id });
752
910
  setMode(null);
753
911
  }
@@ -768,8 +926,8 @@ window.__ModuleLoader__.load({
768
926
  });
769
927
  var j = await resp.json().catch(function () { return null; });
770
928
  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]));
929
+ var inst = { id: instId, name: name, adapter: form.adapter, ref: instId, source: 'vault', type: 'manual', paused: false };
930
+ setProviders(function (prev) { return prev.concat([inst]); });
773
931
  setSettings({ providerId: inst.id });
774
932
  setMode(null);
775
933
  } catch (e) {
@@ -779,11 +937,12 @@ window.__ModuleLoader__.load({
779
937
  }
780
938
  }
781
939
 
782
- // ── 编辑:导入的只改名称;自定义(vault)可改名称 + 覆盖 Key ──
940
+ // ── 编辑:导入的只改名称 + 启用状态;自定义(vault)可改名称 + 覆盖 Key + 启用状态 ──
941
+ // enabled = 表单暂存值(false 表示“暂停”);开关只暂存在表单里,点「保存」才真正写入实例
783
942
  function startEdit(p) {
784
943
  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 });
944
+ setEdit({ id: p.id, kind: p.type === 'manual' ? 'manual' : 'import', adapter: p.adapter, ref: p.ref, paused: !!p.paused });
945
+ setForm({ provider: '', name: p.name, adapter: p.adapter, key: '', ref: p.ref, nameTouched: true, enabled: !p.paused, _editId: p.id });
787
946
  setFormError(null);
788
947
  }
789
948
 
@@ -811,10 +970,30 @@ window.__ModuleLoader__.load({
811
970
  setBusy(false);
812
971
  }
813
972
  }
814
- setProviders(providers.map(function (p) { return p.id === edit.id ? Object.assign({}, p, { name: name }) : p; }));
973
+ // 名称 + 启用状态在同一次 setProviders 里写入(避免两次基于旧数组的写入互相覆盖)
974
+ var enabled2 = form.enabled === false ? false : true;
975
+ // v0.8.0:函数式更新 —— next 基于写入时刻的最新清单计算,不再依赖渲染闭包的旧 providers
976
+ var next = null;
977
+ setProviders(function (prev) {
978
+ next = prev.map(function (p) {
979
+ return p.id === edit.id ? Object.assign({}, p, { name: name, paused: !enabled2 }) : p;
980
+ });
981
+ return next;
982
+ });
983
+ // 卡片当前实例处理(基于更新后的 next):
984
+ // - 停用的正是当前卡片实例 → 显式切到下一个仍启用的(currentProvider 会自动回落,不能依赖它判空)
985
+ // - 恢复某实例且此前没有任何可用实例(卡片隐藏态)→ 恢复的这个直接成为卡片当前实例
986
+ if (!enabled2 && (s.providerId || '') === edit.id) {
987
+ var fallback = null;
988
+ var act = activeProviders(next);
989
+ for (var i = 0; i < act.length; i++) if (act[i].id !== edit.id) { fallback = act[i]; break; }
990
+ setSettings({ providerId: fallback ? fallback.id : '' });
991
+ } else if (enabled2 && !currentProvider(next, s.providerId || '')) {
992
+ setSettings({ providerId: edit.id });
993
+ }
815
994
  if (keyChanged) setNonce(function (n) { return n + 1; });
816
995
  setEdit(null);
817
- setForm({ provider: '', name: '', adapter: 'usage-percent', key: '', ref: '', nameTouched: false });
996
+ setForm({ provider: '', name: '', adapter: 'usage-percent', key: '', ref: '', nameTouched: false, enabled: true, _editId: null });
818
997
  }
819
998
 
820
999
  // ── 状态查询(打开设置页时:describe + 每实例实际查询)──
@@ -828,6 +1007,7 @@ window.__ModuleLoader__.load({
828
1007
  var jobs = [];
829
1008
  providers.forEach(function (p) {
830
1009
  if (!p.ref) return;
1010
+ if (p.paused) return; // 已暂停的实例不做任何查询(设置页打开时也只显示「已暂停」灰点)
831
1011
  if (configuredMap && !configuredMap[p.ref]) return;
832
1012
  var key = p.adapter + '|' + p.ref;
833
1013
  setQueryStatus(function (prev) {
@@ -869,13 +1049,16 @@ window.__ModuleLoader__.load({
869
1049
  j.refs.forEach(function (x) { map[x.name] = !!x.configured; storeMap[x.name] = x.store; });
870
1050
  setRefStatus(map);
871
1051
  // 旧数据自愈: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;
1052
+ // v0.8.0:函数式更新,避免覆盖恢复/添加等交错写入
1053
+ setProviders(function (prev) {
1054
+ var changed = false;
1055
+ var next = prev.map(function (p) {
1056
+ if (p.source === 'vault' || !p.ref) return p;
1057
+ if (storeMap[p.ref] === 'vault') { changed = true; return Object.assign({}, p, { source: 'vault' }); }
1058
+ return p;
1059
+ });
1060
+ return changed ? next : prev;
877
1061
  });
878
- if (changed) setProviders(next);
879
1062
  return startQueries(map, nonce > 0);
880
1063
  }
881
1064
  return startQueries(null, nonce > 0);
@@ -886,7 +1069,7 @@ window.__ModuleLoader__.load({
886
1069
  startQueries(null, nonce > 0);
887
1070
  });
888
1071
  return function () { alive = false; };
889
- }, [refsKey, nonce]);
1072
+ }, [refsKey, refsPauseKey, nonce]);
890
1073
 
891
1074
  var adapterOptions = Object.keys(ADAPTER_META).map(function (a) {
892
1075
  return React.createElement('option', { key: a, value: a }, ADAPTER_META[a].displayName);
@@ -961,6 +1144,19 @@ window.__ModuleLoader__.load({
961
1144
  React.createElement('button', { type: 'button', className: 'zGbnIq_primaryButton', disabled: busy, onClick: saveManual }, '保存')));
962
1145
  } else if (edit) {
963
1146
  var editManual = edit.kind === 'manual';
1147
+ var enabled = form.enabled === false ? false : true;
1148
+ var enableToggle = React.createElement('div', { className: 'zGbnIq_field' },
1149
+ React.createElement('span', { className: 'zGbnIq_fieldLabel' }, '启用'),
1150
+ React.createElement('label', { className: 'zGbnIq_editorSwitchRow' },
1151
+ React.createElement('button', {
1152
+ type: 'button', role: 'switch',
1153
+ 'aria-checked': enabled ? 'true' : 'false',
1154
+ 'aria-label': enabled ? ('已启用 ' + form.name + ',点击暂停') : ('已暂停 ' + form.name + ',点击启用'),
1155
+ className: 'dsh-pu-switch' + (enabled ? ' dsh-pu-switch-on' : ''),
1156
+ onClick: function () { setForm(Object.assign({}, form, { enabled: !enabled })); },
1157
+ }, React.createElement('span', { className: 'dsh-pu-switch-thumb' })),
1158
+ React.createElement('span', { className: 'dsh-pu-switchText' }, enabled ? '已启用' : '已暂停')),
1159
+ React.createElement('span', { className: 'zGbnIq_advancedHint' }, enabled ? '启用中:正常轮询查询,卡片可切换选择' : '已暂停:不再轮询查询,卡片菜单中不可选(Key 与配置保留)'));
964
1160
  addCard = React.createElement('div', { className: 'zGbnIq_editor' },
965
1161
  React.createElement('div', { className: 'zGbnIq_editorHeader' },
966
1162
  React.createElement('span', { className: 'zGbnIq_editorTitle' }, editManual ? '编辑自定义提供方' : '编辑提供方')),
@@ -976,6 +1172,7 @@ window.__ModuleLoader__.load({
976
1172
  className: 'zGbnIq_input', value: form.name,
977
1173
  onChange: function (e) { setForm(Object.assign({}, form, { name: e.target.value, nameTouched: true })); },
978
1174
  })),
1175
+ enableToggle,
979
1176
  editManual ? React.createElement('div', { className: 'zGbnIq_field' },
980
1177
  React.createElement('span', { className: 'zGbnIq_fieldLabel' }, 'API 密钥'),
981
1178
  React.createElement('input', {
@@ -1022,7 +1219,8 @@ window.__ModuleLoader__.load({
1022
1219
  : providers.map(function (pl) {
1023
1220
  var qs = pl.ref ? queryStatus[pl.adapter + '|' + pl.ref] : null;
1024
1221
  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;
1222
+ // 已暂停的实例不再轮询/查询,状态点固定为「已暂停」灰点,查询状态一律不展示
1223
+ var status = (pl.paused || !pl.ref) ? null : statusOf(refKnownRow, refCheckFailed, !!refStatus[pl.ref], qs);
1026
1224
  return React.createElement(ProviderRow, {
1027
1225
  key: pl.id,
1028
1226
  provider: pl,
@@ -1070,7 +1268,7 @@ window.__ModuleLoader__.load({
1070
1268
  usagePanel);
1071
1269
  }
1072
1270
  // 提供方单行:状态点 + 名称 + 编辑/删除 + 展开详情
1073
- // 状态判定全部交给 statusOf;本组件只做渲染
1271
+ // 状态判定全部交给 statusOf;本组件只做渲染;启用/暂停开关在编辑弹层里管理
1074
1272
  function ProviderRow(props) {
1075
1273
  var pl = props.provider;
1076
1274
  var status = props.status;
@@ -1078,24 +1276,36 @@ window.__ModuleLoader__.load({
1078
1276
  var onToggle = props.onToggle;
1079
1277
  var onEdit = props.onEdit;
1080
1278
  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;
1279
+ var paused = pl.paused === true;
1280
+ var dotEl;
1281
+ if (paused) {
1282
+ // 已暂停:灰点「已暂停」,不可展开(暂停时不再查询,无新鲜结果/错误可看)
1283
+ dotEl = React.createElement('span', {
1284
+ className: 'zGbnIq_credentialDot dsh-pu-dot-paused',
1285
+ role: 'img', 'aria-label': '已暂停',
1286
+ });
1287
+ } else if (status && status.dotCls) {
1288
+ dotEl = React.createElement('span', {
1289
+ className: 'zGbnIq_credentialDot' + status.dotCls,
1290
+ role: 'img', 'aria-label': status.label,
1291
+ style: status.clickable ? { cursor: 'pointer' } : undefined,
1292
+ onClick: status.clickable ? onToggle : undefined,
1293
+ });
1294
+ } else {
1295
+ dotEl = null;
1296
+ }
1087
1297
  var detailEl = null;
1088
- if (expanded && status.detailKind) {
1298
+ if (!paused && expanded && status && status.detailKind) {
1089
1299
  if (status.detailKind === 'err') {
1090
1300
  detailEl = React.createElement('p', { className: 'zGbnIq_error', style: { margin: '10px 0 0' } }, status.detailText);
1091
1301
  } else if (status.detailKind === 'resp') {
1092
1302
  detailEl = React.createElement('div', { className: 'zGbnIq_json' }, status.detailText);
1093
1303
  }
1094
1304
  }
1095
- return React.createElement('li', { key: pl.id, className: 'zGbnIq_rowCard' },
1305
+ return React.createElement('li', { key: pl.id, className: 'zGbnIq_rowCard' + (paused ? ' zGbnIq_rowPaused' : '') },
1096
1306
  React.createElement('div', { className: 'zGbnIq_rowHead' },
1097
1307
  React.createElement('span', { className: 'zGbnIq_rowIdentity' },
1098
- React.createElement('span', { className: 'zGbnIq_rowName' }, pl.name),
1308
+ React.createElement('span', { className: 'zGbnIq_rowName' + (paused ? ' zGbnIq_rowNameDim' : '') }, pl.name),
1099
1309
  dotEl),
1100
1310
  React.createElement('span', { className: 'zGbnIq_rowActions' },
1101
1311
  React.createElement('button', { type: 'button', className: 'zGbnIq_secondaryButton', onClick: onEdit }, '编辑'),
@@ -1124,6 +1334,8 @@ window.__ModuleLoader__.load({
1124
1334
  try {
1125
1335
  if (!getSettings().visible) return; // 用户主动隐藏,不自动复活
1126
1336
  if (getProviders().length === 0) return; // 无提供方实例:卡片本就渲染 null,避免 10s 空转重挂
1337
+ // 全部已暂停同理:currentProvider 回落为 null → 卡片渲染 null,无需重挂
1338
+ if (!currentProvider(getProviders(), getSettings().providerId)) return;
1127
1339
  if (rootEl && rootEl.isConnected && document.querySelector('.dsh-pu-card')) return;
1128
1340
  mount();
1129
1341
  } catch (e) {