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(
|
||||
Path((project_id, task_id, attempt_id)): Path<(Uuid, Uuid, Uuid)>,
|
||||
Extension(pool): Extension<SqlitePool>,
|
||||
Extension(config): Extension<Arc<RwLock<crate::models::config::Config>>>,
|
||||
Json(payload): Json<Option<OpenEditorRequest>>,
|
||||
) -> 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 {
|
||||
@@ -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 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
|
||||
@@ -338,11 +367,7 @@ pub async fn open_task_attempt_in_editor(
|
||||
attempt_id,
|
||||
e
|
||||
);
|
||||
Ok(ResponseJson(ApiResponse {
|
||||
success: false,
|
||||
data: None,
|
||||
message: Some(format!("Failed to open editor: {}", e)),
|
||||
}))
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 { Chip } from "@/components/ui/chip";
|
||||
import { ExecutionOutputViewer } from "./ExecutionOutputViewer";
|
||||
import { EditorSelectionDialog } from "./EditorSelectionDialog";
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -40,6 +41,7 @@ import type {
|
||||
ApiResponse,
|
||||
TaskWithAttemptStatus,
|
||||
ExecutionProcess,
|
||||
EditorType,
|
||||
} from "shared/types";
|
||||
|
||||
interface TaskDetailsPanelProps {
|
||||
@@ -143,6 +145,7 @@ export function TaskDetailsPanel({
|
||||
const [expandedOutputs, setExpandedOutputs] = useState<Set<string>>(
|
||||
new Set()
|
||||
);
|
||||
const [showEditorDialog, setShowEditorDialog] = useState(false);
|
||||
const { config } = useConfig();
|
||||
|
||||
// Available executors
|
||||
@@ -310,21 +313,30 @@ export function TaskDetailsPanel({
|
||||
}
|
||||
};
|
||||
|
||||
const openInEditor = async () => {
|
||||
const openInEditor = async (editorType?: EditorType) => {
|
||||
if (!task || !selectedAttempt) return;
|
||||
|
||||
try {
|
||||
await makeRequest(
|
||||
const response = await makeRequest(
|
||||
`/api/projects/${projectId}/tasks/${task.id}/attempts/${selectedAttempt.id}/open-editor`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(editorType ? { editor_type: editorType } : null),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to open editor");
|
||||
}
|
||||
} catch (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
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={openInEditor}
|
||||
onClick={() => openInEditor()}
|
||||
>
|
||||
<Code className="h-4 w-4 mr-1" />
|
||||
Editor
|
||||
@@ -802,6 +814,13 @@ export function TaskDetailsPanel({
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Editor Selection Dialog */}
|
||||
<EditorSelectionDialog
|
||||
isOpen={showEditorDialog}
|
||||
onClose={() => setShowEditorDialog(false)}
|
||||
onSelectEditor={(editorType) => openInEditor(editorType)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user