diff --git a/apps/web/src/components/gen-ui/action-step.tsx b/apps/web/src/components/gen-ui/action-step.tsx
new file mode 100644
index 00000000..15644544
--- /dev/null
+++ b/apps/web/src/components/gen-ui/action-step.tsx
@@ -0,0 +1,281 @@
+"use client";
+
+import "../app/globals.css";
+import { useState } from "react";
+import {
+ Terminal,
+ FileCode,
+ Loader2,
+ CheckCircle,
+ XCircle,
+ ChevronDown,
+ ChevronUp,
+ MessageSquare,
+ FileText,
+} from "lucide-react";
+
+// Common props for all action types
+type BaseActionProps = {
+ status: "loading" | "generating" | "done";
+ success?: boolean;
+ reasoningText?: string;
+ summaryText?: string;
+};
+
+// Shell command specific props
+type ShellActionProps = BaseActionProps & {
+ actionType: "shell";
+ command: string;
+ workdir?: string;
+ output?: string;
+ errorCode?: number;
+};
+
+// Apply patch specific props
+type PatchActionProps = BaseActionProps & {
+ actionType: "apply-patch";
+ file: string;
+ diff?: string;
+ errorMessage?: string;
+ fixedDiff?: string;
+};
+
+// Union type for all possible action props
+type ActionStepProps =
+ | (BaseActionProps & { status: "loading" })
+ | ShellActionProps
+ | PatchActionProps;
+
+export function ActionStep(props: ActionStepProps) {
+ const [expanded, setExpanded] = useState(props.status === "done");
+ const [showReasoning, setShowReasoning] = useState(false);
+ const [showSummary, setShowSummary] = useState(false);
+
+ const getStatusIcon = () => {
+ switch (props.status) {
+ case "loading":
+ return (
+
+ );
+ case "generating":
+ return ;
+ case "done":
+ return props.success ? (
+
+ ) : (
+
+ );
+ }
+ };
+
+ const getStatusText = () => {
+ if (props.status === "loading") {
+ return "Preparing action...";
+ }
+
+ if (props.status === "generating") {
+ return props.actionType === "shell"
+ ? "Executing..."
+ : "Applying patch...";
+ }
+
+ if (props.status === "done") {
+ if (props.actionType === "shell") {
+ return props.success ? "Command completed" : "Command failed";
+ } else if (props.actionType === "apply-patch") {
+ return props.success ? "Patch applied" : "Patch failed";
+ }
+ }
+
+ return "";
+ };
+
+ // Determine if we should show the content toggle button
+ const shouldShowToggle = () => {
+ if (props.status !== "done") return false;
+
+ if (props.actionType === "shell") {
+ return !!props.output;
+ } else if (props.actionType === "apply-patch") {
+ return !!props.diff;
+ }
+
+ return false;
+ };
+
+ // Render the header icon based on action type
+ const renderHeaderIcon = () => {
+ if (props.status === "loading" || !("actionType" in props)) {
+ // In loading state, we don't know the type yet, use a generic icon
+ return ;
+ }
+
+ return props.actionType === "shell" ? (
+
+ ) : (
+
+ );
+ };
+
+ // Render the header content based on action type
+ const renderHeaderContent = () => {
+ if (props.status === "loading" || !("actionType" in props)) {
+ return (
+
+ Preparing action...
+
+ );
+ }
+
+ if (props.actionType === "shell") {
+ return (
+
+ {props.workdir && (
+
+ {props.workdir}
+
+ )}
+
+ {props.command}
+
+
+ );
+ } else {
+ return (
+
+ {props.file}
+
+ );
+ }
+ };
+
+ // Render the content based on action type
+ const renderContent = () => {
+ if (props.status !== "done" || !("actionType" in props)) return null;
+
+ if (!expanded) return null;
+
+ if (props.actionType === "shell" && props.output) {
+ return (
+
+
+ {props.output}
+
+ {props.errorCode !== undefined && !props.success && (
+
+ Exit code: {props.errorCode}
+
+ )}
+
+ );
+ } else if (props.actionType === "apply-patch" && props.diff) {
+ return (
+
+
+
+ {!props.success && props.errorMessage && (
+
+ {props.errorMessage}
+
+ )}
+
+ {!props.success && props.fixedDiff && (
+
+ )}
+
+ );
+ }
+
+ return null;
+ };
+
+ return (
+
+ {props.reasoningText && (
+
+
+ {showReasoning && (
+
+ {props.reasoningText}
+
+ )}
+
+ )}
+
+
+ {renderHeaderIcon()}
+ {renderHeaderContent()}
+
+
+ {getStatusText()}
+
+ {getStatusIcon()}
+ {shouldShowToggle() && (
+
+ )}
+
+
+
+ {renderContent()}
+
+ {props.summaryText && props.status === "done" && (
+
+
+ {showSummary && (
+
+ {props.summaryText}
+
+ )}
+
+ )}
+
+ );
+}
+
+function formatDiff(diff: string) {
+ return diff
+ .split("\n")
+ .map((line) => {
+ if (line.startsWith("+")) {
+ return `${line}`;
+ } else if (line.startsWith("-")) {
+ return `${line}`;
+ } else if (line.startsWith("@")) {
+ return `${line}`;
+ }
+ return line;
+ })
+ .join("\n");
+}
diff --git a/apps/web/src/components/gen-ui/diagnose-error.tsx b/apps/web/src/components/gen-ui/diagnose-error.tsx
new file mode 100644
index 00000000..7013f3cf
--- /dev/null
+++ b/apps/web/src/components/gen-ui/diagnose-error.tsx
@@ -0,0 +1,122 @@
+"use client";
+
+import "../app/globals.css";
+import {
+ AlertTriangle,
+ Loader2,
+ CheckCircle,
+ MessageSquare,
+ FileText,
+} from "lucide-react";
+import { useState } from "react";
+
+type DiagnoseErrorProps = {
+ status: "loading" | "generating" | "done";
+ diagnosis?: string;
+ recommendation?: string;
+ reasoningText?: string;
+ summaryText?: string;
+};
+
+export function DiagnoseError({
+ status,
+ diagnosis,
+ recommendation,
+ reasoningText,
+ summaryText,
+}: DiagnoseErrorProps) {
+ const [showReasoning, setShowReasoning] = useState(false);
+ const [showSummary, setShowSummary] = useState(false);
+
+ const getStatusIcon = () => {
+ switch (status) {
+ case "loading":
+ return (
+
+ );
+ case "generating":
+ return ;
+ case "done":
+ return ;
+ }
+ };
+
+ const getStatusText = () => {
+ switch (status) {
+ case "loading":
+ return "Preparing error analysis...";
+ case "generating":
+ return "Diagnosing errors...";
+ case "done":
+ return "Error diagnosis complete";
+ }
+ };
+
+ return (
+
+ {reasoningText && (
+
+
+ {showReasoning && (
+
+ {reasoningText}
+
+ )}
+
+ )}
+
+
+
+
+ {getStatusText()}
+
+ {getStatusIcon()}
+
+
+ {status === "done" && diagnosis && (
+
+
+
+ Diagnosis
+
+
{diagnosis}
+
+
+ {recommendation && (
+
+
+ Recommendation
+
+
+ {recommendation}
+
+
+ )}
+
+ )}
+
+ {summaryText && status === "done" && (
+
+
+ {showSummary && (
+
+ {summaryText}
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/apps/web/src/components/gen-ui/index.tsx b/apps/web/src/components/gen-ui/index.tsx
new file mode 100644
index 00000000..ebf0d372
--- /dev/null
+++ b/apps/web/src/components/gen-ui/index.tsx
@@ -0,0 +1,15 @@
+import { ActionStep } from "./action-step";
+import { DiagnoseError } from "./diagnose-error";
+import { InitializeStep } from "./initialize-step";
+import { PushChanges } from "./push-changes";
+import { ReplanningStep } from "./replanning-step";
+import { TaskSummary } from "./task-summary";
+
+export default {
+ "action-step": ActionStep,
+ "diagnose-error": DiagnoseError,
+ "initialize-step": InitializeStep,
+ "push-changes": PushChanges,
+ "replanning-step": ReplanningStep,
+ "task-summary": TaskSummary,
+};
diff --git a/apps/web/src/components/gen-ui/initialize-step.tsx b/apps/web/src/components/gen-ui/initialize-step.tsx
new file mode 100644
index 00000000..eed5448a
--- /dev/null
+++ b/apps/web/src/components/gen-ui/initialize-step.tsx
@@ -0,0 +1,145 @@
+"use client";
+
+import "../app/globals.css";
+import {
+ Loader2,
+ CheckCircle,
+ XCircle,
+ GitBranch,
+ MessageSquare,
+ FileText,
+} from "lucide-react";
+import { useState } from "react";
+
+type Step = {
+ name: string;
+ status: "waiting" | "generating" | "success" | "error";
+ error?: string;
+};
+
+type InitializeStepProps = {
+ status: "loading" | "generating" | "done";
+ success?: boolean;
+ steps?: Step[];
+ reasoningText?: string;
+ summaryText?: string;
+};
+
+export function InitializeStep({
+ status,
+ success,
+ steps,
+ reasoningText,
+ summaryText,
+}: InitializeStepProps) {
+ const [showReasoning, setShowReasoning] = useState(false);
+ const [showSummary, setShowSummary] = useState(false);
+
+ const stepStatusIcon = {
+ waiting: (
+
+ ),
+ generating: ,
+ success: ,
+ error: ,
+ };
+
+ const getStatusIcon = () => {
+ switch (status) {
+ case "loading":
+ return (
+
+ );
+ case "generating":
+ return ;
+ case "done":
+ return success ? (
+
+ ) : (
+
+ );
+ }
+ };
+
+ const getStatusText = () => {
+ switch (status) {
+ case "loading":
+ return "Preparing environment...";
+ case "generating":
+ return "Initializing environment...";
+ case "done":
+ return success ? "Environment ready" : "Initialization failed";
+ }
+ };
+
+ return (
+
+ {reasoningText && (
+
+
+ {showReasoning && (
+
+ {reasoningText}
+
+ )}
+
+ )}
+
+
+
+
+ {getStatusText()}
+
+ {getStatusIcon()}
+
+
+ {steps && (status === "generating" || status === "done") && (
+
+
+ {steps.map((step, index) => (
+ -
+ {stepStatusIcon[step.status]}
+
+ {step.name}
+
+ {step.error && (
+
+ ({step.error})
+
+ )}
+
+ ))}
+
+
+ )}
+
+ {summaryText && status === "done" && (
+
+
+ {showSummary && (
+
+ {summaryText}
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/apps/web/src/components/gen-ui/push-changes.tsx b/apps/web/src/components/gen-ui/push-changes.tsx
new file mode 100644
index 00000000..daf613e0
--- /dev/null
+++ b/apps/web/src/components/gen-ui/push-changes.tsx
@@ -0,0 +1,163 @@
+"use client";
+
+import "../app/globals.css";
+import { useState } from "react";
+import {
+ GitCommit,
+ Loader2,
+ CheckCircle,
+ XCircle,
+ ChevronDown,
+ ChevronUp,
+ MessageSquare,
+ FileText,
+} from "lucide-react";
+
+type PushChangesProps = {
+ status: "loading" | "generating" | "done";
+ success?: boolean;
+ gitStatus?: string;
+ commitMessage?: string;
+ errorMessage?: string;
+ reasoningText?: string;
+ summaryText?: string;
+};
+
+export function PushChanges({
+ status,
+ success,
+ gitStatus,
+ commitMessage,
+ errorMessage,
+ reasoningText,
+ summaryText,
+}: PushChangesProps) {
+ const [expanded, setExpanded] = useState(
+ Boolean(status === "done" && gitStatus),
+ );
+ const [showReasoning, setShowReasoning] = useState(false);
+ const [showSummary, setShowSummary] = useState(false);
+
+ const getStatusIcon = () => {
+ switch (status) {
+ case "loading":
+ return (
+
+ );
+ case "generating":
+ return ;
+ case "done":
+ return success ? (
+
+ ) : (
+
+ );
+ }
+ };
+
+ const getStatusText = () => {
+ switch (status) {
+ case "loading":
+ return "Preparing changes...";
+ case "generating":
+ return "Pushing changes...";
+ case "done":
+ return success
+ ? "Changes pushed successfully"
+ : "Failed to push changes";
+ }
+ };
+
+ return (
+
+ {reasoningText && (
+
+
+ {showReasoning && (
+
+ {reasoningText}
+
+ )}
+
+ )}
+
+
+
+
+ {getStatusText()}
+
+
+ {getStatusIcon()}
+ {gitStatus && status === "done" && (
+
+ )}
+
+
+
+ {expanded && gitStatus && status === "done" && (
+
+
+
+ Git Status
+
+
+ {gitStatus}
+
+
+
+ {commitMessage && success && (
+
+
+ Commit Message
+
+
+ {commitMessage}
+
+
+ )}
+
+ {errorMessage && !success && (
+
+
Error
+
+ {errorMessage}
+
+
+ )}
+
+ )}
+
+ {summaryText && status === "done" && (
+
+
+ {showSummary && (
+
+ {summaryText}
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/apps/web/src/components/gen-ui/replanning-step.tsx b/apps/web/src/components/gen-ui/replanning-step.tsx
new file mode 100644
index 00000000..70571b63
--- /dev/null
+++ b/apps/web/src/components/gen-ui/replanning-step.tsx
@@ -0,0 +1,96 @@
+"use client";
+
+import "../app/globals.css";
+import {
+ RefreshCw,
+ Loader2,
+ CheckCircle,
+ MessageSquare,
+ FileText,
+} from "lucide-react";
+import { useState } from "react";
+
+type ReplanningStepProps = {
+ status: "loading" | "generating" | "done";
+ reasoningText?: string;
+ summaryText?: string;
+};
+
+export function ReplanningStep({
+ status,
+ reasoningText,
+ summaryText,
+}: ReplanningStepProps) {
+ const [showReasoning, setShowReasoning] = useState(false);
+ const [showSummary, setShowSummary] = useState(false);
+
+ const getStatusIcon = () => {
+ switch (status) {
+ case "loading":
+ return (
+
+ );
+ case "generating":
+ return ;
+ case "done":
+ return ;
+ }
+ };
+
+ const getStatusText = () => {
+ switch (status) {
+ case "loading":
+ return "Preparing to update plan...";
+ case "generating":
+ return "Updating plan...";
+ case "done":
+ return "Plan updated";
+ }
+ };
+
+ return (
+
+ {reasoningText && (
+
+
+ {showReasoning && (
+
+ {reasoningText}
+
+ )}
+
+ )}
+
+
+
+
+ {getStatusText()}
+
+ {getStatusIcon()}
+
+
+ {summaryText && status === "done" && (
+
+
+ {showSummary && (
+
+ {summaryText}
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/apps/web/src/components/gen-ui/task-summary.tsx b/apps/web/src/components/gen-ui/task-summary.tsx
new file mode 100644
index 00000000..5fc2a57e
--- /dev/null
+++ b/apps/web/src/components/gen-ui/task-summary.tsx
@@ -0,0 +1,124 @@
+"use client";
+
+import "../app/globals.css";
+import { useState } from "react";
+import {
+ CheckCircle,
+ Loader2,
+ ChevronDown,
+ ChevronUp,
+ MessageSquare,
+ FileText,
+} from "lucide-react";
+
+type TaskSummaryProps = {
+ status: "loading" | "generating" | "done";
+ summary?: string;
+ reasoningText?: string;
+ summaryText?: string;
+};
+
+export function TaskSummary({
+ status,
+ summary,
+ reasoningText,
+ summaryText,
+}: TaskSummaryProps) {
+ const [expanded, setExpanded] = useState(false);
+ const [showReasoning, setShowReasoning] = useState(false);
+ const [showSummary, setShowSummary] = useState(false);
+
+ const getStatusIcon = () => {
+ switch (status) {
+ case "loading":
+ return (
+
+ );
+ case "generating":
+ return ;
+ case "done":
+ return ;
+ }
+ };
+
+ const getStatusText = () => {
+ switch (status) {
+ case "loading":
+ return "Preparing task summary...";
+ case "generating":
+ return "Generating summary...";
+ case "done":
+ return "Task completed";
+ }
+ };
+
+ return (
+
+ {reasoningText && (
+
+
+ {showReasoning && (
+
+ {reasoningText}
+
+ )}
+
+ )}
+
+
setExpanded(!expanded)
+ : undefined
+ }
+ >
+ {getStatusIcon()}
+
+ {getStatusText()}
+
+ {status === "done" && summary && (
+
+ )}
+
+
+ {expanded && summary && status === "done" && (
+
+
+ Task Summary
+
+
{summary}
+
+ )}
+
+ {summaryText && status === "done" && (
+
+
+ {showSummary && (
+
+ {summaryText}
+
+ )}
+
+ )}
+
+ );
+}