mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-13 02:41:50 +08:00
Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -110,7 +110,7 @@ function renderCarouselSlide(direction = 'none') {
|
||||
<div class="carousel-empty flex items-center justify-center h-full text-muted-foreground">
|
||||
<div class="text-center">
|
||||
<div class="text-3xl mb-2">🎯</div>
|
||||
<p class="text-sm">No active sessions</p>
|
||||
<p class="text-sm">${t('carousel.noActiveSessions')}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -172,7 +172,7 @@ function renderCarouselSlide(direction = 'none') {
|
||||
<!-- Progress -->
|
||||
<div class="mb-2">
|
||||
<div class="flex items-center justify-between text-xs mb-1">
|
||||
<span class="text-muted-foreground">Progress</span>
|
||||
<span class="text-muted-foreground">${t('session.progress')}</span>
|
||||
<span class="text-foreground font-medium">${completed}/${taskCount}</span>
|
||||
</div>
|
||||
<div class="h-1.5 bg-muted rounded-full overflow-hidden">
|
||||
@@ -196,7 +196,7 @@ function renderCarouselSlide(direction = 'none') {
|
||||
|
||||
<!-- Right Column: Task List -->
|
||||
<div class="w-[45%] flex flex-col border-l border-border pl-3">
|
||||
<div class="text-xs font-medium text-muted-foreground mb-1.5">Recent Tasks</div>
|
||||
<div class="text-xs font-medium text-muted-foreground mb-1.5">${t('tab.tasks')}</div>
|
||||
<div class="task-list flex-1 space-y-1 overflow-hidden">
|
||||
${displayTasks.length > 0 ? displayTasks.map(task => `
|
||||
<div class="flex items-center gap-1.5 text-xs">
|
||||
@@ -204,7 +204,7 @@ function renderCarouselSlide(direction = 'none') {
|
||||
<span class="truncate flex-1 ${task.status === 'in_progress' ? 'text-foreground font-medium' : 'text-muted-foreground'}">${escapeHtml(task.title || task.id || 'Task')}</span>
|
||||
</div>
|
||||
`).join('') : `
|
||||
<div class="text-xs text-muted-foreground">No tasks yet</div>
|
||||
<div class="text-xs text-muted-foreground">${t('empty.noTasks')}</div>
|
||||
`}
|
||||
</div>
|
||||
<!-- Progress percentage -->
|
||||
|
||||
@@ -440,8 +440,59 @@ function renderWizardModalContent() {
|
||||
if (!container || !currentWizardTemplate) return;
|
||||
|
||||
const wizard = currentWizardTemplate;
|
||||
const wizardId = wizard.id;
|
||||
const selectedOption = wizardConfig.triggerType || wizard.options[0].id;
|
||||
|
||||
// Get translated wizard name and description
|
||||
const wizardName = wizardId === 'memory-update' ? t('hook.wizard.memoryUpdate') :
|
||||
wizardId === 'skill-context' ? t('hook.wizard.skillContext') : wizard.name;
|
||||
const wizardDesc = wizardId === 'memory-update' ? t('hook.wizard.memoryUpdateDesc') :
|
||||
wizardId === 'skill-context' ? t('hook.wizard.skillContextDesc') : wizard.description;
|
||||
|
||||
// Helper to get translated option names
|
||||
const getOptionName = (optId) => {
|
||||
if (wizardId === 'memory-update') {
|
||||
if (optId === 'on-stop') return t('hook.wizard.onSessionEnd');
|
||||
if (optId === 'periodic') return t('hook.wizard.periodicUpdate');
|
||||
}
|
||||
if (wizardId === 'skill-context') {
|
||||
if (optId === 'keyword') return t('hook.wizard.keywordMatching');
|
||||
if (optId === 'auto') return t('hook.wizard.autoDetection');
|
||||
}
|
||||
return wizard.options.find(o => o.id === optId)?.name || '';
|
||||
};
|
||||
|
||||
const getOptionDesc = (optId) => {
|
||||
if (wizardId === 'memory-update') {
|
||||
if (optId === 'on-stop') return t('hook.wizard.onSessionEndDesc');
|
||||
if (optId === 'periodic') return t('hook.wizard.periodicUpdateDesc');
|
||||
}
|
||||
if (wizardId === 'skill-context') {
|
||||
if (optId === 'keyword') return t('hook.wizard.keywordMatchingDesc');
|
||||
if (optId === 'auto') return t('hook.wizard.autoDetectionDesc');
|
||||
}
|
||||
return wizard.options.find(o => o.id === optId)?.description || '';
|
||||
};
|
||||
|
||||
// Helper to get translated field labels
|
||||
const getFieldLabel = (fieldKey) => {
|
||||
const labels = {
|
||||
'tool': t('hook.wizard.cliTool'),
|
||||
'interval': t('hook.wizard.intervalSeconds'),
|
||||
'strategy': t('hook.wizard.updateStrategy')
|
||||
};
|
||||
return labels[fieldKey] || wizard.configFields.find(f => f.key === fieldKey)?.label || fieldKey;
|
||||
};
|
||||
|
||||
const getFieldDesc = (fieldKey) => {
|
||||
const descs = {
|
||||
'tool': t('hook.wizard.toolForDocGen'),
|
||||
'interval': t('hook.wizard.timeBetweenUpdates'),
|
||||
'strategy': t('hook.wizard.relatedStrategy')
|
||||
};
|
||||
return descs[fieldKey] || wizard.configFields.find(f => f.key === fieldKey)?.description || '';
|
||||
};
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="space-y-6">
|
||||
<!-- Wizard Header -->
|
||||
@@ -450,14 +501,14 @@ function renderWizardModalContent() {
|
||||
<i data-lucide="${wizard.icon}" class="w-6 h-6 text-primary"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-foreground">${escapeHtml(wizard.name)}</h3>
|
||||
<p class="text-sm text-muted-foreground">${escapeHtml(wizard.description)}</p>
|
||||
<h3 class="text-lg font-semibold text-foreground">${escapeHtml(wizardName)}</h3>
|
||||
<p class="text-sm text-muted-foreground">${escapeHtml(wizardDesc)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Trigger Type Selection -->
|
||||
<div class="space-y-3">
|
||||
<label class="block text-sm font-medium text-foreground">When to Trigger</label>
|
||||
<label class="block text-sm font-medium text-foreground">${t('hook.wizard.whenToTrigger')}</label>
|
||||
<div class="grid grid-cols-1 gap-3">
|
||||
${wizard.options.map(opt => `
|
||||
<label class="flex items-start gap-3 p-3 border rounded-lg cursor-pointer transition-all ${selectedOption === opt.id ? 'border-primary bg-primary/5' : 'border-border hover:border-muted-foreground'}">
|
||||
@@ -466,8 +517,8 @@ function renderWizardModalContent() {
|
||||
onchange="updateWizardTrigger('${opt.id}')"
|
||||
class="mt-1">
|
||||
<div class="flex-1">
|
||||
<span class="font-medium text-foreground">${escapeHtml(opt.name)}</span>
|
||||
<p class="text-sm text-muted-foreground">${escapeHtml(opt.description)}</p>
|
||||
<span class="font-medium text-foreground">${escapeHtml(getOptionName(opt.id))}</span>
|
||||
<p class="text-sm text-muted-foreground">${escapeHtml(getOptionDesc(opt.id))}</p>
|
||||
</div>
|
||||
</label>
|
||||
`).join('')}
|
||||
@@ -476,18 +527,20 @@ function renderWizardModalContent() {
|
||||
|
||||
<!-- Configuration Fields -->
|
||||
<div class="space-y-4">
|
||||
<label class="block text-sm font-medium text-foreground">Configuration</label>
|
||||
<label class="block text-sm font-medium text-foreground">${t('hook.wizard.configuration')}</label>
|
||||
${wizard.customRenderer ? window[wizard.customRenderer]() : wizard.configFields.map(field => {
|
||||
// Check if field should be shown for current trigger type
|
||||
const shouldShow = !field.showFor || field.showFor.includes(selectedOption);
|
||||
if (!shouldShow) return '';
|
||||
|
||||
const value = wizardConfig[field.key] ?? field.default;
|
||||
const fieldLabel = getFieldLabel(field.key);
|
||||
const fieldDesc = getFieldDesc(field.key);
|
||||
|
||||
if (field.type === 'select') {
|
||||
return `
|
||||
<div class="space-y-1">
|
||||
<label class="block text-sm text-muted-foreground">${escapeHtml(field.label)}</label>
|
||||
<label class="block text-sm text-muted-foreground">${escapeHtml(fieldLabel)}</label>
|
||||
<select id="wizard_${field.key}"
|
||||
onchange="updateWizardConfig('${field.key}', this.value)"
|
||||
class="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-primary">
|
||||
@@ -495,13 +548,13 @@ function renderWizardModalContent() {
|
||||
<option value="${opt}" ${value === opt ? 'selected' : ''}>${opt}</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
${field.description ? `<p class="text-xs text-muted-foreground">${escapeHtml(field.description)}</p>` : ''}
|
||||
${fieldDesc ? `<p class="text-xs text-muted-foreground">${escapeHtml(fieldDesc)}</p>` : ''}
|
||||
</div>
|
||||
`;
|
||||
} else if (field.type === 'number') {
|
||||
return `
|
||||
<div class="space-y-1">
|
||||
<label class="block text-sm text-muted-foreground">${escapeHtml(field.label)}</label>
|
||||
<label class="block text-sm text-muted-foreground">${escapeHtml(fieldLabel)}</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="number" id="wizard_${field.key}"
|
||||
value="${value}"
|
||||
@@ -512,7 +565,7 @@ function renderWizardModalContent() {
|
||||
class="flex-1 px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-primary">
|
||||
<span class="text-sm text-muted-foreground">${formatIntervalDisplay(value)}</span>
|
||||
</div>
|
||||
${field.description ? `<p class="text-xs text-muted-foreground">${escapeHtml(field.description)}</p>` : ''}
|
||||
${fieldDesc ? `<p class="text-xs text-muted-foreground">${escapeHtml(fieldDesc)}</p>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -522,7 +575,7 @@ function renderWizardModalContent() {
|
||||
|
||||
<!-- Preview -->
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-foreground">Generated Command Preview</label>
|
||||
<label class="block text-sm font-medium text-foreground">${t('hook.wizard.commandPreview')}</label>
|
||||
<div class="bg-muted/50 rounded-lg p-3 font-mono text-xs overflow-x-auto">
|
||||
<pre id="wizardCommandPreview" class="whitespace-pre-wrap text-muted-foreground">${escapeHtml(generateWizardCommand())}</pre>
|
||||
</div>
|
||||
@@ -530,16 +583,16 @@ function renderWizardModalContent() {
|
||||
|
||||
<!-- Scope Selection -->
|
||||
<div class="space-y-3">
|
||||
<label class="block text-sm font-medium text-foreground">Install To</label>
|
||||
<label class="block text-sm font-medium text-foreground">${t('hook.wizard.installTo')}</label>
|
||||
<div class="flex gap-4">
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="wizardScope" value="project" checked>
|
||||
<span class="text-sm text-foreground">Project</span>
|
||||
<span class="text-sm text-foreground">${t('hook.scopeProject').split('(')[0]}</span>
|
||||
<span class="text-xs text-muted-foreground">(.claude/settings.json)</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="wizardScope" value="global">
|
||||
<span class="text-sm text-foreground">Global</span>
|
||||
<span class="text-sm text-foreground">${t('hook.scopeGlobal').split('(')[0]}</span>
|
||||
<span class="text-xs text-muted-foreground">(~/.claude/settings.json)</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -593,10 +646,10 @@ function renderSkillContextConfig() {
|
||||
return '<div class="bg-muted/30 rounded-lg p-4 text-sm text-muted-foreground">' +
|
||||
'<div class="flex items-center gap-2 mb-2">' +
|
||||
'<i data-lucide="info" class="w-4 h-4"></i>' +
|
||||
'<span class="font-medium">Auto Detection Mode</span>' +
|
||||
'<span class="font-medium">' + t('hook.wizard.autoDetectionMode') + '</span>' +
|
||||
'</div>' +
|
||||
'<p>SKILLs will be automatically loaded when their name appears in your prompt.</p>' +
|
||||
'<p class="mt-2">Available SKILLs: ' + skillBadges + '</p>' +
|
||||
'<p>' + t('hook.wizard.autoDetectionInfo') + '</p>' +
|
||||
'<p class="mt-2">' + t('hook.wizard.availableSkills') + ' ' + skillBadges + '</p>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
@@ -604,8 +657,8 @@ function renderSkillContextConfig() {
|
||||
if (skillConfigs.length === 0) {
|
||||
configListHtml = '<div class="text-center py-6 text-muted-foreground text-sm border border-dashed border-border rounded-lg">' +
|
||||
'<i data-lucide="package" class="w-8 h-8 mx-auto mb-2 opacity-50"></i>' +
|
||||
'<p>No SKILLs configured yet</p>' +
|
||||
'<p class="text-xs mt-1">Click "Add SKILL" to configure keyword triggers</p>' +
|
||||
'<p>' + t('hook.wizard.noSkillsConfigured') + '</p>' +
|
||||
'<p class="text-xs mt-1">' + t('hook.wizard.clickAddSkill') + '</p>' +
|
||||
'</div>';
|
||||
} else {
|
||||
configListHtml = skillConfigs.map(function(config, idx) {
|
||||
@@ -617,7 +670,7 @@ function renderSkillContextConfig() {
|
||||
'<div class="flex items-center justify-between mb-2">' +
|
||||
'<select onchange="updateSkillConfig(' + idx + ', \'skill\', this.value)" ' +
|
||||
'class="px-2 py-1 text-sm bg-background border border-border rounded text-foreground">' +
|
||||
'<option value="">Select SKILL...</option>' +
|
||||
'<option value="">' + t('hook.wizard.selectSkill') + '</option>' +
|
||||
skillOptions +
|
||||
'</select>' +
|
||||
'<button onclick="removeSkillConfig(' + idx + ')" ' +
|
||||
@@ -626,7 +679,7 @@ function renderSkillContextConfig() {
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'<div class="space-y-1">' +
|
||||
'<label class="text-xs text-muted-foreground">Trigger Keywords (comma-separated)</label>' +
|
||||
'<label class="text-xs text-muted-foreground">' + t('hook.wizard.triggerKeywords') + '</label>' +
|
||||
'<input type="text" ' +
|
||||
'value="' + (config.keywords || '') + '" ' +
|
||||
'onchange="updateSkillConfig(' + idx + ', \'keywords\', this.value)" ' +
|
||||
@@ -641,16 +694,16 @@ function renderSkillContextConfig() {
|
||||
if (availableSkills.length === 0) {
|
||||
noSkillsWarning = '<div class="text-xs text-amber-500 flex items-center gap-1">' +
|
||||
'<i data-lucide="alert-triangle" class="w-3 h-3"></i>' +
|
||||
'No SKILLs found. Create SKILL packages in .claude/skills/' +
|
||||
t('hook.wizard.noSkillsFound') +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
return '<div class="space-y-4">' +
|
||||
'<div class="flex items-center justify-between">' +
|
||||
'<span class="text-sm font-medium text-foreground">Configure SKILLs</span>' +
|
||||
'<span class="text-sm font-medium text-foreground">' + t('hook.wizard.configureSkills') + '</span>' +
|
||||
'<button type="button" onclick="addSkillConfig()" ' +
|
||||
'class="px-3 py-1.5 text-xs bg-primary text-primary-foreground rounded-lg hover:opacity-90 flex items-center gap-1">' +
|
||||
'<i data-lucide="plus" class="w-3 h-3"></i> Add SKILL' +
|
||||
'<i data-lucide="plus" class="w-3 h-3"></i> ' + t('hook.wizard.addSkill') +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'<div id="skillConfigsList" class="space-y-3">' + configListHtml + '</div>' +
|
||||
|
||||
@@ -117,25 +117,27 @@ function setActiveNavItem(item) {
|
||||
function updateContentTitle() {
|
||||
const titleEl = document.getElementById('contentTitle');
|
||||
if (currentView === 'project-overview') {
|
||||
titleEl.textContent = 'Project Overview';
|
||||
titleEl.textContent = t('title.projectOverview');
|
||||
} else if (currentView === 'mcp-manager') {
|
||||
titleEl.textContent = 'MCP Server Management';
|
||||
titleEl.textContent = t('title.mcpManagement');
|
||||
} else if (currentView === 'explorer') {
|
||||
titleEl.textContent = 'File Explorer';
|
||||
titleEl.textContent = t('title.fileExplorer');
|
||||
} else if (currentView === 'cli-manager') {
|
||||
titleEl.textContent = 'CLI Tools & CCW';
|
||||
titleEl.textContent = t('title.cliTools');
|
||||
} else if (currentView === 'cli-history') {
|
||||
titleEl.textContent = 'CLI Execution History';
|
||||
titleEl.textContent = t('title.cliHistory');
|
||||
} else if (currentView === 'hook-manager') {
|
||||
titleEl.textContent = t('title.hookManager');
|
||||
} else if (currentView === 'liteTasks') {
|
||||
const names = { 'lite-plan': 'Lite Plan Sessions', 'lite-fix': 'Lite Fix Sessions' };
|
||||
titleEl.textContent = names[currentLiteType] || 'Lite Tasks';
|
||||
const names = { 'lite-plan': t('title.litePlanSessions'), 'lite-fix': t('title.liteFixSessions') };
|
||||
titleEl.textContent = names[currentLiteType] || t('title.liteTasks');
|
||||
} else if (currentView === 'sessionDetail') {
|
||||
titleEl.textContent = 'Session Detail';
|
||||
titleEl.textContent = t('title.sessionDetail');
|
||||
} else if (currentView === 'liteTaskDetail') {
|
||||
titleEl.textContent = 'Lite Task Detail';
|
||||
titleEl.textContent = t('title.liteTaskDetail');
|
||||
} else {
|
||||
const names = { 'all': 'All Sessions', 'active': 'Active Sessions', 'archived': 'Archived Sessions' };
|
||||
titleEl.textContent = names[currentFilter] || 'Sessions';
|
||||
const names = { 'all': t('title.allSessions'), 'active': t('title.activeSessions'), 'archived': t('title.archivedSessions') };
|
||||
titleEl.textContent = names[currentFilter] || t('title.sessions');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +207,7 @@ async function refreshWorkspace() {
|
||||
renderProjectOverview();
|
||||
}
|
||||
|
||||
showRefreshToast('Workspace refreshed', 'success');
|
||||
showRefreshToast(t('toast.workspaceRefreshed'), 'success');
|
||||
}
|
||||
} else {
|
||||
// Non-server mode: just reload page
|
||||
@@ -213,7 +215,7 @@ async function refreshWorkspace() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Refresh failed:', error);
|
||||
showRefreshToast('Refresh failed: ' + error.message, 'error');
|
||||
showRefreshToast(t('toast.refreshFailed', { error: error.message }), 'error');
|
||||
} finally {
|
||||
btn.classList.remove('refreshing');
|
||||
btn.disabled = false;
|
||||
|
||||
1150
ccw/src/templates/dashboard-js/i18n.js
Normal file
1150
ccw/src/templates/dashboard-js/i18n.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Initialize Lucide icons (must be first to render SVG icons)
|
||||
try { lucide.createIcons(); } catch (e) { console.error('Lucide icons init failed:', e); }
|
||||
|
||||
// Initialize i18n (must be early to translate static content)
|
||||
try { initI18n(); } catch (e) { console.error('I18n init failed:', e); }
|
||||
|
||||
// Initialize components with error handling to prevent cascading failures
|
||||
try { initTheme(); } catch (e) { console.error('Theme init failed:', e); }
|
||||
try { initSidebar(); } catch (e) { console.error('Sidebar init failed:', e); }
|
||||
|
||||
@@ -79,9 +79,9 @@ function renderToolsSection() {
|
||||
if (!container) return;
|
||||
|
||||
var toolDescriptions = {
|
||||
gemini: 'Google AI for code analysis',
|
||||
qwen: 'Alibaba AI assistant',
|
||||
codex: 'OpenAI code generation'
|
||||
gemini: t('cli.geminiDesc'),
|
||||
qwen: t('cli.qwenDesc'),
|
||||
codex: t('cli.codexDesc')
|
||||
};
|
||||
|
||||
var tools = ['gemini', 'qwen', 'codex'];
|
||||
@@ -97,17 +97,17 @@ function renderToolsSection() {
|
||||
'<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>' : '') +
|
||||
(isDefault ? '<span class="tool-default-badge">' + t('cli.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>') +
|
||||
? '<span class="tool-status-text success"><i data-lucide="check-circle" class="w-3.5 h-3.5"></i> ' + t('cli.ready') + '</span>'
|
||||
: '<span class="tool-status-text muted"><i data-lucide="circle-dashed" class="w-3.5 h-3.5"></i> ' + t('cli.notInstalled') + '</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>'
|
||||
? '<button class="btn-sm btn-outline" onclick="setDefaultCliTool(\'' + tool + '\')"><i data-lucide="star" class="w-3 h-3"></i> ' + t('cli.setDefault') + '</button>'
|
||||
: '') +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
@@ -119,15 +119,15 @@ function renderToolsSection() {
|
||||
'<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 class="tool-item-desc">' + (codexLensStatus.ready ? t('cli.codexLensDesc') : t('cli.codexLensDescFull')) + '</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>') +
|
||||
'<button class="btn-sm btn-outline" onclick="initCodexLensIndex()"><i data-lucide="database" class="w-3 h-3"></i> ' + t('cli.initIndex') + '</button>'
|
||||
: '<span class="tool-status-text muted"><i data-lucide="circle-dashed" class="w-3.5 h-3.5"></i> ' + t('cli.notInstalled') + '</span>' +
|
||||
'<button class="btn-sm btn-primary" onclick="installCodexLens()"><i data-lucide="download" class="w-3 h-3"></i> ' + t('cli.install') + '</button>') +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
@@ -153,10 +153,10 @@ function renderToolsSection() {
|
||||
|
||||
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>' +
|
||||
'<h3><i data-lucide="terminal" class="w-4 h-4"></i> ' + t('cli.tools') + '</h3>' +
|
||||
'<span class="section-count">' + available + '/' + tools.length + ' ' + t('cli.available') + '</span>' +
|
||||
'</div>' +
|
||||
'<button class="btn-icon" onclick="refreshAllCliStatus()" title="Refresh Status">' +
|
||||
'<button class="btn-icon" onclick="refreshAllCliStatus()" title="' + t('cli.refreshStatus') + '">' +
|
||||
'<i data-lucide="refresh-cw" class="w-4 h-4"></i>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
@@ -179,9 +179,9 @@ function renderCcwSection() {
|
||||
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>' +
|
||||
'<p>' + t('ccw.noInstallations') + '</p>' +
|
||||
'<button class="btn btn-sm btn-primary" onclick="showCcwInstallModal()">' +
|
||||
'<i data-lucide="download" class="w-3 h-3"></i> Install CCW</button>' +
|
||||
'<i data-lucide="download" class="w-3 h-3"></i> ' + t('ccw.installCcw') + '</button>' +
|
||||
'</div>';
|
||||
} else {
|
||||
installationsHtml = '<div class="ccw-list">';
|
||||
@@ -224,8 +224,8 @@ function renderCcwSection() {
|
||||
|
||||
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>' +
|
||||
'<h3><i data-lucide="package" class="w-4 h-4"></i> ' + t('ccw.install') + '</h3>' +
|
||||
'<span class="section-count">' + ccwInstallations.length + ' ' + (ccwInstallations.length !== 1 ? t('ccw.installationsPlural') : t('ccw.installations')) + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="section-header-actions">' +
|
||||
'<button class="btn-icon" onclick="showCcwInstallModal()" title="Add Installation">' +
|
||||
@@ -252,9 +252,9 @@ function renderCcwEndpointToolsSection() {
|
||||
if (!ccwEndpointTools || ccwEndpointTools.length === 0) {
|
||||
toolsHtml = '<div class="ccw-empty-state">' +
|
||||
'<i data-lucide="wrench" class="w-8 h-8"></i>' +
|
||||
'<p>No endpoint tools found</p>' +
|
||||
'<p>' + t('ccw.noEndpointTools') + '</p>' +
|
||||
'<button class="btn btn-sm btn-primary" onclick="loadCcwEndpointTools().then(function() { renderCcwEndpointToolsSection(); if (window.lucide) lucide.createIcons(); })">' +
|
||||
'<i data-lucide="refresh-cw" class="w-3 h-3"></i> Refresh</button>' +
|
||||
'<i data-lucide="refresh-cw" class="w-3 h-3"></i> ' + t('common.refresh') + '</button>' +
|
||||
'</div>';
|
||||
} else {
|
||||
toolsHtml = '<div class="endpoint-tools-grid">' +
|
||||
@@ -284,8 +284,8 @@ function renderCcwEndpointToolsSection() {
|
||||
|
||||
container.innerHTML = '<div class="section-header">' +
|
||||
'<div class="section-header-left">' +
|
||||
'<h3><i data-lucide="server" class="w-4 h-4"></i> CCW Endpoint Tools</h3>' +
|
||||
'<span class="section-count">' + count + ' tool' + (count !== 1 ? 's' : '') + '</span>' +
|
||||
'<h3><i data-lucide="server" class="w-4 h-4"></i> ' + t('ccw.endpointTools') + '</h3>' +
|
||||
'<span class="section-count">' + count + ' ' + (count !== 1 ? t('ccw.tools') : t('ccw.tool')) + '</span>' +
|
||||
'</div>' +
|
||||
'<button class="btn-icon" onclick="loadCcwEndpointTools().then(function() { renderCcwEndpointToolsSection(); if (window.lucide) lucide.createIcons(); })" title="Refresh">' +
|
||||
'<i data-lucide="refresh-cw" class="w-4 h-4"></i>' +
|
||||
@@ -664,7 +664,7 @@ function runCcwInstall(mode, customPath) {
|
||||
}
|
||||
|
||||
async function runCcwUpgrade() {
|
||||
showRefreshToast('Starting upgrade...', 'info');
|
||||
showRefreshToast(t('ccw.upgradeStarting'), 'info');
|
||||
|
||||
try {
|
||||
var response = await fetch('/api/ccw/upgrade', {
|
||||
@@ -676,7 +676,7 @@ async function runCcwUpgrade() {
|
||||
var result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showRefreshToast('Upgrade completed! Refreshing...', 'success');
|
||||
showRefreshToast(t('ccw.upgradeCompleted'), 'success');
|
||||
// Reload installations after upgrade
|
||||
setTimeout(function() {
|
||||
loadCcwInstallations().then(function() {
|
||||
@@ -684,15 +684,15 @@ async function runCcwUpgrade() {
|
||||
});
|
||||
}, 1000);
|
||||
} else {
|
||||
showRefreshToast('Upgrade failed: ' + (result.error || 'Unknown error'), 'error');
|
||||
showRefreshToast(t('ccw.upgradeFailed', { error: result.error || 'Unknown error' }), 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
showRefreshToast('Upgrade error: ' + err.message, 'error');
|
||||
showRefreshToast(t('ccw.upgradeFailed', { error: err.message }), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function confirmCcwUninstall(installPath) {
|
||||
if (confirm('Uninstall CCW from this location?\n' + (installPath || 'Current installation'))) {
|
||||
if (confirm(t('ccw.uninstallConfirm') + '\n' + (installPath || 'Current installation'))) {
|
||||
var command = installPath
|
||||
? 'ccw uninstall --path "' + installPath + '"'
|
||||
: 'ccw uninstall';
|
||||
@@ -720,7 +720,7 @@ async function executeCliFromDashboard() {
|
||||
var prompt = promptEl ? promptEl.value.trim() : '';
|
||||
|
||||
if (!prompt) {
|
||||
showRefreshToast('Please enter a prompt', 'error');
|
||||
showRefreshToast(t('toast.enterPrompt'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -756,11 +756,11 @@ async function executeCliFromDashboard() {
|
||||
|
||||
await loadCliHistory();
|
||||
renderCliHistory();
|
||||
showRefreshToast(result.success ? 'Completed' : (result.error || 'Failed'), result.success ? 'success' : 'error');
|
||||
showRefreshToast(result.success ? t('toast.completed') : (result.error || t('toast.failed')), result.success ? 'success' : 'error');
|
||||
} catch (error) {
|
||||
if (statusIndicator) statusIndicator.className = 'status-indicator error';
|
||||
if (statusText) statusText.textContent = 'Error: ' + error.message;
|
||||
showRefreshToast('Error: ' + error.message, 'error');
|
||||
showRefreshToast(t('toast.error', { error: error.message }), 'error');
|
||||
}
|
||||
|
||||
currentCliExecution = null;
|
||||
|
||||
@@ -60,33 +60,33 @@ async function renderExplorer() {
|
||||
<div class="explorer-tree-header">
|
||||
<div class="explorer-tree-title">
|
||||
<i data-lucide="folder-tree" class="explorer-icon"></i>
|
||||
<span class="explorer-title-text">Explorer</span>
|
||||
<span class="explorer-title-text">${t('explorer.title')}</span>
|
||||
</div>
|
||||
<button class="explorer-refresh-btn" onclick="refreshExplorerTree()" title="Refresh">
|
||||
<button class="explorer-refresh-btn" onclick="refreshExplorerTree()" title="${t('explorer.refresh')}">
|
||||
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="explorer-tree-content" id="explorerTreeContent">
|
||||
<div class="explorer-loading">Loading file tree...</div>
|
||||
<div class="explorer-loading">${t('explorer.loading')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Panel: Preview -->
|
||||
<div class="explorer-preview-panel">
|
||||
<div class="explorer-preview-header" id="explorerPreviewHeader">
|
||||
<span class="preview-filename">Select a file to preview</span>
|
||||
<span class="preview-filename">${t('explorer.selectFile')}</span>
|
||||
</div>
|
||||
<div class="explorer-preview-content" id="explorerPreviewContent">
|
||||
<div class="explorer-preview-empty">
|
||||
<div class="preview-empty-icon"><i data-lucide="file-text" class="w-12 h-12"></i></div>
|
||||
<div class="preview-empty-text">Select a file from the tree to preview its contents</div>
|
||||
<div class="preview-empty-text">${t('explorer.selectFileHint')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Floating Action Button -->
|
||||
<div class="explorer-fab" onclick="toggleTaskQueue()" title="Task Queue">
|
||||
<div class="explorer-fab" onclick="toggleTaskQueue()" title="${t('taskQueue.title')}">
|
||||
<span class="fab-icon"><i data-lucide="list-todo" class="w-5 h-5"></i></span>
|
||||
<span class="fab-badge" id="fabBadge">0</span>
|
||||
</div>
|
||||
@@ -94,12 +94,12 @@ async function renderExplorer() {
|
||||
<!-- Task Queue Panel -->
|
||||
<div class="task-queue-panel" id="taskQueuePanel">
|
||||
<div class="task-queue-header">
|
||||
<span class="task-queue-title"><i data-lucide="clipboard-list" class="w-4 h-4 inline-block mr-1"></i> Update Tasks</span>
|
||||
<span class="task-queue-title"><i data-lucide="clipboard-list" class="w-4 h-4 inline-block mr-1"></i> ${t('taskQueue.title')}</span>
|
||||
<button class="task-queue-close" onclick="toggleTaskQueue()">×</button>
|
||||
</div>
|
||||
<div class="task-queue-toolbar">
|
||||
<div class="queue-cli-selector">
|
||||
<label>CLI:</label>
|
||||
<label>${t('taskQueue.cli')}</label>
|
||||
<select id="queueCliTool" onchange="updateDefaultCliTool(this.value)">
|
||||
<option value="gemini">Gemini</option>
|
||||
<option value="qwen">Qwen</option>
|
||||
@@ -107,21 +107,21 @@ async function renderExplorer() {
|
||||
</select>
|
||||
</div>
|
||||
<div class="task-queue-actions">
|
||||
<button class="queue-action-btn" onclick="openAddTaskModal()" title="Add update task">
|
||||
<button class="queue-action-btn" onclick="openAddTaskModal()" title="${t('taskQueue.addTask')}">
|
||||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button class="queue-action-btn queue-start-btn" onclick="startTaskQueue()" id="startQueueBtn" disabled title="Start all tasks">
|
||||
<button class="queue-action-btn queue-start-btn" onclick="startTaskQueue()" id="startQueueBtn" disabled title="${t('taskQueue.startAll')}">
|
||||
<i data-lucide="play" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button class="queue-action-btn queue-clear-btn" onclick="clearCompletedTasks()" title="Clear completed">
|
||||
<button class="queue-action-btn queue-clear-btn" onclick="clearCompletedTasks()" title="${t('taskQueue.clearCompleted')}">
|
||||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-queue-list" id="taskQueueList">
|
||||
<div class="task-queue-empty">
|
||||
<span>No tasks in queue</span>
|
||||
<p>Hover folder and click <i data-lucide="file" class="w-3 h-3 inline"></i> or <i data-lucide="folder" class="w-3 h-3 inline"></i> to add tasks</p>
|
||||
<span>${t('taskQueue.noTasks')}</span>
|
||||
<p>${t('taskQueue.noTasksHint')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -167,7 +167,7 @@ async function loadExplorerTree(dirPath) {
|
||||
*/
|
||||
function renderTreeLevel(files, parentPath, depth) {
|
||||
if (!files || files.length === 0) {
|
||||
return `<div class="tree-empty" style="padding-left: ${depth * 16 + 8}px">Empty directory</div>`;
|
||||
return `<div class="tree-empty" style="padding-left: ${depth * 16 + 8}px">${t('explorer.emptyDir')}</div>`;
|
||||
}
|
||||
|
||||
return files.map(file => {
|
||||
@@ -190,10 +190,10 @@ function renderTreeLevel(files, parentPath, depth) {
|
||||
</span>
|
||||
` : ''}
|
||||
<div class="tree-folder-actions">
|
||||
<button class="tree-update-btn" onclick="event.stopPropagation(); addFolderToQueue('${escapeHtml(file.path)}', 'single-layer')" title="Update CLAUDE.md (current folder only)">
|
||||
<button class="tree-update-btn" onclick="event.stopPropagation(); addFolderToQueue('${escapeHtml(file.path)}', 'single-layer')" title="${t('explorer.currentFolderOnly')}">
|
||||
<span class="update-icon"><i data-lucide="file" class="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
<button class="tree-update-btn tree-update-multi" onclick="event.stopPropagation(); addFolderToQueue('${escapeHtml(file.path)}', 'multi-layer')" title="Update CLAUDE.md (with subdirectories)">
|
||||
<button class="tree-update-btn tree-update-multi" onclick="event.stopPropagation(); addFolderToQueue('${escapeHtml(file.path)}', 'multi-layer')" title="${t('explorer.withSubdirs')}">
|
||||
<span class="update-icon"><i data-lucide="folder-tree" class="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -425,8 +425,8 @@ async function previewFile(filePath) {
|
||||
</div>
|
||||
${isMarkdown ? `
|
||||
<div class="preview-header-tabs" id="previewHeaderTabs">
|
||||
<button class="preview-tab active" data-tab="rendered" onclick="switchPreviewTab(this, 'rendered')">Preview</button>
|
||||
<button class="preview-tab" data-tab="source" onclick="switchPreviewTab(this, 'source')">Source</button>
|
||||
<button class="preview-tab active" data-tab="rendered" onclick="switchPreviewTab(this, 'rendered')">${t('explorer.preview')}</button>
|
||||
<button class="preview-tab" data-tab="source" onclick="switchPreviewTab(this, 'source')">${t('explorer.source')}</button>
|
||||
</div>
|
||||
` : ''}
|
||||
`;
|
||||
@@ -458,7 +458,7 @@ async function previewFile(filePath) {
|
||||
previewContent.innerHTML = `
|
||||
<div class="preview-info">
|
||||
<span class="preview-lang">${data.language}</span>
|
||||
<span class="preview-lines">${data.lines} lines</span>
|
||||
<span class="preview-lines">${data.lines} ${t('explorer.lines')}</span>
|
||||
<span class="preview-size">${formatFileSize(data.size)}</span>
|
||||
</div>
|
||||
<pre class="preview-code"><code class="language-${data.language}">${escapeHtml(data.content)}</code></pre>
|
||||
@@ -725,8 +725,8 @@ function renderTaskQueue() {
|
||||
if (updateTaskQueue.length === 0) {
|
||||
listEl.innerHTML = `
|
||||
<div class="task-queue-empty">
|
||||
<span>No tasks in queue</span>
|
||||
<p>Right-click a folder or click "Add Task" to queue CLAUDE.md updates</p>
|
||||
<span>${t('taskQueue.noTasks')}</span>
|
||||
<p>${t('taskQueue.noTasksHint')}</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
@@ -735,8 +735,8 @@ function renderTaskQueue() {
|
||||
listEl.innerHTML = updateTaskQueue.map(task => {
|
||||
const folderName = task.path.split('/').pop() || task.path;
|
||||
const strategyLabel = task.strategy === 'multi-layer'
|
||||
? '<i data-lucide="folder-tree" class="w-3 h-3 inline"></i> With subdirs'
|
||||
: '<i data-lucide="file" class="w-3 h-3 inline"></i> Current only';
|
||||
? '<i data-lucide="folder-tree" class="w-3 h-3 inline"></i> ' + t('taskQueue.withSubdirs')
|
||||
: '<i data-lucide="file" class="w-3 h-3 inline"></i> ' + t('taskQueue.currentOnly');
|
||||
const statusIcon = {
|
||||
'pending': '<i data-lucide="clock" class="w-4 h-4"></i>',
|
||||
'running': '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i>',
|
||||
@@ -796,7 +796,7 @@ async function executeTask(task) {
|
||||
|
||||
// Update status to running
|
||||
task.status = 'running';
|
||||
task.message = 'Processing...';
|
||||
task.message = t('taskQueue.processing');
|
||||
renderTaskQueue();
|
||||
|
||||
addGlobalNotification('info', `Processing: ${folderName}`, `Strategy: ${task.strategy}, Tool: ${task.tool}`, 'Explorer');
|
||||
@@ -816,12 +816,12 @@ async function executeTask(task) {
|
||||
|
||||
if (result.success) {
|
||||
task.status = 'completed';
|
||||
task.message = 'Updated successfully';
|
||||
task.message = t('taskQueue.updated');
|
||||
addGlobalNotification('success', `Completed: ${folderName}`, result.message, 'Explorer');
|
||||
return { success: true };
|
||||
} else {
|
||||
task.status = 'failed';
|
||||
task.message = result.error || 'Update failed';
|
||||
task.message = result.error || t('taskQueue.failed');
|
||||
addGlobalNotification('error', `Failed: ${folderName}`, result.error || 'Unknown error', 'Explorer');
|
||||
return { success: false };
|
||||
}
|
||||
@@ -848,7 +848,7 @@ async function startTaskQueue() {
|
||||
isTaskRunning = true;
|
||||
document.getElementById('startQueueBtn').disabled = true;
|
||||
|
||||
addGlobalNotification('info', `Starting ${pendingTasks.length} task(s) in parallel...`, null, 'Explorer');
|
||||
addGlobalNotification('info', t('taskQueue.startingTasks', { count: pendingTasks.length }), null, 'Explorer');
|
||||
|
||||
// Execute all tasks in parallel
|
||||
const results = await Promise.all(pendingTasks.map(task => executeTask(task)));
|
||||
@@ -861,7 +861,7 @@ async function startTaskQueue() {
|
||||
// Summary notification
|
||||
addGlobalNotification(
|
||||
failCount === 0 ? 'success' : 'warning',
|
||||
`Queue completed: ${successCount} succeeded, ${failCount} failed`,
|
||||
t('taskQueue.queueCompleted', { success: successCount, failed: failCount }),
|
||||
null,
|
||||
'Explorer'
|
||||
);
|
||||
|
||||
@@ -74,8 +74,8 @@ function renderSessions() {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state" style="grid-column: 1/-1;">
|
||||
<div class="empty-icon"><i data-lucide="inbox" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">No Sessions Found</div>
|
||||
<div class="empty-text">No workflow sessions match your current filter.</div>
|
||||
<div class="empty-title">${t('empty.noSessions')}</div>
|
||||
<div class="empty-text">${t('empty.noSessionsText')}</div>
|
||||
</div>
|
||||
`;
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
@@ -111,13 +111,13 @@ function renderSessionCard(session) {
|
||||
if (!isActive) {
|
||||
// Archived sessions always show as ARCHIVED regardless of status field
|
||||
statusClass = 'archived';
|
||||
statusText = 'ARCHIVED';
|
||||
statusText = t('session.status.archived');
|
||||
} else if (isPlanning) {
|
||||
statusClass = 'planning';
|
||||
statusText = 'PLANNING';
|
||||
statusText = t('session.status.planning');
|
||||
} else {
|
||||
statusClass = 'active';
|
||||
statusText = 'ACTIVE';
|
||||
statusText = t('session.status.active');
|
||||
}
|
||||
|
||||
// Store session data for modal
|
||||
@@ -146,11 +146,11 @@ function renderSessionCard(session) {
|
||||
<div class="session-body">
|
||||
<div class="session-meta">
|
||||
<span class="session-meta-item"><i data-lucide="calendar" class="w-3.5 h-3.5 inline mr-1"></i>${formatDate(date)}</span>
|
||||
<span class="session-meta-item"><i data-lucide="list-checks" class="w-3.5 h-3.5 inline mr-1"></i>${taskCount} tasks</span>
|
||||
<span class="session-meta-item"><i data-lucide="list-checks" class="w-3.5 h-3.5 inline mr-1"></i>${taskCount} ${t('session.tasks')}</span>
|
||||
</div>
|
||||
${taskCount > 0 && !isPlanning ? `
|
||||
<div class="progress-container">
|
||||
<span class="progress-label">Progress</span>
|
||||
<span class="progress-label">${t('session.progress')}</span>
|
||||
<div class="progress-bar-wrapper">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: ${progress}%"></div>
|
||||
@@ -188,13 +188,13 @@ function renderReviewSessionCard(session, sessionKey, typeBadge, isActive, isPla
|
||||
let statusClass, statusText;
|
||||
if (!isActive) {
|
||||
statusClass = 'archived';
|
||||
statusText = 'ARCHIVED';
|
||||
statusText = t('session.status.archived');
|
||||
} else if (isPlanning) {
|
||||
statusClass = 'planning';
|
||||
statusText = 'PLANNING';
|
||||
statusText = t('session.status.planning');
|
||||
} else {
|
||||
statusClass = 'active';
|
||||
statusText = 'ACTIVE';
|
||||
statusText = t('session.status.active');
|
||||
}
|
||||
|
||||
// Card class includes planning modifier for special styling (only for active sessions)
|
||||
@@ -214,7 +214,7 @@ function renderReviewSessionCard(session, sessionKey, typeBadge, isActive, isPla
|
||||
<div class="session-body">
|
||||
<div class="session-meta">
|
||||
<span class="session-meta-item"><i data-lucide="calendar" class="w-3.5 h-3.5 inline mr-1"></i>${formatDate(date)}</span>
|
||||
<span class="session-meta-item"><i data-lucide="search" class="w-3.5 h-3.5 inline mr-1"></i>${totalFindings} findings</span>
|
||||
<span class="session-meta-item"><i data-lucide="search" class="w-3.5 h-3.5 inline mr-1"></i>${totalFindings} ${t('session.findings')}</span>
|
||||
</div>
|
||||
${totalFindings > 0 ? `
|
||||
<div class="review-findings-summary">
|
||||
@@ -225,7 +225,7 @@ function renderReviewSessionCard(session, sessionKey, typeBadge, isActive, isPla
|
||||
${lowCount > 0 ? `<span class="finding-count low"><i data-lucide="check-circle" class="w-3 h-3 inline"></i> ${lowCount}</span>` : ''}
|
||||
</div>
|
||||
<div class="dimensions-info">
|
||||
${dimensions.length} dimensions
|
||||
${dimensions.length} ${t('session.dimensions')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
@@ -29,21 +29,21 @@ async function renderHookManager() {
|
||||
<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">Project Hooks</h3>
|
||||
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-primary-light text-primary">.claude/settings.json</span>
|
||||
<h3 class="text-lg font-semibold text-foreground">${t('hook.projectHooks')}</h3>
|
||||
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-primary-light text-primary">${t('hook.projectFile')}</span>
|
||||
<button class="px-3 py-1.5 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity flex items-center gap-1"
|
||||
onclick="openHookCreateModal()">
|
||||
<span>+</span> New Hook
|
||||
<span>+</span> ${t('hook.newHook')}
|
||||
</button>
|
||||
</div>
|
||||
<span class="text-sm text-muted-foreground">${projectHookCount} hooks configured</span>
|
||||
<span class="text-sm text-muted-foreground">${projectHookCount} ${t('hook.hooksConfigured')}</span>
|
||||
</div>
|
||||
|
||||
${projectHookCount === 0 ? `
|
||||
<div class="hook-empty-state bg-card border border-border rounded-lg p-6 text-center">
|
||||
<div class="text-muted-foreground mb-3"><i data-lucide="webhook" class="w-10 h-10 mx-auto"></i></div>
|
||||
<p class="text-muted-foreground">No hooks configured for this project</p>
|
||||
<p class="text-sm text-muted-foreground mt-1">Create a hook to automate actions on tool usage</p>
|
||||
<p class="text-muted-foreground">${t('empty.noHooks')}</p>
|
||||
<p class="text-sm text-muted-foreground mt-1">${t('empty.createHookHint')}</p>
|
||||
</div>
|
||||
` : `
|
||||
<div class="hook-grid grid gap-3">
|
||||
@@ -56,16 +56,16 @@ async function renderHookManager() {
|
||||
<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">Global Hooks</h3>
|
||||
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-muted text-muted-foreground">~/.claude/settings.json</span>
|
||||
<h3 class="text-lg font-semibold text-foreground">${t('hook.globalHooks')}</h3>
|
||||
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-muted text-muted-foreground">${t('hook.globalFile')}</span>
|
||||
</div>
|
||||
<span class="text-sm text-muted-foreground">${globalHookCount} hooks configured</span>
|
||||
<span class="text-sm text-muted-foreground">${globalHookCount} ${t('hook.hooksConfigured')}</span>
|
||||
</div>
|
||||
|
||||
${globalHookCount === 0 ? `
|
||||
<div class="hook-empty-state bg-card border border-border rounded-lg p-6 text-center">
|
||||
<p class="text-muted-foreground">No global hooks configured</p>
|
||||
<p class="text-sm text-muted-foreground mt-1">Global hooks apply to all Claude Code sessions</p>
|
||||
<p class="text-muted-foreground">${t('empty.noGlobalHooks')}</p>
|
||||
<p class="text-sm text-muted-foreground mt-1">${t('empty.globalHooksHint')}</p>
|
||||
</div>
|
||||
` : `
|
||||
<div class="hook-grid grid gap-3">
|
||||
@@ -78,10 +78,10 @@ async function renderHookManager() {
|
||||
<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>
|
||||
<h3 class="text-lg font-semibold text-foreground">${t('hook.wizards')}</h3>
|
||||
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-success/20 text-success">${t('hook.guidedSetup')}</span>
|
||||
</div>
|
||||
<span class="text-sm text-muted-foreground">Configure complex hooks with guided wizards</span>
|
||||
<span class="text-sm text-muted-foreground">${t('hook.wizardsDesc')}</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@@ -93,23 +93,23 @@ async function renderHookManager() {
|
||||
<!-- Quick Install Templates -->
|
||||
<div class="hook-section">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-foreground">Quick Install Templates</h3>
|
||||
<span class="text-sm text-muted-foreground">One-click hook installation</span>
|
||||
<h3 class="text-lg font-semibold text-foreground">${t('hook.quickInstall')}</h3>
|
||||
<span class="text-sm text-muted-foreground">${t('hook.oneClick')}</span>
|
||||
</div>
|
||||
|
||||
<div class="hook-templates-grid grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
${renderQuickInstallCard('codexlens-update', 'CodexLens Auto-Sync', 'Auto-update code index when files are written or edited', 'PostToolUse', 'Write|Edit')}
|
||||
${renderQuickInstallCard('ccw-notify', 'CCW Dashboard Notify', 'Notify CCW dashboard when files are written', 'PostToolUse', 'Write')}
|
||||
${renderQuickInstallCard('log-tool', 'Tool Usage Logger', 'Log all tool executions to a file', 'PostToolUse', 'All')}
|
||||
${renderQuickInstallCard('lint-check', 'Auto Lint Check', 'Run ESLint on JavaScript/TypeScript files after write', 'PostToolUse', 'Write')}
|
||||
${renderQuickInstallCard('git-add', 'Auto Git Stage', 'Automatically stage written files to git', 'PostToolUse', 'Write')}
|
||||
${renderQuickInstallCard('codexlens-update', t('hook.tpl.codexlensSync'), t('hook.tpl.codexlensSyncDesc'), 'PostToolUse', 'Write|Edit')}
|
||||
${renderQuickInstallCard('ccw-notify', t('hook.tpl.ccwDashboardNotify'), t('hook.tpl.ccwDashboardNotifyDesc'), 'PostToolUse', 'Write')}
|
||||
${renderQuickInstallCard('log-tool', t('hook.tpl.toolLogger'), t('hook.tpl.toolLoggerDesc'), 'PostToolUse', 'All')}
|
||||
${renderQuickInstallCard('lint-check', t('hook.tpl.autoLint'), t('hook.tpl.autoLintDesc'), 'PostToolUse', 'Write')}
|
||||
${renderQuickInstallCard('git-add', t('hook.tpl.autoGitStage'), t('hook.tpl.autoGitStageDesc'), 'PostToolUse', 'Write')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hook Environment Variables Reference -->
|
||||
<div class="hook-section mt-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-foreground">Environment Variables Reference</h3>
|
||||
<h3 class="text-lg font-semibold text-foreground">${t('hook.envVarsRef')}</h3>
|
||||
</div>
|
||||
|
||||
<div class="bg-card border border-border rounded-lg p-4">
|
||||
@@ -117,29 +117,29 @@ async function renderHookManager() {
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-start gap-2">
|
||||
<code class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0">$CLAUDE_FILE_PATHS</code>
|
||||
<span class="text-muted-foreground">Space-separated file paths affected</span>
|
||||
<span class="text-muted-foreground">${t('hook.filePaths')}</span>
|
||||
</div>
|
||||
<div class="flex items-start gap-2">
|
||||
<code class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0">$CLAUDE_TOOL_NAME</code>
|
||||
<span class="text-muted-foreground">Name of the tool being executed</span>
|
||||
<span class="text-muted-foreground">${t('hook.toolName')}</span>
|
||||
</div>
|
||||
<div class="flex items-start gap-2">
|
||||
<code class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0">$CLAUDE_TOOL_INPUT</code>
|
||||
<span class="text-muted-foreground">JSON input passed to the tool</span>
|
||||
<span class="text-muted-foreground">${t('hook.toolInput')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-start gap-2">
|
||||
<code class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0">$CLAUDE_SESSION_ID</code>
|
||||
<span class="text-muted-foreground">Current Claude session ID</span>
|
||||
<span class="text-muted-foreground">${t('hook.sessionId')}</span>
|
||||
</div>
|
||||
<div class="flex items-start gap-2">
|
||||
<code class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0">$CLAUDE_PROJECT_DIR</code>
|
||||
<span class="text-muted-foreground">Current project directory path</span>
|
||||
<span class="text-muted-foreground">${t('hook.projectDir')}</span>
|
||||
</div>
|
||||
<div class="flex items-start gap-2">
|
||||
<code class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0">$CLAUDE_WORKING_DIR</code>
|
||||
<span class="text-muted-foreground">Current working directory</span>
|
||||
<span class="text-muted-foreground">${t('hook.workingDir')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -153,6 +153,9 @@ async function renderHookManager() {
|
||||
|
||||
// Initialize Lucide icons
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
|
||||
// Load available SKILLs for skill-context wizard
|
||||
loadAvailableSkills();
|
||||
}
|
||||
|
||||
// Load available SKILLs for skill-context wizard
|
||||
@@ -161,25 +164,25 @@ async function loadAvailableSkills() {
|
||||
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>
|
||||
<span class="font-mono bg-muted px-1.5 py-0.5 rounded">${t('hook.wizard.availableSkills')}</span>
|
||||
<span class="text-muted-foreground ml-2">${t('hook.wizard.noSkillsFound').split('.')[0]}</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>
|
||||
<span class="font-mono bg-muted px-1.5 py-0.5 rounded">${t('hook.wizard.availableSkills')}</span>
|
||||
<div class="flex flex-wrap gap-1 mt-1">${skillBadges}</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Store skills for wizard use
|
||||
window.availableSkills = data.skills || [];
|
||||
} catch (err) {
|
||||
@@ -187,8 +190,8 @@ async function loadAvailableSkills() {
|
||||
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>
|
||||
<span class="font-mono bg-muted px-1.5 py-0.5 rounded">${t('hook.wizard.availableSkills')}</span>
|
||||
<span class="text-destructive ml-2">${t('toast.loadFailed', { error: err.message })}</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -201,21 +204,52 @@ function renderWizardCard(wizardId) {
|
||||
const wizard = WIZARD_TEMPLATES[wizardId];
|
||||
if (!wizard) return '';
|
||||
|
||||
// Get translated wizard name and description
|
||||
const wizardName = wizardId === 'memory-update' ? t('hook.wizard.memoryUpdate') :
|
||||
wizardId === 'skill-context' ? t('hook.wizard.skillContext') : wizard.name;
|
||||
const wizardDesc = wizardId === 'memory-update' ? t('hook.wizard.memoryUpdateDesc') :
|
||||
wizardId === 'skill-context' ? t('hook.wizard.skillContextDesc') : wizard.description;
|
||||
|
||||
// Translate options
|
||||
const getOptionName = (wizardId, optId) => {
|
||||
if (wizardId === 'memory-update') {
|
||||
if (optId === 'on-stop') return t('hook.wizard.onSessionEnd');
|
||||
if (optId === 'periodic') return t('hook.wizard.periodicUpdate');
|
||||
}
|
||||
if (wizardId === 'skill-context') {
|
||||
if (optId === 'keyword') return t('hook.wizard.keywordMatching');
|
||||
if (optId === 'auto') return t('hook.wizard.autoDetection');
|
||||
}
|
||||
return wizard.options.find(o => o.id === optId)?.name || '';
|
||||
};
|
||||
|
||||
const getOptionDesc = (wizardId, optId) => {
|
||||
if (wizardId === 'memory-update') {
|
||||
if (optId === 'on-stop') return t('hook.wizard.onSessionEndDesc');
|
||||
if (optId === 'periodic') return t('hook.wizard.periodicUpdateDesc');
|
||||
}
|
||||
if (wizardId === 'skill-context') {
|
||||
if (optId === 'keyword') return t('hook.wizard.keywordMatchingDesc');
|
||||
if (optId === 'auto') return t('hook.wizard.autoDetectionDesc');
|
||||
}
|
||||
return wizard.options.find(o => o.id === optId)?.description || '';
|
||||
};
|
||||
|
||||
// Determine what to show in the tools/skills section
|
||||
const toolsSection = wizard.requiresSkillDiscovery
|
||||
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="font-mono bg-muted px-1.5 py-0.5 rounded">${t('hook.wizard.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>
|
||||
<span class="font-mono bg-muted px-1.5 py-0.5 rounded">${t('hook.wizard.availableSkills')}</span>
|
||||
<span class="text-muted-foreground ml-2">${t('hook.wizard.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="font-mono bg-muted px-1.5 py-0.5 rounded">${t('hook.wizard.cliTools')}</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>
|
||||
@@ -230,8 +264,8 @@ function renderWizardCard(wizardId) {
|
||||
<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>
|
||||
<h4 class="font-semibold text-foreground">${escapeHtml(wizardName)}</h4>
|
||||
<p class="text-sm text-muted-foreground">${escapeHtml(wizardDesc)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -240,7 +274,7 @@ function renderWizardCard(wizardId) {
|
||||
${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>
|
||||
<span>${escapeHtml(getOptionName(wizardId, opt.id))}: ${escapeHtml(getOptionDesc(wizardId, opt.id))}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
@@ -250,7 +284,7 @@ function renderWizardCard(wizardId) {
|
||||
<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
|
||||
${t('hook.openWizard')}
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
@@ -333,6 +367,7 @@ function renderQuickInstallCard(templateId, title, description, event, matcher)
|
||||
const isInstalled = isHookTemplateInstalled(templateId);
|
||||
const template = HOOK_TEMPLATES[templateId];
|
||||
const category = template?.category || 'general';
|
||||
const categoryTranslated = t(`hook.category.${category}`) || category;
|
||||
|
||||
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' : ''}">
|
||||
@@ -346,7 +381,7 @@ function renderQuickInstallCard(templateId, title, description, event, matcher)
|
||||
</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">
|
||||
title="${t('hook.viewDetails')}">
|
||||
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -356,9 +391,9 @@ function renderQuickInstallCard(templateId, title, description, event, matcher)
|
||||
<span class="font-mono bg-muted px-1 py-0.5 rounded">${event}</span>
|
||||
</span>
|
||||
<span class="flex items-center gap-1">
|
||||
Matches: <span class="font-medium">${matcher}</span>
|
||||
${t('hook.wizard.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>
|
||||
<span class="px-1.5 py-0.5 bg-primary/10 text-primary rounded text-xs">${categoryTranslated}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -366,18 +401,18 @@ function renderQuickInstallCard(templateId, title, description, event, matcher)
|
||||
<button class="flex-1 px-3 py-1.5 text-sm bg-destructive/10 text-destructive rounded hover:bg-destructive/20 transition-colors"
|
||||
data-template="${templateId}"
|
||||
data-action="uninstall">
|
||||
Uninstall
|
||||
${t('hook.uninstall')}
|
||||
</button>
|
||||
` : `
|
||||
<button class="flex-1 px-3 py-1.5 text-sm bg-primary text-primary-foreground rounded hover:opacity-90 transition-opacity"
|
||||
data-template="${templateId}"
|
||||
data-action="install-project">
|
||||
Install (Project)
|
||||
${t('hook.installProject')}
|
||||
</button>
|
||||
<button class="px-3 py-1.5 text-sm bg-muted text-foreground rounded hover:bg-hover transition-colors"
|
||||
data-template="${templateId}"
|
||||
data-action="install-global">
|
||||
Global
|
||||
${t('hook.installGlobal')}
|
||||
</button>
|
||||
`}
|
||||
</div>
|
||||
@@ -486,7 +521,7 @@ function attachHookEventListeners() {
|
||||
const event = button.dataset.event;
|
||||
const index = parseInt(button.dataset.index);
|
||||
|
||||
if (confirm(`Remove this ${event} hook?`)) {
|
||||
if (confirm(t('hook.deleteConfirm', { event: event }))) {
|
||||
await removeHook(scope, event, index);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -15,8 +15,8 @@ function renderLiteTasks() {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon"><i data-lucide="zap" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">No ${currentLiteType} Sessions</div>
|
||||
<div class="empty-text">No sessions found in .workflow/.${currentLiteType}/</div>
|
||||
<div class="empty-title">${t('empty.noLiteSessions', { type: currentLiteType })}</div>
|
||||
<div class="empty-text">${t('empty.noLiteSessionsText', { type: currentLiteType })}</div>
|
||||
</div>
|
||||
`;
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
@@ -55,13 +55,13 @@ function renderLiteTaskCard(session) {
|
||||
<div class="session-header">
|
||||
<div class="session-title">${escapeHtml(session.id)}</div>
|
||||
<span class="session-status ${session.type}">
|
||||
${session.type === 'lite-plan' ? '<i data-lucide="file-edit" class="w-3 h-3 inline"></i> PLAN' : '<i data-lucide="wrench" class="w-3 h-3 inline"></i> FIX'}
|
||||
${session.type === 'lite-plan' ? '<i data-lucide="file-edit" class="w-3 h-3 inline"></i> ' + t('lite.plan') : '<i data-lucide="wrench" class="w-3 h-3 inline"></i> ' + t('lite.fix')}
|
||||
</span>
|
||||
</div>
|
||||
<div class="session-body">
|
||||
<div class="session-meta">
|
||||
<span class="session-meta-item"><i data-lucide="calendar" class="w-3.5 h-3.5 inline mr-1"></i>${formatDate(session.createdAt)}</span>
|
||||
<span class="session-meta-item"><i data-lucide="list-checks" class="w-3.5 h-3.5 inline mr-1"></i>${tasks.length} tasks</span>
|
||||
<span class="session-meta-item"><i data-lucide="list-checks" class="w-3.5 h-3.5 inline mr-1"></i>${tasks.length} ${t('session.tasks')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,12 +116,12 @@ function showLiteTaskDetailPage(sessionKey) {
|
||||
<!-- Session Info Bar -->
|
||||
<div class="detail-info-bar">
|
||||
<div class="info-item">
|
||||
<span class="info-label">Created:</span>
|
||||
<span class="info-label">${t('detail.created')}</span>
|
||||
<span class="info-value">${formatDate(session.createdAt)}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">Tasks:</span>
|
||||
<span class="info-value">${tasks.length} tasks</span>
|
||||
<span class="info-label">${t('detail.tasks')}</span>
|
||||
<span class="info-value">${tasks.length} ${t('session.tasks')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -129,27 +129,27 @@ function showLiteTaskDetailPage(sessionKey) {
|
||||
<div class="detail-tabs">
|
||||
<button class="detail-tab active" data-tab="tasks" onclick="switchLiteDetailTab('tasks')">
|
||||
<span class="tab-icon"><i data-lucide="list-checks" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">Tasks</span>
|
||||
<span class="tab-text">${t('tab.tasks')}</span>
|
||||
<span class="tab-count">${tasks.length}</span>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="plan" onclick="switchLiteDetailTab('plan')">
|
||||
<span class="tab-icon"><i data-lucide="ruler" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">Plan</span>
|
||||
<span class="tab-text">${t('tab.plan')}</span>
|
||||
</button>
|
||||
${session.type === 'lite-fix' ? `
|
||||
<button class="detail-tab" data-tab="diagnoses" onclick="switchLiteDetailTab('diagnoses')">
|
||||
<span class="tab-icon"><i data-lucide="stethoscope" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">Diagnoses</span>
|
||||
<span class="tab-text">${t('tab.diagnoses')}</span>
|
||||
${session.diagnoses?.items?.length ? `<span class="tab-count">${session.diagnoses.items.length}</span>` : ''}
|
||||
</button>
|
||||
` : ''}
|
||||
<button class="detail-tab" data-tab="context" onclick="switchLiteDetailTab('context')">
|
||||
<span class="tab-icon"><i data-lucide="package" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">Context</span>
|
||||
<span class="tab-text">${t('tab.context')}</span>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="summary" onclick="switchLiteDetailTab('summary')">
|
||||
<span class="tab-icon"><i data-lucide="file-text" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">Summary</span>
|
||||
<span class="tab-text">${t('tab.summary')}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -232,8 +232,8 @@ function renderLiteTasksTab(session, tasks, completed, inProgress, pending) {
|
||||
return `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon"><i data-lucide="clipboard-list" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">No Tasks</div>
|
||||
<div class="empty-text">This session has no tasks defined.</div>
|
||||
<div class="empty-title">${t('empty.noTasks')}</div>
|
||||
<div class="empty-text">${t('empty.noTasksText')}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -506,8 +506,8 @@ function renderDiagnosesTab(session) {
|
||||
return `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon"><i data-lucide="stethoscope" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">No Diagnoses</div>
|
||||
<div class="empty-text">No diagnosis-*.json files found for this session.</div>
|
||||
<div class="empty-title">${t('empty.noDiagnoses')}</div>
|
||||
<div class="empty-text">${t('empty.noDiagnosesText')}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -41,20 +41,20 @@ async function renderMcpManager() {
|
||||
<div class="mcp-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">Current Project MCP Servers</h3>
|
||||
<h3 class="text-lg font-semibold text-foreground">${t('mcp.currentProject')}</h3>
|
||||
<button class="px-3 py-1.5 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity flex items-center gap-1"
|
||||
onclick="openMcpCreateModal()">
|
||||
<span>+</span> New Server
|
||||
<span>+</span> ${t('mcp.newServer')}
|
||||
</button>
|
||||
</div>
|
||||
<span class="text-sm text-muted-foreground">${currentProjectServerNames.length} servers configured</span>
|
||||
<span class="text-sm text-muted-foreground">${currentProjectServerNames.length} ${t('mcp.serversConfigured')}</span>
|
||||
</div>
|
||||
|
||||
${currentProjectServerNames.length === 0 ? `
|
||||
<div class="mcp-empty-state bg-card border border-border rounded-lg p-6 text-center">
|
||||
<div class="text-muted-foreground mb-3"><i data-lucide="plug" class="w-10 h-10 mx-auto"></i></div>
|
||||
<p class="text-muted-foreground">No MCP servers configured for this project</p>
|
||||
<p class="text-sm text-muted-foreground mt-1">Add servers from the available list below</p>
|
||||
<p class="text-muted-foreground">${t('empty.noMcpServers')}</p>
|
||||
<p class="text-sm text-muted-foreground mt-1">${t('empty.addMcpServersHint')}</p>
|
||||
</div>
|
||||
` : `
|
||||
<div class="mcp-server-grid grid gap-3">
|
||||
@@ -109,13 +109,13 @@ async function renderMcpManager() {
|
||||
<!-- Available MCP Servers from Other Projects -->
|
||||
<div class="mcp-section">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-foreground">Available from Other Projects</h3>
|
||||
<span class="text-sm text-muted-foreground">${otherProjectServers.length} servers available</span>
|
||||
<h3 class="text-lg font-semibold text-foreground">${t('mcp.availableOther')}</h3>
|
||||
<span class="text-sm text-muted-foreground">${otherProjectServers.length} ${t('mcp.serversAvailable')}</span>
|
||||
</div>
|
||||
|
||||
${otherProjectServers.length === 0 ? `
|
||||
<div class="mcp-empty-state bg-card border border-border rounded-lg p-6 text-center">
|
||||
<p class="text-muted-foreground">No additional MCP servers found in other projects</p>
|
||||
<p class="text-muted-foreground">${t('empty.noAdditionalMcp')}</p>
|
||||
</div>
|
||||
` : `
|
||||
<div class="mcp-server-grid grid gap-3">
|
||||
@@ -129,17 +129,17 @@ async function renderMcpManager() {
|
||||
<!-- All Projects MCP Overview Table -->
|
||||
<div class="mcp-section mt-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-foreground">All Projects MCP Overview</h3>
|
||||
<span class="text-sm text-muted-foreground">${Object.keys(mcpAllProjects).length} projects</span>
|
||||
<h3 class="text-lg font-semibold text-foreground">${t('mcp.allProjects')}</h3>
|
||||
<span class="text-sm text-muted-foreground">${Object.keys(mcpAllProjects).length} ${t('mcp.projects')}</span>
|
||||
</div>
|
||||
|
||||
<div class="mcp-projects-table bg-card border border-border rounded-lg overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead class="bg-muted/50">
|
||||
<tr>
|
||||
<th class="text-left px-4 py-3 text-sm font-semibold text-foreground border-b border-border">Project</th>
|
||||
<th class="text-left px-4 py-3 text-sm font-semibold text-foreground border-b border-border">MCP Servers</th>
|
||||
<th class="text-center px-4 py-3 text-sm font-semibold text-foreground border-b border-border w-24">Status</th>
|
||||
<th class="text-left px-4 py-3 text-sm font-semibold text-foreground border-b border-border">${t('mcp.project')}</th>
|
||||
<th class="text-left px-4 py-3 text-sm font-semibold text-foreground border-b border-border">${t('mcp.servers')}</th>
|
||||
<th class="text-center px-4 py-3 text-sm font-semibold text-foreground border-b border-border w-24">${t('mcp.status')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -158,7 +158,7 @@ async function renderMcpManager() {
|
||||
<div class="min-w-0">
|
||||
<div class="font-medium text-foreground truncate text-sm" title="${escapeHtml(path)}">
|
||||
${escapeHtml(path.split('\\').pop() || path)}
|
||||
${isCurrentProject ? '<span class="ml-2 text-xs text-primary font-medium">(Current)</span>' : ''}
|
||||
${isCurrentProject ? `<span class="ml-2 text-xs text-primary font-medium">${t('mcp.current')}</span>` : ''}
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground truncate">${escapeHtml(path)}</div>
|
||||
</div>
|
||||
@@ -167,7 +167,7 @@ async function renderMcpManager() {
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
${serverNames.length === 0
|
||||
? '<span class="text-xs text-muted-foreground italic">No MCP servers</span>'
|
||||
? `<span class="text-xs text-muted-foreground italic">${t('mcp.noMcpServers')}</span>`
|
||||
: serverNames.map(serverName => {
|
||||
const isEnabled = !projectDisabled.includes(serverName);
|
||||
return `
|
||||
@@ -247,7 +247,7 @@ function renderMcpServerCard(serverName, serverConfig, isEnabled, isInCurrentPro
|
||||
<button class="text-xs text-destructive hover:text-destructive/80 transition-colors"
|
||||
data-server-name="${escapeHtml(serverName)}"
|
||||
data-action="remove">
|
||||
Remove from project
|
||||
${t('mcp.removeFromProject')}
|
||||
</button>
|
||||
</div>
|
||||
` : ''}
|
||||
@@ -271,7 +271,7 @@ function renderAvailableServerCard(serverName, serverInfo) {
|
||||
data-server-name="${escapeHtml(serverName)}"
|
||||
data-server-config='${JSON.stringify(serverConfig).replace(/'/g, "'")}'
|
||||
data-action="add">
|
||||
Add
|
||||
${t('mcp.add')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -306,7 +306,7 @@ function renderGlobalServerCard(serverName, serverConfig, source = 'user') {
|
||||
data-server-name="${escapeHtml(serverName)}"
|
||||
data-server-config='${JSON.stringify(serverConfig).replace(/'/g, "'")}'
|
||||
data-action="add">
|
||||
Add to Project
|
||||
${t('mcp.addToProject')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -403,7 +403,7 @@ function attachMcpEventListeners() {
|
||||
document.querySelectorAll('.mcp-server-card button[data-action="remove"]').forEach(btn => {
|
||||
btn.addEventListener('click', async (e) => {
|
||||
const serverName = e.target.dataset.serverName;
|
||||
if (confirm(`Remove MCP server "${serverName}" from this project?`)) {
|
||||
if (confirm(t('mcp.removeConfirm', { name: serverName }))) {
|
||||
await removeMcpServerFromProject(serverName);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -43,14 +43,14 @@ function showSessionDetailPage(sessionKey) {
|
||||
<div class="detail-header">
|
||||
<button class="btn-back" onclick="goBackToSessions()">
|
||||
<span class="back-icon">←</span>
|
||||
<span>Back to Sessions</span>
|
||||
<span>${t('detail.backToSessions')}</span>
|
||||
</button>
|
||||
<div class="detail-title-row">
|
||||
<h2 class="detail-session-id">${escapeHtml(session.session_id)}</h2>
|
||||
<div class="detail-badges">
|
||||
<span class="session-type-badge ${session.type || 'workflow'}">${session.type || 'workflow'}</span>
|
||||
<span class="session-status ${isActive ? 'active' : 'archived'}">
|
||||
${isActive ? 'ACTIVE' : 'ARCHIVED'}
|
||||
${isActive ? t('session.status.active') : t('session.status.archived')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,22 +59,22 @@ function showSessionDetailPage(sessionKey) {
|
||||
<!-- Session Info Bar -->
|
||||
<div class="detail-info-bar">
|
||||
<div class="info-item">
|
||||
<span class="info-label">Created:</span>
|
||||
<span class="info-label">${t('detail.created')}</span>
|
||||
<span class="info-value">${formatDate(session.created_at)}</span>
|
||||
</div>
|
||||
${session.archived_at ? `
|
||||
<div class="info-item">
|
||||
<span class="info-label">Archived:</span>
|
||||
<span class="info-label">${t('detail.archived')}</span>
|
||||
<span class="info-value">${formatDate(session.archived_at)}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="info-item">
|
||||
<span class="info-label">Project:</span>
|
||||
<span class="info-label">${t('detail.project')}</span>
|
||||
<span class="info-value">${escapeHtml(session.project || '-')}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">Tasks:</span>
|
||||
<span class="info-value">${completed}/${tasks.length} completed</span>
|
||||
<span class="info-label">${t('detail.tasks')}</span>
|
||||
<span class="info-value">${completed}/${tasks.length} ${t('detail.completed')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -82,26 +82,26 @@ function showSessionDetailPage(sessionKey) {
|
||||
<div class="detail-tabs">
|
||||
<button class="detail-tab active" data-tab="tasks" onclick="switchDetailTab('tasks')">
|
||||
<span class="tab-icon"><i data-lucide="list-checks" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">Tasks</span>
|
||||
<span class="tab-text">${t('tab.tasks')}</span>
|
||||
<span class="tab-count">${tasks.length}</span>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="context" onclick="switchDetailTab('context')">
|
||||
<span class="tab-icon"><i data-lucide="package" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">Context</span>
|
||||
<span class="tab-text">${t('tab.context')}</span>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="summary" onclick="switchDetailTab('summary')">
|
||||
<span class="tab-icon"><i data-lucide="file-text" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">Summary</span>
|
||||
<span class="tab-text">${t('tab.summary')}</span>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="impl-plan" onclick="switchDetailTab('impl-plan')">
|
||||
<span class="tab-icon"><i data-lucide="ruler" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">IMPL Plan</span>
|
||||
<span class="tab-text">${t('tab.implPlan')}</span>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="conflict" onclick="switchDetailTab('conflict')"> <span class="tab-icon"><i data-lucide="scale" class="w-4 h-4"></i></span> <span class="tab-text">Conflict</span> </button>
|
||||
<button class="detail-tab" data-tab="conflict" onclick="switchDetailTab('conflict')"> <span class="tab-icon"><i data-lucide="scale" class="w-4 h-4"></i></span> <span class="tab-text">${t('tab.conflict')}</span> </button>
|
||||
${session.hasReview ? `
|
||||
<button class="detail-tab" data-tab="review" onclick="switchDetailTab('review')">
|
||||
<span class="tab-icon"><i data-lucide="search" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">Review</span>
|
||||
<span class="tab-text">${t('tab.review')}</span>
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
@@ -178,33 +178,33 @@ function renderTasksTab(session, tasks, completed, inProgress, pending) {
|
||||
<!-- Combined Stats & Actions Bar -->
|
||||
<div class="task-toolbar">
|
||||
<div class="task-stats-bar">
|
||||
<span class="task-stat completed"><i data-lucide="check-circle" class="w-4 h-4 inline mr-1"></i>${completed} completed</span>
|
||||
<span class="task-stat in-progress"><i data-lucide="loader-2" class="w-4 h-4 inline mr-1"></i>${inProgress} in progress</span>
|
||||
<span class="task-stat pending"><i data-lucide="circle" class="w-4 h-4 inline mr-1"></i>${pending} pending</span>
|
||||
<span class="task-stat completed"><i data-lucide="check-circle" class="w-4 h-4 inline mr-1"></i>${completed} ${t('task.completed')}</span>
|
||||
<span class="task-stat in-progress"><i data-lucide="loader-2" class="w-4 h-4 inline mr-1"></i>${inProgress} ${t('task.inProgress')}</span>
|
||||
<span class="task-stat pending"><i data-lucide="circle" class="w-4 h-4 inline mr-1"></i>${pending} ${t('task.pending')}</span>
|
||||
</div>
|
||||
<div class="toolbar-divider"></div>
|
||||
<div class="task-bulk-actions">
|
||||
<span class="bulk-label">Quick Actions:</span>
|
||||
<button class="bulk-action-btn" onclick="bulkSetAllStatus('pending')" title="Set all tasks to pending">
|
||||
<span class="bulk-icon"><i data-lucide="circle" class="w-4 h-4"></i></span> All Pending
|
||||
<span class="bulk-label">${t('task.quickActions')}</span>
|
||||
<button class="bulk-action-btn" onclick="bulkSetAllStatus('pending')" title="${t('task.allPending')}">
|
||||
<span class="bulk-icon"><i data-lucide="circle" class="w-4 h-4"></i></span> ${t('task.allPending')}
|
||||
</button>
|
||||
<button class="bulk-action-btn" onclick="bulkSetAllStatus('in_progress')" title="Set all tasks to in progress">
|
||||
<span class="bulk-icon"><i data-lucide="loader-2" class="w-4 h-4"></i></span> All In Progress
|
||||
<button class="bulk-action-btn" onclick="bulkSetAllStatus('in_progress')" title="${t('task.allInProgress')}">
|
||||
<span class="bulk-icon"><i data-lucide="loader-2" class="w-4 h-4"></i></span> ${t('task.allInProgress')}
|
||||
</button>
|
||||
<button class="bulk-action-btn completed" onclick="bulkSetAllStatus('completed')" title="Set all tasks to completed">
|
||||
<span class="bulk-icon"><i data-lucide="check-circle" class="w-4 h-4"></i></span> All Completed
|
||||
<button class="bulk-action-btn completed" onclick="bulkSetAllStatus('completed')" title="${t('task.allCompleted')}">
|
||||
<span class="bulk-icon"><i data-lucide="check-circle" class="w-4 h-4"></i></span> ${t('task.allCompleted')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tasks-list" id="tasksListContent">
|
||||
${showLoading ? `
|
||||
<div class="tab-loading">Loading task details...</div>
|
||||
<div class="tab-loading">${t('common.loading')}</div>
|
||||
` : (tasks.length === 0 ? `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon"><i data-lucide="clipboard-list" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">No Tasks</div>
|
||||
<div class="empty-text">This session has no tasks defined.</div>
|
||||
<div class="empty-title">${t('empty.noTasks')}</div>
|
||||
<div class="empty-text">${t('empty.noTasksText')}</div>
|
||||
</div>
|
||||
` : tasks.map(task => renderDetailTaskItem(task)).join(''))}
|
||||
</div>
|
||||
@@ -217,7 +217,7 @@ async function loadFullTaskDetails() {
|
||||
if (!session || !window.SERVER_MODE || !session.path) return;
|
||||
|
||||
const tasksContainer = document.getElementById('tasksListContent');
|
||||
tasksContainer.innerHTML = '<div class="tab-loading">Loading full task details...</div>';
|
||||
tasksContainer.innerHTML = `<div class="tab-loading">${t('common.loading')}</div>`;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=tasks`);
|
||||
@@ -231,15 +231,15 @@ async function loadFullTaskDetails() {
|
||||
tasksContainer.innerHTML = `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon"><i data-lucide="clipboard-list" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">No Task Files</div>
|
||||
<div class="empty-text">No IMPL-*.json files found in .task/</div>
|
||||
<div class="empty-title">${t('empty.noTaskFiles')}</div>
|
||||
<div class="empty-text">${t('empty.noTaskFilesText')}</div>
|
||||
</div>
|
||||
`;
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
tasksContainer.innerHTML = `<div class="tab-error">Failed to load tasks: ${err.message}</div>`;
|
||||
tasksContainer.innerHTML = `<div class="tab-error">${t('context.loadError', { error: err.message })}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,9 +271,9 @@ function renderDetailTaskItem(task) {
|
||||
|
||||
function formatStatusLabel(status) {
|
||||
const labels = {
|
||||
'pending': '<i data-lucide="circle" class="w-3 h-3 inline mr-1"></i>Pending',
|
||||
'in_progress': '<i data-lucide="loader-2" class="w-3 h-3 inline mr-1"></i>In Progress',
|
||||
'completed': '<i data-lucide="check-circle" class="w-3 h-3 inline mr-1"></i>Completed'
|
||||
'pending': `<i data-lucide="circle" class="w-3 h-3 inline mr-1"></i>${t('task.status.pending')}`,
|
||||
'in_progress': `<i data-lucide="loader-2" class="w-3 h-3 inline mr-1"></i>${t('task.status.inProgress')}`,
|
||||
'completed': `<i data-lucide="check-circle" class="w-3 h-3 inline mr-1"></i>${t('task.status.completed')}`
|
||||
};
|
||||
return labels[status] || status;
|
||||
}
|
||||
@@ -562,7 +562,7 @@ function showRawSessionJson(sessionKey) {
|
||||
async function updateSingleTaskStatus(taskId, newStatus) {
|
||||
const session = sessionDataStore[currentSessionDetailKey];
|
||||
if (!session || !window.SERVER_MODE || !session.path) {
|
||||
showToast('Status update requires server mode', 'error');
|
||||
showToast(t('toast.statusUpdateRequires'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -582,14 +582,14 @@ async function updateSingleTaskStatus(taskId, newStatus) {
|
||||
// Update UI
|
||||
updateTaskItemUI(taskId, newStatus);
|
||||
updateTaskStatsBar();
|
||||
showToast(`Task ${taskId} status updated`, 'success');
|
||||
showToast(t('task.statusUpdated', { id: taskId }), 'success');
|
||||
} else {
|
||||
showToast(result.error || 'Failed to update status', 'error');
|
||||
showToast(result.error || t('toast.failedToUpdate'), 'error');
|
||||
// Revert select
|
||||
revertTaskSelect(taskId);
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('Error updating status: ' + error.message, 'error');
|
||||
showToast(t('toast.errorUpdating', { error: error.message }), 'error');
|
||||
revertTaskSelect(taskId);
|
||||
}
|
||||
}
|
||||
@@ -597,14 +597,15 @@ async function updateSingleTaskStatus(taskId, newStatus) {
|
||||
async function bulkSetAllStatus(newStatus) {
|
||||
const session = sessionDataStore[currentSessionDetailKey];
|
||||
if (!session || !window.SERVER_MODE || !session.path) {
|
||||
showToast('Bulk update requires server mode', 'error');
|
||||
showToast(t('toast.bulkUpdateRequires'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const taskIds = currentDrawerTasks.map(t => t.task_id || t.id);
|
||||
if (taskIds.length === 0) return;
|
||||
|
||||
if (!confirm(`Set all ${taskIds.length} tasks to "${formatStatusLabel(newStatus)}"?`)) {
|
||||
const statusLabel = t(`task.status.${newStatus === 'in_progress' ? 'inProgress' : newStatus}`);
|
||||
if (!confirm(t('task.setAllConfirm', { count: taskIds.length, status: statusLabel }))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -624,12 +625,12 @@ async function bulkSetAllStatus(newStatus) {
|
||||
// Update all task UIs
|
||||
taskIds.forEach(id => updateTaskItemUI(id, newStatus));
|
||||
updateTaskStatsBar();
|
||||
showToast(`All ${taskIds.length} tasks updated`, 'success');
|
||||
showToast(t('task.tasksUpdated', { count: taskIds.length }), 'success');
|
||||
} else {
|
||||
showToast(result.error || 'Failed to bulk update', 'error');
|
||||
showToast(result.error || t('toast.failedToBulkUpdate'), 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('Error in bulk update: ' + error.message, 'error');
|
||||
showToast(t('toast.errorInBulk', { error: error.message }), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,7 +646,7 @@ async function bulkSetPendingToInProgress() {
|
||||
.map(t => t.task_id || t.id);
|
||||
|
||||
if (pendingTaskIds.length === 0) {
|
||||
showToast('No pending tasks to start', 'info');
|
||||
showToast(t('task.noPendingTasks'), 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -664,12 +665,12 @@ async function bulkSetPendingToInProgress() {
|
||||
if (result.success) {
|
||||
pendingTaskIds.forEach(id => updateTaskItemUI(id, 'in_progress'));
|
||||
updateTaskStatsBar();
|
||||
showToast(`${pendingTaskIds.length} tasks moved to In Progress`, 'success');
|
||||
showToast(t('task.movedToInProgress', { count: pendingTaskIds.length }), 'success');
|
||||
} else {
|
||||
showToast(result.error || 'Failed to update', 'error');
|
||||
showToast(result.error || t('toast.failedToUpdate'), 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('Error: ' + error.message, 'error');
|
||||
showToast(t('toast.error', { error: error.message }), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -685,7 +686,7 @@ async function bulkSetInProgressToCompleted() {
|
||||
.map(t => t.task_id || t.id);
|
||||
|
||||
if (inProgressTaskIds.length === 0) {
|
||||
showToast('No in-progress tasks to complete', 'info');
|
||||
showToast(t('task.noInProgressTasks'), 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -704,12 +705,12 @@ async function bulkSetInProgressToCompleted() {
|
||||
if (result.success) {
|
||||
inProgressTaskIds.forEach(id => updateTaskItemUI(id, 'completed'));
|
||||
updateTaskStatsBar();
|
||||
showToast(`${inProgressTaskIds.length} tasks completed`, 'success');
|
||||
showToast(t('task.tasksCompleted', { count: inProgressTaskIds.length }), 'success');
|
||||
} else {
|
||||
showToast(result.error || 'Failed to update', 'error');
|
||||
showToast(result.error || t('toast.failedToUpdate'), 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('Error: ' + error.message, 'error');
|
||||
showToast(t('toast.error', { error: error.message }), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -743,9 +744,9 @@ function updateTaskStatsBar() {
|
||||
const statsBar = document.querySelector('.task-stats-bar');
|
||||
if (statsBar) {
|
||||
statsBar.innerHTML = `
|
||||
<span class="task-stat completed"><i data-lucide="check-circle" class="w-4 h-4 inline mr-1"></i>${completed} completed</span>
|
||||
<span class="task-stat in-progress"><i data-lucide="loader-2" class="w-4 h-4 inline mr-1"></i>${inProgress} in progress</span>
|
||||
<span class="task-stat pending"><i data-lucide="circle" class="w-4 h-4 inline mr-1"></i>${pending} pending</span>
|
||||
<span class="task-stat completed"><i data-lucide="check-circle" class="w-4 h-4 inline mr-1"></i>${completed} ${t('task.completed')}</span>
|
||||
<span class="task-stat in-progress"><i data-lucide="loader-2" class="w-4 h-4 inline mr-1"></i>${inProgress} ${t('task.inProgress')}</span>
|
||||
<span class="task-stat pending"><i data-lucide="circle" class="w-4 h-4 inline mr-1"></i>${pending} ${t('task.pending')}</span>
|
||||
`;
|
||||
// Reinitialize Lucide icons
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
|
||||
Reference in New Issue
Block a user