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