This commit is contained in:
SuperComboGamer
2025-12-17 21:29:36 -05:00
parent 0d1138dfcf
commit 160bd8bfc7
8 changed files with 502 additions and 182 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, useCallback } from "react";
import { cn } from "@/lib/utils";
import { CheckCircle2, Circle, Loader2, ChevronDown, ChevronRight } from "lucide-react";
import { getElectronAPI } from "@/lib/electron";
@@ -16,15 +16,65 @@ interface TaskInfo {
interface TaskProgressPanelProps {
featureId: string;
projectPath?: string;
className?: string;
}
export function TaskProgressPanel({ featureId, className }: TaskProgressPanelProps) {
export function TaskProgressPanel({ featureId, projectPath, className }: TaskProgressPanelProps) {
const [tasks, setTasks] = useState<TaskInfo[]>([]);
const [isExpanded, setIsExpanded] = useState(true);
const [currentTaskId, setCurrentTaskId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
// Listen to task events
// Load initial tasks from feature's planSpec
const loadInitialTasks = useCallback(async () => {
if (!projectPath) {
setIsLoading(false);
return;
}
try {
const api = getElectronAPI();
if (!api?.features) {
setIsLoading(false);
return;
}
const result = await api.features.get(projectPath, featureId);
if (result.success && result.feature?.planSpec?.tasks) {
const planTasks = result.feature.planSpec.tasks;
const currentId = result.feature.planSpec.currentTaskId;
const completedCount = result.feature.planSpec.tasksCompleted || 0;
// Convert planSpec tasks to TaskInfo with proper status
const initialTasks: TaskInfo[] = planTasks.map((t: any, index: number) => ({
id: t.id,
description: t.description,
filePath: t.filePath,
phase: t.phase,
status: index < completedCount
? "completed" as const
: t.id === currentId
? "in_progress" as const
: "pending" as const,
}));
setTasks(initialTasks);
setCurrentTaskId(currentId || null);
}
} catch (error) {
console.error("Failed to load initial tasks:", error);
} finally {
setIsLoading(false);
}
}, [featureId, projectPath]);
// Load initial state on mount
useEffect(() => {
loadInitialTasks();
}, [loadInitialTasks]);
// Listen to task events for real-time updates
useEffect(() => {
const api = getElectronAPI();
if (!api?.autoMode) return;
@@ -69,24 +119,25 @@ export function TaskProgressPanel({ featureId, className }: TaskProgressPanelPro
t.id === taskEvent.taskId ? { ...t, status: "completed" as const } : t
)
);
// Clear current task if it was completed
if (currentTaskId === taskEvent.taskId) {
setCurrentTaskId(null);
}
setCurrentTaskId(null);
}
break;
}
});
return unsubscribe;
}, [featureId, currentTaskId]);
}, [featureId]);
// Calculate progress
const completedCount = tasks.filter((t) => t.status === "completed").length;
const totalCount = tasks.length;
const progressPercent = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0;
// Don't render if no tasks
// Don't render if loading or no tasks
if (isLoading) {
return null;
}
if (tasks.length === 0) {
return null;
}

View File

