Add files via upload

This commit is contained in:
公明
2026-06-18 23:44:04 +08:00
committed by GitHub
parent 8d622f63ff
commit b7fa18b6d4
3 changed files with 453 additions and 9 deletions
+261
View File
@@ -5851,6 +5851,267 @@ header {
box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.2);
}
/* Model picker: input + custom dropdown */
.model-pick-row {
display: flex;
gap: 8px;
align-items: stretch;
flex-wrap: wrap;
}
.model-pick-input {
flex: 1 1 140px;
min-width: 0;
padding: 10px 12px;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 0.9375rem;
background: var(--bg-primary);
color: var(--text-primary);
transition: border-color 0.2s, box-shadow 0.2s;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.model-pick-input:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
}
.model-pick-input:hover {
border-color: rgba(0, 102, 255, 0.45);
}
.model-pick-input.error {
border-color: var(--error-color);
box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.1);
}
.model-pick-fetch-link {
flex: 0 0 auto;
align-self: center;
font-size: 0.8125rem;
color: var(--accent-color);
text-decoration: none;
cursor: pointer;
user-select: none;
white-space: nowrap;
padding: 4px 2px;
transition: color 0.15s ease, opacity 0.15s ease;
}
.model-pick-fetch-link:hover {
color: var(--accent-hover);
text-decoration: underline;
}
.model-pick-native {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.model-pick-dropdown {
position: relative;
flex: 0 0 auto;
min-width: 148px;
max-width: 220px;
}
.model-pick-trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
width: 100%;
height: 100%;
min-height: 42px;
box-sizing: border-box;
padding: 0 10px 0 12px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: linear-gradient(180deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
color: var(--text-primary);
font-size: 0.8125rem;
line-height: 1.2;
cursor: pointer;
transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.model-pick-trigger-label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
font-weight: 500;
}
.model-pick-trigger-meta {
display: inline-flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
}
.model-pick-count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 20px;
height: 18px;
padding: 0 6px;
border-radius: 999px;
background: rgba(0, 102, 255, 0.1);
color: var(--accent-color);
font-size: 0.6875rem;
font-weight: 600;
line-height: 1;
}
.model-pick-caret {
flex-shrink: 0;
width: 14px;
height: 14px;
color: var(--text-secondary);
transition: transform 0.2s ease, color 0.15s ease;
}
.model-pick-dropdown.open .model-pick-caret {
transform: rotate(180deg);
color: var(--accent-color);
}
.model-pick-trigger:hover:not(:disabled) {
border-color: rgba(0, 102, 255, 0.45);
background: var(--bg-primary);
}
.model-pick-dropdown.open .model-pick-trigger {
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
background: var(--bg-primary);
}
.model-pick-dropdown.is-disabled .model-pick-trigger {
opacity: 0.55;
cursor: not-allowed;
}
.model-pick-menu {
display: none;
position: absolute;
top: calc(100% + 6px);
right: 0;
left: auto;
z-index: 2000;
min-width: 280px;
max-width: min(360px, 92vw);
max-height: 300px;
overflow: hidden;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 10px;
box-shadow: 0 12px 32px rgba(15, 23, 42, 0.14), 0 2px 8px rgba(15, 23, 42, 0.06);
}
.model-pick-dropdown.open .model-pick-menu {
display: flex;
flex-direction: column;
animation: model-pick-menu-in 0.16s ease-out;
}
@keyframes model-pick-menu-in {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.model-pick-menu-header {
flex: 0 0 auto;
padding: 10px 14px 8px;
border-bottom: 1px solid var(--border-color);
font-size: 0.75rem;
font-weight: 600;
color: var(--text-secondary);
letter-spacing: 0.02em;
}
.model-pick-menu-list {
flex: 1 1 auto;
overflow-y: auto;
padding: 6px;
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.15) transparent;
}
.model-pick-menu-list::-webkit-scrollbar {
width: 6px;
}
.model-pick-menu-list::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.15);
border-radius: 999px;
}
.model-pick-option {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
border-radius: 6px;
font-size: 0.8125rem;
line-height: 1.3;
color: var(--text-primary);
cursor: pointer;
transition: background-color 0.12s ease, color 0.12s ease;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.model-pick-option:hover {
background: var(--bg-secondary);
}
.model-pick-option.is-selected {
background: rgba(0, 102, 255, 0.08);
color: var(--accent-color);
font-weight: 600;
}
.model-pick-option-check {
flex: 0 0 14px;
width: 14px;
font-size: 0.75rem;
line-height: 1;
color: var(--accent-color);
opacity: 0;
text-align: center;
}
.model-pick-option.is-selected .model-pick-option-check {
opacity: 1;
}
.model-pick-option-label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.batch-execute-now-label {
display: inline-flex;
align-items: center;
+184 -1
View File
@@ -1577,6 +1577,179 @@ function syncVisionFormEnabled() {
}
}
const modelPickSelectMap = {};
let modelPickSelectDocListener = false;
function modelPickT(key) {
return typeof window.t === 'function' ? window.t(key) : key;
}
function closeAllModelPickDropdowns() {
Object.keys(modelPickSelectMap).forEach(function (id) {
modelPickSelectMap[id].wrapper.classList.remove('open');
});
}
function syncModelPickDropdown(selectId) {
const reg = modelPickSelectMap[selectId];
if (!reg) return;
const { select, dropdown, trigger, wrapper, menuList, countBadge } = reg;
const placeholder = modelPickT('settingsBasic.modelsListSelectPlaceholder');
menuList.innerHTML = '';
let optionCount = 0;
Array.prototype.forEach.call(select.options, function (opt) {
if (!opt.value) return;
optionCount += 1;
const item = document.createElement('div');
item.className = 'model-pick-option';
item.setAttribute('role', 'option');
item.setAttribute('data-value', opt.value);
if (opt.value === select.value) {
item.classList.add('is-selected');
item.setAttribute('aria-selected', 'true');
}
const check = document.createElement('span');
check.className = 'model-pick-option-check';
check.setAttribute('aria-hidden', 'true');
check.textContent = '✓';
const label = document.createElement('span');
label.className = 'model-pick-option-label';
label.textContent = opt.textContent;
item.appendChild(check);
item.appendChild(label);
menuList.appendChild(item);
});
const selectedOpt = select.selectedIndex >= 0 ? select.options[select.selectedIndex] : null;
const labelEl = trigger.querySelector('.model-pick-trigger-label');
if (labelEl) {
labelEl.textContent = (selectedOpt && selectedOpt.value) ? selectedOpt.textContent : placeholder;
}
if (countBadge) {
countBadge.textContent = String(optionCount);
countBadge.style.display = optionCount > 0 ? '' : 'none';
}
const header = wrapper.querySelector('.model-pick-menu-header');
if (header) {
header.textContent = optionCount > 0
? placeholder + ' · ' + optionCount
: placeholder;
}
trigger.disabled = !!select.disabled;
wrapper.classList.toggle('is-disabled', !!select.disabled);
wrapper.style.display = optionCount > 0 ? '' : 'none';
select.style.display = 'none';
}
function enhanceModelPickSelect(selectId) {
const select = document.getElementById(selectId);
if (!select) return;
if (select.dataset.modelPickEnhanced === '1') {
syncModelPickDropdown(selectId);
return;
}
select.dataset.modelPickEnhanced = '1';
select.classList.add('model-pick-native');
select.tabIndex = -1;
select.setAttribute('aria-hidden', 'true');
const wrapper = document.createElement('div');
wrapper.className = 'model-pick-dropdown';
wrapper.style.display = 'none';
const trigger = document.createElement('button');
trigger.type = 'button';
trigger.className = 'model-pick-trigger';
trigger.setAttribute('aria-haspopup', 'listbox');
const labelSpan = document.createElement('span');
labelSpan.className = 'model-pick-trigger-label';
labelSpan.textContent = modelPickT('settingsBasic.modelsListSelectPlaceholder');
const meta = document.createElement('span');
meta.className = 'model-pick-trigger-meta';
const countBadge = document.createElement('span');
countBadge.className = 'model-pick-count';
countBadge.style.display = 'none';
const caret = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
caret.setAttribute('class', 'model-pick-caret');
caret.setAttribute('viewBox', '0 0 16 16');
caret.setAttribute('aria-hidden', 'true');
caret.innerHTML = '<path fill="currentColor" d="M4.47 6.47a.75.75 0 0 1 1.06 0L8 8.94l2.47-2.47a.75.75 0 1 1 1.06 1.06l-3 3a.75.75 0 0 1-1.06 0l-3-3a.75.75 0 0 1 0-1.06z"/>';
meta.appendChild(countBadge);
meta.appendChild(caret);
trigger.appendChild(labelSpan);
trigger.appendChild(meta);
const menu = document.createElement('div');
menu.className = 'model-pick-menu';
const header = document.createElement('div');
header.className = 'model-pick-menu-header';
menu.appendChild(header);
const menuList = document.createElement('div');
menuList.className = 'model-pick-menu-list';
menuList.setAttribute('role', 'listbox');
menu.appendChild(menuList);
const parent = select.parentNode;
const fetchLink = parent.querySelector('.model-pick-fetch-link');
if (fetchLink) {
parent.insertBefore(wrapper, fetchLink);
} else {
parent.appendChild(wrapper);
}
wrapper.appendChild(trigger);
wrapper.appendChild(menu);
wrapper.appendChild(select);
modelPickSelectMap[selectId] = {
wrapper,
trigger,
menu,
menuList,
countBadge,
select
};
if (!modelPickSelectDocListener) {
document.addEventListener('click', closeAllModelPickDropdowns);
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') closeAllModelPickDropdowns();
});
modelPickSelectDocListener = true;
}
trigger.addEventListener('click', function (e) {
e.stopPropagation();
if (select.disabled) return;
const open = wrapper.classList.contains('open');
closeAllModelPickDropdowns();
if (!open) wrapper.classList.add('open');
});
menuList.addEventListener('click', function (e) {
const opt = e.target.closest('.model-pick-option');
if (!opt) return;
const val = opt.getAttribute('data-value');
if (val === null || val === '') return;
if (select.value !== val) {
select.value = val;
select.dispatchEvent(new Event('change', { bubbles: true }));
}
wrapper.classList.remove('open');
syncModelPickDropdown(selectId);
});
syncModelPickDropdown(selectId);
}
function initModelListControls() {
const providerEl = document.getElementById('openai-provider');
if (providerEl && !providerEl.dataset.modelListBound) {
@@ -1605,6 +1778,7 @@ function bindModelSelect(scope) {
const select = document.getElementById(selectId);
if (!select || select.dataset.bound) return;
select.dataset.bound = '1';
enhanceModelPickSelect(selectId);
select.addEventListener('change', function () {
if (!select.value) return;
const input = document.getElementById(inputId);
@@ -1641,6 +1815,10 @@ function syncModelListFetchButtons() {
}
if (openaiSelect && isClaudeOpenai) {
openaiSelect.style.display = 'none';
const openaiWrap = modelPickSelectMap['openai-model-select'];
if (openaiWrap) openaiWrap.wrapper.style.display = 'none';
} else if (openaiSelect && !isClaudeOpenai) {
syncModelPickDropdown('openai-model-select');
}
if (openaiHint) {
if (isClaudeOpenai) {
@@ -1663,6 +1841,10 @@ function syncModelListFetchButtons() {
}
if (visionSelect && isClaudeVision) {
visionSelect.style.display = 'none';
const visionWrap = modelPickSelectMap['vision-model-select'];
if (visionWrap) visionWrap.wrapper.style.display = 'none';
} else if (visionSelect && !isClaudeVision) {
syncModelPickDropdown('vision-model-select');
}
if (visionHint) {
if (isClaudeVision) {
@@ -1705,7 +1887,8 @@ function populateModelSelect(scope, models, currentValue) {
} else {
select.value = '';
}
select.style.display = select.options.length > 1 ? '' : 'none';
enhanceModelPickSelect(selectId);
syncModelPickDropdown(selectId);
}
async function fetchModelList(scope) {
+8 -8
View File
@@ -2413,12 +2413,12 @@
</div>
<div class="form-group">
<label for="openai-model"><span data-i18n="settingsBasic.model">模型</span> <span style="color: red;">*</span></label>
<div style="display: flex; gap: 8px; align-items: center; flex-wrap: wrap;">
<input type="text" id="openai-model" data-i18n="settingsBasic.modelPlaceholder" data-i18n-attr="placeholder" placeholder="gpt-4" required style="flex: 1; min-width: 140px;" />
<select id="openai-model-select" class="model-pick-select" style="display: none; min-width: 160px; max-width: 240px;" title="">
<div class="model-pick-row">
<input type="text" id="openai-model" class="model-pick-input" data-i18n="settingsBasic.modelPlaceholder" data-i18n-attr="placeholder" placeholder="gpt-4" required />
<select id="openai-model-select" class="model-pick-native" style="display: none;" title="" aria-hidden="true" tabindex="-1">
<option value="" disabled data-i18n="settingsBasic.modelsListSelectPlaceholder">请选择模型</option>
</select>
<a href="javascript:void(0)" id="fetch-openai-models-btn" onclick="fetchModelList('openai')" style="font-size: 0.8125rem; color: var(--accent-color, #3182ce); text-decoration: none; cursor: pointer; user-select: none; white-space: nowrap;" data-i18n="settingsBasic.fetchModels">获取列表</a>
<a href="javascript:void(0)" id="fetch-openai-models-btn" class="model-pick-fetch-link" onclick="fetchModelList('openai')" data-i18n="settingsBasic.fetchModels">获取列表</a>
</div>
<small id="fetch-openai-models-hint" class="form-hint" style="display: none; font-size: 0.75rem; margin-top: 4px;"></small>
<span id="fetch-openai-models-result" style="font-size: 0.75rem; margin-top: 2px; display: block;"></span>
@@ -2499,12 +2499,12 @@
</div>
<div class="form-group">
<label for="vision-model"><span data-i18n="settingsBasic.visionModel">视觉模型</span> <span style="color: red;">*</span></label>
<div style="display: flex; gap: 8px; align-items: center; flex-wrap: wrap;">
<input type="text" id="vision-model" data-i18n="settingsBasic.visionModelPlaceholder" data-i18n-attr="placeholder" placeholder="qwen-vl-max" style="flex: 1; min-width: 140px;" />
<select id="vision-model-select" class="model-pick-select" style="display: none; min-width: 160px; max-width: 240px;">
<div class="model-pick-row">
<input type="text" id="vision-model" class="model-pick-input" data-i18n="settingsBasic.visionModelPlaceholder" data-i18n-attr="placeholder" placeholder="qwen-vl-max" />
<select id="vision-model-select" class="model-pick-native" style="display: none;" aria-hidden="true" tabindex="-1">
<option value="" disabled data-i18n="settingsBasic.modelsListSelectPlaceholder">请选择模型</option>
</select>
<a href="javascript:void(0)" id="fetch-vision-models-btn" onclick="fetchModelList('vision')" style="font-size: 0.8125rem; color: var(--accent-color, #3182ce); text-decoration: none; cursor: pointer; user-select: none; white-space: nowrap;" data-i18n="settingsBasic.fetchModels">获取列表</a>
<a href="javascript:void(0)" id="fetch-vision-models-btn" class="model-pick-fetch-link" onclick="fetchModelList('vision')" data-i18n="settingsBasic.fetchModels">获取列表</a>
</div>
<small id="fetch-vision-models-hint" class="form-hint" style="display: none; font-size: 0.75rem; margin-top: 4px;"></small>
<span id="fetch-vision-models-result" style="font-size: 0.75rem; margin-top: 2px; display: block;"></span>