Squashed commit of the following:

commit 93babc04486e64ae55c106478d5c04a9ec891c1f
Author: Louis Knight-Webb <louis@bloop.ai>
Date:   Tue Jun 24 01:05:31 2025 +0100

    UX

commit 91c93187290e4e0882018c392dd744eba7cd2193
Author: Louis Knight-Webb <louis@bloop.ai>
Date:   Tue Jun 24 01:03:39 2025 +0100

    Update TaskDetailsPanel.tsx

    Follow up UI

commit b66cfbfa727eb7d69b2250102712d6169a3af3b1
Author: Louis Knight-Webb <louis@bloop.ai>
Date:   Tue Jun 24 00:58:21 2025 +0100

    Tweaks

commit aa2235c56413ffe88c4ec1bf7950012c019f9455
Author: Louis Knight-Webb <louis@bloop.ai>
Date:   Tue Jun 24 00:34:02 2025 +0100

    Add follow up endpoint

commit 1b536e33c956e39881d5ddfd169d229cfba99c20
Author: Louis Knight-Webb <louis@bloop.ai>
Date:   Tue Jun 24 00:12:55 2025 +0100

    Track executor type

commit 1c5d208f62fce2ed36e04384e139884e85dcb295
Author: Louis Knight-Webb <louis@bloop.ai>
Date:   Mon Jun 23 16:56:58 2025 +0100

    Add executor_session

commit 8e305953afb71d096079587df94cf5e63c4c6a04
Author: Louis Knight-Webb <louis@bloop.ai>
Date:   Mon Jun 23 16:49:07 2025 +0100

    Fix type issue

commit bc2dcf4fd4926ca2a42d71cd429de66fd1215208
Author: Louis Knight-Webb <louis@bloop.ai>
Date:   Mon Jun 23 16:03:27 2025 +0100

    Refactor
This commit is contained in:
Louis Knight-Webb
2025-06-24 01:05:55 +01:00
parent 9149db36a4
commit 1baa25089e
26 changed files with 1434 additions and 225 deletions

View File

@@ -12,11 +12,13 @@ import {
Edit,
Trash2,
StopCircle,
Send,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Chip } from "@/components/ui/chip";
import { Textarea } from "@/components/ui/textarea";
import { ExecutionOutputViewer } from "./ExecutionOutputViewer";
import { EditorSelectionDialog } from "./EditorSelectionDialog";
@@ -146,6 +148,8 @@ export function TaskDetailsPanel({
new Set()
);
const [showEditorDialog, setShowEditorDialog] = useState(false);
const [followUpMessage, setFollowUpMessage] = useState("");
const [isSendingFollowUp, setIsSendingFollowUp] = useState(false);
const { config } = useConfig();
// Available executors
@@ -185,6 +189,20 @@ export function TaskDetailsPanel({
);
}, [selectedAttempt, attemptActivities, isStopping]);
// Check if follow-up should be enabled
const canSendFollowUp = useMemo(() => {
if (!selectedAttempt || attemptActivities.length === 0 || isAttemptRunning || isSendingFollowUp) {
return false;
}
// Need at least one completed coding agent execution
const codingAgentActivities = attemptActivities.filter(
(activity) => activity.status === "executorcomplete"
);
return codingAgentActivities.length > 0;
}, [selectedAttempt, attemptActivities, isAttemptRunning, isSendingFollowUp]);
// Polling for updates when attempt is running
useEffect(() => {
if (!isAttemptRunning || !task) return;
@@ -410,6 +428,39 @@ export function TaskDetailsPanel({
});
};
const handleSendFollowUp = async () => {
if (!task || !selectedAttempt || !followUpMessage.trim()) return;
try {
setIsSendingFollowUp(true);
const response = await makeRequest(
`/api/projects/${projectId}/tasks/${task.id}/attempts/${selectedAttempt.id}/follow-up`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: followUpMessage.trim(),
}),
}
);
if (response.ok) {
// Clear the message
setFollowUpMessage("");
// Refresh activities to show the new follow-up execution
fetchAttemptActivities(selectedAttempt.id);
} else {
console.error("Failed to send follow-up:", await response.text());
}
} catch (err) {
console.error("Failed to send follow-up:", err);
} finally {
setIsSendingFollowUp(false);
}
};
if (!task) return null;
return (
@@ -730,13 +781,13 @@ export function TaskDetailsPanel({
] && (
<div className="mt-2">
<div
className={`transition-all duration-200 ${
expandedOutputs.has(
activity.execution_process_id
)
? ""
: "max-h-64 overflow-hidden"
}`}
className={`transition-all duration-200 ${
expandedOutputs.has(
activity.execution_process_id
)
? ""
: "max-h-64 overflow-hidden flex flex-col justify-end"
}`}
>
<ExecutionOutputViewer
executionProcess={
@@ -786,32 +837,51 @@ export function TaskDetailsPanel({
)}
</div>
{/* Footer */}
{/* <div className="border-t p-4">
<div className="space-y-2">
<Label className="text-sm font-medium">
Follow-up question
</Label>
<div className="flex gap-2">
<Textarea
placeholder="Ask a follow-up question about this task..."
value={followUpMessage}
onChange={(e) => setFollowUpMessage(e.target.value)}
className="flex-1 min-h-[60px] resize-none"
/>
<Button
onClick={handleSendFollowUp}
disabled={!followUpMessage.trim()}
className="self-end"
>
<Send className="h-4 w-4" />
</Button>
{/* Footer - Follow-up section */}
{selectedAttempt && (
<div className="border-t p-4">
<div className="space-y-2">
<Label className="text-sm font-medium">
Follow-up question
</Label>
<div className="flex gap-2">
<Textarea
placeholder="Ask a follow-up question about this task..."
value={followUpMessage}
onChange={(e) => setFollowUpMessage(e.target.value)}
onKeyDown={(e) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
e.preventDefault();
if (canSendFollowUp && followUpMessage.trim() && !isSendingFollowUp) {
handleSendFollowUp();
}
}
}}
className="flex-1 min-h-[60px] resize-none"
disabled={!canSendFollowUp}
/>
<Button
onClick={handleSendFollowUp}
disabled={!canSendFollowUp || !followUpMessage.trim() || isSendingFollowUp}
className="self-end"
>
{isSendingFollowUp ? (
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-current" />
) : (
<Send className="h-4 w-4" />
)}
</Button>
</div>
<p className="text-xs text-muted-foreground">
{!canSendFollowUp
? isAttemptRunning
? "Wait for current execution to complete before asking follow-up questions"
: "Complete at least one coding agent execution to enable follow-up questions"
: "Continue the conversation with the most recent executor session"}
</p>
</div>
<p className="text-xs text-muted-foreground">
Follow-up functionality coming soon
</p>
</div>
</div> */}
)}
</div>
</div>