mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-15 02:42:45 +08:00
feat: Enhance multi-cli-plan support with new synthesis types and update related components
This commit is contained in:
@@ -32,7 +32,7 @@ function EmptyTabState() {
|
|||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col items-center justify-center text-muted-foreground gap-4">
|
<div className="flex-1 flex flex-col items-center justify-center text-muted-foreground gap-4">
|
||||||
<Terminal className="h-12 w-12 opacity-30" />
|
<Terminal className="h-12 w-12 opacity-30" />
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-sm font-medium">
|
<p className="text-sm font-medium">
|
||||||
@@ -56,7 +56,7 @@ function ExecutionNotFoundState({ executionId }: { executionId: string }) {
|
|||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col items-center justify-center text-muted-foreground gap-4">
|
<div className="flex-1 flex flex-col items-center justify-center text-muted-foreground gap-4">
|
||||||
<Terminal className="h-12 w-12 opacity-30" />
|
<Terminal className="h-12 w-12 opacity-30" />
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-sm font-medium">
|
<p className="text-sm font-medium">
|
||||||
@@ -113,7 +113,7 @@ function CliOutputDisplay({ execution, executionId }: { execution: CliExecutionS
|
|||||||
|
|
||||||
if (!execution.output || execution.output.length === 0) {
|
if (!execution.output || execution.output.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col items-center justify-center text-muted-foreground gap-4">
|
<div className="flex-1 flex flex-col items-center justify-center text-muted-foreground gap-4">
|
||||||
<Terminal className="h-12 w-12 opacity-30" />
|
<Terminal className="h-12 w-12 opacity-30" />
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
@@ -185,7 +185,7 @@ export function ContentArea({ paneId, className }: ContentAreaProps) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 overflow-hidden',
|
'flex-1 min-h-0 flex flex-col overflow-hidden',
|
||||||
'bg-background',
|
'bg-background',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// ========================================
|
// ========================================
|
||||||
// Manages allotment-based split panes for CLI viewer
|
// Manages allotment-based split panes for CLI viewer
|
||||||
|
|
||||||
import { useCallback, useMemo, useRef, useEffect } from 'react';
|
import { useMemo, useRef, useEffect, useCallback } from 'react';
|
||||||
import { Allotment } from 'allotment';
|
import { Allotment } from 'allotment';
|
||||||
import 'allotment/dist/style.css';
|
import 'allotment/dist/style.css';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
@@ -46,13 +46,6 @@ function isPaneId(child: PaneId | AllotmentLayoutGroup): child is PaneId {
|
|||||||
function LayoutGroupRenderer({ group, minSize, onSizeChange }: LayoutGroupRendererProps) {
|
function LayoutGroupRenderer({ group, minSize, onSizeChange }: LayoutGroupRendererProps) {
|
||||||
const panes = useViewerPanes();
|
const panes = useViewerPanes();
|
||||||
|
|
||||||
const handleChange = useCallback(
|
|
||||||
(sizes: number[]) => {
|
|
||||||
onSizeChange(sizes);
|
|
||||||
},
|
|
||||||
[onSizeChange]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check if all panes in this group exist
|
// Check if all panes in this group exist
|
||||||
const validChildren = useMemo(() => {
|
const validChildren = useMemo(() => {
|
||||||
return group.children.filter(child => {
|
return group.children.filter(child => {
|
||||||
@@ -71,7 +64,7 @@ function LayoutGroupRenderer({ group, minSize, onSizeChange }: LayoutGroupRender
|
|||||||
<Allotment
|
<Allotment
|
||||||
vertical={group.direction === 'vertical'}
|
vertical={group.direction === 'vertical'}
|
||||||
defaultSizes={group.sizes}
|
defaultSizes={group.sizes}
|
||||||
onChange={handleChange}
|
onChange={onSizeChange}
|
||||||
className="h-full"
|
className="h-full"
|
||||||
>
|
>
|
||||||
{validChildren.map((child, index) => (
|
{validChildren.map((child, index) => (
|
||||||
|
|||||||
@@ -104,14 +104,12 @@ function MonitorBodyComponent(
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className={cn('flex-1 overflow-y-auto bg-background relative', className)}
|
className={cn('flex-1 min-h-0 overflow-y-auto bg-background relative', className)}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
>
|
>
|
||||||
<div className="h-full">
|
{children}
|
||||||
{children}
|
{/* Anchor for scroll to bottom */}
|
||||||
{/* Anchor for scroll to bottom */}
|
<div ref={logsEndRef} />
|
||||||
<div ref={logsEndRef} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Show scroll button when user is not at bottom */}
|
{/* Show scroll button when user is not at bottom */}
|
||||||
{showScrollButton && isUserScrolling && (
|
{showScrollButton && isUserScrolling && (
|
||||||
|
|||||||
@@ -82,15 +82,16 @@ export function TaskDrawer({ task, isOpen, onClose }: TaskDrawerProps) {
|
|||||||
return () => window.removeEventListener('keydown', handleEsc);
|
return () => window.removeEventListener('keydown', handleEsc);
|
||||||
}, [isOpen, onClose]);
|
}, [isOpen, onClose]);
|
||||||
|
|
||||||
if (!task || !isOpen) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize task to unified flat format (handles old nested, new flat, and raw LiteTask/TaskData)
|
// Normalize task to unified flat format (handles old nested, new flat, and raw LiteTask/TaskData)
|
||||||
|
// MUST be called before early return to satisfy React hooks rules
|
||||||
const nt = React.useMemo(
|
const nt = React.useMemo(
|
||||||
() => normalizeTask(task as unknown as Record<string, unknown>),
|
() => task ? normalizeTask(task as unknown as Record<string, unknown>) : null,
|
||||||
[task],
|
[task],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!task || !isOpen || !nt) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const taskId = nt.task_id || 'N/A';
|
const taskId = nt.task_id || 'N/A';
|
||||||
const taskTitle = nt.title || 'Untitled Task';
|
const taskTitle = nt.title || 'Untitled Task';
|
||||||
const taskDescription = nt.description;
|
const taskDescription = nt.description;
|
||||||
|
|||||||
@@ -2173,6 +2173,75 @@ export interface LiteTaskSession {
|
|||||||
status?: string;
|
status?: string;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
|
// Multi-cli-plan specific fields
|
||||||
|
rounds?: RoundSynthesis[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multi-cli-plan synthesis types
|
||||||
|
export interface SolutionFileAction {
|
||||||
|
file: string;
|
||||||
|
line: number;
|
||||||
|
action: 'modify' | 'create' | 'delete';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SolutionTask {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
depends_on: string[];
|
||||||
|
files: SolutionFileAction[];
|
||||||
|
key_point: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Solution {
|
||||||
|
name: string;
|
||||||
|
source_cli: string[];
|
||||||
|
feasibility: number;
|
||||||
|
effort: string;
|
||||||
|
risk: string;
|
||||||
|
summary: string;
|
||||||
|
pros: string[];
|
||||||
|
cons: string[];
|
||||||
|
affected_files: SolutionFileAction[];
|
||||||
|
implementation_plan: {
|
||||||
|
approach: string;
|
||||||
|
tasks: SolutionTask[];
|
||||||
|
execution_flow: string;
|
||||||
|
milestones: string[];
|
||||||
|
};
|
||||||
|
dependencies: {
|
||||||
|
internal: string[];
|
||||||
|
external: string[];
|
||||||
|
};
|
||||||
|
technical_concerns: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SynthesisConvergence {
|
||||||
|
score: number;
|
||||||
|
new_insights: boolean;
|
||||||
|
recommendation: 'converged' | 'continue' | 'user_input_needed';
|
||||||
|
rationale: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SynthesisCrossVerification {
|
||||||
|
agreements: string[];
|
||||||
|
disagreements: Array<{
|
||||||
|
topic: string;
|
||||||
|
gemini: string;
|
||||||
|
codex: string;
|
||||||
|
resolution: string | null;
|
||||||
|
}>;
|
||||||
|
resolution: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoundSynthesis {
|
||||||
|
round: number;
|
||||||
|
timestamp: string;
|
||||||
|
cli_executions: Record<string, { status: string; duration_ms: number; model: string }>;
|
||||||
|
solutions: Solution[];
|
||||||
|
convergence: SynthesisConvergence;
|
||||||
|
cross_verification: SynthesisCrossVerification;
|
||||||
|
clarification_questions: string[];
|
||||||
|
user_feedback_incorporated?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LiteTasksResponse {
|
export interface LiteTasksResponse {
|
||||||
|
|||||||
@@ -30,6 +30,17 @@ import {
|
|||||||
Clock,
|
Clock,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
FileCode,
|
FileCode,
|
||||||
|
ThumbsUp,
|
||||||
|
ThumbsDown,
|
||||||
|
Target,
|
||||||
|
GitCompare,
|
||||||
|
HelpCircle,
|
||||||
|
Cpu,
|
||||||
|
Timer,
|
||||||
|
Sparkles,
|
||||||
|
Layers,
|
||||||
|
CheckCheck,
|
||||||
|
ArrowRight,
|
||||||
} 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';
|
||||||
@@ -37,7 +48,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 } from '@/lib/api';
|
import { fetchLiteSessionContext, type LiteTask, type LiteTaskSession, type LiteSessionContext, type RoundSynthesis } 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';
|
||||||
|
|
||||||
@@ -532,25 +543,36 @@ function ExpandedMultiCliPanel({
|
|||||||
{/* Discussion Tab */}
|
{/* Discussion Tab */}
|
||||||
{activeTab === 'discussion' && (
|
{activeTab === 'discussion' && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<Card className="border-border">
|
{/* Rounds Detail */}
|
||||||
<CardContent className="p-4">
|
{session.rounds && session.rounds.length > 0 ? (
|
||||||
<div className="flex items-center gap-2 mb-3">
|
session.rounds.map((round, idx) => (
|
||||||
<MessagesSquare className="h-5 w-5 text-primary" />
|
<RoundDetailCard
|
||||||
<h4 className="font-medium text-foreground">
|
key={round.round || idx}
|
||||||
{formatMessage({ id: 'liteTasks.multiCli.discussionRounds' })}
|
round={round}
|
||||||
</h4>
|
isLast={idx === session.rounds!.length - 1}
|
||||||
<Badge variant="secondary" className="text-xs">{roundCount} {formatMessage({ id: 'liteTasks.rounds' })}</Badge>
|
/>
|
||||||
</div>
|
))
|
||||||
<p className="text-sm text-muted-foreground">
|
) : (
|
||||||
{formatMessage({ id: 'liteTasks.multiCli.discussionDescription' })}
|
<Card className="border-border">
|
||||||
</p>
|
<CardContent className="p-4">
|
||||||
{goal && (
|
<div className="flex items-center gap-2 mb-3">
|
||||||
<div className="mt-3 p-3 bg-muted/50 rounded-lg">
|
<MessagesSquare className="h-5 w-5 text-primary" />
|
||||||
<p className="text-sm text-foreground">{goal}</p>
|
<h4 className="font-medium text-foreground">
|
||||||
|
{formatMessage({ id: 'liteTasks.multiCli.discussionRounds' })}
|
||||||
|
</h4>
|
||||||
|
<Badge variant="secondary" className="text-xs">{roundCount} {formatMessage({ id: 'liteTasks.rounds' })}</Badge>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<p className="text-sm text-muted-foreground">
|
||||||
</CardContent>
|
{formatMessage({ id: 'liteTasks.multiCli.discussionDescription' })}
|
||||||
</Card>
|
</p>
|
||||||
|
{goal && (
|
||||||
|
<div className="mt-3 p-3 bg-muted/50 rounded-lg">
|
||||||
|
<p className="text-sm text-foreground">{goal}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -118,6 +118,9 @@ interface LiteTaskDetail {
|
|||||||
explorations: unknown[];
|
explorations: unknown[];
|
||||||
clarifications: unknown | null;
|
clarifications: unknown | null;
|
||||||
diagnoses?: Diagnoses;
|
diagnoses?: Diagnoses;
|
||||||
|
// Multi-cli-plan specific
|
||||||
|
rounds?: RoundSynthesis[];
|
||||||
|
latestSynthesis?: RoundSynthesis | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -923,6 +926,9 @@ export async function getLiteTaskDetail(workflowDir: string, type: string, sessi
|
|||||||
tasks: extractTasksFromSyntheses(syntheses),
|
tasks: extractTasksFromSyntheses(syntheses),
|
||||||
explorations,
|
explorations,
|
||||||
clarifications,
|
clarifications,
|
||||||
|
// Multi-cli-plan specific fields
|
||||||
|
rounds: syntheses,
|
||||||
|
latestSynthesis,
|
||||||
};
|
};
|
||||||
|
|
||||||
return detail;
|
return detail;
|
||||||
|
|||||||
Reference in New Issue
Block a user