dsh-model-extension 0.3.3 → 1.0.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,170 +3,323 @@ window.__ModuleLoader__.load({
3
3
  factory: (require) => {
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
- var __extCss = ".Qji2zG_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}.Qji2zG_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}.Qji2zG_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.Qji2zG_notice{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}.Qji2zG_savedNotice{color:var(--dsw-alias-state-success-primary);margin:0;font-size:12px;line-height:18px}.Qji2zG_rows{flex-direction:column;gap:8px;margin:12px 0 0;padding:0;list-style:none;display:flex}.Qji2zG_rowCard{border:.5px solid var(--dsw-alias-border-l4);border-radius:16px;flex-direction:column;gap:12px;padding:12px 14px;display:flex}.Qji2zG_rowHead{align-items:center;gap:10px;display:flex}.Qji2zG_rowIdentity{align-items:center;gap:6px;min-width:0;display:inline-flex}.Qji2zG_rowName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.Qji2zG_rowTag{border:.5px 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}.Qji2zG_credentialDot{box-sizing:border-box;corner-shape:round;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.Qji2zG_credentialDotConfigured{background:var(--dsw-alias-state-success-primary)}.Qji2zG_credentialDotMissing{background:var(--dsw-alias-state-error-primary)}.Qji2zG_rowActions{align-items:center;gap:4px;margin-left:auto;display:inline-flex}.Qji2zG_primaryButton,.Qji2zG_secondaryButton,.Qji2zG_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}.Qji2zG_primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.Qji2zG_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.Qji2zG_secondaryButton,.Qji2zG_addButton{border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-primary);background:0 0}.Qji2zG_secondaryButton:hover:not(:disabled),.Qji2zG_addButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.Qji2zG_secondaryButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.Qji2zG_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}.Qji2zG_dangerButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.Qji2zG_rowActions .Qji2zG_secondaryButton,.Qji2zG_rowActions .Qji2zG_dangerButton{border-radius:14px;height:28px;padding:0 10px;font-size:12px;line-height:18px}.Qji2zG_primaryButton:disabled,.Qji2zG_secondaryButton:disabled,.Qji2zG_dangerButton:disabled,.Qji2zG_addButton:disabled,.Qji2zG_linkButton:disabled,.Qji2zG_addModelButton:disabled{opacity:.4;cursor:default}.Qji2zG_primaryButton:focus-visible,.Qji2zG_secondaryButton:focus-visible,.Qji2zG_dangerButton:focus-visible,.Qji2zG_addButton:focus-visible,.Qji2zG_linkButton:focus-visible,.Qji2zG_addModelButton:focus-visible,.Qji2zG_iconButton:focus-visible,.Qji2zG_customizedSummary:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}.Qji2zG_editor{background:var(--dsw-alias-bg-module-platform);border-radius:12px;flex-direction:column;gap:14px;padding:14px 16px;display:flex}.Qji2zG_editorHeader{align-items:baseline;gap:8px;display:flex}.Qji2zG_editorTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.Qji2zG_editorRoute{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.Qji2zG_field{flex-direction:column;gap:6px;display:flex}.Qji2zG_fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:inline-flex}.Qji2zG_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}.Qji2zG_linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.Qji2zG_advancedHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.Qji2zG_editorActions{justify-content:flex-end;gap:8px;display:flex}.Qji2zG_addBlock{flex-direction:column;gap:12px;display:flex}.Qji2zG_addActions{flex-wrap:wrap;gap:10px;display:flex}.Qji2zG_addButton{border:1px dashed var(--dsw-alias-border-l3);border-radius:16px;flex:1 1 0;gap:6px;min-width:180px;height:44px}.Qji2zG_addCard,.Qji2zG_setupCard{background:var(--dsw-alias-bg-module-platform);border-radius:12px;flex-direction:column;gap:14px;padding:14px 16px;list-style:none;display:flex}.Qji2zG_addCard .Qji2zG_editor,.Qji2zG_setupCard .Qji2zG_editor{background:0 0;padding:0}.Qji2zG_customized{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:10px}.Qji2zG_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}.Qji2zG_customizedSummary::-webkit-details-marker{display:none}.Qji2zG_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)}.Qji2zG_customized[open]>.Qji2zG_customizedSummary:before{transform:rotate(45deg)translate(-1px,-1px)}.Qji2zG_customizedSummary:hover{color:var(--dsw-alias-label-primary)}.Qji2zG_customizedBody{flex-direction:column;gap:12px;padding-top:12px;display:flex}.Qji2zG_modelCatalog{border-top:.5px solid var(--dsw-alias-border-l2);flex-direction:column;gap:10px;padding-top:12px;display:flex}.Qji2zG_modelCatalogHeading{flex-direction:column;gap:2px;display:flex}.Qji2zG_modelCatalogTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.Qji2zG_modelCatalogMeta,.Qji2zG_modelEmpty{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.Qji2zG_modelList{flex-direction:column;gap:8px;display:flex}.Qji2zG_modelListHead{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.Qji2zG_modelEntry{border:.5px solid var(--dsw-alias-border-l4);border-radius:10px;padding:6px}.Qji2zG_modelRow{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) auto auto;align-items:center;gap:6px;display:grid}.Qji2zG_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}.Qji2zG_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.Qji2zG_iconButton:disabled{cursor:default;opacity:.4}.Qji2zG_iconButtonDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}.Qji2zG_modelAdvanced{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:8px;padding:8px 4px 2px;display:grid}.Qji2zG_modelField{flex-direction:column;gap:4px;display:flex}.Qji2zG_modelFieldLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.Qji2zG_modelEmpty{border:1px dashed var(--dsw-alias-border-l3);text-align:center;border-radius:8px;padding:12px}.Qji2zG_addModelButton{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);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}.Qji2zG_addModelButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.Qji2zG_input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);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.Qji2zG_input{cursor:pointer;max-width:240px}.Qji2zG_input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.Qji2zG_input::placeholder{color:var(--dsw-alias-label-dimmed)}.Qji2zG_input:disabled{opacity:.6;cursor:default}.Qji2zG_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}.Qji2zG_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}.Qji2zG_deleteDialog{width:min(480px,100%)}.Qji2zG_deleteConfirm:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.Qji2zG_deleteConfirm:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.Qji2zG_hiddenLabel{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}@media (prefers-reduced-motion:reduce){.Qji2zG_customizedSummary:before,.Qji2zG_switchThumb{transition:none}}.Qji2zG_fetchDialog{--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);max-width:520px}.Qji2zG_candidateToolbar{align-items:center;gap:8px;margin-bottom:6px;display:flex}.Qji2zG_candidateSearch{flex:240px;min-width:0}.Qji2zG_candidateList{flex-direction:column;gap:2px;max-height:320px;margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}.Qji2zG_candidate{border-radius:6px}.Qji2zG_candidateLabel{cursor:pointer;align-items:center;gap:8px;padding:6px 8px;display:flex}.Qji2zG_candidateId{font-family:var(--ds-font-family-code);overflow-wrap:anywhere;flex:auto;font-size:13px}.Qji2zG_candidateEmpty{color:var(--dsw-alias-label-secondary);text-align:center;margin:24px 0;font-size:13px;line-height:20px}.cDGz_W_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}.cDGz_W_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}.cDGz_W_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.cDGz_W_notice{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}.cDGz_W_savedNotice{color:var(--dsw-alias-state-success-primary);margin:0;font-size:12px;line-height:18px}.cDGz_W_rows{flex-direction:column;gap:8px;margin:12px 0 0;padding:0;list-style:none;display:flex}.cDGz_W_rowCard{border:.5px solid var(--dsw-alias-border-l4);border-radius:16px;flex-direction:column;gap:12px;padding:12px 14px;display:flex}.cDGz_W_rowHead{align-items:center;gap:10px;display:flex}.cDGz_W_rowIdentity{align-items:center;gap:6px;min-width:0;display:inline-flex}.cDGz_W_rowName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.cDGz_W_rowTag{border:.5px 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}.cDGz_W_credentialDot{box-sizing:border-box;corner-shape:round;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.cDGz_W_credentialDotConfigured{background:var(--dsw-alias-state-success-primary)}.cDGz_W_credentialDotMissing{background:var(--dsw-alias-state-error-primary)}.cDGz_W_rowActions{align-items:center;gap:4px;margin-left:auto;display:inline-flex}.cDGz_W_primaryButton,.cDGz_W_secondaryButton,.cDGz_W_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}.cDGz_W_primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.cDGz_W_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.cDGz_W_secondaryButton,.cDGz_W_addButton{border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-primary);background:0 0}.cDGz_W_secondaryButton:hover:not(:disabled),.cDGz_W_addButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.cDGz_W_secondaryButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.cDGz_W_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}.cDGz_W_dangerButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.cDGz_W_rowActions .cDGz_W_secondaryButton,.cDGz_W_rowActions .cDGz_W_dangerButton{border-radius:14px;height:28px;padding:0 10px;font-size:12px;line-height:18px}.cDGz_W_primaryButton:disabled,.cDGz_W_secondaryButton:disabled,.cDGz_W_dangerButton:disabled,.cDGz_W_addButton:disabled,.cDGz_W_linkButton:disabled,.cDGz_W_addModelButton:disabled{opacity:.4;cursor:default}.cDGz_W_primaryButton:focus-visible,.cDGz_W_secondaryButton:focus-visible,.cDGz_W_dangerButton:focus-visible,.cDGz_W_addButton:focus-visible,.cDGz_W_linkButton:focus-visible,.cDGz_W_addModelButton:focus-visible,.cDGz_W_iconButton:focus-visible,.cDGz_W_customizedSummary:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}.cDGz_W_editor{background:var(--dsw-alias-bg-module-platform);border-radius:12px;flex-direction:column;gap:14px;padding:14px 16px;display:flex}.cDGz_W_editorHeader{align-items:baseline;gap:8px;display:flex}.cDGz_W_editorTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.cDGz_W_editorRoute{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.cDGz_W_field{flex-direction:column;gap:6px;display:flex}.cDGz_W_fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:inline-flex}.cDGz_W_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}.cDGz_W_linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.cDGz_W_advancedHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.cDGz_W_editorActions{justify-content:flex-end;gap:8px;display:flex}.cDGz_W_addBlock{flex-direction:column;gap:12px;display:flex}.cDGz_W_addActions{flex-wrap:wrap;gap:10px;display:flex}.cDGz_W_addButton{border:1px dashed var(--dsw-alias-border-l3);border-radius:16px;flex:1 1 0;gap:6px;min-width:180px;height:44px}.cDGz_W_addCard,.cDGz_W_setupCard{background:var(--dsw-alias-bg-module-platform);border-radius:12px;flex-direction:column;gap:14px;padding:14px 16px;list-style:none;display:flex}.cDGz_W_addCard .cDGz_W_editor,.cDGz_W_setupCard .cDGz_W_editor{background:0 0;padding:0}.cDGz_W_customized{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:10px}.cDGz_W_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}.cDGz_W_customizedSummary::-webkit-details-marker{display:none}.cDGz_W_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)}.cDGz_W_customized[open]>.cDGz_W_customizedSummary:before{transform:rotate(45deg)translate(-1px,-1px)}.cDGz_W_customizedSummary:hover{color:var(--dsw-alias-label-primary)}.cDGz_W_customizedBody{flex-direction:column;gap:12px;padding-top:12px;display:flex}.cDGz_W_modelCatalog{border-top:.5px solid var(--dsw-alias-border-l2);flex-direction:column;gap:10px;padding-top:12px;display:flex}.cDGz_W_modelCatalogHeading{flex-direction:column;gap:2px;display:flex}.cDGz_W_modelCatalogTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.cDGz_W_modelCatalogMeta,.cDGz_W_modelEmpty{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.cDGz_W_modelList{flex-direction:column;gap:8px;display:flex}.cDGz_W_modelListHead{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.cDGz_W_modelEntry{border:.5px solid var(--dsw-alias-border-l4);border-radius:10px;padding:6px}.cDGz_W_modelRow{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) auto auto;align-items:center;gap:6px;display:grid}.cDGz_W_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}.cDGz_W_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.cDGz_W_iconButton:disabled{cursor:default;opacity:.4}.cDGz_W_iconButtonDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}.cDGz_W_modelAdvanced{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:8px;padding:8px 4px 2px;display:grid}.cDGz_W_modelExtensionFields{box-sizing:border-box;width:100%;display:block}.cDGz_W_modelField{flex-direction:column;gap:4px;display:flex}.cDGz_W_modelFieldLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.cDGz_W_modelEmpty{border:1px dashed var(--dsw-alias-border-l3);text-align:center;border-radius:8px;padding:12px}.cDGz_W_addModelButton{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);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}.cDGz_W_addModelButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.cDGz_W_input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);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.cDGz_W_input{cursor:pointer;max-width:240px}.cDGz_W_input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.cDGz_W_input::placeholder{color:var(--dsw-alias-label-dimmed)}.cDGz_W_input:disabled{opacity:.6;cursor:default}.cDGz_W_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}.cDGz_W_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}.cDGz_W_deleteDialog{width:min(480px,100%)}.cDGz_W_deleteConfirm:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.cDGz_W_deleteConfirm:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.cDGz_W_hiddenLabel{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}@media (prefers-reduced-motion:reduce){.cDGz_W_customizedSummary:before,.cDGz_W_switchThumb{transition:none}}.cDGz_W_fetchDialog{--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);max-width:520px}.cDGz_W_candidateToolbar{align-items:center;gap:8px;margin-bottom:6px;display:flex}.cDGz_W_candidateSearch{flex:240px;min-width:0}.cDGz_W_candidateList{flex-direction:column;gap:2px;max-height:320px;margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}.cDGz_W_candidate{border-radius:6px}.cDGz_W_candidateLabel{cursor:pointer;align-items:center;gap:8px;padding:6px 8px;display:flex}.cDGz_W_candidateId{font-family:var(--ds-font-family-code);overflow-wrap:anywhere;flex:auto;font-size:13px}.cDGz_W_candidateEmpty{color:var(--dsw-alias-label-secondary);text-align:center;margin:24px 0;font-size:13px;line-height:20px}\n";
6
+ var __extCss = ".J84-9a_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}.J84-9a_titleRow{align-items:center;gap:10px;display:flex}.J84-9a_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}.J84-9a_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.J84-9a_savedNotice{color:var(--dsw-alias-state-success-primary);margin:0;font-size:12px;line-height:18px}.J84-9a_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}.J84-9a_rows{flex-direction:column;gap:8px;margin:12px 0 0;padding:0;list-style:none;display:flex}.J84-9a_rowCard{border:.5px solid var(--dsw-alias-border-l4);border-radius:16px;flex-direction:column;gap:12px;padding:12px 14px;display:flex}.J84-9a_rowHead{align-items:center;gap:10px;display:flex}.J84-9a_rowIdentity{align-items:center;gap:6px;min-width:0;display:inline-flex}.J84-9a_rowName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.J84-9a_rowTag{border:.5px 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}.J84-9a_credentialDot{box-sizing:border-box;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.J84-9a_credentialDotConfigured{background:var(--dsw-alias-state-success-primary)}.J84-9a_credentialDotMissing{background:var(--dsw-alias-state-error-primary)}.J84-9a_rowActions{align-items:center;gap:4px;margin-left:auto;display:inline-flex}.J84-9a_primaryButton,.J84-9a_secondaryButton,.J84-9a_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}.J84-9a_primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.J84-9a_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.J84-9a_secondaryButton,.J84-9a_addButton{border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-primary);background:0 0}.J84-9a_secondaryButton:hover:not(:disabled),.J84-9a_addButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.J84-9a_secondaryButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.J84-9a_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}.J84-9a_dangerButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.J84-9a_rowActions .J84-9a_secondaryButton,.J84-9a_rowActions .J84-9a_dangerButton{border-radius:14px;height:28px;padding:0 10px;font-size:12px;line-height:18px}.J84-9a_primaryButton:disabled,.J84-9a_secondaryButton:disabled,.J84-9a_dangerButton:disabled,.J84-9a_addButton:disabled,.J84-9a_linkButton:disabled,.J84-9a_addModelButton:disabled{opacity:.4;cursor:default}.J84-9a_primaryButton:focus-visible,.J84-9a_secondaryButton:focus-visible,.J84-9a_dangerButton:focus-visible,.J84-9a_addButton:focus-visible,.J84-9a_linkButton:focus-visible,.J84-9a_addModelButton:focus-visible,.J84-9a_iconButton:focus-visible,.J84-9a_customizedSummary:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}.J84-9a_editor{background:var(--dsw-alias-bg-module-platform);border-radius:12px;flex-direction:column;gap:14px;padding:14px 16px;display:flex}.J84-9a_field{flex-direction:column;gap:6px;display:flex}.J84-9a_fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:inline-flex}.J84-9a_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}.J84-9a_linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.J84-9a_advancedHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.J84-9a_editorActions{justify-content:flex-end;gap:8px;display:flex}.J84-9a_addBlock{flex-direction:column;gap:12px;display:flex}.J84-9a_addActions{flex-wrap:wrap;gap:10px;display:flex}.J84-9a_addButton{border:1px dashed var(--dsw-alias-border-l3);border-radius:16px;flex:1 1 0;gap:6px;min-width:180px;height:44px}.J84-9a_addCard{background:var(--dsw-alias-bg-module-platform);border-radius:12px;flex-direction:column;gap:14px;padding:14px 16px;list-style:none;display:flex}.J84-9a_addCard .J84-9a_editor,.J84-9a_setupCard .J84-9a_editor{background:0 0;padding:0}.J84-9a_tabs{flex-direction:column;gap:12px;display:flex}.J84-9a_tabBar{border-bottom:.5px solid var(--dsw-alias-border-l2);gap:4px;display:flex}.J84-9a_tabButton{appearance:none;font:inherit;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;padding:6px 12px 8px;font-size:13px;line-height:20px;position:relative}.J84-9a_tabButton:hover{color:var(--dsw-alias-label-primary)}.J84-9a_tabButton:disabled{opacity:.4;cursor:default}.J84-9a_tabButtonActive{color:var(--dsw-alias-label-primary);font-weight:500}.J84-9a_tabButtonActive:after{content:\"\";background:var(--dsw-alias-brand-primary);border-radius:2px;height:2px;position:absolute;bottom:-1px;left:10px;right:10px}.J84-9a_tabPanel{flex-direction:column;gap:12px;display:flex}.J84-9a_modelCatalog{flex-direction:column;gap:10px;display:flex}.J84-9a_modelCatalogHeading{flex-direction:column;gap:2px;display:flex}.J84-9a_modelCatalogTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.J84-9a_modelCatalogMeta,.J84-9a_modelEmpty{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.J84-9a_modelList{flex-direction:column;gap:8px;display:flex}.J84-9a_modelListHead{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.J84-9a_modelEntry{border:.5px solid var(--dsw-alias-border-l4);border-radius:10px;padding:6px}.J84-9a_modelRow{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) auto auto;align-items:center;gap:6px;display:grid}.J84-9a_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}.J84-9a_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.J84-9a_iconButton:disabled{cursor:default;opacity:.4}.J84-9a_iconButtonDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}.J84-9a_modelAdvanced{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:8px;padding:8px 4px 2px;display:grid}.J84-9a_modelField{flex-direction:column;gap:4px;display:flex}.J84-9a_modelFieldLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.J84-9a_modelEmpty{border:1px dashed var(--dsw-alias-border-l3);text-align:center;border-radius:8px;padding:12px}.J84-9a_addModelButton{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);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}.J84-9a_addModelButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.J84-9a_input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);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.J84-9a_input{cursor:pointer;max-width:240px}.J84-9a_input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.J84-9a_input::placeholder{color:var(--dsw-alias-label-dimmed)}.J84-9a_input:disabled{opacity:.6;cursor:default}.J84-9a_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}.J84-9a_deleteDialog{width:min(480px,100%)}.J84-9a_deleteConfirm:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.J84-9a_deleteConfirm:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.J84-9a_fetchDialog{max-width:520px}.J84-9a_candidateToolbar{align-items:center;gap:8px;margin-bottom:6px;display:flex}.J84-9a_candidateSearch{flex:240px;min-width:0}.J84-9a_candidateList{flex-direction:column;gap:2px;max-height:320px;margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}.J84-9a_candidate{border-radius:6px}.J84-9a_candidateLabel{cursor:pointer;align-items:center;gap:8px;padding:6px 8px;display:flex}.J84-9a_candidateId{overflow-wrap:anywhere;flex:auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px}.J84-9a_candidateEmpty{color:var(--dsw-alias-label-secondary);text-align:center;margin:24px 0;font-size:13px;line-height:20px}[hidden]{display:none!important}.J84-9a_extBlock{border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-1);border-radius:10px;flex-direction:column;gap:10px;margin:8px 4px 2px;padding:10px;display:flex}.J84-9a_extGroup{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:12px}.J84-9a_extGroupFirst{border-top:none;padding-top:0}.J84-9a_extCheck{cursor:pointer;align-items:flex-start;gap:8px;display:flex}.J84-9a_extCheckLabel{color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}.J84-9a_extHint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.J84-9a_extGated{flex-direction:column;gap:12px;padding-left:2px;display:flex}.J84-9a_extField{flex-direction:column;gap:6px;display:flex}.J84-9a_extLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.J84-9a_extTable{border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;overflow:hidden}.J84-9a_extTHead{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-module-platform);grid-template-columns:64px minmax(0,1fr) minmax(0,1.2fr);font-size:12px;line-height:18px;display:grid}.J84-9a_extTHead>div{padding:6px 10px}.J84-9a_extTHead>div+div{border-left:.5px solid var(--dsw-alias-border-l2)}.J84-9a_extTRow{border-top:.5px solid var(--dsw-alias-border-l2);grid-template-columns:64px minmax(0,1fr) minmax(0,1.2fr);align-items:center;display:grid}.J84-9a_extTRow>div{padding:6px 10px;font-size:14px}.J84-9a_extTRow>div+div{border-left:.5px solid var(--dsw-alias-border-l2)}.J84-9a_extTRow input[type=text]{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);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}.J84-9a_extTRow input[type=text]:disabled{opacity:.6;cursor:default}.J84-9a_extCheckbox{accent-color:var(--dsw-alias-brand-primary);flex:none;width:15px;height:15px}.J84-9a_switchRow{cursor:pointer;align-items:center;gap:8px;font-size:14px;line-height:22px;display:flex}.J84-9a_extTag{background:var(--dsw-static-deepseek-100);color:var(--dsw-static-deepseek-600);border:.5px solid #4176e659;border-radius:4px;flex:none;padding:1px 6px;font-size:11px;font-weight:500;line-height:16px}.J84-9a_modalMask{background:var(--dsw-alias-bg-mask-1);z-index:100;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}.J84-9a_modal{background:var(--dsw-specific-menu);border-radius:16px;width:100%;padding:20px;box-shadow:0 12px 40px #0000002e}.J84-9a_modalTitle{color:var(--dsw-alias-label-primary);margin-bottom:8px;font-size:16px;font-weight:500;line-height:24px}.J84-9a_modalDesc{color:var(--dsw-alias-label-secondary);margin-bottom:16px;font-size:14px;line-height:22px}.J84-9a_modalFoot{justify-content:flex-end;gap:8px;display:flex}.J84-9a_quickWrap{flex-direction:column;gap:6px;display:flex;position:relative}.J84-9a_quickList{z-index:20;border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-specific-menu);border-radius:8px;max-height:264px;margin-top:2px;padding:4px;position:absolute;top:100%;left:0;right:0;overflow-y:auto;box-shadow:0 8px 24px #0000001f}.J84-9a_quickItem{width:100%;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;overflow-wrap:anywhere;background:0 0;border:none;border-radius:6px;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;display:block}.J84-9a_quickItem:hover{background:var(--dsw-alias-interactive-bg-hover)}.J84-9a_quickEmpty{color:var(--dsw-alias-label-tertiary);padding:6px 8px;font-size:12px;line-height:18px}@media (prefers-reduced-motion:reduce){.J84-9a_customizedSummary:before{transition:none}}\n";
7
7
  if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css="dsh-model-extension"]') === null) {
8
8
  var __tag = document.createElement('style');
9
9
  __tag.dataset.plugin = "dsh-model-extension";
10
- __tag.dataset.pluginCss = "dsh-model-extension/ModelsSection.ext.module.css";
10
+ __tag.dataset.pluginCss = "dsh-model-extension/models-plus.module.css";
11
11
  __tag.textContent = __extCss;
12
12
  document.head.appendChild(__tag);
13
13
  }
14
14
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
15
15
  let react = require("react");
16
16
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
17
- let react_jsx_runtime = require("react/jsx-runtime");
18
17
  let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
19
- //#region
18
+ let react_jsx_runtime = require("react/jsx-runtime");
19
+ //#region src/client/vendor/store.ts
20
20
  /**
21
- * Browser-side judgement of a typed API key.
22
- * @module @deepseek-ai/dsh-client-ui-settings-models/apiKey
21
+ * Any route key walks a dict schema to the same profile node, so the lookup
22
+ * names one that cannot collide with a configured route.
23
23
  */
24
+ const PROBE_ROUTE = "\0probe";
24
25
  /**
25
- * Twin of `normalizeApiKey` in `@deepseek-ai/dsh-llm`: printable ASCII, space
26
- * excluded. Client packages reference only client packages, so the charset
27
- * rule is mirrored here rather than imported; keep the two in step, as
28
- * `validateDeepSeekModels` is kept in step with the host's `catalogModel`.
26
+ * Join declared configurable providers with the currently registered routes.
27
+ * @param registered - live provider routes in registration order.
28
+ * @param directory - declared configurable providers in declaration order.
29
+ * @returns declared rows followed by live routes with no declaration.
29
30
  */
30
- const LEGAL_API_KEY = /^[\x21-\x7E]+$/;
31
+ function joinProviderDirectory(registered, directory) {
32
+ const active = new Set(registered.map((provider) => provider.id));
33
+ const declared = new Set(directory.map((entry) => entry.provider));
34
+ const rows = directory.map((entry) => ({
35
+ provider: entry.provider,
36
+ displayName: entry.displayName,
37
+ settingsNs: entry.settingsNs,
38
+ settingsPath: [...entry.settingsPath],
39
+ active: active.has(entry.provider),
40
+ ...entry.declared === void 0 ? {} : { declared: entry.declared }
41
+ }));
42
+ for (const provider of registered) {
43
+ if (declared.has(provider.id)) continue;
44
+ rows.push({
45
+ provider: provider.id,
46
+ displayName: provider.name,
47
+ settingsNs: "",
48
+ settingsPath: [],
49
+ active: true
50
+ });
51
+ }
52
+ return rows;
53
+ }
31
54
  /**
32
- * A pasted `NAME=value` environment line. Two narrowings keep real keys clear
33
- * of it: the name must be upper-case, so `sk-` forms break at the hyphen, and
34
- * the `=` must be followed by something other than another `=`, so base64
35
- * padding on an all-upper-case key (`ABCD==`) is not mistaken for an
36
- * assignment. This heuristic runs only here — a resolver applying it could
37
- * lock a user out of a gateway whose key legitimately takes this shape, with
38
- * the environment refusing it too and no way through.
55
+ * Derive the conventional credential reference for a provider route: the v1
56
+ * page never asks for an environment-variable name, so a typed key stores
57
+ * under this derived reference and the profile records it as `apiKeyEnv`.
58
+ * @param provider - provider route id (e.g. `anthropic`, `minimax-cn`).
59
+ * @returns the derived reference name (e.g. `MINIMAX_CN_API_KEY`).
39
60
  */
40
- const ENV_LINE = /^[A-Z][A-Z0-9_]*=[^=]/;
41
- /** Whether a value is wrapped in one matching pair of quotes. */
42
- function isQuoted(value) {
43
- const first = value[0];
44
- if (first !== "\"" && first !== "'" && first !== "`") return false;
45
- return value.length > 1 && value.endsWith(first);
61
+ function deriveKeyRef(provider) {
62
+ return `${provider.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_API_KEY`;
46
63
  }
47
64
  /**
48
- * Judge the key input's current value.
49
- *
50
- * An empty field is not a failure: every card opens with it empty even when a
51
- * key is already stored, where it means keep that one. A field holding only
52
- * whitespace is a failure rather than an empty field, so typed input is never
53
- * silently discarded.
54
- * @param draft - the key input's current value, untrimmed.
55
- * @returns the copy key for a field-level failure, or `undefined` to allow submit.
65
+ * The wire protocols a hand-declared route may name, read out of the owning
66
+ * namespace's own schema. This stays a schema read rather than a wire field so
67
+ * the choices the page offers cannot drift from the ones the adapter accepts:
68
+ * both come from the same `Config`.
69
+ * @param namespace - the namespace view whose schema declares the profile shape.
70
+ * @param schema - settings schema operations.
71
+ * @returns the protocol identifiers, or an empty list when the schema has none.
56
72
  */
57
- function apiKeyFailure(draft) {
58
- if (draft.length === 0) return void 0;
59
- const value = draft.trim();
60
- if (value.length === 0) return "keyBlank";
61
- if (ENV_LINE.test(value) || isQuoted(value)) return "keyIllegalCharacters";
62
- if (!LEGAL_API_KEY.test(value)) return "keyIllegalCharacters";
73
+ function protocolChoices(namespace, schema) {
74
+ if (namespace === void 0) return [];
75
+ const list = schema.nodeAtPath(schema.rehydrate(namespace.schema), [
76
+ "providers",
77
+ PROBE_ROUTE,
78
+ "api"
79
+ ]);
80
+ if (list?.type !== "union" || list.list === void 0) return [];
81
+ return list.list.map((entry) => entry.value).filter((value) => typeof value === "string");
63
82
  }
64
- //#endregion
65
- //#region
66
- var ModelsSection_module_default = {
67
- "addActions": "Qji2zG_addActions",
68
- "addBlock": "Qji2zG_addBlock",
69
- "addButton": "Qji2zG_addButton",
70
- "addCard": "Qji2zG_addCard",
71
- "addModelButton": "Qji2zG_addModelButton",
72
- "advancedHint": "Qji2zG_advancedHint",
73
- "candidate": "Qji2zG_candidate",
74
- "candidateEmpty": "Qji2zG_candidateEmpty",
75
- "candidateId": "Qji2zG_candidateId",
76
- "candidateLabel": "Qji2zG_candidateLabel",
77
- "candidateList": "Qji2zG_candidateList",
78
- "candidateSearch": "Qji2zG_candidateSearch",
79
- "candidateToolbar": "Qji2zG_candidateToolbar",
80
- "credentialDot": "Qji2zG_credentialDot",
81
- "credentialDotConfigured": "Qji2zG_credentialDotConfigured",
82
- "credentialDotMissing": "Qji2zG_credentialDotMissing",
83
- "customized": "Qji2zG_customized",
84
- "customizedBody": "Qji2zG_customizedBody",
85
- "customizedSummary": "Qji2zG_customizedSummary",
86
- "dangerButton": "Qji2zG_dangerButton",
87
- "deleteConfirm": "Qji2zG_deleteConfirm",
88
- "deleteDialog": "Qji2zG_deleteDialog",
89
- "editor": "Qji2zG_editor",
90
- "editorActions": "Qji2zG_editorActions",
91
- "editorHeader": "Qji2zG_editorHeader",
92
- "editorRoute": "Qji2zG_editorRoute",
93
- "editorTitle": "Qji2zG_editorTitle",
94
- "error": "Qji2zG_error",
95
- "fetchDialog": "Qji2zG_fetchDialog",
96
- "field": "Qji2zG_field",
97
- "fieldLabel": "Qji2zG_fieldLabel",
98
- "hiddenLabel": "Qji2zG_hiddenLabel",
99
- "iconButton": "Qji2zG_iconButton",
100
- "iconButtonDanger": "Qji2zG_iconButtonDanger",
101
- "input": "Qji2zG_input",
102
- "intro": "Qji2zG_intro",
103
- "linkButton": "Qji2zG_linkButton",
104
- "modelAdvanced": "Qji2zG_modelAdvanced",
105
- "modelCatalog": "Qji2zG_modelCatalog",
106
- "modelCatalogHeading": "Qji2zG_modelCatalogHeading",
107
- "modelCatalogMeta": "Qji2zG_modelCatalogMeta",
108
- "modelCatalogTitle": "Qji2zG_modelCatalogTitle",
109
- "modelEmpty": "Qji2zG_modelEmpty",
110
- "modelEntry": "Qji2zG_modelEntry",
111
- "modelField": "Qji2zG_modelField",
112
- "modelFieldLabel": "Qji2zG_modelFieldLabel",
113
- "modelList": "Qji2zG_modelList",
114
- "modelListHead": "Qji2zG_modelListHead",
115
- "modelRow": "Qji2zG_modelRow",
116
- "notice": "Qji2zG_notice",
117
- "primaryButton": "Qji2zG_primaryButton",
118
- "rowActions": "Qji2zG_rowActions",
119
- "rowCard": "Qji2zG_rowCard",
120
- "rowHead": "Qji2zG_rowHead",
121
- "rowIdentity": "Qji2zG_rowIdentity",
122
- "rowName": "Qji2zG_rowName",
123
- "rows": "Qji2zG_rows",
124
- "rowTag": "Qji2zG_rowTag",
125
- "savedNotice": "Qji2zG_savedNotice",
126
- "secondaryButton": "Qji2zG_secondaryButton",
127
- "section": "Qji2zG_section",
128
- "selectInput": "Qji2zG_selectInput",
129
- "setupCard": "Qji2zG_setupCard",
130
- "switchThumb": "Qji2zG_switchThumb",
131
- "title": "Qji2zG_title"
83
+ /** The credential reference a resolved profile names (its `apiKeyEnv` field). */
84
+ function apiKeyEnvOf(namespace, path, schema) {
85
+ if (namespace === void 0) return void 0;
86
+ const profile = schema.getPath(namespace.value, path);
87
+ if (typeof profile !== "object" || profile === null) return void 0;
88
+ const ref = profile.apiKeyEnv;
89
+ return typeof ref === "string" && ref.length > 0 ? ref : void 0;
90
+ }
91
+ /** The models settings page controller (one per settings surface). */
92
+ var ModelsSettingsStore = class {
93
+ ctx;
94
+ schema;
95
+ describeFace;
96
+ /** The snapshot the section renders from (uSES-safe store). */
97
+ store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)({
98
+ status: "idle",
99
+ error: null,
100
+ credentialError: null,
101
+ writable: false,
102
+ rows: [],
103
+ namespaces: /* @__PURE__ */ new Map()
104
+ });
105
+ /** Latest load wins; an older response never overwrites a newer one. */
106
+ generation = 0;
107
+ /**
108
+ * @param ctx - the page plugin's context, whose `remote.llm` and
109
+ * `remote.credentials` namespaces carry the directory and credential reads.
110
+ * @param schema - settings-owned schema and immutable path operations.
111
+ * @param describeFace - the shared mirror's describe face (namespace views and writability).
112
+ */
113
+ constructor(ctx, schema, describeFace) {
114
+ this.ctx = ctx;
115
+ this.schema = schema;
116
+ this.describeFace = describeFace;
117
+ }
118
+ /**
119
+ * Refresh the whole page snapshot: the provider directory and the mirror's
120
+ * settings answer in parallel, then one batched credential describe over
121
+ * every referenced ref. Provider failure or absence of an initial settings
122
+ * answer keeps the last good rows and surfaces an error; a failed settings
123
+ * refresh reuses the mirror's held view.
124
+ * @returns nothing; the snapshot carries the outcome.
125
+ */
126
+ async load() {
127
+ const generation = ++this.generation;
128
+ this.store.update((s) => {
129
+ s.status = "loading";
130
+ s.error = null;
131
+ });
132
+ const [registered, declared] = await Promise.all([
133
+ this.ctx.remote.llm.listProviders(),
134
+ this.ctx.remote.llm.listConfigurableProviders(),
135
+ this.describeFace.ensure()
136
+ ]);
137
+ if (!registered.ok) {
138
+ this.failLoad(generation, registered.error.message);
139
+ return;
140
+ }
141
+ if (!declared.ok) {
142
+ this.failLoad(generation, declared.error.message);
143
+ return;
144
+ }
145
+ const mirrored = this.describeFace.getSnapshot();
146
+ if (mirrored.view === void 0) {
147
+ this.failLoad(generation, mirrored.error ?? "settings are unavailable in this browser");
148
+ return;
149
+ }
150
+ const providers = joinProviderDirectory(registered.value, declared.value);
151
+ const writable = mirrored.view.writable;
152
+ const views = mirrored.view.namespaces;
153
+ const namespaces = new Map(views.map((view) => [view.ns, view]));
154
+ const rows = providers.map((entry) => {
155
+ const namespace = namespaces.get(entry.settingsNs);
156
+ return {
157
+ entry,
158
+ configured: namespace !== void 0 && (entry.settingsPath.length === 0 || this.schema.getPath(namespace.value, entry.settingsPath) !== void 0),
159
+ removable: namespace !== void 0 && entry.settingsPath.length > 0 && this.schema.hasPath(namespace.user, entry.settingsPath) && !this.schema.hasPath(namespace.base, entry.settingsPath),
160
+ apiKeyEnv: apiKeyEnvOf(namespace, entry.settingsPath, this.schema),
161
+ credential: void 0
162
+ };
163
+ });
164
+ const refs = [...new Set(rows.map((row) => row.apiKeyEnv ?? deriveKeyRef(row.entry.provider)))];
165
+ let credentials = {};
166
+ let credentialError = null;
167
+ if (refs.length > 0) {
168
+ const response = await this.ctx.remote.credentials.describe(refs);
169
+ if (response.ok) credentials = response.value;
170
+ else credentialError = response.error.message;
171
+ }
172
+ if (generation !== this.generation) return;
173
+ this.store.update((s) => {
174
+ s.status = "ready";
175
+ s.error = null;
176
+ s.credentialError = credentialError;
177
+ s.writable = writable;
178
+ s.rows = rows.map((row) => {
179
+ const named = row.apiKeyEnv === void 0 ? void 0 : credentials[row.apiKeyEnv];
180
+ const derived = row.apiKeyEnv !== void 0 ? void 0 : credentials[deriveKeyRef(row.entry.provider)];
181
+ return {
182
+ ...row,
183
+ ...named === void 0 ? {} : { credential: named },
184
+ ...derived === void 0 ? {} : { derivedCredential: derived }
185
+ };
186
+ });
187
+ s.namespaces = namespaces;
188
+ });
189
+ }
190
+ /** Publish one load's failure text, unless a newer load already took over. */
191
+ failLoad(generation, message) {
192
+ if (generation !== this.generation) return;
193
+ this.store.update((s) => {
194
+ s.status = "error";
195
+ s.error = message;
196
+ });
197
+ }
132
198
  };
199
+ /**
200
+ * Whether a joined row can serve model requests as it stands: the route is
201
+ * registered with the adapter registry, and whatever credential its resolved
202
+ * profile names is stored. A profile naming no reference authenticates through
203
+ * the provider's own path (the Bedrock chain, Vertex ADC, a gateway that needs
204
+ * nothing), as does a live route with no settings address at all, so neither
205
+ * owes this page a key.
206
+ * @param row - one joined provider row.
207
+ * @returns whether the user already has this provider to talk to.
208
+ */
209
+ function providerUsable(row) {
210
+ if (!row.entry.active) return false;
211
+ if (row.apiKeyEnv === void 0) return true;
212
+ return row.credential?.configured === true;
213
+ }
133
214
  //#endregion
134
- //#region
215
+ //#region src/client/models-plus/models-index.ts
216
+ /** Same-origin routes served by the server half. */
217
+ const INDEX_URL = "/plugins/dsh-model-extension/models-index";
218
+ const DOWNLOAD_URL = "/plugins/dsh-model-extension/models-download";
219
+ /** Module-level cache: one page load fetches the index at most once. */
220
+ let cached;
221
+ function normalize(raw) {
222
+ if (!Array.isArray(raw)) return [];
223
+ const entries = [];
224
+ for (const item of raw) {
225
+ if (typeof item !== "object" || item === null) continue;
226
+ const row = item;
227
+ if (typeof row["id"] !== "string" || row["id"].length === 0) continue;
228
+ const modalities = Array.isArray(row["input"]) ? row["input"].filter((m) => typeof m === "string") : [];
229
+ entries.push({
230
+ id: row["id"],
231
+ context: typeof row["context"] === "number" ? row["context"] : void 0,
232
+ output: typeof row["output"] === "number" ? row["output"] : void 0,
233
+ input: modalities,
234
+ reasoning: row["reasoning"] === true
235
+ });
236
+ }
237
+ return entries;
238
+ }
135
239
  /**
136
- * Render one provider card's action row.
137
- * @param props - the labels, commit gating, and handlers the owning card supplies.
138
- * @returns the cancel/commit row.
240
+ * The metadata index, fetched once per page load; `undefined` while the
241
+ * server has no metadata file (the caller shows the not-ready hint).
139
242
  */
140
- function EditorFooter(props) {
141
- const { t } = props;
142
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
143
- className: ModelsSection_module_default["editorActions"],
144
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
145
- type: "button",
146
- className: ModelsSection_module_default["secondaryButton"],
147
- disabled: props.busy,
148
- onClick: props.onCancel,
149
- children: t(props.cancelLabelKey ?? "cancel")
150
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
151
- type: "button",
152
- className: ModelsSection_module_default["primaryButton"],
153
- disabled: props.submitDisabled,
154
- onClick: props.onSubmit,
155
- children: props.busy ? t(props.submitBusyLabelKey) : t(props.submitLabelKey)
156
- })]
157
- });
243
+ async function getMetadataIndex() {
244
+ if (cached !== void 0) return cached;
245
+ try {
246
+ const response = await fetch(INDEX_URL);
247
+ if (!response.ok) return void 0;
248
+ cached = normalize(await response.json());
249
+ return cached;
250
+ } catch {
251
+ return;
252
+ }
253
+ }
254
+ /**
255
+ * Download/refresh the metadata file through the server half (user-triggered
256
+ * from the title-row button). A success also refreshes the local cache.
257
+ */
258
+ async function downloadMetadata() {
259
+ try {
260
+ const response = await fetch(DOWNLOAD_URL, { method: "POST" });
261
+ const body = await response.json().catch(() => void 0);
262
+ if (!response.ok || body?.ok !== true) return {
263
+ ok: false,
264
+ message: body?.message ?? `HTTP ${String(response.status)}`
265
+ };
266
+ cached = normalize(body["index"]);
267
+ return { ok: true };
268
+ } catch (error) {
269
+ return {
270
+ ok: false,
271
+ message: error instanceof Error ? error.message : String(error)
272
+ };
273
+ }
274
+ }
275
+ /**
276
+ * Case-insensitive substring match over model ids, at most {@link limit}
277
+ * candidates (plan: show ids only, never the whole list).
278
+ * @param index - the metadata index, or undefined while unavailable.
279
+ * @param query - the user's typed text.
280
+ * @param limit - maximum candidates to return.
281
+ * @returns the matching ids; empty when the index is unavailable.
282
+ */
283
+ function searchModelIds(index, query, limit = 10) {
284
+ if (index === void 0) return [];
285
+ const needle = query.trim().toLowerCase();
286
+ if (needle.length === 0) return [];
287
+ const hits = [];
288
+ for (const entry of index) {
289
+ if (!entry.id.toLowerCase().includes(needle)) continue;
290
+ hits.push(entry.id);
291
+ if (hits.length >= limit) break;
292
+ }
293
+ return hits;
158
294
  }
