mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-15 02:42:45 +08:00
feat: Add specialized context rendering for multi-cli-plan sessions
- Add MultiCliContextPackage type for multi-cli-plan context-package.json - Create MultiCliContextContent component with sections for: - Selected solution (feasibility, effort, risk, CLI sources) - Implementation plan (approach, tasks with files, execution flow, milestones) - Dependencies (internal and external) - CLI consensus (agreements, resolved conflicts) - Technical concerns and constraints - Update ExpandedMultiCliPanel to detect and use appropriate context renderer - Add i18n translations for new context labels
This commit is contained in:
@@ -2244,6 +2244,42 @@ export interface RoundSynthesis {
|
|||||||
user_feedback_incorporated?: string;
|
user_feedback_incorporated?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Multi-cli-plan context-package.json structure
|
||||||
|
export interface MultiCliContextPackage {
|
||||||
|
solution?: {
|
||||||
|
name: string;
|
||||||
|
source_cli: string[];
|
||||||
|
feasibility: number;
|
||||||
|
effort: string;
|
||||||
|
risk: string;
|
||||||
|
summary: string;
|
||||||
|
};
|
||||||
|
implementation_plan?: {
|
||||||
|
approach: string;
|
||||||
|
tasks: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
depends_on: string[];
|
||||||
|
files: SolutionFileAction[];
|
||||||
|
key_point: string | null;
|
||||||
|
}>;
|
||||||
|
execution_flow: string;
|
||||||
|
milestones: string[];
|
||||||
|
};
|
||||||
|
dependencies?: {
|
||||||
|
internal: string[];
|
||||||
|
external: string[];
|
||||||
|
};
|
||||||
|
technical_concerns?: string[];
|
||||||
|
consensus?: {
|
||||||
|
agreements: string[];
|
||||||
|
resolved_conflicts?: string;
|
||||||
|
};
|
||||||
|
constraints?: string[];
|
||||||
|
task_description?: string;
|
||||||
|
session_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LiteTasksResponse {
|
export interface LiteTasksResponse {
|
||||||
litePlan?: LiteTaskSession[];
|
litePlan?: LiteTaskSession[];
|
||||||
liteFix?: LiteTaskSession[];
|
liteFix?: LiteTaskSession[];
|
||||||
|
|||||||
@@ -36,7 +36,22 @@
|
|||||||
"agreements": "Agreements",
|
"agreements": "Agreements",
|
||||||
"disagreements": "Disagreements",
|
"disagreements": "Disagreements",
|
||||||
"resolution": "Resolution",
|
"resolution": "Resolution",
|
||||||
"clarificationQuestions": "Clarification Questions"
|
"clarificationQuestions": "Clarification Questions",
|
||||||
|
"context": {
|
||||||
|
"solution": "Selected Solution",
|
||||||
|
"implementationPlan": "Implementation Plan",
|
||||||
|
"approach": "Approach",
|
||||||
|
"tasks": "Tasks",
|
||||||
|
"milestones": "Milestones",
|
||||||
|
"dependencies": "Dependencies",
|
||||||
|
"internal": "Internal",
|
||||||
|
"external": "External",
|
||||||
|
"consensus": "CLI Consensus",
|
||||||
|
"resolvedConflicts": "Resolved Conflicts",
|
||||||
|
"technicalConcerns": "Technical Concerns",
|
||||||
|
"constraints": "Constraints",
|
||||||
|
"sessionInfo": "Session Info"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"createdAt": "Created",
|
"createdAt": "Created",
|
||||||
"rounds": "rounds",
|
"rounds": "rounds",
|
||||||
|
|||||||
@@ -36,7 +36,22 @@
|
|||||||
"agreements": "共识",
|
"agreements": "共识",
|
||||||
"disagreements": "分歧",
|
"disagreements": "分歧",
|
||||||
"resolution": "解决方式",
|
"resolution": "解决方式",
|
||||||
"clarificationQuestions": "澄清问题"
|
"clarificationQuestions": "澄清问题",
|
||||||
|
"context": {
|
||||||
|
"solution": "选定方案",
|
||||||
|
"implementationPlan": "实现计划",
|
||||||
|
"approach": "实现路径",
|
||||||
|
"tasks": "任务列表",
|
||||||
|
"milestones": "里程碑",
|
||||||
|
"dependencies": "依赖项",
|
||||||
|
"internal": "内部依赖",
|
||||||
|
"external": "外部依赖",
|
||||||
|
"consensus": "CLI 共识",
|
||||||
|
"resolvedConflicts": "已解决分歧",
|
||||||
|
"technicalConcerns": "技术关注点",
|
||||||
|
"constraints": "约束条件",
|
||||||
|
"sessionInfo": "会话信息"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"createdAt": "创建时间",
|
"createdAt": "创建时间",
|
||||||
"rounds": "轮",
|
"rounds": "轮",
|
||||||
|
|||||||
@@ -39,6 +39,12 @@ import {
|
|||||||
Timer,
|
Timer,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
CheckCheck,
|
CheckCheck,
|
||||||
|
Route,
|
||||||
|
Flag,
|
||||||
|
AlertOctagon,
|
||||||
|
Link2,
|
||||||
|
ShieldCheck,
|
||||||
|
Settings2,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useLiteTasks } from '@/hooks/useLiteTasks';
|
import { useLiteTasks } from '@/hooks/useLiteTasks';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
@@ -46,7 +52,7 @@ import { Badge } from '@/components/ui/Badge';
|
|||||||
import { Card, CardContent } from '@/components/ui/Card';
|
import { Card, CardContent } from '@/components/ui/Card';
|
||||||
import { TabsNavigation } from '@/components/ui/TabsNavigation';
|
import { TabsNavigation } from '@/components/ui/TabsNavigation';
|
||||||
import { TaskDrawer } from '@/components/shared/TaskDrawer';
|
import { TaskDrawer } from '@/components/shared/TaskDrawer';
|
||||||
import { fetchLiteSessionContext, type LiteTask, type LiteTaskSession, type LiteSessionContext, type RoundSynthesis } from '@/lib/api';
|
import { fetchLiteSessionContext, type LiteTask, type LiteTaskSession, type LiteSessionContext, type RoundSynthesis, type MultiCliContextPackage } from '@/lib/api';
|
||||||
import { LiteContextContent } from '@/components/lite-tasks/LiteContextContent';
|
import { LiteContextContent } from '@/components/lite-tasks/LiteContextContent';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -540,6 +546,292 @@ function RoundDetailCard({ round, isLast }: { round: RoundSynthesis; isLast: boo
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MultiCliContextContent - Display context-package.json for multi-cli-plan sessions
|
||||||
|
*/
|
||||||
|
function MultiCliContextContent({ data }: { data: MultiCliContextPackage }) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const [expandedSections, setExpandedSections] = React.useState<Set<string>>(new Set(['solution', 'plan']));
|
||||||
|
|
||||||
|
const toggleSection = (section: string) => {
|
||||||
|
setExpandedSections(prev => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(section)) {
|
||||||
|
next.delete(section);
|
||||||
|
} else {
|
||||||
|
next.add(section);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const isExpanded = (section: string) => expandedSections.has(section);
|
||||||
|
|
||||||
|
// Section wrapper component
|
||||||
|
const Section = ({ id, icon, title, badge, children, defaultExpanded = true }: {
|
||||||
|
id: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
title: string;
|
||||||
|
badge?: React.ReactNode;
|
||||||
|
children: React.ReactNode;
|
||||||
|
defaultExpanded?: boolean;
|
||||||
|
}) => {
|
||||||
|
// Initialize expanded state based on defaultExpanded if not already in set
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (defaultExpanded && !expandedSections.has(id)) {
|
||||||
|
setExpandedSections(prev => new Set(prev).add(id));
|
||||||
|
}
|
||||||
|
}, [id, defaultExpanded]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="border-border">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="w-full flex items-center gap-2 p-3 text-left hover:bg-muted/50 transition-colors"
|
||||||
|
onClick={() => toggleSection(id)}
|
||||||
|
>
|
||||||
|
<span className="text-muted-foreground">{icon}</span>
|
||||||
|
<span className="text-sm font-medium text-foreground flex-1">{title}</span>
|
||||||
|
{badge}
|
||||||
|
{isExpanded(id) ? (
|
||||||
|
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
||||||
|
) : (
|
||||||
|
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
{isExpanded(id) && (
|
||||||
|
<CardContent className="px-3 pb-3 pt-0">
|
||||||
|
{children}
|
||||||
|
</CardContent>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{/* Solution Section */}
|
||||||
|
{data.solution && (
|
||||||
|
<Section
|
||||||
|
id="solution"
|
||||||
|
icon={<Sparkles className="h-4 w-4" />}
|
||||||
|
title={formatMessage({ id: 'liteTasks.multiCli.context.solution' })}
|
||||||
|
badge={
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{data.solution.source_cli.map(cli => (
|
||||||
|
<Badge key={cli} variant="outline" className="text-[10px] px-1.5 py-0">{cli}</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="font-medium text-foreground text-sm">{data.solution.name}</h4>
|
||||||
|
<p className="text-xs text-muted-foreground">{data.solution.summary}</p>
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
<Badge variant="success" className="text-[10px]">
|
||||||
|
{Math.round(data.solution.feasibility * 100)}% {formatMessage({ id: 'liteTasks.multiCli.feasible' })}
|
||||||
|
</Badge>
|
||||||
|
<Badge variant="warning" className="text-[10px]">{data.solution.effort}</Badge>
|
||||||
|
<Badge variant={data.solution.risk === 'high' ? 'destructive' : data.solution.risk === 'low' ? 'success' : 'warning'} className="text-[10px]">
|
||||||
|
{data.solution.risk} {formatMessage({ id: 'liteTasks.multiCli.risk' })}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Implementation Plan Section */}
|
||||||
|
{data.implementation_plan && (
|
||||||
|
<Section
|
||||||
|
id="plan"
|
||||||
|
icon={<Route className="h-4 w-4" />}
|
||||||
|
title={formatMessage({ id: 'liteTasks.multiCli.context.implementationPlan' })}
|
||||||
|
badge={<Badge variant="secondary" className="text-[10px]">{data.implementation_plan.tasks?.length || 0} tasks</Badge>}
|
||||||
|
>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{/* Approach */}
|
||||||
|
<div className="text-xs text-muted-foreground">
|
||||||
|
<span className="font-medium text-foreground">{formatMessage({ id: 'liteTasks.multiCli.context.approach' })}:</span>{' '}
|
||||||
|
{data.implementation_plan.approach}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Execution Flow */}
|
||||||
|
{data.implementation_plan.execution_flow && (
|
||||||
|
<div className="p-2 bg-muted/50 rounded-md">
|
||||||
|
<code className="text-xs font-mono text-foreground">{data.implementation_plan.execution_flow}</code>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Tasks */}
|
||||||
|
{data.implementation_plan.tasks && data.implementation_plan.tasks.length > 0 && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="text-xs font-medium text-foreground">{formatMessage({ id: 'liteTasks.multiCli.context.tasks' })}</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
{data.implementation_plan.tasks.map((task, idx) => (
|
||||||
|
<div key={task.id || idx} className="flex items-start gap-2 p-2 bg-muted/30 rounded-md">
|
||||||
|
<Badge variant="outline" className="text-[10px] font-mono shrink-0">{task.id}</Badge>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="text-xs font-medium text-foreground">{task.name}</div>
|
||||||
|
{task.key_point && (
|
||||||
|
<div className="text-[10px] text-muted-foreground mt-0.5">{task.key_point}</div>
|
||||||
|
)}
|
||||||
|
{task.files && task.files.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-1 mt-1">
|
||||||
|
{task.files.map((f, i) => (
|
||||||
|
<Badge key={i} variant="secondary" className="text-[9px] font-mono">
|
||||||
|
{f.action === 'create' ? '+' : f.action === 'delete' ? '-' : '~'} {f.file}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Milestones */}
|
||||||
|
{data.implementation_plan.milestones && data.implementation_plan.milestones.length > 0 && (
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<div className="flex items-center gap-2 text-xs font-medium text-foreground">
|
||||||
|
<Flag className="h-3 w-3" />
|
||||||
|
{formatMessage({ id: 'liteTasks.multiCli.context.milestones' })}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{data.implementation_plan.milestones.map((milestone, i) => (
|
||||||
|
<Badge key={i} variant="info" className="text-[10px]">{milestone}</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Dependencies Section */}
|
||||||
|
{data.dependencies && (data.dependencies.internal?.length > 0 || data.dependencies.external?.length > 0) && (
|
||||||
|
<Section
|
||||||
|
id="deps"
|
||||||
|
icon={<Link2 className="h-4 w-4" />}
|
||||||
|
title={formatMessage({ id: 'liteTasks.multiCli.context.dependencies' })}
|
||||||
|
badge={<Badge variant="secondary" className="text-[10px]">{(data.dependencies.internal?.length || 0) + (data.dependencies.external?.length || 0)}</Badge>}
|
||||||
|
>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{data.dependencies.internal && data.dependencies.internal.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<div className="text-xs font-medium text-foreground mb-1">{formatMessage({ id: 'liteTasks.multiCli.context.internal' })}</div>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{data.dependencies.internal.map((dep, i) => (
|
||||||
|
<Badge key={i} variant="outline" className="text-[10px] font-mono">{dep}</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{data.dependencies.external && data.dependencies.external.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<div className="text-xs font-medium text-foreground mb-1">{formatMessage({ id: 'liteTasks.multiCli.context.external' })}</div>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{data.dependencies.external.map((dep, i) => (
|
||||||
|
<Badge key={i} variant="secondary" className="text-[10px] font-mono">{dep}</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Consensus Section */}
|
||||||
|
{data.consensus && data.consensus.agreements?.length > 0 && (
|
||||||
|
<Section
|
||||||
|
id="consensus"
|
||||||
|
icon={<ShieldCheck className="h-4 w-4" />}
|
||||||
|
title={formatMessage({ id: 'liteTasks.multiCli.context.consensus' })}
|
||||||
|
badge={<Badge variant="success" className="text-[10px]">{data.consensus.agreements.length}</Badge>}
|
||||||
|
>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<ul className="space-y-1">
|
||||||
|
{data.consensus.agreements.map((agreement, i) => (
|
||||||
|
<li key={i} className="flex items-start gap-2 text-xs text-muted-foreground">
|
||||||
|
<CheckCircle2 className="h-3 w-3 text-success shrink-0 mt-0.5" />
|
||||||
|
<span>{agreement}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{data.consensus.resolved_conflicts && (
|
||||||
|
<div className="mt-2 p-2 bg-success/10 border border-success/20 rounded-md text-xs text-muted-foreground">
|
||||||
|
<span className="font-medium text-success">{formatMessage({ id: 'liteTasks.multiCli.context.resolvedConflicts' })}:</span>{' '}
|
||||||
|
{data.consensus.resolved_conflicts}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Technical Concerns Section */}
|
||||||
|
{data.technical_concerns && data.technical_concerns.length > 0 && (
|
||||||
|
<Section
|
||||||
|
id="concerns"
|
||||||
|
icon={<AlertOctagon className="h-4 w-4" />}
|
||||||
|
title={formatMessage({ id: 'liteTasks.multiCli.context.technicalConcerns' })}
|
||||||
|
badge={<Badge variant="warning" className="text-[10px]">{data.technical_concerns.length}</Badge>}
|
||||||
|
>
|
||||||
|
<ul className="space-y-1">
|
||||||
|
{data.technical_concerns.map((concern, i) => (
|
||||||
|
<li key={i} className="flex items-start gap-2 text-xs text-muted-foreground">
|
||||||
|
<AlertCircle className="h-3 w-3 text-warning shrink-0 mt-0.5" />
|
||||||
|
<span>{concern}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Constraints Section */}
|
||||||
|
{data.constraints && data.constraints.length > 0 && (
|
||||||
|
<Section
|
||||||
|
id="constraints"
|
||||||
|
icon={<Settings2 className="h-4 w-4" />}
|
||||||
|
title={formatMessage({ id: 'liteTasks.multiCli.context.constraints' })}
|
||||||
|
badge={<Badge variant="secondary" className="text-[10px]">{data.constraints.length}</Badge>}
|
||||||
|
>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{data.constraints.map((constraint, i) => (
|
||||||
|
<Badge key={i} variant="outline" className="text-[10px]">{constraint}</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Session Info */}
|
||||||
|
{(data.task_description || data.session_id) && (
|
||||||
|
<Section
|
||||||
|
id="info"
|
||||||
|
icon={<Package className="h-4 w-4" />}
|
||||||
|
title={formatMessage({ id: 'liteTasks.multiCli.context.sessionInfo' })}
|
||||||
|
defaultExpanded={false}
|
||||||
|
>
|
||||||
|
<div className="space-y-2 text-xs">
|
||||||
|
{data.task_description && (
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
<span className="font-medium text-foreground">{formatMessage({ id: 'liteTasks.contextPanel.taskDescription' })}:</span>{' '}
|
||||||
|
{data.task_description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{data.session_id && (
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
<span className="font-medium text-foreground">{formatMessage({ id: 'liteTasks.contextPanel.sessionId' })}:</span>{' '}
|
||||||
|
<span className="font-mono bg-muted/50 px-1.5 py-0.5 rounded">{data.session_id}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type MultiCliExpandedTab = 'tasks' | 'discussion' | 'context';
|
type MultiCliExpandedTab = 'tasks' | 'discussion' | 'context';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -882,7 +1174,12 @@ function ExpandedMultiCliPanel({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!contextLoading && !contextError && contextData && (
|
{!contextLoading && !contextError && contextData && (
|
||||||
<LiteContextContent contextData={contextData} session={session} />
|
// Detect multi-cli-plan context by checking for solution field
|
||||||
|
(contextData.context as MultiCliContextPackage)?.solution ? (
|
||||||
|
<MultiCliContextContent data={contextData.context as MultiCliContextPackage} />
|
||||||
|
) : (
|
||||||
|
<LiteContextContent contextData={contextData} session={session} />
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
{!contextLoading && !contextError && !contextData && !session.path && (
|
{!contextLoading && !contextError && !contextData && !session.path && (
|
||||||
<div className="flex flex-col items-center justify-center py-8 text-center">
|
<div className="flex flex-col items-center justify-center py-8 text-center">
|
||||||
|
|||||||
Reference in New Issue
Block a user