mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +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 { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from 'fs';
|
||||||
import { homedir } from 'os';
|
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
import { homedir } from 'os';
|
||||||
import { StoragePaths, GlobalPaths, ensureStorageDir } from './storage-paths.js';
|
import { StoragePaths, GlobalPaths, ensureStorageDir } from './storage-paths.js';
|
||||||
|
import { getCodexLensDataDir } from '../utils/codexlens-path.js';
|
||||||
import type {
|
import type {
|
||||||
LiteLLMApiConfig,
|
LiteLLMApiConfig,
|
||||||
ProviderCredential,
|
ProviderCredential,
|
||||||
@@ -798,7 +799,7 @@ export function syncCodexLensConfig(baseDir: string): { success: boolean; messag
|
|||||||
const rotationConfig = config.codexlensEmbeddingRotation;
|
const rotationConfig = config.codexlensEmbeddingRotation;
|
||||||
|
|
||||||
// Get CodexLens settings path
|
// Get CodexLens settings path
|
||||||
const codexlensDir = join(homedir(), '.codexlens');
|
const codexlensDir = getCodexLensDataDir();
|
||||||
const settingsPath = join(codexlensDir, 'settings.json');
|
const settingsPath = join(codexlensDir, 'settings.json');
|
||||||
|
|
||||||
// Ensure directory exists
|
// Ensure directory exists
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ async function getWorkflowData(projectPath: string): Promise<any> {
|
|||||||
generatedAt: new Date().toISOString(),
|
generatedAt: new Date().toISOString(),
|
||||||
activeSessions: [],
|
activeSessions: [],
|
||||||
archivedSessions: [],
|
archivedSessions: [],
|
||||||
liteTasks: { litePlan: [], liteFix: [] },
|
liteTasks: { litePlan: [], liteFix: [], multiCliPlan: [] },
|
||||||
reviewData: { dimensions: {} },
|
reviewData: { dimensions: {} },
|
||||||
projectOverview: null,
|
projectOverview: null,
|
||||||
statistics: {
|
statistics: {
|
||||||
@@ -155,7 +155,8 @@ async function getWorkflowData(projectPath: string): Promise<any> {
|
|||||||
completedTasks: 0,
|
completedTasks: 0,
|
||||||
reviewFindings: 0,
|
reviewFindings: 0,
|
||||||
litePlanCount: 0,
|
litePlanCount: 0,
|
||||||
liteFixCount: 0
|
liteFixCount: 0,
|
||||||
|
multiCliPlanCount: 0
|
||||||
},
|
},
|
||||||
projectPath: normalizePathForDisplay(resolvedPath),
|
projectPath: normalizePathForDisplay(resolvedPath),
|
||||||
recentPaths: getRecentPaths()
|
recentPaths: getRecentPaths()
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { handleLiteLLMApiRoutes } from './routes/litellm-api-routes.js';
|
|||||||
import { handleNavStatusRoutes } from './routes/nav-status-routes.js';
|
import { handleNavStatusRoutes } from './routes/nav-status-routes.js';
|
||||||
import { handleAuthRoutes } from './routes/auth-routes.js';
|
import { handleAuthRoutes } from './routes/auth-routes.js';
|
||||||
import { handleLoopRoutes } from './routes/loop-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 { handleTestLoopRoutes } from './routes/test-loop-routes.js';
|
||||||
import { handleTaskRoutes } from './routes/task-routes.js';
|
import { handleTaskRoutes } from './routes/task-routes.js';
|
||||||
|
|
||||||
@@ -383,10 +383,10 @@ function generateServerDashboard(initialPath: string): string {
|
|||||||
generatedAt: new Date().toISOString(),
|
generatedAt: new Date().toISOString(),
|
||||||
activeSessions: [],
|
activeSessions: [],
|
||||||
archivedSessions: [],
|
archivedSessions: [],
|
||||||
liteTasks: { litePlan: [], liteFix: [] },
|
liteTasks: { litePlan: [], liteFix: [], multiCliPlan: [] },
|
||||||
reviewData: { dimensions: {} },
|
reviewData: { dimensions: {} },
|
||||||
projectOverview: null,
|
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
|
// 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(`WebSocket endpoint available at ws://${host}:${serverPort}/ws`);
|
||||||
console.log(`Hook endpoint available at POST http://${host}:${serverPort}/api/hook`);
|
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)
|
// Start periodic cleanup of stale CLI executions (every 2 minutes)
|
||||||
const CLEANUP_INTERVAL_MS = 2 * 60 * 1000;
|
const CLEANUP_INTERVAL_MS = 2 * 60 * 1000;
|
||||||
const cleanupInterval = setInterval(cleanupStaleExecutions, CLEANUP_INTERVAL_MS);
|
const cleanupInterval = setInterval(cleanupStaleExecutions, CLEANUP_INTERVAL_MS);
|
||||||
|
|||||||
@@ -464,6 +464,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-back {
|
.btn-back {
|
||||||
@@ -492,6 +493,7 @@
|
|||||||
.detail-title-row {
|
.detail-title-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1732,12 +1732,25 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
background: hsl(var(--muted) / 0.2);
|
background: hsl(var(--muted) / 0.2);
|
||||||
border-bottom: 1px solid hsl(var(--border));
|
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 {
|
.solution-title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
@@ -2782,6 +2795,39 @@
|
|||||||
border-bottom: 1px solid hsl(var(--border));
|
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-approach,
|
||||||
.solution-dependencies,
|
.solution-dependencies,
|
||||||
.solution-concerns {
|
.solution-concerns {
|
||||||
@@ -3976,6 +4022,62 @@
|
|||||||
border-top: 1px solid hsl(var(--border) / 0.5);
|
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 */
|
||||||
.feasibility-badge {
|
.feasibility-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|||||||
@@ -36,10 +36,12 @@ const sessionDataStore = {};
|
|||||||
// Store lite task session data for detail page access
|
// Store lite task session data for detail page access
|
||||||
// Key: session key, Value: lite session data object
|
// Key: session key, Value: lite session data object
|
||||||
const liteTaskDataStore = {};
|
const liteTaskDataStore = {};
|
||||||
|
window.liteTaskDataStore = liteTaskDataStore;
|
||||||
|
|
||||||
// Store task JSON data in a global map instead of inline script tags
|
// Store task JSON data in a global map instead of inline script tags
|
||||||
// Key: unique task ID, Value: raw task JSON data
|
// Key: unique task ID, Value: raw task JSON data
|
||||||
const taskJsonStore = {};
|
const taskJsonStore = {};
|
||||||
|
window.taskJsonStore = taskJsonStore;
|
||||||
|
|
||||||
// ========== Global Notification Queue ==========
|
// ========== Global Notification Queue ==========
|
||||||
// Notification queue visible from any view (persisted to localStorage)
|
// Notification queue visible from any view (persisted to localStorage)
|
||||||
|
|||||||
@@ -418,7 +418,7 @@ function showMultiCliDetailPage(sessionKey) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab Content -->
|
<!-- Tab Content -->
|
||||||
<div class="detail-tab-content" id="multiCliDetailTabContent">
|
<div class="detail-tab-content active" id="multiCliDetailTabContent">
|
||||||
${renderMultiCliTasksTab(session)}
|
${renderMultiCliTasksTab(session)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -653,12 +653,17 @@ function switchMultiCliDetailTab(tabName) {
|
|||||||
break;
|
break;
|
||||||
case 'context':
|
case 'context':
|
||||||
loadAndRenderMultiCliContextTab(session, contentArea);
|
loadAndRenderMultiCliContextTab(session, contentArea);
|
||||||
|
contentArea.classList.add('active');
|
||||||
return; // Early return as this is async
|
return; // Early return as this is async
|
||||||
case 'summary':
|
case 'summary':
|
||||||
loadAndRenderMultiCliSummaryTab(session, contentArea);
|
loadAndRenderMultiCliSummaryTab(session, contentArea);
|
||||||
|
contentArea.classList.add('active');
|
||||||
return; // Early return as this is async
|
return; // Early return as this is async
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add active class to show content
|
||||||
|
contentArea.classList.add('active');
|
||||||
|
|
||||||
// Re-initialize after tab switch
|
// Re-initialize after tab switch
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
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>
|
<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>
|
||||||
<div class="collapsible-content collapsed">
|
<div class="collapsible-content collapsed">
|
||||||
${synthesis.solutions.map((sol, idx) => `
|
${synthesis.solutions.map((sol, idx) => {
|
||||||
<div class="solution-summary-item">
|
const name = getI18nText(sol.name) || sol.title || sol.id || `${t('multiCli.summary.solution')} ${idx + 1}`;
|
||||||
<span class="solution-num">#${idx + 1}</span>
|
const summary = getI18nText(sol.summary) || '';
|
||||||
<span class="solution-name">${escapeHtml(getI18nText(sol.title) || sol.id || `${t('multiCli.summary.solution')} ${idx + 1}`)}</span>
|
const feasibility = sol.feasibility?.score || sol.feasibility || 0;
|
||||||
${sol.feasibility?.score ? `<span class="feasibility-badge">${Math.round(sol.feasibility.score * 100)}%</span>` : ''}
|
const effort = sol.effort || '';
|
||||||
</div>
|
const risk = sol.risk || '';
|
||||||
`).join('')}
|
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>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
@@ -2722,12 +2766,12 @@ function showLiteTaskDetailPage(sessionKey) {
|
|||||||
<div class="detail-header">
|
<div class="detail-header">
|
||||||
<button class="btn-back" onclick="goBackToLiteTasks()">
|
<button class="btn-back" onclick="goBackToLiteTasks()">
|
||||||
<span class="back-icon">←</span>
|
<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>
|
</button>
|
||||||
<div class="detail-title-row">
|
<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">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user