159
295
  //#endregion
160
- //#region
296
+ //#region src/client/models-plus/compat.ts
161
297
  /**
162
- * Curated editor for the direct DeepSeek adapter's advisory model catalog.
163
- * The settings layer replaces `models` as one array, so the parent supplies
164
- * the effective inherited rows until the first edit materializes a user
165
- * override; reset removes that override instead of copying defaults into it.
298
+ * The minimal path ops carrying `after` over `before`, both as the card sees
299
+ * them. Only keys the card observed are named; fields absent from both sides
300
+ * produce no op, which is why edits are path-addressed rather than a rebuilt
301
+ * section.
302
+ * @param base - path of the edited subtree inside the user section.
303
+ * @param before - the subtree as loaded, or undefined when it is new.
304
+ * @param after - the subtree as edited.
305
+ * @returns ordered set/unset ops; empty when nothing changed.
166
306
  */
167
- /** Row index encoded in an editing-buffer key. */
168
- function rowOf(key) {
169
- return Number(key.slice(0, key.indexOf(":")));
307
+ function pathOps(base, before, after) {
308
+ const previous = typeof before === "object" && before !== null && !Array.isArray(before) ? before : {};
309
+ const ops = [];
310
+ for (const [key, value] of Object.entries(after)) {
311
+ if (JSON.stringify(previous[key]) === JSON.stringify(value)) continue;
312
+ ops.push({
313
+ op: "set",
314
+ path: [...base, key],
315
+ value
316
+ });
317
+ }
318
+ for (const key of Object.keys(previous)) if (!(key in after)) ops.push({
319
+ op: "unset",
320
+ path: [...base, key]
321
+ });
322
+ return ops;
170
323
  }
171
324
  /** Accepted capacity spellings: a decimal count with an optional K/M suffix. */
172
325
  const CAPACITY_PATTERN = /^(\d+(?:\.\d+)?)([km])?$/i;
@@ -180,7 +333,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
180
333
  * zeroes. The stored value stays a plain token count.
181
334
  * @param text - raw field text.
182
335
  * @returns the count; `undefined` when blank (inherit), `NaN` when unreadable
183
- * (rejected by {@link validateDeepSeekModels} before any write).
336
+ * (rejected by {@link validateModelRows} before any write).
184
337
  */
185
338
  function parseCapacity(text) {
186
339
  const trimmed = text.trim();
@@ -211,12 +364,52 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
211
364
  if (!Array.isArray(value)) return [];
212
365
  return value.map((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) ? entry : {});
213
366
  }
367
+ /** Canonical key order for one models[] row (plugin-known fields first). */
368
+ const ROW_KEY_ORDER = [
369
+ "id",
370
+ "name",
371
+ "contextWindow",
372
+ "maxTokens",
373
+ "input",
374
+ "reasoningEfforts",
375
+ "compat"
376
+ ];
377
+ /** Canonical key order for the row's compat sub-object. */
378
+ const COMPAT_KEY_ORDER = [
379
+ "supportsDeveloperRole",
380
+ "supportsReasoningEffort",
381
+ "thinkingFormat"
382
+ ];
383
+ /**
384
+ * Re-key one model row into the canonical order (plugin-known fields in
385
+ * order, unknown/future fields kept at the tail so nothing is dropped).
386
+ * YAML emits keys in object insertion order, and interactive edits append
387
+ * keys at the tail — without this, rows saved after quick-load or gated-key
388
+ * removals come out with scrambled key orders (purely cosmetic, but noisy
389
+ * in settings.yaml diffs).
390
+ * @param row - the drafted row.
391
+ * @returns the re-keyed row (shallow copy).
392
+ */
393
+ function normalizeModelRow(row) {
394
+ const next = {};
395
+ for (const key of ROW_KEY_ORDER) if (key in row) next[key] = row[key];
396
+ for (const key of Object.keys(row)) if (!ROW_KEY_ORDER.includes(key)) next[key] = row[key];
397
+ const compat = next["compat"];
398
+ if (typeof compat === "object" && compat !== null && !Array.isArray(compat)) {
399
+ const source = compat;
400
+ const normalized = {};
401
+ for (const key of COMPAT_KEY_ORDER) if (key in source) normalized[key] = source[key];
402
+ for (const key of Object.keys(source)) if (!COMPAT_KEY_ORDER.includes(key)) normalized[key] = source[key];
403
+ next["compat"] = normalized;
404
+ }
405
+ return next;
406
+ }
214
407
  /**
215
408
  * Validate adapter constraints that the serialized schema cannot express.
216
409
  * @param value - user-owned `models` value, or undefined while inherited.
217
410
  * @returns the first invalid row, or undefined when the adapter will accept it.
218
411
  */
219
- function validateDeepSeekModels(value) {
412
+ function validateModelRows(value) {
220
413
  if (value === void 0) return void 0;
221
414
  const models = modelDrafts(value);
222
415
  const seen = /* @__PURE__ */ new Set();
@@ -249,273 +442,33 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
249
442
  };
250
443
  }
251
444
  }
252
- /**
253
- * Render the direct DeepSeek adapter's model catalog: id and display name on
254
- * each row, capacities behind the row's own disclosure.
255
- * @param props - effective rows plus the array-level override actions.
256
- * @returns the catalog editor.
257
- */
258
- function DeepSeekModelsEditor(props) {
259
- const [editing, setEditing] = (0, react.useState)(() => /* @__PURE__ */ new Map());
260
- const [expanded, setExpanded] = (0, react.useState)(() => /* @__PURE__ */ new Set());
261
- const update = (index, key, value) => {
262
- const next = props.models.map((model, at) => {
263
- const copy = { ...model };
264
- if (at !== index) return copy;
265
- if (value === void 0) Reflect.deleteProperty(copy, key);
266
- else copy[key] = value;
267
- return copy;
268
- });
269
- props.onChange(next);
270
- };
271
- const remove = (index) => {
272
- setEditing((current) => {
273
- const next = /* @__PURE__ */ new Map();
274
- for (const [key, text] of current) {
275
- const at = rowOf(key);
276
- if (at === index) continue;
277
- next.set(at > index ? key.replace(/^\d+/, String(at - 1)) : key, text);
278
- }
279
- return next;
280
- });
281
- setExpanded((current) => {
282
- const next = /* @__PURE__ */ new Set();
283
- for (const at of current) {
284
- if (at === index) continue;
285
- next.add(at > index ? at - 1 : at);
286
- }
287
- return next;
288
- });
289
- props.onChange(props.models.filter((_model, at) => at !== index).map((model) => ({ ...model })));
290
- };
291
- const reset = () => {
292
- setEditing(/* @__PURE__ */ new Map());
293
- setExpanded(/* @__PURE__ */ new Set());
294
- props.onReset();
445
+ //#endregion
446
+ //#region src/client/models-plus/presets.ts
447
+ /** The tri-state `compat.supportsDeveloperRole` seed: absent (reads checked). */
448
+ const DEVELOPER_ROLE_SEED = {};
449
+ /** Draft seed for one new model row. */
450
+ function newModelDraft() {
451
+ return {
452
+ id: "",
453
+ input: ["text", "image"],
454
+ reasoningEfforts: {
455
+ off: null,
456
+ minimal: "minimal",
457
+ low: "low",
458
+ medium: "medium",
459
+ high: "high",
460
+ xhigh: "xhigh",
461
+ max: "max"
462
+ },
463
+ compat: {
464
+ ...DEVELOPER_ROLE_SEED,
465
+ supportsReasoningEffort: true,
466
+ thinkingFormat: "openai"
467
+ }
295
468
  };
296
- const toggle = (index) => {
297
- setExpanded((current) => {
298
- const next = new Set(current);
299
- if (!next.delete(index)) next.add(index);
300
- return next;
301
- });
302
- };
303
- /** The field's text: its live keystrokes, else the stored count spelled short. */
304
- const capacityText = (model, index, field) => {
305
- const typed = editing.get(`${String(index)}:${field}`);
306
- if (typed !== void 0) return typed;
307
- const value = model[field];
308
- return typeof value === "number" ? formatCapacity(value) : "";
309
- };
310
- const settleCapacity = (index, field) => {
311
- const key = `${String(index)}:${field}`;
312
- const typed = editing.get(key);
313
- if (typed === void 0) return;
314
- const parsed = parseCapacity(typed);
315
- if (parsed !== void 0 && Number.isNaN(parsed)) return;
316
- setEditing((current) => {
317
- const next = new Map(current);
318
- next.delete(key);
319
- return next;
320
- });
321
- };
322
- /** One capacity field of one row, rendered inside the row's disclosure. */
323
- const capacityField = (model, index, field, fallback) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
324
- className: ModelsSection_module_default["modelField"],
325
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
326
- className: ModelsSection_module_default["modelFieldLabel"],
327
- children: props.t(field === "contextWindow" ? "contextWindow" : "maxTokens")
328
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
329
- className: ModelsSection_module_default["input"],
330
- type: "text",
331
- inputMode: "numeric",
332
- value: capacityText(model, index, field),
333
- placeholder: fallback === void 0 ? props.t(field === "contextWindow" ? "contextWindowPlaceholder" : "maxTokensPlaceholder") : formatCapacity(fallback),
334
- "aria-label": `${props.t(field === "contextWindow" ? "contextWindow" : "maxTokens")} ${String(index + 1)}`,
335
- disabled: props.disabled,
336
- onChange: (event) => {
337
- const text = event.target.value;
338
- setEditing((current) => new Map(current).set(`${String(index)}:${field}`, text));
339
- update(index, field, parseCapacity(text));
340
- },
341
- onBlur: () => {
342
- settleCapacity(index, field);
343
- }
344
- })]
345
- });
346
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
347
- className: ModelsSection_module_default["modelCatalog"],
348
- "aria-label": props.t("models"),
349
- children: [
350
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
351
- className: ModelsSection_module_default["modelListHead"],
352
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
353
- className: ModelsSection_module_default["modelCatalogHeading"],
354
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
355
- className: ModelsSection_module_default["modelCatalogTitle"],
356
- children: props.t("models")
357
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
358
- className: ModelsSection_module_default["modelCatalogMeta"],
359
- children: props.overridden ? props.t("modelsCustomized") : props.t("modelsInherited")
360
- })]
361
- }), props.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
362
- type: "button",
363
- className: ModelsSection_module_default["linkButton"],
364
- disabled: props.disabled,
365
- onClick: reset,
366
- children: props.t("resetModels")
367
- }) : null]
368
- }),
369
- props.models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
370
- className: ModelsSection_module_default["modelEmpty"],
371
- children: props.t("modelsEmpty")
372
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
373
- className: ModelsSection_module_default["modelList"],
374
- children: props.models.map((model, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
375
- className: ModelsSection_module_default["modelEntry"],
376
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
377
- className: ModelsSection_module_default["modelRow"],
378
- children: [
379
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
380
- className: ModelsSection_module_default["input"],
381
- type: "text",
382
- value: typeof model["id"] === "string" ? model["id"] : "",
383
- placeholder: props.t("modelId"),
384
- "aria-label": `${props.t("modelId")} ${String(index + 1)}`,
385
- disabled: props.disabled,
386
- onChange: (event) => {
387
- update(index, "id", event.target.value);
388
- },
389
- onBlur: (event) => {
390
- const trimmed = event.target.value.trim();
391
- if (trimmed !== event.target.value) update(index, "id", trimmed);
392
- }
393
- }),
394
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
395
- className: ModelsSection_module_default["input"],
396
- type: "text",
397
- value: typeof model["name"] === "string" ? model["name"] : "",
398
- placeholder: props.t("modelName"),
399
- "aria-label": `${props.t("modelName")} ${String(index + 1)}`,
400
- disabled: props.disabled,
401
- onChange: (event) => {
402
- update(index, "name", event.target.value === "" ? void 0 : event.target.value);
403
- }
404
- }),
405
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
406
- type: "button",
407
- className: ModelsSection_module_default["iconButton"],
408
- "aria-label": `${props.t("modelAdvanced")} ${String(index + 1)}`,
409
- "aria-expanded": expanded.has(index),
410
- title: props.t("modelAdvanced"),
411
- onClick: () => {
412
- toggle(index);
413
- },
414
- children: expanded.has(index) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {})
415
- }),
416
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
417
- type: "button",
418
- className: `${ModelsSection_module_default["iconButton"]} ${ModelsSection_module_default["iconButtonDanger"]}`,
419
- "aria-label": `${props.t("removeModel")} ${String(index + 1)}`,
420
- title: props.t("removeModel"),
421
- disabled: props.disabled,
422
- onClick: () => {
423
- remove(index);
424
- },
425
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
426
- })
427
- ]
428
- }), expanded.has(index) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
429
- className: ModelsSection_module_default["modelAdvanced"],
430
- children: [capacityField(model, index, "contextWindow", props.defaultContextWindow), capacityField(model, index, "maxTokens", props.defaultMaxTokens)]
431
- }) : null]
432
- }, index))
433
- }),
434
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
435
- type: "button",
436
- className: ModelsSection_module_default["addModelButton"],
437
- disabled: props.disabled,
438
- onClick: () => {
439
- props.onChange([...props.models.map((model) => ({ ...model })), { id: "" }]);
440
- },
441
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }), props.t("addModel")]
442
- })
443
- ]
444
- });
445
469
  }