@@ -218,6 +218,13 @@ export function AgentOutputModal({
// Show when plan is auto-approved
newContent = `\n✅ Plan auto-approved - continuing to implementation...\n`;
break;
case "plan_revision_requested":
// Show when user requests plan revision
if ("planVersion" in event) {
const revisionEvent = event as Extract<AutoModeEvent, { type: "plan_revision_requested" }>;
newContent = `\n🔄 Revising plan based on your feedback (v${revisionEvent.planVersion})...\n`;
}
break;
case "auto_mode_task_started":
// Show when a task starts
if ("taskId" in event && "taskDescription" in event) {
@@ -362,7 +369,11 @@ export function AgentOutputModal({
</DialogHeader>
{/* Task Progress Panel - shows when tasks are being executed */}
<TaskProgressPanel featureId={featureId} className="flex-shrink-0 mx-1" />
<TaskProgressPanel
featureId={featureId}
projectPath={projectPath}
className="flex-shrink-0 mx-1"
/>
{viewMode === "changes" ? (
<div className="flex-1 min-h-[400px] max-h-[60vh] overflow-y-auto scrollbar-visible">

View File

@@ -14,7 +14,7 @@ import { Textarea } from "@/components/ui/textarea";
import { Markdown } from "@/components/ui/markdown";
import { Label } from "@/components/ui/label";
import { Feature } from "@/store/app-store";
import { Check, X, Edit2, Eye, Loader2 } from "lucide-react";
import { Check, RefreshCw, Edit2, Eye, Loader2 } from "lucide-react";
interface PlanApprovalDialogProps {
open: boolean;
@@ -143,18 +143,21 @@ export function PlanApprovalDialog({
)}
</div>
{/* Reject Feedback Section - Only show when not in viewOnly mode */}
{/* Revision Feedback Section - Only show when not in viewOnly mode */}
{showRejectFeedback && !viewOnly && (
<div className="mt-4 space-y-2">
<Label htmlFor="reject-feedback">Feedback (optional)</Label>
<Label htmlFor="reject-feedback">What changes would you like?</Label>
<Textarea
id="reject-feedback"
value={rejectFeedback}
onChange={(e) => setRejectFeedback(e.target.value)}
placeholder="Provide feedback on why this plan is being rejected..."
placeholder="Describe the changes you'd like to see in the plan..."
className="min-h-[80px]"
disabled={isLoading}
/>
<p className="text-xs text-muted-foreground">
Leave empty to cancel the feature, or provide feedback to regenerate the plan.
</p>
</div>
)}
</div>
@@ -171,30 +174,30 @@ export function PlanApprovalDialog({
onClick={handleCancelReject}
disabled={isLoading}
>
Cancel
Back
</Button>
<Button
variant="destructive"
variant="secondary"
onClick={handleReject}
disabled={isLoading}
>
{isLoading ? (
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
) : (
<X className="w-4 h-4 mr-2" />
<RefreshCw className="w-4 h-4 mr-2" />
)}
Confirm Reject
{rejectFeedback.trim() ? "Revise Plan" : "Cancel Feature"}
</Button>
</>
) : (
<>
<Button
variant="destructive"
variant="outline"
onClick={handleReject}
disabled={isLoading}
>
<X className="w-4 h-4 mr-2" />
Reject
<RefreshCw className="w-4 h-4 mr-2" />
Request Changes
</Button>
<Button
onClick={handleApprove}

View File

@@ -12,6 +12,15 @@ import { cn } from "@/lib/utils";
export type PlanningMode = 'skip' | 'lite' | 'spec' | 'full';
// Parsed task from spec (for spec and full planning modes)
export interface ParsedTask {
id: string; // e.g., "T001"
description: string; // e.g., "Create user model"
filePath?: string; // e.g., "src/models/user.ts"
phase?: string; // e.g., "Phase 1: Foundation" (for full mode)
status: 'pending' | 'in_progress' | 'completed' | 'failed';
}
export interface PlanSpec {
status: 'pending' | 'generating' | 'generated' | 'approved' | 'rejected';
content?: string;
@@ -21,6 +30,8 @@ export interface PlanSpec {
reviewedByUser: boolean;
tasksCompleted?: number;
tasksTotal?: number;
currentTaskId?: string; // ID of the task currently being worked on
tasks?: ParsedTask[]; // Parsed tasks from the spec
}
interface PlanningModeSelectorProps {

View File

@@ -294,6 +294,20 @@ export function useAutoMode() {
}
break;
case "plan_revision_requested":
// Log when user requests plan revision with feedback
if (event.featureId) {
const revisionEvent = event as Extract<AutoModeEvent, { type: "plan_revision_requested" }>;
console.log(`[AutoMode] Plan revision requested for ${event.featureId} (v${revisionEvent.planVersion})`);
addAutoModeActivity({
featureId: event.featureId,
type: "planning",
message: `Revising plan based on feedback (v${revisionEvent.planVersion})...`,
phase: "planning",
});
}
break;
case "auto_mode_task_started":
// Task started - show which task is being worked on
if (event.featureId && "taskId" in event && "taskDescription" in event) {

View File

@@ -305,6 +305,15 @@ export interface Feature {
requirePlanApproval?: boolean; // Whether to pause and require manual approval before implementation
}
// Parsed task from spec (for spec and full planning modes)
export interface ParsedTask {
id: string; // e.g., "T001"
description: string; // e.g., "Create user model"
filePath?: string; // e.g., "src/models/user.ts"
phase?: string; // e.g., "Phase 1: Foundation" (for full mode)
status: 'pending' | 'in_progress' | 'completed' | 'failed';
}
// PlanSpec status for feature planning/specification
export interface PlanSpec {
status: 'pending' | 'generating' | 'generated' | 'approved' | 'rejected';
@@ -315,6 +324,8 @@ export interface PlanSpec {
reviewedByUser: boolean; // True if user has seen the spec
tasksCompleted?: number;
tasksTotal?: number;
currentTaskId?: string; // ID of the task currently being worked on
tasks?: ParsedTask[]; // Parsed tasks from the spec
}
// File tree node for project analysis

View File

@@ -244,6 +244,7 @@ export type AutoModeEvent =
projectPath?: string;
planContent: string;
planningMode: "lite" | "spec" | "full";
planVersion?: number;
}
| {
type: "plan_auto_approved";
@@ -257,6 +258,7 @@ export type AutoModeEvent =
featureId: string;
projectPath?: string;
hasEdits: boolean;
planVersion?: number;
}
| {
type: "plan_rejected";
@@ -264,6 +266,14 @@ export type AutoModeEvent =
projectPath?: string;
feedback?: string;
}
| {
type: "plan_revision_requested";
featureId: string;
projectPath?: string;
feedback?: string;
hasEdits?: boolean;
planVersion?: number;
}
| {
type: "planning_started";
featureId: string;