Skip to content
This repository was archived by the owner on Jun 18, 2026. It is now read-only.

Commit 30ff742

Browse files
author
catlog22
committed
feat: 添加高可用性模型池支持,优化路径解析功能
1 parent 8416882 commit 30ff742

3 files changed

Lines changed: 68 additions & 9 deletions

File tree

ccw/src/templates/dashboard-css/31-api-settings.css

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,55 @@ select.cli-input {
11221122
opacity: 0.5;
11231123
}
11241124

1125+
/* ===========================
1126+
Model Pools (High Availability)
1127+
=========================== */
1128+
1129+
.model-pools-list {
1130+
flex: 1;
1131+
overflow-y: auto;
1132+
}
1133+
1134+
.pool-type-group {
1135+
margin-bottom: 1rem;
1136+
}
1137+
1138+
.pool-type-header {
1139+
padding: 0.5rem 0.75rem;
1140+
font-size: 0.7rem;
1141+
font-weight: 600;
1142+
text-transform: uppercase;
1143+
color: hsl(var(--muted-foreground));
1144+
background: hsl(var(--muted) / 0.3);
1145+
border-bottom: 1px solid hsl(var(--border));
1146+
}
1147+
1148+
.pool-item {
1149+
padding: 0.75rem;
1150+
cursor: pointer;
1151+
border-bottom: 1px solid hsl(var(--border));
1152+
transition: background-color 0.15s;
1153+
}
1154+
1155+
.pool-item:hover {
1156+
background: hsl(var(--muted) / 0.3);
1157+
}
1158+
1159+
.pool-item.selected {
1160+
background: hsl(var(--primary) / 0.1);
1161+
border-left: 3px solid hsl(var(--primary));
1162+
}
1163+
1164+
.pool-item .pool-name {
1165+
font-weight: 500;
1166+
margin-bottom: 0.25rem;
1167+
}
1168+
1169+
.pool-item .pool-target {
1170+
font-size: 0.75rem;
1171+
color: hsl(var(--muted-foreground));
1172+
}
1173+
11251174
/* Responsive adjustments for tabs */
11261175
@media (max-width: 768px) {
11271176
.sidebar-tab {

ccw/src/templates/dashboard-js/views/api-settings.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2396,7 +2396,7 @@ function deleteModel(providerId, modelId, modelType) {
23962396
});
23972397
})
23982398
.then(function() {
2399-
return loadApiSettings();
2399+
return loadApiSettings(true); // Force refresh to get updated data
24002400
})
24012401
.then(function() {
24022402
if (selectedProviderId === providerId) {
@@ -4091,23 +4091,21 @@ function renderModelPoolsList() {
40914091
type === 'llm' ? t('apiSettings.llmPools') :
40924092
t('apiSettings.rerankerPools');
40934093

4094-
html += '<div class="pool-type-group" style="margin-bottom: 1.5rem;">' +
4095-
'<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);">' +
4096-
typeLabel +
4097-
'</div>';
4094+
html += '<div class="pool-type-group">' +
4095+
'<div class="pool-type-header">' + typeLabel + '</div>';
40984096

40994097
pools.forEach(function(pool) {
41004098
var isSelected = selectedPoolId === pool.id;
41014099
var statusClass = pool.enabled ? 'status-enabled' : 'status-disabled';
41024100
var statusText = pool.enabled ? t('common.enabled') : t('common.disabled');
41034101

4104-
html += '<div class="pool-item' + (isSelected ? ' selected' : '') + '" onclick="selectModelPool(\'' + pool.id + '\')" style="padding: 0.75rem; cursor: pointer; border-bottom: 1px solid var(--border);">' +
4102+
html += '<div class="pool-item' + (isSelected ? ' selected' : '') + '" onclick="selectModelPool(\'' + pool.id + '\')">' +
41054103
'<div style="display: flex; justify-content: space-between; align-items: center;">' +
41064104
'<div style="flex: 1; min-width: 0;">' +
4107-
'<div style="font-weight: 500; margin-bottom: 0.25rem;">' + escapeHtml(pool.name || pool.targetModel) + '</div>' +
4108-
'<div style="font-size: 0.75rem; color: var(--text-secondary);">' + escapeHtml(pool.targetModel) + '</div>' +
4105+
'<div class="pool-name">' + escapeHtml(pool.name || pool.targetModel) + '</div>' +
4106+
'<div class="pool-target">' + escapeHtml(pool.targetModel) + '</div>' +
41094107
'</div>' +
4110-
'<span class="status-badge ' + statusClass + '" style="font-size: 0.7rem; padding: 0.25rem 0.5rem; border-radius: 4px;">' + statusText + '</span>' +
4108+
'<span class="status-badge ' + statusClass + '">' + statusText + '</span>' +
41114109
'</div>' +
41124110
'</div>';
41134111
});

ccw/src/utils/path-resolver.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface ValidatePathOptions {
2323

2424
/**
2525
* Resolve a path, handling ~ for home directory
26+
* Also handles Windows drive-relative paths (e.g., "D:path" -> "D:\path")
2627
* @param inputPath - Path to resolve
2728
* @returns Absolute path
2829
*/
@@ -34,6 +35,17 @@ export function resolvePath(inputPath: string): string {
3435
return join(homedir(), inputPath.slice(1));
3536
}
3637

38+
// Handle Windows drive-relative paths (e.g., "D:path" without backslash)
39+
// Pattern: single letter followed by colon, then immediately a non-slash character
40+
// This converts "D:path" to "D:\path" to make it absolute
41+
if (process.platform === 'win32' || /^[a-zA-Z]:/.test(inputPath)) {
42+
const driveRelativeMatch = inputPath.match(/^([a-zA-Z]:)([^/\\].*)$/);
43+
if (driveRelativeMatch) {
44+
// Insert backslash after drive letter
45+
inputPath = driveRelativeMatch[1] + '\\' + driveRelativeMatch[2];
46+
}
47+
}
48+
3749
return resolve(inputPath);
3850
}
3951

0 commit comments

Comments
 (0)