446
470
  //#endregion
447
- //#region src/client/ModelsSection.ext.module.css
448
- var ModelsSection_ext_module_default = {
449
- "addActions": "cDGz_W_addActions",
450
- "addBlock": "cDGz_W_addBlock",
451
- "addButton": "cDGz_W_addButton",
452
- "addCard": "cDGz_W_addCard",
453
- "addModelButton": "cDGz_W_addModelButton",
454
- "advancedHint": "cDGz_W_advancedHint",
455
- "candidate": "cDGz_W_candidate",
456
- "candidateEmpty": "cDGz_W_candidateEmpty",
457
- "candidateId": "cDGz_W_candidateId",
458
- "candidateLabel": "cDGz_W_candidateLabel",
459
- "candidateList": "cDGz_W_candidateList",
460
- "candidateSearch": "cDGz_W_candidateSearch",
461
- "candidateToolbar": "cDGz_W_candidateToolbar",
462
- "credentialDot": "cDGz_W_credentialDot",
463
- "credentialDotConfigured": "cDGz_W_credentialDotConfigured",
464
- "credentialDotMissing": "cDGz_W_credentialDotMissing",
465
- "customized": "cDGz_W_customized",
466
- "customizedBody": "cDGz_W_customizedBody",
467
- "customizedSummary": "cDGz_W_customizedSummary",
468
- "dangerButton": "cDGz_W_dangerButton",
469
- "deleteConfirm": "cDGz_W_deleteConfirm",
470
- "deleteDialog": "cDGz_W_deleteDialog",
471
- "editor": "cDGz_W_editor",
472
- "editorActions": "cDGz_W_editorActions",
473
- "editorHeader": "cDGz_W_editorHeader",
474
- "editorRoute": "cDGz_W_editorRoute",
475
- "editorTitle": "cDGz_W_editorTitle",
476
- "error": "cDGz_W_error",
477
- "fetchDialog": "cDGz_W_fetchDialog",
478
- "field": "cDGz_W_field",
479
- "fieldLabel": "cDGz_W_fieldLabel",
480
- "hiddenLabel": "cDGz_W_hiddenLabel",
481
- "iconButton": "cDGz_W_iconButton",
482
- "iconButtonDanger": "cDGz_W_iconButtonDanger",
483
- "input": "cDGz_W_input",
484
- "intro": "cDGz_W_intro",
485
- "linkButton": "cDGz_W_linkButton",
486
- "modelAdvanced": "cDGz_W_modelAdvanced",
487
- "modelCatalog": "cDGz_W_modelCatalog",
488
- "modelCatalogHeading": "cDGz_W_modelCatalogHeading",
489
- "modelCatalogMeta": "cDGz_W_modelCatalogMeta",
490
- "modelCatalogTitle": "cDGz_W_modelCatalogTitle",
491
- "modelEmpty": "cDGz_W_modelEmpty",
492
- "modelEntry": "cDGz_W_modelEntry",
493
- "modelExtensionFields": "cDGz_W_modelExtensionFields",
494
- "modelField": "cDGz_W_modelField",
495
- "modelFieldLabel": "cDGz_W_modelFieldLabel",
496
- "modelList": "cDGz_W_modelList",
497
- "modelListHead": "cDGz_W_modelListHead",
498
- "modelRow": "cDGz_W_modelRow",
499
- "notice": "cDGz_W_notice",
500
- "primaryButton": "cDGz_W_primaryButton",
501
- "rowActions": "cDGz_W_rowActions",
502
- "rowCard": "cDGz_W_rowCard",
503
- "rowHead": "cDGz_W_rowHead",
504
- "rowIdentity": "cDGz_W_rowIdentity",
505
- "rowName": "cDGz_W_rowName",
506
- "rows": "cDGz_W_rows",
507
- "rowTag": "cDGz_W_rowTag",
508
- "savedNotice": "cDGz_W_savedNotice",
509
- "secondaryButton": "cDGz_W_secondaryButton",
510
- "section": "cDGz_W_section",
511
- "selectInput": "cDGz_W_selectInput",
512
- "setupCard": "cDGz_W_setupCard",
513
- "switchThumb": "cDGz_W_switchThumb",
514
- "title": "cDGz_W_title"
515
- };
516
- //#endregion
517
471
  //#region src/client/extension-meta.ts
518
- const ADAPTER_VERSION = "0.1.2-rc.1";
519
472
  /** Thinking levels offered by llm-pi-ai (THINKING_LEVEL_GATE @ 0.1.2-alpha.4). Only `off` may carry an empty wire value. */
520
473
  const THINKING_LEVELS = [
521
474
  "off",
@@ -526,8 +479,6 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
526
479
  "xhigh",
527
480
  "max"
528
481
  ];
529
- /** Input modalities accepted by llm-pi-ai (MODALITY_GATE @ 0.1.2-alpha.4). */
530
- const MODALITIES = ["text", "image"];
531
482
  /** Reasoning wire formats understood by llm-pi-ai (SUPPORTED_THINKING_FORMATS @ 0.1.2-alpha.4). */
532
483
  const THINKING_FORMATS = [
533
484
  "openai",
@@ -637,7 +588,6 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
637
588
  onboardingSave: "Save and continue",
638
589
  onboardingSaving: "Saving…",
639
590
  keyRequired: "Enter an API key to continue.",
640
- adapterVersion: "Current adapter: {version}",
641
591
  extDeclare: "Declared",
642
592
  extSection: "Model extensions",
643
593
  extSectionHint: "Extra per-model fields that live in settings.yaml.",
@@ -749,7 +699,6 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
749
699
  onboardingSave: "保存并继续",
750
700
  onboardingSaving: "保存中…",
751
701
  keyRequired: "请输入 API 密钥以继续。",
752
- adapterVersion: "当前适配器:{version}",
753
702
  extDeclare: "声明",
754
703
  extSection: "模型扩展",
755
704
  extSectionHint: "settings.yaml 中按模型生效的扩展字段。",
@@ -767,167 +716,374 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
767
716
  thinkingFormat: "思考格式"
768
717
  };
769
718
  //#endregion
770
- //#region src/client/ModelExtensionFields.tsx
771
- /** Chinese display names for the thinking levels (wire value stays in parentheses). */
772
- const LEVEL_LABELS = {
773
- off: {
774
- zh: "关闭",
775
- en: "Off"
776
- },
777
- minimal: {
778
- zh: "极小",
779
- en: "Minimal"
780
- },
781
- low: {
782
- zh: "低",
783
- en: "Low"
784
- },
785
- medium: {
786
- zh: "中",
787
- en: "Medium"
788
- },
789
- high: {
790
- zh: "高",
791
- en: "High"
792
- },
793
- xhigh: {
794
- zh: "超高",
795
- en: "Extra High"
796
- },
797
- max: {
798
- zh: "最大",
799
- en: "Max"
800
- }
801
- };
802
- /** Chinese display names for the input modalities. */
803
- const MODALITY_LABELS = {
804
- text: {
805
- zh: "文本",
806
- en: "Text"
807
- },
808
- image: {
809
- zh: "图像",
810
- en: "Image"
811
- }
812
- };
813
- /** Shared checkbox inline style (matches the host's checkbox look). */
814
- const checkboxStyle = {
815
- width: "15px",
816
- height: "15px",
817
- margin: 0,
818
- accentColor: "var(--dsw-alias-brand-primary)",
819
- flex: "none"
719
+ //#region src/client/models-plus/models-plus.module.css
720
+ var models_plus_module_default = {
721
+ "addActions": "J84-9a_addActions",
722
+ "addBlock": "J84-9a_addBlock",
723
+ "addButton": "J84-9a_addButton",
724
+ "addCard": "J84-9a_addCard",
725
+ "addModelButton": "J84-9a_addModelButton",
726
+ "advancedHint": "J84-9a_advancedHint",
727
+ "candidate": "J84-9a_candidate",
728
+ "candidateEmpty": "J84-9a_candidateEmpty",
729
+ "candidateId": "J84-9a_candidateId",
730
+ "candidateLabel": "J84-9a_candidateLabel",
731
+ "candidateList": "J84-9a_candidateList",
732
+ "candidateSearch": "J84-9a_candidateSearch",
733
+ "candidateToolbar": "J84-9a_candidateToolbar",
734
+ "credentialDot": "J84-9a_credentialDot",
735
+ "credentialDotConfigured": "J84-9a_credentialDotConfigured",
736
+ "credentialDotMissing": "J84-9a_credentialDotMissing",
737
+ "customizedSummary": "J84-9a_customizedSummary",
738
+ "dangerButton": "J84-9a_dangerButton",
739
+ "deleteConfirm": "J84-9a_deleteConfirm",
740
+ "deleteDialog": "J84-9a_deleteDialog",
741
+ "editor": "J84-9a_editor",
742
+ "editorActions": "J84-9a_editorActions",
743
+ "error": "J84-9a_error",
744
+ "extBlock": "J84-9a_extBlock",
745
+ "extCheck": "J84-9a_extCheck",
746
+ "extCheckbox": "J84-9a_extCheckbox",
747
+ "extCheckLabel": "J84-9a_extCheckLabel",
748
+ "extField": "J84-9a_extField",
749
+ "extGated": "J84-9a_extGated",
750
+ "extGroup": "J84-9a_extGroup",
751
+ "extGroupFirst": "J84-9a_extGroupFirst",
752
+ "extHint": "J84-9a_extHint",
753
+ "extLabel": "J84-9a_extLabel",
754
+ "extTable": "J84-9a_extTable",
755
+ "extTag": "J84-9a_extTag",
756
+ "extTHead": "J84-9a_extTHead",
757
+ "extTRow": "J84-9a_extTRow",
758
+ "fetchDialog": "J84-9a_fetchDialog",
759
+ "field": "J84-9a_field",
760
+ "fieldLabel": "J84-9a_fieldLabel",
761
+ "iconButton": "J84-9a_iconButton",
762
+ "iconButtonDanger": "J84-9a_iconButtonDanger",
763
+ "input": "J84-9a_input",
764
+ "intro": "J84-9a_intro",
765
+ "linkButton": "J84-9a_linkButton",
766
+ "modal": "J84-9a_modal",
767
+ "modalDesc": "J84-9a_modalDesc",
768
+ "modalFoot": "J84-9a_modalFoot",
769
+ "modalMask": "J84-9a_modalMask",
770
+ "modalTitle": "J84-9a_modalTitle",
771
+ "modelAdvanced": "J84-9a_modelAdvanced",
772
+ "modelCatalog": "J84-9a_modelCatalog",
773
+ "modelCatalogHeading": "J84-9a_modelCatalogHeading",
774
+ "modelCatalogMeta": "J84-9a_modelCatalogMeta",
775
+ "modelCatalogTitle": "J84-9a_modelCatalogTitle",
776
+ "modelEmpty": "J84-9a_modelEmpty",
777
+ "modelEntry": "J84-9a_modelEntry",
778
+ "modelField": "J84-9a_modelField",
779
+ "modelFieldLabel": "J84-9a_modelFieldLabel",
780
+ "modelList": "J84-9a_modelList",
781
+ "modelListHead": "J84-9a_modelListHead",
782
+ "modelRow": "J84-9a_modelRow",
783
+ "primaryButton": "J84-9a_primaryButton",
784
+ "quickEmpty": "J84-9a_quickEmpty",
785
+ "quickItem": "J84-9a_quickItem",
786
+ "quickList": "J84-9a_quickList",
787
+ "quickWrap": "J84-9a_quickWrap",
788
+ "rowActions": "J84-9a_rowActions",
789
+ "rowCard": "J84-9a_rowCard",
790
+ "rowHead": "J84-9a_rowHead",
791
+ "rowIdentity": "J84-9a_rowIdentity",
792
+ "rowName": "J84-9a_rowName",
793
+ "rows": "J84-9a_rows",
794
+ "rowTag": "J84-9a_rowTag",
795
+ "savedNotice": "J84-9a_savedNotice",
796
+ "secondaryButton": "J84-9a_secondaryButton",
797
+ "section": "J84-9a_section",
798
+ "selectInput": "J84-9a_selectInput",
799
+ "setupCard": "J84-9a_setupCard",
800
+ "switchRow": "J84-9a_switchRow",
801
+ "tabBar": "J84-9a_tabBar",
802
+ "tabButton": "J84-9a_tabButton",
803
+ "tabButtonActive": "J84-9a_tabButtonActive",
804
+ "tabPanel": "J84-9a_tabPanel",
805
+ "tabs": "J84-9a_tabs",
806
+ "title": "J84-9a_title",
807
+ "titleRow": "J84-9a_titleRow"
820
808
  };
809
+ //#endregion
810
+ //#region src/client/models-plus/QuickLoad.tsx
811
+ /**
812
+ * Quick-load: a searchable dropdown over the models.dev metadata index.
813
+ * Plan constraints: ids only, case-insensitive substring match, at most 10
814
+ * candidates rendered — the index is filtered on every keystroke and the
815
+ * candidate list is only mounted while open, so a 370-row index never lands
816
+ * in the DOM wholesale.
817
+ */
818
+ /**
819
+ * Render the quick-load input with its filtered candidate dropdown.
820
+ * @param props - the pick handler and disabled flag.
821
+ * @returns the quick-load field.
822
+ */
823
+ function QuickLoad(props) {
824
+ const [query, setQuery] = (0, react.useState)("");
825
+ const [open, setOpen] = (0, react.useState)(false);
826
+ const [index, setIndex] = (0, react.useState)(void 0);
827
+ const ensureIndex = () => {
828
+ if (index !== void 0) return;
829
+ getMetadataIndex().then((loaded) => {
830
+ setIndex(loaded);
831
+ });
832
+ };
833
+ const hits = searchModelIds(index, query, 10);
834
+ const pick = (id) => {
835
+ const entry = index?.find((candidate) => candidate.id === id);
836
+ setOpen(false);
837
+ setQuery("");
838
+ if (entry !== void 0) props.onPick(entry);
839
+ };
840
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
841
+ className: models_plus_module_default["quickWrap"],
842
+ children: [
843
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
844
+ className: models_plus_module_default["extLabel"],
845
+ children: "快速装入"
846
+ }),
847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
848
+ className: models_plus_module_default["input"],
849
+ type: "text",
850
+ value: query,
851
+ placeholder: "搜索模型 id(如 aper)…",
852
+ "aria-label": "快速装入",
853
+ disabled: props.disabled,
854
+ onFocus: () => {
855
+ ensureIndex();
856
+ setOpen(true);
857
+ },
858
+ onBlur: () => {
859
+ setOpen(false);
860
+ },
861
+ onChange: (event) => {
862
+ setQuery(event.target.value);
863
+ setOpen(true);
864
+ }
865
+ }),
866
+ open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
867
+ className: models_plus_module_default["quickList"],
868
+ children: index === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
869
+ className: models_plus_module_default["quickEmpty"],
870
+ children: "元数据预填不可用——点击页首「下载/更新元数据」"
871
+ }) : hits.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
872
+ className: models_plus_module_default["quickEmpty"],
873
+ children: query.trim().length === 0 ? "输入关键字搜索模型 id" : "没有匹配的模型"
874
+ }) : hits.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
875
+ type: "button",
876
+ className: models_plus_module_default["quickItem"],
877
+ onMouseDown: (event) => {
878
+ event.preventDefault();
879
+ pick(id);
880
+ },
881
+ children: id
882
+ }, id))
883
+ }) : null
884
+ ]
885
+ });
886
+ }
887
+ //#endregion
888
+ //#region src/client/models-plus/ModelEntryPanel.tsx
889
+ /**
890
+ * One model entry's expanded panel: quick-load, capacities (the official
891
+ * modelAdvanced fields, merged into this block per the v1.0.0 design), input
892
+ * modalities, the developer-role tri-state, the reasoning-effort gate and its
893
+ * gated area (thinking format, then the seven-level effort table).
894
+ *
895
+ * Every edit patches the row's draft object through `onChange` — persistence
896
+ * rides the card's existing path-ops save with no extra wire calls. Clearing
897
+ * the effort gate drops `reasoningEfforts` AND `compat.thinkingFormat` in ONE
898
+ * commit so partial updates never overwrite each other (no orphan settings).
899
+ */
821
900
  /** The row's compat sub-object as a plain record (absent → empty). */
822
901
  function compatOf(model) {
823
902
  const value = model["compat"];
824
903
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
825
904
  }
826
- /** The row's reasoningEfforts dict (absent/false/invalid → undefined = not declared). */
905
+ /** The row's reasoningEfforts dict (absent/invalid → undefined = not declared). */
827
906
  function effortsOf(model) {
828
907
  const value = model["reasoningEfforts"];
829
908
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
830
909
  return value;
831
910
  }
911
+ /** The row's input modality list (absent → empty, matching the host reader). */
912
+ function inputsOf(model) {
913
+ const value = model["input"];
914
+ return Array.isArray(value) ? value.filter((m) => typeof m === "string") : [];
915
+ }
832
916
  /**
833
- * Render the extension block for one model row.
917
+ * Render one model entry's expanded panel.
834
918
  * @param props - the row draft, its writer, and the disabled flag.
835
- * @returns the extension fields section.
919
+ * @returns the expanded panel.
836
920
  */
837
- function ModelExtensionFields(props) {
838
- const { model, onChange, disabled = false } = props;
839
- const isZh = typeof document !== "undefined" && document.documentElement.lang.startsWith("zh");
840
- const t = (key) => (isZh ? zh : en)[key];
841
- const levelLabel = (level) => isZh ? `${LEVEL_LABELS[level].zh} (${level})` : `${LEVEL_LABELS[level].en} (${level})`;
842
- const modalityLabel = (modality) => isZh ? MODALITY_LABELS[modality]?.zh ?? modality : MODALITY_LABELS[modality]?.en ?? modality;
843
- const compat = compatOf(model);
844
- const supportsReasoningEffort = compat["supportsReasoningEffort"] === true;
845
- const supportsDeveloperRole = compat["supportsDeveloperRole"] !== false;
846
- /** Write one key into `compat`, merging into the row's existing compat and
847
- * clearing the whole `compat` node when it would become empty. Used for
848
- * `supportsReasoningEffort`, `thinkingFormat` and `supportsDeveloperRole`
849
- * ONLY — those are the only model extension fields that live under `compat`. */
850
- const setCompatKey = (key, value) => {
851
- const next = { ...compat };
852
- if (value === void 0) delete next[key];
921
+ function ModelEntryPanel(props) {
922
+ const { model, onChange, disabled } = props;
923
+ const [capacityText, setCapacityText] = (0, react.useState)(() => /* @__PURE__ */ new Map());
924
+ /** Patch one key onto the row; `undefined` clears it. */
925
+ const setKey = (key, value) => {
926
+ const next = { ...model };
927
+ if (value === void 0) Reflect.deleteProperty(next, key);
853
928
  else next[key] = value;
854
- onChange({ compat: Object.keys(next).length === 0 ? void 0 : next });
929
+ onChange(next);
930
+ };
931
+ /** Patch one key inside `compat`; an emptied compat leaves no trace. */
932
+ const setCompatKey = (key, value) => {
933
+ const compat = { ...compatOf(model) };
934
+ if (value === void 0) Reflect.deleteProperty(compat, key);
935
+ else compat[key] = value;
936
+ setKey("compat", Object.keys(compat).length === 0 ? void 0 : compat);
855
937
  };
856
938
  const setEfforts = (efforts) => {
857
- onChange({ reasoningEfforts: efforts });
939
+ setKey("reasoningEfforts", efforts);
858
940
  };
859
941
  const setInput = (modalities) => {
860
- onChange({ input: modalities.length === 0 ? void 0 : modalities });
942
+ setKey("input", modalities.length === 0 ? void 0 : [...modalities]);
861
943
  };
862
- const toggleLevel = (level, checked) => {
863
- const efforts = { ...effortsOf(model) ?? {} };
864
- if (checked) efforts[level] = level === "off" ? null : level;
865
- else delete efforts[level];
866
- const next = { reasoningEfforts: Object.keys(efforts).length === 0 ? void 0 : efforts };
944
+ const applyMetadata = (entry) => {
945
+ const next = { ...model };
946
+ if (entry.context === void 0) Reflect.deleteProperty(next, "contextWindow");
947
+ else next["contextWindow"] = entry.context;
948
+ if (entry.output === void 0) Reflect.deleteProperty(next, "maxTokens");
949
+ else next["maxTokens"] = entry.output;
950
+ const compat = { ...compatOf(model) };
951
+ compat["supportsReasoningEffort"] = entry.reasoning;
952
+ if (!entry.reasoning) {
953
+ Reflect.deleteProperty(compat, "thinkingFormat");
954
+ Reflect.deleteProperty(next, "reasoningEfforts");
955
+ }
956
+ setKey("compat", Object.keys(compat).length === 0 ? void 0 : compat);
957
+ const supported = entry.input.filter((m) => m === "text" || m === "image");
958
+ if (supported.length > 0) next["input"] = supported;
867
959
  onChange(next);
960
+ setCapacityText(/* @__PURE__ */ new Map());
868
961
  };
869
- const setWireValue = (level, text) => {
870
- const efforts = { ...effortsOf(model) ?? {} };
871
- if (!(level in efforts)) return;
872
- efforts[level] = text.trim().length === 0 ? null : text.trim();
873
- setEfforts(efforts);
874
- };
875
- const toggleModality = (modality, checked) => {
876
- const current = Array.isArray(model["input"]) ? model["input"] : ["text"];
877
- setInput(checked ? [.../* @__PURE__ */ new Set([...current, modality])] : current.filter((entry) => entry !== modality));
962
+ const capacityField = (field, label) => {
963
+ const stored = model[field];
964
+ const text = capacityText.get(field) ?? (typeof stored === "number" ? formatCapacity(stored) : "");
965
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
966
+ className: models_plus_module_default["modelField"],
967
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
968
+ className: models_plus_module_default["modelFieldLabel"],
969
+ children: label
970
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
971
+ className: models_plus_module_default["input"],
972
+ type: "text",
973
+ inputMode: "numeric",
974
+ value: text,
975
+ placeholder: "使用提供方默认值",
976
+ "aria-label": label,
977
+ disabled,
978
+ onChange: (event) => {
979
+ setCapacityText((current) => new Map(current).set(field, event.target.value));
980
+ setKey(field, parseCapacity(event.target.value));
981
+ },
982
+ onBlur: () => {
983
+ const raw = capacityText.get(field);
984
+ if (raw === void 0) return;
985
+ const parsed = parseCapacity(raw);
986
+ if (parsed !== void 0 && Number.isNaN(parsed)) return;
987
+ setCapacityText((current) => {
988
+ const next = new Map(current);
989
+ next.delete(field);
990
+ return next;
991
+ });
992
+ }
993
+ })]
994
+ });
878
995
  };
996
+ const compat = compatOf(model);
997
+ const developerRole = compat["supportsDeveloperRole"] !== false;
998
+ const supportsReasoningEffort = compat["supportsReasoningEffort"] === true;
879
999
  const efforts = effortsOf(model);
1000
+ const toggleGate = (checked) => {
1001
+ if (checked) {
1002
+ setCompatKey("supportsReasoningEffort", true);
1003
+ return;
1004
+ }
1005
+ const nextCompat = { ...compat };
1006
+ Reflect.deleteProperty(nextCompat, "supportsReasoningEffort");
1007
+ Reflect.deleteProperty(nextCompat, "thinkingFormat");
1008
+ const next = {
1009
+ ...model,
1010
+ compat: Object.keys(nextCompat).length === 0 ? void 0 : nextCompat
1011
+ };
1012
+ Reflect.deleteProperty(next, "reasoningEfforts");
1013
+ onChange(next);
1014
+ };
1015
+ const toggleLevel = (level, checked) => {
1016
+ const current = { ...efforts ?? {} };
1017
+ if (checked) current[level] = level === "off" ? null : level;
1018
+ else Reflect.deleteProperty(current, level);
1019
+ setEfforts(Object.keys(current).length === 0 ? void 0 : current);
1020
+ };
1021
+ const setWireValue = (level, text) => {
1022
+ if (efforts === void 0 || !(level in efforts)) return;
1023
+ const current = { ...efforts };
1024
+ current[level] = text.trim().length === 0 ? null : text.trim();
1025
+ setEfforts(current);
1026
+ };
880
1027
  const declaredLevels = efforts === void 0 ? [] : THINKING_LEVELS.filter((level) => level in efforts);
881
- const inputList = Array.isArray(model["input"]) ? model["input"] : [];
882
- const thinkingFormat = typeof compat["thinkingFormat"] === "string" ? compat["thinkingFormat"] : void 0;
883
1028
  const hasEmptyWire = declaredLevels.some((level) => level !== "off" && String(efforts?.[level] ?? "").trim().length === 0);
884
1029
  const onlyOffDeclared = efforts !== void 0 && declaredLevels.length > 0 && !declaredLevels.some((level) => level !== "off");
