mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat: Enhance CLI tools and history management
- Added CLI Manager and CLI History views to the navigation. - Implemented rendering for CLI tools with detailed status and actions. - Introduced a new CLI History view to display execution history with search and filter capabilities. - Added hooks for managing and displaying available SKILLs in the Hook Manager. - Created modals for Hook Wizards and Template View for better user interaction. - Implemented semantic search dependency checks and installation functions in CodexLens. - Updated dashboard layout to accommodate new features and improve user experience.
This commit is contained in:
@@ -1,26 +1,11 @@
|
||||
// CLI Manager View
|
||||
// Main view combining CLI status, CCW installations, and history panels
|
||||
// Main view combining CLI status and CCW installations panels (two-column layout)
|
||||
|
||||
// ========== CLI Manager State ==========
|
||||
var currentCliExecution = null;
|
||||
var cliExecutionOutput = '';
|
||||
var ccwInstallations = [];
|
||||
|
||||
// ========== Initialization ==========
|
||||
function initCliManager() {
|
||||
document.querySelectorAll('.nav-item[data-view="cli-manager"]').forEach(function(item) {
|
||||
item.addEventListener('click', function() {
|
||||
setActiveNavItem(item);
|
||||
currentView = 'cli-manager';
|
||||
currentFilter = null;
|
||||
currentLiteType = null;
|
||||
currentSessionDetailKey = null;
|
||||
updateContentTitle();
|
||||
renderCliManager();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ========== CCW Installations ==========
|
||||
async function loadCcwInstallations() {
|
||||
try {
|
||||
@@ -50,27 +35,192 @@ async function renderCliManager() {
|
||||
// Load data
|
||||
await Promise.all([
|
||||
loadCliToolStatus(),
|
||||
loadCliHistory(),
|
||||
loadCcwInstallations()
|
||||
]);
|
||||
|
||||
container.innerHTML = '<div class="cli-manager-container">' +
|
||||
'<div class="cli-manager-grid">' +
|
||||
'<div class="cli-panel"><div id="cli-status-panel"></div></div>' +
|
||||
'<div class="cli-panel"><div id="ccw-install-panel"></div></div>' +
|
||||
container.innerHTML = '<div class="status-manager">' +
|
||||
'<div class="status-two-column">' +
|
||||
'<div class="status-section" id="tools-section"></div>' +
|
||||
'<div class="status-section" id="ccw-section"></div>' +
|
||||
'</div>' +
|
||||
'<div class="cli-panel cli-panel-full"><div id="cli-history-panel"></div></div>' +
|
||||
'</div>';
|
||||
|
||||
// Render sub-panels
|
||||
renderCliStatus();
|
||||
renderCcwInstallPanel();
|
||||
renderCliHistory();
|
||||
renderToolsSection();
|
||||
renderCcwSection();
|
||||
|
||||
// Initialize Lucide icons
|
||||
if (window.lucide) lucide.createIcons();
|
||||
}
|
||||
|
||||
// ========== Tools Section (Left Column) ==========
|
||||
function renderToolsSection() {
|
||||
var container = document.getElementById('tools-section');
|
||||
if (!container) return;
|
||||
|
||||
var toolDescriptions = {
|
||||
gemini: 'Google AI for code analysis',
|
||||
qwen: 'Alibaba AI assistant',
|
||||
codex: 'OpenAI code generation'
|
||||
};
|
||||
|
||||
var tools = ['gemini', 'qwen', 'codex'];
|
||||
var available = Object.values(cliToolStatus).filter(function(t) { return t.available; }).length;
|
||||
|
||||
var toolsHtml = tools.map(function(tool) {
|
||||
var status = cliToolStatus[tool] || {};
|
||||
var isAvailable = status.available;
|
||||
var isDefault = defaultCliTool === tool;
|
||||
|
||||
return '<div class="tool-item ' + (isAvailable ? 'available' : 'unavailable') + '">' +
|
||||
'<div class="tool-item-left">' +
|
||||
'<span class="tool-status-dot ' + (isAvailable ? 'status-available' : 'status-unavailable') + '"></span>' +
|
||||
'<div class="tool-item-info">' +
|
||||
'<div class="tool-item-name">' + tool.charAt(0).toUpperCase() + tool.slice(1) +
|
||||
(isDefault ? '<span class="tool-default-badge">Default</span>' : '') +
|
||||
'</div>' +
|
||||
'<div class="tool-item-desc">' + toolDescriptions[tool] + '</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="tool-item-right">' +
|
||||
(isAvailable
|
||||
? '<span class="tool-status-text success"><i data-lucide="check-circle" class="w-3.5 h-3.5"></i> Ready</span>'
|
||||
: '<span class="tool-status-text muted"><i data-lucide="circle-dashed" class="w-3.5 h-3.5"></i> Not Installed</span>') +
|
||||
(isAvailable && !isDefault
|
||||
? '<button class="btn-sm btn-outline" onclick="setDefaultCliTool(\'' + tool + '\')"><i data-lucide="star" class="w-3 h-3"></i> Set Default</button>'
|
||||
: '') +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
|
||||
// CodexLens item
|
||||
var codexLensHtml = '<div class="tool-item ' + (codexLensStatus.ready ? 'available' : 'unavailable') + '">' +
|
||||
'<div class="tool-item-left">' +
|
||||
'<span class="tool-status-dot ' + (codexLensStatus.ready ? 'status-available' : 'status-unavailable') + '"></span>' +
|
||||
'<div class="tool-item-info">' +
|
||||
'<div class="tool-item-name">CodexLens <span class="tool-type-badge">Index</span></div>' +
|
||||
'<div class="tool-item-desc">' + (codexLensStatus.ready ? 'Code indexing & FTS search' : 'Full-text code search engine') + '</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="tool-item-right">' +
|
||||
(codexLensStatus.ready
|
||||
? '<span class="tool-status-text success"><i data-lucide="check-circle" class="w-3.5 h-3.5"></i> v' + (codexLensStatus.version || 'installed') + '</span>' +
|
||||
'<button class="btn-sm btn-outline" onclick="initCodexLensIndex()"><i data-lucide="database" class="w-3 h-3"></i> Init Index</button>'
|
||||
: '<span class="tool-status-text muted"><i data-lucide="circle-dashed" class="w-3.5 h-3.5"></i> Not Installed</span>' +
|
||||
'<button class="btn-sm btn-primary" onclick="installCodexLens()"><i data-lucide="download" class="w-3 h-3"></i> Install</button>') +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
// Semantic Search item (only show if CodexLens is installed)
|
||||
var semanticHtml = '';
|
||||
if (codexLensStatus.ready) {
|
||||
semanticHtml = '<div class="tool-item ' + (semanticStatus.available ? 'available' : 'unavailable') + '">' +
|
||||
'<div class="tool-item-left">' +
|
||||
'<span class="tool-status-dot ' + (semanticStatus.available ? 'status-available' : 'status-unavailable') + '"></span>' +
|
||||
'<div class="tool-item-info">' +
|
||||
'<div class="tool-item-name">Semantic Search <span class="tool-type-badge ai">AI</span></div>' +
|
||||
'<div class="tool-item-desc">' + (semanticStatus.available ? 'AI-powered code understanding' : 'Natural language code search') + '</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="tool-item-right">' +
|
||||
(semanticStatus.available
|
||||
? '<span class="tool-status-text success"><i data-lucide="sparkles" class="w-3.5 h-3.5"></i> ' + (semanticStatus.backend || 'Ready') + '</span>'
|
||||
: '<span class="tool-status-text muted"><i data-lucide="circle-dashed" class="w-3.5 h-3.5"></i> Not Installed</span>' +
|
||||
'<button class="btn-sm btn-primary" onclick="openSemanticInstallWizard()"><i data-lucide="brain" class="w-3 h-3"></i> Install</button>') +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
container.innerHTML = '<div class="section-header">' +
|
||||
'<div class="section-header-left">' +
|
||||
'<h3><i data-lucide="terminal" class="w-4 h-4"></i> CLI Tools</h3>' +
|
||||
'<span class="section-count">' + available + '/' + tools.length + ' available</span>' +
|
||||
'</div>' +
|
||||
'<button class="btn-icon" onclick="refreshAllCliStatus()" title="Refresh Status">' +
|
||||
'<i data-lucide="refresh-cw" class="w-4 h-4"></i>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'<div class="tools-list">' +
|
||||
toolsHtml +
|
||||
codexLensHtml +
|
||||
semanticHtml +
|
||||
'</div>';
|
||||
|
||||
if (window.lucide) lucide.createIcons();
|
||||
}
|
||||
|
||||
// ========== CCW Section (Right Column) ==========
|
||||
function renderCcwSection() {
|
||||
var container = document.getElementById('ccw-section');
|
||||
if (!container) return;
|
||||
|
||||
var installationsHtml = '';
|
||||
|
||||
if (ccwInstallations.length === 0) {
|
||||
installationsHtml = '<div class="ccw-empty-state">' +
|
||||
'<i data-lucide="package-x" class="w-8 h-8"></i>' +
|
||||
'<p>No installations found</p>' +
|
||||
'<button class="btn btn-sm btn-primary" onclick="showCcwInstallModal()">' +
|
||||
'<i data-lucide="download" class="w-3 h-3"></i> Install CCW</button>' +
|
||||
'</div>';
|
||||
} else {
|
||||
installationsHtml = '<div class="ccw-list">';
|
||||
for (var i = 0; i < ccwInstallations.length; i++) {
|
||||
var inst = ccwInstallations[i];
|
||||
var isGlobal = inst.installation_mode === 'Global';
|
||||
var modeIcon = isGlobal ? 'home' : 'folder';
|
||||
var version = inst.application_version || 'unknown';
|
||||
var installDate = new Date(inst.installation_date).toLocaleDateString();
|
||||
|
||||
installationsHtml += '<div class="ccw-item">' +
|
||||
'<div class="ccw-item-left">' +
|
||||
'<div class="ccw-item-mode ' + (isGlobal ? 'global' : 'path') + '">' +
|
||||
'<i data-lucide="' + modeIcon + '" class="w-4 h-4"></i>' +
|
||||
'</div>' +
|
||||
'<div class="ccw-item-info">' +
|
||||
'<div class="ccw-item-header">' +
|
||||
'<span class="ccw-item-name">' + inst.installation_mode + '</span>' +
|
||||
'<span class="ccw-version-tag">v' + version + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="ccw-item-path" title="' + inst.installation_path + '">' + escapeHtml(inst.installation_path) + '</div>' +
|
||||
'<div class="ccw-item-meta">' +
|
||||
'<span><i data-lucide="calendar" class="w-3 h-3"></i> ' + installDate + '</span>' +
|
||||
'<span><i data-lucide="file" class="w-3 h-3"></i> ' + (inst.files_count || 0) + ' files</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="ccw-item-actions">' +
|
||||
'<button class="btn-icon btn-icon-sm" onclick="runCcwUpgrade()" title="Upgrade">' +
|
||||
'<i data-lucide="arrow-up-circle" class="w-4 h-4"></i>' +
|
||||
'</button>' +
|
||||
'<button class="btn-icon btn-icon-sm btn-danger" onclick="confirmCcwUninstall(\'' + escapeHtml(inst.installation_path) + '\')" title="Uninstall">' +
|
||||
'<i data-lucide="trash-2" class="w-4 h-4"></i>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
installationsHtml += '</div>';
|
||||
}
|
||||
|
||||
container.innerHTML = '<div class="section-header">' +
|
||||
'<div class="section-header-left">' +
|
||||
'<h3><i data-lucide="package" class="w-4 h-4"></i> CCW Install</h3>' +
|
||||
'<span class="section-count">' + ccwInstallations.length + ' installation' + (ccwInstallations.length !== 1 ? 's' : '') + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="section-header-actions">' +
|
||||
'<button class="btn-icon" onclick="showCcwInstallModal()" title="Add Installation">' +
|
||||
'<i data-lucide="plus" class="w-4 h-4"></i>' +
|
||||
'</button>' +
|
||||
'<button class="btn-icon" onclick="loadCcwInstallations().then(function() { renderCcwSection(); if (window.lucide) lucide.createIcons(); })" title="Refresh">' +
|
||||
'<i data-lucide="refresh-cw" class="w-4 h-4"></i>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
installationsHtml;
|
||||
|
||||
if (window.lucide) lucide.createIcons();
|
||||
}
|
||||
|
||||
// CCW Install Carousel State
|
||||
var ccwCarouselIndex = 0;
|
||||
|
||||
|
||||
132
ccw/src/templates/dashboard-js/views/history.js
Normal file
132
ccw/src/templates/dashboard-js/views/history.js
Normal file
@@ -0,0 +1,132 @@
|
||||
// CLI History View
|
||||
// Standalone view for CLI execution history
|
||||
|
||||
// ========== Rendering ==========
|
||||
async function renderCliHistoryView() {
|
||||
var container = document.getElementById('mainContent');
|
||||
if (!container) return;
|
||||
|
||||
// Hide stats grid and search for History view
|
||||
var statsGrid = document.getElementById('statsGrid');
|
||||
var searchInput = document.getElementById('searchInput');
|
||||
if (statsGrid) statsGrid.style.display = 'none';
|
||||
if (searchInput) searchInput.parentElement.style.display = 'none';
|
||||
|
||||
// Load history data
|
||||
await loadCliHistory();
|
||||
|
||||
// Filter by search query
|
||||
var filteredHistory = cliHistorySearch
|
||||
? cliExecutionHistory.filter(function(exec) {
|
||||
return exec.prompt_preview.toLowerCase().includes(cliHistorySearch.toLowerCase()) ||
|
||||
exec.tool.toLowerCase().includes(cliHistorySearch.toLowerCase());
|
||||
})
|
||||
: cliExecutionHistory;
|
||||
|
||||
var historyHtml = '';
|
||||
|
||||
if (cliExecutionHistory.length === 0) {
|
||||
historyHtml = '<div class="history-empty-state">' +
|
||||
'<i data-lucide="terminal" class="w-12 h-12"></i>' +
|
||||
'<h3>No executions yet</h3>' +
|
||||
'<p>CLI execution history will appear here</p>' +
|
||||
'</div>';
|
||||
} else if (filteredHistory.length === 0) {
|
||||
historyHtml = '<div class="history-empty-state">' +
|
||||
'<i data-lucide="search-x" class="w-10 h-10"></i>' +
|
||||
'<h3>No matching results</h3>' +
|
||||
'<p>Try adjusting your search or filter</p>' +
|
||||
'</div>';
|
||||
} else {
|
||||
historyHtml = '<div class="history-list">';
|
||||
for (var i = 0; i < filteredHistory.length; i++) {
|
||||
var exec = filteredHistory[i];
|
||||
var statusIcon = exec.status === 'success' ? 'check-circle' :
|
||||
exec.status === 'timeout' ? 'clock' : 'x-circle';
|
||||
var statusClass = exec.status === 'success' ? 'success' :
|
||||
exec.status === 'timeout' ? 'warning' : 'error';
|
||||
var duration = formatDuration(exec.duration_ms);
|
||||
var timeAgo = getTimeAgo(new Date(exec.timestamp));
|
||||
|
||||
historyHtml += '<div class="history-item" onclick="showExecutionDetail(\'' + exec.id + '\')">' +
|
||||
'<div class="history-item-main">' +
|
||||
'<div class="history-item-header">' +
|
||||
'<span class="history-tool-tag tool-' + exec.tool + '">' + exec.tool + '</span>' +
|
||||
'<span class="history-mode-tag">' + (exec.mode || 'analysis') + '</span>' +
|
||||
'<span class="history-status ' + statusClass + '">' +
|
||||
'<i data-lucide="' + statusIcon + '" class="w-3.5 h-3.5"></i>' +
|
||||
exec.status +
|
||||
'</span>' +
|
||||
'</div>' +
|
||||
'<div class="history-item-prompt">' + escapeHtml(exec.prompt_preview) + '</div>' +
|
||||
'<div class="history-item-meta">' +
|
||||
'<span class="history-time"><i data-lucide="clock" class="w-3 h-3"></i> ' + timeAgo + '</span>' +
|
||||
'<span class="history-duration"><i data-lucide="timer" class="w-3 h-3"></i> ' + duration + '</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="history-item-actions">' +
|
||||
'<button class="btn-icon" onclick="event.stopPropagation(); showExecutionDetail(\'' + exec.id + '\')" title="View Details">' +
|
||||
'<i data-lucide="eye" class="w-4 h-4"></i>' +
|
||||
'</button>' +
|
||||
'<button class="btn-icon btn-danger" onclick="event.stopPropagation(); confirmDeleteExecution(\'' + exec.id + '\')" title="Delete">' +
|
||||
'<i data-lucide="trash-2" class="w-4 h-4"></i>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
historyHtml += '</div>';
|
||||
}
|
||||
|
||||
container.innerHTML = '<div class="history-view">' +
|
||||
'<div class="history-header">' +
|
||||
'<div class="history-header-left">' +
|
||||
'<span class="history-count">' + cliExecutionHistory.length + ' execution' + (cliExecutionHistory.length !== 1 ? 's' : '') + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="history-header-right">' +
|
||||
'<div class="history-search-wrapper">' +
|
||||
'<i data-lucide="search" class="w-4 h-4"></i>' +
|
||||
'<input type="text" class="history-search-input" placeholder="Search executions..." ' +
|
||||
'value="' + escapeHtml(cliHistorySearch) + '" ' +
|
||||
'onkeyup="searchCliHistoryView(this.value)" oninput="searchCliHistoryView(this.value)">' +
|
||||
'</div>' +
|
||||
'<select class="history-filter-select" onchange="filterCliHistoryView(this.value)">' +
|
||||
'<option value=""' + (cliHistoryFilter === null ? ' selected' : '') + '>All Tools</option>' +
|
||||
'<option value="gemini"' + (cliHistoryFilter === 'gemini' ? ' selected' : '') + '>Gemini</option>' +
|
||||
'<option value="qwen"' + (cliHistoryFilter === 'qwen' ? ' selected' : '') + '>Qwen</option>' +
|
||||
'<option value="codex"' + (cliHistoryFilter === 'codex' ? ' selected' : '') + '>Codex</option>' +
|
||||
'</select>' +
|
||||
'<button class="btn-icon" onclick="refreshCliHistoryView()" title="Refresh">' +
|
||||
'<i data-lucide="refresh-cw" class="w-4 h-4"></i>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
historyHtml +
|
||||
'</div>';
|
||||
|
||||
// Initialize Lucide icons
|
||||
if (window.lucide) lucide.createIcons();
|
||||
}
|
||||
|
||||
// ========== Actions ==========
|
||||
async function filterCliHistoryView(tool) {
|
||||
cliHistoryFilter = tool || null;
|
||||
await loadCliHistory();
|
||||
renderCliHistoryView();
|
||||
}
|
||||
|
||||
function searchCliHistoryView(query) {
|
||||
cliHistorySearch = query;
|
||||
renderCliHistoryView();
|
||||
// Preserve focus and cursor position
|
||||
var searchInput = document.querySelector('.history-search-input');
|
||||
if (searchInput) {
|
||||
searchInput.focus();
|
||||
searchInput.setSelectionRange(query.length, query.length);
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshCliHistoryView() {
|
||||
await loadCliHistory();
|
||||
renderCliHistoryView();
|
||||
showRefreshToast('History refreshed', 'success');
|
||||
}
|
||||
@@ -74,6 +74,22 @@ async function renderHookManager() {
|
||||
`}
|
||||
</div>
|
||||
|
||||
<!-- Hook Wizards -->
|
||||
<div class="hook-section mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<h3 class="text-lg font-semibold text-foreground">Hook Wizards</h3>
|
||||
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-success/20 text-success">Guided Setup</span>
|
||||
</div>
|
||||
<span class="text-sm text-muted-foreground">Configure complex hooks with guided wizards</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
${renderWizardCard('memory-update')}
|
||||
${renderWizardCard('skill-context')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Install Templates -->
|
||||
<div class="hook-section">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
@@ -134,11 +150,112 @@ async function renderHookManager() {
|
||||
|
||||
// Attach event listeners
|
||||
attachHookEventListeners();
|
||||
|
||||
|
||||
// Initialize Lucide icons
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
}
|
||||
|
||||
// Load available SKILLs for skill-context wizard
|
||||
async function loadAvailableSkills() {
|
||||
try {
|
||||
const response = await fetch(`/api/skills?path=${encodeURIComponent(projectPath)}`);
|
||||
if (!response.ok) throw new Error('Failed to load skills');
|
||||
const data = await response.json();
|
||||
|
||||
const container = document.getElementById('skill-discovery-skill-context');
|
||||
if (container && data.skills) {
|
||||
if (data.skills.length === 0) {
|
||||
container.innerHTML = `
|
||||
<span class="font-mono bg-muted px-1.5 py-0.5 rounded">Available SKILLs:</span>
|
||||
<span class="text-muted-foreground ml-2">No SKILLs found in .claude/skills/</span>
|
||||
`;
|
||||
} else {
|
||||
const skillBadges = data.skills.map(skill => `
|
||||
<span class="px-2 py-0.5 bg-emerald-500/10 text-emerald-500 rounded" title="${escapeHtml(skill.description)}">${escapeHtml(skill.name)}</span>
|
||||
`).join('');
|
||||
container.innerHTML = `
|
||||
<span class="font-mono bg-muted px-1.5 py-0.5 rounded">Available SKILLs:</span>
|
||||
<div class="flex flex-wrap gap-1 mt-1">${skillBadges}</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Store skills for wizard use
|
||||
window.availableSkills = data.skills || [];
|
||||
} catch (err) {
|
||||
console.error('Failed to load skills:', err);
|
||||
const container = document.getElementById('skill-discovery-skill-context');
|
||||
if (container) {
|
||||
container.innerHTML = `
|
||||
<span class="font-mono bg-muted px-1.5 py-0.5 rounded">Available SKILLs:</span>
|
||||
<span class="text-destructive ml-2">Error loading skills</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call loadAvailableSkills after rendering hook manager
|
||||
const originalRenderHookManager = typeof renderHookManager === 'function' ? renderHookManager : null;
|
||||
|
||||
function renderWizardCard(wizardId) {
|
||||
const wizard = WIZARD_TEMPLATES[wizardId];
|
||||
if (!wizard) return '';
|
||||
|
||||
// Determine what to show in the tools/skills section
|
||||
const toolsSection = wizard.requiresSkillDiscovery
|
||||
? `
|
||||
<div class="flex items-center gap-2 text-xs text-muted-foreground mb-4">
|
||||
<span class="font-mono bg-muted px-1.5 py-0.5 rounded">Event:</span>
|
||||
<span class="px-2 py-0.5 bg-amber-500/10 text-amber-500 rounded">UserPromptSubmit</span>
|
||||
</div>
|
||||
<div id="skill-discovery-${wizardId}" class="text-xs text-muted-foreground mb-4">
|
||||
<span class="font-mono bg-muted px-1.5 py-0.5 rounded">Available SKILLs:</span>
|
||||
<span class="text-muted-foreground ml-2">Loading...</span>
|
||||
</div>
|
||||
`
|
||||
: `
|
||||
<div class="flex items-center gap-2 text-xs text-muted-foreground mb-4">
|
||||
<span class="font-mono bg-muted px-1.5 py-0.5 rounded">CLI Tools:</span>
|
||||
<span class="px-2 py-0.5 bg-blue-500/10 text-blue-500 rounded">gemini</span>
|
||||
<span class="px-2 py-0.5 bg-purple-500/10 text-purple-500 rounded">qwen</span>
|
||||
<span class="px-2 py-0.5 bg-green-500/10 text-green-500 rounded">codex</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return `
|
||||
<div class="hook-wizard-card bg-gradient-to-br from-primary/5 to-primary/10 border border-primary/20 rounded-lg p-5 hover:shadow-lg transition-all">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2.5 bg-primary/10 rounded-lg">
|
||||
<i data-lucide="${wizard.icon}" class="w-6 h-6 text-primary"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-foreground">${escapeHtml(wizard.name)}</h4>
|
||||
<p class="text-sm text-muted-foreground">${escapeHtml(wizard.description)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 mb-4">
|
||||
${wizard.options.map(opt => `
|
||||
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<i data-lucide="check" class="w-4 h-4 text-success"></i>
|
||||
<span>${escapeHtml(opt.name)}: ${escapeHtml(opt.description)}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
${toolsSection}
|
||||
|
||||
<button class="w-full px-4 py-2.5 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity flex items-center justify-center gap-2"
|
||||
onclick="openHookWizardModal('${wizardId}')">
|
||||
<i data-lucide="wand-2" class="w-4 h-4"></i>
|
||||
Open Wizard
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function countHooks(hooks) {
|
||||
let count = 0;
|
||||
for (const event of Object.keys(hooks)) {
|
||||
@@ -214,6 +331,8 @@ function renderHooksByEvent(hooks, scope) {
|
||||
|
||||
function renderQuickInstallCard(templateId, title, description, event, matcher) {
|
||||
const isInstalled = isHookTemplateInstalled(templateId);
|
||||
const template = HOOK_TEMPLATES[templateId];
|
||||
const category = template?.category || 'general';
|
||||
|
||||
return `
|
||||
<div class="hook-template-card bg-card border border-border rounded-lg p-4 hover:shadow-md transition-all ${isInstalled ? 'border-success bg-success-light/30' : ''}">
|
||||
@@ -225,6 +344,11 @@ function renderQuickInstallCard(templateId, title, description, event, matcher)
|
||||
<p class="text-xs text-muted-foreground">${escapeHtml(description)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="p-1.5 text-muted-foreground hover:text-foreground hover:bg-hover rounded transition-colors"
|
||||
onclick="viewTemplateDetails('${templateId}')"
|
||||
title="View template details">
|
||||
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="hook-template-meta text-xs text-muted-foreground mb-3 flex items-center gap-3">
|
||||
@@ -234,6 +358,7 @@ function renderQuickInstallCard(templateId, title, description, event, matcher)
|
||||
<span class="flex items-center gap-1">
|
||||
Matches: <span class="font-medium">${matcher}</span>
|
||||
</span>
|
||||
<span class="px-1.5 py-0.5 bg-primary/10 text-primary rounded text-xs">${category}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user