From 30ff74231019383966a1c3df31dd5eb1afb6cc8b Mon Sep 17 00:00:00 2001 From: catlog22 Date: Thu, 8 Jan 2026 23:54:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=AB=98=E5=8F=AF?= =?UTF-8?q?=E7=94=A8=E6=80=A7=E6=A8=A1=E5=9E=8B=E6=B1=A0=E6=94=AF=E6=8C=81?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E8=B7=AF=E5=BE=84=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard-css/31-api-settings.css | 49 +++++++++++++++++++ .../dashboard-js/views/api-settings.js | 16 +++--- ccw/src/utils/path-resolver.ts | 12 +++++ 3 files changed, 68 insertions(+), 9 deletions(-) diff --git a/ccw/src/templates/dashboard-css/31-api-settings.css b/ccw/src/templates/dashboard-css/31-api-settings.css index c5475386..29dc1a4e 100644 --- a/ccw/src/templates/dashboard-css/31-api-settings.css +++ b/ccw/src/templates/dashboard-css/31-api-settings.css @@ -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 { diff --git a/ccw/src/templates/dashboard-js/views/api-settings.js b/ccw/src/templates/dashboard-js/views/api-settings.js index 3bbbda51..05bf9c71 100644 --- a/ccw/src/templates/dashboard-js/views/api-settings.js +++ b/ccw/src/templates/dashboard-js/views/api-settings.js @@ -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 += '
' + - '
' + - typeLabel + - '
'; + html += '
' + + '
' + typeLabel + '
'; 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 += '
' + + html += '
' + '
' + '
' + - '
' + escapeHtml(pool.name || pool.targetModel) + '
' + - '
' + escapeHtml(pool.targetModel) + '
' + + '
' + escapeHtml(pool.name || pool.targetModel) + '
' + + '
' + escapeHtml(pool.targetModel) + '
' + '
' + - '' + statusText + '' + + '' + statusText + '' + '
' + '
'; }); diff --git a/ccw/src/utils/path-resolver.ts b/ccw/src/utils/path-resolver.ts index db1a4eaa..25f3c22b 100644 --- a/ccw/src/utils/path-resolver.ts +++ b/ccw/src/utils/path-resolver.ts @@ -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); }