885
- /** One declared level's wire-value row inside the levels table. */
886
- const wireRow = (level) => {
887
- return level === "off" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
888
- style: {
889
- fontSize: 12,
890
- color: "var(--color-text-tertiary, #999)"
891
- },
892
- children: "—"
893
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
894
- className: ModelsSection_ext_module_default["input"],
895
- type: "text",
896
- value: String(efforts?.[level] ?? ""),
897
- "aria-label": `${t("reasoningEffortWire")} ${level}`,
898
- placeholder: level,
899
- disabled,
900
- onChange: (event) => {
901
- setWireValue(level, event.target.value);
902
- }
903
- });
1030
+ const inputList = inputsOf(model);
1031
+ const toggleModality = (modality, checked) => {
1032
+ const base = inputList.length === 0 && model["input"] === void 0 ? ["text"] : [...inputList];
1033
+ setInput(checked ? [.../* @__PURE__ */ new Set([...base, modality])] : base.filter((m) => m !== modality));
904
1034
  };
905
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
906
- style: {
907
- border: "none",
908
- padding: 0,
909
- margin: 0,
910
- display: "flex",
911
- flexDirection: "column",
912
- gap: 14
913
- },
1035
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1036
+ className: models_plus_module_default["extBlock"],
914
1037
  children: [
915
1038
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
916
- style: {
917
- borderTop: "0.5px solid var(--color-border-tertiary, rgba(0,0,0,0.15))",
918
- paddingTop: 12
919
- },
920
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1039
+ className: `${models_plus_module_default["extGroup"]} ${models_plus_module_default["extGroupFirst"]}`,
1040
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuickLoad, {
1041
+ onPick: applyMetadata,
1042
+ disabled
1043
+ })
1044
+ }),
1045
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1046
+ className: models_plus_module_default["extGroup"],
1047
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1048
+ className: models_plus_module_default["modelField"],
1049
+ style: { padding: "0 4px" },
1050
+ children: [capacityField("contextWindow", "上下文窗口"), capacityField("maxTokens", "最大输出 token")]
1051
+ })
1052
+ }),
1053
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1054
+ className: models_plus_module_default["extGroup"],
1055
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1056
+ className: models_plus_module_default["extLabel"],
1057
+ style: { marginBottom: 6 },
1058
+ children: "输入模态"
1059
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
921
1060
  style: {
922
1061
  display: "flex",
923
- alignItems: "flex-start",
924
- gap: 8,
925
- cursor: "pointer"
1062
+ gap: 18
926
1063
  },
1064
+ children: ["text", "image"].map((modality) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1065
+ className: models_plus_module_default["switchRow"],
1066
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1067
+ type: "checkbox",
1068
+ className: models_plus_module_default["extCheckbox"],
1069
+ checked: inputList.includes(modality),
1070
+ disabled,
1071
+ onChange: (event) => {
1072
+ toggleModality(modality, event.target.checked);
1073
+ }
1074
+ }), modality === "text" ? "文本" : "图像"]
1075
+ }, modality))
1076
+ })]
1077
+ }),
1078
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1079
+ className: models_plus_module_default["extGroup"],
1080
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1081
+ className: models_plus_module_default["extCheck"],
927
1082
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
928
1083
  type: "checkbox",
929
- style: checkboxStyle,
930
- checked: supportsDeveloperRole,
1084
+ className: models_plus_module_default["extCheckbox"],
1085
+ style: { marginTop: 3 },
1086
+ checked: developerRole,
931
1087
  disabled,
932
1088
  onChange: (event) => {
933
1089
  setCompatKey("supportsDeveloperRole", event.target.checked ? void 0 : false);
@@ -939,356 +1095,165 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
939
1095
  gap: 2
940
1096
  },
941
1097
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
942
- className: ModelsSection_ext_module_default["modelFieldLabel"],
943
- children: t("supportsDeveloperRole")
1098
+ className: models_plus_module_default["extCheckLabel"],
1099
+ children: "允许以 developer 角色发送系统提示"
944
1100
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
945
- style: {
946
- fontSize: 12,
947
- color: "var(--color-text-tertiary, #999)"
948
- },
949
- children: t("supportsDeveloperRoleHint")
1101
+ className: models_plus_module_default["extHint"],
1102
+ children: "推理模型的系统提示将以 developer 角色发出;网关拒绝该角色时取消勾选,改用 system。"
950
1103
  })]
951
1104
  })]
952
1105
  })
953
1106
  }),
954
1107
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
955
- style: {
956
- borderTop: "0.5px solid var(--color-border-tertiary, rgba(0,0,0,0.15))",
957
- paddingTop: 12
958
- },
1108
+ className: models_plus_module_default["extGroup"],
959
1109
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
960
- style: {
961
- display: "inline-flex",
962
- alignItems: "center",
963
- gap: 8,
964
- cursor: "pointer"
965
- },
1110
+ className: models_plus_module_default["switchRow"],
966
1111
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
967
1112
  type: "checkbox",
968
- style: checkboxStyle,
1113
+ className: models_plus_module_default["extCheckbox"],
969
1114
  checked: supportsReasoningEffort,
970
1115
  disabled,
971
1116
  onChange: (event) => {
972
- if (event.target.checked) setCompatKey("supportsReasoningEffort", true);
973
- else {
974
- const nextCompat = { ...compat };
975
- delete nextCompat["supportsReasoningEffort"];
976
- delete nextCompat["thinkingFormat"];
977
- onChange({
978
- compat: Object.keys(nextCompat).length === 0 ? void 0 : nextCompat,
979
- reasoningEfforts: void 0
980
- });
981
- }
1117
+ toggleGate(event.target.checked);
982
1118
  }
983
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
984
- className: ModelsSection_ext_module_default["modelFieldLabel"],
985
- children: t("supportsReasoningEffort")
986
- })]
1119
+ }), "端点接受推理挡位参数(reasoning_effort)"]
987
1120
  })
988
1121
  }),
989
- !supportsReasoningEffort ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
990
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
991
- className: ModelsSection_ext_module_default["modelFieldLabel"],
992
- style: { marginBottom: 6 },
993
- children: t("reasoningEfforts")
994
- }),
995
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
996
- style: {
997
- border: "0.5px solid var(--color-border-tertiary, rgba(0,0,0,0.15))",
998
- borderRadius: "var(--border-radius-md, 8px)",
999
- overflow: "hidden"
1000
- },
1001
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1002
- style: {
1003
- display: "grid",
1004
- gridTemplateColumns: "56px 1fr 1fr",
1005
- fontSize: 12,
1006
- color: "var(--color-text-secondary, #666)",
1007
- background: "var(--color-background-secondary, #f5f5f5)"
1008
- },
1009
- children: [
1010
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1011
- style: { padding: "5px 10px" },
1012
- children: t("extDeclare")
1013
- }),
1014
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1015
- style: {
1016
- padding: "5px 10px",
1017
- borderLeft: "0.5px solid var(--color-border-tertiary, rgba(0,0,0,0.15))"
1018
- },
1019
- children: t("reasoningEffortLevel")
1020
- }),
1021
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1022
- style: {
1023
- padding: "5px 10px",
1024
- borderLeft: "0.5px solid var(--color-border-tertiary, rgba(0,0,0,0.15))"
1025
- },
1026
- children: t("reasoningEffortWire")
1027
- })
1028
- ]
1029
- }), THINKING_LEVELS.map((level, at) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1030
- style: {
1031
- display: "grid",
1032
- gridTemplateColumns: "56px 1fr 1fr",
1033
- alignItems: "center",
1034
- borderTop: at === 0 ? "none" : "0.5px solid var(--color-border-tertiary, rgba(0,0,0,0.15))"
1035
- },
1036
- children: [
1037
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1038
- style: {
1039
- padding: "5px 10px",
1040
- display: "flex",
1041
- justifyContent: "center"
1042
- },
1043
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1044
- type: "checkbox",
1045
- style: checkboxStyle,
1046
- checked: level in (efforts ?? {}),
1047
- disabled,
1048
- onChange: (event) => {
1049
- toggleLevel(level, event.target.checked);
1050
- }
1051
- })
1052
- }),
1053
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1054
- style: {
1055
- padding: "5px 10px",
1056
- fontSize: 13,
1057
- borderLeft: "0.5px solid var(--color-border-tertiary, rgba(0,0,0,0.15))",
1058
- opacity: level in (efforts ?? {}) ? 1 : .5
1059
- },
1060
- children: levelLabel(level)
1061
- }),
1062
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1063
- style: {
1064
- padding: "5px 10px",
1065
- borderLeft: "0.5px solid var(--color-border-tertiary, rgba(0,0,0,0.15))",
1066
- opacity: level in (efforts ?? {}) ? 1 : .4
1067
- },
1068
- children: level in (efforts ?? {}) ? wireRow(level) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1069
- style: {
1070
- fontSize: 12,
1071
- color: "var(--color-text-tertiary, #999)"
1072
- },
1073
- children: "—"
1074
- })
1075
- })
1076
- ]
1077
- }, level))]
1078
- }),
1079
- hasEmptyWire || onlyOffDeclared ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1080
- className: ModelsSection_ext_module_default["error"],
1081
- style: { marginTop: 6 },
1082
- children: hasEmptyWire ? t("reasoningEffortEmptyWire") : t("reasoningEffortsNeedNonOff")
1083
- }) : null
1084
- ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1085
- className: ModelsSection_ext_module_default["modelField"],
1086
- style: {
1087
- display: "flex",
1088
- flexDirection: "column",
1089
- gap: 4
1090
- },
1091
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1092
- className: ModelsSection_ext_module_default["modelFieldLabel"],
1093
- children: t("thinkingFormat")
1094
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1095
- className: ModelsSection_ext_module_default["input"],
1096
- value: thinkingFormat ?? "",
1097
- disabled,
1098
- onChange: (event) => {
1099
- const value = event.target.value;
1100
- setCompatKey("thinkingFormat", value === "" ? void 0 : value);
1101
- },
1102
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1103
- value: "",
1104
- children: "—"
1105
- }), THINKING_FORMATS.map((format) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1106
- value: format,
1107
- children: format
1108
- }, format))]
1109
- })]
1110
- })] }),
1111
1122
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1112
- style: {
1113
- borderTop: "0.5px solid var(--color-border-tertiary, rgba(0,0,0,0.15))",
1114
- paddingTop: 12
1115
- },
1116
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1117
- className: ModelsSection_ext_module_default["modelFieldLabel"],
1118
- style: { marginBottom: 6 },
1119
- children: t("inputModalities")
1120
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1121
- style: {
1122
- display: "flex",
1123
- flexWrap: "wrap",
1124
- gap: 16
1125
- },
1126
- children: MODALITIES.map((modality) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1127
- style: {
1128
- display: "inline-flex",
1129
- alignItems: "center",
1130
- gap: 6,
1131
- cursor: "pointer",
1132
- fontSize: 13
1123
+ className: models_plus_module_default["extGated"],
1124
+ hidden: !supportsReasoningEffort,
1125
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1126
+ className: models_plus_module_default["extField"],
1127
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1128
+ className: models_plus_module_default["extLabel"],
1129
+ children: "思考格式"
1130
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1131
+ className: `${models_plus_module_default["input"]} ${models_plus_module_default["selectInput"]}`,
1132
+ value: typeof compat["thinkingFormat"] === "string" ? compat["thinkingFormat"] : "",
1133
+ disabled,
1134
+ onChange: (event) => {
1135
+ setCompatKey("thinkingFormat", event.target.value === "" ? void 0 : event.target.value);
1133
1136
  },
1134
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1135
- type: "checkbox",
1136
- style: checkboxStyle,
1137
- checked: inputList.includes(modality),
1138
- disabled,
1139
- onChange: (event) => {
1140
- toggleModality(modality, event.target.checked);
1141
- }
1142
- }), modalityLabel(modality)]
1143
- }, modality))
1137
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1138
+ value: "",
1139
+ children: "—"
1140
+ }), THINKING_FORMATS.map((format) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1141
+ value: format,
1142
+ children: format
1143
+ }, format))]
1144
+ })]
1145
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1146
+ className: models_plus_module_default["extField"],
1147
+ children: [
1148
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1149
+ className: models_plus_module_default["extLabel"],
1150
+ children: "推理挡位"
1151
+ }),
1152
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1153
+ className: models_plus_module_default["extTable"],
1154
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1155
+ className: models_plus_module_default["extTHead"],
1156
+ children: [
1157
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: "声明" }),
1158
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: "挡位" }),
1159
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: "线上取值" })
1160
+ ]
1161
+ }), THINKING_LEVELS.map((level) => {
1162
+ const declared = efforts !== void 0 && level in efforts;
1163
+ const isOff = level === "off";
1164
+ const wire = declared ? String(efforts?.[level] ?? "") : "";
1165
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1166
+ className: models_plus_module_default["extTRow"],
1167
+ children: [
1168
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1169
+ style: {
1170
+ display: "flex",
1171
+ justifyContent: "center"
1172
+ },
1173
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1174
+ type: "checkbox",
1175
+ className: models_plus_module_default["extCheckbox"],
1176
+ checked: declared,
1177
+ disabled,
1178
+ "aria-label": `声明 ${level}`,
1179
+ onChange: (event) => {
1180
+ toggleLevel(level, event.target.checked);
1181
+ }
1182
+ })
1183
+ }),
1184
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1185
+ style: { opacity: declared ? 1 : .5 },
1186
+ children: level
1187
+ }),
1188
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1189
+ style: { opacity: declared ? 1 : .4 },
1190
+ children: declared && !isOff ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1191
+ type: "text",
1192
+ value: wire,
1193
+ placeholder: level,
1194
+ "aria-label": `${level} 线上取值`,
1195
+ disabled,
1196
+ onChange: (event) => {
1197
+ setWireValue(level, event.target.value);
1198
+ }
1199
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1200
+ style: {
1201
+ fontSize: 12,
1202
+ color: "var(--dsw-alias-label-tertiary)"
1203
+ },
1204
+ children: "—"
1205
+ })
1206
+ })
1207
+ ]
1208
+ }, level);
1209
+ })]
1210
+ }),
1211
+ hasEmptyWire || onlyOffDeclared ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1212
+ className: models_plus_module_default["error"],
1213
+ style: { marginTop: 6 },
1214
+ children: hasEmptyWire ? "除 off 外勾选的挡位必须填写线上取值。" : "至少声明一个 off 以外的挡位。"
1215
+ }) : null
1216
+ ]
1144
1217
  })]
1145
1218
  })
1146
1219
  ]
1147
1220
  });
1148
1221
  }
1149
1222
  //#endregion
1150
- //#region src/client/ModelListEditor.ext.tsx
1223
+ //#region src/client/models-plus/ModelCatalog.tsx
1151
1224
  /**
1152
- * The model list of one pi-ai provider profile (EXT fork for dsh-model-extension).
1153
- *
1154
- * Forked from deepseek-harness packages/client/ui-settings-models
1155
- * src/client/ModelListEditor.tsx @ 0.1.2-alpha.4.
1156
- * Only difference vs upstream: each row's expanded capacities area additionally
1157
- * renders <ModelExtensionFields>, which edits the same draft object in place —
1158
- * the extension fields ride the card's existing save path untouched.
1159
- *
1160
- * The list is the profile's `models` array as the card holds it: an empty list
1161
- * means "serve this route's built-in catalog", and any entry replaces that
1162
- * catalog, so a row is only ever added deliberately. Fetching asks the endpoint
1163
- * **the form currently shows** — including a key typed but not yet saved — so
1164
- * adding a provider is one pass instead of save-then-return; the reply is
1165
- * candidates the user picks from, never configuration written behind them.
1166
- *
1167
- * A provider that cannot be interrogated (an unreachable endpoint, a protocol
1168
- * with no readable listing) is not a dead end: the failure is shown next to the
1169
- * rows the user can still fill in by hand.
1225
+ * One provider profile's model catalog: rows joined from the draft's `models`
1226
+ * array, the fetch action (interrogating the endpoint the form currently
1227
+ * shows), and the candidate-picker modal. Each row's expanded panel is the
1228
+ * plugin's ModelEntryPanel — capacities and extension fields in one block.
1170
1229
  */
1171
1230
  /** A row's text field, or the empty string when unset or not a string. */
1172
1231
  function textOf(model, key) {
1173
1232
  const value = model[key];
1174
1233
  return typeof value === "string" ? value : "";
1175
1234
  }
1176
- /** A row's numeric field, or `undefined` when unset or not a number. */
1177
- function numberOf(model, key) {
1178
- const value = model[key];
1179
- return typeof value === "number" ? value : void 0;
1180
- }
1181
- /** Disclosure chevron; rotates to point down while its row is open. */
1182
- function IconChevron({ open }) {
1183
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1184
- width: "14",
1185
- height: "14",
1186
- viewBox: "0 0 16 16",
1187
- fill: "none",
1188
- "aria-hidden": true,
1189
- style: {
1190
- transform: open ? "rotate(90deg)" : void 0,
1191
- transition: "transform 120ms ease"
1192
- },
1193
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1194
- d: "M6 3.5L10.5 8L6 12.5",
1195
- stroke: "currentColor",
1196
- strokeWidth: "1.5",
1197
- strokeLinecap: "round",
1198
- strokeLinejoin: "round"
1199
- })
1200
- });
1201
- }
1202
- /** Removal glyph for one model row. */
1203
- function IconTrash() {
1204
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1205
- width: "14",
1206
- height: "14",
1207
- viewBox: "0 0 16 16",
1208
- fill: "none",
1209
- "aria-hidden": true,
1210
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1211
- d: "M2.5 4h11M6.5 4V2.5h3V4M4 4l.7 9a1 1 0 001 .9h4.6a1 1 0 001-.9L12 4M6.5 6.8v4.4M9.5 6.8v4.4",
1212
- stroke: "currentColor",
1213
- strokeWidth: "1.3",
1214
- strokeLinecap: "round",
1215
- strokeLinejoin: "round"
1216
- })
1217
- });
1218
- }
1219
- /**
1220
- * What an empty capacity field is worth, shown as its placeholder so a row left
1221
- * blank does not read as a model with no capacity at all.
1222
- *
1223
- * The magnitudes are the adapter's own route-level fallbacks (`llm-pi-ai`'s
1224
- * `defaultContextWindow` and `defaultMaxTokens`), spelled the way a person
1225
- * would say them. They are a hint, not a mirror: this page counts `K` as 1000,
1226
- * so typing `256K` stores 256000 while leaving the field blank keeps the
1227
- * adapter's 262144. A deployment that overrides those defaults is not
1228
- * reflected here — nothing on this page can read them.
1229
- */
1230
- const CAPACITY_HINT = {
1231
- contextWindow: "256K",
1232
- maxTokens: "32K"
1233
- };
1234
- /**
1235
- * Spell a stored count for a field that may be unset. The spelling itself is
1236
- * {@link formatCapacity}, shared with the DeepSeek catalog editor so both
1237
- * surfaces read and write one K/M vocabulary.
1238
- * @param value - stored capacity, or `undefined` for an unset field.
1239
- * @returns the field text, empty when unset.
1240
- */
1241
- function capacitySpelling(value) {
1242
- return value === void 0 ? "" : formatCapacity(value);
1243
- }
1244
- /** Adopt a candidate, keeping whatever capacities the provider disclosed. */
1235
+ /** Adopt one candidate, v1.0.0 defaults + the endpoint's disclosed capacities. */
1245
1236
  function adopt(candidate) {
1246
- return {
1247
- id: candidate.id,
1248
- ...candidate.name === void 0 ? {} : { name: candidate.name },
1249
- ...candidate.contextWindow === void 0 ? {} : { contextWindow: candidate.contextWindow },
1250
- ...candidate.maxTokens === void 0 ? {} : { maxTokens: candidate.maxTokens }
1251
- };
1237
+ const row = newModelDraft();
1238
+ row["id"] = candidate.id;
1239
+ row["name"] = candidate.name ?? candidate.id;
1240
+ if (candidate.contextWindow !== void 0) row["contextWindow"] = candidate.contextWindow;
1241
+ if (candidate.maxTokens !== void 0) row["maxTokens"] = candidate.maxTokens;
1242
+ return row;
1252
1243
  }
1253
1244
  /**
1254
- * Render the model list with its fetch action.
1255
- * @param props - the drafted rows, probe target, wire face, and copy.
1256
- * @returns the model-list editor.
1245
+ * Render the model catalog with its fetch action.
1246
+ * @param props - the drafted rows, probe target, wire face, and mutators.
1247
+ * @returns the catalog editor.
1257
1248
  */
1258
- function ModelListEditor(props) {
1259
- const { models, onChange, probe, operations, t, disabled } = props;
1249
+ function ModelCatalog(props) {
1250
+ const { models, onChange, probe, operations, disabled } = props;
1260
1251
  const [busy, setBusy] = (0, react.useState)(false);
1261
1252
  const [failure, setFailure] = (0, react.useState)(void 0);
1262
1253
  const [candidates, setCandidates] = (0, react.useState)(void 0);
1263
- const [picked, setPicked] = (0, react.useState)(/* @__PURE__ */ new Set());
1264
- const [candidateQuery, setCandidateQuery] = (0, react.useState)("");
1265
- const [expanded, setExpanded] = (0, react.useState)(/* @__PURE__ */ new Set());
1266
- const [editing, setEditing] = (0, react.useState)(/* @__PURE__ */ new Map());
1267
- /** Buffer key for one capacity field; the row half moves when rows do. */
1268
- const bufferKey = (index, field) => `${String(index)}:${field}`;
1269
- const editCapacity = (index, field, text) => {
1270
- setEditing((current) => new Map(current).set(bufferKey(index, field), text));
1271
- patch(index, { [field]: parseCapacity(text) });
1272
- };
1273
- /** What a capacity field shows: the buffer while typing, else the stored count. */
1274
- const capacityText = (model, index, field) => editing.get(bufferKey(index, field)) ?? capacitySpelling(numberOf(model, field));
1275
- /** Drop one row's entries and shift the rows after it down, in one pass. */
1276
- const reindexOnRemove = (current, index) => {
1277
- const next = /* @__PURE__ */ new Map();
1278
- for (const [key, value] of current) {
1279
- const at = Number(key.slice(0, key.indexOf(":")));
1280
- if (at === index) continue;
1281
- next.set(at > index ? key.replace(/^\d+/, String(at - 1)) : key, value);
1282
- }
1283
- return next;
1284
- };
1285
- const toggleExpanded = (index) => {
1286
- setExpanded((current) => {
1287
- const next = new Set(current);
1288
- if (!next.delete(index)) next.add(index);
1289
- return next;
1290
- });
1291
- };
1254
+ const [picked, setPicked] = (0, react.useState)(() => /* @__PURE__ */ new Set());
1255
+ const [query, setQuery] = (0, react.useState)("");
1256
+ const [expanded, setExpanded] = (0, react.useState)(() => /* @__PURE__ */ new Set());
1292
1257
  const patch = (index, next) => {
1293
1258
  onChange(models.map((model, at) => {
1294
1259
  if (at !== index) return model;
@@ -1299,6 +1264,22 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1299
1264
  }).filter(([key]) => !cleared.has(key)));
1300
1265
  }));
1301
1266
  };
1267
+ const toggleExpanded = (index) => {
1268
+ setExpanded((current) => {
1269
+ const next = new Set(current);
1270
+ if (!next.delete(index)) next.add(index);
1271
+ return next;
1272
+ });
1273
+ };
1274
+ const removeRow = (index) => {
1275
+ onChange(models.filter((_model, at) => at !== index));
1276
+ setExpanded((current) => {
1277
+ const next = /* @__PURE__ */ new Set();
1278
+ for (const at of current) if (at < index) next.add(at);
1279
+ else if (at > index) next.add(at - 1);
1280
+ return next;
1281
+ });
1282
+ };
1302
1283
  const fetchModels = async () => {
1303
1284
  setBusy(true);
1304
1285
  setFailure(void 0);
@@ -1315,11 +1296,11 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1315
1296
  }
1316
1297
  const found = answer.models;
1317
1298
  if (found.length === 0) {
1318
- setFailure(t("fetchEmpty"));
1299
+ setFailure("该提供方没有列出任何模型,请手动添加。");
1319
1300
  return;
1320
1301
  }
1321
1302
  const known = new Set(models.map((model) => textOf(model, "id")));
1322
- setCandidateQuery("");
1303
+ setQuery("");
1323
1304
  setCandidates(found);
1324
1305
  setPicked(new Set(found.filter((model) => !known.has(model.id)).map((model) => model.id)));
1325
1306
  } finally {
@@ -1329,10 +1310,9 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1329
1310
  const closePicker = () => {
1330
1311
  setCandidates(void 0);
1331
1312
  setPicked(/* @__PURE__ */ new Set());
1332
- setCandidateQuery("");
1313
+ setQuery("");
1333
1314
  };
1334
1315
  const adoptPicked = () => {
1335
- /* v8 ignore next -- the dialog only renders with candidates loaded */
1336
1316
  if (candidates === void 0) return;
1337
1317
  const byId = new Map(models.map((model) => [textOf(model, "id"), model]));
1338
1318
  for (const candidate of candidates) {
@@ -1350,220 +1330,219 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1350
1330
  });
1351
1331
  };
1352
1332
  const activeCandidates = candidates ?? [];
1353
- const normalizedCandidateQuery = candidateQuery.trim().toLowerCase();
1354
- const visibleCandidates = normalizedCandidateQuery.length === 0 ? activeCandidates : activeCandidates.filter((candidate) => candidate.id.toLowerCase().includes(normalizedCandidateQuery) || candidate.name?.toLowerCase().includes(normalizedCandidateQuery) === true);
1355
- const allVisibleCandidatesPicked = visibleCandidates.length > 0 && visibleCandidates.every((candidate) => picked.has(candidate.id));
1356
- const toggleVisibleCandidates = () => {
1357
- setPicked((current) => {
1358
- const next = new Set(current);
1359
- if (visibleCandidates.every((candidate) => current.has(candidate.id))) for (const candidate of visibleCandidates) next.delete(candidate.id);
1360
- else for (const candidate of visibleCandidates) next.add(candidate.id);
1361
- return next;
1362
- });
1363
- };
1333
+ const needle = query.trim().toLowerCase();
1334
+ const visible = needle.length === 0 ? activeCandidates : activeCandidates.filter((candidate) => candidate.id.toLowerCase().includes(needle));
1335
+ const allPicked = visible.length > 0 && visible.every((candidate) => picked.has(candidate.id));
1364
1336
  const askable = probe.provider !== void 0 || probe.baseURL !== void 0 && probe.baseURL.length > 0;
