mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-09-05 23:56:37 +08:00
feat(config): 添加模型助手功能以选择和管理模型
本次提交主要包含以下更改: 1. **后端更新**: - 在 `app/service/config/config_service.py` 中新增 `fetch_ui_models` 方法,用于获取可用于 UI 的模型列表,并处理相关的错误情况。 - 在 `app/router/config_routes.py` 中新增 `/ui/models` 路由,提供模型列表的 API 接口,并添加身份验证逻辑。 2. **前端更新**: - 在 `app/static/js/config_editor.js` 中实现模型助手的功能,包括模型列表的加载、搜索和选择。 - 在 `app/templates/config_editor.html` 中添加模型助手的模态框和相关的 UI 元素,允许用户从列表中选择模型。 这些更改旨在增强用户体验,使用户能够更方便地选择和管理模型,提高配置界面的交互性和功能性。
This commit is contained in:
@@ -31,6 +31,23 @@ const bulkDeleteProxyInput = document.getElementById("bulkDeleteProxyInput");
|
||||
const resetConfirmModal = document.getElementById("resetConfirmModal");
|
||||
const configForm = document.getElementById("configForm"); // Added for frequent use
|
||||
|
||||
// Model Helper Modal Elements
|
||||
const modelHelperModal = document.getElementById("modelHelperModal");
|
||||
const modelHelperTitleElement = document.getElementById("modelHelperTitle");
|
||||
const modelHelperSearchInput = document.getElementById(
|
||||
"modelHelperSearchInput"
|
||||
);
|
||||
const modelHelperListContainer = document.getElementById(
|
||||
"modelHelperListContainer"
|
||||
);
|
||||
const closeModelHelperModalBtn = document.getElementById(
|
||||
"closeModelHelperModalBtn"
|
||||
);
|
||||
const cancelModelHelperBtn = document.getElementById("cancelModelHelperBtn");
|
||||
|
||||
let cachedModelsList = null;
|
||||
let currentModelHelperTarget = null; // { type: 'input'/'array', target: elementOrIdOrKey }
|
||||
|
||||
// Modal Control Functions
|
||||
function openModal(modalElement) {
|
||||
if (modalElement) {
|
||||
@@ -227,6 +244,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
bulkDeleteApiKeyModal,
|
||||
proxyModal,
|
||||
bulkDeleteProxyModal,
|
||||
modelHelperModal,
|
||||
];
|
||||
modals.forEach((modal) => {
|
||||
if (event.target === modal) {
|
||||
@@ -353,6 +371,44 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
}
|
||||
|
||||
initializeSensitiveFields(); // Initialize sensitive field handling
|
||||
|
||||
// Model Helper Modal Event Listeners
|
||||
if (closeModelHelperModalBtn) {
|
||||
closeModelHelperModalBtn.addEventListener("click", () =>
|
||||
closeModal(modelHelperModal)
|
||||
);
|
||||
}
|
||||
if (cancelModelHelperBtn) {
|
||||
cancelModelHelperBtn.addEventListener("click", () =>
|
||||
closeModal(modelHelperModal)
|
||||
);
|
||||
}
|
||||
if (modelHelperSearchInput) {
|
||||
modelHelperSearchInput.addEventListener("input", () =>
|
||||
renderModelsInModal()
|
||||
);
|
||||
}
|
||||
|
||||
// Add event listeners to all model helper trigger buttons
|
||||
const modelHelperTriggerBtns = document.querySelectorAll(
|
||||
".model-helper-trigger-btn"
|
||||
);
|
||||
modelHelperTriggerBtns.forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
const targetInputId = btn.dataset.targetInputId;
|
||||
const targetArrayKey = btn.dataset.targetArrayKey;
|
||||
|
||||
if (targetInputId) {
|
||||
currentModelHelperTarget = {
|
||||
type: "input",
|
||||
target: document.getElementById(targetInputId),
|
||||
};
|
||||
} else if (targetArrayKey) {
|
||||
currentModelHelperTarget = { type: "array", targetKey: targetArrayKey };
|
||||
}
|
||||
openModelHelperModal();
|
||||
});
|
||||
});
|
||||
}); // <-- DOMContentLoaded end
|
||||
|
||||
/**
|
||||
@@ -1719,3 +1775,140 @@ function addSafetySettingItem(category = "", threshold = "") {
|
||||
|
||||
container.appendChild(settingItem);
|
||||
}
|
||||
|
||||
// --- Model Helper Functions ---
|
||||
async function fetchModels() {
|
||||
if (cachedModelsList) {
|
||||
return cachedModelsList;
|
||||
}
|
||||
try {
|
||||
showNotification("正在从 /api/config/ui/models 加载模型列表...", "info");
|
||||
const response = await fetch("/api/config/ui/models");
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
throw new Error(`HTTP error ${response.status}: ${errorData}`);
|
||||
}
|
||||
const responseData = await response.json(); // Changed variable name to responseData
|
||||
// The backend returns an object like: { object: "list", data: [{id: "m1"}, {id: "m2"}], success: true }
|
||||
if (
|
||||
responseData &&
|
||||
responseData.success &&
|
||||
Array.isArray(responseData.data)
|
||||
) {
|
||||
cachedModelsList = responseData.data; // Use responseData.data
|
||||
showNotification("模型列表加载成功", "success");
|
||||
return cachedModelsList;
|
||||
} else {
|
||||
console.error("Invalid model list format received:", responseData);
|
||||
throw new Error("模型列表格式无效或请求未成功");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载模型列表失败:", error);
|
||||
showNotification(`加载模型列表失败: ${error.message}`, "error");
|
||||
cachedModelsList = []; // Avoid repeated fetches on error for this session, or set to null to retry
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function renderModelsInModal() {
|
||||
if (!modelHelperListContainer) return;
|
||||
if (!cachedModelsList) {
|
||||
modelHelperListContainer.innerHTML =
|
||||
'<p class="text-gray-400 text-sm italic">模型列表尚未加载。</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
const searchTerm = modelHelperSearchInput.value.toLowerCase();
|
||||
const filteredModels = cachedModelsList.filter((model) =>
|
||||
model.id.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
|
||||
modelHelperListContainer.innerHTML = ""; // Clear previous items
|
||||
|
||||
if (filteredModels.length === 0) {
|
||||
modelHelperListContainer.innerHTML =
|
||||
'<p class="text-gray-400 text-sm italic">未找到匹配的模型。</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
filteredModels.forEach((model) => {
|
||||
const modelItemElement = document.createElement("button");
|
||||
modelItemElement.type = "button";
|
||||
modelItemElement.textContent = model.id;
|
||||
modelItemElement.className =
|
||||
"block w-full text-left px-4 py-2 rounded-md hover:bg-violet-700 focus:bg-violet-700 focus:outline-none transition-colors text-gray-200";
|
||||
// Add any other classes for styling, e.g., from existing modals or array items
|
||||
|
||||
modelItemElement.addEventListener("click", () =>
|
||||
handleModelSelection(model.id)
|
||||
);
|
||||
modelHelperListContainer.appendChild(modelItemElement);
|
||||
});
|
||||
}
|
||||
|
||||
async function openModelHelperModal() {
|
||||
if (!currentModelHelperTarget) {
|
||||
console.error("Model helper target not set.");
|
||||
showNotification("无法打开模型助手:目标未设置", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
await fetchModels(); // Ensure models are loaded
|
||||
renderModelsInModal(); // Render them (handles empty/error cases internally)
|
||||
|
||||
if (modelHelperTitleElement) {
|
||||
if (
|
||||
currentModelHelperTarget.type === "input" &&
|
||||
currentModelHelperTarget.target
|
||||
) {
|
||||
const label = document.querySelector(
|
||||
`label[for="${currentModelHelperTarget.target.id}"]`
|
||||
);
|
||||
modelHelperTitleElement.textContent = label
|
||||
? `为 "${label.textContent.trim()}" 选择模型`
|
||||
: "选择模型";
|
||||
} else if (currentModelHelperTarget.type === "array") {
|
||||
modelHelperTitleElement.textContent = `为 ${currentModelHelperTarget.targetKey} 添加模型`;
|
||||
} else {
|
||||
modelHelperTitleElement.textContent = "选择模型";
|
||||
}
|
||||
}
|
||||
if (modelHelperSearchInput) modelHelperSearchInput.value = ""; // Clear search on open
|
||||
if (modelHelperModal) openModal(modelHelperModal);
|
||||
}
|
||||
|
||||
function handleModelSelection(selectedModelId) {
|
||||
if (!currentModelHelperTarget) return;
|
||||
|
||||
if (
|
||||
currentModelHelperTarget.type === "input" &&
|
||||
currentModelHelperTarget.target
|
||||
) {
|
||||
const inputElement = currentModelHelperTarget.target;
|
||||
inputElement.value = selectedModelId;
|
||||
// If the input is a sensitive field, dispatch focusout to trigger masking behavior if needed
|
||||
if (inputElement.classList.contains(SENSITIVE_INPUT_CLASS)) {
|
||||
const event = new Event("focusout", { bubbles: true, cancelable: true });
|
||||
inputElement.dispatchEvent(event);
|
||||
}
|
||||
// Dispatch input event for any other listeners
|
||||
inputElement.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
} else if (
|
||||
currentModelHelperTarget.type === "array" &&
|
||||
currentModelHelperTarget.targetKey
|
||||
) {
|
||||
const modelId = addArrayItemWithValue(
|
||||
currentModelHelperTarget.targetKey,
|
||||
selectedModelId
|
||||
);
|
||||
if (currentModelHelperTarget.targetKey === "THINKING_MODELS" && modelId) {
|
||||
// Automatically add corresponding budget map item with default budget 0
|
||||
createAndAppendBudgetMapItem(selectedModelId, 0, modelId);
|
||||
}
|
||||
}
|
||||
|
||||
if (modelHelperModal) closeModal(modelHelperModal);
|
||||
currentModelHelperTarget = null; // Reset target
|
||||
}
|
||||
|
||||
// -- End Model Helper Functions --
|
||||
|
||||
Reference in New Issue
Block a user