Task attempt ec641adb-5bf7-43ae-91d5-39e9dd4caa3a - Final changes

This commit is contained in:
Louis Knight-Webb
2025-06-22 22:58:01 +01:00
parent 8f09d59210
commit 329069049f
6 changed files with 251 additions and 12 deletions

View File

@@ -6,18 +6,23 @@ import { ProjectTasks } from "@/pages/project-tasks";
import { TaskAttemptComparePage } from "@/pages/task-attempt-compare";
import { Settings } from "@/pages/Settings";
import { DisclaimerDialog } from "@/components/DisclaimerDialog";
import { OnboardingDialog } from "@/components/OnboardingDialog";
import { ConfigProvider, useConfig } from "@/components/config-provider";
import { ThemeProvider } from "@/components/theme-provider";
import type { Config, ApiResponse } from "shared/types";
import type { Config, ApiResponse, ExecutorConfig, EditorType } from "shared/types";
function AppContent() {
const { config, updateConfig, loading } = useConfig();
const [showDisclaimer, setShowDisclaimer] = useState(false);
const [showOnboarding, setShowOnboarding] = useState(false);
const showNavbar = true;
useEffect(() => {
if (config) {
setShowDisclaimer(!config.disclaimer_acknowledged);
if (config.disclaimer_acknowledged) {
setShowOnboarding(!config.onboarding_acknowledged);
}
}
}, [config]);
@@ -39,6 +44,38 @@ function AppContent() {
if (data.success) {
setShowDisclaimer(false);
setShowOnboarding(!config.onboarding_acknowledged);
}
} catch (err) {
console.error("Error saving config:", err);
}
};
const handleOnboardingComplete = async (onboardingConfig: { executor: ExecutorConfig; editor: { editor_type: EditorType; custom_command: string | null } }) => {
if (!config) return;
const updatedConfig = {
...config,
onboarding_acknowledged: true,
executor: onboardingConfig.executor,
editor: onboardingConfig.editor,
};
updateConfig(updatedConfig);
try {
const response = await fetch("/api/config", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(updatedConfig),
});
const data: ApiResponse<Config> = await response.json();
if (data.success) {
setShowOnboarding(false);
}
} catch (err) {
console.error("Error saving config:", err);
@@ -63,6 +100,10 @@ function AppContent() {
open={showDisclaimer}
onAccept={handleDisclaimerAccept}
/>
<OnboardingDialog
open={showOnboarding}
onComplete={handleOnboardingComplete}
/>
{showNavbar && <Navbar />}
<div className="flex-1 overflow-y-scroll">
<Routes>

View File

@@ -0,0 +1,145 @@
import { useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Sparkles, Code } from "lucide-react";
import type { EditorType, ExecutorConfig } from "shared/types";
import { EXECUTOR_TYPES, EDITOR_TYPES, EXECUTOR_LABELS, EDITOR_LABELS } from "shared/types";
interface OnboardingDialogProps {
open: boolean;
onComplete: (config: { executor: ExecutorConfig; editor: { editor_type: EditorType; custom_command: string | null } }) => void;
}
export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) {
const [executor, setExecutor] = useState<ExecutorConfig>({ type: "claude" });
const [editorType, setEditorType] = useState<EditorType>("vscode");
const [customCommand, setCustomCommand] = useState<string>("");
const handleComplete = () => {
onComplete({
executor,
editor: {
editor_type: editorType,
custom_command: editorType === "custom" ? customCommand || null : null,
},
});
};
const isValid = editorType !== "custom" || (editorType === "custom" && customCommand.trim() !== "");
return (
<Dialog open={open} onOpenChange={() => {}}>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<div className="flex items-center gap-3">
<Sparkles className="h-6 w-6 text-primary" />
<DialogTitle>Welcome to Mission Control</DialogTitle>
</div>
<DialogDescription className="text-left pt-2">
Let's set up your coding preferences. You can always change these later in Settings.
</DialogDescription>
</DialogHeader>
<div className="space-y-6 py-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Sparkles className="h-4 w-4" />
Choose Your Coding Agent
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="executor">Default Executor</Label>
<Select
value={executor.type}
onValueChange={(value: "echo" | "claude" | "amp") => setExecutor({ type: value })}
>
<SelectTrigger id="executor">
<SelectValue placeholder="Select your preferred coding agent" />
</SelectTrigger>
<SelectContent>
{EXECUTOR_TYPES.map((type) => (
<SelectItem key={type} value={type}>
{EXECUTOR_LABELS[type]}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-sm text-muted-foreground">
{executor.type === "claude" && "Claude will handle your coding tasks with advanced reasoning."}
{executor.type === "amp" && "Amp provides powerful code generation and debugging capabilities."}
{executor.type === "echo" && "Echo is a simple testing executor that repeats your commands."}
</p>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Code className="h-4 w-4" />
Choose Your Code Editor
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="editor">Preferred Editor</Label>
<Select
value={editorType}
onValueChange={(value: EditorType) => setEditorType(value)}
>
<SelectTrigger id="editor">
<SelectValue placeholder="Select your preferred editor" />
</SelectTrigger>
<SelectContent>
{EDITOR_TYPES.map((type) => (
<SelectItem key={type} value={type}>
{EDITOR_LABELS[type]}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-sm text-muted-foreground">
This editor will be used to open task attempts and project files.
</p>
</div>
{editorType === "custom" && (
<div className="space-y-2">
<Label htmlFor="custom-command">Custom Command</Label>
<Input
id="custom-command"
placeholder="e.g., code, subl, vim"
value={customCommand}
onChange={(e) => setCustomCommand(e.target.value)}
/>
<p className="text-sm text-muted-foreground">
Enter the command to run your custom editor. Use spaces for arguments (e.g., "code --wait").
</p>
</div>
)}
</CardContent>
</Card>
</div>
<DialogFooter>
<Button onClick={handleComplete} disabled={!isValid} className="w-full">
Complete Setup
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -8,6 +8,7 @@ import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Loader2 } from "lucide-react";
import type { ThemeMode, EditorType } from "shared/types";
import { EXECUTOR_TYPES, EDITOR_TYPES, EXECUTOR_LABELS, EDITOR_LABELS } from "shared/types";
import { useTheme } from "@/components/theme-provider";
import { useConfig } from "@/components/config-provider";
@@ -51,6 +52,12 @@ export function Settings() {
updateConfig({ disclaimer_acknowledged: false });
};
const resetOnboarding = async () => {
if (!config) return;
updateConfig({ onboarding_acknowledged: false });
};
if (loading) {
return (
<div className="container mx-auto px-4 py-8">
@@ -145,9 +152,11 @@ export function Settings() {
<SelectValue placeholder="Select executor" />
</SelectTrigger>
<SelectContent>
<SelectItem value="claude">Claude</SelectItem>
<SelectItem value="amp">Amp</SelectItem>
<SelectItem value="echo">Echo</SelectItem>
{EXECUTOR_TYPES.map((type) => (
<SelectItem key={type} value={type}>
{EXECUTOR_LABELS[type]}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-sm text-muted-foreground">
@@ -181,12 +190,11 @@ export function Settings() {
<SelectValue placeholder="Select editor" />
</SelectTrigger>
<SelectContent>
<SelectItem value="vscode">VS Code</SelectItem>
<SelectItem value="cursor">Cursor</SelectItem>
<SelectItem value="windsurf">Windsurf</SelectItem>
<SelectItem value="intellij">IntelliJ IDEA</SelectItem>
<SelectItem value="zed">Zed</SelectItem>
<SelectItem value="custom">Custom Command</SelectItem>
{EDITOR_TYPES.map((type) => (
<SelectItem key={type} value={type}>
{EDITOR_LABELS[type]}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-sm text-muted-foreground">
@@ -260,7 +268,7 @@ export function Settings() {
Manage safety warnings and acknowledgments.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<CardContent className="space-y-6">
<div className="space-y-2">
<div className="flex items-center justify-between">
<div>
@@ -284,6 +292,29 @@ export function Settings() {
Resetting the disclaimer will require you to acknowledge the safety warning again on next app start.
</p>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<div>
<Label>Onboarding Status</Label>
<p className="text-sm text-muted-foreground">
{config.onboarding_acknowledged
? "You have completed the onboarding process."
: "The onboarding process has not been completed."}
</p>
</div>
<Button
onClick={resetOnboarding}
variant="outline"
size="sm"
disabled={!config.onboarding_acknowledged}
>
Reset Onboarding
</Button>
</div>
<p className="text-xs text-muted-foreground">
Resetting the onboarding will show the setup screen again on next app start.
</p>
</div>
</CardContent>
</Card>
</div>

View File

@@ -7,6 +7,7 @@ export default defineConfig({
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
"shared": path.resolve(__dirname, "../shared"),
},
},
server: {