diff --git a/ccw/src/templates/dashboard-css/10-cli-status.css b/ccw/src/templates/dashboard-css/10-cli-status.css
index 8522a709..3af1a485 100644
--- a/ccw/src/templates/dashboard-css/10-cli-status.css
+++ b/ccw/src/templates/dashboard-css/10-cli-status.css
@@ -284,6 +284,37 @@
border-radius: 0.25rem;
}
+/* Tag color variants */
+.tag-item.tag-分析, .tool-tag.tag-分析 {
+ background: hsl(210 100% 50% / 0.15);
+ color: hsl(210 100% 45%);
+}
+
+.tag-item.tag-编码, .tool-tag.tag-编码 {
+ background: hsl(142 76% 36% / 0.15);
+ color: hsl(142 76% 36%);
+}
+
+.tag-item.tag-Debug, .tool-tag.tag-Debug {
+ background: hsl(0 84% 60% / 0.15);
+ color: hsl(0 84% 50%);
+}
+
+.tag-item.tag-重构, .tool-tag.tag-重构 {
+ background: hsl(280 70% 50% / 0.15);
+ color: hsl(280 70% 45%);
+}
+
+.tag-item.tag-测试, .tool-tag.tag-测试 {
+ background: hsl(45 93% 47% / 0.15);
+ color: hsl(45 93% 35%);
+}
+
+.tag-item.tag-文档, .tool-tag.tag-文档 {
+ background: hsl(180 70% 40% / 0.15);
+ color: hsl(180 70% 35%);
+}
+
.tag-remove {
display: flex;
align-items: center;
@@ -334,6 +365,15 @@
border-color: hsl(var(--primary) / 0.3);
}
+.predefined-tag-btn.selected,
+.predefined-tag-btn:disabled {
+ background: hsl(var(--primary) / 0.1);
+ color: hsl(var(--muted-foreground));
+ border-color: hsl(var(--primary) / 0.2);
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
.predefined-tag-btn i {
opacity: 0.7;
}
diff --git a/ccw/src/templates/dashboard-js/views/cli-manager.js b/ccw/src/templates/dashboard-js/views/cli-manager.js
index d2e8af1e..364ea35a 100644
--- a/ccw/src/templates/dashboard-js/views/cli-manager.js
+++ b/ccw/src/templates/dashboard-js/views/cli-manager.js
@@ -335,7 +335,7 @@ function buildToolConfigModalContent(tool, config, models, status) {
'
Tags (optional labels)
' +
'' +
(config.tags || []).map(function(tag) {
- return '' + escapeHtml(tag) + '';
+ return '' + escapeHtml(tag) + '';
}).join('') +
'' +
'
' +
@@ -375,7 +375,7 @@ function initToolConfigModalEvents(tool, currentConfig, models) {
// Insert tags before the input
currentTags.forEach(function(tag) {
var tagEl = document.createElement('span');
- tagEl.className = 'tag-item';
+ tagEl.className = 'tag-item tag-' + escapeHtml(tag);
tagEl.innerHTML = escapeHtml(tag) + '';
container.insertBefore(tagEl, input);
});
@@ -389,6 +389,18 @@ function initToolConfigModalEvents(tool, currentConfig, models) {
renderTags();
};
});
+
+ // Update predefined tag buttons state
+ document.querySelectorAll('.predefined-tag-btn').forEach(function(btn) {
+ var tag = btn.getAttribute('data-tag');
+ if (currentTags.indexOf(tag) !== -1) {
+ btn.classList.add('selected');
+ btn.disabled = true;
+ } else {
+ btn.classList.remove('selected');
+ btn.disabled = false;
+ }
+ });
}
// Click on unified input container focuses the input
@@ -665,10 +677,10 @@ function renderToolsSection() {
var toolConfig = cliToolConfig && cliToolConfig.tools ? cliToolConfig.tools[tool] : null;
var tags = toolConfig && toolConfig.tags ? toolConfig.tags : [];
- // Build tags HTML
+ // Build tags HTML with color classes
var tagsHtml = tags.length > 0
? '' + tags.map(function(tag) {
- return '' + escapeHtml(tag) + '';
+ return '' + escapeHtml(tag) + '';
}).join('') + '
'
: '';
diff --git a/ccw/src/tools/cli-config-manager.ts b/ccw/src/tools/cli-config-manager.ts
index 71847bd8..13047dc8 100644
--- a/ccw/src/tools/cli-config-manager.ts
+++ b/ccw/src/tools/cli-config-manager.ts
@@ -185,6 +185,24 @@ export function getToolConfig(baseDir: string, tool: string): CliToolConfig {
return config.tools[tool] || DEFAULT_CONFIG.tools[tool];
}
+/**
+ * Validate and sanitize tags array
+ * @param tags - Raw tags array from user input
+ * @returns Sanitized tags array
+ */
+function validateTags(tags: string[] | undefined): string[] | undefined {
+ if (!tags || !Array.isArray(tags)) return undefined;
+
+ const MAX_TAGS = 10;
+ const MAX_TAG_LENGTH = 30;
+
+ return tags
+ .filter(tag => typeof tag === 'string')
+ .map(tag => tag.trim())
+ .filter(tag => tag.length > 0 && tag.length <= MAX_TAG_LENGTH)
+ .slice(0, MAX_TAGS);
+}
+
/**
* Update configuration for a specific tool
* Returns the updated tool config
@@ -206,7 +224,7 @@ export function updateToolConfig(
enabled: updates.enabled !== undefined ? updates.enabled : currentToolConfig.enabled,
primaryModel: updates.primaryModel || currentToolConfig.primaryModel,
secondaryModel: updates.secondaryModel || currentToolConfig.secondaryModel,
- tags: updates.tags !== undefined ? updates.tags : currentToolConfig.tags
+ tags: updates.tags !== undefined ? validateTags(updates.tags) : currentToolConfig.tags
};
// Save updated config