1365
1337
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1366
- className: ModelsSection_ext_module_default["modelCatalog"],
1367
- "aria-label": t("models"),
1338
+ className: models_plus_module_default["modelCatalog"],
1339
+ "aria-label": "模型目录",
1368
1340
  children: [
1369
1341
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1370
- className: ModelsSection_ext_module_default["modelListHead"],
1371
- children: [
1372
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1373
- className: ModelsSection_ext_module_default["modelCatalogHeading"],
1374
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1375
- className: ModelsSection_ext_module_default["modelCatalogTitle"],
1376
- children: t("models")
1377
- }), props.overridden === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1378
- className: ModelsSection_ext_module_default["modelCatalogMeta"],
1379
- children: props.overridden ? t("modelsCustomized") : t("modelsInherited")
1380
- })]
1381
- }),
1382
- props.overridden === true && props.onReset !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1342
+ className: models_plus_module_default["modelListHead"],
1343
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1344
+ className: models_plus_module_default["modelCatalogHeading"],
1345
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1346
+ className: models_plus_module_default["modelCatalogTitle"],
1347
+ children: "模型目录"
1348
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1349
+ className: models_plus_module_default["modelCatalogMeta"],
1350
+ children: props.overridden ? "已自定义模型目录" : "正在使用适配器默认模型"
1351
+ })]
1352
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1353
+ style: {
1354
+ display: "flex",
1355
+ alignItems: "center",
1356
+ gap: 4
1357
+ },
1358
+ children: [props.overridden && props.onReset !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1383
1359
  type: "button",
1384
- className: ModelsSection_ext_module_default["linkButton"],
1360
+ className: models_plus_module_default["linkButton"],
1385
1361
  disabled,
1386
1362
  onClick: props.onReset,
1387
- children: t("resetModels")
1388
- }) : null,
1389
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1363
+ children: "恢复默认模型"
1364
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1390
1365
  type: "button",
1391
- className: ModelsSection_ext_module_default["linkButton"],
1366
+ className: models_plus_module_default["linkButton"],
1392
1367
  disabled: disabled || busy || !askable || props.probeBlocked !== void 0,
1393
- title: props.probeBlocked !== void 0 ? t(props.probeBlocked) : askable ? void 0 : t("fetchNeedsBaseUrl"),
1368
+ title: props.probeBlocked !== void 0 ? props.probeBlocked : askable ? void 0 : "请先填写 API 地址,再获取。",
1394
1369
  onClick: () => {
1395
1370
  fetchModels();
1396
1371
  },
1397
- children: busy ? t("fetching") : t("fetchModels")
1398
- })
1399
- ]
1372
+ children: busy ? "正在询问提供方…" : "获取可用模型"
1373
+ })]
1374
+ })]
1400
1375
  }),
1401
1376
  models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1402
- className: ModelsSection_ext_module_default["modelEmpty"],
1403
- children: t("modelsEmpty")
1404
- }) : null,
1405
- models.map((model, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1406
- className: ModelsSection_ext_module_default["modelEntry"],
1407
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1408
- className: ModelsSection_ext_module_default["modelRow"],
1409
- children: [
1410
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1411
- className: ModelsSection_ext_module_default["input"],
1412
- type: "text",
1413
- value: textOf(model, "id"),
1414
- placeholder: t("modelId"),
1415
- "aria-label": `${t("modelId")} ${index + 1}`,
1416
- disabled,
1417
- onChange: (event) => {
1418
- patch(index, { id: event.target.value });
1419
- }
1420
- }),
1421
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1422
- className: ModelsSection_ext_module_default["input"],
1423
- type: "text",
1424
- value: textOf(model, "name"),
1425
- placeholder: t("modelName"),
1426
- "aria-label": `${t("modelName")} ${index + 1}`,
1427
- disabled,
1428
- onChange: (event) => {
1429
- patch(index, { name: event.target.value === "" ? void 0 : event.target.value });
1430
- }
1431
- }),
1432
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1433
- type: "button",
1434
- className: ModelsSection_ext_module_default["iconButton"],
1435
- "aria-label": `${t("modelAdvanced")} ${index + 1}`,
1436
- "aria-expanded": expanded.has(index),
1437
- title: t("modelAdvanced"),
1438
- onClick: () => {
1439
- toggleExpanded(index);
1440
- },
1441
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevron, { open: expanded.has(index) })
1442
- }),
1443
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1444
- type: "button",
1445
- className: `${ModelsSection_ext_module_default["iconButton"]} ${ModelsSection_ext_module_default["iconButtonDanger"]}`,
1446
- "aria-label": `${t("removeModel")} ${index + 1}`,
1447
- title: t("removeModel"),
1448
- disabled,
1449
- onClick: () => {
1450
- onChange(models.filter((_model, at) => at !== index));
1451
- setExpanded((current) => {
1452
- const next = /* @__PURE__ */ new Set();
1453
- for (const at of current) if (at < index) next.add(at);
1454
- else if (at > index) next.add(at - 1);
1455
- return next;
1456
- });
1457
- setEditing((current) => reindexOnRemove(current, index));
1458
- },
1459
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconTrash, {})
1460
- })
1461
- ]
1462
- }), expanded.has(index) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1463
- className: ModelsSection_ext_module_default["modelAdvanced"],
1464
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1465
- className: ModelsSection_ext_module_default["modelField"],
1466
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1467
- className: ModelsSection_ext_module_default["modelFieldLabel"],
1468
- children: t("modelContextWindow")
1469
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1470
- className: ModelsSection_ext_module_default["input"],
1471
- type: "text",
1472
- inputMode: "numeric",
1473
- value: capacityText(model, index, "contextWindow"),
1474
- placeholder: CAPACITY_HINT.contextWindow,
1475
- "aria-label": `${t("modelContextWindow")} ${index + 1}`,
1476
- disabled,
1477
- onChange: (event) => {
1478
- editCapacity(index, "contextWindow", event.target.value);
1479
- }
1480
- })]
1481
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1482
- className: ModelsSection_ext_module_default["modelField"],
1483
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1484
- className: ModelsSection_ext_module_default["modelFieldLabel"],
1485
- children: t("modelMaxTokens")
1486
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1487
- className: ModelsSection_ext_module_default["input"],
1488
- type: "text",
1489
- inputMode: "numeric",
1490
- value: capacityText(model, index, "maxTokens"),
1491
- placeholder: CAPACITY_HINT.maxTokens,
1492
- "aria-label": `${t("modelMaxTokens")} ${index + 1}`,
1493
- disabled,
1494
- onChange: (event) => {
1495
- editCapacity(index, "maxTokens", event.target.value);
1496
- }
1497
- })]
1498
- })]
1499
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1500
- className: ModelsSection_ext_module_default["modelExtensionFields"],
1501
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelExtensionFields, {
1377
+ className: models_plus_module_default["modelEmpty"],
1378
+ children: "模型选择器中将不显示任何模型;目录外 ID 仍可直接发送。"
1379
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1380
+ className: models_plus_module_default["modelList"],
1381
+ children: models.map((model, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1382
+ className: models_plus_module_default["modelEntry"],
1383
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1384
+ className: models_plus_module_default["modelRow"],
1385
+ children: [
1386
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1387
+ className: models_plus_module_default["input"],
1388
+ type: "text",
1389
+ value: textOf(model, "id"),
1390
+ placeholder: "模型 ID",
1391
+ "aria-label": `模型 ID ${String(index + 1)}`,
1392
+ disabled,
1393
+ onChange: (event) => {
1394
+ patch(index, { id: event.target.value });
1395
+ }
1396
+ }),
1397
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1398
+ className: models_plus_module_default["input"],
1399
+ type: "text",
1400
+ value: textOf(model, "name"),
1401
+ placeholder: "显示名称",
1402
+ "aria-label": `显示名称 ${String(index + 1)}`,
1403
+ disabled,
1404
+ onChange: (event) => {
1405
+ patch(index, { name: event.target.value === "" ? void 0 : event.target.value });
1406
+ }
1407
+ }),
1408
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1409
+ type: "button",
1410
+ className: models_plus_module_default["iconButton"],
1411
+ "aria-label": `容量 ${String(index + 1)}`,
1412
+ "aria-expanded": expanded.has(index),
1413
+ title: "容量",
1414
+ onClick: () => {
1415
+ toggleExpanded(index);
1416
+ },
1417
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1418
+ width: "14",
1419
+ height: "14",
1420
+ viewBox: "0 0 16 16",
1421
+ fill: "none",
1422
+ "aria-hidden": "true",
1423
+ style: {
1424
+ transform: expanded.has(index) ? "rotate(90deg)" : void 0,
1425
+ transition: "transform 120ms ease"
1426
+ },
1427
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1428
+ d: "M6 3.5L10.5 8L6 12.5",
1429
+ stroke: "currentColor",
1430
+ strokeWidth: "1.5",
1431
+ strokeLinecap: "round",
1432
+ strokeLinejoin: "round"
1433
+ })
1434
+ })
1435
+ }),
1436
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1437
+ type: "button",
1438
+ className: `${models_plus_module_default["iconButton"]} ${models_plus_module_default["iconButtonDanger"]}`,
1439
+ "aria-label": `删除模型 ${String(index + 1)}`,
1440
+ title: "删除模型",
1441
+ disabled,
1442
+ onClick: () => {
1443
+ removeRow(index);
1444
+ },
1445
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1446
+ width: "14",
1447
+ height: "14",
1448
+ viewBox: "0 0 16 16",
1449
+ fill: "none",
1450
+ "aria-hidden": "true",
1451
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1452
+ d: "M2.5 4h11M6.5 4V2.5h3V4M4 4l.7 9a1 1 0 001 .9h4.6a1 1 0 001-.9L12 4M6.5 6.8v4.4M9.5 6.8v4.4",
1453
+ stroke: "currentColor",
1454
+ strokeWidth: "1.3",
1455
+ strokeLinecap: "round",
1456
+ strokeLinejoin: "round"
1457
+ })
1458
+ })
1459
+ })
1460
+ ]
1461
+ }), expanded.has(index) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelEntryPanel, {
1502
1462
  model,
1463
+ disabled,
1503
1464
  onChange: (next) => {
1504
- patch(index, next);
1505
- },
1506
- disabled
1507
- })
1508
- })] }) : null]
1509
- }, index)),
1510
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1465
+ onChange(models.map((row, at) => at === index ? next : row));
1466
+ }
1467
+ }) : null]
1468
+ }, `${String(index)}:${textOf(model, "id")}`))
1469
+ }),
1470
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1511
1471
  type: "button",
1512
- className: ModelsSection_ext_module_default["addModelButton"],
1472
+ className: models_plus_module_default["addModelButton"],
1513
1473
  disabled,
1514
1474
  onClick: () => {
1515
- onChange([...models, { id: "" }]);
1475
+ onChange([...models, newModelDraft()]);
1516
1476
  },
1517
- children: t("addModel")
1477
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1478
+ width: "14",
1479
+ height: "14",
1480
+ viewBox: "0 0 16 16",
1481
+ fill: "none",
1482
+ "aria-hidden": "true",
1483
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1484
+ d: "M8 3.5v9M3.5 8h9",
1485
+ stroke: "currentColor",
1486
+ strokeWidth: "1.5",
1487
+ strokeLinecap: "round"
1488
+ })
1489
+ }), "添加模型"]
1518
1490
  }),
1519
1491
  failure !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1520
- className: ModelsSection_ext_module_default["error"],
1492
+ className: models_plus_module_default["error"],
1521
1493
  children: failure
1522
1494
  }) : null,
1523
1495
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1524
1496
  open: candidates !== void 0,
1525
1497
  onClose: closePicker,
1526
- title: t("fetchTitle"),
1527
- closeLabel: t("close"),
1528
- description: t("fetchDescription"),
1529
- className: ModelsSection_ext_module_default["fetchDialog"],
1530
- footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1531
- variant: "outline",
1498
+ title: "选择要添加的模型",
1499
+ closeLabel: "关闭",
1500
+ description: "以下是模型提供方的可用模型,勾选要添加的模型。",
1501
+ className: models_plus_module_default["fetchDialog"],
1502
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1503
+ type: "button",
1504
+ className: models_plus_module_default["secondaryButton"],
1532
1505
  onClick: closePicker,
1533
- children: t("cancel")
1534
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1535
- variant: "outline",
1506
+ children: "取消"
1507
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1508
+ type: "button",
1509
+ className: models_plus_module_default["primaryButton"],
1536
1510
  onClick: adoptPicked,
1537
- children: t("fetchAdopt")
1511
+ children: "添加所选"
1538
1512
  })] }),
1539
1513
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1540
- className: ModelsSection_ext_module_default["candidateToolbar"],
1514
+ className: models_plus_module_default["candidateToolbar"],
1541
1515
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1542
- className: `${ModelsSection_ext_module_default["input"]} ${ModelsSection_ext_module_default["candidateSearch"]}`,
1516
+ className: `${models_plus_module_default["input"]} ${models_plus_module_default["candidateSearch"]}`,
1543
1517
  type: "search",
1544
- value: candidateQuery,
1545
- placeholder: t("fetchSearch"),
1546
- "aria-label": t("fetchSearch"),
1518
+ value: query,
1519
+ placeholder: "搜索模型",
1520
+ "aria-label": "搜索模型",
1547
1521
  onChange: (event) => {
1548
- setCandidateQuery(event.target.value);
1522
+ setQuery(event.target.value);
1549
1523
  }
1550
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1551
- variant: "ghost",
1552
- size: "sm",
1553
- disabled: visibleCandidates.length === 0,
1554
- onClick: toggleVisibleCandidates,
1555
- children: t(allVisibleCandidatesPicked ? "fetchDeselectAll" : "fetchSelectAll")
1524
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1525
+ type: "button",
1526
+ className: models_plus_module_default["linkButton"],
1527
+ disabled: visible.length === 0,
1528
+ onClick: () => {
1529
+ setPicked((current) => {
1530
+ if (visible.every((candidate) => current.has(candidate.id))) return new Set([...current].filter((id) => !visible.some((candidate) => candidate.id === id)));
1531
+ return /* @__PURE__ */ new Set([...current, ...visible.map((candidate) => candidate.id)]);
1532
+ });
1533
+ },
1534
+ children: allPicked ? "取消全选" : "全选"
1556
1535
  })]
1557
- }), visibleCandidates.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1558
- className: ModelsSection_ext_module_default["candidateEmpty"],
1536
+ }), visible.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1537
+ className: models_plus_module_default["candidateEmpty"],
1559
1538
  role: "status",
1560
- children: t("fetchNoMatches")
1539
+ children: "没有匹配的模型。"
1561
1540
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1562
- className: ModelsSection_ext_module_default["candidateList"],
1563
- children: visibleCandidates.map((candidate) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
1564
- className: ModelsSection_ext_module_default["candidate"],
1541
+ className: models_plus_module_default["candidateList"],
1542
+ children: visible.map((candidate) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
1543
+ className: models_plus_module_default["candidate"],
1565
1544
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1566
- className: ModelsSection_ext_module_default["candidateLabel"],
1545
+ className: models_plus_module_default["candidateLabel"],
1567
1546
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1568
1547
  type: "checkbox",
1569
1548
  checked: picked.has(candidate.id),
@@ -1571,7 +1550,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1571
1550
  toggle(candidate.id);
1572
1551
  }
1573
1552
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1574
- className: ModelsSection_ext_module_default["candidateId"],
1553
+ className: models_plus_module_default["candidateId"],
1575
1554
  children: candidate.id
1576
1555
  })]
1577
1556
  })
@@ -1582,300 +1561,152 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1582
1561
  });
1583
1562
  }
1584
1563
  //#endregion
1585
- //#region
1586
- /**
1587
- * Any route key walks a dict schema to the same profile node, so the lookup
1588
- * names one that cannot collide with a configured route.
1589
- */
1590
- const PROBE_ROUTE = "\0probe";
1564
+ //#region src/client/models-plus/ProviderEditor.tsx
1591
1565
  /**
1592
- * Join declared configurable providers with the currently registered routes.
1593
- * @param registered - live provider routes in registration order.
1594
- * @param directory - declared configurable providers in declaration order.
1595
- * @returns declared rows followed by live routes with no declaration.
1566
+ * One existing provider's editor card, fully plugin-owned (v1.0.0 rewrite).
1567
+ * Primary field: a single write-only API key input; the collapsed
1568
+ * 「供应商」 section carries baseURL (plus displayName/protocol for a
1569
+ * hand-declared route), the 「模型」 section the model catalog with the
1570
+ * extension panel. Save semantics mirror the host card: minimal path ops
1571
+ * against the stored section with a revision fence, then the credential
1572
+ * write — the draft comes from the user layer, so a wholesale replace can
1573
+ * never delete fields outside the card.
1596
1574
  */
