feat: 添加高可用性模型池支持,优化路径解析功能

This commit is contained in:
catlog22
2026-01-08 23:54:32 +08:00
parent 84168825d6
commit 30ff742310
3 changed files with 68 additions and 9 deletions

View File

@@ -1122,6 +1122,55 @@ select.cli-input {
opacity: 0.5;
}
/* ===========================
Model Pools (High Availability)
=========================== */
.model-pools-list {
flex: 1;
overflow-y: auto;
}
.pool-type-group {
margin-bottom: 1rem;
}
.pool-type-header {
padding: 0.5rem 0.75rem;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
color: hsl(var(--muted-foreground));
background: hsl(var(--muted) / 0.3);
border-bottom: 1px solid hsl(var(--border));
}
.pool-item {
padding: 0.75rem;
cursor: pointer;
border-bottom: 1px solid hsl(var(--border));
transition: background-color 0.15s;
}
.pool-item:hover {
background: hsl(var(--muted) / 0.3);
}
.pool-item.selected {
background: hsl(var(--primary) / 0.1);
border-left: 3px solid hsl(var(--primary));
}
.pool-item .pool-name {
font-weight: 500;
margin-bottom: 0.25rem;
}
.pool-item .pool-target {
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
}
/* Responsive adjustments for tabs */
@media (max-width: 768px) {
.sidebar-tab {

View File

@@ -2396,7 +2396,7 @@ function deleteModel(providerId, modelId, modelType) {
});
})
.then(function() {
return loadApiSettings();
return loadApiSettings(true); // Force refresh to get updated data
})
.then(function() {
if (selectedProviderId === providerId) {
@@ -4091,23 +4091,21 @@ function renderModelPoolsList() {
type === 'llm' ? t('apiSettings.llmPools') :
t('apiSettings.rerankerPools');
html += '<div class="pool-type-group" style="margin-bottom: 1.5rem;">' +
'<div class="pool-type-header" style="padding: 0.5rem; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; color: var(--text-secondary); border-bottom: 1px solid var(--border);">' +
typeLabel +
'</div>';
html += '<div class="pool-type-group">' +
'<div class="pool-type-header">' + typeLabel + '</div>';
pools.forEach(function(pool) {
var isSelected = selectedPoolId === pool.id;
var statusClass = pool.enabled ? 'status-enabled' : 'status-disabled';
var statusText = pool.enabled ? t('common.enabled') : t('common.disabled');
html += '<div class="pool-item' + (isSelected ? ' selected' : '') + '" onclick="selectModelPool(\'' + pool.id + '\')" style="padding: 0.75rem; cursor: pointer; border-bottom: 1px solid var(--border);">' +
html += '<div class="pool-item' + (isSelected ? ' selected' : '') + '" onclick="selectModelPool(\'' + pool.id + '\')">' +
'<div style="display: flex; justify-content: space-between; align-items: center;">' +
'<div style="flex: 1; min-width: 0;">' +
'<div style="font-weight: 500; margin-bottom: 0.25rem;">' + escapeHtml(pool.name || pool.targetModel) + '</div>' +
'<div style="font-size: 0.75rem; color: var(--text-secondary);">' + escapeHtml(pool.targetModel) + '</div>' +
'<div class="pool-name">' + escapeHtml(pool.name || pool.targetModel) + '</div>' +
'<div class="pool-target">' + escapeHtml(pool.targetModel) + '</div>' +
'</div>' +
'<span class="status-badge ' + statusClass + '" style="font-size: 0.7rem; padding: 0.25rem 0.5rem; border-radius: 4px;">' + statusText + '</span>' +
'<span class="status-badge ' + statusClass + '">' + statusText + '</span>' +
'</div>' +
'</div>';
});

View File

@@ -23,6 +23,7 @@ export interface ValidatePathOptions {
/**
* Resolve a path, handling ~ for home directory
* Also handles Windows drive-relative paths (e.g., "D:path" -> "D:\path")
* @param inputPath - Path to resolve
* @returns Absolute path
*/
@@ -34,6 +35,17 @@ export function resolvePath(inputPath: string): string {
return join(homedir(), inputPath.slice(1));
}
// Handle Windows drive-relative paths (e.g., "D:path" without backslash)
// Pattern: single letter followed by colon, then immediately a non-slash character
// This converts "D:path" to "D:\path" to make it absolute
if (process.platform === 'win32' || /^[a-zA-Z]:/.test(inputPath)) {
const driveRelativeMatch = inputPath.match(/^([a-zA-Z]:)([^/\\].*)$/);
if (driveRelativeMatch) {
// Insert backslash after drive letter
inputPath = driveRelativeMatch[1] + '\\' + driveRelativeMatch[2];
}
}
return resolve(inputPath);
}