Task attempt 015264cd-8abd-49bc-aa95-5672dbab0759 - Final changes
This commit is contained in:
@@ -279,10 +279,16 @@ pub async fn merge_task_attempt(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct OpenEditorRequest {
|
||||||
|
editor_type: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn open_task_attempt_in_editor(
|
pub async fn open_task_attempt_in_editor(
|
||||||
Path((project_id, task_id, attempt_id)): Path<(Uuid, Uuid, Uuid)>,
|
Path((project_id, task_id, attempt_id)): Path<(Uuid, Uuid, Uuid)>,
|
||||||
Extension(pool): Extension<SqlitePool>,
|
Extension(pool): Extension<SqlitePool>,
|
||||||
Extension(config): Extension<Arc<RwLock<crate::models::config::Config>>>,
|
Extension(config): Extension<Arc<RwLock<crate::models::config::Config>>>,
|
||||||
|
Json(payload): Json<Option<OpenEditorRequest>>,
|
||||||
) -> Result<ResponseJson<ApiResponse<()>>, StatusCode> {
|
) -> Result<ResponseJson<ApiResponse<()>>, StatusCode> {
|
||||||
// Verify task attempt exists and belongs to the correct task
|
// Verify task attempt exists and belongs to the correct task
|
||||||
match TaskAttempt::exists_for_task(&pool, attempt_id, task_id, project_id).await {
|
match TaskAttempt::exists_for_task(&pool, attempt_id, task_id, project_id).await {
|
||||||
@@ -304,10 +310,33 @@ pub async fn open_task_attempt_in_editor(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get editor command from config
|
// Get editor command from config or override
|
||||||
let editor_command = {
|
let editor_command = {
|
||||||
let config_guard = config.read().await;
|
let config_guard = config.read().await;
|
||||||
config_guard.editor.get_command()
|
if let Some(ref request) = payload {
|
||||||
|
if let Some(ref editor_type) = request.editor_type {
|
||||||
|
// Create a temporary editor config with the override
|
||||||
|
use crate::models::config::{EditorConfig, EditorType};
|
||||||
|
let override_editor_type = match editor_type.as_str() {
|
||||||
|
"vscode" => EditorType::VSCode,
|
||||||
|
"cursor" => EditorType::Cursor,
|
||||||
|
"windsurf" => EditorType::Windsurf,
|
||||||
|
"intellij" => EditorType::IntelliJ,
|
||||||
|
"zed" => EditorType::Zed,
|
||||||
|
"custom" => EditorType::Custom,
|
||||||
|
_ => config_guard.editor.editor_type.clone(),
|
||||||
|
};
|
||||||
|
let temp_config = EditorConfig {
|
||||||
|
editor_type: override_editor_type,
|
||||||
|
custom_command: config_guard.editor.custom_command.clone(),
|
||||||
|
};
|
||||||
|
temp_config.get_command()
|
||||||
|
} else {
|
||||||
|
config_guard.editor.get_command()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
config_guard.editor.get_command()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Open editor in the worktree directory
|
// Open editor in the worktree directory
|
||||||
@@ -338,11 +367,7 @@ pub async fn open_task_attempt_in_editor(
|
|||||||
attempt_id,
|
attempt_id,
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
Ok(ResponseJson(ApiResponse {
|
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
success: false,
|
|
||||||
data: None,
|
|
||||||
message: Some(format!("Failed to open editor: {}", e)),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
115
frontend/src/components/tasks/EditorSelectionDialog.tsx
Normal file
115
frontend/src/components/tasks/EditorSelectionDialog.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import type { EditorType } from "shared/types";
|
||||||
|
|
||||||
|
interface EditorSelectionDialogProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSelectEditor: (editorType: EditorType) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const editorOptions: { value: EditorType; label: string; description: string }[] = [
|
||||||
|
{
|
||||||
|
value: "vscode",
|
||||||
|
label: "Visual Studio Code",
|
||||||
|
description: "Microsoft's popular code editor",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "cursor",
|
||||||
|
label: "Cursor",
|
||||||
|
description: "AI-powered code editor",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "windsurf",
|
||||||
|
label: "Windsurf",
|
||||||
|
description: "Modern code editor",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "intellij",
|
||||||
|
label: "IntelliJ IDEA",
|
||||||
|
description: "JetBrains IDE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "zed",
|
||||||
|
label: "Zed",
|
||||||
|
description: "High-performance code editor",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "custom",
|
||||||
|
label: "Custom Editor",
|
||||||
|
description: "Use your configured custom editor",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function EditorSelectionDialog({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
onSelectEditor,
|
||||||
|
}: EditorSelectionDialogProps) {
|
||||||
|
const [selectedEditor, setSelectedEditor] = useState<EditorType>("vscode");
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
onSelectEditor(selectedEditor);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||||
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Choose Editor</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
The default editor failed to open. Please select an alternative
|
||||||
|
editor to open the task worktree.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="grid gap-4 py-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium">Editor</label>
|
||||||
|
<Select
|
||||||
|
value={selectedEditor}
|
||||||
|
onValueChange={(value) => setSelectedEditor(value as EditorType)}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{editorOptions.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="font-medium">{option.label}</span>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{option.description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleConfirm}>Open Editor</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Chip } from "@/components/ui/chip";
|
import { Chip } from "@/components/ui/chip";
|
||||||
import { ExecutionOutputViewer } from "./ExecutionOutputViewer";
|
import { ExecutionOutputViewer } from "./ExecutionOutputViewer";
|
||||||
|
import { EditorSelectionDialog } from "./EditorSelectionDialog";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -40,6 +41,7 @@ import type {
|
|||||||
ApiResponse,
|
ApiResponse,
|
||||||
TaskWithAttemptStatus,
|
TaskWithAttemptStatus,
|
||||||
ExecutionProcess,
|
ExecutionProcess,
|
||||||
|
EditorType,
|
||||||
} from "shared/types";
|
} from "shared/types";
|
||||||
|
|
||||||
interface TaskDetailsPanelProps {
|
interface TaskDetailsPanelProps {
|
||||||
@@ -143,6 +145,7 @@ export function TaskDetailsPanel({
|
|||||||
const [expandedOutputs, setExpandedOutputs] = useState<Set<string>>(
|
const [expandedOutputs, setExpandedOutputs] = useState<Set<string>>(
|
||||||
new Set()
|
new Set()
|
||||||
);
|
);
|
||||||
|
const [showEditorDialog, setShowEditorDialog] = useState(false);
|
||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
|
|
||||||
// Available executors
|
// Available executors
|
||||||
@@ -310,21 +313,30 @@ export function TaskDetailsPanel({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const openInEditor = async () => {
|
const openInEditor = async (editorType?: EditorType) => {
|
||||||
if (!task || !selectedAttempt) return;
|
if (!task || !selectedAttempt) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await makeRequest(
|
const response = await makeRequest(
|
||||||
`/api/projects/${projectId}/tasks/${task.id}/attempts/${selectedAttempt.id}/open-editor`,
|
`/api/projects/${projectId}/tasks/${task.id}/attempts/${selectedAttempt.id}/open-editor`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
|
body: JSON.stringify(editorType ? { editor_type: editorType } : null),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to open editor");
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to open editor:", err);
|
console.error("Failed to open editor:", err);
|
||||||
|
// Show editor selection dialog if editor failed to open
|
||||||
|
if (!editorType) {
|
||||||
|
setShowEditorDialog(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -616,7 +628,7 @@ export function TaskDetailsPanel({
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={openInEditor}
|
onClick={() => openInEditor()}
|
||||||
>
|
>
|
||||||
<Code className="h-4 w-4 mr-1" />
|
<Code className="h-4 w-4 mr-1" />
|
||||||
Editor
|
Editor
|
||||||
@@ -802,6 +814,13 @@ export function TaskDetailsPanel({
|
|||||||
</div> */}
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Editor Selection Dialog */}
|
||||||
|
<EditorSelectionDialog
|
||||||
|
isOpen={showEditorDialog}
|
||||||
|
onClose={() => setShowEditorDialog(false)}
|
||||||
|
onSelectEditor={(editorType) => openInEditor(editorType)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user