1597
- function joinProviderDirectory(registered, directory) {
1598
- const active = new Set(registered.map((provider) => provider.id));
1599
- const declared = new Set(directory.map((entry) => entry.provider));
1600
- const rows = directory.map((entry) => ({
1601
- provider: entry.provider,
1602
- displayName: entry.displayName,
1603
- settingsNs: entry.settingsNs,
1604
- settingsPath: [...entry.settingsPath],
1605
- active: active.has(entry.provider),
1606
- ...entry.declared === void 0 ? {} : { declared: entry.declared }
1607
- }));
1608
- for (const provider of registered) {
1609
- if (declared.has(provider.id)) continue;
1610
- rows.push({
1611
- provider: provider.id,
1612
- displayName: provider.name,
1613
- settingsNs: "",
1614
- settingsPath: [],
1615
- active: true
1616
- });
1617
- }
1618
- return rows;
1575
+ /** The public DeepSeek endpoint shown as the deepseek base-URL placeholder. */
1576
+ const DEEPSEEK_PUBLIC_BASE_URL = "https://api.deepseek.com";
1577
+ /** The editor layout the owning namespace selects. */
1578
+ function layoutOf(ns) {
1579
+ if (ns === "llm-deepseek") return "deepseek";
1580
+ if (ns === "llm-pi-ai") return "pi-ai";
1581
+ return "unknown";
1619
1582
  }
1620
- /**
1621
- * Derive the conventional credential reference for a provider route: the v1
1622
- * page never asks for an environment-variable name, so a typed key stores
1623
- * under this derived reference and the profile records it as `apiKeyEnv`.
1624
- * @param provider - provider route id (e.g. `anthropic`, `minimax-cn`).
1625
- * @returns the derived reference name (e.g. `MINIMAX_CN_API_KEY`).
1626
- */
1627
- function deriveKeyRef(provider) {
1628
- return `${provider.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_API_KEY`;
1583
+ /** A user-section subtree as a plain draft object (absent → empty). */
1584
+ function draftAt(schema, namespace, path) {
1585
+ const subtree = schema.getPath(namespace.user, path);
1586
+ if (typeof subtree !== "object" || subtree === null || Array.isArray(subtree)) return {};
1587
+ return structuredClone(subtree);
1588
+ }
1589
+ /** The credential reference this profile resolves keys through. */
1590
+ function refFor(schema, namespace, path, provider) {
1591
+ const profile = schema.getPath(namespace.value, path);
1592
+ const named = typeof profile === "object" && profile !== null ? profile.apiKeyEnv : void 0;
1593
+ return typeof named === "string" && named.length > 0 ? named : deriveKeyRef(provider);
1629
1594
  }
1630
1595
  /**
1631
- * The wire protocols a hand-declared route may name, read out of the owning
1632
- * namespace's own schema. This stays a schema read rather than a wire field so
1633
- * the choices the page offers cannot drift from the ones the adapter accepts:
1634
- * both come from the same `Config`.
1635
- * @param namespace - the namespace view whose schema declares the profile shape.
1636
- * @param schema - settings schema operations.
1637
- * @returns the protocol identifiers, or an empty list when the schema has none.
1596
+ * Render one existing provider's editing card.
1597
+ * @param props - the addressed profile plus wire faces.
1598
+ * @returns the editor card.
1638
1599
  */
1639
- function protocolChoices(namespace, schema) {
1640
- if (namespace === void 0) return [];
1641
- const list = schema.nodeAtPath(schema.rehydrate(namespace.schema), [
1642
- "providers",
1643
- PROBE_ROUTE,
1644
- "api"
1600
+ function ProviderEditor(props) {
1601
+ const { namespace, schema, settingsPath, operations } = props;
1602
+ const [draft, setDraft] = (0, react.useState)(() => draftAt(schema, namespace, settingsPath));
1603
+ const [tab, setTab] = (0, react.useState)("provider");
1604
+ const [keyDraft, setKeyDraft] = (0, react.useState)("");
1605
+ const [keyState, setKeyState] = (0, react.useState)(void 0);
1606
+ const [busy, setBusy] = (0, react.useState)(false);
1607
+ const [failure, setFailure] = (0, react.useState)(void 0);
1608
+ const [committedOriginal, setCommittedOriginal] = (0, react.useState)(() => schema.getPath(namespace.user, settingsPath));
1609
+ const [expectedRevision, setExpectedRevision] = (0, react.useState)(() => namespace.revision);
1610
+ const root = (0, react.useMemo)(() => schema.rehydrate(namespace.schema), [namespace.schema, schema]);
1611
+ const node = (0, react.useMemo)(() => schema.nodeAtPath(root, settingsPath), [
1612
+ root,
1613
+ schema,
1614
+ settingsPath
1645
1615
  ]);
1646
- if (list?.type !== "union" || list.list === void 0) return [];
1647
- return list.list.map((entry) => entry.value).filter((value) => typeof value === "string");
1648
- }
1649
- /** The credential reference a resolved profile names (its `apiKeyEnv` field). */
1650
- function apiKeyEnvOf(namespace, path, schema) {
1651
- if (namespace === void 0) return void 0;
1652
- const profile = schema.getPath(namespace.value, path);
1653
- if (typeof profile !== "object" || profile === null) return void 0;
1654
- const ref = profile.apiKeyEnv;
1655
- return typeof ref === "string" && ref.length > 0 ? ref : void 0;
1656
- }
1657
- /** The models settings page controller (one per settings surface). */
1658
- var ModelsSettingsStore = class {
1659
- ctx;
1660
- schema;
1661
- describeFace;
1662
- /** The snapshot the section renders from (uSES-safe store). */
1663
- store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)({
1664
- status: "idle",
1665
- error: null,
1666
- credentialError: null,
1667
- writable: false,
1668
- rows: [],
1669
- namespaces: /* @__PURE__ */ new Map()
1670
- });
1671
- /** Latest load wins; an older response never overwrites a newer one. */
1672
- generation = 0;
1673
- /**
1674
- * @param ctx - the page plugin's context, whose `remote.llm` and
1675
- * `remote.credentials` namespaces carry the directory and credential reads.
1676
- * @param schema - settings-owned schema and immutable path operations.
1677
- * @param describeFace - the shared mirror's describe face (namespace views and writability).
1678
- */
1679
- constructor(ctx, schema, describeFace) {
1680
- this.ctx = ctx;
1681
- this.schema = schema;
1682
- this.describeFace = describeFace;
1683
- }
1684
- /**
1685
- * Refresh the whole page snapshot: the provider directory and the mirror's
1686
- * settings answer in parallel, then one batched credential describe over
1687
- * every referenced ref. Provider failure or absence of an initial settings
1688
- * answer keeps the last good rows and surfaces an error; a failed settings
1689
- * refresh reuses the mirror's held view.
1690
- * @returns nothing; the snapshot carries the outcome.
1691
- */
1692
- async load() {
1693
- const generation = ++this.generation;
1694
- this.store.update((s) => {
1695
- s.status = "loading";
1696
- s.error = null;
1616
+ const fallback = schema.getPath(namespace.value, settingsPath);
1617
+ const disabled = props.readOnly || busy;
1618
+ const layout = layoutOf(namespace.ns);
1619
+ const keyRef = refFor(schema, namespace, settingsPath, props.provider);
1620
+ const protocols = (0, react.useMemo)(() => layout === "pi-ai" ? protocolChoices(namespace, schema) : [], [
1621
+ layout,
1622
+ namespace,
1623
+ schema
1624
+ ]);
1625
+ (0, react.useEffect)(() => {
1626
+ let stale = false;
1627
+ setKeyState(void 0);
1628
+ operations.describeCredential(keyRef).then((described) => {
1629
+ if (stale) return;
1630
+ setKeyState(described);
1697
1631
  });
1698
- const [registered, declared] = await Promise.all([
1699
- this.ctx.remote.llm.listProviders(),
1700
- this.ctx.remote.llm.listConfigurableProviders(),
1701
- this.describeFace.ensure()
1702
- ]);
1703
- if (!registered.ok) {
1704
- this.failLoad(generation, registered.error.message);
1705
- return;
1706
- }
1707
- if (!declared.ok) {
1708
- this.failLoad(generation, declared.error.message);
1709
- return;
1710
- }
1711
- const mirrored = this.describeFace.getSnapshot();
1712
- if (mirrored.view === void 0) {
1713
- this.failLoad(generation, mirrored.error ?? "settings are unavailable in this browser");
1714
- return;
1715
- }
1716
- const providers = joinProviderDirectory(registered.value, declared.value);
1717
- const writable = mirrored.view.writable;
1718
- const views = mirrored.view.namespaces;
1719
- const namespaces = new Map(views.map((view) => [view.ns, view]));
1720
- const rows = providers.map((entry) => {
1721
- const namespace = namespaces.get(entry.settingsNs);
1722
- return {
1723
- entry,
1724
- configured: namespace !== void 0 && (entry.settingsPath.length === 0 || this.schema.getPath(namespace.value, entry.settingsPath) !== void 0),
1725
- removable: namespace !== void 0 && entry.settingsPath.length > 0 && this.schema.hasPath(namespace.user, entry.settingsPath) && !this.schema.hasPath(namespace.base, entry.settingsPath),
1726
- apiKeyEnv: apiKeyEnvOf(namespace, entry.settingsPath, this.schema),
1727
- credential: void 0
1728
- };
1729
- });
1730
- const refs = [...new Set(rows.map((row) => row.apiKeyEnv ?? deriveKeyRef(row.entry.provider)))];
1731
- let credentials = {};
1732
- let credentialError = null;
1733
- if (refs.length > 0) {
1734
- const response = await this.ctx.remote.credentials.describe(refs);
1735
- if (response.ok) credentials = response.value;
1736
- else credentialError = response.error.message;
1737
- }
1738
- if (generation !== this.generation) return;
1739
- this.store.update((s) => {
1740
- s.status = "ready";
1741
- s.error = null;
1742
- s.credentialError = credentialError;
1743
- s.writable = writable;
1744
- s.rows = rows.map((row) => {
1745
- const named = row.apiKeyEnv === void 0 ? void 0 : credentials[row.apiKeyEnv];
1746
- const derived = row.apiKeyEnv !== void 0 ? void 0 : credentials[deriveKeyRef(row.entry.provider)];
1747
- return {
1748
- ...row,
1749
- ...named === void 0 ? {} : { credential: named },
1750
- ...derived === void 0 ? {} : { derivedCredential: derived }
1751
- };
1752
- });
1753
- s.namespaces = namespaces;
1754
- });
1755
- }
1756
- /** Publish one load's failure text, unless a newer load already took over. */
1757
- failLoad(generation, message) {
1758
- if (generation !== this.generation) return;
1759
- this.store.update((s) => {
1760
- s.status = "error";
1761
- s.error = message;
1762
- });
1763
- }
1764
- };
1765
- /**
1766
- * Whether a joined row can serve model requests as it stands: the route is
1767
- * registered with the adapter registry, and whatever credential its resolved
1768
- * profile names is stored. A profile naming no reference authenticates through
1769
- * the provider's own path (the Bedrock chain, Vertex ADC, a gateway that needs
1770
- * nothing), as does a live route with no settings address at all, so neither
1771
- * owes this page a key.
1772
- * @param row - one joined provider row.
1773
- * @returns whether the user already has this provider to talk to.
1774
- */
1775
- function providerUsable(row) {
1776
- if (!row.entry.active) return false;
1777
- if (row.apiKeyEnv === void 0) return true;
1778
- return row.credential?.configured === true;
1779
- }
1780
- //#endregion
1781
- //#region
1782
- /**
1783
- * The card that declares a provider pi-ai does not ship — an OpenAI-compatible
1784
- * gateway, a self-hosted server, or a provider newer than the installed
1785
- * catalog.
1786
- *
1787
- * This is a create, not an edit, which is why it is its own card rather than
1788
- * the provider editor with extra fields: the route id is being *chosen* here,
1789
- * and the settings address does not exist until it is. One `settings.mutate`
1790
- * sets the whole profile at `providers.<route>`; the key travels separately
1791
- * through `credentials/set` under the reference the profile records, exactly as
1792
- * an existing provider's key does.
1793
- *
1794
- * The three fields a hand-declared route cannot default — endpoint, protocol,
1795
- * and at least one model — are required here rather than at load, so the
1796
- * failure names the field while the user is still looking at it.
1797
- *
1798
- * There is deliberately no reasoning-effort control, here or on the editor
1799
- * card: effort is a per-MODEL capability, and the models under one provider
1800
- * disagree about it, so a provider-scoped control can only be set to a value
1801
- * some of them reject. The composer's model picker offers each model its own
1802
- * levels instead.
1803
- */
1804
- /** The settings namespace a hand-declared provider is written into. */
1805
- const NS = "llm-pi-ai";
1806
- /**
1807
- * A route id usable as a settings key AND as the stem of a credential name.
1808
- * The leading letter is the second half of that: `deriveKeyRef` uppercases the
1809
- * id and replaces every non-alphanumeric run with `_`, and a credential
1810
- * reference is a POSIX shell identifier, which cannot start with a digit. A
1811
- * digit-leading id passes every check this card makes and then fails at the
1812
- * credential seam with a raw regular expression the user cannot act on.
1813
- */
1814
- const ROUTE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
1815
- /**
1816
- * Render the custom-provider creation card.
1817
- * @param props - existing routes, protocol choices, wire faces, and copy.
1818
- * @returns the creation card.
1819
- */
1820
- function CustomProviderCard(props) {
1821
- const { taken, protocols, operations, t } = props;
1822
- const [openedAt] = (0, react.useState)(() => props.revision);
1823
- const [route, setRoute] = (0, react.useState)("");
1824
- const [displayName, setDisplayName] = (0, react.useState)("");
1825
- const [baseURL, setBaseURL] = (0, react.useState)("");
1826
- const [protocol, setProtocol] = (0, react.useState)(protocols[0] ?? "");
1827
- const [keyDraft, setKeyDraft] = (0, react.useState)("");
1828
- const [models, setModels] = (0, react.useState)([]);
1829
- const [busy, setBusy] = (0, react.useState)(false);
1830
- const [failure, setFailure] = (0, react.useState)(void 0);
1831
- /**
1832
- * The profile write landed. Only the key write can still be outstanding, so
1833
- * the fields that describe the provider are settled and the retry path is
1834
- * the credential alone.
1835
- */
1836
- const [committed, setCommitted] = (0, react.useState)(false);
1837
- const disabled = props.readOnly || busy;
1838
- /** Everything but the key stops being editable once the provider exists. */
1839
- const profileDisabled = disabled || committed;
1840
- const routeInvalid = route.length > 0 && !ROUTE_PATTERN.test(route);
1841
- const routeTaken = taken.includes(route);
1842
- const modelFailure = validateDeepSeekModels(models);
1843
- const keyFailure = apiKeyFailure(keyDraft);
1632
+ return () => {
1633
+ stale = true;
1634
+ };
1635
+ }, [operations, keyRef]);
1636
+ const stringAt = (source, key) => {
1637
+ const value = schema.getPath(source, [key]);
1638
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
1639
+ };
1640
+ const setField = (key, next) => {
1641
+ const value = next === void 0 || next.trim().length === 0 ? void 0 : next;
1642
+ setDraft((current) => value === void 0 ? schema.deletePath(current, [key]) : schema.setPath(current, [key], value));
1643
+ };
1644
+ const modelFailure = validateModelRows(schema.getPath(draft, ["models"]));
1645
+ const keyFailure = (() => {
1646
+ if (keyDraft.length === 0) return void 0;
1647
+ const value = keyDraft.trim();
1648
+ if (value.length === 0) return "keyBlank";
1649
+ if (/^[A-Z][A-Z0-9_]*=[^=]/.test(value)) return "keyIllegalCharacters";
1650
+ const first = value[0];
1651
+ if ((first === "\"" || first === "'" || first === "`") && value.length > 1 && value.endsWith(first)) return "keyIllegalCharacters";
1652
+ return /^[\x21-\x7E]+$/.test(value) ? void 0 : "keyIllegalCharacters";
1653
+ })();
1844
1654
  const keyValue = keyDraft.trim();
1845
- const ready = route.length > 0 && !routeInvalid && !routeTaken && baseURL.length > 0 && models.length > 0 && modelFailure === void 0 && keyFailure === void 0;
1846
- const hint = failure !== void 0 || ready || keyFailure !== void 0 || route.length === 0 || routeInvalid || routeTaken ? void 0 : baseURL.length === 0 ? t("customNeedsBaseUrl") : modelFailure !== void 0 ? `${t("model")} ${String(modelFailure.index + 1)}: ${t(modelFailure.key)}` : t("customNeedsModels");
1847
- /** Perform the create, returning a failure message or undefined. */
1848
- const createOnce = async () => {
1849
- const keyRef = deriveKeyRef(route);
1850
- const storesKey = keyValue.length > 0;
1851
- if (!committed) {
1852
- const profile = {
1853
- ...displayName.length === 0 ? {} : { displayName },
1854
- ...storesKey ? { apiKeyEnv: keyRef } : {},
1855
- api: protocol,
1856
- baseURL,
1857
- models: models.map((model) => ({ ...model }))
1858
- };
1859
- const written = await operations.writeSettings(NS, [{
1860
- op: "set",
1861
- path: ["providers", route],
1862
- value: profile
1863
- }], openedAt);
1864
- if (written.kind !== "written") return written.kind === "conflict" ? t("conflict") : written.message;
1865
- setCommitted(true);
1655
+ const probe = {
1656
+ settingsNs: namespace.ns,
1657
+ provider: props.provider,
1658
+ ...(() => {
1659
+ const baseURL = stringAt(draft, "baseURL") ?? stringAt(fallback, "baseURL");
1660
+ return baseURL === void 0 ? {} : { baseURL };
1661
+ })(),
1662
+ ...(() => {
1663
+ const api = layout === "pi-ai" && props.declared === true ? stringAt(draft, "api") ?? stringAt(fallback, "api") : void 0;
1664
+ return api === void 0 ? {} : { api };
1665
+ })(),
1666
+ ...keyValue.length === 0 ? {} : { apiKey: keyValue }
1667
+ };
1668
+ /** The write for this card, or a failure message. */
1669
+ const applyOnce = async () => {
1670
+ const ns = namespace.ns;
1671
+ let next = layout === "pi-ai" && stringAt(draft, "apiKeyEnv") === void 0 && stringAt(fallback, "apiKeyEnv") === void 0 && keyValue.length > 0 ? schema.setPath(draft, ["apiKeyEnv"], keyRef) : draft;
1672
+ const rowFailure = validateModelRows(schema.getPath(next, ["models"]));
1673
+ if (rowFailure !== void 0) return `模型 ${String(rowFailure.index + 1)}:${{
1674
+ modelIdRequired: "模型 ID 不能为空。",
1675
+ modelIdDuplicate: "模型 ID 不能重复。",
1676
+ modelNameInvalid: "显示名称不能为空。",
1677
+ modelContextInvalid: "上下文窗口须为正数,如 131072、256K、1M。",
1678
+ modelMaxTokensInvalid: "最大输出 token 数须为正数,如 8192、64K、1M。"
1679
+ }[rowFailure.key]}`;
1680
+ if (node !== void 0 && settingsPath.length === 0) {
1681
+ const sectionError = schema.validate(node, next);
1682
+ if (sectionError !== void 0) return sectionError;
1866
1683
  }
1867
- if (storesKey) {
1684
+ if (schema.hasPath(next, ["models"])) next = schema.setPath(next, ["models"], modelDrafts(schema.getPath(next, ["models"])).map(normalizeModelRow));
1685
+ const ops = layout === "pi-ai" && fallback === void 0 && committedOriginal === void 0 && Object.keys(next).length === 0 ? [{
1686
+ op: "set",
1687
+ path: [...settingsPath],
1688
+ value: {}
1689
+ }] : pathOps(settingsPath, committedOriginal, next);
1690
+ if (ops.length > 0) {
1691
+ const written = await operations.writeSettings(ns, ops, expectedRevision);
1692
+ if (written.kind !== "written") return written.kind === "conflict" ? "这张卡片打开期间,这些设置已被其他地方改动。请关闭后重新打开,在当前值上编辑。" : written.message;
1693
+ setCommittedOriginal(schema.getPath(written.view.user, settingsPath));
1694
+ setExpectedRevision(written.view.revision);
1695
+ setDraft(next);
1696
+ }
1697
+ if (keyValue.length > 0) {
1868
1698
  const stored = await operations.storeCredential(keyRef, keyValue);
1869
1699
  if (stored !== void 0) return stored;
1870
1700
  }
1701
+ setKeyDraft("");
1871
1702
  };
1872
- const create = async () => {
1703
+ const apply = async () => {
1873
1704
  setBusy(true);
1874
1705
  setFailure(void 0);
1875
1706
  try {
1876
- const outcome = await createOnce();
1877
- if (outcome !== void 0) {
1878
- setFailure(outcome);
1707
+ const applyFailure = await applyOnce();
1708
+ if (applyFailure !== void 0) {
1709
+ setFailure(applyFailure);
1879
1710
  return;
1880
1711
  }
1881
1712
  props.onClose(true);
@@ -1883,333 +1714,288 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1883
1714
  setBusy(false);
1884
1715
  }
1885
1716
  };
1717
+ if (node === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1718
+ className: models_plus_module_default["error"],
1719
+ children: [props.provider, ": 无法解析设置路径"]
1720
+ });
1721
+ /**
1722
+ * The catalog beneath the user layer: the composition entry's pin, or the
1723
+ * schema default — reading the effective value would echo the stored
1724
+ * override back the moment reset drops it.
1725
+ */
1726
+ const inheritedModels = () => {
1727
+ return schema.getPath(namespace.base, [...settingsPath, "models"]) ?? schema.nodeAtPath(root, [...settingsPath, "models"])?.meta.default;
1728
+ };
1729
+ const customModels = schema.getPath(draft, ["models"]);
1730
+ const modelsOverridden = schema.hasPath(draft, ["models"]);
1731
+ const models = modelDrafts(modelsOverridden ? customModels : inheritedModels());
1732
+ const defaultContextWindow = schema.getPath(fallback, ["defaultContextWindow"]);
1733
+ const defaultMaxTokens = schema.getPath(fallback, ["maxTokens"]);
1734
+ const keyPlaceholder = keyState?.writable === false ? "由启动环境提供(只读)" : keyState?.configured === true ? "已配置——输入新值可替换" : layout === "pi-ai" && props.declared === true ? "输入 API 密钥,或留空使用环境认证" : "输入 API 密钥";
1735
+ const keyLocked = keyState?.writable === false;
1886
1736
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1887
- className: ModelsSection_module_default["editor"],
1737
+ className: models_plus_module_default["editor"],
1888
1738
  children: [
1889
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1890
- className: ModelsSection_module_default["editorHeader"],
1891
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1892
- className: ModelsSection_module_default["editorTitle"],
1893
- children: t("customTitle")
1894
- })
1895
- }),
1896
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1897
- className: ModelsSection_module_default["field"],
1898
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1899
- className: ModelsSection_module_default["fieldLabel"],
1900
- children: t("customRoute")
1901
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1902
- className: ModelsSection_module_default["input"],
1903
- type: "text",
1904
- value: route,
1905
- placeholder: "acme-gateway",
1906
- "aria-label": t("customRoute"),
1907
- disabled: profileDisabled,
1908
- onChange: (event) => {
1909
- setRoute(event.target.value);
1910
- }
1911
- })]
1912
- }),
1913
- routeInvalid || routeTaken ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1914
- className: ModelsSection_module_default["error"],
1915
- children: t(routeInvalid ? "customRouteInvalid" : "customRouteTaken")
1916
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1917
- className: ModelsSection_module_default["advancedHint"],
1918
- children: t("customRouteHint")
1919
- }),
1920
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1921
- className: ModelsSection_module_default["field"],
1922
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1923
- className: ModelsSection_module_default["fieldLabel"],
1924
- children: t("customDisplayName")
1925
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1926
- className: ModelsSection_module_default["input"],
1927
- type: "text",
1928
- value: displayName,
1929
- placeholder: route.length === 0 ? t("customDisplayName") : route,
1930
- "aria-label": t("customDisplayName"),
1931
- disabled: profileDisabled,
1932
- onChange: (event) => {
1933
- setDisplayName(event.target.value);
1934
- }
1935
- })]
1936
- }),
1937
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1938
- className: ModelsSection_module_default["field"],
1939
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1940
- className: ModelsSection_module_default["fieldLabel"],
1941
- children: t("baseUrl")
1942
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1943
- className: ModelsSection_module_default["input"],
1944
- type: "text",
1945
- value: baseURL,
1946
- placeholder: t("customBaseUrlPlaceholder"),
1947
- "aria-label": t("baseUrl"),
1948
- disabled: profileDisabled,
1949
- onChange: (event) => {
1950
- setBaseURL(event.target.value);
1951
- }
1952
- })]
1953
- }),
1954
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1955
- className: ModelsSection_module_default["field"],
1956
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1957
- className: ModelsSection_module_default["fieldLabel"],
1958
- children: t("customApi")
1959
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
1960
- className: `${ModelsSection_module_default["input"]} ${ModelsSection_module_default["selectInput"]}`,
1961
- value: protocol,
1962
- "aria-label": t("customApi"),
1963
- disabled: profileDisabled,
1964
- onChange: (event) => {
1965
- setProtocol(event.target.value);
1966
- },
1967
- children: protocols.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1968
- value: choice,
1969
- children: choice
1970
- }, choice))
1971
- })]
1972
- }),
1973
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1974
- className: ModelsSection_module_default["field"],
1739
+ layout === "unknown" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1740
+ className: models_plus_module_default["advancedHint"],
1975
1741
  children: [
1976
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1977
- className: ModelsSection_module_default["fieldLabel"],
1978
- children: t("keyInput")
1742
+ "其余字段存于 settings.yaml;请直接编辑对应段。(",
1743
+ namespace.ns,
1744
+ ")"
1745
+ ]
1746
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1747
+ className: models_plus_module_default["tabs"],
1748
+ children: [
1749
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1750
+ className: models_plus_module_default["tabBar"],
1751
+ role: "tablist",
1752
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1753
+ type: "button",
1754
+ role: "tab",
1755
+ "aria-selected": tab === "provider",
1756
+ className: tab === "provider" ? `${models_plus_module_default["tabButton"]} ${models_plus_module_default["tabButtonActive"]}` : models_plus_module_default["tabButton"],
1757
+ onClick: () => {
1758
+ setTab("provider");
1759
+ },
1760
+ children: "供应商"
1761
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1762
+ type: "button",
1763
+ role: "tab",
1764
+ "aria-selected": tab === "models",
1765
+ className: tab === "models" ? `${models_plus_module_default["tabButton"]} ${models_plus_module_default["tabButtonActive"]}` : models_plus_module_default["tabButton"],
1766
+ onClick: () => {
1767
+ setTab("models");
1768
+ },
1769
+ children: "模型"
1770
+ })]
1979
1771
  }),
1980
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1981
- className: ModelsSection_module_default["input"],
1982
- type: "password",
1983
- autoComplete: "off",
1984
- value: keyDraft,
1985
- placeholder: t("keyPlaceholder"),
1986
- "aria-label": t("keyInput"),
1987
- disabled,
1988
- onChange: (event) => {
1989
- setKeyDraft(event.target.value);
1990
- }
1772
+ " ",
1773
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1774
+ className: models_plus_module_default["tabPanel"],
1775
+ role: "tabpanel",
1776
+ hidden: tab !== "provider",
1777
+ children: [
1778
+ layout === "pi-ai" && props.declared === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1779
+ className: models_plus_module_default["field"],
1780
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1781
+ className: models_plus_module_default["fieldLabel"],
1782
+ children: "显示名称"
1783
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1784
+ className: models_plus_module_default["input"],
1785
+ type: "text",
1786
+ value: stringAt(draft, "displayName") ?? "",
1787
+ placeholder: stringAt(schema.getPath(namespace.base, settingsPath), "displayName") ?? props.provider,
1788
+ "aria-label": "显示名称",
1789
+ disabled,
1790
+ onChange: (event) => {
1791
+ setField("displayName", event.target.value);
1792
+ }
1793
+ })]
1794
+ }) : null,
1795
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1796
+ className: models_plus_module_default["field"],
1797
+ children: [
1798
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1799
+ className: models_plus_module_default["fieldLabel"],
1800
+ children: "API 密钥"
1801
+ }),
1802
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1803
+ className: models_plus_module_default["input"],
1804
+ type: "password",
1805
+ autoComplete: "off",
1806
+ value: keyDraft,
1807
+ placeholder: keyPlaceholder,
1808
+ "aria-label": "API 密钥",
1809
+ disabled: disabled || keyLocked,
1810
+ onChange: (event) => {
1811
+ setKeyDraft(event.target.value);
1812
+ }
1813
+ }),
1814
+ keyFailure === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1815
+ className: models_plus_module_default["error"],
1816
+ children: keyFailure === "keyBlank" ? "请输入 API 密钥;留空则保持已存储的密钥。" : "此 API 密钥格式无效,请检查。"
1817
+ })
1818
+ ]
1819
+ }),
1820
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1821
+ className: models_plus_module_default["field"],
1822
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1823
+ className: models_plus_module_default["fieldLabel"],
1824
+ children: "API 地址"
1825
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1826
+ className: models_plus_module_default["input"],
1827
+ type: "text",
1828
+ value: stringAt(draft, "baseURL") ?? "",
1829
+ placeholder: layout === "deepseek" ? DEEPSEEK_PUBLIC_BASE_URL : stringAt(fallback, "baseURL") ?? "提供方默认",
1830
+ "aria-label": "API 地址",
1831
+ disabled,
1832
+ onChange: (event) => {
1833
+ setField("baseURL", event.target.value === "" ? void 0 : event.target.value);
1834
+ }
1835
+ })]
1836
+ }),
1837
+ layout === "pi-ai" && props.declared === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1838
+ className: models_plus_module_default["field"],
1839
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1840
+ className: models_plus_module_default["fieldLabel"],
1841
+ children: "API 协议"
1842
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1843
+ className: `${models_plus_module_default["input"]} ${models_plus_module_default["selectInput"]}`,
1844
+ value: stringAt(draft, "api") ?? stringAt(fallback, "api") ?? "",
1845
+ "aria-label": "API 协议",
1846
+ disabled,
1847
+ onChange: (event) => {
1848
+ setField("api", event.target.value);
1849
+ },
1850
+ children: [(stringAt(draft, "api") ?? stringAt(fallback, "api")) === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1851
+ value: "",
1852
+ children: "未选择"
1853
+ }) : null, protocols.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1854
+ value: choice,
1855
+ children: choice
1856
+ }, choice))]
1857
+ })]
1858
+ }) : null
1859
+ ]
1991
1860
  }),
1992
- keyFailure === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1993
- className: ModelsSection_module_default["error"],
1994
- children: t(keyFailure === "keyBlank" ? "keyBlankNew" : keyFailure)
1861
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1862
+ className: models_plus_module_default["tabPanel"],
1863
+ role: "tabpanel",
1864
+ hidden: tab !== "models",
1865
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalog, {
1866
+ models,
1867
+ overridden: modelsOverridden,
1868
+ defaultContextWindow: typeof defaultContextWindow === "number" ? defaultContextWindow : void 0,
1869
+ defaultMaxTokens: typeof defaultMaxTokens === "number" ? defaultMaxTokens : void 0,
1870
+ probe,
1871
+ probeBlocked: keyFailure,
1872
+ operations,
1873
+ disabled,
1874
+ onChange: (rows) => {
1875
+ setDraft((current) => schema.setPath(current, ["models"], rows));
1876
+ },
1877
+ onReset: () => {
1878
+ setDraft((current) => schema.deletePath(current, ["models"]));
1879
+ }
1880
+ })
1995
1881
  })
1996
1882
  ]
1997
- }),
1998
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelListEditor, {
1999
- models,
2000
- onChange: setModels,
2001
- probe: {
2002
- settingsNs: NS,
2003
- baseURL,
2004
- api: protocol,
2005
- ...keyValue.length === 0 ? {} : { apiKey: keyValue }
2006
- },
2007
- probeBlocked: keyFailure === "keyBlank" ? "keyBlankNew" : keyFailure,
2008
- operations,
2009
- t,
2010
- disabled: profileDisabled
2011
- }),
1883
+ }) }),
2012
1884
  failure !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2013
- className: ModelsSection_module_default["error"],
1885
+ className: models_plus_module_default["error"],
2014
1886
  children: failure
2015
1887
  }) : null,
2016
- hint === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2017
- className: ModelsSection_module_default["advancedHint"],
2018
- children: hint
1888
+ modelFailure === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1889
+ className: models_plus_module_default["advancedHint"],
1890
+ children: `模型 ${String(modelFailure.index + 1)}:${modelFailure.key}`
2019
1891
  }),
2020
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditorFooter, {
2021
- t,
2022
- busy,
2023
- submitDisabled: disabled || !ready,
2024
- submitLabelKey: "create",
2025
- submitBusyLabelKey: "creating",
2026
- onCancel: () => {
2027
- props.onClose(committed);
2028
- },
2029
- onSubmit: () => {
2030
- create();
2031
- }
1892
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1893
+ className: models_plus_module_default["editorActions"],
1894
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1895
+ type: "button",
1896
+ className: models_plus_module_default["secondaryButton"],
1897
+ disabled: busy,
1898
+ onClick: () => {
1899
+ props.onClose(false);
1900
+ },
1901
+ children: "取消"
1902
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1903
+ type: "button",
1904
+ className: models_plus_module_default["primaryButton"],
1905
+ disabled: disabled || layout === "unknown" || modelFailure !== void 0 || keyFailure !== void 0,
1906
+ onClick: () => {
1907
+ apply();
1908
+ },
1909
+ children: busy ? "保存中…" : "保存"
1910
+ })]
2032
1911
  })
2033
1912
  ]
2034
1913
  });
2035
1914
  }
2036
1915
  //#endregion
2037
- //#region
2038
- /**
2039
- * One provider's editor card, hand-written per adapter family: the primary
2040
- * field is a single write-only **API key** input (the page never asks for an
2041
- * environment-variable name — a typed key stores through `credentials/set`
2042
- * under the profile's reference, deriving `<ROUTE>_API_KEY` when the profile
2043
- * has none. The pi-ai profile records that derivation as `apiKeyEnv` only when
2044
- * a key is entered; a blank key materializes a reference-free profile for
2045
- * provider-native authentication);
2046
- * the collapsed 自定义设置 area carries the per-family extras (`baseURL` for
2047
- * both families, DeepSeek's id/name/context-window model catalog, and the
2048
- * display name and wire protocol of a pi-ai route the adapter does not ship —
2049
- * the two fields the create card asked that route for, editable here for the
2050
- * same reason).
2051
- * Reasoning effort is deliberately absent: it is a per-MODEL capability, and
2052
- * the models under one provider disagree about it, so a provider-scoped
2053
- * control can only be set to a value some of them reject. The composer's
2054
- * model picker offers each model its own levels; `settings.yaml` keeps the
2055
- * profile field for a deployment that knows its route. Everything else stays
2056
- * owned by `settings.yaml`. Profile edits land as minimal `settings.mutate`
2057
- * path ops against the stored section — the card names only the fields it can
2058
- * see instead of rebuilding the whole subtree from a partial descriptor.
2059
- */
2060
- /** The public DeepSeek endpoint shown as the deepseek base-URL placeholder. */
2061
- const DEEPSEEK_PUBLIC_BASE_URL = "https://api.deepseek.com";
2062
- /** A user-section subtree as a plain draft object (absent → empty). */
2063
- function draftAt(schema, namespace, path) {
2064
- const subtree = schema.getPath(namespace.user, path);
2065
- if (typeof subtree !== "object" || subtree === null || Array.isArray(subtree)) return {};
2066
- return structuredClone(subtree);
2067
- }
1916
+ //#region src/client/models-plus/CreateProviderCard.tsx
2068
1917
  /**
2069
- * The minimal path ops carrying `after` over `before`, both as the card sees
2070
- * them. Only keys the card observed are named; fields absent from both sides
2071
- * produce no op, which is why edits are path-addressed rather than a rebuilt
2072
- * section.
2073
- * @param base - path of the edited subtree inside the user section.
2074
- * @param before - the subtree as loaded, or undefined when it is new.
2075
- * @param after - the subtree as edited.
2076
- * @returns ordered set/unset ops; empty when nothing changed.
1918
+ * The card that declares a provider pi-ai does not ship — an OpenAI-compatible
1919
+ * gateway, a self-hosted server, or a provider newer than the installed
1920
+ * catalog. One `settings.mutate` sets the whole profile at `providers.<route>`;
1921
+ * the key travels separately through `credentials/set` under the reference
1922
+ * the profile records. The three fields a hand-declared route cannot default
1923
+ * (endpoint, protocol, at least one model) are required here.
2077
1924
  */
2078
- function pathOps(base, before, after) {
2079
- const previous = typeof before === "object" && before !== null && !Array.isArray(before) ? before : {};
2080
- const ops = [];
2081
- for (const [key, value] of Object.entries(after)) {
2082
- if (JSON.stringify(previous[key]) === JSON.stringify(value)) continue;
2083
- ops.push({
2084
- op: "set",
2085
- path: [...base, key],
2086
- value
2087
- });
2088
- }
2089
- for (const key of Object.keys(previous)) if (!(key in after)) ops.push({
2090
- op: "unset",
2091
- path: [...base, key]
2092
- });
2093
- return ops;
2094
- }
2095
- /** The editor layout the owning namespace selects. */
2096
- function layoutOf(ns) {
2097
- if (ns === "llm-deepseek") return "deepseek";
2098
- if (ns === "llm-pi-ai") return "pi-ai";
2099
- return "unknown";
2100
- }
2101
- /** The credential reference this profile resolves keys through. */
2102
- function refFor(schema, namespace, path, provider) {
2103
- const profile = schema.getPath(namespace.value, path);
2104
- const named = typeof profile === "object" && profile !== null ? profile.apiKeyEnv : void 0;
2105
- return typeof named === "string" && named.length > 0 ? named : deriveKeyRef(provider);
2106
- }
1925
+ /** The settings namespace a hand-declared provider is written into. */
1926
+ const NS = "llm-pi-ai";
2107
1927
  /**
2108
- * Render one provider's editing card.
2109
- * @param props - the addressed profile plus wire faces and copy.
2110
- * @returns the editor card.
1928
+ * A route id usable as a settings key AND as the stem of a credential name.
2111
1929
  */
