dsh-model-extension 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/lib/client.js +1091 -280
- package/lib/client.js.map +1 -1
- package/lib/index.js +402 -5
- package/lib/index.js.map +1 -1
- package/package.json +5 -3
package/lib/client.js
CHANGED
|
@@ -3,7 +3,7 @@ window.__ModuleLoader__.load({
|
|
|
3
3
|
factory: (require) => {
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
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";
|
|
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;background:0 0;border:none;border-radius:6px;flex-direction:column;gap:1px;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;display:flex}.J84-9a_quickItemId{overflow-wrap:anywhere}.J84-9a_quickItemApi{color:var(--dsw-alias-label-tertiary);letter-spacing:.2px;font-size:11px;line-height:14px}.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";
|
|
@@ -217,8 +217,10 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
217
217
|
/** Same-origin routes served by the server half. */
|
|
218
218
|
const INDEX_URL = "/plugins/dsh-model-extension/models-index";
|
|
219
219
|
const DOWNLOAD_URL = "/plugins/dsh-model-extension/models-download";
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
const PI_AI_URL = "/plugins/dsh-model-extension/pi-ai-catalog";
|
|
221
|
+
/** Module-level cache: one page load fetches each source at most once. */
|
|
222
|
+
let cachedIndex;
|
|
223
|
+
/** Normalize the models.dev route payload. */
|
|
222
224
|
function normalize(raw) {
|
|
223
225
|
if (!Array.isArray(raw)) return [];
|
|
224
226
|
const entries = [];
|
|
@@ -237,23 +239,160 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
237
239
|
}
|
|
238
240
|
return entries;
|
|
239
241
|
}
|
|
242
|
+
/** Narrow a modality list to what this UI offers. */
|
|
243
|
+
function supportedInput(value) {
|
|
244
|
+
if (!Array.isArray(value)) return [];
|
|
245
|
+
return value.filter((m) => typeof m === "string" && (m === "text" || m === "image"));
|
|
246
|
+
}
|
|
247
|
+
/** Read one object-valued field off a raw record. */
|
|
248
|
+
function objectField(source, key) {
|
|
249
|
+
const value = source[key];
|
|
250
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
251
|
+
}
|
|
240
252
|
/**
|
|
241
|
-
* The
|
|
242
|
-
*
|
|
253
|
+
* The provider a models.dev id belongs to.
|
|
254
|
+
*
|
|
255
|
+
* models.dev ids are directory paths (`zhipuai/glm-5.3-flash`), so the leading
|
|
256
|
+
* segment names the provider. Reading it here gives the picker the same
|
|
257
|
+
* provider-first navigation that pi-ai's per-provider catalog has for free —
|
|
258
|
+
* and it is what lets a user narrow by vendor instead of guessing between
|
|
259
|
+
* same-named ids.
|
|
260
|
+
* @param id - the models.dev directory id.
|
|
261
|
+
* @returns the provider segment, or undefined when the id carries none.
|
|
243
262
|
*/
|
|
244
|
-
|
|
245
|
-
|
|
263
|
+
function modelsDevProvider(id) {
|
|
264
|
+
const slash = id.indexOf("/");
|
|
265
|
+
return slash > 0 ? id.slice(0, slash) : void 0;
|
|
266
|
+
}
|
|
267
|
+
/** Turn models.dev rows into prefill candidates. */
|
|
268
|
+
function toModelsDevEntries(rows) {
|
|
269
|
+
return rows.map((row) => ({
|
|
270
|
+
source: "models.dev",
|
|
271
|
+
key: `models.dev\u0000${row.id}`,
|
|
272
|
+
id: row.id,
|
|
273
|
+
provider: modelsDevProvider(row.id),
|
|
274
|
+
context: row.context,
|
|
275
|
+
output: row.output,
|
|
276
|
+
input: row.input,
|
|
277
|
+
reasoning: row.reasoning
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
/** Read the diagnostics warnings the server attaches to a catalog payload. */
|
|
281
|
+
function diagnosticsWarnings(payload) {
|
|
282
|
+
if (typeof payload !== "object" || payload === null) return [];
|
|
283
|
+
const diagnostics = payload["diagnostics"];
|
|
284
|
+
if (typeof diagnostics !== "object" || diagnostics === null) return [];
|
|
285
|
+
const warnings = diagnostics["warnings"];
|
|
286
|
+
if (!Array.isArray(warnings)) return [];
|
|
287
|
+
return warnings.filter((warning) => typeof warning === "string");
|
|
288
|
+
}
|
|
289
|
+
/** Turn a pi-ai route payload into prefill candidates, keeping both ids' provenance. */
|
|
290
|
+
function toPiAiEntries(payload) {
|
|
291
|
+
const warnings = diagnosticsWarnings(payload);
|
|
292
|
+
if (typeof payload !== "object" || payload === null) return {
|
|
293
|
+
entries: [],
|
|
294
|
+
warnings
|
|
295
|
+
};
|
|
296
|
+
const body = payload;
|
|
297
|
+
if (!Array.isArray(body["models"])) return {
|
|
298
|
+
entries: [],
|
|
299
|
+
warnings
|
|
300
|
+
};
|
|
301
|
+
const entries = [];
|
|
302
|
+
for (const item of body["models"]) {
|
|
303
|
+
if (typeof item !== "object" || item === null) continue;
|
|
304
|
+
const row = item;
|
|
305
|
+
const id = row["id"];
|
|
306
|
+
const provider = row["provider"];
|
|
307
|
+
if (typeof id !== "string" || id.length === 0) continue;
|
|
308
|
+
if (typeof provider !== "string" || provider.length === 0) continue;
|
|
309
|
+
entries.push({
|
|
310
|
+
source: "pi-ai",
|
|
311
|
+
key: `pi-ai\u0000${provider}\u0000${id}`,
|
|
312
|
+
id,
|
|
313
|
+
provider,
|
|
314
|
+
name: typeof row["name"] === "string" && row["name"].length > 0 ? row["name"] : void 0,
|
|
315
|
+
context: typeof row["contextWindow"] === "number" ? row["contextWindow"] : void 0,
|
|
316
|
+
output: typeof row["maxTokens"] === "number" ? row["maxTokens"] : void 0,
|
|
317
|
+
input: supportedInput(row["input"]),
|
|
318
|
+
reasoning: row["reasoning"] === true,
|
|
319
|
+
api: typeof row["api"] === "string" ? row["api"] : void 0,
|
|
320
|
+
thinkingLevelMap: objectField(row, "thinkingLevelMap"),
|
|
321
|
+
compat: objectField(row, "compat")
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
entries,
|
|
326
|
+
warnings,
|
|
327
|
+
version: typeof body["piAiVersion"] === "string" ? body["piAiVersion"] : void 0,
|
|
328
|
+
generatedAt: typeof body["generatedAt"] === "string" ? body["generatedAt"] : void 0
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Fetch one JSON route, keeping a refusal distinguishable from an empty source.
|
|
333
|
+
*
|
|
334
|
+
* The distinction matters for the pi-ai half: "the host ships no catalog" and
|
|
335
|
+
* "the host's catalog could not be called" both leave the list empty, but only
|
|
336
|
+
* the second one has a fix, and the server names it in the refusal body.
|
|
337
|
+
* @param url - the same-origin route.
|
|
338
|
+
* @returns the parsed payload, or the reason it is missing.
|
|
339
|
+
*/
|
|
340
|
+
async function fetchJson(url) {
|
|
246
341
|
try {
|
|
247
|
-
const response = await fetch(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
342
|
+
const response = await fetch(url);
|
|
343
|
+
const payload = await response.json().catch(() => void 0);
|
|
344
|
+
if (response.ok) return { payload };
|
|
345
|
+
const message = typeof payload === "object" && payload !== null ? payload["message"] : void 0;
|
|
346
|
+
return { failure: typeof message === "string" ? message : `HTTP ${String(response.status)}` };
|
|
347
|
+
} catch (error) {
|
|
348
|
+
return { failure: error instanceof Error ? error.message : String(error) };
|
|
253
349
|
}
|
|
254
350
|
}
|
|
255
351
|
/**
|
|
256
|
-
*
|
|
352
|
+
* The prefill index, fetched once per page load. Both sources are requested
|
|
353
|
+
* together and either may come back empty; an entirely empty index is a
|
|
354
|
+
* legitimate answer the caller renders as "nothing to load yet".
|
|
355
|
+
* @returns the merged index.
|
|
356
|
+
*/
|
|
357
|
+
async function getPrefillIndex() {
|
|
358
|
+
if (cachedIndex !== void 0) return cachedIndex;
|
|
359
|
+
const [piAiResult, modelsDevResult] = await Promise.all([fetchJson(PI_AI_URL), fetchJson(INDEX_URL)]);
|
|
360
|
+
const piAi = toPiAiEntries(piAiResult.payload);
|
|
361
|
+
cachedIndex = {
|
|
362
|
+
piAi: piAi.entries,
|
|
363
|
+
modelsDev: toModelsDevEntries(normalize(modelsDevResult.payload)),
|
|
364
|
+
piAiVersion: piAi.version,
|
|
365
|
+
piAiGeneratedAt: piAi.generatedAt,
|
|
366
|
+
piAiWarnings: piAi.warnings,
|
|
367
|
+
...piAiResult.failure !== void 0 ? { piAiFailure: piAiResult.failure } : {}
|
|
368
|
+
};
|
|
369
|
+
return cachedIndex;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* The one wire protocol one catalog provider's entries agree on.
|
|
373
|
+
*
|
|
374
|
+
* This mirrors the host's own `sharedCatalogApi`: a route whose installed
|
|
375
|
+
* entries all speak the same protocol needs no `api` in configuration, because
|
|
376
|
+
* the host resolves every model — including ones the catalog does not describe
|
|
377
|
+
* — to that shared protocol. Reading it here is what lets the panel judge a
|
|
378
|
+
* row's compat switches the way the adapter will. A provider whose entries
|
|
379
|
+
* disagree (an OpenAI-style catalog spanning Responses and Chat Completions)
|
|
380
|
+
* has no such answer, and neither does an index that has not loaded yet.
|
|
381
|
+
* @param index - the fetched index, or undefined while the page still loads.
|
|
382
|
+
* @param provider - the route key to inspect.
|
|
383
|
+
* @returns the shared protocol, or undefined when none settles it.
|
|
384
|
+
*/
|
|
385
|
+
function sharedCatalogApi(index, provider) {
|
|
386
|
+
if (index === void 0 || provider === void 0 || provider.length === 0) return void 0;
|
|
387
|
+
const apis = /* @__PURE__ */ new Set();
|
|
388
|
+
for (const entry of index.piAi) {
|
|
389
|
+
if (entry.provider !== provider || entry.api === void 0) continue;
|
|
390
|
+
apis.add(entry.api);
|
|
391
|
+
}
|
|
392
|
+
return apis.size === 1 ? [...apis][0] : void 0;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Download/refresh the models.dev file through the server half (user-triggered
|
|
257
396
|
* from the title-row button). A success also refreshes the local cache.
|
|
258
397
|
*/
|
|
259
398
|
async function downloadMetadata() {
|
|
@@ -264,7 +403,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
264
403
|
ok: false,
|
|
265
404
|
message: body?.message ?? `HTTP ${String(response.status)}`
|
|
266
405
|
};
|
|
267
|
-
|
|
406
|
+
cachedIndex = void 0;
|
|
268
407
|
return { ok: true };
|
|
269
408
|
} catch (error) {
|
|
270
409
|
return {
|
|
@@ -274,198 +413,60 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
274
413
|
}
|
|
275
414
|
}
|
|
276
415
|
/**
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
* @param limit - maximum candidates to return.
|
|
282
|
-
* @returns the matching ids; empty when the index is unavailable.
|
|
283
|
-
*/
|
|
284
|
-
function searchModelIds(index, query, limit = 10) {
|
|
285
|
-
if (index === void 0) return [];
|
|
286
|
-
const needle = query.trim().toLowerCase();
|
|
287
|
-
if (needle.length === 0) return [];
|
|
288
|
-
const hits = [];
|
|
289
|
-
for (const entry of index) {
|
|
290
|
-
if (!entry.id.toLowerCase().includes(needle)) continue;
|
|
291
|
-
hits.push(entry.id);
|
|
292
|
-
if (hits.length >= limit) break;
|
|
293
|
-
}
|
|
294
|
-
return hits;
|
|
295
|
-
}
|
|
296
|
-
//#endregion
|
|
297
|
-
//#region src/client/models-plus/compat.ts
|
|
298
|
-
/**
|
|
299
|
-
* The minimal path ops carrying `after` over `before`, both as the card sees
|
|
300
|
-
* them. Only keys the card observed are named; fields absent from both sides
|
|
301
|
-
* produce no op, which is why edits are path-addressed rather than a rebuilt
|
|
302
|
-
* section.
|
|
303
|
-
* @param base - path of the edited subtree inside the user section.
|
|
304
|
-
* @param before - the subtree as loaded, or undefined when it is new.
|
|
305
|
-
* @param after - the subtree as edited.
|
|
306
|
-
* @returns ordered set/unset ops; empty when nothing changed.
|
|
307
|
-
*/
|
|
308
|
-
function pathOps(base, before, after) {
|
|
309
|
-
const previous = typeof before === "object" && before !== null && !Array.isArray(before) ? before : {};
|
|
310
|
-
const ops = [];
|
|
311
|
-
for (const [key, value] of Object.entries(after)) {
|
|
312
|
-
if (JSON.stringify(previous[key]) === JSON.stringify(value)) continue;
|
|
313
|
-
ops.push({
|
|
314
|
-
op: "set",
|
|
315
|
-
path: [...base, key],
|
|
316
|
-
value
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
for (const key of Object.keys(previous)) if (!(key in after)) ops.push({
|
|
320
|
-
op: "unset",
|
|
321
|
-
path: [...base, key]
|
|
322
|
-
});
|
|
323
|
-
return ops;
|
|
324
|
-
}
|
|
325
|
-
/** Accepted capacity spellings: a decimal count with an optional K/M suffix. */
|
|
326
|
-
const CAPACITY_PATTERN = /^(\d+(?:\.\d+)?)([km])?$/i;
|
|
327
|
-
/** Decimal suffix scales — `1M` is 1000K, matching how model capacities are quoted. */
|
|
328
|
-
const CAPACITY_SCALE = {
|
|
329
|
-
k: 1e3,
|
|
330
|
-
m: 1e6
|
|
331
|
-
};
|
|
332
|
-
/**
|
|
333
|
-
* Read a typed capacity, so a user can write `256K` or `1M` instead of counting
|
|
334
|
-
* zeroes. The stored value stays a plain token count.
|
|
335
|
-
* @param text - raw field text.
|
|
336
|
-
* @returns the count; `undefined` when blank (inherit), `NaN` when unreadable
|
|
337
|
-
* (rejected by {@link validateModelRows} before any write).
|
|
416
|
+
* How many candidates the picker renders before it stops and reports the rest.
|
|
417
|
+
*
|
|
418
|
+
* Ten was too few once one id could arrive from a dozen providers; fifty shows
|
|
419
|
+
* a vendor's catalog without pouring a 1354-row index into the DOM.
|
|
338
420
|
*/
|
|
339
|
-
|
|
340
|
-
const trimmed = text.trim();
|
|
341
|
-
if (trimmed.length === 0) return void 0;
|
|
342
|
-
const match = CAPACITY_PATTERN.exec(trimmed);
|
|
343
|
-
if (match === null) return NaN;
|
|
344
|
-
const suffix = match[2]?.toLowerCase();
|
|
345
|
-
const scale = suffix === "k" || suffix === "m" ? CAPACITY_SCALE[suffix] : 1;
|
|
346
|
-
const scaled = Number(match[1]) * scale;
|
|
347
|
-
const rounded = Math.round(scaled);
|
|
348
|
-
return Math.abs(scaled - rounded) < 1e-6 ? rounded : scaled;
|
|
349
|
-
}
|
|
421
|
+
const DEFAULT_PREFILL_LIMIT = 50;
|
|
350
422
|
/**
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if (value % CAPACITY_SCALE.m === 0) return `${String(value / CAPACITY_SCALE.m)}M`;
|
|
360
|
-
if (value % CAPACITY_SCALE.k === 0) return `${String(value / CAPACITY_SCALE.k)}K`;
|
|
361
|
-
return String(value);
|
|
362
|
-
}
|
|
363
|
-
/** Convert a schema-validated catalog value into records without dropping hidden fields. */
|
|
364
|
-
function modelDrafts(value) {
|
|
365
|
-
if (!Array.isArray(value)) return [];
|
|
366
|
-
return value.map((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) ? entry : {});
|
|
367
|
-
}
|
|
368
|
-
/** Canonical key order for one models[] row (plugin-known fields first). */
|
|
369
|
-
const ROW_KEY_ORDER = [
|
|
370
|
-
"id",
|
|
371
|
-
"name",
|
|
372
|
-
"contextWindow",
|
|
373
|
-
"maxTokens",
|
|
374
|
-
"input",
|
|
375
|
-
"reasoningEfforts",
|
|
376
|
-
"compat"
|
|
377
|
-
];
|
|
378
|
-
/** Canonical key order for the row's compat sub-object. */
|
|
379
|
-
const COMPAT_KEY_ORDER = [
|
|
380
|
-
"supportsDeveloperRole",
|
|
381
|
-
"supportsReasoningEffort",
|
|
382
|
-
"thinkingFormat"
|
|
383
|
-
];
|
|
384
|
-
/**
|
|
385
|
-
* Re-key one model row into the canonical order (plugin-known fields in
|
|
386
|
-
* order, unknown/future fields kept at the tail so nothing is dropped).
|
|
387
|
-
* YAML emits keys in object insertion order, and interactive edits append
|
|
388
|
-
* keys at the tail — without this, rows saved after quick-load or gated-key
|
|
389
|
-
* removals come out with scrambled key orders (purely cosmetic, but noisy
|
|
390
|
-
* in settings.yaml diffs).
|
|
391
|
-
* @param row - the drafted row.
|
|
392
|
-
* @returns the re-keyed row (shallow copy).
|
|
423
|
+
* The providers a source offers, alphabetically.
|
|
424
|
+
*
|
|
425
|
+
* This is what turns "which `glm-5.3-flash` did you mean?" into a choice the
|
|
426
|
+
* user can actually make: the same id ships under several providers, and each
|
|
427
|
+
* provider's copy can carry different capacities and compat switches.
|
|
428
|
+
* @param index - the fetched index, or undefined while the page still loads.
|
|
429
|
+
* @param source - the source to enumerate.
|
|
430
|
+
* @returns the provider names; empty when the source has none.
|
|
393
431
|
*/
|
|
394
|
-
function
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
const source = compat;
|
|
401
|
-
const normalized = {};
|
|
402
|
-
for (const key of COMPAT_KEY_ORDER) if (key in source) normalized[key] = source[key];
|
|
403
|
-
for (const key of Object.keys(source)) if (!COMPAT_KEY_ORDER.includes(key)) normalized[key] = source[key];
|
|
404
|
-
next["compat"] = normalized;
|
|
405
|
-
}
|
|
406
|
-
return next;
|
|
432
|
+
function providersOf(index, source) {
|
|
433
|
+
if (index === void 0) return [];
|
|
434
|
+
const entries = source === "pi-ai" ? index.piAi : index.modelsDev;
|
|
435
|
+
const providers = /* @__PURE__ */ new Set();
|
|
436
|
+
for (const entry of entries) if (entry.provider !== void 0 && entry.provider.length > 0) providers.add(entry.provider);
|
|
437
|
+
return [...providers].sort((a, b) => a.localeCompare(b));
|
|
407
438
|
}
|
|
408
439
|
/**
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
440
|
+
* Candidates matching one source, one provider and one id substring.
|
|
441
|
+
*
|
|
442
|
+
* Either filter may be blank, and a blank one *widens* rather than matching
|
|
443
|
+
* nothing: a provider with no id typed lists that provider's whole catalog,
|
|
444
|
+
* which is how a user browses a vendor whose model names they have not
|
|
445
|
+
* memorized. Both blank lists the source's first page.
|
|
446
|
+
* @param index - the fetched index, or undefined while the page still loads.
|
|
447
|
+
* @param query - the source, provider, id substring and cap.
|
|
448
|
+
* @returns the capped candidates and the total match count.
|
|
412
449
|
*/
|
|
413
|
-
function
|
|
414
|
-
if (
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
const name = model["name"];
|
|
430
|
-
if (name !== void 0 && (typeof name !== "string" || name.length === 0)) return {
|
|
431
|
-
index,
|
|
432
|
-
key: "modelNameInvalid"
|
|
433
|
-
};
|
|
434
|
-
const contextWindow = model["contextWindow"];
|
|
435
|
-
if (contextWindow !== void 0 && (typeof contextWindow !== "number" || !Number.isInteger(contextWindow) || contextWindow <= 0)) return {
|
|
436
|
-
index,
|
|
437
|
-
key: "modelContextInvalid"
|
|
438
|
-
};
|
|
439
|
-
const maxTokens = model["maxTokens"];
|
|
440
|
-
if (maxTokens !== void 0 && (typeof maxTokens !== "number" || !Number.isInteger(maxTokens) || maxTokens <= 0)) return {
|
|
441
|
-
index,
|
|
442
|
-
key: "modelMaxTokensInvalid"
|
|
443
|
-
};
|
|
450
|
+
function searchPrefill(index, query) {
|
|
451
|
+
if (index === void 0) return {
|
|
452
|
+
hits: [],
|
|
453
|
+
total: 0
|
|
454
|
+
};
|
|
455
|
+
const entries = query.source === "pi-ai" ? index.piAi : index.modelsDev;
|
|
456
|
+
const provider = query.provider?.trim() ?? "";
|
|
457
|
+
const needle = query.text?.trim().toLowerCase() ?? "";
|
|
458
|
+
const limit = query.limit ?? DEFAULT_PREFILL_LIMIT;
|
|
459
|
+
const hits = [];
|
|
460
|
+
let total = 0;
|
|
461
|
+
for (const entry of entries) {
|
|
462
|
+
if (provider.length > 0 && entry.provider !== provider) continue;
|
|
463
|
+
if (needle.length > 0 && !entry.id.toLowerCase().includes(needle)) continue;
|
|
464
|
+
total += 1;
|
|
465
|
+
if (hits.length < limit) hits.push(entry);
|
|
444
466
|
}
|
|
445
|
-
}
|
|
446
|
-
//#endregion
|
|
447
|
-
//#region src/client/models-plus/presets.ts
|
|
448
|
-
/** The tri-state `compat.supportsDeveloperRole` seed: absent (reads checked). */
|
|
449
|
-
const DEVELOPER_ROLE_SEED = {};
|
|
450
|
-
/** Draft seed for one new model row. */
|
|
451
|
-
function newModelDraft() {
|
|
452
467
|
return {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
reasoningEfforts: {
|
|
456
|
-
off: null,
|
|
457
|
-
minimal: "minimal",
|
|
458
|
-
low: "low",
|
|
459
|
-
medium: "medium",
|
|
460
|
-
high: "high",
|
|
461
|
-
xhigh: "xhigh",
|
|
462
|
-
max: "max"
|
|
463
|
-
},
|
|
464
|
-
compat: {
|
|
465
|
-
...DEVELOPER_ROLE_SEED,
|
|
466
|
-
supportsReasoningEffort: true,
|
|
467
|
-
thinkingFormat: "openai"
|
|
468
|
-
}
|
|
468
|
+
hits,
|
|
469
|
+
total
|
|
469
470
|
};
|
|
470
471
|
}
|
|
471
472
|
//#endregion
|
|
@@ -494,6 +495,99 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
494
495
|
"string-thinking",
|
|
495
496
|
"ant-ling"
|
|
496
497
|
];
|
|
498
|
+
/**
|
|
499
|
+
* The compatibility switches llm-pi-ai *offers*, by wire protocol (the `offer`
|
|
500
|
+
* set of its COMPAT_GATES @ 0.1.5-rc.2). Only these may reach a model row: the
|
|
501
|
+
* adapter refuses a switch no protocol offers rather than ignoring it, so
|
|
502
|
+
* copying pi-ai's catalog `compat` across verbatim would fail resolution on
|
|
503
|
+
* the withheld fields it also records (routing preferences, session affinity,
|
|
504
|
+
* deferred tools, grammar tools). Re-check against the host's COMPAT_GATES on
|
|
505
|
+
* every adapter-anchor bump — the same drift gate the host keeps for itself.
|
|
506
|
+
*/
|
|
507
|
+
const COMPAT_FIELDS = {
|
|
508
|
+
"openai-completions": [
|
|
509
|
+
"supportsStore",
|
|
510
|
+
"supportsDeveloperRole",
|
|
511
|
+
"supportsReasoningEffort",
|
|
512
|
+
"supportsUsageInStreaming",
|
|
513
|
+
"supportsFinishReason",
|
|
514
|
+
"maxTokensField",
|
|
515
|
+
"requiresToolResultName",
|
|
516
|
+
"requiresAssistantAfterToolResult",
|
|
517
|
+
"requiresThinkingAsText",
|
|
518
|
+
"requiresReasoningContentOnAssistantMessages",
|
|
519
|
+
"thinkingFormat",
|
|
520
|
+
"chatTemplateKwargs",
|
|
521
|
+
"chatTemplateArgs",
|
|
522
|
+
"supportsThinkingTokenBudget",
|
|
523
|
+
"thinkingTokenBudgetField",
|
|
524
|
+
"vllmPriority",
|
|
525
|
+
"supportsStrictMode",
|
|
526
|
+
"cacheControlFormat",
|
|
527
|
+
"supportsLongCacheRetention"
|
|
528
|
+
],
|
|
529
|
+
"openai-responses": [
|
|
530
|
+
"supportsDeveloperRole",
|
|
531
|
+
"supportsMaxOutputTokens",
|
|
532
|
+
"supportsStrictMode",
|
|
533
|
+
"supportsLongCacheRetention"
|
|
534
|
+
],
|
|
535
|
+
"azure-openai-responses": [
|
|
536
|
+
"supportsDeveloperRole",
|
|
537
|
+
"supportsMaxOutputTokens",
|
|
538
|
+
"supportsStrictMode",
|
|
539
|
+
"supportsLongCacheRetention"
|
|
540
|
+
],
|
|
541
|
+
"openai-codex-responses": [
|
|
542
|
+
"supportsDeveloperRole",
|
|
543
|
+
"supportsMaxOutputTokens",
|
|
544
|
+
"supportsStrictMode",
|
|
545
|
+
"supportsLongCacheRetention"
|
|
546
|
+
],
|
|
547
|
+
"anthropic-messages": [
|
|
548
|
+
"supportsEagerToolInputStreaming",
|
|
549
|
+
"supportsLongCacheRetention",
|
|
550
|
+
"supportsCacheControlOnTools",
|
|
551
|
+
"supportsTemperature",
|
|
552
|
+
"forceAdaptiveThinking",
|
|
553
|
+
"allowEmptySignature",
|
|
554
|
+
"supportsStrictTools"
|
|
555
|
+
],
|
|
556
|
+
"bedrock-converse-stream": ["supportsStrictMode"]
|
|
557
|
+
};
|
|
558
|
+
/**
|
|
559
|
+
* Switches the base panel already renders a dedicated control for; the
|
|
560
|
+
* "other compatibility switches" block lists everything else.
|
|
561
|
+
*/
|
|
562
|
+
const COMPAT_BASE_FIELDS = [
|
|
563
|
+
"supportsDeveloperRole",
|
|
564
|
+
"supportsReasoningEffort",
|
|
565
|
+
"thinkingFormat"
|
|
566
|
+
];
|
|
567
|
+
/**
|
|
568
|
+
* The offered compat field names for one wire protocol.
|
|
569
|
+
* @param api - the model's protocol, when known.
|
|
570
|
+
* @returns the field names; empty for an unknown or absent protocol.
|
|
571
|
+
*/
|
|
572
|
+
function offeredCompatFields(api) {
|
|
573
|
+
if (api === void 0) return [];
|
|
574
|
+
const fields = COMPAT_FIELDS[api];
|
|
575
|
+
return fields === void 0 ? [] : fields;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Whether one protocol takes one compat switch.
|
|
579
|
+
*
|
|
580
|
+
* The adapter *refuses* a switch its protocol does not declare rather than
|
|
581
|
+
* ignoring it, so this is the difference between a control that can be offered
|
|
582
|
+
* and one that would fail the save. An unknown protocol takes nothing: its
|
|
583
|
+
* field set cannot be read, and the adapter resolves it to the same refusal.
|
|
584
|
+
* @param api - the model's wire protocol, when known.
|
|
585
|
+
* @param field - the compat field name.
|
|
586
|
+
* @returns true when the protocol offers the field.
|
|
587
|
+
*/
|
|
588
|
+
function protocolOffers(api, field) {
|
|
589
|
+
return offeredCompatFields(api).includes(field);
|
|
590
|
+
}
|
|
497
591
|
/** Extra copy for the extension, keyed like the official dictionaries (en is authoritative). */
|
|
498
592
|
const en = {
|
|
499
593
|
nav: "Models+",
|
|
@@ -719,6 +813,347 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
719
813
|
thinkingFormat: "思考格式"
|
|
720
814
|
};
|
|
721
815
|
//#endregion
|
|
816
|
+
//#region src/client/models-plus/compat.ts
|
|
817
|
+
/**
|
|
818
|
+
* The minimal path ops carrying `after` over `before`, both as the card sees
|
|
819
|
+
* them. Only keys the card observed are named; fields absent from both sides
|
|
820
|
+
* produce no op, which is why edits are path-addressed rather than a rebuilt
|
|
821
|
+
* section.
|
|
822
|
+
* @param base - path of the edited subtree inside the user section.
|
|
823
|
+
* @param before - the subtree as loaded, or undefined when it is new.
|
|
824
|
+
* @param after - the subtree as edited.
|
|
825
|
+
* @returns ordered set/unset ops; empty when nothing changed.
|
|
826
|
+
*/
|
|
827
|
+
function pathOps(base, before, after) {
|
|
828
|
+
const previous = typeof before === "object" && before !== null && !Array.isArray(before) ? before : {};
|
|
829
|
+
const ops = [];
|
|
830
|
+
for (const [key, value] of Object.entries(after)) {
|
|
831
|
+
if (JSON.stringify(previous[key]) === JSON.stringify(value)) continue;
|
|
832
|
+
ops.push({
|
|
833
|
+
op: "set",
|
|
834
|
+
path: [...base, key],
|
|
835
|
+
value
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
for (const key of Object.keys(previous)) if (!(key in after)) ops.push({
|
|
839
|
+
op: "unset",
|
|
840
|
+
path: [...base, key]
|
|
841
|
+
});
|
|
842
|
+
return ops;
|
|
843
|
+
}
|
|
844
|
+
/** Accepted capacity spellings: a decimal count with an optional K/M suffix. */
|
|
845
|
+
const CAPACITY_PATTERN = /^(\d+(?:\.\d+)?)([km])?$/i;
|
|
846
|
+
/** Decimal suffix scales — `1M` is 1000K, matching how model capacities are quoted. */
|
|
847
|
+
const CAPACITY_SCALE = {
|
|
848
|
+
k: 1e3,
|
|
849
|
+
m: 1e6
|
|
850
|
+
};
|
|
851
|
+
/**
|
|
852
|
+
* Read a typed capacity, so a user can write `256K` or `1M` instead of counting
|
|
853
|
+
* zeroes. The stored value stays a plain token count.
|
|
854
|
+
* @param text - raw field text.
|
|
855
|
+
* @returns the count; `undefined` when blank (inherit), `NaN` when unreadable
|
|
856
|
+
* (rejected by {@link validateModelRows} before any write).
|
|
857
|
+
*/
|
|
858
|
+
function parseCapacity(text) {
|
|
859
|
+
const trimmed = text.trim();
|
|
860
|
+
if (trimmed.length === 0) return void 0;
|
|
861
|
+
const match = CAPACITY_PATTERN.exec(trimmed);
|
|
862
|
+
if (match === null) return NaN;
|
|
863
|
+
const suffix = match[2]?.toLowerCase();
|
|
864
|
+
const scale = suffix === "k" || suffix === "m" ? CAPACITY_SCALE[suffix] : 1;
|
|
865
|
+
const scaled = Number(match[1]) * scale;
|
|
866
|
+
const rounded = Math.round(scaled);
|
|
867
|
+
return Math.abs(scaled - rounded) < 1e-6 ? rounded : scaled;
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Spell a stored count back in the shortest form that survives a round trip
|
|
871
|
+
* through {@link parseCapacity}; a count that is not a whole number of
|
|
872
|
+
* thousands stays written out.
|
|
873
|
+
* @param value - stored capacity.
|
|
874
|
+
* @returns the field text.
|
|
875
|
+
*/
|
|
876
|
+
function formatCapacity(value) {
|
|
877
|
+
if (!Number.isInteger(value) || value <= 0) return String(value);
|
|
878
|
+
if (value % CAPACITY_SCALE.m === 0) return `${String(value / CAPACITY_SCALE.m)}M`;
|
|
879
|
+
if (value % CAPACITY_SCALE.k === 0) return `${String(value / CAPACITY_SCALE.k)}K`;
|
|
880
|
+
return String(value);
|
|
881
|
+
}
|
|
882
|
+
/** Convert a schema-validated catalog value into records without dropping hidden fields. */
|
|
883
|
+
function modelDrafts(value) {
|
|
884
|
+
if (!Array.isArray(value)) return [];
|
|
885
|
+
return value.map((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) ? entry : {});
|
|
886
|
+
}
|
|
887
|
+
/** Canonical key order for one models[] row (plugin-known fields first). */
|
|
888
|
+
const ROW_KEY_ORDER = [
|
|
889
|
+
"id",
|
|
890
|
+
"name",
|
|
891
|
+
"contextWindow",
|
|
892
|
+
"maxTokens",
|
|
893
|
+
"input",
|
|
894
|
+
"reasoningEfforts",
|
|
895
|
+
"compat"
|
|
896
|
+
];
|
|
897
|
+
/** Canonical key order for the row's compat sub-object. */
|
|
898
|
+
const COMPAT_KEY_ORDER = [
|
|
899
|
+
"supportsDeveloperRole",
|
|
900
|
+
"supportsReasoningEffort",
|
|
901
|
+
"thinkingFormat"
|
|
902
|
+
];
|
|
903
|
+
/**
|
|
904
|
+
* Re-key one model row into the canonical order (plugin-known fields in
|
|
905
|
+
* order, unknown/future fields kept at the tail so nothing is dropped).
|
|
906
|
+
* YAML emits keys in object insertion order, and interactive edits append
|
|
907
|
+
* keys at the tail — without this, rows saved after quick-load or gated-key
|
|
908
|
+
* removals come out with scrambled key orders (purely cosmetic, but noisy
|
|
909
|
+
* in settings.yaml diffs).
|
|
910
|
+
* @param row - the drafted row.
|
|
911
|
+
* @returns the re-keyed row (shallow copy).
|
|
912
|
+
*/
|
|
913
|
+
function normalizeModelRow(row) {
|
|
914
|
+
const next = {};
|
|
915
|
+
for (const key of ROW_KEY_ORDER) if (key in row) next[key] = row[key];
|
|
916
|
+
for (const key of Object.keys(row)) if (!ROW_KEY_ORDER.includes(key)) next[key] = row[key];
|
|
917
|
+
const compat = next["compat"];
|
|
918
|
+
if (typeof compat === "object" && compat !== null && !Array.isArray(compat)) {
|
|
919
|
+
const source = compat;
|
|
920
|
+
const normalized = {};
|
|
921
|
+
for (const key of COMPAT_KEY_ORDER) if (key in source) normalized[key] = source[key];
|
|
922
|
+
for (const key of Object.keys(source)) if (!COMPAT_KEY_ORDER.includes(key)) normalized[key] = source[key];
|
|
923
|
+
next["compat"] = normalized;
|
|
924
|
+
}
|
|
925
|
+
return next;
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* The message for a refused compat switch, shared by both provider cards so the
|
|
929
|
+
* same failure reads the same way wherever it is hit.
|
|
930
|
+
* @param failure - a `modelCompatNotOffered` failure from {@link validateModelRows}.
|
|
931
|
+
* @returns the localised sentence.
|
|
932
|
+
*/
|
|
933
|
+
function compatFailureMessage(failure) {
|
|
934
|
+
const offered = failure.offered ?? [];
|
|
935
|
+
const available = offered.length === 0 ? ",该协议不接受任何可配置开关" : `,该协议可用:${offered.join("、")}`;
|
|
936
|
+
return `模型 ${String(failure.index + 1)}:compat 开关 ${String(failure.field)} 不适用于当前协议(${String(failure.api)})${available}。请在展开的「其他兼容开关」中清空该项以移除,或回到供应商页把协议改成端点真正使用的协议。`;
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Validate adapter constraints that the serialized schema cannot express.
|
|
940
|
+
*
|
|
941
|
+
* The compat half of the check exists because the adapter *refuses* a switch a
|
|
942
|
+
* model's protocol does not declare rather than ignoring it — and the protocol
|
|
943
|
+
* a row resolves to is `route.api` first, the installed catalog entry second.
|
|
944
|
+
* A row can therefore hold switches that were legal when it was written (a
|
|
945
|
+
* protocol change on the route, or a prefill taken from another protocol's
|
|
946
|
+
* catalog entry) and only fail at save time, in English, from the host. This
|
|
947
|
+
* turns that into a positioned, named, localised refusal.
|
|
948
|
+
*
|
|
949
|
+
* An unknown protocol is skipped on purpose: the route's protocol could not be
|
|
950
|
+
* resolved, so the row's real one is unknowable here, and a guess would refuse
|
|
951
|
+
* a configuration the host accepts. The host's own diagnostic still covers it.
|
|
952
|
+
* @param value - user-owned `models` value, or undefined while inherited.
|
|
953
|
+
* @param api - the protocol every row resolves to, when the route settles one.
|
|
954
|
+
* @returns the first invalid row, or undefined when the adapter will accept it.
|
|
955
|
+
*/
|
|
956
|
+
function validateModelRows(value, api) {
|
|
957
|
+
if (value === void 0) return void 0;
|
|
958
|
+
const models = modelDrafts(value);
|
|
959
|
+
const seen = /* @__PURE__ */ new Set();
|
|
960
|
+
for (const [index, model] of models.entries()) {
|
|
961
|
+
const id = model["id"];
|
|
962
|
+
const trimmed = typeof id === "string" ? id.trim() : void 0;
|
|
963
|
+
if (trimmed === void 0 || trimmed.length === 0) return {
|
|
964
|
+
index,
|
|
965
|
+
key: "modelIdRequired"
|
|
966
|
+
};
|
|
967
|
+
if (seen.has(trimmed)) return {
|
|
968
|
+
index,
|
|
969
|
+
key: "modelIdDuplicate"
|
|
970
|
+
};
|
|
971
|
+
seen.add(trimmed);
|
|
972
|
+
const name = model["name"];
|
|
973
|
+
if (name !== void 0 && (typeof name !== "string" || name.length === 0)) return {
|
|
974
|
+
index,
|
|
975
|
+
key: "modelNameInvalid"
|
|
976
|
+
};
|
|
977
|
+
const contextWindow = model["contextWindow"];
|
|
978
|
+
if (contextWindow !== void 0 && (typeof contextWindow !== "number" || !Number.isInteger(contextWindow) || contextWindow <= 0)) return {
|
|
979
|
+
index,
|
|
980
|
+
key: "modelContextInvalid"
|
|
981
|
+
};
|
|
982
|
+
const maxTokens = model["maxTokens"];
|
|
983
|
+
if (maxTokens !== void 0 && (typeof maxTokens !== "number" || !Number.isInteger(maxTokens) || maxTokens <= 0)) return {
|
|
984
|
+
index,
|
|
985
|
+
key: "modelMaxTokensInvalid"
|
|
986
|
+
};
|
|
987
|
+
const compat = model["compat"];
|
|
988
|
+
if (api !== void 0 && typeof compat === "object" && compat !== null && !Array.isArray(compat)) for (const field of Object.keys(compat)) {
|
|
989
|
+
if (protocolOffers(api, field)) continue;
|
|
990
|
+
return {
|
|
991
|
+
index,
|
|
992
|
+
key: "modelCompatNotOffered",
|
|
993
|
+
field,
|
|
994
|
+
api,
|
|
995
|
+
offered: offeredCompatFields(api)
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
//#endregion
|
|
1001
|
+
//#region src/client/models-plus/presets.ts
|
|
1002
|
+
/**
|
|
1003
|
+
* Draft defaults for a NEW model row (added by hand or adopted from a fetch),
|
|
1004
|
+
* per the v1.0.0 plan: reasoning effort ON with every level declared, thinking
|
|
1005
|
+
* format `openai`, both input modalities checked. These are draft seeds that
|
|
1006
|
+
* land in settings.yaml when the row is saved — they are not inherited-state
|
|
1007
|
+
* sentinels, except `supportsDeveloperRole`, which stays UNWRITTEN (its
|
|
1008
|
+
* tri-state reads an absent key as checked, so only an explicit user uncheck
|
|
1009
|
+
* ever writes `false`).
|
|
1010
|
+
*
|
|
1011
|
+
* The compat half of the seed is PROTOCOL-DEPENDENT, and that is load-bearing:
|
|
1012
|
+
* `supportsReasoningEffort` and `thinkingFormat` belong to `openai-completions`
|
|
1013
|
+
* alone, and the adapter refuses a switch its protocol does not declare rather
|
|
1014
|
+
* than ignoring it. Seeding them onto an `anthropic-messages` route — which is
|
|
1015
|
+
* what a fixed vocabulary here did — made every hand-added model on such a
|
|
1016
|
+
* route fail the save over a switch the user never wrote. So the seed asks
|
|
1017
|
+
* {@link protocolOffers} first and writes nothing its protocol would refuse.
|
|
1018
|
+
* `reasoningEfforts` is unaffected: the levels travel through each protocol's
|
|
1019
|
+
* own thinking parameter, so every protocol takes them.
|
|
1020
|
+
*/
|
|
1021
|
+
/** The tri-state `compat.supportsDeveloperRole` seed: absent (reads checked). */
|
|
1022
|
+
const DEVELOPER_ROLE_SEED = {};
|
|
1023
|
+
/**
|
|
1024
|
+
* Draft seed for one new model row.
|
|
1025
|
+
*
|
|
1026
|
+
* An UNKNOWN protocol (no route-level `api`, and the installed catalog does not
|
|
1027
|
+
* describe the route) seeds no compat at all: nothing can say which switches
|
|
1028
|
+
* its endpoint would take, and a guessed `supportsReasoningEffort` on an
|
|
1029
|
+
* Anthropic-shaped route is refused rather than dropped. The levels stay seeded
|
|
1030
|
+
* — they are protocol-independent — so the row still opens with its effort
|
|
1031
|
+
* table populated.
|
|
1032
|
+
* @param api - the wire protocol this row will resolve to, when known.
|
|
1033
|
+
* @returns the draft row.
|
|
1034
|
+
*/
|
|
1035
|
+
function newModelDraft(api) {
|
|
1036
|
+
const compat = { ...DEVELOPER_ROLE_SEED };
|
|
1037
|
+
if (protocolOffers(api, "supportsReasoningEffort")) compat["supportsReasoningEffort"] = true;
|
|
1038
|
+
if (protocolOffers(api, "thinkingFormat")) compat["thinkingFormat"] = "openai";
|
|
1039
|
+
return {
|
|
1040
|
+
id: "",
|
|
1041
|
+
input: ["text", "image"],
|
|
1042
|
+
reasoningEfforts: {
|
|
1043
|
+
off: null,
|
|
1044
|
+
minimal: "minimal",
|
|
1045
|
+
low: "low",
|
|
1046
|
+
medium: "medium",
|
|
1047
|
+
high: "high",
|
|
1048
|
+
xhigh: "xhigh",
|
|
1049
|
+
max: "max"
|
|
1050
|
+
},
|
|
1051
|
+
...Object.keys(compat).length === 0 ? {} : { compat }
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
//#endregion
|
|
1055
|
+
//#region src/client/models-plus/prefill.ts
|
|
1056
|
+
/**
|
|
1057
|
+
* Mapping pi-ai catalog values onto a Models+ row.
|
|
1058
|
+
*
|
|
1059
|
+
* The two sides describe the same thing, but two conventions differ in a way
|
|
1060
|
+
* that makes a straight copy wrong:
|
|
1061
|
+
*
|
|
1062
|
+
* 1. **`null` means the opposite in each.** pi-ai's `thinkingLevelMap[level]`
|
|
1063
|
+
* is *unsupported* when null; our `reasoningEfforts[level]` is *supported,
|
|
1064
|
+
* send nothing* when null (the adapter then drops the key entirely, and
|
|
1065
|
+
* pi-ai reads an absent `off` key as "supported but send no parameter").
|
|
1066
|
+
* Copying one across would invert the meaning for 338 of the catalog's
|
|
1067
|
+
* models, so values are mapped rather than copied: a pi-ai `null` becomes
|
|
1068
|
+
* "omit our key", and an absent pi-ai key becomes an explicit entry.
|
|
1069
|
+
* 2. **An absent base level has a protocol-specific default.** pi-ai resolves
|
|
1070
|
+
* an absent `off`/`minimal`/`low`/`medium`/`high` by falling back to the
|
|
1071
|
+
* level name — except on the Anthropic protocols, where `minimal` collapses
|
|
1072
|
+
* onto `low`, and on Mistral, where every level resolves to `high`. Writing
|
|
1073
|
+
* the level name there would send a spelling those providers never see.
|
|
1074
|
+
*
|
|
1075
|
+
* `xhigh` and `max` are the one pair pi-ai treats as unsupported when absent,
|
|
1076
|
+
* so they are only written when the catalog declares them.
|
|
1077
|
+
*
|
|
1078
|
+
* @module dsh-model-extension/prefill
|
|
1079
|
+
*/
|
|
1080
|
+
/** The seven levels, in pi-ai's escalation order. */
|
|
1081
|
+
const LEVELS = [
|
|
1082
|
+
"off",
|
|
1083
|
+
"minimal",
|
|
1084
|
+
"low",
|
|
1085
|
+
"medium",
|
|
1086
|
+
"high",
|
|
1087
|
+
"xhigh",
|
|
1088
|
+
"max"
|
|
1089
|
+
];
|
|
1090
|
+
/**
|
|
1091
|
+
* The wire spelling pi-ai falls back to for one absent base level, per
|
|
1092
|
+
* protocol. This mirrors the fallbacks inside pi-ai's own dispatch branches.
|
|
1093
|
+
* @param api - the model's wire protocol.
|
|
1094
|
+
* @param level - the level whose key was absent.
|
|
1095
|
+
* @returns the spelling pi-ai would send for that level.
|
|
1096
|
+
*/
|
|
1097
|
+
function defaultWireSpelling(api, level) {
|
|
1098
|
+
if (api === "anthropic-messages" || api === "bedrock-converse-stream") return level === "minimal" ? "low" : level;
|
|
1099
|
+
if (api === "mistral-conversations") return "high";
|
|
1100
|
+
return level;
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Convert a catalog `thinkingLevelMap` into our `reasoningEfforts` dict.
|
|
1104
|
+
*
|
|
1105
|
+
* A model that does not reason gets `false`. An outcome that offers nothing
|
|
1106
|
+
* beyond `off` also becomes `false`, because the adapter rejects a dict whose
|
|
1107
|
+
* only level is `off` — and the two describe the same request anyway.
|
|
1108
|
+
* @param levelMap - the catalog's map, when it carries one.
|
|
1109
|
+
* @param api - the model's wire protocol, which picks the fallback spellings.
|
|
1110
|
+
* @param reasoning - whether the model reasons at all.
|
|
1111
|
+
* @returns the dict to write into the row, or `false` for a non-reasoning model.
|
|
1112
|
+
*/
|
|
1113
|
+
function reasoningEffortsFrom(levelMap, api, reasoning) {
|
|
1114
|
+
if (!reasoning) return false;
|
|
1115
|
+
const map = levelMap ?? {};
|
|
1116
|
+
const efforts = {};
|
|
1117
|
+
for (const level of LEVELS) {
|
|
1118
|
+
if (!(level in map)) {
|
|
1119
|
+
if (level === "off") {
|
|
1120
|
+
efforts[level] = null;
|
|
1121
|
+
continue;
|
|
1122
|
+
}
|
|
1123
|
+
if (level === "xhigh" || level === "max") continue;
|
|
1124
|
+
efforts[level] = defaultWireSpelling(api, level);
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
const wire = map[level];
|
|
1128
|
+
if (wire === null) continue;
|
|
1129
|
+
efforts[level] = wire;
|
|
1130
|
+
}
|
|
1131
|
+
return Object.keys(efforts).some((level) => level !== "off") ? efforts : false;
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Project a catalog `compat` block onto the switches the host offers for one
|
|
1135
|
+
* protocol.
|
|
1136
|
+
*
|
|
1137
|
+
* The filter is load-bearing twice over. It drops pi-ai's withheld switches
|
|
1138
|
+
* (routing preferences, session affinity, deferred tools, grammar tools), which
|
|
1139
|
+
* the adapter *refuses* rather than ignores — a verbatim copy would make the
|
|
1140
|
+
* route fail to resolve. And it filters by the protocol the row will be JUDGED
|
|
1141
|
+
* by, which is not always the catalog entry's own: a hand-declared route states
|
|
1142
|
+
* its `api`, that value overrides the catalog for every model on the route, and
|
|
1143
|
+
* a block taken from another protocol's entry (`supportsStore` on a gateway
|
|
1144
|
+
* that speaks `anthropic-messages`) is refused at save time.
|
|
1145
|
+
* @param compat - the catalog's compat record, when it carries one.
|
|
1146
|
+
* @param api - the protocol the row resolves to, as the adapter resolves it.
|
|
1147
|
+
* @returns the writable subset; empty when nothing survives.
|
|
1148
|
+
*/
|
|
1149
|
+
function compatFrom(compat, api) {
|
|
1150
|
+
const offered = offeredCompatFields(api);
|
|
1151
|
+
if (offered.length === 0 || compat === void 0) return {};
|
|
1152
|
+
const next = {};
|
|
1153
|
+
for (const field of offered) if (field in compat) next[field] = compat[field];
|
|
1154
|
+
return next;
|
|
1155
|
+
}
|
|
1156
|
+
//#endregion
|
|
722
1157
|
//#region src/client/models-plus/models-plus.module.css
|
|
723
1158
|
var models_plus_module_default = {
|
|
724
1159
|
"addActions": "J84-9a_addActions",
|
|
@@ -786,6 +1221,8 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
786
1221
|
"primaryButton": "J84-9a_primaryButton",
|
|
787
1222
|
"quickEmpty": "J84-9a_quickEmpty",
|
|
788
1223
|
"quickItem": "J84-9a_quickItem",
|
|
1224
|
+
"quickItemApi": "J84-9a_quickItemApi",
|
|
1225
|
+
"quickItemId": "J84-9a_quickItemId",
|
|
789
1226
|
"quickList": "J84-9a_quickList",
|
|
790
1227
|
"quickWrap": "J84-9a_quickWrap",
|
|
791
1228
|
"rowActions": "J84-9a_rowActions",
|
|
@@ -812,33 +1249,170 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
812
1249
|
//#endregion
|
|
813
1250
|
//#region src/client/models-plus/QuickLoad.tsx
|
|
814
1251
|
/**
|
|
815
|
-
* Quick-load:
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
*
|
|
1252
|
+
* Quick-load: source → provider → model id, three narrowing steps.
|
|
1253
|
+
*
|
|
1254
|
+
* The single search box could not answer the question it raised. One model id
|
|
1255
|
+
* ships under a dozen providers (`glm-5.3-flash` exists in zai, opencode,
|
|
1256
|
+
* opencode-go, baseten, together, huggingface and two gateways), and each
|
|
1257
|
+
* provider's copy can carry different capacities and compat switches — so a
|
|
1258
|
+
* flat list of ten same-looking ids left the user to pick blind, or to decode
|
|
1259
|
+
* a suffix the plugin had appended for want of a better idea.
|
|
1260
|
+
*
|
|
1261
|
+
* Splitting the axis fixes it at the source: the user states *which catalog*
|
|
1262
|
+
* and *whose copy* first, and the id search then runs inside that slice.
|
|
1263
|
+
*
|
|
1264
|
+
* Both drop-downs are plugin-rendered rather than native. A `<datalist>` was
|
|
1265
|
+
* tried for the provider field and abandoned: the browser positions its popup
|
|
1266
|
+
* itself (it opened across unrelated fields) and none of it is themeable, so
|
|
1267
|
+
* it could not look like the list beside it. One `quickList` now serves both,
|
|
1268
|
+
* which also keeps only one popup on screen at a time.
|
|
1269
|
+
*/
|
|
1270
|
+
/** The two sources, in the order the picker offers them; pi-ai leads by default. */
|
|
1271
|
+
const SOURCES = [{
|
|
1272
|
+
id: "pi-ai",
|
|
1273
|
+
label: "llm-pi-ai"
|
|
1274
|
+
}, {
|
|
1275
|
+
id: "models.dev",
|
|
1276
|
+
label: "models.dev"
|
|
1277
|
+
}];
|
|
1278
|
+
/**
|
|
1279
|
+
* One line describing what the selected source currently offers, shown while
|
|
1280
|
+
* nothing is typed. The pi-ai version and snapshot date matter here: a DSH home
|
|
1281
|
+
* can hold more than one pi-ai copy, and knowing which one answered is the
|
|
1282
|
+
* difference between a diagnosis and a mystery.
|
|
1283
|
+
* @param index - the fetched index.
|
|
1284
|
+
* @param source - the selected source.
|
|
1285
|
+
* @returns the summary line.
|
|
820
1286
|
*/
|
|
1287
|
+
function sourceSummary(index, source) {
|
|
1288
|
+
if (source === "pi-ai") {
|
|
1289
|
+
if (index.piAi.length === 0) return index.piAiFailure === void 0 ? "pi-ai 目录不可用" : `pi-ai 目录不可用:${index.piAiFailure}`;
|
|
1290
|
+
const version = index.piAiVersion === void 0 ? "" : ` v${index.piAiVersion}`;
|
|
1291
|
+
const when = index.piAiGeneratedAt === void 0 ? "" : ` · ${index.piAiGeneratedAt.slice(0, 10)}`;
|
|
1292
|
+
return `pi-ai 目录${version},${String(index.piAi.length)} 个模型${when};选择提供方或输入 model id`;
|
|
1293
|
+
}
|
|
1294
|
+
if (index.modelsDev.length === 0) return "models.dev 元数据未下载——点击页首「下载/更新元数据」";
|
|
1295
|
+
return `models.dev,${String(index.modelsDev.length)} 条;选择提供方或输入 model id`;
|
|
1296
|
+
}
|
|
821
1297
|
/**
|
|
822
|
-
* Render the quick-load
|
|
1298
|
+
* Render the three-step quick-load picker and its candidate list.
|
|
823
1299
|
* @param props - the pick handler and disabled flag.
|
|
824
|
-
* @returns the quick-load
|
|
1300
|
+
* @returns the quick-load block.
|
|
825
1301
|
*/
|
|
826
1302
|
function QuickLoad(props) {
|
|
827
|
-
const [
|
|
828
|
-
const [
|
|
1303
|
+
const [source, setSource] = (0, react.useState)("pi-ai");
|
|
1304
|
+
const [provider, setProvider] = (0, react.useState)("");
|
|
1305
|
+
const [text, setText] = (0, react.useState)("");
|
|
1306
|
+
const [panel, setPanel] = (0, react.useState)(null);
|
|
829
1307
|
const [index, setIndex] = (0, react.useState)(void 0);
|
|
1308
|
+
const textField = (0, react.useRef)(null);
|
|
830
1309
|
const ensureIndex = () => {
|
|
831
|
-
|
|
832
|
-
getMetadataIndex().then((loaded) => {
|
|
1310
|
+
getPrefillIndex().then((loaded) => {
|
|
833
1311
|
setIndex(loaded);
|
|
834
1312
|
});
|
|
835
1313
|
};
|
|
836
|
-
const
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1314
|
+
const providers = providersOf(index, source);
|
|
1315
|
+
const chosenProvider = providers.includes(provider) ? provider : "";
|
|
1316
|
+
const { hits, total } = searchPrefill(index, {
|
|
1317
|
+
source,
|
|
1318
|
+
provider: chosenProvider,
|
|
1319
|
+
text
|
|
1320
|
+
});
|
|
1321
|
+
const pick = (entry) => {
|
|
1322
|
+
setPanel(null);
|
|
1323
|
+
setText(entry.id);
|
|
1324
|
+
props.onPick(entry);
|
|
1325
|
+
};
|
|
1326
|
+
const switchSource = (next) => {
|
|
1327
|
+
setSource(next);
|
|
1328
|
+
setProvider("");
|
|
1329
|
+
setText("");
|
|
1330
|
+
setPanel(null);
|
|
1331
|
+
};
|
|
1332
|
+
/**
|
|
1333
|
+
* Commit one provider choice and move straight on to the id field, so the
|
|
1334
|
+
* flow is pick → type rather than pick → click → type.
|
|
1335
|
+
* @param name - the provider to select; blank means "every provider".
|
|
1336
|
+
*/
|
|
1337
|
+
const chooseProvider = (name) => {
|
|
1338
|
+
setProvider(name);
|
|
1339
|
+
setText("");
|
|
1340
|
+
textField.current?.focus();
|
|
1341
|
+
};
|
|
1342
|
+
const providerBody = () => {
|
|
1343
|
+
if (index === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1344
|
+
className: models_plus_module_default["quickEmpty"],
|
|
1345
|
+
children: "正在载入预填数据…"
|
|
1346
|
+
});
|
|
1347
|
+
const needle = provider.trim().toLowerCase();
|
|
1348
|
+
const matches = needle.length === 0 ? providers : providers.filter((name) => name.toLowerCase().includes(needle));
|
|
1349
|
+
if (providers.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1350
|
+
className: models_plus_module_default["quickEmpty"],
|
|
1351
|
+
children: "该数据源没有提供方信息"
|
|
1352
|
+
});
|
|
1353
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1354
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1355
|
+
type: "button",
|
|
1356
|
+
className: models_plus_module_default["quickItem"],
|
|
1357
|
+
onMouseDown: (event) => {
|
|
1358
|
+
event.preventDefault();
|
|
1359
|
+
chooseProvider("");
|
|
1360
|
+
},
|
|
1361
|
+
children: "全部(不限提供方)"
|
|
1362
|
+
}),
|
|
1363
|
+
matches.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1364
|
+
type: "button",
|
|
1365
|
+
className: models_plus_module_default["quickItem"],
|
|
1366
|
+
onMouseDown: (event) => {
|
|
1367
|
+
event.preventDefault();
|
|
1368
|
+
chooseProvider(name);
|
|
1369
|
+
},
|
|
1370
|
+
children: name
|
|
1371
|
+
}, name)),
|
|
1372
|
+
matches.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1373
|
+
className: models_plus_module_default["quickEmpty"],
|
|
1374
|
+
children: "没有匹配的提供方"
|
|
1375
|
+
}) : null
|
|
1376
|
+
] });
|
|
1377
|
+
};
|
|
1378
|
+
const modelBody = () => {
|
|
1379
|
+
if (index === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1380
|
+
className: models_plus_module_default["quickEmpty"],
|
|
1381
|
+
children: "正在载入预填数据…"
|
|
1382
|
+
});
|
|
1383
|
+
if (chosenProvider.length === 0 && text.trim().length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1384
|
+
className: models_plus_module_default["quickEmpty"],
|
|
1385
|
+
children: sourceSummary(index, source)
|
|
1386
|
+
}), source === "pi-ai" && index.piAiWarnings.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1387
|
+
className: models_plus_module_default["error"],
|
|
1388
|
+
children: index.piAiWarnings.join(";")
|
|
1389
|
+
}) : null] });
|
|
1390
|
+
if (hits.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1391
|
+
className: models_plus_module_default["quickEmpty"],
|
|
1392
|
+
children: "没有匹配的模型"
|
|
1393
|
+
});
|
|
1394
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [hits.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1395
|
+
type: "button",
|
|
1396
|
+
className: models_plus_module_default["quickItem"],
|
|
1397
|
+
onMouseDown: (event) => {
|
|
1398
|
+
event.preventDefault();
|
|
1399
|
+
pick(entry);
|
|
1400
|
+
},
|
|
1401
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1402
|
+
className: models_plus_module_default["quickItemId"],
|
|
1403
|
+
children: entry.id
|
|
1404
|
+
}), entry.api === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1405
|
+
className: models_plus_module_default["quickItemApi"],
|
|
1406
|
+
children: entry.api
|
|
1407
|
+
})]
|
|
1408
|
+
}, entry.key)), total > hits.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1409
|
+
className: models_plus_module_default["quickEmpty"],
|
|
1410
|
+
children: [
|
|
1411
|
+
"另有 ",
|
|
1412
|
+
total - hits.length,
|
|
1413
|
+
" 项匹配——继续输入或选定提供方以缩小范围"
|
|
1414
|
+
]
|
|
1415
|
+
}) : null] });
|
|
842
1416
|
};
|
|
843
1417
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
844
1418
|
className: models_plus_module_default["quickWrap"],
|
|
@@ -847,42 +1421,88 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
847
1421
|
className: models_plus_module_default["extLabel"],
|
|
848
1422
|
children: "快速装入"
|
|
849
1423
|
}),
|
|
850
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
"aria-label": "快速装入",
|
|
856
|
-
disabled: props.disabled,
|
|
857
|
-
onFocus: () => {
|
|
858
|
-
ensureIndex();
|
|
859
|
-
setOpen(true);
|
|
860
|
-
},
|
|
861
|
-
onBlur: () => {
|
|
862
|
-
setOpen(false);
|
|
1424
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1425
|
+
style: {
|
|
1426
|
+
display: "flex",
|
|
1427
|
+
gap: 6,
|
|
1428
|
+
alignItems: "center"
|
|
863
1429
|
},
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1430
|
+
children: [
|
|
1431
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
1432
|
+
className: `${models_plus_module_default["input"]} ${models_plus_module_default["selectInput"]}`,
|
|
1433
|
+
style: {
|
|
1434
|
+
flex: "0 0 108px",
|
|
1435
|
+
minWidth: 0
|
|
1436
|
+
},
|
|
1437
|
+
value: source,
|
|
1438
|
+
disabled: props.disabled,
|
|
1439
|
+
"aria-label": "数据源",
|
|
1440
|
+
onChange: (event) => {
|
|
1441
|
+
switchSource(event.target.value);
|
|
1442
|
+
},
|
|
1443
|
+
children: SOURCES.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1444
|
+
value: option.id,
|
|
1445
|
+
children: option.label
|
|
1446
|
+
}, option.id))
|
|
1447
|
+
}),
|
|
1448
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1449
|
+
className: models_plus_module_default["input"],
|
|
1450
|
+
style: {
|
|
1451
|
+
flex: "0 0 132px",
|
|
1452
|
+
minWidth: 0
|
|
1453
|
+
},
|
|
1454
|
+
type: "text",
|
|
1455
|
+
value: provider,
|
|
1456
|
+
placeholder: "提供方:全部",
|
|
1457
|
+
"aria-label": "提供方",
|
|
1458
|
+
autoComplete: "off",
|
|
1459
|
+
disabled: props.disabled,
|
|
1460
|
+
onFocus: () => {
|
|
1461
|
+
ensureIndex();
|
|
1462
|
+
setPanel("provider");
|
|
1463
|
+
},
|
|
1464
|
+
onBlur: () => {
|
|
1465
|
+
setPanel(null);
|
|
1466
|
+
},
|
|
1467
|
+
onChange: (event) => {
|
|
1468
|
+
setProvider(event.target.value);
|
|
1469
|
+
setPanel("provider");
|
|
1470
|
+
}
|
|
1471
|
+
}),
|
|
1472
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1473
|
+
className: models_plus_module_default["input"],
|
|
1474
|
+
style: {
|
|
1475
|
+
flex: "1 1 auto",
|
|
1476
|
+
minWidth: 0
|
|
1477
|
+
},
|
|
1478
|
+
type: "text",
|
|
1479
|
+
ref: textField,
|
|
1480
|
+
value: text,
|
|
1481
|
+
placeholder: "model id(如 glm)…",
|
|
1482
|
+
"aria-label": "model id",
|
|
1483
|
+
autoComplete: "off",
|
|
1484
|
+
disabled: props.disabled,
|
|
1485
|
+
onFocus: () => {
|
|
1486
|
+
ensureIndex();
|
|
1487
|
+
setPanel("model");
|
|
1488
|
+
},
|
|
1489
|
+
onBlur: () => {
|
|
1490
|
+
setPanel(null);
|
|
1491
|
+
},
|
|
1492
|
+
onChange: (event) => {
|
|
1493
|
+
setText(event.target.value);
|
|
1494
|
+
setPanel("model");
|
|
1495
|
+
}
|
|
1496
|
+
})
|
|
1497
|
+
]
|
|
868
1498
|
}),
|
|
869
|
-
|
|
1499
|
+
panel === "provider" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
870
1500
|
className: models_plus_module_default["quickList"],
|
|
871
|
-
children:
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
children: query.trim().length === 0 ? "输入关键字搜索模型 id" : "没有匹配的模型"
|
|
877
|
-
}) : hits.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
878
|
-
type: "button",
|
|
879
|
-
className: models_plus_module_default["quickItem"],
|
|
880
|
-
onMouseDown: (event) => {
|
|
881
|
-
event.preventDefault();
|
|
882
|
-
pick(id);
|
|
883
|
-
},
|
|
884
|
-
children: id
|
|
885
|
-
}, id))
|
|
1501
|
+
children: providerBody()
|
|
1502
|
+
}) : null,
|
|
1503
|
+
panel === "model" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1504
|
+
className: models_plus_module_default["quickList"],
|
|
1505
|
+
children: modelBody()
|
|
886
1506
|
}) : null
|
|
887
1507
|
]
|
|
888
1508
|
});
|
|
@@ -899,7 +1519,23 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
899
1519
|
* rides the card's existing path-ops save with no extra wire calls. Clearing
|
|
900
1520
|
* the effort gate drops `reasoningEfforts` AND `compat.thinkingFormat` in ONE
|
|
901
1521
|
* commit so partial updates never overwrite each other (no orphan settings).
|
|
1522
|
+
*
|
|
1523
|
+
* Which compat switches this panel may write at all is decided by `api` (see
|
|
1524
|
+
* {@link ModelEntryPanelProps.api}): the adapter refuses a switch the model's
|
|
1525
|
+
* protocol does not declare instead of ignoring it, so a control offered on the
|
|
1526
|
+
* wrong protocol is a save that cannot succeed. An UNKNOWN protocol hides
|
|
1527
|
+
* nothing — nothing can be judged — and the host's own diagnostic still covers
|
|
1528
|
+
* that case.
|
|
1529
|
+
*/
|
|
1530
|
+
/**
|
|
1531
|
+
* Whether a draft field is absent or whitespace-only.
|
|
1532
|
+
* @param value - the field's current value.
|
|
1533
|
+
* @returns true when the field carries nothing worth keeping.
|
|
902
1534
|
*/
|
|
1535
|
+
function isBlank(value) {
|
|
1536
|
+
if (value === void 0 || value === null) return true;
|
|
1537
|
+
return typeof value === "string" && value.trim().length === 0;
|
|
1538
|
+
}
|
|
903
1539
|
/** The row's compat sub-object as a plain record (absent → empty). */
|
|
904
1540
|
function compatOf(model) {
|
|
905
1541
|
const value = model["compat"];
|
|
@@ -922,7 +1558,18 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
922
1558
|
* @returns the expanded panel.
|
|
923
1559
|
*/
|
|
924
1560
|
function ModelEntryPanel(props) {
|
|
925
|
-
const { model, onChange, disabled } = props;
|
|
1561
|
+
const { model, onChange, disabled, api } = props;
|
|
1562
|
+
/**
|
|
1563
|
+
* Whether this panel may OFFER a control for one compat switch.
|
|
1564
|
+
*
|
|
1565
|
+
* Not the same question as `protocolOffers`, and deliberately so: an
|
|
1566
|
+
* unresolvable protocol answers `true` throughout, because with no protocol to
|
|
1567
|
+
* read nothing can be judged — hiding a control on a guess would remove a
|
|
1568
|
+
* switch the route may well take, while the host still names anything it
|
|
1569
|
+
* refuses. A resolved protocol that offers nothing (one pi-ai gives no compat
|
|
1570
|
+
* type) answers `false` throughout, which is exactly what the adapter says.
|
|
1571
|
+
*/
|
|
1572
|
+
const mayOffer = (field) => api === void 0 || offeredCompatFields(api).includes(field);
|
|
926
1573
|
const [capacityText, setCapacityText] = (0, react.useState)(() => /* @__PURE__ */ new Map());
|
|
927
1574
|
/** Patch one key onto the row; `undefined` clears it. */
|
|
928
1575
|
const setKey = (key, value) => {
|
|
@@ -944,21 +1591,45 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
944
1591
|
const setInput = (modalities) => {
|
|
945
1592
|
setKey("input", modalities.length === 0 ? void 0 : [...modalities]);
|
|
946
1593
|
};
|
|
1594
|
+
/**
|
|
1595
|
+
* Apply one prefill candidate.
|
|
1596
|
+
*
|
|
1597
|
+
* Every value is *restated* from the picked source rather than merged into
|
|
1598
|
+
* whatever was already there: capacities, modalities, reasoning levels and
|
|
1599
|
+
* compat are replaced wholesale. A prefill answers "what does this catalog
|
|
1600
|
+
* say about the model", so an absent field clears — leaving another source's
|
|
1601
|
+
* value behind would describe a model neither catalog claims, and switching
|
|
1602
|
+
* sources then re-picking is meant to be a clean rewrite.
|
|
1603
|
+
*
|
|
1604
|
+
* Two things do not follow that rule. `id` and `name` are filled only into a
|
|
1605
|
+
* blank row, because a catalog id is the request id for the vendor's *own*
|
|
1606
|
+
* endpoint while behind a gateway the string on the wire is often different.
|
|
1607
|
+
* And pi-ai's level map is *mapped* rather than copied, because `null` means
|
|
1608
|
+
* the opposite on each side.
|
|
1609
|
+
* @param entry - the picked candidate.
|
|
1610
|
+
*/
|
|
947
1611
|
const applyMetadata = (entry) => {
|
|
948
1612
|
const next = { ...model };
|
|
1613
|
+
const judgedBy = props.api ?? entry.api;
|
|
949
1614
|
if (entry.context === void 0) Reflect.deleteProperty(next, "contextWindow");
|
|
950
1615
|
else next["contextWindow"] = entry.context;
|
|
951
1616
|
if (entry.output === void 0) Reflect.deleteProperty(next, "maxTokens");
|
|
952
1617
|
else next["maxTokens"] = entry.output;
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1618
|
+
const modality = entry.input.filter((m) => m === "text" || m === "image");
|
|
1619
|
+
if (modality.length > 0) next["input"] = modality;
|
|
1620
|
+
else Reflect.deleteProperty(next, "input");
|
|
1621
|
+
if (entry.source === "pi-ai") {
|
|
1622
|
+
if (isBlank(next["id"])) next["id"] = entry.id;
|
|
1623
|
+
if (isBlank(next["name"]) && entry.name !== void 0) next["name"] = entry.name;
|
|
1624
|
+
next["reasoningEfforts"] = reasoningEffortsFrom(entry.thinkingLevelMap, judgedBy, entry.reasoning);
|
|
1625
|
+
const compat = compatFrom(entry.compat, judgedBy);
|
|
1626
|
+
if (Object.keys(compat).length === 0) Reflect.deleteProperty(next, "compat");
|
|
1627
|
+
else next["compat"] = compat;
|
|
1628
|
+
} else {
|
|
957
1629
|
Reflect.deleteProperty(next, "reasoningEfforts");
|
|
1630
|
+
if (entry.reasoning && mayOffer("supportsReasoningEffort")) next["compat"] = { supportsReasoningEffort: true };
|
|
1631
|
+
else Reflect.deleteProperty(next, "compat");
|
|
958
1632
|
}
|
|
959
|
-
setKey("compat", Object.keys(compat).length === 0 ? void 0 : compat);
|
|
960
|
-
const supported = entry.input.filter((m) => m === "text" || m === "image");
|
|
961
|
-
if (supported.length > 0) next["input"] = supported;
|
|
962
1633
|
onChange(next);
|
|
963
1634
|
setCapacityText(/* @__PURE__ */ new Map());
|
|
964
1635
|
};
|
|
@@ -998,8 +1669,61 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
998
1669
|
};
|
|
999
1670
|
const compat = compatOf(model);
|
|
1000
1671
|
const developerRole = compat["supportsDeveloperRole"] !== false;
|
|
1001
|
-
|
|
1672
|
+
/**
|
|
1673
|
+
* Switches a prefill wrote that no control above stands for. Listing them
|
|
1674
|
+
* keeps a prefilled value from becoming invisible state: the pi-ai catalog
|
|
1675
|
+
* records far more of the compat surface than this panel models explicitly,
|
|
1676
|
+
* and that API refuses an unknown switch rather than ignoring it — so a value
|
|
1677
|
+
* the user cannot see is a value the user cannot fix.
|
|
1678
|
+
*
|
|
1679
|
+
* The set is the panel's base list narrowed to what this protocol takes. A
|
|
1680
|
+
* switch the protocol refuses keeps NO control — writing it would fail the
|
|
1681
|
+
* save — so it lands here instead, where it is named as refused and can be
|
|
1682
|
+
* cleared. That is the only way out of a row written before a protocol change
|
|
1683
|
+
* on the route, which no amount of hiding would repair.
|
|
1684
|
+
*/
|
|
1685
|
+
const dedicated = COMPAT_BASE_FIELDS.filter((field) => mayOffer(field));
|
|
1686
|
+
const extraCompat = Object.entries(compat).filter(([key]) => !dedicated.includes(key)).sort(([a], [b]) => a.localeCompare(b));
|
|
1687
|
+
/**
|
|
1688
|
+
* Read an edited switch back as the type it already had.
|
|
1689
|
+
* @param previous - the value the switch carried when rendered.
|
|
1690
|
+
* @param raw - the field text.
|
|
1691
|
+
* @returns the coerced value, or the previous one when the text is unreadable.
|
|
1692
|
+
*/
|
|
1693
|
+
const readCompatValue = (previous, raw) => {
|
|
1694
|
+
if (typeof previous === "boolean") return raw === "true";
|
|
1695
|
+
if (typeof previous === "number") {
|
|
1696
|
+
const parsed = Number(raw);
|
|
1697
|
+
return Number.isNaN(parsed) ? previous : parsed;
|
|
1698
|
+
}
|
|
1699
|
+
if (typeof previous === "string") return raw;
|
|
1700
|
+
try {
|
|
1701
|
+
return JSON.parse(raw);
|
|
1702
|
+
} catch {
|
|
1703
|
+
return previous;
|
|
1704
|
+
}
|
|
1705
|
+
};
|
|
1706
|
+
/**
|
|
1707
|
+
* The switch as a wire field: whether the endpoint takes `reasoning_effort`.
|
|
1708
|
+
*
|
|
1709
|
+
* Gated on the protocol as well as on the stored value: on a protocol that
|
|
1710
|
+
* does not take the switch a leftover `true` describes nothing this route can
|
|
1711
|
+
* act on, and the field's real home is the refused-switch list below.
|
|
1712
|
+
*/
|
|
1713
|
+
const gateOn = mayOffer("supportsReasoningEffort") && compat["supportsReasoningEffort"] === true;
|
|
1002
1714
|
const efforts = effortsOf(model);
|
|
1715
|
+
/**
|
|
1716
|
+
* Whether the reasoning area is worth showing.
|
|
1717
|
+
*
|
|
1718
|
+
* The switch and the area answer different questions: the switch tells the
|
|
1719
|
+
* adapter the endpoint accepts `reasoning_effort`, while the area edits what
|
|
1720
|
+
* *this model* offers. A pi-ai prefill can leave the switch unset on purpose
|
|
1721
|
+
* — an absent key lets the adapter auto-detect from the endpoint — while
|
|
1722
|
+
* still carrying levels worth editing, and those levels reach the provider
|
|
1723
|
+
* through the protocol's own thinking parameter either way. So the area keys
|
|
1724
|
+
* off either signal, not the switch alone.
|
|
1725
|
+
*/
|
|
1726
|
+
const reasoningAreaShown = gateOn || efforts !== void 0 || mayOffer("thinkingFormat") && compat["thinkingFormat"] !== void 0;
|
|
1003
1727
|
const toggleGate = (checked) => {
|
|
1004
1728
|
if (checked) {
|
|
1005
1729
|
setCompatKey("supportsReasoningEffort", true);
|
|
@@ -1078,7 +1802,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1078
1802
|
}, modality))
|
|
1079
1803
|
})]
|
|
1080
1804
|
}),
|
|
1081
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1805
|
+
mayOffer("supportsDeveloperRole") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1082
1806
|
className: models_plus_module_default["extGroup"],
|
|
1083
1807
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1084
1808
|
className: models_plus_module_default["extCheck"],
|
|
@@ -1106,26 +1830,37 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1106
1830
|
})]
|
|
1107
1831
|
})]
|
|
1108
1832
|
})
|
|
1109
|
-
}),
|
|
1110
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1833
|
+
}) : null,
|
|
1834
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1111
1835
|
className: models_plus_module_default["extGroup"],
|
|
1112
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1836
|
+
children: [mayOffer("supportsReasoningEffort") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1113
1837
|
className: models_plus_module_default["switchRow"],
|
|
1114
1838
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1115
1839
|
type: "checkbox",
|
|
1116
1840
|
className: models_plus_module_default["extCheckbox"],
|
|
1117
|
-
checked:
|
|
1841
|
+
checked: gateOn,
|
|
1118
1842
|
disabled,
|
|
1119
1843
|
onChange: (event) => {
|
|
1120
1844
|
toggleGate(event.target.checked);
|
|
1121
1845
|
}
|
|
1122
1846
|
}), "端点接受推理挡位参数(reasoning_effort)"]
|
|
1123
|
-
})
|
|
1847
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1848
|
+
className: models_plus_module_default["extHint"],
|
|
1849
|
+
children: [
|
|
1850
|
+
"当前协议(",
|
|
1851
|
+
String(api),
|
|
1852
|
+
")不接受 reasoning_effort 开关,故此处不提供;下面的挡位仍通过该协议自身的 thinking 参数生效。"
|
|
1853
|
+
]
|
|
1854
|
+
}), mayOffer("supportsReasoningEffort") && !gateOn && reasoningAreaShown ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1855
|
+
className: models_plus_module_default["extHint"],
|
|
1856
|
+
style: { marginTop: 6 },
|
|
1857
|
+
children: "此项未设置:由 llm-pi-ai 按其 baseURL 检测结果决定是否发送 reasoning_effort; 下面的挡位不受影响,仍通过各协议自己的 thinking 参数生效。"
|
|
1858
|
+
}) : null]
|
|
1124
1859
|
}),
|
|
1125
1860
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1126
1861
|
className: models_plus_module_default["extGated"],
|
|
1127
|
-
hidden: !
|
|
1128
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1862
|
+
hidden: !reasoningAreaShown,
|
|
1863
|
+
children: [mayOffer("thinkingFormat") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1129
1864
|
className: models_plus_module_default["extField"],
|
|
1130
1865
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1131
1866
|
className: models_plus_module_default["extLabel"],
|
|
@@ -1145,7 +1880,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1145
1880
|
children: format
|
|
1146
1881
|
}, format))]
|
|
1147
1882
|
})]
|
|
1148
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1883
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1149
1884
|
className: models_plus_module_default["extField"],
|
|
1150
1885
|
children: [
|
|
1151
1886
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1218,7 +1953,53 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1218
1953
|
}) : null
|
|
1219
1954
|
]
|
|
1220
1955
|
})]
|
|
1221
|
-
})
|
|
1956
|
+
}),
|
|
1957
|
+
extraCompat.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1958
|
+
className: models_plus_module_default["extGroup"],
|
|
1959
|
+
children: [
|
|
1960
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1961
|
+
className: models_plus_module_default["extLabel"],
|
|
1962
|
+
style: { marginBottom: 6 },
|
|
1963
|
+
children: "其他兼容开关"
|
|
1964
|
+
}),
|
|
1965
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1966
|
+
style: {
|
|
1967
|
+
display: "flex",
|
|
1968
|
+
flexDirection: "column",
|
|
1969
|
+
gap: 6
|
|
1970
|
+
},
|
|
1971
|
+
children: extraCompat.map(([key, value]) => {
|
|
1972
|
+
const refused = !mayOffer(key);
|
|
1973
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1974
|
+
style: {
|
|
1975
|
+
display: "flex",
|
|
1976
|
+
flexDirection: "column",
|
|
1977
|
+
gap: 2
|
|
1978
|
+
},
|
|
1979
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1980
|
+
className: refused ? models_plus_module_default["error"] : models_plus_module_default["extHint"],
|
|
1981
|
+
children: refused ? `${key} —— 当前协议(${String(api)})不接受此开关,请清空输入框以移除` : key
|
|
1982
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1983
|
+
className: models_plus_module_default["input"],
|
|
1984
|
+
type: "text",
|
|
1985
|
+
defaultValue: typeof value === "string" ? value : JSON.stringify(value) ?? "",
|
|
1986
|
+
"aria-label": key,
|
|
1987
|
+
disabled,
|
|
1988
|
+
onBlur: (event) => {
|
|
1989
|
+
const raw = event.target.value.trim();
|
|
1990
|
+
setCompatKey(key, raw.length === 0 ? void 0 : readCompatValue(value, raw));
|
|
1991
|
+
}
|
|
1992
|
+
})]
|
|
1993
|
+
}, key);
|
|
1994
|
+
})
|
|
1995
|
+
}),
|
|
1996
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1997
|
+
className: models_plus_module_default["extHint"],
|
|
1998
|
+
style: { marginTop: 6 },
|
|
1999
|
+
children: "这些开关由 pi-ai 目录预填;清空输入框即移除该开关,回到目录默认。"
|
|
2000
|
+
})
|
|
2001
|
+
]
|
|
2002
|
+
}) : null
|
|
1222
2003
|
]
|
|
1223
2004
|
});
|
|
1224
2005
|
}
|
|
@@ -1236,8 +2017,8 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1236
2017
|
return typeof value === "string" ? value : "";
|
|
1237
2018
|
}
|
|
1238
2019
|
/** Adopt one candidate, v1.0.0 defaults + the endpoint's disclosed capacities. */
|
|
1239
|
-
function adopt(candidate) {
|
|
1240
|
-
const row = newModelDraft();
|
|
2020
|
+
function adopt(candidate, routeApi) {
|
|
2021
|
+
const row = newModelDraft(routeApi);
|
|
1241
2022
|
row["id"] = candidate.id;
|
|
1242
2023
|
row["name"] = candidate.name ?? candidate.id;
|
|
1243
2024
|
if (candidate.contextWindow !== void 0) row["contextWindow"] = candidate.contextWindow;
|
|
@@ -1320,7 +2101,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1320
2101
|
const byId = new Map(models.map((model) => [textOf(model, "id"), model]));
|
|
1321
2102
|
for (const candidate of candidates) {
|
|
1322
2103
|
if (!picked.has(candidate.id)) continue;
|
|
1323
|
-
byId.set(candidate.id, byId.get(candidate.id) ?? adopt(candidate));
|
|
2104
|
+
byId.set(candidate.id, byId.get(candidate.id) ?? adopt(candidate, props.routeApi));
|
|
1324
2105
|
}
|
|
1325
2106
|
onChange([...byId.values()]);
|
|
1326
2107
|
closePicker();
|
|
@@ -1463,19 +2244,20 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1463
2244
|
]
|
|
1464
2245
|
}), expanded.has(index) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelEntryPanel, {
|
|
1465
2246
|
model,
|
|
2247
|
+
api: props.routeApi,
|
|
1466
2248
|
disabled,
|
|
1467
2249
|
onChange: (next) => {
|
|
1468
2250
|
onChange(models.map((row, at) => at === index ? next : row));
|
|
1469
2251
|
}
|
|
1470
2252
|
}) : null]
|
|
1471
|
-
},
|
|
2253
|
+
}, String(index)))
|
|
1472
2254
|
}),
|
|
1473
2255
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1474
2256
|
type: "button",
|
|
1475
2257
|
className: models_plus_module_default["addModelButton"],
|
|
1476
2258
|
disabled,
|
|
1477
2259
|
onClick: () => {
|
|
1478
|
-
onChange([...models, newModelDraft()]);
|
|
2260
|
+
onChange([...models, newModelDraft(props.routeApi)]);
|
|
1479
2261
|
},
|
|
1480
2262
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1481
2263
|
width: "14",
|
|
@@ -1625,6 +2407,22 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1625
2407
|
namespace,
|
|
1626
2408
|
schema
|
|
1627
2409
|
]);
|
|
2410
|
+
/**
|
|
2411
|
+
* The pi-ai catalog, loaded only on the family that needs it: it answers the
|
|
2412
|
+
* protocol of a route that states none (a catalog route), which is what the
|
|
2413
|
+
* model panel judges compat switches by.
|
|
2414
|
+
*/
|
|
2415
|
+
const [prefillIndex, setPrefillIndex] = (0, react.useState)(void 0);
|
|
2416
|
+
(0, react.useEffect)(() => {
|
|
2417
|
+
if (layout !== "pi-ai") return;
|
|
2418
|
+
let stale = false;
|
|
2419
|
+
getPrefillIndex().then((loaded) => {
|
|
2420
|
+
if (!stale) setPrefillIndex(loaded);
|
|
2421
|
+
});
|
|
2422
|
+
return () => {
|
|
2423
|
+
stale = true;
|
|
2424
|
+
};
|
|
2425
|
+
}, [layout]);
|
|
1628
2426
|
(0, react.useEffect)(() => {
|
|
1629
2427
|
let stale = false;
|
|
1630
2428
|
setKeyState(void 0);
|
|
@@ -1644,7 +2442,18 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1644
2442
|
const value = next === void 0 || next.trim().length === 0 ? void 0 : next;
|
|
1645
2443
|
setDraft((current) => value === void 0 ? schema.deletePath(current, [key]) : schema.setPath(current, [key], value));
|
|
1646
2444
|
};
|
|
1647
|
-
|
|
2445
|
+
/**
|
|
2446
|
+
* The protocol every row of this route resolves to, resolved the way the
|
|
2447
|
+
* adapter resolves it: a hand-declared route's own `api` first — it overrides
|
|
2448
|
+
* the catalog for every model on the route — then the protocol the installed
|
|
2449
|
+
* catalog's entries for this route agree on, then nothing.
|
|
2450
|
+
*
|
|
2451
|
+
* The model panel needs it because the adapter judges each row's compat
|
|
2452
|
+
* switches by THIS protocol, not by the catalog entry the row was prefilled
|
|
2453
|
+
* from; without it a row can carry switches the route will refuse.
|
|
2454
|
+
*/
|
|
2455
|
+
const routeApi = layout === "pi-ai" ? (props.declared === true ? stringAt(draft, "api") ?? stringAt(fallback, "api") : void 0) ?? sharedCatalogApi(prefillIndex, props.provider) : void 0;
|
|
2456
|
+
const modelFailure = validateModelRows(schema.getPath(draft, ["models"]), routeApi);
|
|
1648
2457
|
const keyFailure = (() => {
|
|
1649
2458
|
if (keyDraft.length === 0) return void 0;
|
|
1650
2459
|
const value = keyDraft.trim();
|
|
@@ -1672,8 +2481,8 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1672
2481
|
const applyOnce = async () => {
|
|
1673
2482
|
const ns = namespace.ns;
|
|
1674
2483
|
let next = layout === "pi-ai" && stringAt(draft, "apiKeyEnv") === void 0 && stringAt(fallback, "apiKeyEnv") === void 0 && keyValue.length > 0 ? schema.setPath(draft, ["apiKeyEnv"], keyRef) : draft;
|
|
1675
|
-
const rowFailure = validateModelRows(schema.getPath(next, ["models"]));
|
|
1676
|
-
if (rowFailure !== void 0) return `模型 ${String(rowFailure.index + 1)}:${{
|
|
2484
|
+
const rowFailure = validateModelRows(schema.getPath(next, ["models"]), routeApi);
|
|
2485
|
+
if (rowFailure !== void 0) return rowFailure.key === "modelCompatNotOffered" ? compatFailureMessage(rowFailure) : `模型 ${String(rowFailure.index + 1)}:${{
|
|
1677
2486
|
modelIdRequired: "模型 ID 不能为空。",
|
|
1678
2487
|
modelIdDuplicate: "模型 ID 不能重复。",
|
|
1679
2488
|
modelNameInvalid: "显示名称不能为空。",
|
|
@@ -1778,7 +2587,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1778
2587
|
role: "tabpanel",
|
|
1779
2588
|
hidden: tab !== "provider",
|
|
1780
2589
|
children: [
|
|
1781
|
-
layout === "pi-ai"
|
|
2590
|
+
layout === "pi-ai" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1782
2591
|
className: models_plus_module_default["field"],
|
|
1783
2592
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1784
2593
|
className: models_plus_module_default["fieldLabel"],
|
|
@@ -1868,6 +2677,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1868
2677
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalog, {
|
|
1869
2678
|
models,
|
|
1870
2679
|
overridden: modelsOverridden,
|
|
2680
|
+
routeApi,
|
|
1871
2681
|
defaultContextWindow: typeof defaultContextWindow === "number" ? defaultContextWindow : void 0,
|
|
1872
2682
|
defaultMaxTokens: typeof defaultMaxTokens === "number" ? defaultMaxTokens : void 0,
|
|
1873
2683
|
probe,
|
|
@@ -1890,7 +2700,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1890
2700
|
}) : null,
|
|
1891
2701
|
modelFailure === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1892
2702
|
className: models_plus_module_default["advancedHint"],
|
|
1893
|
-
children: `模型 ${String(modelFailure.index + 1)}:${modelFailure.key}`
|
|
2703
|
+
children: modelFailure.key === "modelCompatNotOffered" ? compatFailureMessage(modelFailure) : `模型 ${String(modelFailure.index + 1)}:${modelFailure.key}`
|
|
1894
2704
|
}),
|
|
1895
2705
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1896
2706
|
className: models_plus_module_default["editorActions"],
|
|
@@ -1954,7 +2764,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1954
2764
|
const profileDisabled = disabled || committed;
|
|
1955
2765
|
const routeInvalid = route.length > 0 && !ROUTE_PATTERN.test(route);
|
|
1956
2766
|
const routeTaken = taken.includes(route);
|
|
1957
|
-
const modelFailure = validateModelRows(models);
|
|
2767
|
+
const modelFailure = validateModelRows(models, protocol);
|
|
1958
2768
|
const keyFailure = (() => {
|
|
1959
2769
|
if (keyDraft.length === 0) return void 0;
|
|
1960
2770
|
const value = keyDraft.trim();
|
|
@@ -1966,7 +2776,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
1966
2776
|
})();
|
|
1967
2777
|
const keyValue = keyDraft.trim();
|
|
1968
2778
|
const ready = route.length > 0 && !routeInvalid && !routeTaken && baseURL.length > 0 && models.length > 0 && modelFailure === void 0 && keyFailure === void 0;
|
|
1969
|
-
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}` : "自定义提供方至少需要一个模型。";
|
|
2779
|
+
const hint = failure !== void 0 || ready || keyFailure !== void 0 || route.length === 0 || routeInvalid || routeTaken ? void 0 : baseURL.length === 0 ? "自定义提供方需要填写 API 地址。" : modelFailure !== void 0 ? modelFailure.key === "modelCompatNotOffered" ? compatFailureMessage(modelFailure) : `模型 ${String(modelFailure.index + 1)}:${modelFailure.key}` : "自定义提供方至少需要一个模型。";
|
|
1970
2780
|
/** Perform the create, returning a failure message or undefined. */
|
|
1971
2781
|
const createOnce = async () => {
|
|
1972
2782
|
const keyRef = deriveKeyRef(route);
|
|
@@ -2158,6 +2968,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
|
|
|
2158
2968
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalog, {
|
|
2159
2969
|
models,
|
|
2160
2970
|
overridden: true,
|
|
2971
|
+
routeApi: protocol,
|
|
2161
2972
|
defaultContextWindow: void 0,
|
|
2162
2973
|
defaultMaxTokens: void 0,
|
|
2163
2974
|
probe: {
|