Task attempt 7002fc66-7954-44a7-a023-35faabd0e93f - Final changes
This commit is contained in:
@@ -449,6 +449,54 @@ pub async fn merge_task_attempt(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn open_task_attempt_in_editor(
|
||||||
|
Path((project_id, task_id, attempt_id)): Path<(Uuid, Uuid, Uuid)>,
|
||||||
|
Extension(pool): Extension<SqlitePool>,
|
||||||
|
) -> Result<ResponseJson<ApiResponse<()>>, StatusCode> {
|
||||||
|
// Verify task attempt exists and belongs to the correct task
|
||||||
|
match TaskAttempt::exists_for_task(&pool, attempt_id, task_id, project_id).await {
|
||||||
|
Ok(false) => return Err(StatusCode::NOT_FOUND),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to check task attempt existence: {}", e);
|
||||||
|
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
Ok(true) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the task attempt to access the worktree path
|
||||||
|
let attempt = match TaskAttempt::find_by_id(&pool, attempt_id).await {
|
||||||
|
Ok(Some(attempt)) => attempt,
|
||||||
|
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to fetch task attempt {}: {}", attempt_id, e);
|
||||||
|
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open VSCode in the worktree directory
|
||||||
|
match std::process::Command::new("code")
|
||||||
|
.arg(&attempt.worktree_path)
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
tracing::info!("Opened VSCode for task attempt {} at path: {}", attempt_id, attempt.worktree_path);
|
||||||
|
Ok(ResponseJson(ApiResponse {
|
||||||
|
success: true,
|
||||||
|
data: None,
|
||||||
|
message: Some("VSCode opened successfully".to_string()),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to open VSCode for attempt {}: {}", attempt_id, e);
|
||||||
|
Ok(ResponseJson(ApiResponse {
|
||||||
|
success: false,
|
||||||
|
data: None,
|
||||||
|
message: Some(format!("Failed to open VSCode: {}", e)),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn tasks_router() -> Router {
|
pub fn tasks_router() -> Router {
|
||||||
use axum::routing::{delete, post, put};
|
use axum::routing::{delete, post, put};
|
||||||
|
|
||||||
@@ -481,6 +529,10 @@ pub fn tasks_router() -> Router {
|
|||||||
"/projects/:project_id/tasks/:task_id/attempts/:attempt_id/merge",
|
"/projects/:project_id/tasks/:task_id/attempts/:attempt_id/merge",
|
||||||
post(merge_task_attempt),
|
post(merge_task_attempt),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/projects/:project_id/tasks/:task_id/attempts/:attempt_id/open-editor",
|
||||||
|
post(open_task_attempt_in_editor),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { ArrowLeft, FileText } from "lucide-react";
|
import { ArrowLeft, FileText, Code } from "lucide-react";
|
||||||
import { makeRequest } from "@/lib/api";
|
import { makeRequest } from "@/lib/api";
|
||||||
import { TaskFormDialog } from "@/components/tasks/TaskFormDialog";
|
import { TaskFormDialog } from "@/components/tasks/TaskFormDialog";
|
||||||
import type {
|
import type {
|
||||||
@@ -90,6 +90,7 @@ export function TaskDetailsPage() {
|
|||||||
const [selectedExecutor, setSelectedExecutor] = useState<string>("claude");
|
const [selectedExecutor, setSelectedExecutor] = useState<string>("claude");
|
||||||
const [creatingAttempt, setCreatingAttempt] = useState(false);
|
const [creatingAttempt, setCreatingAttempt] = useState(false);
|
||||||
const [stoppingAttempt, setStoppingAttempt] = useState(false);
|
const [stoppingAttempt, setStoppingAttempt] = useState(false);
|
||||||
|
const [openingEditor, setOpeningEditor] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const [isTaskDialogOpen, setIsTaskDialogOpen] = useState(false);
|
const [isTaskDialogOpen, setIsTaskDialogOpen] = useState(false);
|
||||||
@@ -354,6 +355,36 @@ export function TaskDetailsPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openTaskAttemptInEditor = async () => {
|
||||||
|
if (!task || !selectedAttempt || !projectId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setOpeningEditor(true);
|
||||||
|
const response = await makeRequest(
|
||||||
|
`/api/projects/${projectId}/tasks/${task.id}/attempts/${selectedAttempt.id}/open-editor`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: ApiResponse<null> = await response.json();
|
||||||
|
if (!result.success && result.message) {
|
||||||
|
setError(result.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setError("Failed to open editor");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError("Failed to open editor");
|
||||||
|
} finally {
|
||||||
|
setOpeningEditor(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleBackClick = () => {
|
const handleBackClick = () => {
|
||||||
navigate(`/projects/${projectId}/tasks`);
|
navigate(`/projects/${projectId}/tasks`);
|
||||||
};
|
};
|
||||||
@@ -618,19 +649,31 @@ export function TaskDetailsPage() {
|
|||||||
</Label>
|
</Label>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{selectedAttempt && (
|
{selectedAttempt && (
|
||||||
<Button
|
<>
|
||||||
onClick={() =>
|
<Button
|
||||||
navigate(
|
onClick={() =>
|
||||||
`/projects/${projectId}/tasks/${taskId}/attempts/${selectedAttempt.id}/compare`
|
navigate(
|
||||||
)
|
`/projects/${projectId}/tasks/${taskId}/attempts/${selectedAttempt.id}/compare`
|
||||||
}
|
)
|
||||||
size="sm"
|
}
|
||||||
variant="outline"
|
size="sm"
|
||||||
className="w-full"
|
variant="outline"
|
||||||
>
|
className="w-full"
|
||||||
<FileText className="mr-2 h-4 w-4" />
|
>
|
||||||
View Changes
|
<FileText className="mr-2 h-4 w-4" />
|
||||||
</Button>
|
View Changes
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={openTaskAttemptInEditor}
|
||||||
|
disabled={openingEditor}
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<Code className="mr-2 h-4 w-4" />
|
||||||
|
{openingEditor ? "Opening..." : "Open in Editor"}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{isAttemptRunning && (
|
{isAttemptRunning && (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
4134
pnpm-lock.yaml
generated
Normal file
4134
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user