2112
- function ProviderEditor(props) {
2113
- const { namespace, schema, settingsPath, operations, t } = props;
2114
- const [draft, setDraft] = (0, react.useState)(() => draftAt(schema, namespace, settingsPath));
1930
+ const ROUTE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
1931
+ /**
1932
+ * Render the custom-provider creation card.
1933
+ * @param props - existing routes, protocol choices, wire faces.
1934
+ * @returns the creation card.
1935
+ */
1936
+ function CreateProviderCard(props) {
1937
+ const { taken, protocols, operations } = props;
1938
+ const [openedAt] = (0, react.useState)(() => props.revision);
1939
+ const [route, setRoute] = (0, react.useState)("");
1940
+ const [displayName, setDisplayName] = (0, react.useState)("");
1941
+ const [baseURL, setBaseURL] = (0, react.useState)("");
1942
+ const [protocol, setProtocol] = (0, react.useState)(protocols[0] ?? "");
1943
+ const [tab, setTab] = (0, react.useState)("provider");
2115
1944
  const [keyDraft, setKeyDraft] = (0, react.useState)("");
2116
- const [keyState, setKeyState] = (0, react.useState)(void 0);
1945
+ const [models, setModels] = (0, react.useState)([]);
2117
1946
  const [busy, setBusy] = (0, react.useState)(false);
2118
1947
  const [failure, setFailure] = (0, react.useState)(void 0);
2119
- const [committedOriginal, setCommittedOriginal] = (0, react.useState)(() => schema.getPath(namespace.user, settingsPath));
2120
- const [expectedRevision, setExpectedRevision] = (0, react.useState)(() => namespace.revision);
2121
- const root = (0, react.useMemo)(() => schema.rehydrate(namespace.schema), [namespace.schema, schema]);
2122
- const node = (0, react.useMemo)(() => schema.nodeAtPath(root, settingsPath), [
2123
- root,
2124
- schema,
2125
- settingsPath
2126
- ]);
2127
- const fallback = schema.getPath(namespace.value, settingsPath);
1948
+ /** The profile write landed; only the key write can still be outstanding. */
1949
+ const [committed, setCommitted] = (0, react.useState)(false);
2128
1950
  const disabled = props.readOnly || busy;
2129
- const layout = layoutOf(namespace.ns);
2130
- const keyRef = refFor(schema, namespace, settingsPath, props.provider);
2131
- const protocols = (0, react.useMemo)(() => layout === "pi-ai" ? protocolChoices(namespace, schema) : [], [
2132
- layout,
2133
- namespace,
2134
- schema
2135
- ]);
2136
- (0, react.useEffect)(() => {
2137
- let stale = false;
2138
- setKeyState(void 0);
2139
- operations.describeCredential(keyRef).then((described) => {
2140
- if (stale) return;
2141
- setKeyState(described);
2142
- });
2143
- return () => {
2144
- stale = true;
2145
- };
2146
- }, [operations, keyRef]);
2147
- const stringAt = (source, key) => {
2148
- const value = schema.getPath(source, [key]);
2149
- return typeof value === "string" && value.trim().length > 0 ? value : void 0;
2150
- };
2151
- const setField = (key, next) => {
2152
- const value = next === void 0 || next.trim().length === 0 ? void 0 : next;
2153
- setDraft((current) => value === void 0 ? schema.deletePath(current, [key]) : schema.setPath(current, [key], value));
2154
- };
2155
- const modelFailure = validateDeepSeekModels(schema.getPath(draft, ["models"]));
2156
- const keyFailure = apiKeyFailure(keyDraft);
1951
+ const profileDisabled = disabled || committed;
1952
+ const routeInvalid = route.length > 0 && !ROUTE_PATTERN.test(route);
1953
+ const routeTaken = taken.includes(route);
1954
+ const modelFailure = validateModelRows(models);
1955
+ const keyFailure = (() => {
1956
+ if (keyDraft.length === 0) return void 0;
1957
+ const value = keyDraft.trim();
1958
+ if (value.length === 0) return "keyBlank";
1959
+ if (/^[A-Z][A-Z0-9_]*=[^=]/.test(value)) return "keyIllegalCharacters";
1960
+ const first = value[0];
1961
+ if ((first === "\"" || first === "'" || first === "`") && value.length > 1 && value.endsWith(first)) return "keyIllegalCharacters";
1962
+ return /^[\x21-\x7E]+$/.test(value) ? void 0 : "keyIllegalCharacters";
1963
+ })();
2157
1964
  const keyValue = keyDraft.trim();
2158
- const shownKeyFailure = (props.credentialRequired === true && keyDraft.length > 0 && keyValue.length === 0 ? "keyRequired" : void 0) ?? keyFailure;
2159
- const probeApi = stringAt(draft, "api") ?? stringAt(fallback, "api");
2160
- const probeBaseURL = stringAt(draft, "baseURL") ?? stringAt(fallback, "baseURL");
2161
- const probe = {
2162
- settingsNs: namespace.ns,
2163
- provider: props.provider,
2164
- ...probeBaseURL === void 0 ? {} : { baseURL: probeBaseURL },
2165
- ...probeApi === void 0 ? {} : { api: probeApi },
2166
- ...keyValue.length === 0 ? {} : { apiKey: keyValue }
2167
- };
2168
- /**
2169
- * The write for this card, or a failure message. Every edit travels as
2170
- * path ops against the STORED section: the draft comes from the redacted
2171
- * descriptor, so a wholesale replace rebuilt from it could delete fields
2172
- * outside the card. Ops name only the fields this card can see.
2173
- */
2174
- const applyOnce = async () => {
2175
- const ns = namespace.ns;
2176
- const next = layout === "pi-ai" && stringAt(draft, "apiKeyEnv") === void 0 && stringAt(fallback, "apiKeyEnv") === void 0 && keyValue.length > 0 ? schema.setPath(draft, ["apiKeyEnv"], keyRef) : draft;
2177
- if (props.credentialOnly !== true) {
2178
- const failure = validateDeepSeekModels(schema.getPath(next, ["models"]));
2179
- /* v8 ignore next 3 -- unreachable from the card: the same failure disables submit */
2180
- if (failure !== void 0) return `${t("model")} ${String(failure.index + 1)}: ${t(failure.key)}`;
2181
- }
2182
- /* v8 ignore next -- apply is only reachable from the rendered card, which required a resolved node */
2183
- if (props.credentialOnly !== true && node !== void 0 && settingsPath.length === 0) {
2184
- const sectionError = schema.validate(node, next);
2185
- if (sectionError !== void 0) return sectionError;
2186
- }
2187
- const materializesNativeProfile = layout === "pi-ai" && fallback === void 0 && committedOriginal === void 0 && Object.keys(next).length === 0;
2188
- const ops = props.credentialOnly === true ? [] : materializesNativeProfile ? [{
2189
- op: "set",
2190
- path: [...settingsPath],
2191
- value: {}
2192
- }] : pathOps(settingsPath, committedOriginal, next);
2193
- if (ops.length > 0) {
2194
- const written = await operations.writeSettings(ns, ops, expectedRevision);
2195
- if (written.kind !== "written") return written.kind === "conflict" ? t("conflict") : written.message;
2196
- setCommittedOriginal(schema.getPath(written.view.user, settingsPath));
2197
- setExpectedRevision(written.view.revision);
2198
- setDraft(next);
1965
+ const ready = route.length > 0 && !routeInvalid && !routeTaken && baseURL.length > 0 && models.length > 0 && modelFailure === void 0 && keyFailure === void 0;
1966
+ const hint = failure !== void 0 || ready || keyFailure !== void 0 || route.length === 0 || routeInvalid || routeTaken ? void 0 : baseURL.length === 0 ? "自定义提供方需要填写 API 地址。" : modelFailure !== void 0 ? `模型 ${String(modelFailure.index + 1)}:${modelFailure.key}` : "自定义提供方至少需要一个模型。";
1967
+ /** Perform the create, returning a failure message or undefined. */
1968
+ const createOnce = async () => {
1969
+ const keyRef = deriveKeyRef(route);
1970
+ const storesKey = keyValue.length > 0;
1971
+ if (!committed) {
1972
+ const profile = {
1973
+ ...displayName.length === 0 ? {} : { displayName },
1974
+ ...storesKey ? { apiKeyEnv: keyRef } : {},
1975
+ api: protocol,
1976
+ baseURL,
1977
+ models: models.map(normalizeModelRow)
1978
+ };
1979
+ const written = await operations.writeSettings(NS, [{
1980
+ op: "set",
1981
+ path: ["providers", route],
1982
+ value: profile
1983
+ }], openedAt);
1984
+ if (written.kind !== "written") return written.kind === "conflict" ? "这张卡片打开期间,这些设置已被其他地方改动。请关闭后重新打开,在当前值上编辑。" : written.message;
1985
+ setCommitted(true);
2199
1986
  }
2200
- if (keyValue.length > 0) {
1987
+ if (storesKey) {
2201
1988
  const stored = await operations.storeCredential(keyRef, keyValue);
2202
1989
  if (stored !== void 0) return stored;
2203
1990
  }
2204
- setKeyDraft("");
2205
1991
  };
2206
- const apply = async () => {
1992
+ const create = async () => {
2207
1993
  setBusy(true);
2208
1994
  setFailure(void 0);
2209
1995
  try {
2210
- const failure = await applyOnce();
2211
- if (failure !== void 0) {
2212
- setFailure(failure);
1996
+ const outcome = await createOnce();
1997
+ if (outcome !== void 0) {
1998
+ setFailure(outcome);
2213
1999
  return;
2214
2000
  }
2215
2001
  props.onClose(true);
@@ -2217,272 +2003,215 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2217
2003
  setBusy(false);
2218
2004
  }
2219
2005
  };
2220
- if (node === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2221
- className: ModelsSection_module_default["error"],
2006
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2007
+ className: models_plus_module_default["editor"],
2222
2008
  children: [
2223
- props.provider,
2224
- ": ",
2225
- props.t("settingsPathUnresolvable")
2226
- ]
2227
- });
2228
- const keyLocked = keyState?.writable === false;
2229
- /**
2230
- * The catalog beneath the user layer: what the composition entry pinned, or
2231
- * else the schema default that `resolve` would supply. The effective value
2232
- * cannot answer this — it still carries the stored override until the unset
2233
- * is applied, so reading it would echo that override straight back the
2234
- * moment reset drops it, leaving the rows unchanged until a reload.
2235
- */
2236
- const inheritedModels = () => {
2237
- return schema.getPath(namespace.base, [...settingsPath, "models"]) ?? schema.nodeAtPath(root, [...settingsPath, "models"])?.meta.default;
2238
- };
2239
- /**
2240
- * The curated fields of one known adapter family. The family arrives
2241
- * narrowed so the per-family branches below are total: an unknown namespace
2242
- * renders the hint instead and never reaches this body.
2243
- */
2244
- const curatedFields = (family) => {
2245
- const ownsIdentity = family === "pi-ai" && props.declared === true;
2246
- const customModels = schema.getPath(draft, ["models"]);
2247
- const modelsOverridden = schema.hasPath(draft, ["models"]);
2248
- const models = modelDrafts(modelsOverridden ? customModels : inheritedModels());
2249
- const defaultContextWindow = schema.getPath(fallback, ["defaultContextWindow"]);
2250
- const defaultMaxTokens = schema.getPath(fallback, ["maxTokens"]);
2251
- const keyPlaceholder = keyLocked ? t("keyEnvLocked") : keyState?.configured === true && props.credentialRequired !== true ? t("keyStored") : family === "pi-ai" ? t("keyPlaceholderNative") : t("keyPlaceholder");
2252
- /** What both family editors take: the rows, whose layer owns them, and the two writes. */
2253
- const catalogProps = {
2254
- models,
2255
- overridden: modelsOverridden,
2256
- t,
2257
- disabled,
2258
- onChange: (next) => {
2259
- setDraft((current) => schema.setPath(current, ["models"], next));
2260
- },
2261
- onReset: () => {
2262
- setDraft((current) => schema.deletePath(current, ["models"]));
2263
- }
2264
- };
2265
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2266
- className: ModelsSection_module_default["field"],
2267
- children: [
2268
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2269
- className: ModelsSection_module_default["fieldLabel"],
2270
- children: t("keyInput")
2271
- }),
2272
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2273
- className: ModelsSection_module_default["input"],
2274
- type: "password",
2275
- autoComplete: "off",
2276
- value: keyDraft,
2277
- placeholder: keyPlaceholder,
2278
- "aria-label": t("keyInput"),
2279
- "aria-invalid": shownKeyFailure !== void 0,
2280
- required: props.credentialRequired === true,
2281
- autoFocus: props.autoFocusCredential === true,
2282
- disabled: disabled || keyLocked,
2283
- onChange: (event) => {
2284
- setKeyDraft(event.target.value);
2285
- }
2286
- }),
2287
- shownKeyFailure === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2288
- className: ModelsSection_module_default["error"],
2289
- children: t(shownKeyFailure)
2290
- })
2291
- ]
2292
- }), props.credentialOnly === true ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
2293
- className: ModelsSection_module_default["customized"],
2294
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
2295
- className: ModelsSection_module_default["customizedSummary"],
2296
- children: t("customized")
2297
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2298
- className: ModelsSection_module_default["customizedBody"],
2009
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2010
+ className: models_plus_module_default["field"],
2011
+ style: {
2012
+ paddingTop: 12,
2013
+ borderTop: "0.5px solid var(--dsw-alias-border-l2)"
2014
+ },
2015
+ children: [
2016
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2017
+ className: models_plus_module_default["fieldLabel"],
2018
+ children: "Provider ID"
2019
+ }),
2020
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2021
+ className: models_plus_module_default["input"],
2022
+ type: "text",
2023
+ value: route,
2024
+ placeholder: "acme-gateway",
2025
+ "aria-label": "Provider ID",
2026
+ disabled: profileDisabled,
2027
+ onChange: (event) => {
2028
+ setRoute(event.target.value);
2029
+ }
2030
+ }),
2031
+ routeInvalid || routeTaken ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2032
+ className: models_plus_module_default["error"],
2033
+ children: routeInvalid ? "需以小写字母开头,之后可用小写字母、数字和短横线。" : "已有提供方使用了这个 ID。"
2034
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2035
+ className: models_plus_module_default["advancedHint"],
2036
+ children: "以小写字母开头的标识,在请求中唯一标识该提供方,并用于派生凭据名。"
2037
+ })
2038
+ ]
2039
+ }),
2040
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2041
+ className: models_plus_module_default["tabs"],
2299
2042
  children: [
2300
- ownsIdentity ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2301
- className: ModelsSection_module_default["field"],
2302
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2303
- className: ModelsSection_module_default["fieldLabel"],
2304
- children: t("customDisplayName")
2305
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2306
- className: ModelsSection_module_default["input"],
2307
- type: "text",
2308
- value: stringAt(draft, "displayName") ?? "",
2309
- placeholder: stringAt(schema.getPath(namespace.base, settingsPath), "displayName") ?? props.provider,
2310
- "aria-label": t("customDisplayName"),
2311
- disabled,
2312
- onChange: (event) => {
2313
- setField("displayName", event.target.value);
2314
- }
2315
- })]
2316
- }) : null,
2317
2043
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2318
- className: ModelsSection_module_default["field"],
2319
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2320
- className: ModelsSection_module_default["fieldLabel"],
2321
- children: t("baseUrl")
2322
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2323
- className: ModelsSection_module_default["input"],
2324
- type: "text",
2325
- value: stringAt(draft, "baseURL") ?? "",
2326
- placeholder: family === "deepseek" ? DEEPSEEK_PUBLIC_BASE_URL : stringAt(fallback, "baseURL") ?? t("baseUrlDefault"),
2327
- "aria-label": t("baseUrl"),
2328
- disabled,
2329
- onChange: (event) => {
2330
- setField("baseURL", event.target.value === "" ? void 0 : event.target.value);
2331
- }
2044
+ className: models_plus_module_default["tabBar"],
2045
+ role: "tablist",
2046
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2047
+ type: "button",
2048
+ role: "tab",
2049
+ "aria-selected": tab === "provider",
2050
+ className: tab === "provider" ? `${models_plus_module_default["tabButton"]} ${models_plus_module_default["tabButtonActive"]}` : models_plus_module_default["tabButton"],
2051
+ onClick: () => {
2052
+ setTab("provider");
2053
+ },
2054
+ children: "供应商"
2055
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2056
+ type: "button",
2057
+ role: "tab",
2058
+ "aria-selected": tab === "models",
2059
+ className: tab === "models" ? `${models_plus_module_default["tabButton"]} ${models_plus_module_default["tabButtonActive"]}` : models_plus_module_default["tabButton"],
2060
+ onClick: () => {
2061
+ setTab("models");
2062
+ },
2063
+ children: "模型"
2332
2064
  })]
2333
2065
  }),
2334
- ownsIdentity ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2335
- className: ModelsSection_module_default["field"],
2336
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2337
- className: ModelsSection_module_default["fieldLabel"],
2338
- children: t("customApi")
2339
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2340
- className: `${ModelsSection_module_default["input"]} ${ModelsSection_module_default["selectInput"]}`,
2341
- value: probeApi ?? "",
2342
- "aria-label": t("customApi"),
2343
- disabled,
2344
- onChange: (event) => {
2345
- setField("api", event.target.value);
2066
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2067
+ className: models_plus_module_default["tabPanel"],
2068
+ role: "tabpanel",
2069
+ hidden: tab !== "provider",
2070
+ children: [
2071
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2072
+ className: models_plus_module_default["field"],
2073
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2074
+ className: models_plus_module_default["fieldLabel"],
2075
+ children: "显示名称"
2076
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2077
+ className: models_plus_module_default["input"],
2078
+ type: "text",
2079
+ value: displayName,
2080
+ placeholder: route.length === 0 ? "显示名称" : route,
2081
+ "aria-label": "显示名称",
2082
+ disabled: profileDisabled,
2083
+ onChange: (event) => {
2084
+ setDisplayName(event.target.value);
2085
+ }
2086
+ })]
2087
+ }),
2088
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2089
+ className: models_plus_module_default["field"],
2090
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2091
+ className: models_plus_module_default["fieldLabel"],
2092
+ children: "API 地址"
2093
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2094
+ className: models_plus_module_default["input"],
2095
+ type: "text",
2096
+ value: baseURL,
2097
+ placeholder: "https://gateway.example/v1",
2098
+ "aria-label": "API 地址",
2099
+ disabled: profileDisabled,
2100
+ onChange: (event) => {
2101
+ setBaseURL(event.target.value);
2102
+ }
2103
+ })]
2104
+ }),
2105
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2106
+ className: models_plus_module_default["field"],
2107
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2108
+ className: models_plus_module_default["fieldLabel"],
2109
+ children: "API 协议"
2110
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
2111
+ className: `${models_plus_module_default["input"]} ${models_plus_module_default["selectInput"]}`,
2112
+ value: protocol,
2113
+ "aria-label": "API 协议",
2114
+ disabled: profileDisabled,
2115
+ onChange: (event) => {
2116
+ setProtocol(event.target.value);
2117
+ },
2118
+ children: protocols.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2119
+ value: choice,
2120
+ children: choice
2121
+ }, choice))
2122
+ })]
2123
+ }),
2124
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2125
+ className: models_plus_module_default["field"],
2126
+ children: [
2127
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2128
+ className: models_plus_module_default["fieldLabel"],
2129
+ children: "API 密钥"
2130
+ }),
2131
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2132
+ className: models_plus_module_default["input"],
2133
+ type: "password",
2134
+ autoComplete: "off",
2135
+ value: keyDraft,
2136
+ placeholder: "输入 API 密钥,或留空使用环境认证",
2137
+ "aria-label": "API 密钥",
2138
+ disabled,
2139
+ onChange: (event) => {
2140
+ setKeyDraft(event.target.value);
2141
+ }
2142
+ }),
2143
+ keyFailure === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2144
+ className: models_plus_module_default["error"],
2145
+ children: keyFailure === "keyBlank" ? "请输入 API 密钥;若该提供方以其他方式鉴权,可以留空。" : "此 API 密钥格式无效,请检查。"
2146
+ })
2147
+ ]
2148
+ })
2149
+ ]
2150
+ }),
2151
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2152
+ className: models_plus_module_default["tabPanel"],
2153
+ role: "tabpanel",
2154
+ hidden: tab !== "models",
2155
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalog, {
2156
+ models,
2157
+ overridden: true,
2158
+ defaultContextWindow: void 0,
2159
+ defaultMaxTokens: void 0,
2160
+ probe: {
2161
+ settingsNs: NS,
2162
+ baseURL,
2163
+ api: protocol,
2164
+ ...keyValue.length === 0 ? {} : { apiKey: keyValue }
2346
2165
  },
2347
- children: [probeApi === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2348
- value: "",
2349
- children: t("customApiUnset")
2350
- }) : null, protocols.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2351
- value: choice,
2352
- children: choice
2353
- }, choice))]
2354
- })]
2355
- }) : null,
2356
- family === "deepseek" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeepSeekModelsEditor, {
2357
- ...catalogProps,
2358
- defaultContextWindow: typeof defaultContextWindow === "number" ? defaultContextWindow : void 0,
2359
- defaultMaxTokens: typeof defaultMaxTokens === "number" ? defaultMaxTokens : void 0
2360
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelListEditor, {
2361
- ...catalogProps,
2362
- probe,
2363
- probeBlocked: keyFailure,
2364
- operations
2166
+ probeBlocked: keyFailure === "keyBlank" ? "请输入 API 密钥;若该提供方以其他方式鉴权,可以留空。" : keyFailure,
2167
+ operations,
2168
+ disabled: profileDisabled,
2169
+ onChange: setModels
2170
+ })
2365
2171
  })
2366
2172
  ]
2367
- })]
2368
- })] });
2369
- };
2370
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2371
- className: props.credentialOnly === true ? ModelsSection_module_default["addBlock"] : ModelsSection_module_default["editor"],
2372
- children: [
2373
- props.hideTitle === true ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2374
- className: ModelsSection_module_default["editorHeader"],
2375
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2376
- className: ModelsSection_module_default["editorTitle"],
2377
- children: props.displayName
2378
- }), props.provider !== props.displayName ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2379
- className: ModelsSection_module_default["editorRoute"],
2380
- children: props.provider
2381
- }) : null]
2382
2173
  }),
2383
- layout === "unknown" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2384
- className: ModelsSection_module_default["advancedHint"],
2385
- children: `${t("advancedHint")} (${namespace.ns})`
2386
- }) : curatedFields(layout),
2387
2174
  failure !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2388
- className: ModelsSection_module_default["error"],
2175
+ className: models_plus_module_default["error"],
2389
2176
  children: failure
2390
2177
  }) : null,
2391
- props.credentialOnly === true || modelFailure === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2392
- className: ModelsSection_module_default["advancedHint"],
2393
- children: `${t("model")} ${String(modelFailure.index + 1)}: ${t(modelFailure.key)}`
2178
+ hint === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2179
+ className: models_plus_module_default["advancedHint"],
2180
+ children: hint
2394
2181
  }),
2395
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditorFooter, {
2396
- t,
2397
- busy,
2398
- submitDisabled: disabled || layout === "unknown" || props.credentialOnly !== true && modelFailure !== void 0 || shownKeyFailure !== void 0 || props.credentialRequired === true && keyValue.length === 0,
2399
- submitLabelKey: props.submitLabelKey ?? "apply",
2400
- submitBusyLabelKey: props.submitBusyLabelKey ?? "applying",
2401
- ...props.cancelLabelKey === void 0 ? {} : { cancelLabelKey: props.cancelLabelKey },
2402
- onCancel: () => {
2403
- props.onClose(false);
2404
- },
2405
- onSubmit: () => {
2406
- apply();
2407
- }
2182
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2183
+ className: models_plus_module_default["editorActions"],
2184
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2185
+ type: "button",
2186
+ className: models_plus_module_default["secondaryButton"],
2187
+ disabled: busy,
2188
+ onClick: () => {
2189
+ props.onClose(committed);
2190
+ },
2191
+ children: "取消"
2192
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2193
+ type: "button",
2194
+ className: models_plus_module_default["primaryButton"],
2195
+ disabled: disabled || !ready,
2196
+ onClick: () => {
2197
+ create();
2198
+ },
2199
+ children: busy ? "创建中…" : "创建提供方"
2200
+ })]
2408
2201
  })
2409
2202
  ]
2410
2203
  });
2411
2204
  }
2412
2205
  //#endregion
2413
- //#region src/client/ModelsSection.ext.tsx
2414
- /**
2415
- * Models settings section (EXT fork for dsh-model-extension).
2416
- *
2417
- * Forked from deepseek-harness packages/client/ui-settings-models
2418
- * src/client/ModelsSection.tsx @ 0.1.2-alpha.4.
2419
- * Differences vs upstream (keep this list exhaustive when merging drift):
2420
- * 1. The h2 title row carries an adapter-version badge.
2421
- * 2. No child-slot machinery: this is a standalone section (id
2422
- * 'models-extension'), and the 'settings.models.provider-card' /
2423
- * 'settings.models.footer' children are already declared globally by the
2424
- * official Models section — redeclaring them throws, so every renderSlot
2425
- * call (and the addRow/keyConfiguredOf helpers serving only the seat) is
2426
- * removed. Third-party provider-card/footer extensions therefore render on
2427
- * the official Models page only; that is an accepted trade-off of the fork.
2428
- * 3. Styles come from the plugin's own ModelsSection.ext.module.css.
2429
- * Everything else is upstream verbatim — keep this file minimal when merging
2430
- * drift after a host upgrade.
2431
- *
2432
- * Models settings section: the provider rows joined from the configurable
2433
- * directory, settings namespaces, and credential states, with one editor
2434
- * card at a time. Rows expose only confirmed API-key state through accessible
2435
- * solid configured or missing dots. A whole-section provider without a
2436
- * configured key renders as its open setup card instead of a row, but only in
2437
- * the first-run posture — no provider on the page can serve requests yet — and
2438
- * only until the user closes that card; the add flow is a card carrying the
2439
- * dormant-provider select. Each card kind owns its own open state, so closing
2440
- * one never discards a draft in another. Every mutation writes through the
2441
- * wire, while a provider removal first requires confirmation; the page
2442
- * re-renders from pushed invalidations or the post-apply reload.
2443
- */
2444
- /** Render an editor for either the setup posture or an expanded provider row. */
2445
- function renderProviderEditor({ target, ...props }) {
2446
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderEditor, {
2447
- provider: target.provider,
2448
- displayName: target.displayName,
2449
- settingsPath: target.settingsPath,
2450
- ...target.declared === true ? { declared: true } : {},
2451
- ...props
2452
- });
2453
- }
2206
+ //#region src/client/models-plus/ModelsPlusSection.tsx
2454
2207
  /**
2455
- * Remove one user-added provider and its page-managed credential. Credential
2456
- * removal comes first so a second-step failure leaves the provider row visible
2457
- * and the whole operation safely retryable; both unsets are idempotent.
2458
- * The settings removal names the profile rather than rebuilding its whole
2459
- * namespace from a partial view.
2460
- * @param operations - the page's Host operations.
2461
- * @param controller - the page store to refresh.
2462
- * @param target - the provider's settings address and optional managed credential.
2463
- * @returns the failure message, or undefined once the write and reload landed.
2464
- */
2465
- async function removeProviderProfile(operations, controller, target) {
2466
- if (target.credentialRef !== void 0) {
2467
- const credential = await operations.removeCredential(target.credentialRef);
2468
- if (credential !== void 0) return credential;
2469
- }
2470
- const written = await operations.writeSettings(target.settingsNs, [{
2471
- op: "unset",
2472
- path: [...target.settingsPath]
2473
- }], void 0);
2474
- if (written.kind !== "written") return written.message;
2475
- await controller.load();
2476
- }
2477
- /**
2478
- * Whether a whole-section provider still needs its first key: an unconfigured
2479
- * credential opens the setup card instead of showing a row. This is the
2480
- * first-run posture alone — a user who can already reach some provider gets an
2481
- * ordinary row with the missing-key dot, since nothing here is blocking them.
2482
- * @param row - the joined provider row.
2483
- * @param anyUsable - whether any joined row can already serve requests.
2484
- * @returns whether to render the setup card.
2208
+ * The Models+ settings section (v1.0.0, fully plugin-owned UI): provider rows
2209
+ * joined from the configurable directory, settings namespaces, and credential
2210
+ * states, one editor card at a time. The title row carries the adapter badge
2211
+ * and the metadata download button — the only place models.dev data is ever
2212
+ * fetched, on an explicit user click.
2485
2213
  */
