2158 lines
77 KiB
JavaScript
2158 lines
77 KiB
JavaScript
// Script to assemble client.js with complete bidirectional i18n (Chinese & English)
|
|
const fs = require('fs');
|
|
|
|
const origClientPath = '/home/cho/.nvm/versions/node/v24.18.0/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-client-ui-settings-models/lib/client.js';
|
|
const origCode = fs.readFileSync(origClientPath, 'utf8');
|
|
|
|
const enhancedModelListEditorCode = `
|
|
//#region lib/types/client/ModelListEditor.js (REFINED APPROVED LAYOUT & FULL I18N)
|
|
function textOf(model, key) {
|
|
const value = model[key];
|
|
return typeof value === "string" ? value : "";
|
|
}
|
|
function numberOf(model, key) {
|
|
const value = model[key];
|
|
return typeof value === "number" ? value : void 0;
|
|
}
|
|
function IconChevron({ open }) {
|
|
return (0, react_jsx_runtime.jsx)("svg", {
|
|
width: "14",
|
|
height: "14",
|
|
viewBox: "0 0 16 16",
|
|
fill: "none",
|
|
"aria-hidden": true,
|
|
style: {
|
|
transform: open ? "rotate(90deg)" : void 0,
|
|
transition: "transform 140ms ease"
|
|
},
|
|
children: (0, react_jsx_runtime.jsx)("path", {
|
|
d: "M6 3.5L10.5 8L6 12.5",
|
|
stroke: "currentColor",
|
|
strokeWidth: "1.5",
|
|
strokeLinecap: "round",
|
|
strokeLinejoin: "round"
|
|
})
|
|
});
|
|
}
|
|
function IconTrash() {
|
|
return (0, react_jsx_runtime.jsx)("svg", {
|
|
width: "14",
|
|
height: "14",
|
|
viewBox: "0 0 16 16",
|
|
fill: "none",
|
|
"aria-hidden": true,
|
|
children: (0, react_jsx_runtime.jsx)("path", {
|
|
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",
|
|
stroke: "currentColor",
|
|
strokeWidth: "1.3",
|
|
strokeLinecap: "round",
|
|
strokeLinejoin: "round"
|
|
})
|
|
});
|
|
}
|
|
const CAPACITY_HINT = {
|
|
contextWindow: "256K",
|
|
maxTokens: "32K"
|
|
};
|
|
function capacitySpelling(value) {
|
|
return value === void 0 ? "" : formatCapacity(value);
|
|
}
|
|
function adopt(candidate) {
|
|
return {
|
|
id: candidate.id,
|
|
...candidate.name === void 0 ? {} : { name: candidate.name },
|
|
...candidate.contextWindow === void 0 ? {} : { contextWindow: candidate.contextWindow },
|
|
...candidate.maxTokens === void 0 ? {} : { maxTokens: candidate.maxTokens }
|
|
};
|
|
}
|
|
|
|
// CSS styles (Light & Dark Theme adaptive via DSH tokens and body[data-ds-dark-theme])
|
|
const ENHANCER_CSS = \`
|
|
.VOzbGW_modelRow, .zGbnIq_modelRow, div[class*="modelRow"] {
|
|
grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr) 28px 28px auto !important;
|
|
align-items: center;
|
|
gap: 6px;
|
|
display: grid;
|
|
}
|
|
.me-model-badges-row {
|
|
display: flex;
|
|
gap: 6px;
|
|
align-items: center;
|
|
margin-top: 4px;
|
|
padding-left: 2px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.me-badge {
|
|
font-size: 11px;
|
|
line-height: 16px;
|
|
padding: 0 6px;
|
|
border-radius: 4px;
|
|
font-weight: 500;
|
|
white-space: nowrap;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
.me-badge-vision {
|
|
color: var(--dsw-alias-state-success-primary, #10b981);
|
|
border: 1px solid var(--dsw-alias-border-l2, rgba(16, 185, 129, 0.3));
|
|
background: var(--dsw-alias-surface-l2, rgba(16, 185, 129, 0.08));
|
|
}
|
|
.me-badge-thinking {
|
|
color: #9d7dfa;
|
|
border: 1px solid var(--dsw-alias-border-l2, rgba(157, 125, 250, 0.3));
|
|
background: var(--dsw-alias-surface-l2, rgba(157, 125, 250, 0.08));
|
|
}
|
|
.me-action-btn {
|
|
box-sizing: border-box;
|
|
height: 28px;
|
|
padding: 0 9px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.15));
|
|
background: transparent;
|
|
color: var(--dsw-alias-label-secondary, #61666b);
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
transition: all 120ms ease;
|
|
margin-left: 2px;
|
|
}
|
|
.me-action-btn:hover:not(:disabled) {
|
|
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05));
|
|
color: var(--dsw-alias-label-primary, #0f1115);
|
|
border-color: var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.25));
|
|
}
|
|
body[data-ds-dark-theme] .me-action-btn {
|
|
border-color: var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.12));
|
|
color: var(--dsw-alias-label-secondary, #999);
|
|
}
|
|
body[data-ds-dark-theme] .me-action-btn:hover:not(:disabled) {
|
|
background: var(--dsw-alias-surface-l2, rgba(255, 255, 255, 0.06));
|
|
color: var(--dsw-alias-label-primary, #fff);
|
|
border-color: var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.25));
|
|
}
|
|
.me-action-btn:disabled {
|
|
opacity: 0.4;
|
|
cursor: not-allowed;
|
|
}
|
|
.me-advanced-container {
|
|
margin-top: 10px;
|
|
padding: 12px 14px;
|
|
border-radius: 8px;
|
|
background: var(--dsw-alias-bg-module-platform, rgba(0, 0, 0, 0.02));
|
|
border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08));
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
body[data-ds-dark-theme] .me-advanced-container {
|
|
background: var(--dsw-alias-surface-l1, rgba(255, 255, 255, 0.02));
|
|
border-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.08));
|
|
}
|
|
.me-advanced-row {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
.me-advanced-row-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
.me-section-title {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: var(--dsw-alias-label-secondary, #888);
|
|
}
|
|
.me-checkbox-group {
|
|
display: flex;
|
|
gap: 12px;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
}
|
|
.me-checkbox-label {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
color: var(--dsw-alias-label-primary, inherit);
|
|
user-select: none;
|
|
}
|
|
.me-checkbox-label input[type="checkbox"] {
|
|
cursor: pointer;
|
|
accent-color: var(--dsw-alias-button-primary-fill, #2563eb);
|
|
}
|
|
.me-effort-tag {
|
|
font-size: 11px;
|
|
line-height: 16px;
|
|
padding: 2px 7px;
|
|
border-radius: 4px;
|
|
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05));
|
|
border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.1));
|
|
color: var(--dsw-alias-label-secondary, #666);
|
|
cursor: pointer;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
transition: all 120ms ease;
|
|
user-select: none;
|
|
}
|
|
body[data-ds-dark-theme] .me-effort-tag {
|
|
background: var(--dsw-alias-surface-l2, rgba(255, 255, 255, 0.06));
|
|
border-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.1));
|
|
color: var(--dsw-alias-label-secondary, #aaa);
|
|
}
|
|
.me-effort-tag.active {
|
|
background: rgba(147, 105, 245, 0.15);
|
|
border-color: rgba(147, 105, 245, 0.4);
|
|
color: #7c3aed;
|
|
font-weight: 500;
|
|
}
|
|
body[data-ds-dark-theme] .me-effort-tag.active {
|
|
color: #b59fff;
|
|
}
|
|
.me-diff-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 14px 0;
|
|
font-size: 13px;
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.1));
|
|
}
|
|
body[data-ds-dark-theme] .me-diff-table {
|
|
border-color: var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.12));
|
|
}
|
|
.me-diff-table th, .me-diff-table td {
|
|
padding: 9px 12px;
|
|
border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08));
|
|
text-align: left;
|
|
}
|
|
body[data-ds-dark-theme] .me-diff-table th, body[data-ds-dark-theme] .me-diff-table td {
|
|
border-bottom-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.08));
|
|
}
|
|
.me-diff-table th {
|
|
background: var(--dsw-alias-bg-module-platform, rgba(0, 0, 0, 0.04));
|
|
color: var(--dsw-alias-label-secondary, #666);
|
|
font-weight: 500;
|
|
font-size: 12px;
|
|
}
|
|
body[data-ds-dark-theme] .me-diff-table th {
|
|
background: var(--dsw-alias-surface-l2, rgba(255, 255, 255, 0.04));
|
|
color: var(--dsw-alias-label-secondary, #999);
|
|
}
|
|
.me-diff-table tr:last-child td {
|
|
border-bottom: none;
|
|
}
|
|
.me-val-new {
|
|
color: var(--dsw-alias-state-success-primary, #10b981);
|
|
font-weight: 500;
|
|
}
|
|
.me-val-old {
|
|
color: var(--dsw-alias-label-tertiary, #888);
|
|
text-decoration: line-through;
|
|
margin-right: 6px;
|
|
}
|
|
|
|
/* TOOLBAR: WRAP [+] AND [📎] TOGETHER IN A PILL ISLAND CONTAINER (ON THE FAR LEFT) */
|
|
div[class*="tools"], div[class*="InputBar_module_css_tools"] {
|
|
display: flex !important;
|
|
align-items: center !important;
|
|
}
|
|
div[data-slot="conversation.input.left"] {
|
|
order: -10 !important;
|
|
display: inline-flex !important;
|
|
align-items: center !important;
|
|
}
|
|
.me-pill-island {
|
|
box-sizing: border-box;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
padding: 2px 4px;
|
|
height: 28px;
|
|
border-radius: 14px;
|
|
border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12));
|
|
background: var(--dsw-alias-bg-module-platform, rgba(0, 0, 0, 0.03));
|
|
margin-right: 6px;
|
|
flex-shrink: 0;
|
|
}
|
|
body[data-ds-dark-theme] .me-pill-island {
|
|
border-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.1));
|
|
background: var(--dsw-alias-surface-l1, rgba(255, 255, 255, 0.03));
|
|
}
|
|
.me-pill-btn {
|
|
box-sizing: border-box;
|
|
width: 22px;
|
|
height: 22px;
|
|
border-radius: 50% !important;
|
|
border: none;
|
|
background: transparent;
|
|
color: var(--dsw-alias-label-secondary, #61666b);
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
transition: all 120ms ease;
|
|
padding: 0;
|
|
}
|
|
.me-pill-btn:hover:not(:disabled) {
|
|
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.07)) !important;
|
|
color: var(--dsw-alias-label-primary, #0f1115);
|
|
}
|
|
body[data-ds-dark-theme] .me-pill-btn {
|
|
color: var(--dsw-alias-label-secondary, #999);
|
|
}
|
|
body[data-ds-dark-theme] .me-pill-btn:hover:not(:disabled) {
|
|
background: var(--dsw-alias-surface-l2, rgba(255, 255, 255, 0.08)) !important;
|
|
color: var(--dsw-alias-label-primary, #fff);
|
|
}
|
|
.me-pill-btn:disabled {
|
|
opacity: 0.4;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* HIDE THE NATIVE STANDALONE PLUS BUTTON SINCE IT IS RE-HOSTED IN THE PILL ISLAND */
|
|
button.uV2eYG_add, button[class*="InputBar_module_css_add"] {
|
|
display: none !important;
|
|
}
|
|
|
|
/* HIDE DEFAULT TRIGGER AND SHOW CODEX TRIGGER */
|
|
div[class*="_7KE1Ra_root"], div[class*="ModelSelect_module_css_root"] {
|
|
display: none !important;
|
|
}
|
|
|
|
/* CODEX-STYLE POPUP & SLIDER STYLES: SEAMLESS NO BORDER NORMALLY, PURE OUTLINE (NO BG TINT) ON HOVER/OPEN */
|
|
.me-codex-trigger {
|
|
box-sizing: border-box;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
height: 28px;
|
|
padding: 0 10px;
|
|
border-radius: 14px;
|
|
border: 1px solid transparent;
|
|
background: transparent !important;
|
|
color: var(--dsw-alias-label-primary, inherit);
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 140ms ease;
|
|
}
|
|
.me-codex-trigger:hover:not(:disabled),
|
|
.me-codex-trigger.active-open {
|
|
background: transparent !important;
|
|
border-color: var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.16)) !important;
|
|
}
|
|
body[data-ds-dark-theme] .me-codex-trigger:hover:not(:disabled),
|
|
body[data-ds-dark-theme] .me-codex-trigger.active-open {
|
|
border-color: var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16)) !important;
|
|
}
|
|
.me-codex-trigger-chevron {
|
|
color: var(--dsw-alias-label-tertiary, #888);
|
|
transition: transform 140ms ease;
|
|
}
|
|
.me-codex-trigger-chevron.open {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
/* POPUP CONTAINER (ADAPTIVE LIGHT & DARK) */
|
|
.me-codex-popup {
|
|
position: absolute;
|
|
bottom: calc(100% + 8px);
|
|
right: 0;
|
|
width: 290px;
|
|
padding: 16px;
|
|
border-radius: 16px;
|
|
background: var(--dsw-specific-menu, #ffffff);
|
|
border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.1));
|
|
box-shadow: var(--dsw-shadow-lv3, 0 8px 24px rgba(0, 0, 0, 0.12));
|
|
z-index: 1000;
|
|
user-select: none;
|
|
font-family: inherit;
|
|
color: var(--dsw-alias-label-primary, #0f1115);
|
|
}
|
|
body[data-ds-dark-theme] .me-codex-popup {
|
|
background: #1b1c21;
|
|
border-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.08));
|
|
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.55), 0 0 0 1px var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.08));
|
|
color: var(--dsw-alias-label-primary, #fff);
|
|
}
|
|
.me-codex-topbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 16px;
|
|
}
|
|
.me-codex-icon-btn {
|
|
background: none;
|
|
border: none;
|
|
color: var(--dsw-alias-label-secondary, #61666b);
|
|
cursor: pointer;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 4px;
|
|
border-radius: 6px;
|
|
transition: all 120ms ease;
|
|
}
|
|
.me-codex-icon-btn:hover {
|
|
color: var(--dsw-alias-label-primary, #0f1115);
|
|
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.06));
|
|
}
|
|
body[data-ds-dark-theme] .me-codex-icon-btn {
|
|
color: var(--dsw-alias-label-secondary, #8a8d9b);
|
|
}
|
|
body[data-ds-dark-theme] .me-codex-icon-btn:hover {
|
|
color: var(--dsw-alias-label-primary, #fff);
|
|
background: var(--dsw-alias-surface-l2, rgba(255, 255, 255, 0.06));
|
|
}
|
|
.me-codex-center {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
}
|
|
.me-codex-effort-title {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: var(--dsw-alias-button-primary-fill, #2563eb);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
.me-codex-model-name {
|
|
font-size: 12px;
|
|
color: var(--dsw-alias-label-secondary, #61666b);
|
|
margin-top: 1px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 3px;
|
|
}
|
|
.me-codex-model-name:hover {
|
|
color: var(--dsw-alias-label-primary, #0f1115);
|
|
}
|
|
body[data-ds-dark-theme] .me-codex-model-name {
|
|
color: var(--dsw-alias-label-secondary, #8a8d9b);
|
|
}
|
|
body[data-ds-dark-theme] .me-codex-model-name:hover {
|
|
color: var(--dsw-alias-label-primary, #e0e0e0);
|
|
}
|
|
|
|
/* SLIDER TRACK & THUMB (LIGHT & DARK ADAPTIVE) */
|
|
.me-slider-track-wrap {
|
|
position: relative;
|
|
height: 38px;
|
|
display: flex;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
.me-slider-track {
|
|
width: 100%;
|
|
height: 26px;
|
|
border-radius: 13px;
|
|
background: rgba(0, 0, 0, 0.07);
|
|
position: relative;
|
|
}
|
|
body[data-ds-dark-theme] .me-slider-track {
|
|
background: var(--dsw-alias-surface-l2, rgba(255, 255, 255, 0.08));
|
|
}
|
|
.me-slider-active-fill {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
background: linear-gradient(90deg, #2563eb, #38bdf8);
|
|
border-radius: 13px;
|
|
box-shadow: 0 0 10px rgba(56, 189, 248, 0.25);
|
|
transition: width 120ms ease;
|
|
}
|
|
.me-slider-dots-layer {
|
|
position: absolute;
|
|
left: 16px;
|
|
right: 16px;
|
|
top: 0;
|
|
bottom: 0;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
pointer-events: none;
|
|
}
|
|
.me-slider-dot {
|
|
width: 5px;
|
|
height: 5px;
|
|
border-radius: 50%;
|
|
background: rgba(0, 0, 0, 0.22);
|
|
transition: background 120ms ease;
|
|
}
|
|
body[data-ds-dark-theme] .me-slider-dot {
|
|
background: rgba(255, 255, 255, 0.35);
|
|
}
|
|
.me-slider-dot.lit {
|
|
background: #ffffff;
|
|
box-shadow: 0 0 4px rgba(255, 255, 255, 0.8);
|
|
}
|
|
.me-slider-thumb {
|
|
position: absolute;
|
|
top: 50%;
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 50%;
|
|
background: #ffffff;
|
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.04);
|
|
transform: translate(-50%, -50%) scale(1);
|
|
pointer-events: none;
|
|
transition: left 120ms ease, transform 160ms cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 160ms ease;
|
|
}
|
|
.me-slider-track-wrap:hover .me-slider-thumb {
|
|
transform: translate(-50%, -50%) scale(1.08);
|
|
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22), 0 0 10px rgba(56, 189, 248, 0.3);
|
|
}
|
|
.me-slider-track-wrap:active .me-slider-thumb {
|
|
transform: translate(-50%, -50%) scale(1.15);
|
|
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.28), 0 0 14px rgba(56, 189, 248, 0.4);
|
|
}
|
|
body[data-ds-dark-theme] .me-slider-thumb {
|
|
border: 1px solid rgba(56, 189, 248, 0.3);
|
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.45), 0 0 10px rgba(56, 189, 248, 0.3);
|
|
}
|
|
body[data-ds-dark-theme] .me-slider-track-wrap:hover .me-slider-thumb {
|
|
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5), 0 0 14px rgba(56, 189, 248, 0.45);
|
|
}
|
|
body[data-ds-dark-theme] .me-slider-track-wrap:active .me-slider-thumb {
|
|
box-shadow: 0 5px 18px rgba(0, 0, 0, 0.6), 0 0 18px rgba(56, 189, 248, 0.55);
|
|
}
|
|
|
|
/* MODEL PICKER LIST VIEW INSIDE POPUP (LIGHT & DARK ADAPTIVE) */
|
|
.me-model-picker-list {
|
|
max-height: 240px;
|
|
overflow-y: auto;
|
|
margin: -8px -8px 0;
|
|
padding: 6px 8px 10px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 3px;
|
|
}
|
|
.me-model-group-section {
|
|
margin-top: 10px;
|
|
padding-top: 8px;
|
|
border-top: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08));
|
|
}
|
|
body[data-ds-dark-theme] .me-model-group-section {
|
|
border-top-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.08));
|
|
}
|
|
.me-model-group-section:first-child {
|
|
margin-top: 0;
|
|
padding-top: 2px;
|
|
border-top: none;
|
|
}
|
|
.me-model-picker-group {
|
|
font-size: 11px;
|
|
color: #2563eb;
|
|
font-weight: 600;
|
|
padding: 4px 8px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
background: rgba(37, 99, 235, 0.08);
|
|
border-radius: 4px;
|
|
margin-bottom: 4px;
|
|
}
|
|
body[data-ds-dark-theme] .me-model-picker-group {
|
|
color: #60a5fa;
|
|
background: rgba(59, 130, 246, 0.08);
|
|
}
|
|
.me-model-picker-item {
|
|
box-sizing: border-box;
|
|
width: 100%;
|
|
padding: 7px 10px 7px 14px;
|
|
border-radius: 8px;
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--dsw-alias-label-primary, #0f1115);
|
|
font-size: 13px;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
transition: all 120ms ease;
|
|
}
|
|
.me-model-picker-item:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05));
|
|
color: var(--dsw-alias-label-primary, #000);
|
|
}
|
|
.me-model-picker-item.active {
|
|
background: rgba(37, 99, 235, 0.1);
|
|
color: #2563eb;
|
|
font-weight: 500;
|
|
}
|
|
body[data-ds-dark-theme] .me-model-picker-item {
|
|
color: var(--dsw-alias-label-primary, #e2e8f0);
|
|
}
|
|
body[data-ds-dark-theme] .me-model-picker-item:hover {
|
|
background: var(--dsw-alias-surface-l2, rgba(255, 255, 255, 0.06));
|
|
color: #fff;
|
|
}
|
|
body[data-ds-dark-theme] .me-model-picker-item.active {
|
|
background: rgba(56, 189, 248, 0.12);
|
|
color: #38bdf8;
|
|
}
|
|
\`;
|
|
if (typeof document !== "undefined" && !document.querySelector("style[data-plugin='dsh-model-enhancer']")) {
|
|
const tag = document.createElement("style");
|
|
tag.dataset.plugin = "dsh-model-enhancer";
|
|
tag.textContent = ENHANCER_CSS;
|
|
document.head.appendChild(tag);
|
|
}
|
|
|
|
const THINKING_LEVELS_ALL = ["minimal", "low", "medium", "high", "xhigh", "max"];
|
|
|
|
let modelsDevGlobalCache = null;
|
|
let modelsDevGlobalPromise = null;
|
|
|
|
async function fetchAllModelsDev() {
|
|
if (modelsDevGlobalCache) return modelsDevGlobalCache;
|
|
if (modelsDevGlobalPromise) return modelsDevGlobalPromise;
|
|
modelsDevGlobalPromise = (async () => {
|
|
const endpoints = ["/api/models-dev/models", "https://models.dev/api.json"];
|
|
let lastError = null;
|
|
for (const url of endpoints) {
|
|
try {
|
|
const res = await fetch(url);
|
|
if (res.ok) {
|
|
const json = await res.json();
|
|
const list = [];
|
|
for (const [pId, p] of Object.entries(json)) {
|
|
if (p && p.models) {
|
|
for (const [mId, m] of Object.entries(p.models)) {
|
|
list.push({
|
|
providerKey: pId,
|
|
providerName: p.name || pId,
|
|
id: m.id || mId,
|
|
rawKey: mId,
|
|
...m
|
|
});
|
|
}
|
|
}
|
|
}
|
|
modelsDevGlobalCache = list;
|
|
return list;
|
|
}
|
|
} catch (e) {
|
|
lastError = e;
|
|
}
|
|
}
|
|
throw lastError || new Error("Failed to load models.dev");
|
|
})();
|
|
try {
|
|
return await modelsDevGlobalPromise;
|
|
} finally {
|
|
modelsDevGlobalPromise = null;
|
|
}
|
|
}
|
|
|
|
function normModelKey(key) {
|
|
if (!key || typeof key !== "string") return "";
|
|
return key.toLowerCase()
|
|
.replace(/^[a-z0-9_-]+\\//, "")
|
|
.replace(/[-_.:/]/g, "")
|
|
.replace(/\\d{8}$/, "");
|
|
}
|
|
|
|
function findDevModel(queryId, allList) {
|
|
if (!queryId || !allList) return null;
|
|
const q = queryId.trim().toLowerCase();
|
|
const qNorm = normModelKey(q);
|
|
|
|
let m = allList.find((x) => x.id?.toLowerCase() === q || x.rawKey?.toLowerCase() === q);
|
|
if (m) return m;
|
|
|
|
m = allList.find((x) => x.id?.split("/").pop()?.toLowerCase() === q);
|
|
if (m) return m;
|
|
|
|
m = allList.find((x) => normModelKey(x.id) === qNorm || normModelKey(x.name || "") === qNorm);
|
|
if (m) return m;
|
|
|
|
m = allList.find((x) => {
|
|
const n = normModelKey(x.id);
|
|
return n.length >= 3 && (n.includes(qNorm) || qNorm.includes(n));
|
|
});
|
|
return m || null;
|
|
}
|
|
|
|
function PrefillModal({ isOpen, onClose, original, suggested, onApply, t }) {
|
|
if (!isOpen || !suggested) return null;
|
|
const [selectedFields, setSelectedFields] = (0, react.useState)({
|
|
name: Boolean(suggested.name && suggested.name !== original.name),
|
|
contextWindow: Boolean(suggested.contextWindow && suggested.contextWindow !== original.contextWindow),
|
|
maxTokens: Boolean(suggested.maxTokens && suggested.maxTokens !== original.maxTokens),
|
|
input: Boolean(suggested.hasImage !== (Array.isArray(original.input) && original.input.includes("image"))),
|
|
reasoningEfforts: Boolean(suggested.hasReasoning !== (Boolean(original.reasoningEfforts) && original.reasoningEfforts !== false))
|
|
});
|
|
|
|
const toggle = (f) => setSelectedFields((prev) => ({ ...prev, [f]: !prev[f] }));
|
|
|
|
const handleApply = () => {
|
|
const p = {};
|
|
if (selectedFields.name && suggested.name) p.name = suggested.name;
|
|
if (selectedFields.contextWindow && suggested.contextWindow) p.contextWindow = suggested.contextWindow;
|
|
if (selectedFields.maxTokens && suggested.maxTokens) p.maxTokens = suggested.maxTokens;
|
|
if (selectedFields.input) p.input = suggested.hasImage ? ["text", "image"] : ["text"];
|
|
if (selectedFields.reasoningEfforts) {
|
|
p.reasoningEfforts = suggested.hasReasoning ? (suggested.reasoningLevels || { off: "none", low: "low", medium: "medium", high: "high" }) : false;
|
|
}
|
|
onApply(p);
|
|
onClose();
|
|
};
|
|
|
|
const origImg = Array.isArray(original.input) && original.input.includes("image") ? t("visionSupported") : t("visionOnlyText");
|
|
const suggImg = suggested.hasImage ? t("visionSupported") : t("visionOnlyText");
|
|
|
|
const origReason = original.reasoningEfforts === false || !original.reasoningEfforts
|
|
? t("thinkingOff")
|
|
: Object.keys(original.reasoningEfforts).filter((k) => k !== "off").join("/") || t("thinkingOn");
|
|
const suggReason = suggested.hasReasoning
|
|
? Object.keys(suggested.reasoningLevels || {}).filter((k) => k !== "off").join("/") || t("thinkingOn")
|
|
: t("thinkingOff");
|
|
|
|
return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
open: isOpen,
|
|
onClose,
|
|
title: t("modalTitle"),
|
|
closeLabel: t("cancel"),
|
|
className: ModelsSection_module_css_default["fetchDialog"],
|
|
footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, {
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
variant: "outline",
|
|
onClick: onClose,
|
|
children: t("cancel")
|
|
}),
|
|
(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
variant: "primary",
|
|
onClick: handleApply,
|
|
children: t("applySelected")
|
|
})
|
|
]
|
|
}),
|
|
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
style: { padding: "4px 0", color: "var(--dsw-alias-label-primary)" },
|
|
children: [
|
|
(0, react_jsx_runtime.jsxs)("p", {
|
|
style: { margin: "0 0 14px", fontSize: "13px", color: "var(--dsw-alias-label-secondary)" },
|
|
children: [
|
|
t("matchedModelLabel"),
|
|
(0, react_jsx_runtime.jsx)("strong", {
|
|
style: { color: "var(--dsw-alias-label-primary)", fontWeight: 600 },
|
|
children: suggested.sourceModel.name || suggested.sourceModel.id
|
|
}),
|
|
\` (\${suggested.sourceModel.providerName || suggested.sourceModel.providerKey})\`
|
|
]
|
|
}),
|
|
(0, react_jsx_runtime.jsxs)("table", {
|
|
className: "me-diff-table",
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("thead", {
|
|
children: (0, react_jsx_runtime.jsxs)("tr", {
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("th", { style: { width: "42px", textAlign: "center" }, children: t("thSelect") }),
|
|
(0, react_jsx_runtime.jsx)("th", { style: { width: "120px" }, children: t("thField") }),
|
|
(0, react_jsx_runtime.jsx)("th", { children: t("thCurrent") }),
|
|
(0, react_jsx_runtime.jsx)("th", { children: t("thRecommended") })
|
|
]
|
|
})
|
|
}),
|
|
(0, react_jsx_runtime.jsxs)("tbody", {
|
|
children: [
|
|
(0, react_jsx_runtime.jsxs)("tr", {
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("td", { style: { textAlign: "center" }, children: (0, react_jsx_runtime.jsx)("input", { type: "checkbox", checked: selectedFields.name, onChange: () => toggle("name") }) }),
|
|
(0, react_jsx_runtime.jsx)("td", { children: t("fieldName") }),
|
|
(0, react_jsx_runtime.jsx)("td", { children: original.name || t("fieldUnset") }),
|
|
(0, react_jsx_runtime.jsx)("td", { className: "me-val-new", children: suggested.name || "-" })
|
|
]
|
|
}),
|
|
(0, react_jsx_runtime.jsxs)("tr", {
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("td", { style: { textAlign: "center" }, children: (0, react_jsx_runtime.jsx)("input", { type: "checkbox", checked: selectedFields.contextWindow, onChange: () => toggle("contextWindow") }) }),
|
|
(0, react_jsx_runtime.jsx)("td", { children: t("fieldContext") }),
|
|
(0, react_jsx_runtime.jsx)("td", { children: formatCapacity(original.contextWindow) || t("fieldDefault") }),
|
|
(0, react_jsx_runtime.jsx)("td", { className: "me-val-new", children: formatCapacity(suggested.contextWindow) || "-" })
|
|
]
|
|
}),
|
|
(0, react_jsx_runtime.jsxs)("tr", {
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("td", { style: { textAlign: "center" }, children: (0, react_jsx_runtime.jsx)("input", { type: "checkbox", checked: selectedFields.maxTokens, onChange: () => toggle("maxTokens") }) }),
|
|
(0, react_jsx_runtime.jsx)("td", { children: t("fieldMaxTokens") }),
|
|
(0, react_jsx_runtime.jsx)("td", { children: formatCapacity(original.maxTokens) || t("fieldDefault") }),
|
|
(0, react_jsx_runtime.jsx)("td", { className: "me-val-new", children: formatCapacity(suggested.maxTokens) || "-" })
|
|
]
|
|
}),
|
|
(0, react_jsx_runtime.jsxs)("tr", {
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("td", { style: { textAlign: "center" }, children: (0, react_jsx_runtime.jsx)("input", { type: "checkbox", checked: selectedFields.input, onChange: () => toggle("input") }) }),
|
|
(0, react_jsx_runtime.jsx)("td", { children: t("fieldInput") }),
|
|
(0, react_jsx_runtime.jsx)("td", { children: origImg }),
|
|
(0, react_jsx_runtime.jsx)("td", { className: "me-val-new", children: suggImg })
|
|
]
|
|
}),
|
|
(0, react_jsx_runtime.jsxs)("tr", {
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("td", { style: { textAlign: "center" }, children: (0, react_jsx_runtime.jsx)("input", { type: "checkbox", checked: selectedFields.reasoningEfforts, onChange: () => toggle("reasoningEfforts") }) }),
|
|
(0, react_jsx_runtime.jsx)("td", { children: t("fieldReasoning") }),
|
|
(0, react_jsx_runtime.jsx)("td", { children: origReason }),
|
|
(0, react_jsx_runtime.jsx)("td", { className: "me-val-new", children: suggReason })
|
|
]
|
|
})
|
|
]
|
|
})
|
|
]
|
|
})
|
|
]
|
|
})
|
|
});
|
|
}
|
|
|
|
function ModelListEditor(props) {
|
|
const { models, onChange, probe, api, t, disabled } = props;
|
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
const [failure, setFailure] = (0, react.useState)(void 0);
|
|
const [candidates, setCandidates] = (0, react.useState)(void 0);
|
|
const [picked, setPicked] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
const [expanded, setExpanded] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
const [editing, setEditing] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
|
|
// Enhancer states
|
|
const [matchingIndex, setMatchingIndex] = (0, react.useState)(null);
|
|
const [confirmModalData, setConfirmModalData] = (0, react.useState)(null);
|
|
const [batchMatching, setBatchMatching] = (0, react.useState)(false);
|
|
const [notice, setNotice] = (0, react.useState)(null);
|
|
|
|
const bufferKey = (index, field) => \`\${String(index)}:\${field}\`;
|
|
const editCapacity = (index, field, text) => {
|
|
setEditing((current) => new Map(current).set(bufferKey(index, field), text));
|
|
patch(index, { [field]: parseCapacity(text) });
|
|
};
|
|
const capacityText = (model, index, field) => editing.get(bufferKey(index, field)) ?? capacitySpelling(numberOf(model, field));
|
|
|
|
const reindexOnRemove = (current, index) => {
|
|
const next = /* @__PURE__ */ new Map();
|
|
for (const [key, value] of current) {
|
|
const at = Number(key.slice(0, key.indexOf(":")));
|
|
if (at === index) continue;
|
|
next.set(at > index ? key.replace(/^\\d+/, String(at - 1)) : key, value);
|
|
}
|
|
return next;
|
|
};
|
|
|
|
const toggleExpanded = (index) => {
|
|
setExpanded((current) => {
|
|
const next = new Set(current);
|
|
if (!next.delete(index)) next.add(index);
|
|
return next;
|
|
});
|
|
};
|
|
|
|
const patch = (index, next) => {
|
|
onChange(models.map((model, at) => {
|
|
if (at !== index) return model;
|
|
const merged = { ...model, ...next };
|
|
const cleared = new Set(Object.entries(next).filter(([, value]) => value === void 0 || value === "").map(([key]) => key));
|
|
return Object.fromEntries(Object.entries(merged).filter(([key]) => !cleared.has(key)));
|
|
}));
|
|
};
|
|
|
|
const fetchModels = async () => {
|
|
setBusy(true);
|
|
setFailure(void 0);
|
|
try {
|
|
const response = await api.llm.discoverModels({
|
|
settingsNs: probe.settingsNs,
|
|
...probe.provider === void 0 ? {} : { provider: probe.provider },
|
|
...probe.baseURL === void 0 || probe.baseURL.length === 0 ? {} : { baseURL: probe.baseURL },
|
|
...probe.api === void 0 ? {} : { api: probe.api },
|
|
...probe.apiKey === void 0 ? {} : { apiKey: probe.apiKey }
|
|
});
|
|
if (!response.result.ok) {
|
|
setFailure(response.result.error.message);
|
|
return;
|
|
}
|
|
const found = response.result.value.models;
|
|
if (found.length === 0) {
|
|
setFailure(t("fetchEmpty"));
|
|
return;
|
|
}
|
|
const known = new Set(models.map((model) => textOf(model, "id")));
|
|
setCandidates(found);
|
|
setPicked(new Set(found.filter((model) => !known.has(model.id)).map((model) => model.id)));
|
|
} catch (error) {
|
|
setFailure(messageOf(error));
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
const closePicker = () => {
|
|
setCandidates(void 0);
|
|
setPicked(/* @__PURE__ */ new Set());
|
|
};
|
|
|
|
const adoptPicked = () => {
|
|
if (candidates === void 0) return;
|
|
const byId = new Map(models.map((model) => [textOf(model, "id"), model]));
|
|
for (const candidate of candidates) {
|
|
if (!picked.has(candidate.id)) continue;
|
|
byId.set(candidate.id, byId.get(candidate.id) ?? adopt(candidate));
|
|
}
|
|
onChange([...byId.values()]);
|
|
closePicker();
|
|
};
|
|
|
|
const toggle = (id) => {
|
|
setPicked((current) => {
|
|
const next = new Set(current);
|
|
if (!next.delete(id)) next.add(id);
|
|
return next;
|
|
});
|
|
};
|
|
|
|
const activeCandidates = candidates ?? [];
|
|
const allCandidatesPicked = activeCandidates.length > 0 && activeCandidates.every((candidate) => picked.has(candidate.id));
|
|
const toggleAllCandidates = () => {
|
|
setPicked((current) => {
|
|
return activeCandidates.every((candidate) => current.has(candidate.id)) ? /* @__PURE__ */ new Set() : new Set(activeCandidates.map((candidate) => candidate.id));
|
|
});
|
|
};
|
|
|
|
// Enhancer models.dev single match action
|
|
const handleSingleDevMatch = async (model, index) => {
|
|
if (!model.id) {
|
|
setNotice(t("singleFillRequireId"));
|
|
return;
|
|
}
|
|
setMatchingIndex(index);
|
|
setNotice(null);
|
|
try {
|
|
const allDevModels = await fetchAllModelsDev();
|
|
const matched = findDevModel(model.id, allDevModels);
|
|
if (!matched) {
|
|
setNotice(t("singleFillNotFound", { id: model.id }));
|
|
return;
|
|
}
|
|
const hasImage = Boolean(matched.modalities?.input?.includes("image"));
|
|
const hasReasoning = Boolean(matched.reasoning);
|
|
let reasoningLevels = null;
|
|
|
|
if (hasReasoning) {
|
|
reasoningLevels = { off: "none" };
|
|
let added = false;
|
|
if (Array.isArray(matched.reasoning_options)) {
|
|
for (const opt of matched.reasoning_options) {
|
|
if (opt.type === "effort" && Array.isArray(opt.values)) {
|
|
for (const val of opt.values) {
|
|
if (val === "none" || val === "off") reasoningLevels.off = "none";
|
|
else if (THINKING_LEVELS_ALL.includes(val)) {
|
|
reasoningLevels[val] = val;
|
|
added = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!added) {
|
|
reasoningLevels.low = "low";
|
|
reasoningLevels.medium = "medium";
|
|
reasoningLevels.high = "high";
|
|
}
|
|
}
|
|
|
|
setConfirmModalData({
|
|
index,
|
|
original: model,
|
|
suggested: {
|
|
sourceModel: matched,
|
|
name: matched.name || model.name,
|
|
contextWindow: matched.limit?.context || model.contextWindow,
|
|
maxTokens: matched.limit?.output || model.maxTokens,
|
|
hasImage,
|
|
hasReasoning,
|
|
reasoningLevels
|
|
}
|
|
});
|
|
} catch (err) {
|
|
setNotice(t("singleFillFetchFailed", { message: err.message || err }));
|
|
} finally {
|
|
setMatchingIndex(null);
|
|
}
|
|
};
|
|
|
|
// Enhancer models.dev batch match action
|
|
const handleBatchDevMatch = async () => {
|
|
if (!models || models.length === 0) return;
|
|
setBatchMatching(true);
|
|
setNotice(null);
|
|
try {
|
|
const allDevModels = await fetchAllModelsDev();
|
|
let count = 0;
|
|
const cleanedModels = models.filter((m) => m && typeof m.id === "string" && m.id.trim().length > 0);
|
|
if (cleanedModels.length === 0) {
|
|
setNotice(t("fillNoIdError"));
|
|
return;
|
|
}
|
|
|
|
const nextModels = cleanedModels.map((m) => {
|
|
const matched = findDevModel(m.id, allDevModels);
|
|
if (!matched) return m;
|
|
count++;
|
|
const hasImage = Boolean(matched.modalities?.input?.includes("image"));
|
|
const hasReasoning = Boolean(matched.reasoning);
|
|
let reasoningLevels = undefined;
|
|
|
|
if (hasReasoning) {
|
|
reasoningLevels = { off: "none" };
|
|
let added = false;
|
|
if (Array.isArray(matched.reasoning_options)) {
|
|
for (const opt of matched.reasoning_options) {
|
|
if (opt.type === "effort" && Array.isArray(opt.values)) {
|
|
for (const val of opt.values) {
|
|
if (val === "none" || val === "off") reasoningLevels.off = "none";
|
|
else if (THINKING_LEVELS_ALL.includes(val)) {
|
|
reasoningLevels[val] = val;
|
|
added = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!added) {
|
|
reasoningLevels.low = "low";
|
|
reasoningLevels.medium = "medium";
|
|
reasoningLevels.high = "high";
|
|
}
|
|
}
|
|
|
|
return {
|
|
...m,
|
|
name: m.name || matched.name,
|
|
contextWindow: m.contextWindow || matched.limit?.context,
|
|
maxTokens: m.maxTokens || matched.limit?.output,
|
|
input: m.input || (hasImage ? ["text", "image"] : ["text"]),
|
|
reasoningEfforts: m.reasoningEfforts !== undefined ? m.reasoningEfforts : (hasReasoning ? reasoningLevels : false)
|
|
};
|
|
});
|
|
onChange(nextModels);
|
|
setNotice(t("fillSuccessNotice", { count, total: cleanedModels.length }));
|
|
} catch (err) {
|
|
setNotice(t("fillFailedNotice", { message: err.message || err }));
|
|
} finally {
|
|
setBatchMatching(false);
|
|
}
|
|
};
|
|
|
|
const askable = probe.provider !== void 0 || probe.baseURL !== void 0 && probe.baseURL.length > 0;
|
|
const validationErr = validateDeepSeekModels(models);
|
|
|
|
return (0, react_jsx_runtime.jsxs)("section", {
|
|
className: ModelsSection_module_css_default["modelCatalog"],
|
|
"aria-label": t("models"),
|
|
children: [
|
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
className: ModelsSection_module_css_default["modelListHead"],
|
|
children: [
|
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
className: ModelsSection_module_css_default["modelCatalogHeading"],
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("span", {
|
|
className: ModelsSection_module_css_default["modelCatalogTitle"],
|
|
children: t("models")
|
|
}),
|
|
props.overridden === void 0 ? null : (0, react_jsx_runtime.jsx)("span", {
|
|
className: ModelsSection_module_css_default["modelCatalogMeta"],
|
|
children: props.overridden ? t("modelsCustomized") : t("modelsInherited")
|
|
})
|
|
]
|
|
}),
|
|
props.overridden === true && props.onReset !== void 0 ? (0, react_jsx_runtime.jsx)("button", {
|
|
type: "button",
|
|
className: ModelsSection_module_css_default["linkButton"],
|
|
disabled,
|
|
onClick: props.onReset,
|
|
children: t("resetModels")
|
|
}) : null,
|
|
(0, react_jsx_runtime.jsx)("button", {
|
|
type: "button",
|
|
className: ModelsSection_module_css_default["linkButton"],
|
|
disabled: disabled || busy || !askable || props.probeBlocked !== void 0,
|
|
title: props.probeBlocked !== void 0 ? t(props.probeBlocked) : askable ? void 0 : t("fetchNeedsBaseUrl"),
|
|
onClick: () => {
|
|
fetchModels();
|
|
},
|
|
children: busy ? t("fetching") : t("fetchModels")
|
|
}),
|
|
(0, react_jsx_runtime.jsx)("button", {
|
|
type: "button",
|
|
className: ModelsSection_module_css_default["linkButton"],
|
|
disabled: disabled || batchMatching || models.length === 0,
|
|
onClick: handleBatchDevMatch,
|
|
title: t("batchBtnTitle"),
|
|
children: batchMatching ? t("batchBtnBusy") : t("batchBtn")
|
|
})
|
|
]
|
|
}),
|
|
notice ? (0, react_jsx_runtime.jsx)("p", {
|
|
className: ModelsSection_module_css_default["advancedHint"],
|
|
style: {
|
|
color: notice.includes("失败") || notice.includes("Failed") || notice.includes("failed") ? "var(--dsw-alias-state-error-primary)" : "var(--dsw-alias-state-success-primary)",
|
|
margin: "4px 0 8px"
|
|
},
|
|
children: notice
|
|
}) : null,
|
|
validationErr ? (0, react_jsx_runtime.jsxs)("p", {
|
|
className: ModelsSection_module_css_default["error"],
|
|
style: { margin: "4px 0 8px" },
|
|
children: [
|
|
t("saveBlockedHint", { index: validationErr.index + 1, reason: t(validationErr.key) })
|
|
]
|
|
}) : null,
|
|
models.length === 0 ? (0, react_jsx_runtime.jsx)("p", {
|
|
className: ModelsSection_module_css_default["modelEmpty"],
|
|
children: t("modelsEmpty")
|
|
}) : null,
|
|
models.map((model, index) => {
|
|
const hasImage = Array.isArray(model.input) && model.input.includes("image");
|
|
const reasoningEnabled = Boolean(model.reasoningEfforts) && model.reasoningEfforts !== false;
|
|
const activeLevels = reasoningEnabled && typeof model.reasoningEfforts === "object"
|
|
? Object.keys(model.reasoningEfforts).filter((k) => k !== "off")
|
|
: [];
|
|
|
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
className: ModelsSection_module_css_default["modelEntry"],
|
|
children: [
|
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
className: ModelsSection_module_css_default["modelRow"],
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("input", {
|
|
className: ModelsSection_module_css_default["input"],
|
|
type: "text",
|
|
value: textOf(model, "id"),
|
|
placeholder: t("modelId"),
|
|
"aria-label": \`\${t("modelId")} \${index + 1}\`,
|
|
disabled,
|
|
onChange: (event) => {
|
|
patch(index, { id: event.target.value });
|
|
}
|
|
}),
|
|
(0, react_jsx_runtime.jsx)("input", {
|
|
className: ModelsSection_module_css_default["input"],
|
|
type: "text",
|
|
value: textOf(model, "name"),
|
|
placeholder: t("modelName"),
|
|
"aria-label": \`\${t("modelName")} \${index + 1}\`,
|
|
disabled,
|
|
onChange: (event) => {
|
|
patch(index, { name: event.target.value === "" ? void 0 : event.target.value });
|
|
}
|
|
}),
|
|
(0, react_jsx_runtime.jsx)("button", {
|
|
type: "button",
|
|
className: ModelsSection_module_css_default["iconButton"],
|
|
"aria-label": \`\${t("modelAdvanced")} \${index + 1}\`,
|
|
"aria-expanded": expanded.has(index),
|
|
title: t("modelAdvanced"),
|
|
onClick: () => {
|
|
toggleExpanded(index);
|
|
},
|
|
children: (0, react_jsx_runtime.jsx)(IconChevron, { open: expanded.has(index) })
|
|
}),
|
|
(0, react_jsx_runtime.jsx)("button", {
|
|
type: "button",
|
|
className: \`\${ModelsSection_module_css_default["iconButton"]} \${ModelsSection_module_css_default["iconButtonDanger"]}\`,
|
|
"aria-label": \`\${t("removeModel")} \${index + 1}\`,
|
|
title: t("removeModel"),
|
|
disabled,
|
|
onClick: () => {
|
|
onChange(models.filter((_model, at) => at !== index));
|
|
setExpanded((current) => {
|
|
const next = /* @__PURE__ */ new Set();
|
|
for (const at of current) if (at < index) next.add(at);
|
|
else if (at > index) next.add(at - 1);
|
|
return next;
|
|
});
|
|
setEditing((current) => reindexOnRemove(current, index));
|
|
},
|
|
children: (0, react_jsx_runtime.jsx)(IconTrash, {})
|
|
}),
|
|
(0, react_jsx_runtime.jsx)("button", {
|
|
type: "button",
|
|
className: "me-action-btn",
|
|
disabled: disabled || matchingIndex === index || !model.id,
|
|
title: t("singleBtnTitle"),
|
|
onClick: () => {
|
|
handleSingleDevMatch(model, index);
|
|
},
|
|
children: matchingIndex === index ? t("singleBtnBusy") : t("singleBtn")
|
|
})
|
|
]
|
|
}),
|
|
(hasImage || reasoningEnabled) ? (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "me-model-badges-row",
|
|
children: [
|
|
hasImage ? (0, react_jsx_runtime.jsx)("span", {
|
|
className: "me-badge me-badge-vision",
|
|
title: t("badgeVisionTitle"),
|
|
children: t("badgeVision")
|
|
}) : null,
|
|
reasoningEnabled ? (0, react_jsx_runtime.jsx)("span", {
|
|
className: "me-badge me-badge-thinking",
|
|
title: t("badgeThinkingTitle", { levels: activeLevels.join("/") || t("defaultEffort") }),
|
|
children: t("badgeThinking") + (activeLevels.length ? \` · \${activeLevels.join("/")}\` : "")
|
|
}) : null
|
|
]
|
|
}) : null,
|
|
expanded.has(index) ? (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "me-advanced-container",
|
|
children: [
|
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
style: { display: "flex", gap: "12px" },
|
|
children: [
|
|
(0, react_jsx_runtime.jsxs)("label", {
|
|
className: ModelsSection_module_css_default["modelField"],
|
|
style: { flex: 1 },
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("span", {
|
|
className: ModelsSection_module_css_default["modelFieldLabel"],
|
|
children: t("modelContextWindow")
|
|
}),
|
|
(0, react_jsx_runtime.jsx)("input", {
|
|
className: ModelsSection_module_css_default["input"],
|
|
type: "text",
|
|
inputMode: "numeric",
|
|
value: capacityText(model, index, "contextWindow"),
|
|
placeholder: CAPACITY_HINT.contextWindow,
|
|
"aria-label": \`\${t("modelContextWindow")} \${index + 1}\`,
|
|
disabled,
|
|
onChange: (event) => {
|
|
editCapacity(index, "contextWindow", event.target.value);
|
|
}
|
|
})
|
|
]
|
|
}),
|
|
(0, react_jsx_runtime.jsxs)("label", {
|
|
className: ModelsSection_module_css_default["modelField"],
|
|
style: { flex: 1 },
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("span", {
|
|
className: ModelsSection_module_css_default["modelFieldLabel"],
|
|
children: t("modelMaxTokens")
|
|
}),
|
|
(0, react_jsx_runtime.jsx)("input", {
|
|
className: ModelsSection_module_css_default["input"],
|
|
type: "text",
|
|
inputMode: "numeric",
|
|
value: capacityText(model, index, "maxTokens"),
|
|
placeholder: CAPACITY_HINT.maxTokens,
|
|
"aria-label": \`\${t("modelMaxTokens")} \${index + 1}\`,
|
|
disabled,
|
|
onChange: (event) => {
|
|
editCapacity(index, "maxTokens", event.target.value);
|
|
}
|
|
})
|
|
]
|
|
})
|
|
]
|
|
}),
|
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
className: "me-advanced-row",
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("div", {
|
|
className: "me-section-title",
|
|
children: t("modalitiesTitle")
|
|
}),
|
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
className: "me-checkbox-group",
|
|
children: [
|
|
(0, react_jsx_runtime.jsxs)("label", {
|
|
className: "me-checkbox-label",
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("input", {
|
|
type: "checkbox",
|
|
checked: true,
|
|
disabled: true
|
|
}),
|
|
t("modalityText")
|
|
]
|
|
}),
|
|
(0, react_jsx_runtime.jsxs)("label", {
|
|
className: "me-checkbox-label",
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("input", {
|
|
type: "checkbox",
|
|
checked: hasImage,
|
|
disabled,
|
|
onChange: (e) => {
|
|
const currentInput = Array.isArray(model.input) ? [...model.input] : ["text"];
|
|
if (e.target.checked) {
|
|
if (!currentInput.includes("image")) currentInput.push("image");
|
|
} else {
|
|
const at = currentInput.indexOf("image");
|
|
if (at !== -1) currentInput.splice(at, 1);
|
|
}
|
|
patch(index, { input: currentInput });
|
|
}
|
|
}),
|
|
t("modalityVision")
|
|
]
|
|
})
|
|
]
|
|
})
|
|
]
|
|
}),
|
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
className: "me-advanced-row",
|
|
children: [
|
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
className: "me-advanced-row-header",
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("div", {
|
|
className: "me-section-title",
|
|
children: t("reasoningTitle")
|
|
}),
|
|
(0, react_jsx_runtime.jsxs)("label", {
|
|
className: "me-checkbox-label",
|
|
style: { fontSize: "12px" },
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("input", {
|
|
type: "checkbox",
|
|
checked: reasoningEnabled,
|
|
disabled,
|
|
onChange: (e) => {
|
|
if (e.target.checked) {
|
|
patch(index, {
|
|
reasoningEfforts: {
|
|
off: "none",
|
|
low: "low",
|
|
medium: "medium",
|
|
high: "high"
|
|
}
|
|
});
|
|
} else {
|
|
patch(index, { reasoningEfforts: false });
|
|
}
|
|
}
|
|
}),
|
|
t("enableReasoning")
|
|
]
|
|
})
|
|
]
|
|
}),
|
|
reasoningEnabled ? (0, react_jsx_runtime.jsx)("div", {
|
|
className: "me-checkbox-group",
|
|
style: { gap: "6px" },
|
|
children: THINKING_LEVELS_ALL.map((lvl) => {
|
|
const map = typeof model.reasoningEfforts === "object" && model.reasoningEfforts !== null ? model.reasoningEfforts : {};
|
|
const active = map[lvl] !== undefined;
|
|
|
|
return (0, react_jsx_runtime.jsxs)("button", {
|
|
type: "button",
|
|
className: \`me-effort-tag \${active ? "active" : ""}\`,
|
|
disabled,
|
|
onClick: () => {
|
|
const nextMap = { ...map };
|
|
if (active) {
|
|
delete nextMap[lvl];
|
|
} else {
|
|
nextMap[lvl] = lvl;
|
|
}
|
|
patch(index, { reasoningEfforts: nextMap });
|
|
},
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("span", {
|
|
style: {
|
|
display: "inline-block",
|
|
width: "6px",
|
|
height: "6px",
|
|
borderRadius: "50%",
|
|
background: active ? "#9d7dfa" : "var(--dsw-alias-label-tertiary, #666)"
|
|
}
|
|
}),
|
|
lvl
|
|
]
|
|
}, lvl);
|
|
})
|
|
}) : null
|
|
]
|
|
})
|
|
]
|
|
}) : null
|
|
]
|
|
}, index);
|
|
}),
|
|
(0, react_jsx_runtime.jsx)("button", {
|
|
type: "button",
|
|
className: ModelsSection_module_css_default["addModelButton"],
|
|
disabled,
|
|
onClick: () => {
|
|
onChange([...models, { id: "" }]);
|
|
},
|
|
children: t("addModel")
|
|
}),
|
|
failure !== void 0 ? (0, react_jsx_runtime.jsx)("p", {
|
|
className: ModelsSection_module_css_default["error"],
|
|
children: failure
|
|
}) : null,
|
|
(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
open: candidates !== void 0,
|
|
onClose: closePicker,
|
|
title: t("fetchTitle"),
|
|
closeLabel: t("close"),
|
|
description: t("fetchDescription"),
|
|
className: ModelsSection_module_css_default["fetchDialog"],
|
|
footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
variant: "outline",
|
|
onClick: closePicker,
|
|
children: t("cancel")
|
|
}), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
variant: "outline",
|
|
onClick: adoptPicked,
|
|
children: t("fetchAdopt")
|
|
})] }),
|
|
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
className: ModelsSection_module_css_default["candidateActions"],
|
|
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
variant: "outline",
|
|
onClick: toggleAllCandidates,
|
|
children: t(allCandidatesPicked ? "fetchDeselectAll" : "fetchSelectAll")
|
|
})
|
|
}), (0, react_jsx_runtime.jsx)("div", {
|
|
className: ModelsSection_module_css_default["candidateList"],
|
|
children: (candidates ?? []).map((candidate) => (0, react_jsx_runtime.jsx)("div", {
|
|
className: ModelsSection_module_css_default["candidate"],
|
|
children: (0, react_jsx_runtime.jsxs)("label", {
|
|
className: ModelsSection_module_css_default["candidateLabel"],
|
|
children: [(0, react_jsx_runtime.jsx)("input", {
|
|
type: "checkbox",
|
|
checked: picked.has(candidate.id),
|
|
onChange: () => {
|
|
toggle(candidate.id);
|
|
}
|
|
}), (0, react_jsx_runtime.jsx)("span", {
|
|
className: ModelsSection_module_css_default["candidateId"],
|
|
children: candidate.id
|
|
})]
|
|
})
|
|
}, candidate.id))
|
|
})]
|
|
}),
|
|
confirmModalData ? (0, react_jsx_runtime.jsx)(PrefillModal, {
|
|
isOpen: true,
|
|
original: confirmModalData.original,
|
|
suggested: confirmModalData.suggested,
|
|
t,
|
|
onClose: () => setConfirmModalData(null),
|
|
onApply: (patchData) => {
|
|
patch(confirmModalData.index, patchData);
|
|
setNotice(t("singleFillApplied", { id: confirmModalData.original.id }));
|
|
}
|
|
}) : null
|
|
]
|
|
});
|
|
}
|
|
//#endregion
|
|
`;
|
|
|
|
// Splice ModelListEditor
|
|
const startTag = "//#region lib/types/client/ModelListEditor.js";
|
|
const endTag = "//#endregion\n\t\t//#region lib/types/client/CustomProviderCard.js";
|
|
|
|
const startIndex = origCode.indexOf(startTag);
|
|
const endIndex = origCode.indexOf(endTag);
|
|
|
|
if (startIndex === -1 || endIndex === -1) {
|
|
console.error("Failed to find replacement markers!");
|
|
process.exit(1);
|
|
}
|
|
|
|
let finalCode = origCode.slice(0, startIndex) + enhancedModelListEditorCode.trim() + "\n\t\t" + origCode.slice(endIndex);
|
|
|
|
// Update module loader id
|
|
finalCode = finalCode.replace('id: "@deepseek-ai/dsh-client-ui-settings-models"', 'id: "dsh-plugin-model-enhancer"');
|
|
|
|
// Extra components: Paperclip & Codex Slider with dynamic language detection
|
|
const extraComponentsCode = `
|
|
// Helper to detect current language ("zh" or "en")
|
|
function isZhLocale() {
|
|
return typeof document !== "undefined" && (document.documentElement.lang || "").toLowerCase().startsWith("zh");
|
|
}
|
|
|
|
// Paperclip file upload button for conversation.input.left
|
|
function PaperclipUploadButton(props) {
|
|
const fileInputRef = (0, react.useRef)(null);
|
|
const isZh = isZhLocale();
|
|
const onButtonClick = () => {
|
|
fileInputRef.current?.click();
|
|
};
|
|
const onFileChange = async (e) => {
|
|
const fileList = Array.from(e.target.files || []);
|
|
if (fileList.length === 0) return;
|
|
e.target.value = "";
|
|
|
|
const textarea = document.querySelector("textarea");
|
|
const isZh = isZhLocale();
|
|
|
|
const imageFiles = [];
|
|
const textCodeFiles = [];
|
|
const binaryUploadFiles = [];
|
|
|
|
const CODE_EXT_MAP = {
|
|
js: "javascript", jsx: "javascript", mjs: "javascript", cjs: "javascript",
|
|
ts: "typescript", tsx: "typescript",
|
|
py: "python",
|
|
json: "json",
|
|
md: "markdown", markdown: "markdown",
|
|
html: "html", htm: "html",
|
|
css: "css", scss: "scss", less: "less",
|
|
sh: "bash", bash: "bash", zsh: "bash",
|
|
yaml: "yaml", yml: "yaml",
|
|
xml: "xml", svg: "xml",
|
|
sql: "sql",
|
|
c: "c", h: "c", cpp: "cpp", hpp: "cpp", cc: "cpp",
|
|
rs: "rust",
|
|
go: "go",
|
|
java: "java",
|
|
kt: "kotlin",
|
|
rb: "ruby",
|
|
php: "php",
|
|
lua: "lua",
|
|
toml: "toml", ini: "ini", conf: "ini", env: "ini",
|
|
txt: "text", log: "text", csv: "csv"
|
|
};
|
|
|
|
for (const file of fileList) {
|
|
const ext = (file.name.split(".").pop() || "").toLowerCase();
|
|
const isImage = file.type.startsWith("image/") || ["png", "jpg", "jpeg", "webp", "gif", "bmp"].includes(ext);
|
|
|
|
if (isImage) {
|
|
imageFiles.push(file);
|
|
} else if ((CODE_EXT_MAP[ext] || file.type.startsWith("text/")) && file.size <= 300 * 1024) {
|
|
textCodeFiles.push({ file, ext: CODE_EXT_MAP[ext] || "text" });
|
|
} else {
|
|
binaryUploadFiles.push(file);
|
|
}
|
|
}
|
|
|
|
// 1. Process Images via native drop event to textarea
|
|
if (imageFiles.length > 0 && textarea) {
|
|
const dt = new DataTransfer();
|
|
for (const f of imageFiles) dt.items.add(f);
|
|
const dropEvt = new DragEvent("drop", { bubbles: true, cancelable: true, dataTransfer: dt });
|
|
textarea.dispatchEvent(dropEvt);
|
|
}
|
|
|
|
// Helper to safely insert text into textarea and trigger React/DSH state updates
|
|
const insertIntoTextarea = (textToInsert) => {
|
|
if (!textarea) return;
|
|
const start = textarea.selectionStart ?? textarea.value.length;
|
|
const end = textarea.selectionEnd ?? textarea.value.length;
|
|
const val = textarea.value;
|
|
const prefix = (start > 0 && !val.endsWith("\\n") && !val.slice(0, start).endsWith("\\n")) ? "\\n" : "";
|
|
const nextValue = val.slice(0, start) + prefix + textToInsert + val.slice(end);
|
|
|
|
const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value")?.set;
|
|
if (setter) {
|
|
setter.call(textarea, nextValue);
|
|
} else {
|
|
textarea.value = nextValue;
|
|
}
|
|
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
textarea.dispatchEvent(new Event("change", { bubbles: true }));
|
|
textarea.focus();
|
|
const newPos = start + prefix.length + textToInsert.length;
|
|
textarea.setSelectionRange(newPos, newPos);
|
|
};
|
|
|
|
const formatSize = (bytes) => {
|
|
if (bytes < 1024) return bytes + " B";
|
|
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
|
|
return (bytes / (1024 * 1024)).toFixed(1) + " MB";
|
|
};
|
|
|
|
// 2. Process Text/Code files by reading and formatting as code blocks
|
|
for (const item of textCodeFiles) {
|
|
try {
|
|
const content = await item.file.text();
|
|
const tag = isZh ? "附加文件" : "Attached File";
|
|
const block = "[" + tag + ": " + item.file.name + "]\\n\`\`\`" + item.ext + "\\n" + content + "\\n\`\`\`\\n\\n";
|
|
insertIntoTextarea(block);
|
|
} catch (err) {
|
|
console.error("Failed to read text file:", err);
|
|
}
|
|
}
|
|
|
|
// 3. Process Binary/Large files by uploading to workspace
|
|
for (const file of binaryUploadFiles) {
|
|
try {
|
|
const res = await fetch("/api/model-enhancer/upload?filename=" + encodeURIComponent(file.name), {
|
|
method: "POST",
|
|
headers: { "X-Filename": encodeURIComponent(file.name) },
|
|
body: file
|
|
});
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
const tag = isZh ? "已上传文件至工作区" : "Uploaded file to workspace";
|
|
const ref = "[" + tag + ": ./" + data.path + " (" + formatSize(file.size) + ")]\\n";
|
|
insertIntoTextarea(ref);
|
|
} else {
|
|
const tag = isZh ? "文件上传失败" : "File upload failed";
|
|
insertIntoTextarea("[" + tag + ": " + file.name + "]\\n");
|
|
}
|
|
} catch (err) {
|
|
console.error("Failed to upload binary file:", err);
|
|
const tag = isZh ? "文件上传出错" : "File upload error";
|
|
insertIntoTextarea("[" + tag + ": " + file.name + " - " + (err.message || err) + "]\\n");
|
|
}
|
|
}
|
|
};
|
|
|
|
const onPlusClick = (e) => {
|
|
e.preventDefault();
|
|
const nativeAdd = document.querySelector("button[class*='uV2eYG_add'], button[class*='InputBar_module_css_add']");
|
|
if (nativeAdd) {
|
|
nativeAdd.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
}
|
|
};
|
|
|
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "me-pill-island",
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("input", {
|
|
ref: fileInputRef,
|
|
type: "file",
|
|
multiple: true,
|
|
style: { display: "none" },
|
|
onChange: onFileChange
|
|
}),
|
|
(0, react_jsx_runtime.jsx)("button", {
|
|
type: "button",
|
|
className: "me-pill-btn",
|
|
title: isZh ? "命令菜单 (/)" : "Commands (/)",
|
|
"aria-label": "Commands",
|
|
onClick: onPlusClick,
|
|
children: (0, react_jsx_runtime.jsx)("svg", {
|
|
width: "14",
|
|
height: "14",
|
|
viewBox: "0 0 16 16",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "2",
|
|
strokeLinecap: "round",
|
|
strokeLinejoin: "round",
|
|
children: (0, react_jsx_runtime.jsx)("path", { d: "M8 3v10M3 8h10" })
|
|
})
|
|
}),
|
|
(0, react_jsx_runtime.jsx)("button", {
|
|
type: "button",
|
|
className: "me-pill-btn",
|
|
title: isZh ? "上传图片或文件" : "Attach files or images",
|
|
"aria-label": "Attach files",
|
|
onClick: onButtonClick,
|
|
children: (0, react_jsx_runtime.jsx)("svg", {
|
|
width: "14",
|
|
height: "14",
|
|
viewBox: "0 0 24 24",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "2",
|
|
strokeLinecap: "round",
|
|
strokeLinejoin: "round",
|
|
children: (0, react_jsx_runtime.jsx)("path", {
|
|
d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48"
|
|
})
|
|
})
|
|
})
|
|
]
|
|
});
|
|
}
|
|
|
|
// Codex Effort Levels translation and order (Zh & En)
|
|
const CODEX_LEVELS_ZH = [
|
|
{ id: "off", label: "关", short: "关" },
|
|
{ id: "minimal", label: "极低", short: "极低" },
|
|
{ id: "low", label: "低", short: "低" },
|
|
{ id: "medium", label: "中", short: "中" },
|
|
{ id: "high", label: "高", short: "高" },
|
|
{ id: "xhigh", label: "超高", short: "超高" },
|
|
{ id: "max", label: "最大", short: "最大" }
|
|
];
|
|
const CODEX_LEVELS_EN = [
|
|
{ id: "off", label: "Off", short: "Off" },
|
|
{ id: "minimal", label: "Minimal", short: "Minimal" },
|
|
{ id: "low", label: "Low", short: "Low" },
|
|
{ id: "medium", label: "Medium", short: "Medium" },
|
|
{ id: "high", label: "High", short: "High" },
|
|
{ id: "xhigh", label: "X-High", short: "X-High" },
|
|
{ id: "max", label: "Max", short: "Max" }
|
|
];
|
|
|
|
// Codex-style Model and Reasoning Slider selector
|
|
function CodexModelSelect(props) {
|
|
const { directory, load, select, available, locked } = props;
|
|
const state = (0, react.useSyncExternalStore)(directory.subscribe, directory.getSnapshot);
|
|
const [open, setOpen] = (0, react.useState)(false);
|
|
const [showModelPicker, setShowModelPicker] = (0, react.useState)(false);
|
|
const rootRef = (0, react.useRef)(null);
|
|
const isZh = isZhLocale();
|
|
const codexLevels = isZh ? CODEX_LEVELS_ZH : CODEX_LEVELS_EN;
|
|
|
|
(0, react.useEffect)(() => {
|
|
if (available) load();
|
|
}, [available]);
|
|
|
|
(0, react.useEffect)(() => {
|
|
if (!open) return;
|
|
const onMouseDown = (e) => {
|
|
if (!rootRef.current?.contains(e.target)) {
|
|
setOpen(false);
|
|
setShowModelPicker(false);
|
|
}
|
|
};
|
|
document.addEventListener("mousedown", onMouseDown);
|
|
return () => document.removeEventListener("mousedown", onMouseDown);
|
|
}, [open]);
|
|
|
|
const choices = (0, react.useMemo)(() => state.groups.flatMap((g) => g.models.map((m) => ({
|
|
group: g,
|
|
model: m
|
|
}))), [state.groups]);
|
|
|
|
const currentChoice = choices.find((c) => c.group.id === state.current?.provider && c.model.id === state.current?.model);
|
|
const currentModel = currentChoice?.model;
|
|
const reasoning = currentModel?.reasoning;
|
|
|
|
// Available reasoning efforts for this model
|
|
const availableEfforts = (0, react.useMemo)(() => {
|
|
if (!reasoning || !reasoning.efforts || reasoning.efforts.length === 0) return [];
|
|
return reasoning.efforts.map((e) => {
|
|
const found = codexLevels.find((l) => l.id === e.id);
|
|
return {
|
|
id: e.id,
|
|
label: found?.label || e.name || e.id,
|
|
short: found?.short || e.name || e.id
|
|
};
|
|
});
|
|
}, [reasoning, isZh]);
|
|
|
|
const effectiveEffort = state.current?.reasoningEffort ?? reasoning?.defaultEffort;
|
|
const currentEffortIndex = (0, react.useMemo)(() => {
|
|
if (availableEfforts.length === 0) return 0;
|
|
const idx = availableEfforts.findIndex((e) => e.id === effectiveEffort);
|
|
return idx === -1 ? 0 : idx;
|
|
}, [availableEfforts, effectiveEffort]);
|
|
|
|
const currentEffortObj = availableEfforts[currentEffortIndex];
|
|
const effortDisplayLabel = currentEffortObj ? currentEffortObj.label : (isZh ? "中" : "Medium");
|
|
|
|
const triggerModelName = currentModel?.name || (isZh ? "选择模型" : "Select model");
|
|
const triggerEffortLabel = reasoning ? (currentEffortObj ? currentEffortObj.short : (isZh ? "默认" : "Default")) : null;
|
|
|
|
const onSelectEffortByIndex = (idx) => {
|
|
if (!currentChoice || !availableEfforts[idx]) return;
|
|
const effort = availableEfforts[idx].id;
|
|
select({
|
|
provider: currentChoice.group.id,
|
|
model: currentChoice.model.id,
|
|
reasoningEffort: effort
|
|
});
|
|
};
|
|
|
|
const onResetDefault = () => {
|
|
if (!currentChoice) return;
|
|
select({
|
|
provider: currentChoice.group.id,
|
|
model: currentChoice.model.id,
|
|
reasoningEffort: reasoning?.defaultEffort
|
|
});
|
|
};
|
|
|
|
const onTrackClick = (e) => {
|
|
if (availableEfforts.length <= 1) return;
|
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
|
|
const idx = Math.round(ratio * (availableEfforts.length - 1));
|
|
onSelectEffortByIndex(idx);
|
|
};
|
|
|
|
if (!available) return null;
|
|
|
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
ref: rootRef,
|
|
style: { position: "relative", display: "inline-flex", alignItems: "center" },
|
|
children: [
|
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
type: "button",
|
|
className: \`me-codex-trigger \${open ? "active-open" : ""}\`,
|
|
disabled: locked,
|
|
onClick: () => setOpen(!open),
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("span", { children: triggerModelName }),
|
|
triggerEffortLabel ? (0, react_jsx_runtime.jsxs)("span", {
|
|
style: { color: "#3b82f6", fontWeight: 600 },
|
|
children: ["· ", triggerEffortLabel]
|
|
}) : null,
|
|
(0, react_jsx_runtime.jsx)("svg", {
|
|
className: \`me-codex-trigger-chevron \${open ? "open" : ""}\`,
|
|
width: "12",
|
|
height: "12",
|
|
viewBox: "0 0 16 16",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "1.8",
|
|
children: (0, react_jsx_runtime.jsx)("path", { d: "M4 6l4 4 4-4" })
|
|
})
|
|
]
|
|
}),
|
|
open ? (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "me-codex-popup",
|
|
children: [
|
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
className: "me-codex-topbar",
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("div", {
|
|
className: "me-codex-icon-btn",
|
|
title: isZh ? "推理强度" : "Reasoning Effort",
|
|
children: (0, react_jsx_runtime.jsx)("svg", {
|
|
width: "16",
|
|
height: "16",
|
|
viewBox: "0 0 24 24",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "2",
|
|
children: (0, react_jsx_runtime.jsx)("path", { d: "M13 2L3 14h9l-1 8 10-12h-9l1-8z" })
|
|
})
|
|
}),
|
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
className: "me-codex-center",
|
|
onClick: () => setShowModelPicker(!showModelPicker),
|
|
title: isZh ? "点击切换模型" : "Click to switch model",
|
|
children: [
|
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
className: "me-codex-effort-title",
|
|
children: [
|
|
effortDisplayLabel,
|
|
(0, react_jsx_runtime.jsx)("svg", {
|
|
width: "12",
|
|
height: "12",
|
|
viewBox: "0 0 16 16",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "2",
|
|
style: { transform: showModelPicker ? "rotate(90deg)" : void 0, transition: "transform 140ms ease" },
|
|
children: (0, react_jsx_runtime.jsx)("path", { d: "M6 3.5L10.5 8L6 12.5" })
|
|
})
|
|
]
|
|
}),
|
|
(0, react_jsx_runtime.jsx)("div", {
|
|
className: "me-codex-model-name",
|
|
children: currentModel?.name || (isZh ? "选择模型" : "Select model")
|
|
})
|
|
]
|
|
}),
|
|
(0, react_jsx_runtime.jsx)("button", {
|
|
type: "button",
|
|
className: "me-codex-icon-btn",
|
|
title: isZh ? "恢复默认档位" : "Reset to default effort",
|
|
onClick: onResetDefault,
|
|
children: (0, react_jsx_runtime.jsx)("svg", {
|
|
width: "16",
|
|
height: "16",
|
|
viewBox: "0 0 24 24",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "2",
|
|
children: (0, react_jsx_runtime.jsx)("path", { d: "M3 12a9 9 0 109-9 9.75 9.75 0 00-6.74 2.74L3 8m0 0V3m0 5h5" })
|
|
})
|
|
})
|
|
]
|
|
}),
|
|
showModelPicker ? (0, react_jsx_runtime.jsx)("div", {
|
|
className: "me-model-picker-list",
|
|
children: state.groups.map((group) => (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "me-model-group-section",
|
|
children: [
|
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
className: "me-model-picker-group",
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("svg", {
|
|
width: "12",
|
|
height: "12",
|
|
viewBox: "0 0 16 16",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "1.8",
|
|
children: (0, react_jsx_runtime.jsx)("path", { d: "M2 4h12M2 8h12M2 12h8" })
|
|
}),
|
|
group.name
|
|
]
|
|
}),
|
|
group.models.map((m) => {
|
|
const active = state.current?.provider === group.id && state.current?.model === m.id;
|
|
return (0, react_jsx_runtime.jsxs)("button", {
|
|
type: "button",
|
|
className: \`me-model-picker-item \${active ? "active" : ""}\`,
|
|
onClick: () => {
|
|
select({
|
|
provider: group.id,
|
|
model: m.id,
|
|
reasoningEffort: m.reasoning?.defaultEffort
|
|
});
|
|
setShowModelPicker(false);
|
|
},
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("span", { children: m.name }),
|
|
active ? (0, react_jsx_runtime.jsx)("svg", {
|
|
width: "14",
|
|
height: "14",
|
|
viewBox: "0 0 16 16",
|
|
fill: "none",
|
|
stroke: "#38bdf8",
|
|
strokeWidth: "2",
|
|
children: (0, react_jsx_runtime.jsx)("path", { d: "M3.5 8.5l3 3 6-6" })
|
|
}) : null
|
|
]
|
|
}, m.id);
|
|
})
|
|
]
|
|
}, group.id))
|
|
}) : (0, react_jsx_runtime.jsx)("div", {
|
|
className: "me-slider-track-wrap",
|
|
onClick: onTrackClick,
|
|
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "me-slider-track",
|
|
children: [
|
|
(0, react_jsx_runtime.jsx)("div", {
|
|
className: "me-slider-active-fill",
|
|
style: {
|
|
width: availableEfforts.length <= 1 ? "100%" : \`calc(16px + (100% - 32px) * \${currentEffortIndex / (availableEfforts.length - 1)})\`
|
|
}
|
|
}),
|
|
(0, react_jsx_runtime.jsx)("div", {
|
|
className: "me-slider-dots-layer",
|
|
children: availableEfforts.map((eff, i) => (0, react_jsx_runtime.jsx)("div", {
|
|
className: \`me-slider-dot \${i <= currentEffortIndex ? "lit" : ""}\`
|
|
}, eff.id))
|
|
}),
|
|
(0, react_jsx_runtime.jsx)("div", {
|
|
className: "me-slider-thumb",
|
|
style: {
|
|
left: availableEfforts.length <= 1 ? "calc(100% - 16px)" : \`calc(16px + (100% - 32px) * \${currentEffortIndex / (availableEfforts.length - 1)})\`
|
|
}
|
|
})
|
|
]
|
|
})
|
|
})
|
|
]
|
|
}) : null
|
|
]
|
|
});
|
|
}
|
|
`;
|
|
|
|
// Inject i18n keys into original en and zh dictionaries
|
|
const enLocalesPatch = `
|
|
// Model Enhancer English Dictionary keys
|
|
Object.assign(en, {
|
|
batchBtn: "Auto-Fill All",
|
|
batchBtnBusy: "Filling…",
|
|
batchBtnTitle: "Auto-match and prefill missing parameters from models.dev",
|
|
singleBtn: "Auto-Fill",
|
|
singleBtnBusy: "Filling…",
|
|
singleBtnTitle: "Fetch recommended parameters for this model from models.dev",
|
|
badgeVision: "Vision",
|
|
badgeVisionTitle: "Supports Image/Vision input",
|
|
badgeThinking: "Reasoning",
|
|
badgeThinkingTitle: "Reasoning efforts: {levels}",
|
|
defaultEffort: "Default",
|
|
modalitiesTitle: "Input Modalities",
|
|
modalityText: "Text Input",
|
|
modalityVision: "Vision Input",
|
|
reasoningTitle: "Reasoning Efforts",
|
|
enableReasoning: "Enable Deep Reasoning",
|
|
saveBlockedHint: "Save blocked: Model {index} {reason}",
|
|
fillNoIdError: "No valid models with a Model ID found in the list",
|
|
fillSuccessNotice: "Auto-fill complete: matched & updated {count}/{total} models",
|
|
fillFailedNotice: "Auto-fill failed: {message}",
|
|
singleFillRequireId: "Please enter a Model ID first",
|
|
singleFillNotFound: 'No matching model found on models.dev for "{id}"',
|
|
singleFillFetchFailed: "Failed to fetch models.dev: {message}",
|
|
singleFillApplied: 'Prefilled recommended parameters for "{id}"',
|
|
modalTitle: "models.dev Prefill Confirmation",
|
|
matchedModelLabel: "Matched Model: ",
|
|
thSelect: "Apply",
|
|
thField: "Property",
|
|
thCurrent: "Current Setting",
|
|
thRecommended: "models.dev Value",
|
|
fieldName: "Display Name",
|
|
fieldContext: "Context Window",
|
|
fieldMaxTokens: "Max Output Tokens",
|
|
fieldInput: "Vision Input",
|
|
fieldReasoning: "Reasoning Efforts",
|
|
fieldUnset: "(Unset)",
|
|
fieldDefault: "(Default)",
|
|
visionSupported: "Vision Supported",
|
|
visionOnlyText: "Text Only",
|
|
thinkingOff: "Disabled",
|
|
thinkingOn: "Enabled",
|
|
applySelected: "Apply Selected"
|
|
});
|
|
`;
|
|
|
|
const zhLocalesPatch = `
|
|
// Model Enhancer Chinese Dictionary keys
|
|
Object.assign(zh, {
|
|
batchBtn: "一键补全",
|
|
batchBtnBusy: "补全中...",
|
|
batchBtnTitle: "根据模型 ID 从 models.dev 自动匹配并预填充缺失参数",
|
|
singleBtn: "智能补全",
|
|
singleBtnBusy: "补全中",
|
|
singleBtnTitle: "从 models.dev 获取该模型推荐参数",
|
|
badgeVision: "图像",
|
|
badgeVisionTitle: "支持图像输入 (Vision)",
|
|
badgeThinking: "思考",
|
|
badgeThinkingTitle: "思考档位: {levels}",
|
|
defaultEffort: "默认",
|
|
modalitiesTitle: "输入模态支持 (Input Modalities)",
|
|
modalityText: "文本输入 (Text)",
|
|
modalityVision: "图像输入 (Vision)",
|
|
reasoningTitle: "推理思考档位 (Reasoning Efforts)",
|
|
enableReasoning: "启用深度思考",
|
|
saveBlockedHint: "保存受阻提示:模型 {index} {reason}",
|
|
fillNoIdError: "列表中没有填写模型 ID 的有效模型",
|
|
fillSuccessNotice: "一键补全完成:已匹配并更新 {count}/{total} 个模型",
|
|
fillFailedNotice: "一键补全失败: {message}",
|
|
singleFillRequireId: "请先填写模型 ID",
|
|
singleFillNotFound: '未在 models.dev 中找到与 "{id}" 匹配的模型信息',
|
|
singleFillFetchFailed: "获取 models.dev 失败: {message}",
|
|
singleFillApplied: '已预填充 "{id}" 推荐参数',
|
|
modalTitle: "models.dev 预填充确认与选择",
|
|
matchedModelLabel: "检索到匹配模型:",
|
|
thSelect: "选用",
|
|
thField: "配置项",
|
|
thCurrent: "当前设置",
|
|
thRecommended: "models.dev 推荐值",
|
|
fieldName: "显示名称",
|
|
fieldContext: "上下文长度",
|
|
fieldMaxTokens: "最大输出",
|
|
fieldInput: "图像输入",
|
|
fieldReasoning: "推理思考等级",
|
|
fieldUnset: "(未设置)",
|
|
fieldDefault: "(默认)",
|
|
visionSupported: "支持图像",
|
|
visionOnlyText: "仅文本",
|
|
thinkingOff: "关闭思考",
|
|
thinkingOn: "开启",
|
|
applySelected: "应用已选推荐项"
|
|
});
|
|
`;
|
|
|
|
// Insert locales patches into finalCode
|
|
const zhEndTag = "const zh = {";
|
|
const zhIdx = finalCode.indexOf(zhEndTag);
|
|
const zhObjEndIdx = finalCode.indexOf("};", zhIdx) + 2;
|
|
|
|
finalCode = finalCode.slice(0, zhObjEndIdx) + enLocalesPatch + zhLocalesPatch + finalCode.slice(zhObjEndIdx);
|
|
|
|
// Append extraComponentsCode and custom apply
|
|
const applyStartTag = "function apply(ctx) {";
|
|
const applyIdx = finalCode.indexOf(applyStartTag);
|
|
|
|
const newApply = `function apply(ctx) {
|
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
zh,
|
|
en
|
|
}), "ui-settings-models: copy dictionaries");
|
|
const connection = ctx.get("connection");
|
|
const schema = createSettingsSchemaOperations(ctx.settingsSchema);
|
|
const controller = new ModelsSettingsStore(connection.api, schema, ctx.settingsScope.describe());
|
|
const t = ctx.locale.bind(NS);
|
|
const injected = () => ({
|
|
controller,
|
|
hooks: { snapshot: controller.store },
|
|
api: connection.api,
|
|
schema,
|
|
t
|
|
});
|
|
ctx.effect(() => {
|
|
const refreshModels = () => {
|
|
refreshIfLoaded(controller);
|
|
};
|
|
const disposers = [
|
|
ctx.remote.$on("settings/document-updated", () => {
|
|
refreshModels();
|
|
}),
|
|
ctx.remote.$on("credentials/reference-updated", refreshModels),
|
|
ctx.remote.$on("llm/adapters-updated", refreshModels),
|
|
ctx.on("connection/reset", refreshModels)
|
|
];
|
|
return () => {
|
|
for (const dispose of disposers) dispose();
|
|
};
|
|
}, "ui-settings-models: pushed invalidations");
|
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
name: "settings.section",
|
|
id: "models",
|
|
order: 10,
|
|
label: () => t("nav"),
|
|
inject: injected
|
|
}, ModelsSection));
|
|
|
|
// 1. Paperclip button in conversation.input.left
|
|
ctx.slots.inject("conversation.input.left", () => ctx.slots.register({
|
|
name: "conversation.input.left",
|
|
id: "me-paperclip-upload",
|
|
order: -100
|
|
}, PaperclipUploadButton));
|
|
|
|
// 2. Codex-style model and reasoning selector in conversation.input.right
|
|
ctx.inject(["slots"], (scope) => {
|
|
const sessions = ctx.get("sessions");
|
|
scope.slots.inject("conversation.input.right", () => scope.slots.register({
|
|
name: "conversation.input.right",
|
|
id: "me-codex-model-selector",
|
|
order: 100,
|
|
inject: (sessionId) => {
|
|
const dirSvc = ctx.get("modelDirectories");
|
|
const directory = dirSvc ? dirSvc.directoryFor(sessionId) : null;
|
|
const available = sessions ? sessions.subagentAddress(sessionId) === void 0 : true;
|
|
return {
|
|
available: available && Boolean(directory),
|
|
directory: directory ? directory.store : { subscribe: () => () => {}, getSnapshot: () => ({ status: "idle", groups: [], current: null }) },
|
|
load: () => {
|
|
if (available && directory) directory.load().catch(() => {});
|
|
},
|
|
select: (selection) => (available && directory) ? directory.select(selection).then(() => true, () => false) : Promise.resolve(false)
|
|
};
|
|
}
|
|
}, CodexModelSelect));
|
|
});
|
|
}
|
|
//#endregion
|
|
exports.apply = apply;
|
|
exports.inject = inject;
|
|
exports.refreshIfLoaded = refreshIfLoaded;
|
|
return module.exports;
|
|
}
|
|
});
|
|
`;
|
|
|
|
finalCode = finalCode.slice(0, applyIdx) + extraComponentsCode + newApply;
|
|
|
|
fs.writeFileSync('/home/cho/Public/dsh-workspace/dsh-plugin-model-enhancer/lib/client.js', finalCode, 'utf8');
|
|
console.log("Successfully rebuilt complete client.js with Full i18n (Chinese & English)! Length:", finalCode.length);
|