优化模型列表编辑

This commit is contained in:
Von committed 2026-03-12 13:24:22 +08:00
1 parent 6d99b41f9e
commit 88bc95f513
2 files changed
+153 -6

No files matched your search

+79 -2
View File
@@ -550,6 +550,13 @@
margin-bottom: 10px;
}
.providers-page .field-header-meta {
display: inline-flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.providers-page .field-header-actions {
display: inline-flex;
align-items: center;
@@ -560,6 +567,39 @@
margin-bottom: 0;
}
.providers-page .field-meta-badge {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 0 8px;
border: 1px solid #d8e5f3;
border-radius: 999px;
background: rgba(17, 24, 39, 0.04);
color: var(--text-secondary);
font-size: 12px;
font-weight: 600;
white-space: nowrap;
}
.providers-page .btn-inline-secondary {
border: none;
background: transparent;
color: var(--text-secondary);
font-size: 13px;
font-weight: 700;
padding: 0;
cursor: pointer;
}
.providers-page .btn-inline-secondary:hover {
color: var(--primary);
}
.providers-page .btn-inline-secondary:focus-visible {
outline: none;
color: var(--primary);
}
.providers-page .field-help-button {
width: 22px;
height: 22px;
@@ -680,6 +720,21 @@
color: #bcc8d8;
}
:root[data-theme="dark"] .providers-page .field-meta-badge {
border-color: #313b47;
background: rgba(255, 255, 255, 0.04);
color: #a8b3c2;
}
:root[data-theme="dark"] .providers-page .btn-inline-secondary {
color: #a8b3c2;
}
:root[data-theme="dark"] .providers-page .btn-inline-secondary:hover,
:root[data-theme="dark"] .providers-page .btn-inline-secondary:focus-visible {
color: #c7e0ff;
}
:root[data-theme="dark"] .providers-page .field-help-button {
border-color: #3b4654;
background: rgba(255, 255, 255, 0.04);
@@ -731,11 +786,28 @@
.providers-page .provider-models-textarea {
min-height: 180px;
padding-top: 10px;
padding-bottom: 10px;
padding: 12px 14px;
resize: vertical;
font-family: "JetBrains Mono", "Consolas", monospace;
font-size: 13px;
line-height: 1.55;
scrollbar-width: thin;
scrollbar-color: rgba(126, 148, 176, 0.7) transparent;
}
.providers-page .provider-models-textarea::-webkit-scrollbar {
width: 10px;
}
.providers-page .provider-models-textarea::-webkit-scrollbar-track {
background: transparent;
}
.providers-page .provider-models-textarea::-webkit-scrollbar-thumb {
border: 3px solid transparent;
border-radius: 999px;
background: rgba(126, 148, 176, 0.7);
background-clip: padding-box;
}
@media (max-width: 960px) {
@@ -777,8 +849,13 @@
align-items: stretch;
}
.providers-page .field-header-meta {
justify-content: space-between;
}
.providers-page .field-header-actions {
justify-content: flex-end;
flex-wrap: wrap;
}
.providers-page .providers-table {
+74 -4
View File
@@ -88,8 +88,12 @@
</div>
<div class="mb-3">
<div class="field-header">
<label class="form-label" for="providerModelList">Model List</label>
<div class="field-header-meta">
<label class="form-label" for="providerModelList">Model List</label>
<span class="field-meta-badge" id="providerModelCount">0 个模型</span>
</div>
<div class="field-header-actions">
<button type="button" class="btn-inline-secondary" onclick="tidyModelList()">整理</button>
<button type="button" class="btn btn-secondary btn-fetch-inline" id="fetchModelsBtn" onclick="fetchModels()">拉取模型</button>
<button
type="button"
@@ -101,7 +105,7 @@
>?</button>
</div>
</div>
<textarea class="form-control provider-models-textarea" id="providerModelList" placeholder="每行一个模型,留空则不写入配置"></textarea>
<textarea class="form-control provider-models-textarea" id="providerModelList" placeholder="每行一个模型,支持粘贴后整理;保存时会自动去空行并去重"></textarea>
</div>
</div>
<div class="modal-footer border-top-0">
@@ -270,7 +274,65 @@
modelDetailModalInstance.show();
}
function normalizeModelListItems(value) {
const rawItems = String(value ?? '').split(/\r?\n/);
const models = [];
const seen = new Set();
rawItems.forEach(item => {
const model = item.trim();
if (!model || seen.has(model)) return;
seen.add(model);
models.push(model);
});
return models;
}
function buildModelListValue(models) {
return models.join('\n');
}
function updateModelListCount() {
const countElement = document.getElementById('providerModelCount');
const textarea = document.getElementById('providerModelList');
if (!countElement || !textarea) return;
const models = normalizeModelListItems(textarea.value);
countElement.textContent = `${models.length} 个模型`;
}
function applyNormalizedModelListValue(showFeedback = false) {
const textarea = document.getElementById('providerModelList');
if (!textarea) return false;
const before = textarea.value;
const normalizedItems = normalizeModelListItems(before);
const normalizedValue = buildModelListValue(normalizedItems);
const changed = before !== normalizedValue;
textarea.value = normalizedValue;
updateModelListCount();
if (showFeedback) {
if (changed) {
showMessage(`Model List 已整理,保留 ${normalizedItems.length} 个模型`, 'success');
} else {
showMessage('Model List 已是整理后的格式', 'success');
}
}
return changed;
}
function tidyModelList() {
applyNormalizedModelListValue(true);
}
function collectFormData() {
const normalizedModelList = buildModelListValue(
normalizeModelListItems(document.getElementById('providerModelList').value)
);
return {
name: document.getElementById('providerName').value.trim(),
api: document.getElementById('providerApi').value.trim(),
@@ -279,7 +341,7 @@
timeout_seconds: document.getElementById('providerTimeout').value.trim(),
max_retries: document.getElementById('providerRetries').value.trim(),
verify_ssl: document.getElementById('providerVerifySsl').value,
model_list: document.getElementById('providerModelList').value,
model_list: normalizedModelList,
hook: document.getElementById('providerHook').value.trim()
};
}
@@ -292,8 +354,9 @@
document.getElementById('providerTimeout').value = provider?.timeout_seconds || '';
document.getElementById('providerRetries').value = provider?.max_retries || '';
document.getElementById('providerVerifySsl').value = provider?.verify_ssl === true ? 'true' : (provider?.verify_ssl === false ? 'false' : '');
document.getElementById('providerModelList').value = Array.isArray(provider?.model_list) ? provider.model_list.join('\n') : '';
document.getElementById('providerModelList').value = Array.isArray(provider?.model_list) ? buildModelListValue(provider.model_list) : '';
document.getElementById('providerHook').value = provider?.hook || '';
updateModelListCount();
updateFetchModelsButtonState();
}
@@ -413,6 +476,7 @@
}
async function saveProvider() {
applyNormalizedModelListValue();
const payload = collectFormData();
if (!payload.name) {
showMessage('请输入 provider 名称', 'error');
@@ -467,6 +531,7 @@
}
async function fetchModels() {
applyNormalizedModelListValue();
const payload = collectFormData();
if (!payload.name || !payload.api) {
showMessage('请先填写并保存 provider 名称和 API 地址', 'error');
@@ -532,7 +597,12 @@
});
document.getElementById('providerName').addEventListener('input', updateFetchModelsButtonState);
document.getElementById('providerApi').addEventListener('input', updateFetchModelsButtonState);
document.getElementById('providerModelList').addEventListener('input', updateModelListCount);
document.getElementById('providerModelList').addEventListener('blur', function() {
applyNormalizedModelListValue();
});
document.getElementById('modelSearchInput').addEventListener('input', renderModelDetailList);
updateModelListCount();
updateFetchModelsButtonState();
loadProviders();
});