2214
+ /** One fact decides the first-run posture: whether any provider is usable. */
2486
2215
  function needsSetup(row, anyUsable) {
2487
2216
  if (anyUsable) return false;
2488
2217
  if (row.entry.settingsPath.length > 0) return false;
@@ -2500,20 +2229,33 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2500
2229
  ...row.entry.declared === true ? { declared: true } : {}
2501
2230
  };
2502
2231
  }
2503
- /** Stable visible and accessible identity for one provider target. */
2504
- function providerTargetLabel(target) {
2505
- return target.provider === target.displayName ? target.provider : `${target.displayName} (${target.provider})`;
2232
+ /**
2233
+ * Remove one user-added provider and its page-managed credential. Credential
2234
+ * removal comes first so a second-step failure leaves the row retryable.
2235
+ */
2236
+ async function removeProviderProfile(operations, controller, target) {
2237
+ if (target.credentialRef !== void 0) {
2238
+ const credential = await operations.removeCredential(target.credentialRef);
2239
+ if (credential !== void 0) return credential;
2240
+ }
2241
+ const written = await operations.writeSettings(target.settingsNs, [{
2242
+ op: "unset",
2243
+ path: [...target.settingsPath]
2244
+ }], void 0);
2245
+ if (written.kind !== "written") return written.message;
2246
+ await controller.load();
2506
2247
  }
2507
- /** Replace the one provider placeholder in localized destructive-action copy. */
2248
+ /** Replace the one provider placeholder in localized destructive copy. */
2508
2249
  function providerCopy(template, target) {
2509
- return template.replace("{provider}", () => providerTargetLabel(target));
2250
+ const label = target.provider === target.displayName ? target.provider : `${target.displayName} (${target.provider})`;
2251
+ return template.replace("{provider}", () => label);
2510
2252
  }
2511
2253
  /**
2512
- * Render the Models section content column.
2254
+ * Render the Models+ section content column.
2513
2255
  * @param props - slot-delivered injected dependencies.
2514
2256
  * @returns the section, or null while the shell has not injected yet.
2515
2257
  */
2516
- function ModelsSection(props) {
2258
+ function ModelsPlusSection(props) {
2517
2259
  const { controller, useSnapshot, operations, schema, t } = props;
2518
2260
  if (controller === void 0 || useSnapshot === void 0 || operations === void 0 || schema === void 0 || t === void 0) return null;
2519
2261
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Loaded, { injected: {
@@ -2526,15 +2268,18 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2526
2268
  }
2527
2269
  function Loaded({ injected }) {
2528
2270
  const { controller, operations, schema, t } = injected;
2529
- const state = injected.useSnapshot((snapshot) => snapshot);
2271
+ const useSnapshot = injected.useSnapshot;
2272
+ const state = useSnapshot((snapshot) => snapshot);
2530
2273
  const [editing, setEditing] = (0, react.useState)(void 0);
2531
2274
  const [adding, setAdding] = (0, react.useState)(false);
2275
+ const [declaring, setDeclaring] = (0, react.useState)(false);
2532
2276
  const [deleteTarget, setDeleteTarget] = (0, react.useState)(void 0);
2533
2277
  const [deleting, setDeleting] = (0, react.useState)(false);
2534
2278
  const [deleteFailure, setDeleteFailure] = (0, react.useState)(void 0);
2535
2279
  const [savedTarget, setSavedTarget] = (0, react.useState)(void 0);
2536
- const [declaring, setDeclaring] = (0, react.useState)(false);
2537
2280
  const [dismissedSetup, setDismissedSetup] = (0, react.useState)(() => /* @__PURE__ */ new Set());
2281
+ const [metaBusy, setMetaBusy] = (0, react.useState)(false);
2282
+ const [metaMessage, setMetaMessage] = (0, react.useState)(void 0);
2538
2283
  const announceSaved = (target) => {
2539
2284
  controller.load().then(() => {
2540
2285
  setSavedTarget(target);
@@ -2546,198 +2291,196 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2546
2291
  setDeclaring(false);
2547
2292
  if (changed) announceSaved(target);
2548
2293
  };
2549
- /**
2550
- * Close a setup card, which owns none of the state above: the row-editor,
2551
- * add, and declare cards each own one of those, so clearing them here would
2552
- * discard a draft the user opened beside this card. Dismissal is this card's
2553
- * own — the provider falls back to an ordinary row for the rest of the
2554
- * session, and reopens through Edit.
2555
- */
2556
2294
  const closeSetup = (changed, target) => {
2557
2295
  setDismissedSetup((previous) => /* @__PURE__ */ new Set([...previous, target.provider]));
2558
2296
  if (changed) announceSaved(target);
2559
2297
  };
2560
- const closeDelete = () => {
2561
- if (deleting) return;
2562
- setDeleteTarget(void 0);
2563
- setDeleteFailure(void 0);
2564
- };
2565
- const confirmDelete = () => {
2566
- /* v8 ignore next -- the action only renders with a target and is disabled while a deletion is pending */
2567
- if (deleteTarget === void 0 || deleting) return;
2568
- setDeleting(true);
2569
- setDeleteFailure(void 0);
2570
- removeProviderProfile(operations, controller, deleteTarget).then((failure) => {
2571
- if (failure !== void 0) {
2572
- setDeleteFailure(failure);
2573
- return;
2574
- }
2575
- setDeleteTarget(void 0);
2576
- }).finally(() => {
2577
- setDeleting(false);
2578
- });
2298
+ const downloadMeta = async () => {
2299
+ setMetaBusy(true);
2300
+ setMetaMessage(void 0);
2301
+ try {
2302
+ const outcome = await downloadMetadata();
2303
+ setMetaMessage(outcome.ok ? {
2304
+ ok: true,
2305
+ text: "models.dev 元数据已下载并就绪。"
2306
+ } : {
2307
+ ok: false,
2308
+ text: `元数据下载失败:${outcome.message}`
2309
+ });
2310
+ } finally {
2311
+ setMetaBusy(false);
2312
+ }
2579
2313
  };
2580
2314
  if (state.status === "idle") controller.load();
2581
2315
  if (state.status === "error") {
2582
- /* v8 ignore next -- an error status always carries text; the fallback satisfies the nullable type */
2583
2316
  const errorText = state.error ?? "";
2584
2317
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2585
- className: ModelsSection_ext_module_default["section"],
2318
+ className: models_plus_module_default["section"],
2586
2319
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2587
- className: ModelsSection_ext_module_default["error"],
2588
- children: `${t("loadFailed")}: ${errorText}`
2320
+ className: models_plus_module_default["error"],
2321
+ children: `加载提供方目录失败: ${errorText}`
2589
2322
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2590
2323
  type: "button",
2591
- className: ModelsSection_ext_module_default["secondaryButton"],
2324
+ className: models_plus_module_default["secondaryButton"],
2592
2325
  onClick: () => {
2593
2326
  controller.load();
2594
2327
  },
2595
- children: t("retry")
2328
+ children: "重试"
2596
2329
  })]
2597
2330
  });
2598
2331
  }
2599
2332
  const savedRow = savedTarget === void 0 ? void 0 : state.rows.find((row) => row.entry.provider === savedTarget.provider);
2600
2333
  const savedIdentity = savedRow === void 0 ? savedTarget : {
2601
- provider: savedRow.entry.provider,
2602
- displayName: savedRow.entry.displayName
2334
+ displayName: savedRow.entry.displayName,
2335
+ provider: savedRow.entry.provider
2603
2336
  };
2604
2337
  const anyUsable = state.rows.some(providerUsable);
2605
2338
  const configured = state.rows.filter((row) => row.configured);
2606
2339
  const addable = state.rows.filter((row) => !row.configured && row.entry.settingsNs !== "");
2607
2340
  const addTarget = adding ? editing : void 0;
2608
2341
  const addNamespace = addTarget === void 0 ? void 0 : state.namespaces.get(addTarget.settingsNs);
2609
- const protocols = protocolChoices(state.namespaces.get("llm-pi-ai"), schema);
2342
+ const protocols = (() => {
2343
+ return protocolChoices(state.namespaces.get("llm-pi-ai"), schema);
2344
+ })();
2345
+ const renderEditor = (target, namespace, onClose) => {
2346
+ if (namespace === void 0) return null;
2347
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderEditor, {
2348
+ provider: target.provider,
2349
+ displayName: target.displayName,
2350
+ settingsPath: target.settingsPath,
2351
+ ...target.declared === true ? { declared: true } : {},
2352
+ namespace,
2353
+ schema,
2354
+ operations,
2355
+ readOnly: !state.writable,
2356
+ onClose
2357
+ });
2358
+ };
2610
2359
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2611
- className: ModelsSection_ext_module_default["section"],
2360
+ className: models_plus_module_default["section"],
2612
2361
  children: [
2613
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
2614
- className: ModelsSection_ext_module_default["title"],
2615
- children: [t("title"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2616
- className: ModelsSection_ext_module_default["intro"],
2617
- children: ` — ${t("adapterVersion").replace("{version}", ADAPTER_VERSION)}`
2362
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2363
+ className: models_plus_module_default["titleRow"],
2364
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
2365
+ className: models_plus_module_default["title"],
2366
+ children: t("nav")
2367
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2368
+ className: models_plus_module_default["rowActions"],
2369
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2370
+ type: "button",
2371
+ className: models_plus_module_default["linkButton"],
2372
+ disabled: metaBusy,
2373
+ title: "下载/更新 models.dev 元数据(供快速装入预填)",
2374
+ onClick: () => {
2375
+ downloadMeta();
2376
+ },
2377
+ children: metaBusy ? "正在下载元数据…" : "下载/更新元数据"
2378
+ })
2618
2379
  })]
2619
2380
  }),
2381
+ metaMessage === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2382
+ className: metaMessage.ok ? models_plus_module_default["savedNotice"] : models_plus_module_default["error"],
2383
+ children: metaMessage.text
2384
+ }),
2620
2385
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2621
- className: ModelsSection_ext_module_default["intro"],
2386
+ className: models_plus_module_default["intro"],
2622
2387
  children: t("intro")
2623
2388
  }),
2624
- !state.writable && state.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2625
- className: ModelsSection_ext_module_default["notice"],
2626
- children: t("readOnly")
2627
- }) : null,
2628
2389
  savedIdentity === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2629
- className: ModelsSection_ext_module_default["savedNotice"],
2390
+ className: models_plus_module_default["savedNotice"],
2630
2391
  role: "status",
2631
2392
  "aria-live": "polite",
2632
- children: providerCopy(t("savedProvider"), savedIdentity)
2393
+ children: providerCopy("已保存 {provider}。", savedIdentity)
2633
2394
  }),
2634
2395
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2635
- className: ModelsSection_ext_module_default["rows"],
2396
+ className: models_plus_module_default["rows"],
2636
2397
  children: configured.map((row) => {
2637
2398
  const target = targetOf(row);
2638
2399
  const namespace = state.namespaces.get(target.settingsNs);
2639
- /* v8 ignore next -- the join marks a row configured only when its namespace resolved */
2640
2400
  if (namespace === void 0) return null;
2641
2401
  if (needsSetup(row, anyUsable) && !dismissedSetup.has(row.entry.provider)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
2642
- className: ModelsSection_ext_module_default["setupCard"],
2643
- children: renderProviderEditor({
2644
- target,
2645
- namespace,
2646
- schema,
2647
- operations,
2648
- t,
2649
- readOnly: !state.writable,
2650
- onClose: (changed) => {
2651
- closeSetup(changed, target);
2652
- }
2402
+ className: models_plus_module_default["rowCard"],
2403
+ children: renderEditor(target, namespace, (changed) => {
2404
+ closeSetup(changed, target);
2653
2405
  })
2654
2406
  }, row.entry.provider);
2655
- const open = !adding && editing?.provider === row.entry.provider;
2407
+ const open = !adding && !declaring && editing?.provider === row.entry.provider;
2656
2408
  const credentialConfigured = row.credential?.configured === true;
2657
2409
  const credentialMissing = !credentialConfigured && row.apiKeyEnv !== void 0 && row.credential?.configured === false;
2658
2410
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2659
- className: ModelsSection_ext_module_default["rowCard"],
2411
+ className: models_plus_module_default["rowCard"],
2660
2412
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2661
- className: ModelsSection_ext_module_default["rowHead"],
2413
+ className: models_plus_module_default["rowHead"],
2662
2414
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2663
- className: ModelsSection_ext_module_default["rowIdentity"],
2415
+ className: models_plus_module_default["rowIdentity"],
2664
2416
  children: [
2665
2417
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2666
- className: ModelsSection_ext_module_default["rowName"],
2418
+ className: models_plus_module_default["rowName"],
2667
2419
  children: row.entry.displayName
2668
2420
  }),
2669
2421
  row.entry.declared === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2670
- className: ModelsSection_ext_module_default["rowTag"],
2671
- children: t("customTag")
2422
+ className: models_plus_module_default["rowTag"],
2423
+ children: "自定义"
2672
2424
  }) : null,
2673
2425
  credentialConfigured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2674
- className: `${ModelsSection_ext_module_default["credentialDot"]} ${ModelsSection_ext_module_default["credentialDotConfigured"]}`,
2426
+ className: `${models_plus_module_default["credentialDot"]} ${models_plus_module_default["credentialDotConfigured"]}`,
2675
2427
  role: "img",
2676
- "aria-label": t("credentialConfigured"),
2677
- title: t("credentialConfigured")
2428
+ "aria-label": "API 密钥已配置",
2429
+ title: "API 密钥已配置"
2678
2430
  }) : credentialMissing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2679
- className: `${ModelsSection_ext_module_default["credentialDot"]} ${ModelsSection_ext_module_default["credentialDotMissing"]}`,
2431
+ className: `${models_plus_module_default["credentialDot"]} ${models_plus_module_default["credentialDotMissing"]}`,
2680
2432
  role: "img",
2681
- "aria-label": t("credentialMissing"),
2682
- title: t("credentialMissing")
2433
+ "aria-label": "API 密钥缺失",
2434
+ title: "API 密钥缺失"
2683
2435
  }) : null
2684
2436
  ]
2685
2437
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2686
- className: ModelsSection_ext_module_default["rowActions"],
2438
+ className: models_plus_module_default["rowActions"],
2687
2439
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2688
2440
  type: "button",
2689
- className: ModelsSection_ext_module_default["secondaryButton"],
2690
- "aria-label": providerCopy(t("editProvider"), target),
2441
+ className: models_plus_module_default["secondaryButton"],
2442
+ "aria-label": providerCopy("编辑 {provider}", target),
2691
2443
  onClick: () => {
2692
2444
  setSavedTarget(void 0);
2693
2445
  setDeclaring(false);
2694
2446
  setAdding(false);
2695
2447
  setEditing(open ? void 0 : target);
2696
2448
  },
2697
- children: t("edit")
2449
+ children: "编辑"
2698
2450
  }), row.removable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2699
2451
  type: "button",
2700
- className: ModelsSection_ext_module_default["dangerButton"],
2701
- "aria-label": providerCopy(t("removeProvider"), target),
2452
+ className: models_plus_module_default["dangerButton"],
2453
+ "aria-label": providerCopy("删除 {provider}", target),
2702
2454
  disabled: !state.writable,
2703
2455
  onClick: () => {
2704
2456
  setSavedTarget(void 0);
2705
2457
  setDeleteFailure(void 0);
2706
2458
  setDeleteTarget(target);
2707
2459
  },
2708
- children: t("remove")
2460
+ children: "删除"
2709
2461
  }) : null]
2710
2462
  })]
2711
- }), open ? renderProviderEditor({
2712
- target,
2713
- namespace,
2714
- schema,
2715
- operations,
2716
- t,
2717
- readOnly: !state.writable,
2718
- onClose: (changed) => {
2719
- closeEditor(changed, target);
2720
- }
2463
+ }), open ? renderEditor(target, namespace, (changed) => {
2464
+ closeEditor(changed, target);
2721
2465
  }) : null]
2722
2466
  }, row.entry.provider);
2723
2467
  })
2724
2468
  }),
2725
2469
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2726
- className: ModelsSection_ext_module_default["addBlock"],
2470
+ className: models_plus_module_default["addBlock"],
2727
2471
  children: addTarget !== void 0 && addNamespace !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2728
- className: ModelsSection_ext_module_default["addCard"],
2472
+ className: models_plus_module_default["addCard"],
2729
2473
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2730
- className: ModelsSection_ext_module_default["field"],
2731
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2732
- className: ModelsSection_ext_module_default["fieldLabel"],
2733
- children: t("provider")
2474
+ className: models_plus_module_default["field"],
2475
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2476
+ className: models_plus_module_default["fieldLabel"],
2477
+ children: "提供方"
2734
2478
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
2735
- className: `${ModelsSection_ext_module_default["input"]} ${ModelsSection_ext_module_default["selectInput"]}`,
2479
+ className: `${models_plus_module_default["input"]} ${models_plus_module_default["selectInput"]}`,
2736
2480
  value: addTarget.provider,
2737
- "aria-label": t("provider"),
2481
+ "aria-label": "提供方",
2738
2482
  onChange: (event) => {
2739
2483
  const row = addable.find((candidate) => candidate.entry.provider === event.target.value);
2740
- /* v8 ignore next -- the select only lists addable rows */
2741
2484
  if (row === void 0) return;
2742
2485
  setEditing(targetOf(row));
2743
2486
  },
@@ -2749,26 +2492,22 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2749
2492
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderEditor, {
2750
2493
  provider: addTarget.provider,
2751
2494
  displayName: addTarget.displayName,
2752
- hideTitle: true,
2753
2495
  namespace: addNamespace,
2754
2496
  schema,
2755
2497
  settingsPath: addTarget.settingsPath,
2756
2498
  operations,
2757
- t,
2758
2499
  readOnly: !state.writable,
2759
2500
  onClose: (changed) => {
2760
2501
  closeEditor(changed, addTarget);
2761
2502
  }
2762
2503
  }, addTarget.provider)]
2763
2504
  }) : declaring ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2764
- className: ModelsSection_ext_module_default["addCard"],
2765
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomProviderCard, {
2505
+ className: models_plus_module_default["addCard"],
2506
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreateProviderCard, {
2766
2507
  taken: state.rows.map((row) => row.entry.provider),
2767
2508
  protocols,
2768
- /* v8 ignore next -- the card only opens from a button disabled without this namespace */
2769
2509
  revision: state.namespaces.get("llm-pi-ai")?.revision ?? 0,
2770
2510
  operations,
2771
- t,
2772
2511
  readOnly: !state.writable,
2773
2512
  onClose: (changed) => {
2774
2513
  setDeclaring(false);
@@ -2776,24 +2515,35 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2776
2515
  }
2777
2516
  })
2778
2517
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2779
- className: ModelsSection_ext_module_default["addActions"],
2518
+ className: models_plus_module_default["addActions"],
2780
2519
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2781
2520
  type: "button",
2782
- className: ModelsSection_ext_module_default["addButton"],
2521
+ className: models_plus_module_default["addButton"],
2783
2522
  disabled: addable.length === 0 || !state.writable,
2784
2523
  onClick: () => {
2785
2524
  const first = addable[0];
2786
- /* v8 ignore next -- the button is disabled while nothing is addable */
2787
2525
  if (first === void 0) return;
2788
2526
  setSavedTarget(void 0);
2789
2527
  setDeclaring(false);
2790
2528
  setAdding(true);
2791
2529
  setEditing(targetOf(first));
2792
2530
  },
2793
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }), t("add")]
2531
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2532
+ width: "14",
2533
+ height: "14",
2534
+ viewBox: "0 0 16 16",
2535
+ fill: "none",
2536
+ "aria-hidden": "true",
2537
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2538
+ d: "M8 3.5v9M3.5 8h9",
2539
+ stroke: "currentColor",
2540
+ strokeWidth: "1.5",
2541
+ strokeLinecap: "round"
2542
+ })
2543
+ }), "添加提供方"]
2794
2544
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2795
2545
  type: "button",
2796
- className: ModelsSection_ext_module_default["addButton"],
2546
+ className: models_plus_module_default["addButton"],
2797
2547
  disabled: protocols.length === 0 || !state.writable,
2798
2548
  onClick: () => {
2799
2549
  setSavedTarget(void 0);
@@ -2801,32 +2551,64 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2801
2551
  setEditing(void 0);
2802
2552
  setDeclaring(true);
2803
2553
  },
2804
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }), t("customAdd")]
2554
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2555
+ width: "14",
2556
+ height: "14",
2557
+ viewBox: "0 0 16 16",
2558
+ fill: "none",
2559
+ "aria-hidden": "true",
2560
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2561
+ d: "M8 3.5v9M3.5 8h9",
2562
+ stroke: "currentColor",
2563
+ strokeWidth: "1.5",
2564
+ strokeLinecap: "round"
2565
+ })
2566
+ }), "添加自定义提供方"]
2805
2567
  })]
2806
2568
  })
2807
2569
  }),
2808
2570
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2809
2571
  open: deleteTarget !== void 0,
2810
- onClose: closeDelete,
2811
- title: deleteTarget === void 0 ? "" : providerCopy(t("deleteTitle"), deleteTarget),
2812
- closeLabel: t("close"),
2813
- description: deleteTarget === void 0 ? "" : providerCopy(deleteTarget.credentialRef === void 0 ? t("deleteDescription") : t("deleteDescriptionWithCredential"), deleteTarget),
2814
- className: ModelsSection_ext_module_default["deleteDialog"],
2815
- footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2816
- variant: "outline",
2817
- autoFocus: true,
2572
+ onClose: () => {
2573
+ if (deleting) return;
2574
+ setDeleteTarget(void 0);
2575
+ setDeleteFailure(void 0);
2576
+ },
2577
+ title: deleteTarget === void 0 ? "" : providerCopy("删除 {provider}?", deleteTarget),
2578
+ closeLabel: "关闭",
2579
+ description: deleteTarget === void 0 ? "" : providerCopy(deleteTarget.credentialRef === void 0 ? "删除 {provider} 会移除其配置;其使用的凭证(如有)由其他位置管理,将会保留。" : "删除 {provider} 会移除其配置和存储的 API 密钥。", deleteTarget),
2580
+ className: models_plus_module_default["deleteDialog"],
2581
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2582
+ type: "button",
2583
+ className: models_plus_module_default["secondaryButton"],
2818
2584
  disabled: deleting,
2819
- onClick: closeDelete,
2820
- children: t("cancel")
2821
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2822
- variant: "outline",
2823
- className: ModelsSection_ext_module_default["deleteConfirm"],
2585
+ onClick: () => {
2586
+ setDeleteTarget(void 0);
2587
+ setDeleteFailure(void 0);
2588
+ },
2589
+ children: "取消"
2590
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2591
+ type: "button",
2592
+ className: `${models_plus_module_default["secondaryButton"]} ${models_plus_module_default["deleteConfirm"]}`,
2824
2593
  disabled: deleting,
2825
- onClick: confirmDelete,
2826
- children: deleteTarget === void 0 ? "" : providerCopy(deleting ? t("deleting") : t("deleteConfirm"), deleteTarget)
2594
+ onClick: () => {
2595
+ if (deleteTarget === void 0 || deleting) return;
2596
+ setDeleting(true);
2597
+ setDeleteFailure(void 0);
2598
+ removeProviderProfile(operations, controller, deleteTarget).then((removeFailure) => {
2599
+ if (removeFailure !== void 0) {
2600
+ setDeleteFailure(removeFailure);
2601
+ return;
2602
+ }
2603
+ setDeleteTarget(void 0);
2604
+ }).finally(() => {
2605
+ setDeleting(false);
2606
+ });
2607
+ },
2608
+ children: deleteTarget === void 0 ? "" : providerCopy(deleting ? "正在删除 {provider}…" : "删除 {provider}", deleteTarget)
2827
2609
  })] }),
2828
2610
  children: deleteFailure === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2829
- className: ModelsSection_ext_module_default["error"],
2611
+ className: models_plus_module_default["error"],
2830
2612
  children: deleteFailure
2831
2613
  })
2832
2614
  })
@@ -2834,7 +2616,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2834
2616
  });
2835
2617
  }
2836
2618
  //#endregion
2837
- //#region
2619
+ //#region src/client/vendor/operations.ts
2838
2620
  /**
2839
2621
  * Bind the page's Host operations to the plugin's own Remote namespaces.
2840
2622
  * @param ctx - the page plugin's context, which declares `remote.credentials`,
@@ -2883,7 +2665,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2883
2665
  };
2884
2666
  }
2885
2667
  //#endregion
2886
- //#region
2668
+ //#region src/client/vendor/schema-operations.ts
2887
2669
  /**
2888
2670
  * Hide the Cordis service identity behind bound schema callbacks.
2889
2671
  * @param service - settings-owned schema service available in the apply context.
@@ -2904,7 +2686,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2904
2686
  //#region src/client/index-extension.ts
2905
2687
  /** Cordis service name (distinct from the npm package name). */
2906
2688
  const name = "model-extension-client";
2907
- /** Required services — mirrors the upstream Models section registration @ 0.1.2-alpha.4. */
2689
+ /** Required services — mirrors the upstream Models section registration @ 0.1.2-rc.1. */
2908
2690
  const inject = [
2909
2691
  "slots",
2910
2692
  "locale",
@@ -2960,7 +2742,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2960
2742
  order: 11,
2961
2743
  label: () => t("nav"),
2962
2744
  inject: injected
2963
- }, ModelsSection));
2745
+ }, ModelsPlusSection));
2964
2746
  }
2965
2747
  //#endregion
2966
2748
  exports.apply = apply;