mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
Add unified command indices for CCW and CCW-Help with detailed capabilities, flows, and intent rules
- Introduced command.json for CCW-Help with 88 commands and 16 agents, covering essential workflows and memory management. - Created command.json for CCW with comprehensive capabilities for exploration, planning, execution, bug fixing, testing, reviewing, and documentation. - Defined complex flows for rapid iteration, full exploration, coupled planning, bug fixing, issue lifecycle management, and more. - Implemented intent rules for bug fixing, issue batch processing, exploration, UI design, TDD, review, and documentation. - Established CLI tools and injection rules to enhance command execution based on context and complexity.
This commit is contained in:
@@ -2877,3 +2877,747 @@
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
Multi-CLI Right Toolbar
|
||||
=================================== */
|
||||
|
||||
/* Container with toolbar layout */
|
||||
.multi-cli-detail-with-toolbar {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.multi-cli-main-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
transition: margin-right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.multi-cli-detail-with-toolbar.toolbar-expanded .multi-cli-main-content {
|
||||
margin-right: 320px;
|
||||
}
|
||||
|
||||
/* Toolbar Container */
|
||||
.multi-cli-toolbar {
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 320px;
|
||||
background: hsl(var(--card));
|
||||
border-left: 1px solid hsl(var(--border));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 50;
|
||||
transform: translateX(calc(100% - 48px));
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: -4px 0 16px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
.multi-cli-toolbar.expanded {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.multi-cli-toolbar.collapsed {
|
||||
transform: translateX(calc(100% - 48px));
|
||||
}
|
||||
|
||||
/* Toggle Button */
|
||||
.toolbar-toggle-btn {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
z-index: 51;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 0.15);
|
||||
}
|
||||
|
||||
.toolbar-toggle-btn:hover {
|
||||
background: hsl(var(--primary));
|
||||
border-color: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
transform: translate(-50%, -50%) scale(1.1);
|
||||
}
|
||||
|
||||
.toolbar-toggle-btn i {
|
||||
color: hsl(var(--muted-foreground));
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.toolbar-toggle-btn:hover i {
|
||||
color: hsl(var(--primary-foreground));
|
||||
}
|
||||
|
||||
/* Toolbar Content */
|
||||
.toolbar-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease 0.1s;
|
||||
}
|
||||
|
||||
.multi-cli-toolbar.expanded .toolbar-content {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.multi-cli-toolbar.collapsed .toolbar-content {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Toolbar Header */
|
||||
.toolbar-header {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
}
|
||||
|
||||
.toolbar-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.toolbar-title i {
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.toolbar-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: 0 0.375rem;
|
||||
background: hsl(var(--primary) / 0.15);
|
||||
color: hsl(var(--primary));
|
||||
border-radius: 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Toolbar Actions */
|
||||
.toolbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.toolbar-action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: hsl(var(--muted));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.toolbar-action-btn i {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.toolbar-action-btn:hover {
|
||||
background: hsl(var(--primary) / 0.1);
|
||||
border-color: hsl(var(--primary) / 0.3);
|
||||
}
|
||||
|
||||
.toolbar-action-btn:hover i {
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
/* Toolbar Task List */
|
||||
.toolbar-task-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0.5rem;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.toolbar-task-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.toolbar-task-list::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.toolbar-task-list::-webkit-scrollbar-thumb {
|
||||
background: hsl(var(--border));
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.toolbar-task-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.625rem;
|
||||
padding: 0.625rem 0.75rem;
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
margin-bottom: 0.375rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.toolbar-task-item:hover {
|
||||
background: hsl(var(--primary) / 0.08);
|
||||
border-color: hsl(var(--primary) / 0.3);
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
|
||||
.toolbar-task-item:active {
|
||||
transform: translateX(-2px) scale(0.98);
|
||||
}
|
||||
|
||||
.toolbar-task-num {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toolbar-task-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.toolbar-task-title {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--foreground));
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.toolbar-task-scope {
|
||||
font-size: 0.7rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-family: var(--font-mono);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Toolbar Empty State */
|
||||
.toolbar-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem 1rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
text-align: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.toolbar-empty i {
|
||||
color: hsl(var(--muted-foreground) / 0.5);
|
||||
}
|
||||
|
||||
.toolbar-empty span {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Toolbar Session Info */
|
||||
.toolbar-session-info {
|
||||
padding: 0.75rem 1rem;
|
||||
border-top: 1px solid hsl(var(--border));
|
||||
background: hsl(var(--muted) / 0.2);
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.toolbar-info-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.toolbar-info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.toolbar-info-label {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.025em;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.toolbar-info-value {
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--foreground));
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.toolbar-info-value.toolbar-summary {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
white-space: normal;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Task Highlight Animation */
|
||||
.fix-task-summary-item.toolbar-highlight {
|
||||
animation: toolbarHighlightPulse 2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes toolbarHighlightPulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 hsl(var(--primary));
|
||||
}
|
||||
20% {
|
||||
box-shadow: 0 0 0 4px hsl(var(--primary) / 0.4);
|
||||
border-color: hsl(var(--primary));
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 hsl(var(--primary) / 0);
|
||||
border-color: hsl(var(--border));
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 1024px) {
|
||||
.multi-cli-toolbar {
|
||||
width: 280px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.multi-cli-toolbar {
|
||||
width: 100%;
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
height: 50vh;
|
||||
transform: translateY(calc(100% - 48px));
|
||||
border-left: none;
|
||||
border-top: 1px solid hsl(var(--border));
|
||||
border-radius: 1rem 1rem 0 0;
|
||||
}
|
||||
|
||||
.multi-cli-toolbar.expanded {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.multi-cli-toolbar.collapsed {
|
||||
transform: translateY(calc(100% - 48px));
|
||||
}
|
||||
|
||||
.toolbar-toggle-btn {
|
||||
left: 50%;
|
||||
top: 0;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.toolbar-toggle-btn:hover {
|
||||
transform: translate(-50%, -50%) scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== Discussion Section Styles ========== */
|
||||
.multi-cli-discussion-section {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.discussion-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.75rem;
|
||||
border-bottom: 1px solid hsl(var(--border) / 0.5);
|
||||
}
|
||||
|
||||
.discussion-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.discussion-status {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.discussion-status.converged { background: hsl(var(--success) / 0.15); color: hsl(var(--success)); }
|
||||
.discussion-status.analyzing { background: hsl(var(--warning) / 0.15); color: hsl(var(--warning)); }
|
||||
|
||||
.discussion-round {
|
||||
margin-bottom: 0.75rem;
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: hsl(var(--card));
|
||||
}
|
||||
|
||||
.discussion-round-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.discussion-round-header:hover {
|
||||
background: hsl(var(--muted) / 0.5);
|
||||
}
|
||||
|
||||
.round-title-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.round-badge {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
padding: 0.125rem 0.5rem;
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.round-timestamp {
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.round-indicators {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.convergence-badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.125rem 0.375rem;
|
||||
background: hsl(var(--success) / 0.15);
|
||||
color: hsl(var(--success));
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.recommendation-badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 4px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.recommendation-badge.converged { background: hsl(var(--success) / 0.15); color: hsl(var(--success)); }
|
||||
.recommendation-badge.continue { background: hsl(var(--info) / 0.15); color: hsl(var(--info)); }
|
||||
.recommendation-badge.user_input_needed { background: hsl(var(--warning) / 0.15); color: hsl(var(--warning)); }
|
||||
|
||||
.discussion-round-content {
|
||||
padding: 1rem;
|
||||
border-top: 1px solid hsl(var(--border) / 0.5);
|
||||
}
|
||||
|
||||
.round-section-title {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin: 0.75rem 0 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.round-section-title:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.agent-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.agent-badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
background: hsl(var(--accent));
|
||||
color: hsl(var(--accent-foreground));
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.solution-cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 0.75rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.solution-mini-card {
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
background: hsl(var(--background));
|
||||
}
|
||||
|
||||
.solution-mini-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.solution-mini-title {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.solution-mini-source {
|
||||
font-size: 0.65rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.solution-mini-description {
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Discussion Round Expand/Collapse */
|
||||
.discussion-round.collapsed .discussion-round-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.discussion-round-header .expand-icon {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.discussion-round.collapsed .discussion-round-header .expand-icon {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
/* ========== Association Section Styles ========== */
|
||||
.association-section {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.association-section-title {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin-bottom: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.association-section-title i {
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.association-cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.association-card {
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background: hsl(var(--card));
|
||||
transition: box-shadow 0.2s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.association-card:hover {
|
||||
box-shadow: 0 4px 12px hsl(var(--foreground) / 0.08);
|
||||
border-color: hsl(var(--border));
|
||||
}
|
||||
|
||||
.association-card .card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.association-card .card-number {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 50%;
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.association-card .card-title {
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.association-card .card-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.association-card .metric {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.association-card .metric-label {
|
||||
display: block;
|
||||
font-size: 0.65rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin-bottom: 0.125rem;
|
||||
}
|
||||
|
||||
.association-card .metric-value {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.association-card .metric-value.effort-low { color: hsl(var(--success)); }
|
||||
.association-card .metric-value.effort-medium { color: hsl(var(--warning)); }
|
||||
.association-card .metric-value.effort-high { color: hsl(var(--error)); }
|
||||
|
||||
.association-card .metric-value.risk-low { color: hsl(var(--success)); }
|
||||
.association-card .metric-value.risk-medium { color: hsl(var(--warning)); }
|
||||
.association-card .metric-value.risk-high { color: hsl(var(--error)); }
|
||||
|
||||
.association-card .card-section-title {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin: 0.75rem 0 0.375rem;
|
||||
}
|
||||
|
||||
.dependency-list, .consensus-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0.5rem 0 0 0;
|
||||
}
|
||||
|
||||
.dependency-item, .consensus-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
padding: 0.375rem 0;
|
||||
font-size: 0.8rem;
|
||||
border-bottom: 1px solid hsl(var(--border) / 0.3);
|
||||
}
|
||||
|
||||
.dependency-item:last-child, .consensus-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.dep-type {
|
||||
font-size: 0.65rem;
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 4px;
|
||||
text-transform: uppercase;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dep-type.internal { background: hsl(var(--info) / 0.15); color: hsl(var(--info)); }
|
||||
.dep-type.external { background: hsl(var(--warning) / 0.15); color: hsl(var(--warning)); }
|
||||
|
||||
.dep-name {
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.consensus-icon {
|
||||
color: hsl(var(--success));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.consensus-text {
|
||||
flex: 1;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Association Empty State */
|
||||
.association-empty {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.association-empty i {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.association-empty p {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* Discussion/Association Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.association-cards-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.solution-cards-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.association-card .card-metrics {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1220,6 +1220,8 @@ const i18n = {
|
||||
'multiCli.tab.decision': 'Decision',
|
||||
'multiCli.tab.timeline': 'Timeline',
|
||||
'multiCli.tab.rounds': 'Rounds',
|
||||
'multiCli.tab.discussion': 'Discussion',
|
||||
'multiCli.tab.association': 'Association',
|
||||
'multiCli.scope': 'Scope',
|
||||
'multiCli.scope.included': 'Included',
|
||||
'multiCli.scope.excluded': 'Excluded',
|
||||
@@ -1262,6 +1264,24 @@ const i18n = {
|
||||
'multiCli.empty.decisionText': 'No decision has been made for this discussion yet.',
|
||||
'multiCli.empty.timeline': 'No Timeline Events',
|
||||
'multiCli.empty.timelineText': 'No decision timeline available for this session.',
|
||||
'multiCli.empty.association': 'No Association Data',
|
||||
'multiCli.empty.associationText': 'No context package or related files available for this session.',
|
||||
'multiCli.round': 'Round',
|
||||
'multiCli.solutionSummary': 'Solution Summary',
|
||||
'multiCli.feasibility': 'Feasibility',
|
||||
'multiCli.effort': 'Effort',
|
||||
'multiCli.risk': 'Risk',
|
||||
'multiCli.consensus': 'Consensus',
|
||||
'multiCli.resolvedConflicts': 'Resolved Conflicts',
|
||||
|
||||
// Toolbar
|
||||
'multiCli.toolbar.title': 'Task Navigator',
|
||||
'multiCli.toolbar.tasks': 'Tasks',
|
||||
'multiCli.toolbar.refresh': 'Refresh',
|
||||
'multiCli.toolbar.exportJson': 'Export JSON',
|
||||
'multiCli.toolbar.viewRaw': 'View Raw Data',
|
||||
'multiCli.toolbar.noTasks': 'No tasks available',
|
||||
'multiCli.toolbar.scrollToTask': 'Click to scroll to task',
|
||||
|
||||
// Modals
|
||||
'modal.contentPreview': 'Content Preview',
|
||||
@@ -3440,6 +3460,8 @@ const i18n = {
|
||||
'multiCli.tab.decision': '决策',
|
||||
'multiCli.tab.timeline': '时间线',
|
||||
'multiCli.tab.rounds': '轮次',
|
||||
'multiCli.tab.discussion': '讨论',
|
||||
'multiCli.tab.association': '关联',
|
||||
'multiCli.scope': '范围',
|
||||
'multiCli.scope.included': '包含',
|
||||
'multiCli.scope.excluded': '排除',
|
||||
@@ -3482,6 +3504,24 @@ const i18n = {
|
||||
'multiCli.empty.decisionText': '此讨论尚未做出决策。',
|
||||
'multiCli.empty.timeline': '无时间线事件',
|
||||
'multiCli.empty.timelineText': '此会话无可用的决策时间线。',
|
||||
'multiCli.empty.association': '无关联数据',
|
||||
'multiCli.empty.associationText': '此会话无可用的上下文包或相关文件。',
|
||||
'multiCli.round': '轮次',
|
||||
'multiCli.solutionSummary': '方案摘要',
|
||||
'multiCli.feasibility': '可行性',
|
||||
'multiCli.effort': '工作量',
|
||||
'multiCli.risk': '风险',
|
||||
'multiCli.consensus': '共识',
|
||||
'multiCli.resolvedConflicts': '已解决冲突',
|
||||
|
||||
// Toolbar
|
||||
'multiCli.toolbar.title': '任务导航',
|
||||
'multiCli.toolbar.tasks': '任务列表',
|
||||
'multiCli.toolbar.refresh': '刷新',
|
||||
'multiCli.toolbar.exportJson': '导出JSON',
|
||||
'multiCli.toolbar.viewRaw': '查看原始数据',
|
||||
'multiCli.toolbar.noTasks': '暂无任务',
|
||||
'multiCli.toolbar.scrollToTask': '点击定位到任务',
|
||||
|
||||
// Modals
|
||||
'modal.contentPreview': '内容预览',
|
||||
|
||||
@@ -216,6 +216,11 @@ async function saveCliSettingsEndpoint(data) {
|
||||
const result = await response.json();
|
||||
showRefreshToast(t('apiSettings.settingsSaved'), 'success');
|
||||
|
||||
// Invalidate cache to ensure fresh data on page refresh
|
||||
if (window.cacheManager) {
|
||||
window.cacheManager.invalidate('cli-wrapper-endpoints');
|
||||
}
|
||||
|
||||
// Refresh data and re-render
|
||||
await loadCliSettings(true);
|
||||
renderCliSettingsList();
|
||||
@@ -250,6 +255,11 @@ async function deleteCliSettingsEndpoint(endpointId) {
|
||||
|
||||
showRefreshToast(t('apiSettings.settingsDeleted'), 'success');
|
||||
|
||||
// Invalidate cache to ensure fresh data on page refresh
|
||||
if (window.cacheManager) {
|
||||
window.cacheManager.invalidate('cli-wrapper-endpoints');
|
||||
}
|
||||
|
||||
// Refresh data and re-render
|
||||
await loadCliSettings(true);
|
||||
selectedCliSettingsId = null;
|
||||
@@ -635,6 +645,11 @@ async function saveProvider() {
|
||||
const result = await response.json();
|
||||
showRefreshToast(t('apiSettings.providerSaved'), 'success');
|
||||
|
||||
// Invalidate cache to ensure fresh data on page refresh
|
||||
if (window.cacheManager) {
|
||||
window.cacheManager.invalidate('cli-litellm-endpoints');
|
||||
}
|
||||
|
||||
closeProviderModal();
|
||||
// Force refresh data after saving
|
||||
apiSettingsData = null;
|
||||
@@ -660,6 +675,12 @@ async function deleteProvider(providerId) {
|
||||
if (!response.ok) throw new Error('Failed to delete provider');
|
||||
|
||||
showRefreshToast(t('apiSettings.providerDeleted'), 'success');
|
||||
|
||||
// Invalidate cache to ensure fresh data on page refresh
|
||||
if (window.cacheManager) {
|
||||
window.cacheManager.invalidate('cli-litellm-endpoints');
|
||||
}
|
||||
|
||||
// Force refresh data after deleting
|
||||
apiSettingsData = null;
|
||||
await renderApiSettings();
|
||||
@@ -998,6 +1019,11 @@ async function saveEndpoint() {
|
||||
const result = await response.json();
|
||||
showRefreshToast(t('apiSettings.endpointSaved'), 'success');
|
||||
|
||||
// Invalidate cache to ensure fresh data on page refresh
|
||||
if (window.cacheManager) {
|
||||
window.cacheManager.invalidate('cli-litellm-endpoints');
|
||||
}
|
||||
|
||||
closeEndpointModal();
|
||||
// Force refresh data after saving
|
||||
apiSettingsData = null;
|
||||
@@ -1023,6 +1049,12 @@ async function deleteEndpoint(endpointId) {
|
||||
if (!response.ok) throw new Error('Failed to delete endpoint');
|
||||
|
||||
showRefreshToast(t('apiSettings.endpointDeleted'), 'success');
|
||||
|
||||
// Invalidate cache to ensure fresh data on page refresh
|
||||
if (window.cacheManager) {
|
||||
window.cacheManager.invalidate('cli-litellm-endpoints');
|
||||
}
|
||||
|
||||
// Force refresh data after deleting
|
||||
apiSettingsData = null;
|
||||
await renderApiSettings();
|
||||
|
||||
@@ -395,6 +395,10 @@ async function updateCliToolConfig(tool, updates) {
|
||||
}
|
||||
if (data.success && cliToolConfig && cliToolConfig.tools) {
|
||||
cliToolConfig.tools[tool] = data.config;
|
||||
// Invalidate cache to ensure fresh data on page refresh
|
||||
if (window.cacheManager) {
|
||||
window.cacheManager.invalidate('cli-config');
|
||||
}
|
||||
}
|
||||
return data;
|
||||
} catch (err) {
|
||||
|
||||
@@ -347,6 +347,9 @@ function extractTimelineFromSynthesis(synthesis) {
|
||||
return timeline;
|
||||
}
|
||||
|
||||
// Track multi-cli toolbar state
|
||||
let isMultiCliToolbarExpanded = false;
|
||||
|
||||
/**
|
||||
* Show multi-cli detail page with tabs
|
||||
*/
|
||||
@@ -368,70 +371,71 @@ function showMultiCliDetailPage(sessionKey) {
|
||||
const status = latestSynthesis.status || session.status || 'analyzing';
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="session-detail-page multi-cli-detail-page">
|
||||
<!-- Header -->
|
||||
<div class="detail-header">
|
||||
<button class="btn-back" onclick="goBackToLiteTasks()">
|
||||
<span class="back-icon">←</span>
|
||||
<span>${t('multiCli.backToList') || 'Back to Multi-CLI Plan'}</span>
|
||||
</button>
|
||||
<div class="detail-title-row">
|
||||
<h2 class="detail-session-id"><i data-lucide="messages-square" class="w-5 h-5 inline mr-2"></i> ${escapeHtml(session.id)}</h2>
|
||||
<div class="detail-badges">
|
||||
<span class="session-type-badge multi-cli-plan">multi-cli-plan</span>
|
||||
<span class="session-status-badge ${status}">${escapeHtml(status)}</span>
|
||||
<div class="session-detail-page multi-cli-detail-page multi-cli-detail-with-toolbar">
|
||||
<!-- Main Content Area -->
|
||||
<div class="multi-cli-main-content">
|
||||
<!-- Header -->
|
||||
<div class="detail-header">
|
||||
<button class="btn-back" onclick="goBackToLiteTasks()">
|
||||
<span class="back-icon">←</span>
|
||||
<span>${t('multiCli.backToList') || 'Back to Multi-CLI Plan'}</span>
|
||||
</button>
|
||||
<div class="detail-title-row">
|
||||
<h2 class="detail-session-id"><i data-lucide="messages-square" class="w-5 h-5 inline mr-2"></i> ${escapeHtml(session.id)}</h2>
|
||||
<div class="detail-badges">
|
||||
<span class="session-type-badge multi-cli-plan">multi-cli-plan</span>
|
||||
<span class="session-status-badge ${status}">${escapeHtml(status)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Session Info Bar -->
|
||||
<div class="detail-info-bar">
|
||||
<div class="info-item">
|
||||
<span class="info-label">${t('detail.created') || 'Created'}</span>
|
||||
<span class="info-value">${formatDate(metadata.timestamp || session.createdAt)}</span>
|
||||
<!-- Session Info Bar -->
|
||||
<div class="detail-info-bar">
|
||||
<div class="info-item">
|
||||
<span class="info-label">${t('detail.created') || 'Created'}</span>
|
||||
<span class="info-value">${formatDate(metadata.timestamp || session.createdAt)}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">${t('multiCli.roundCount') || 'Rounds'}</span>
|
||||
<span class="info-value">${roundCount}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">${t('multiCli.topic') || 'Topic'}</span>
|
||||
<span class="info-value">${escapeHtml(topicTitle)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">${t('multiCli.roundCount') || 'Rounds'}</span>
|
||||
<span class="info-value">${roundCount}</span>
|
||||
|
||||
<!-- Tab Navigation -->
|
||||
<div class="detail-tabs">
|
||||
<button class="detail-tab active" data-tab="planning" onclick="switchMultiCliDetailTab('planning')">
|
||||
<span class="tab-icon"><i data-lucide="list-checks" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">${t('multiCli.tab.planning') || 'Planning'}</span>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="discussion" onclick="switchMultiCliDetailTab('discussion')">
|
||||
<span class="tab-icon"><i data-lucide="messages-square" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">${t('multiCli.tab.discussion') || 'Discussion'}</span>
|
||||
<span class="tab-count">${roundCount}</span>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="association" onclick="switchMultiCliDetailTab('association')">
|
||||
<span class="tab-icon"><i data-lucide="link-2" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">${t('multiCli.tab.association') || 'Association'}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">${t('multiCli.topic') || 'Topic'}</span>
|
||||
<span class="info-value">${escapeHtml(topicTitle)}</span>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="detail-tab-content" id="multiCliDetailTabContent">
|
||||
${renderMultiCliPlanningTab(session)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Navigation -->
|
||||
<div class="detail-tabs">
|
||||
<button class="detail-tab active" data-tab="topic" onclick="switchMultiCliDetailTab('topic')">
|
||||
<span class="tab-icon"><i data-lucide="message-circle" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">${t('multiCli.tab.topic') || 'Discussion Topic'}</span>
|
||||
<!-- Right Toolbar -->
|
||||
<div class="multi-cli-toolbar ${isMultiCliToolbarExpanded ? 'expanded' : 'collapsed'}" id="multiCliToolbar">
|
||||
<button class="toolbar-toggle-btn" onclick="toggleMultiCliToolbar()" title="${t('multiCli.toolbar.toggle') || 'Toggle Task Panel'}">
|
||||
<i data-lucide="${isMultiCliToolbarExpanded ? 'panel-right-close' : 'panel-right-open'}" class="w-5 h-5"></i>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="files" onclick="switchMultiCliDetailTab('files')">
|
||||
<span class="tab-icon"><i data-lucide="folder-tree" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">${t('multiCli.tab.files') || 'Related Files'}</span>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="planning" onclick="switchMultiCliDetailTab('planning')">
|
||||
<span class="tab-icon"><i data-lucide="list-checks" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">${t('multiCli.tab.planning') || 'Planning'}</span>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="decision" onclick="switchMultiCliDetailTab('decision')">
|
||||
<span class="tab-icon"><i data-lucide="check-circle" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">${t('multiCli.tab.decision') || 'Decision'}</span>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="timeline" onclick="switchMultiCliDetailTab('timeline')">
|
||||
<span class="tab-icon"><i data-lucide="git-commit" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">${t('multiCli.tab.timeline') || 'Timeline'}</span>
|
||||
</button>
|
||||
<button class="detail-tab" data-tab="rounds" onclick="switchMultiCliDetailTab('rounds')">
|
||||
<span class="tab-icon"><i data-lucide="layers" class="w-4 h-4"></i></span>
|
||||
<span class="tab-text">${t('multiCli.tab.rounds') || 'Rounds'}</span>
|
||||
<span class="tab-count">${roundCount}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="detail-tab-content" id="multiCliDetailTabContent">
|
||||
${renderMultiCliTopicTab(session)}
|
||||
<div class="toolbar-content">
|
||||
${renderMultiCliToolbar(session)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -443,6 +447,216 @@ function showMultiCliDetailPage(sessionKey) {
|
||||
}, 50);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle multi-cli toolbar expanded/collapsed state
|
||||
*/
|
||||
function toggleMultiCliToolbar() {
|
||||
isMultiCliToolbarExpanded = !isMultiCliToolbarExpanded;
|
||||
const toolbar = document.getElementById('multiCliToolbar');
|
||||
const toggleBtn = toolbar?.querySelector('.toolbar-toggle-btn i');
|
||||
|
||||
if (toolbar) {
|
||||
toolbar.classList.toggle('expanded', isMultiCliToolbarExpanded);
|
||||
toolbar.classList.toggle('collapsed', !isMultiCliToolbarExpanded);
|
||||
|
||||
// Update icon
|
||||
if (toggleBtn) {
|
||||
toggleBtn.setAttribute('data-lucide', isMultiCliToolbarExpanded ? 'panel-right-close' : 'panel-right-open');
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the multi-cli toolbar content
|
||||
*/
|
||||
function renderMultiCliToolbar(session) {
|
||||
const plan = session.plan;
|
||||
const tasks = plan?.tasks || [];
|
||||
const taskCount = tasks.length;
|
||||
|
||||
let toolbarHtml = `
|
||||
<div class="toolbar-header">
|
||||
<h4 class="toolbar-title">
|
||||
<i data-lucide="list-checks" class="w-4 h-4"></i>
|
||||
<span>${t('multiCli.toolbar.tasks') || 'Tasks'}</span>
|
||||
<span class="toolbar-count">${taskCount}</span>
|
||||
</h4>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Quick Actions
|
||||
toolbarHtml += `
|
||||
<div class="toolbar-actions">
|
||||
<button class="toolbar-action-btn" onclick="refreshMultiCliToolbar()" title="${t('multiCli.toolbar.refresh') || 'Refresh'}">
|
||||
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button class="toolbar-action-btn" onclick="exportMultiCliPlanJson()" title="${t('multiCli.toolbar.export') || 'Export JSON'}">
|
||||
<i data-lucide="download" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button class="toolbar-action-btn" onclick="viewMultiCliRawJson()" title="${t('multiCli.toolbar.viewRaw') || 'View Raw Data'}">
|
||||
<i data-lucide="code" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Task List
|
||||
if (tasks.length > 0) {
|
||||
toolbarHtml += `
|
||||
<div class="toolbar-task-list">
|
||||
${tasks.map((task, idx) => {
|
||||
const taskTitle = task.title || task.summary || `Task ${idx + 1}`;
|
||||
const taskScope = task.scope || '';
|
||||
const taskId = `task-${idx}`;
|
||||
|
||||
return `
|
||||
<div class="toolbar-task-item" onclick="scrollToMultiCliTask(${idx})" data-task-idx="${idx}">
|
||||
<span class="toolbar-task-num">#${idx + 1}</span>
|
||||
<div class="toolbar-task-info">
|
||||
<span class="toolbar-task-title" title="${escapeHtml(taskTitle)}">${escapeHtml(taskTitle)}</span>
|
||||
${taskScope ? `<span class="toolbar-task-scope">${escapeHtml(taskScope)}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
toolbarHtml += `
|
||||
<div class="toolbar-empty">
|
||||
<i data-lucide="inbox" class="w-8 h-8"></i>
|
||||
<span>${t('multiCli.toolbar.noTasks') || 'No tasks available'}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Session Info
|
||||
toolbarHtml += `
|
||||
<div class="toolbar-session-info">
|
||||
<div class="toolbar-info-item">
|
||||
<span class="toolbar-info-label">${t('multiCli.toolbar.sessionId') || 'Session'}</span>
|
||||
<span class="toolbar-info-value" title="${escapeHtml(session.id)}">${escapeHtml(session.id)}</span>
|
||||
</div>
|
||||
${plan?.summary ? `
|
||||
<div class="toolbar-info-item">
|
||||
<span class="toolbar-info-label">${t('multiCli.toolbar.summary') || 'Summary'}</span>
|
||||
<span class="toolbar-info-value toolbar-summary" title="${escapeHtml(plan.summary)}">${escapeHtml(plan.summary)}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
return toolbarHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to a specific task in the planning tab
|
||||
*/
|
||||
function scrollToMultiCliTask(taskIdx) {
|
||||
// Switch to planning tab if not active
|
||||
const planningTab = document.querySelector('.detail-tab[data-tab="planning"]');
|
||||
if (planningTab && !planningTab.classList.contains('active')) {
|
||||
switchMultiCliDetailTab('planning');
|
||||
// Wait for tab content to render
|
||||
setTimeout(() => scrollToTaskElement(taskIdx), 100);
|
||||
} else {
|
||||
scrollToTaskElement(taskIdx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to task element in the DOM
|
||||
*/
|
||||
function scrollToTaskElement(taskIdx) {
|
||||
const taskItems = document.querySelectorAll('.fix-task-summary-item');
|
||||
if (taskItems[taskIdx]) {
|
||||
taskItems[taskIdx].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
// Highlight the task briefly
|
||||
taskItems[taskIdx].classList.add('toolbar-highlight');
|
||||
setTimeout(() => {
|
||||
taskItems[taskIdx].classList.remove('toolbar-highlight');
|
||||
}, 2000);
|
||||
// Expand the collapsible if collapsed
|
||||
const header = taskItems[taskIdx].querySelector('.collapsible-header');
|
||||
const content = taskItems[taskIdx].querySelector('.collapsible-content');
|
||||
if (header && content && content.classList.contains('collapsed')) {
|
||||
header.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the toolbar content
|
||||
*/
|
||||
function refreshMultiCliToolbar() {
|
||||
const session = liteTaskDataStore[currentSessionDetailKey];
|
||||
if (!session) return;
|
||||
|
||||
const toolbarContent = document.querySelector('.toolbar-content');
|
||||
if (toolbarContent) {
|
||||
toolbarContent.innerHTML = renderMultiCliToolbar(session);
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export plan.json content
|
||||
*/
|
||||
function exportMultiCliPlanJson() {
|
||||
const session = liteTaskDataStore[currentSessionDetailKey];
|
||||
if (!session || !session.plan) {
|
||||
if (typeof showRefreshToast === 'function') {
|
||||
showRefreshToast(t('multiCli.toolbar.noPlan') || 'No plan data available', 'warning');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const jsonStr = JSON.stringify(session.plan, null, 2);
|
||||
const blob = new Blob([jsonStr], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `plan-${session.id}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
if (typeof showRefreshToast === 'function') {
|
||||
showRefreshToast(t('multiCli.toolbar.exported') || 'Plan exported successfully', 'success');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* View raw session JSON in modal
|
||||
*/
|
||||
function viewMultiCliRawJson() {
|
||||
const session = liteTaskDataStore[currentSessionDetailKey];
|
||||
if (!session) return;
|
||||
|
||||
// Reuse existing JSON modal pattern
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'json-modal-overlay active';
|
||||
overlay.innerHTML = `
|
||||
<div class="json-modal">
|
||||
<div class="json-modal-header">
|
||||
<div class="json-modal-title">
|
||||
<span class="session-id-badge">${escapeHtml(session.id)}</span>
|
||||
<span>${t('multiCli.toolbar.rawData') || 'Raw Session Data'}</span>
|
||||
</div>
|
||||
<button class="json-modal-close" onclick="closeJsonModal(this)">×</button>
|
||||
</div>
|
||||
<div class="json-modal-body">
|
||||
<pre class="json-modal-content">${escapeHtml(JSON.stringify(session, null, 2))}</pre>
|
||||
</div>
|
||||
<div class="json-modal-footer">
|
||||
<button class="btn-copy-json" onclick="copyJsonToClipboard(this)">${t('action.copy') || 'Copy to Clipboard'}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch between multi-cli detail tabs
|
||||
*/
|
||||
@@ -458,23 +672,14 @@ function switchMultiCliDetailTab(tabName) {
|
||||
const contentArea = document.getElementById('multiCliDetailTabContent');
|
||||
|
||||
switch (tabName) {
|
||||
case 'topic':
|
||||
contentArea.innerHTML = renderMultiCliTopicTab(session);
|
||||
break;
|
||||
case 'files':
|
||||
contentArea.innerHTML = renderMultiCliFilesTab(session);
|
||||
break;
|
||||
case 'planning':
|
||||
contentArea.innerHTML = renderMultiCliPlanningTab(session);
|
||||
break;
|
||||
case 'decision':
|
||||
contentArea.innerHTML = renderMultiCliDecisionTab(session);
|
||||
case 'discussion':
|
||||
contentArea.innerHTML = renderMultiCliDiscussionSection(session);
|
||||
break;
|
||||
case 'timeline':
|
||||
contentArea.innerHTML = renderMultiCliTimelineTab(session);
|
||||
break;
|
||||
case 'rounds':
|
||||
contentArea.innerHTML = renderMultiCliRoundsTab(session);
|
||||
case 'association':
|
||||
contentArea.innerHTML = renderMultiCliAssociationSection(session);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -695,75 +900,147 @@ function renderFileTreeNodes(nodes, depth = 0) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Planning tab
|
||||
* Shows: functional, nonFunctional requirements, acceptanceCriteria
|
||||
* Render Planning tab - displays session.plan (plan.json content)
|
||||
* Reuses renderLitePlanTab style with Summary, Approach, Focus Paths, Metadata, and Tasks
|
||||
*/
|
||||
function renderMultiCliPlanningTab(session) {
|
||||
// Use helper to extract planning from synthesis data structure
|
||||
const planning = extractPlanningFromSynthesis(session.latestSynthesis);
|
||||
const plan = session.plan;
|
||||
|
||||
if (!planning || (!planning.functional?.length && !planning.acceptanceCriteria?.length)) {
|
||||
if (!plan) {
|
||||
return `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon"><i data-lucide="list-checks" class="w-12 h-12"></i></div>
|
||||
<div class="empty-icon"><i data-lucide="ruler" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">${t('multiCli.empty.planning') || 'No Planning Data'}</div>
|
||||
<div class="empty-text">${t('multiCli.empty.planningText') || 'No planning requirements available for this session.'}</div>
|
||||
<div class="empty-text">${t('multiCli.empty.planningText') || 'No plan.json found for this session.'}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const functional = planning.functional || [];
|
||||
const nonFunctional = planning.nonFunctional || [];
|
||||
const acceptanceCriteria = planning.acceptanceCriteria || [];
|
||||
return `
|
||||
<div class="plan-tab-content">
|
||||
<!-- Summary -->
|
||||
${plan.summary ? `
|
||||
<div class="plan-section">
|
||||
<h4 class="plan-section-title"><i data-lucide="clipboard-list" class="w-4 h-4 inline mr-1"></i> Summary</h4>
|
||||
<p class="plan-summary-text">${escapeHtml(plan.summary)}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
let sections = [];
|
||||
<!-- Root Cause (fix-plan specific) -->
|
||||
${plan.root_cause ? `
|
||||
<div class="plan-section">
|
||||
<h4 class="plan-section-title"><i data-lucide="search" class="w-4 h-4 inline mr-1"></i> Root Cause</h4>
|
||||
<p class="plan-root-cause-text">${escapeHtml(plan.root_cause)}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
// Functional Requirements
|
||||
if (functional.length) {
|
||||
sections.push(`
|
||||
<div class="multi-cli-section requirements-section">
|
||||
<h4 class="section-title"><i data-lucide="check-square" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.functional') || 'Functional Requirements'} (${functional.length})</h4>
|
||||
<div class="requirements-list">
|
||||
${functional.map(req => renderRequirementItem(req)).join('')}
|
||||
<!-- Strategy (fix-plan specific) -->
|
||||
${plan.strategy ? `
|
||||
<div class="plan-section">
|
||||
<h4 class="plan-section-title"><i data-lucide="route" class="w-4 h-4 inline mr-1"></i> Fix Strategy</h4>
|
||||
<p class="plan-strategy-text">${escapeHtml(plan.strategy)}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Approach -->
|
||||
${plan.approach ? `
|
||||
<div class="plan-section">
|
||||
<h4 class="plan-section-title"><i data-lucide="target" class="w-4 h-4 inline mr-1"></i> Approach</h4>
|
||||
<p class="plan-approach-text">${escapeHtml(plan.approach)}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- User Requirements -->
|
||||
${plan.user_requirements ? `
|
||||
<div class="plan-section">
|
||||
<h4 class="plan-section-title"><i data-lucide="user" class="w-4 h-4 inline mr-1"></i> User Requirements</h4>
|
||||
<p class="plan-requirements-text">${escapeHtml(plan.user_requirements)}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Focus Paths -->
|
||||
${plan.focus_paths?.length ? `
|
||||
<div class="plan-section">
|
||||
<h4 class="plan-section-title"><i data-lucide="folder" class="w-4 h-4 inline mr-1"></i> Focus Paths</h4>
|
||||
<div class="path-tags">
|
||||
${plan.focus_paths.map(p => `<span class="path-tag">${escapeHtml(p)}</span>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Metadata -->
|
||||
<div class="plan-section">
|
||||
<h4 class="plan-section-title"><i data-lucide="info" class="w-4 h-4 inline mr-1"></i> Metadata</h4>
|
||||
<div class="plan-meta-grid">
|
||||
${plan.severity ? `<div class="meta-item"><span class="meta-label">Severity:</span> <span class="severity-badge ${escapeHtml(plan.severity)}">${escapeHtml(plan.severity)}</span></div>` : ''}
|
||||
${plan.risk_level ? `<div class="meta-item"><span class="meta-label">Risk Level:</span> <span class="risk-badge ${escapeHtml(plan.risk_level)}">${escapeHtml(plan.risk_level)}</span></div>` : ''}
|
||||
${plan.estimated_time ? `<div class="meta-item"><span class="meta-label">Estimated Time:</span> ${escapeHtml(plan.estimated_time)}</div>` : ''}
|
||||
${plan.complexity ? `<div class="meta-item"><span class="meta-label">Complexity:</span> ${escapeHtml(plan.complexity)}</div>` : ''}
|
||||
${plan.recommended_execution ? `<div class="meta-item"><span class="meta-label">Execution:</span> ${escapeHtml(plan.recommended_execution)}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
// Non-Functional Requirements
|
||||
if (nonFunctional.length) {
|
||||
sections.push(`
|
||||
<div class="multi-cli-section requirements-section">
|
||||
<h4 class="section-title"><i data-lucide="settings" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.nonFunctional') || 'Non-Functional Requirements'} (${nonFunctional.length})</h4>
|
||||
<div class="requirements-list">
|
||||
${nonFunctional.map(req => renderRequirementItem(req)).join('')}
|
||||
<!-- Tasks -->
|
||||
${plan.tasks?.length ? `
|
||||
<div class="plan-section">
|
||||
<h4 class="plan-section-title"><i data-lucide="list-checks" class="w-4 h-4 inline mr-1"></i> Tasks (${plan.tasks.length})</h4>
|
||||
<div class="fix-tasks-summary">
|
||||
${plan.tasks.map((task, idx) => `
|
||||
<div class="fix-task-summary-item collapsible-section">
|
||||
<div class="collapsible-header">
|
||||
<span class="collapse-icon">▶</span>
|
||||
<span class="task-num">#${idx + 1}</span>
|
||||
<span class="task-title-brief">${escapeHtml(task.title || task.summary || 'Untitled')}</span>
|
||||
${task.scope ? `<span class="task-scope-badge">${escapeHtml(task.scope)}</span>` : ''}
|
||||
</div>
|
||||
<div class="collapsible-content collapsed">
|
||||
${task.modification_points?.length ? `
|
||||
<div class="task-detail-section">
|
||||
<strong>Modification Points:</strong>
|
||||
<ul class="mod-points-list">
|
||||
${task.modification_points.map(mp => `
|
||||
<li>
|
||||
<code>${escapeHtml(mp.file || '')}</code>
|
||||
${mp.function_name ? `<span class="func-name">-> ${escapeHtml(mp.function_name)}</span>` : ''}
|
||||
${mp.change_type ? `<span class="change-type">(${escapeHtml(mp.change_type)})</span>` : ''}
|
||||
</li>
|
||||
`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
${task.implementation?.length ? `
|
||||
<div class="task-detail-section">
|
||||
<strong>Implementation Steps:</strong>
|
||||
<ol class="impl-steps-list">
|
||||
${task.implementation.map(step => `<li>${escapeHtml(step)}</li>`).join('')}
|
||||
</ol>
|
||||
</div>
|
||||
` : ''}
|
||||
${task.verification?.length ? `
|
||||
<div class="task-detail-section">
|
||||
<strong>Verification:</strong>
|
||||
<ul class="verify-list">
|
||||
${task.verification.map(v => `<li>${escapeHtml(v)}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Raw JSON -->
|
||||
<div class="plan-section collapsible-section">
|
||||
<div class="collapsible-header">
|
||||
<span class="collapse-icon">▶</span>
|
||||
<span class="section-label">{ } Raw JSON</span>
|
||||
</div>
|
||||
<div class="collapsible-content collapsed">
|
||||
<pre class="json-content">${escapeHtml(JSON.stringify(plan, null, 2))}</pre>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
// Acceptance Criteria
|
||||
if (acceptanceCriteria.length) {
|
||||
sections.push(`
|
||||
<div class="multi-cli-section acceptance-section">
|
||||
<h4 class="section-title"><i data-lucide="clipboard-check" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.acceptanceCriteria') || 'Acceptance Criteria'} (${acceptanceCriteria.length})</h4>
|
||||
<div class="acceptance-list">
|
||||
${acceptanceCriteria.map(ac => `
|
||||
<div class="acceptance-item ${ac.isMet ? 'met' : 'unmet'}">
|
||||
<span class="acceptance-check"><i data-lucide="${ac.isMet ? 'check' : 'circle'}" class="w-3 h-3"></i></span>
|
||||
<span class="acceptance-id">${escapeHtml(ac.id || '')}</span>
|
||||
<span class="acceptance-desc">${escapeHtml(getI18nText(ac.description))}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
return sections.length ? `<div class="multi-cli-planning-tab">${sections.join('')}</div>` : `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon"><i data-lucide="list-checks" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">${t('multiCli.empty.planning') || 'No Planning Data'}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -1298,6 +1575,312 @@ async function loadMultiCliRound(sessionKey, roundNum) {
|
||||
contentArea.innerHTML = `<div class="round-empty">${t('multiCli.noRoundData') || 'No data for this round.'}</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Discussion Section (combines Topic, Rounds, Decision)
|
||||
* Uses accordion layout to display discussion rounds
|
||||
*/
|
||||
function renderMultiCliDiscussionSection(session) {
|
||||
const rounds = session.rounds || [];
|
||||
const metadata = session.metadata || {};
|
||||
const totalRounds = metadata.roundId || rounds.length || 1;
|
||||
const topic = session.discussionTopic || session.latestSynthesis?.discussionTopic || {};
|
||||
|
||||
// If no rounds, show topic summary and current synthesis
|
||||
if (!rounds.length) {
|
||||
const title = getI18nText(topic.title) || 'Discussion Topic';
|
||||
const description = getI18nText(topic.description) || '';
|
||||
const status = topic.status || session.status || 'analyzing';
|
||||
|
||||
return `
|
||||
<div class="multi-cli-discussion-section">
|
||||
<div class="discussion-header">
|
||||
<h3 class="discussion-title">${escapeHtml(title)}</h3>
|
||||
<span class="discussion-status ${status}">${escapeHtml(status)}</span>
|
||||
</div>
|
||||
${description ? `<p class="discussion-description">${escapeHtml(description)}</p>` : ''}
|
||||
<div class="discussion-empty-state">
|
||||
<i data-lucide="message-circle" class="w-8 h-8"></i>
|
||||
<p>${t('multiCli.singleRoundInfo') || 'This is a single-round discussion. View Planning tab for execution details.'}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Render accordion for multiple rounds
|
||||
const accordionItems = rounds.map((round, idx) => {
|
||||
const roundNum = idx + 1;
|
||||
const isLatest = roundNum === totalRounds;
|
||||
const roundMeta = round.metadata || {};
|
||||
const convergence = round._internal?.convergence || round.convergence || {};
|
||||
const recommendation = convergence.recommendation || 'continue';
|
||||
const score = convergence.score !== undefined ? Math.round(convergence.score * 100) : null;
|
||||
const solutions = round.solutions || [];
|
||||
const agents = roundMeta.contributingAgents || [];
|
||||
|
||||
return `
|
||||
<div class="discussion-round collapsible-section ${isLatest ? 'expanded' : ''}">
|
||||
<div class="collapsible-header discussion-round-header">
|
||||
<span class="collapse-icon">${isLatest ? '▼' : '►'}</span>
|
||||
<div class="round-title-group">
|
||||
<span class="round-badge">${t('multiCli.round') || 'Round'} ${roundNum}</span>
|
||||
<span class="round-timestamp">${formatDate(roundMeta.timestamp)}</span>
|
||||
</div>
|
||||
<div class="round-indicators">
|
||||
${score !== null ? `<span class="convergence-badge" title="${t('multiCli.convergence') || 'Convergence'}">${score}%</span>` : ''}
|
||||
<span class="recommendation-badge ${recommendation}">${escapeHtml(recommendation)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapsible-content ${isLatest ? '' : 'collapsed'}">
|
||||
<!-- Discussion Topic for this round -->
|
||||
${round.discussionTopic ? `
|
||||
<div class="round-topic">
|
||||
<h4 class="round-section-title"><i data-lucide="message-circle" class="w-4 h-4 inline"></i> ${t('multiCli.topic') || 'Topic'}</h4>
|
||||
<p>${escapeHtml(getI18nText(round.discussionTopic.title || round.discussionTopic))}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Contributing Agents -->
|
||||
${agents.length ? `
|
||||
<div class="round-agents">
|
||||
<h4 class="round-section-title"><i data-lucide="users" class="w-4 h-4 inline"></i> ${t('multiCli.contributors') || 'Contributors'}</h4>
|
||||
<div class="agent-badges">
|
||||
${agents.map(agent => `<span class="agent-badge">${escapeHtml(agent.name || agent.id || agent)}</span>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Solutions -->
|
||||
${solutions.length ? `
|
||||
<div class="round-solutions-summary">
|
||||
<h4 class="round-section-title"><i data-lucide="lightbulb" class="w-4 h-4 inline"></i> ${t('multiCli.solutions') || 'Solutions'} (${solutions.length})</h4>
|
||||
<div class="solution-cards-grid">
|
||||
${solutions.map((sol, sidx) => `
|
||||
<div class="solution-mini-card">
|
||||
<div class="solution-mini-header">
|
||||
<span class="solution-number">${sidx + 1}</span>
|
||||
<span class="solution-name">${escapeHtml(sol.name || 'Solution ' + (sidx + 1))}</span>
|
||||
</div>
|
||||
<div class="solution-mini-scores">
|
||||
<span class="score-pill feasibility" title="Feasibility">${Math.round((sol.feasibility || 0) * 100)}%</span>
|
||||
<span class="score-pill effort-${sol.effort || 'medium'}">${escapeHtml(sol.effort || 'M')}</span>
|
||||
<span class="score-pill risk-${sol.risk || 'medium'}">${escapeHtml(sol.risk || 'M')}</span>
|
||||
</div>
|
||||
${sol.summary ? `<p class="solution-mini-summary">${escapeHtml(getI18nText(sol.summary).substring(0, 100))}${getI18nText(sol.summary).length > 100 ? '...' : ''}</p>` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Decision/Recommendation -->
|
||||
${convergence.reasoning || round.decision ? `
|
||||
<div class="round-decision">
|
||||
<h4 class="round-section-title"><i data-lucide="check-circle" class="w-4 h-4 inline"></i> ${t('multiCli.decision') || 'Decision'}</h4>
|
||||
<p class="decision-text">${escapeHtml(convergence.reasoning || round.decision || '')}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
return `
|
||||
<div class="multi-cli-discussion-section">
|
||||
<div class="discussion-header">
|
||||
<h3 class="discussion-title">${escapeHtml(getI18nText(topic.title) || 'Discussion')}</h3>
|
||||
<span class="rounds-count">${totalRounds} ${t('multiCli.tab.rounds') || 'Rounds'}</span>
|
||||
</div>
|
||||
<div class="discussion-accordion">
|
||||
${accordionItems}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Association Section (context-package key fields)
|
||||
* Shows solution summary, dependencies, consensus
|
||||
*/
|
||||
function renderMultiCliAssociationSection(session) {
|
||||
const contextPkg = session.contextPackage || session.context_package || session.latestSynthesis?.context_package || {};
|
||||
const solutions = contextPkg.solutions || session.latestSynthesis?.solutions || [];
|
||||
const dependencies = contextPkg.dependencies || {};
|
||||
const consensus = contextPkg.consensus || session.latestSynthesis?._internal?.cross_verification || {};
|
||||
const relatedFiles = extractFilesFromSynthesis(session.latestSynthesis);
|
||||
|
||||
// Check if we have any content to display
|
||||
const hasSolutions = solutions.length > 0;
|
||||
const hasDependencies = dependencies.internal?.length || dependencies.external?.length;
|
||||
const hasConsensus = consensus.agreements?.length || consensus.resolved_conflicts?.length || consensus.disagreements?.length;
|
||||
const hasFiles = relatedFiles?.fileTree?.length || relatedFiles?.impactSummary?.length;
|
||||
|
||||
if (!hasSolutions && !hasDependencies && !hasConsensus && !hasFiles) {
|
||||
return `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon"><i data-lucide="link-2" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">${t('multiCli.empty.association') || 'No Association Data'}</div>
|
||||
<div class="empty-text">${t('multiCli.empty.associationText') || 'No context package or related files available for this session.'}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
let sections = [];
|
||||
|
||||
// Solution Summary Cards
|
||||
if (hasSolutions) {
|
||||
sections.push(`
|
||||
<div class="association-section solutions-section">
|
||||
<h4 class="association-section-title">
|
||||
<i data-lucide="lightbulb" class="w-4 h-4 inline"></i>
|
||||
${t('multiCli.solutionSummary') || 'Solution Summary'}
|
||||
</h4>
|
||||
<div class="association-cards-grid">
|
||||
${solutions.map((sol, idx) => `
|
||||
<div class="association-card solution-card">
|
||||
<div class="card-header">
|
||||
<span class="card-number">${idx + 1}</span>
|
||||
<span class="card-title">${escapeHtml(sol.name || 'Solution ' + (idx + 1))}</span>
|
||||
</div>
|
||||
<div class="card-metrics">
|
||||
<div class="metric">
|
||||
<span class="metric-label">${t('multiCli.feasibility') || 'Feasibility'}</span>
|
||||
<span class="metric-value">${Math.round((sol.feasibility || 0) * 100)}%</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">${t('multiCli.effort') || 'Effort'}</span>
|
||||
<span class="metric-value effort-${sol.effort || 'medium'}">${escapeHtml(sol.effort || 'medium')}</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">${t('multiCli.risk') || 'Risk'}</span>
|
||||
<span class="metric-value risk-${sol.risk || 'medium'}">${escapeHtml(sol.risk || 'medium')}</span>
|
||||
</div>
|
||||
</div>
|
||||
${sol.summary ? `<p class="card-description">${escapeHtml(getI18nText(sol.summary))}</p>` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
// Dependencies Card
|
||||
if (hasDependencies) {
|
||||
sections.push(`
|
||||
<div class="association-section dependencies-section">
|
||||
<h4 class="association-section-title">
|
||||
<i data-lucide="git-branch" class="w-4 h-4 inline"></i>
|
||||
${t('multiCli.dependencies') || 'Dependencies'}
|
||||
</h4>
|
||||
<div class="dependencies-grid">
|
||||
${dependencies.internal?.length ? `
|
||||
<div class="association-card dependency-card">
|
||||
<div class="card-header">
|
||||
<i data-lucide="folder" class="w-4 h-4"></i>
|
||||
<span class="card-title">${t('multiCli.internalDeps') || 'Internal'}</span>
|
||||
<span class="card-count">${dependencies.internal.length}</span>
|
||||
</div>
|
||||
<ul class="dependency-list">
|
||||
${dependencies.internal.map(dep => `<li>${escapeHtml(getI18nText(dep))}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
${dependencies.external?.length ? `
|
||||
<div class="association-card dependency-card">
|
||||
<div class="card-header">
|
||||
<i data-lucide="package" class="w-4 h-4"></i>
|
||||
<span class="card-title">${t('multiCli.externalDeps') || 'External'}</span>
|
||||
<span class="card-count">${dependencies.external.length}</span>
|
||||
</div>
|
||||
<ul class="dependency-list">
|
||||
${dependencies.external.map(dep => `<li>${escapeHtml(getI18nText(dep))}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
// Consensus Card
|
||||
if (hasConsensus) {
|
||||
sections.push(`
|
||||
<div class="association-section consensus-section">
|
||||
<h4 class="association-section-title">
|
||||
<i data-lucide="check-check" class="w-4 h-4 inline"></i>
|
||||
${t('multiCli.consensus') || 'Consensus'}
|
||||
</h4>
|
||||
<div class="consensus-grid">
|
||||
${consensus.agreements?.length ? `
|
||||
<div class="association-card consensus-card agreements">
|
||||
<div class="card-header">
|
||||
<i data-lucide="thumbs-up" class="w-4 h-4"></i>
|
||||
<span class="card-title">${t('multiCli.agreements') || 'Agreements'}</span>
|
||||
<span class="card-count">${consensus.agreements.length}</span>
|
||||
</div>
|
||||
<ul class="consensus-list">
|
||||
${consensus.agreements.map(item => `<li class="agreement-item">${escapeHtml(item)}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
${(consensus.resolved_conflicts?.length || consensus.disagreements?.length) ? `
|
||||
<div class="association-card consensus-card conflicts">
|
||||
<div class="card-header">
|
||||
<i data-lucide="git-merge" class="w-4 h-4"></i>
|
||||
<span class="card-title">${t('multiCli.resolvedConflicts') || 'Resolved Conflicts'}</span>
|
||||
<span class="card-count">${(consensus.resolved_conflicts || consensus.disagreements || []).length}</span>
|
||||
</div>
|
||||
<ul class="consensus-list">
|
||||
${(consensus.resolved_conflicts || consensus.disagreements || []).map(item => `<li class="conflict-item">${escapeHtml(item)}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
// Related Files (from existing Files tab logic)
|
||||
if (hasFiles) {
|
||||
sections.push(`
|
||||
<div class="association-section files-section">
|
||||
<h4 class="association-section-title">
|
||||
<i data-lucide="folder-tree" class="w-4 h-4 inline"></i>
|
||||
${t('multiCli.tab.files') || 'Related Files'}
|
||||
</h4>
|
||||
<div class="files-summary">
|
||||
${relatedFiles.fileTree?.length ? `
|
||||
<div class="files-list">
|
||||
${relatedFiles.fileTree.slice(0, 10).map(file => `
|
||||
<div class="file-item">
|
||||
<i data-lucide="file" class="w-3 h-3"></i>
|
||||
<span class="file-path">${escapeHtml(typeof file === 'string' ? file : file.path || file.name)}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
${relatedFiles.fileTree.length > 10 ? `
|
||||
<div class="files-more">+${relatedFiles.fileTree.length - 10} ${t('common.more') || 'more'}</div>
|
||||
` : ''}
|
||||
</div>
|
||||
` : ''}
|
||||
${relatedFiles.impactSummary?.length ? `
|
||||
<div class="impact-summary">
|
||||
<h5>${t('multiCli.impactSummary') || 'Impact Summary'}</h5>
|
||||
<ul>
|
||||
${relatedFiles.impactSummary.slice(0, 5).map(item => `<li>${escapeHtml(getI18nText(item))}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="multi-cli-association-section">
|
||||
${sections.join('')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Lite Task Detail Page
|
||||
function showLiteTaskDetailPage(sessionKey) {
|
||||
const session = liteTaskDataStore[sessionKey];
|
||||
|
||||
Reference in New Issue
Block a user