Task attempt 661f4103-d3a8-4f67-a2b8-fd9c8633ac00 - Final changes
This commit is contained in:
@@ -684,300 +684,316 @@ export function TaskDetailsPanel({
|
||||
<div className={getTaskPanelClasses()}>
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Header */}
|
||||
<div className="p-6 border-b space-y-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="text-xl font-bold mb-2 line-clamp-2">
|
||||
{task.title}
|
||||
</h2>
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Chip dotColor={getTaskStatusDotColor(task.status)}>
|
||||
{statusLabels[task.status]}
|
||||
</Chip>
|
||||
<div className="border-b">
|
||||
{/* Title and Task Actions */}
|
||||
<div className="p-6 pb-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="text-xl font-bold mb-2 line-clamp-2">
|
||||
{task.title}
|
||||
</h2>
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Chip dotColor={getTaskStatusDotColor(task.status)}>
|
||||
{statusLabels[task.status]}
|
||||
</Chip>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{onEditTask && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onEditTask(task)}
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
{onDeleteTask && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onDeleteTask(task.id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 text-red-500" />
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="ghost" size="icon" onClick={onClose}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{onEditTask && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onEditTask(task)}
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
{onDeleteTask && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onDeleteTask(task.id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 text-red-500" />
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="ghost" size="icon" onClick={onClose}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div>
|
||||
<div className="p-3 bg-muted/30 rounded-md">
|
||||
{task.description ? (
|
||||
<div>
|
||||
<p
|
||||
className={`text-sm whitespace-pre-wrap ${
|
||||
!isDescriptionExpanded &&
|
||||
task.description.length > 200
|
||||
? "line-clamp-6"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{task.description}
|
||||
</p>
|
||||
{task.description.length > 200 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setIsDescriptionExpanded(!isDescriptionExpanded)
|
||||
}
|
||||
className="mt-2 p-0 h-auto text-xs text-muted-foreground hover:text-foreground"
|
||||
{/* Description */}
|
||||
<div className="mt-4">
|
||||
<div className="p-3 bg-muted/30 rounded-md">
|
||||
{task.description ? (
|
||||
<div>
|
||||
<p
|
||||
className={`text-sm whitespace-pre-wrap ${
|
||||
!isDescriptionExpanded &&
|
||||
task.description.length > 200
|
||||
? "line-clamp-6"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{isDescriptionExpanded ? (
|
||||
<>
|
||||
<ChevronUp className="h-3 w-3 mr-1" />
|
||||
Show less
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ChevronDown className="h-3 w-3 mr-1" />
|
||||
Show more
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground italic">
|
||||
No description provided
|
||||
</p>
|
||||
)}
|
||||
{task.description}
|
||||
</p>
|
||||
{task.description.length > 200 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setIsDescriptionExpanded(!isDescriptionExpanded)
|
||||
}
|
||||
className="mt-2 p-0 h-auto text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{isDescriptionExpanded ? (
|
||||
<>
|
||||
<ChevronUp className="h-3 w-3 mr-1" />
|
||||
Show less
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ChevronDown className="h-3 w-3 mr-1" />
|
||||
Show more
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground italic">
|
||||
No description provided
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Attempt Selection */}
|
||||
<div className="flex items-center gap-2 p-3 bg-muted/30 rounded-md">
|
||||
<div className="flex items-center gap-2 flex-1">
|
||||
{selectedAttempt && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-sm font-medium">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Current attempt:{" "}
|
||||
</span>
|
||||
{new Date(
|
||||
selectedAttempt.created_at
|
||||
).toLocaleDateString()}{" "}
|
||||
{new Date(
|
||||
selectedAttempt.created_at
|
||||
).toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-1">
|
||||
{taskAttempts.length > 1 && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
<History className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-64">
|
||||
{taskAttempts.map((attempt) => (
|
||||
<DropdownMenuItem
|
||||
key={attempt.id}
|
||||
onClick={() => handleAttemptChange(attempt.id)}
|
||||
className={
|
||||
selectedAttempt?.id === attempt.id
|
||||
? "bg-accent"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col w-full">
|
||||
<span className="font-medium text-sm">
|
||||
{new Date(
|
||||
attempt.created_at
|
||||
).toLocaleDateString()}{" "}
|
||||
{new Date(
|
||||
attempt.created_at
|
||||
).toLocaleTimeString()}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{attempt.executor || "executor"}
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
{/* Integrated Toolbar */}
|
||||
<div className="px-6 pb-4">
|
||||
<div className="flex items-center justify-between gap-4 p-3 bg-muted/20 rounded-lg border">
|
||||
{/* Current Attempt Info */}
|
||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||
{selectedAttempt ? (
|
||||
<>
|
||||
<div className="text-sm">
|
||||
<span className="font-medium">
|
||||
{new Date(selectedAttempt.created_at).toLocaleDateString()}{" "}
|
||||
{new Date(selectedAttempt.created_at).toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
</span>
|
||||
<span className="text-muted-foreground ml-2">
|
||||
({selectedAttempt.executor || "executor"})
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-4 w-px bg-border" />
|
||||
</>
|
||||
) : (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
No attempts yet
|
||||
</div>
|
||||
)}
|
||||
<div className="flex">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => createNewAttempt()}
|
||||
className="rounded-r-none border-r-0"
|
||||
>
|
||||
{selectedAttempt ? "Retry " : "Attempt "}
|
||||
with{" "}
|
||||
{
|
||||
availableExecutors.find(
|
||||
(e) => e.id === selectedExecutor
|
||||
)?.name
|
||||
}
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
</div>
|
||||
|
||||
{/* Action Button Groups */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Attempt Management Group */}
|
||||
<div className="flex items-center gap-1">
|
||||
{taskAttempts.length > 1 && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
<History className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-64">
|
||||
{taskAttempts.map((attempt) => (
|
||||
<DropdownMenuItem
|
||||
key={attempt.id}
|
||||
onClick={() => handleAttemptChange(attempt.id)}
|
||||
className={
|
||||
selectedAttempt?.id === attempt.id
|
||||
? "bg-accent"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col w-full">
|
||||
<span className="font-medium text-sm">
|
||||
{new Date(
|
||||
attempt.created_at
|
||||
).toLocaleDateString()}{" "}
|
||||
{new Date(
|
||||
attempt.created_at
|
||||
).toLocaleTimeString()}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{attempt.executor || "executor"}
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
<div className="flex">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => createNewAttempt()}
|
||||
className="rounded-r-none border-r-0"
|
||||
>
|
||||
{selectedAttempt ? "Retry" : "Start"}
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="rounded-l-none px-2"
|
||||
>
|
||||
<Settings2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{availableExecutors.map((executor) => (
|
||||
<DropdownMenuItem
|
||||
key={executor.id}
|
||||
onClick={() => setSelectedExecutor(executor.id)}
|
||||
className={
|
||||
selectedExecutor === executor.id
|
||||
? "bg-accent"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
{executor.name}
|
||||
{selectedExecutor === executor.id &&
|
||||
" (Default)"}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedAttempt && (
|
||||
<>
|
||||
<div className="h-4 w-px bg-border" />
|
||||
|
||||
{/* Execution Control Group */}
|
||||
<div className="flex items-center gap-1">
|
||||
{(isAttemptRunning || isStopping) && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={stopAllExecutions}
|
||||
disabled={isStopping}
|
||||
className="text-red-600 hover:text-red-700 hover:bg-red-50 disabled:opacity-50"
|
||||
>
|
||||
<StopCircle className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span
|
||||
className={
|
||||
!project?.dev_script ? "cursor-not-allowed" : ""
|
||||
}
|
||||
onMouseEnter={() => setIsHoveringDevServer(true)}
|
||||
onMouseLeave={() => setIsHoveringDevServer(false)}
|
||||
>
|
||||
<Button
|
||||
variant={
|
||||
runningDevServer ? "destructive" : "outline"
|
||||
}
|
||||
size="sm"
|
||||
onClick={
|
||||
runningDevServer
|
||||
? stopDevServer
|
||||
: startDevServer
|
||||
}
|
||||
disabled={
|
||||
isStartingDevServer || !project?.dev_script
|
||||
}
|
||||
>
|
||||
{runningDevServer ? (
|
||||
<StopCircle className="h-4 w-4" />
|
||||
) : (
|
||||
<Play className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
className={runningDevServer ? "max-w-2xl p-4" : ""}
|
||||
side="top"
|
||||
align="center"
|
||||
avoidCollisions={true}
|
||||
>
|
||||
{!project?.dev_script ? (
|
||||
<p>
|
||||
Configure a dev server command in project
|
||||
settings
|
||||
</p>
|
||||
) : runningDevServer && devServerDetails ? (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium">
|
||||
Dev Server Logs (Last 10 lines):
|
||||
</p>
|
||||
<pre className="text-xs bg-muted p-2 rounded max-h-64 overflow-y-auto whitespace-pre-wrap">
|
||||
{(() => {
|
||||
const stdout =
|
||||
devServerDetails.stdout || "";
|
||||
const stderr =
|
||||
devServerDetails.stderr || "";
|
||||
const allOutput =
|
||||
stdout + (stderr ? "\n" + stderr : "");
|
||||
const lines = allOutput
|
||||
.split("\n")
|
||||
.filter((line) => line.trim());
|
||||
const lastLines = lines.slice(-10);
|
||||
return lastLines.length > 0
|
||||
? lastLines.join("\n")
|
||||
: "No output yet...";
|
||||
})()}
|
||||
</pre>
|
||||
</div>
|
||||
) : runningDevServer ? (
|
||||
<p>Stop the running dev server</p>
|
||||
) : (
|
||||
<p>Start the dev server</p>
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
|
||||
<div className="h-4 w-px bg-border" />
|
||||
|
||||
{/* Code Actions Group */}
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="rounded-l-none px-2"
|
||||
onClick={() => openInEditor()}
|
||||
>
|
||||
<Settings2 className="h-4 w-4" />
|
||||
<Code className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{availableExecutors.map((executor) => (
|
||||
<DropdownMenuItem
|
||||
key={executor.id}
|
||||
onClick={() => setSelectedExecutor(executor.id)}
|
||||
className={
|
||||
selectedExecutor === executor.id
|
||||
? "bg-accent"
|
||||
: ""
|
||||
}
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link
|
||||
to={`/projects/${projectId}/tasks/${task.id}/attempts/${selectedAttempt.id}/compare`}
|
||||
>
|
||||
{executor.name}
|
||||
{selectedExecutor === executor.id &&
|
||||
" (Default)"}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<FileText className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedAttempt && (
|
||||
<div className="flex gap-1">
|
||||
{(isAttemptRunning || isStopping) && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={stopAllExecutions}
|
||||
disabled={isStopping}
|
||||
className="text-red-600 hover:text-red-700 hover:bg-red-50 disabled:opacity-50"
|
||||
>
|
||||
<StopCircle className="h-4 w-4 mr-1" />
|
||||
{isStopping ? "Stopping..." : "Stop"}
|
||||
</Button>
|
||||
)}
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span
|
||||
className={
|
||||
!project?.dev_script ? "cursor-not-allowed" : ""
|
||||
}
|
||||
onMouseEnter={() => setIsHoveringDevServer(true)}
|
||||
onMouseLeave={() => setIsHoveringDevServer(false)}
|
||||
>
|
||||
<Button
|
||||
variant={
|
||||
runningDevServer ? "destructive" : "outline"
|
||||
}
|
||||
size="sm"
|
||||
onClick={
|
||||
runningDevServer
|
||||
? stopDevServer
|
||||
: startDevServer
|
||||
}
|
||||
disabled={
|
||||
isStartingDevServer || !project?.dev_script
|
||||
}
|
||||
>
|
||||
{runningDevServer ? (
|
||||
<StopCircle className="h-4 w-4 mr-1" />
|
||||
) : (
|
||||
<Play className="h-4 w-4 mr-1" />
|
||||
)}
|
||||
{isStartingDevServer
|
||||
? runningDevServer
|
||||
? "Stopping..."
|
||||
: "Starting..."
|
||||
: runningDevServer
|
||||
? "Stop Dev Server"
|
||||
: "Start Dev Server"}
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
className={runningDevServer ? "max-w-2xl p-4" : ""}
|
||||
side="top"
|
||||
align="center"
|
||||
avoidCollisions={true}
|
||||
>
|
||||
{!project?.dev_script ? (
|
||||
<p>
|
||||
Configure a dev server command in project
|
||||
settings
|
||||
</p>
|
||||
) : runningDevServer && devServerDetails ? (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium">
|
||||
Dev Server Logs (Last 10 lines):
|
||||
</p>
|
||||
<pre className="text-xs bg-muted p-2 rounded max-h-64 overflow-y-auto whitespace-pre-wrap">
|
||||
{(() => {
|
||||
const stdout =
|
||||
devServerDetails.stdout || "";
|
||||
const stderr =
|
||||
devServerDetails.stderr || "";
|
||||
const allOutput =
|
||||
stdout + (stderr ? "\n" + stderr : "");
|
||||
const lines = allOutput
|
||||
.split("\n")
|
||||
.filter((line) => line.trim());
|
||||
const lastLines = lines.slice(-10);
|
||||
return lastLines.length > 0
|
||||
? lastLines.join("\n")
|
||||
: "No output yet...";
|
||||
})()}
|
||||
</pre>
|
||||
</div>
|
||||
) : null}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => openInEditor()}
|
||||
>
|
||||
<Code className="h-4 w-4 mr-1" />
|
||||
Editor
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link
|
||||
to={`/projects/${projectId}/tasks/${task.id}/attempts/${selectedAttempt.id}/compare`}
|
||||
>
|
||||
<FileText className="h-4 w-4 mr-1" />
|
||||
Changes
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user