mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
fix(multi-cli): complete solution details display in summary tab (#98)
Fixed issue where multi-CLI planning solution cards only showed count, feasibility, effort, and risk badges but had empty content area. Changes: - Enhanced renderMultiCliSummaryContent() to extract and display all solution fields - Solution name (name/title) - Feasibility score (feasibility) - Effort level (effort) - Risk level (risk) - Summary/description (summary) - Pros list (pros) - Cons list (cons) - Added CSS styles for solution cards - .solution-details, .details-label, .details-list - .solution-header, .solution-title-row, .solution-badges - .badge with variants for feasibility/effort/risk - Fixed related issues: - Added multiCliPlan support to backend data structures - Exposed liteTaskDataStore to window for global access - Fixed header left-alignment in detail pages - Added 'active' class to tab content for visibility Files modified: - ccw/src/templates/dashboard-js/views/lite-tasks.js - ccw/src/templates/dashboard-css/04-lite-tasks.css - ccw/src/core/server.ts - ccw/src/core/routes/system-routes.ts - ccw/src/templates/dashboard-js/state.js - ccw/src/templates/dashboard-css/02-session.css - ccw/src/config/litellm-api-config-manager.ts (fix homedir import) Closes #98
This commit is contained in:
@@ -4,9 +4,10 @@
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from 'fs';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { StoragePaths, GlobalPaths, ensureStorageDir } from './storage-paths.js';
|
||||
import { getCodexLensDataDir } from '../utils/codexlens-path.js';
|
||||
import type {
|
||||
LiteLLMApiConfig,
|
||||
ProviderCredential,
|
||||
@@ -798,7 +799,7 @@ export function syncCodexLensConfig(baseDir: string): { success: boolean; messag
|
||||
const rotationConfig = config.codexlensEmbeddingRotation;
|
||||
|
||||
// Get CodexLens settings path
|
||||
const codexlensDir = join(homedir(), '.codexlens');
|
||||
const codexlensDir = getCodexLensDataDir();
|
||||
const settingsPath = join(codexlensDir, 'settings.json');
|
||||
|
||||
// Ensure directory exists
|
||||
|
||||
@@ -145,7 +145,7 @@ async function getWorkflowData(projectPath: string): Promise<any> {
|
||||
generatedAt: new Date().toISOString(),
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
liteTasks: { litePlan: [], liteFix: [] },
|
||||
liteTasks: { litePlan: [], liteFix: [], multiCliPlan: [] },
|
||||
reviewData: { dimensions: {} },
|
||||
projectOverview: null,
|
||||
statistics: {
|
||||
@@ -155,7 +155,8 @@ async function getWorkflowData(projectPath: string): Promise<any> {
|
||||
completedTasks: 0,
|
||||
reviewFindings: 0,
|
||||
litePlanCount: 0,
|
||||
liteFixCount: 0
|
||||
liteFixCount: 0,
|
||||
multiCliPlanCount: 0
|
||||
},
|
||||
projectPath: normalizePathForDisplay(resolvedPath),
|
||||
recentPaths: getRecentPaths()
|
||||
|
||||
@@ -29,7 +29,7 @@ import { handleLiteLLMApiRoutes } from './routes/litellm-api-routes.js';
|
||||
import { handleNavStatusRoutes } from './routes/nav-status-routes.js';
|
||||
import { handleAuthRoutes } from './routes/auth-routes.js';
|
||||
import { handleLoopRoutes } from './routes/loop-routes.js';
|
||||
import { handleLoopV2Routes } from './routes/loop-v2-routes.js';
|
||||
import { handleLoopV2Routes, initializeCliToolsCache } from './routes/loop-v2-routes.js';
|
||||
import { handleTestLoopRoutes } from './routes/test-loop-routes.js';
|
||||
import { handleTaskRoutes } from './routes/task-routes.js';
|
||||
|
||||
@@ -383,10 +383,10 @@ function generateServerDashboard(initialPath: string): string {
|
||||
generatedAt: new Date().toISOString(),
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
liteTasks: { litePlan: [], liteFix: [] },
|
||||
liteTasks: { litePlan: [], liteFix: [], multiCliPlan: [] },
|
||||
reviewData: { dimensions: {} },
|
||||
projectOverview: null,
|
||||
statistics: { totalSessions: 0, activeSessions: 0, totalTasks: 0, completedTasks: 0, reviewFindings: 0, litePlanCount: 0, liteFixCount: 0 }
|
||||
statistics: { totalSessions: 0, activeSessions: 0, totalTasks: 0, completedTasks: 0, reviewFindings: 0, litePlanCount: 0, liteFixCount: 0, multiCliPlanCount: 0 }
|
||||
};
|
||||
|
||||
// Replace JS placeholders
|
||||
@@ -723,6 +723,9 @@ export async function startServer(options: ServerOptions = {}): Promise<http.Ser
|
||||
console.log(`WebSocket endpoint available at ws://${host}:${serverPort}/ws`);
|
||||
console.log(`Hook endpoint available at POST http://${host}:${serverPort}/api/hook`);
|
||||
|
||||
// Initialize CLI tools cache for Loop V2 routes
|
||||
initializeCliToolsCache();
|
||||
|
||||
// Start periodic cleanup of stale CLI executions (every 2 minutes)
|
||||
const CLEANUP_INTERVAL_MS = 2 * 60 * 1000;
|
||||
const cleanupInterval = setInterval(cleanupStaleExecutions, CLEANUP_INTERVAL_MS);
|
||||
|
||||
@@ -464,6 +464,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.btn-back {
|
||||
@@ -492,6 +493,7 @@
|
||||
.detail-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@@ -1732,12 +1732,25 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: hsl(var(--muted) / 0.2);
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.solution-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.solution-badges {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.solution-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
@@ -2782,6 +2795,39 @@
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.solution-details {
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid hsl(var(--border) / 0.5);
|
||||
}
|
||||
|
||||
.solution-details:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.details-label {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.details-list {
|
||||
margin: 0;
|
||||
padding-left: 1.25rem;
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
.details-list li {
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.6;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin-bottom: 0.375rem;
|
||||
}
|
||||
|
||||
.details-list li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.solution-approach,
|
||||
.solution-dependencies,
|
||||
.solution-concerns {
|
||||
@@ -3976,6 +4022,62 @@
|
||||
border-top: 1px solid hsl(var(--border) / 0.5);
|
||||
}
|
||||
|
||||
/* Badge - generic badge styling */
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.625rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badge.feasibility {
|
||||
background: hsl(var(--primary) / 0.1);
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.badge.effort {
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.badge.effort.low {
|
||||
background: hsl(142 70% 50% / 0.15);
|
||||
color: hsl(142 70% 35%);
|
||||
}
|
||||
|
||||
.badge.effort.medium {
|
||||
background: hsl(30 90% 50% / 0.15);
|
||||
color: hsl(30 90% 40%);
|
||||
}
|
||||
|
||||
.badge.effort.high {
|
||||
background: hsl(0 70% 50% / 0.15);
|
||||
color: hsl(0 70% 45%);
|
||||
}
|
||||
|
||||
.badge.risk {
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.badge.risk.low {
|
||||
background: hsl(142 70% 50% / 0.15);
|
||||
color: hsl(142 70% 35%);
|
||||
}
|
||||
|
||||
.badge.risk.medium {
|
||||
background: hsl(30 90% 50% / 0.15);
|
||||
color: hsl(30 90% 40%);
|
||||
}
|
||||
|
||||
.badge.risk.high {
|
||||
background: hsl(0 70% 50% / 0.15);
|
||||
color: hsl(0 70% 45%);
|
||||
}
|
||||
|
||||
/* Feasibility badge */
|
||||
.feasibility-badge {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -36,10 +36,12 @@ const sessionDataStore = {};
|
||||
// Store lite task session data for detail page access
|
||||
// Key: session key, Value: lite session data object
|
||||
const liteTaskDataStore = {};
|
||||
window.liteTaskDataStore = liteTaskDataStore;
|
||||
|
||||
// Store task JSON data in a global map instead of inline script tags
|
||||
// Key: unique task ID, Value: raw task JSON data
|
||||
const taskJsonStore = {};
|
||||
window.taskJsonStore = taskJsonStore;
|
||||
|
||||
// ========== Global Notification Queue ==========
|
||||
// Notification queue visible from any view (persisted to localStorage)
|
||||
|
||||
@@ -418,7 +418,7 @@ function showMultiCliDetailPage(sessionKey) {
|
||||
</div>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="detail-tab-content" id="multiCliDetailTabContent">
|
||||
<div class="detail-tab-content active" id="multiCliDetailTabContent">
|
||||
${renderMultiCliTasksTab(session)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -653,12 +653,17 @@ function switchMultiCliDetailTab(tabName) {
|
||||
break;
|
||||
case 'context':
|
||||
loadAndRenderMultiCliContextTab(session, contentArea);
|
||||
contentArea.classList.add('active');
|
||||
return; // Early return as this is async
|
||||
case 'summary':
|
||||
loadAndRenderMultiCliSummaryTab(session, contentArea);
|
||||
contentArea.classList.add('active');
|
||||
return; // Early return as this is async
|
||||
}
|
||||
|
||||
// Add active class to show content
|
||||
contentArea.classList.add('active');
|
||||
|
||||
// Re-initialize after tab switch
|
||||
setTimeout(() => {
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
@@ -1097,13 +1102,52 @@ function renderMultiCliSummaryContent(summary, session) {
|
||||
<span class="section-label"><i data-lucide="lightbulb" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.summary.solutions')} (${synthesis.solutions.length})</span>
|
||||
</div>
|
||||
<div class="collapsible-content collapsed">
|
||||
${synthesis.solutions.map((sol, idx) => `
|
||||
<div class="solution-summary-item">
|
||||
<span class="solution-num">#${idx + 1}</span>
|
||||
<span class="solution-name">${escapeHtml(getI18nText(sol.title) || sol.id || `${t('multiCli.summary.solution')} ${idx + 1}`)}</span>
|
||||
${sol.feasibility?.score ? `<span class="feasibility-badge">${Math.round(sol.feasibility.score * 100)}%</span>` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
${synthesis.solutions.map((sol, idx) => {
|
||||
const name = getI18nText(sol.name) || sol.title || sol.id || `${t('multiCli.summary.solution')} ${idx + 1}`;
|
||||
const summary = getI18nText(sol.summary) || '';
|
||||
const feasibility = sol.feasibility?.score || sol.feasibility || 0;
|
||||
const effort = sol.effort || '';
|
||||
const risk = sol.risk || '';
|
||||
const pros = sol.pros || [];
|
||||
const cons = sol.cons || [];
|
||||
|
||||
return `
|
||||
<div class="solution-card">
|
||||
<div class="solution-header">
|
||||
<div class="solution-title-row">
|
||||
<span class="solution-num">#${idx + 1}</span>
|
||||
<span class="solution-name">${escapeHtml(name)}</span>
|
||||
</div>
|
||||
<div class="solution-badges">
|
||||
${feasibility ? `<span class="badge feasibility">${Math.round(feasibility * 100)}%</span>` : ''}
|
||||
${effort ? `<span class="badge effort ${escapeHtml(effort)}">${escapeHtml(effort)}</span>` : ''}
|
||||
${risk ? `<span class="badge risk ${escapeHtml(risk)}">${escapeHtml(risk)}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
${summary ? `
|
||||
<div class="solution-summary">
|
||||
<p>${escapeHtml(summary)}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
${pros.length > 0 ? `
|
||||
<div class="solution-details">
|
||||
<h5 class="details-label">✓ ${t('multiCli.summary.pros') || 'Pros'}:</h5>
|
||||
<ul class="details-list">
|
||||
${pros.map(p => `<li>${escapeHtml(getI18nText(p) || p)}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
${cons.length > 0 ? `
|
||||
<div class="solution-details">
|
||||
<h5 class="details-label">✗ ${t('multiCli.summary.cons') || 'Cons'}:</h5>
|
||||
<ul class="details-list">
|
||||
${cons.map(c => `<li>${escapeHtml(getI18nText(c) || c)}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
@@ -2722,12 +2766,12 @@ function showLiteTaskDetailPage(sessionKey) {
|
||||
<div class="detail-header">
|
||||
<button class="btn-back" onclick="goBackToLiteTasks()">
|
||||
<span class="back-icon">←</span>
|
||||
<span>Back to ${session.type === 'lite-plan' ? 'Lite Plan' : 'Lite Fix'}</span>
|
||||
<span>${session.type === 'lite-plan' ? t('lite.backToList', { type: 'Plan' }) : session.type === 'lite-fix' ? t('lite.backToList', { type: 'Fix' }) : t('multiCli.backToList')}</span>
|
||||
</button>
|
||||
<div class="detail-title-row">
|
||||
<h2 class="detail-session-id">${session.type === 'lite-plan' ? '<i data-lucide="file-edit" class="w-5 h-5 inline mr-2"></i>' : '<i data-lucide="wrench" class="w-5 h-5 inline mr-2"></i>'} ${escapeHtml(session.id)}</h2>
|
||||
<h2 class="detail-session-id">${session.type === 'lite-plan' ? '<i data-lucide="file-edit" class="w-5 h-5 inline mr-2"></i>' : session.type === 'lite-fix' ? '<i data-lucide="wrench" class="w-5 h-5 inline mr-2"></i>' : '<i data-lucide="speech-icon" class="w-5 h-5 inline mr-2"></i>'} ${escapeHtml(session.id)}</h2>
|
||||
<div class="detail-badges">
|
||||
<span class="session-type-badge ${session.type}">${session.type}</span>
|
||||
<span class="session-type-badge ${session.type}">${session.type === 'multi-cli-plan' ? 'MULTI-CLI' : session.type}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user