Improve OnboardingWizard: model picker and styled working directory input
- Step 2 now queries adapter models and shows a model selection dropdown - Working directory input uses the shared folder icon + border style from AgentConfigForm for visual consistency - Directory picker button falls back gracefully when showDirectoryPicker is unavailable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useDialog } from "../context/DialogContext";
|
import { useDialog } from "../context/DialogContext";
|
||||||
import { useCompany } from "../context/CompanyContext";
|
import { useCompany } from "../context/CompanyContext";
|
||||||
import { companiesApi } from "../api/companies";
|
import { companiesApi } from "../api/companies";
|
||||||
@@ -9,6 +9,11 @@ import { agentsApi } from "../api/agents";
|
|||||||
import { issuesApi } from "../api/issues";
|
import { issuesApi } from "../api/issues";
|
||||||
import { queryKeys } from "../lib/queryKeys";
|
import { queryKeys } from "../lib/queryKeys";
|
||||||
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { cn } from "../lib/utils";
|
import { cn } from "../lib/utils";
|
||||||
import {
|
import {
|
||||||
@@ -23,6 +28,8 @@ import {
|
|||||||
Sparkles,
|
Sparkles,
|
||||||
Check,
|
Check,
|
||||||
Loader2,
|
Loader2,
|
||||||
|
FolderOpen,
|
||||||
|
ChevronDown,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
type Step = 1 | 2 | 3 | 4;
|
type Step = 1 | 2 | 3 | 4;
|
||||||
@@ -37,6 +44,7 @@ export function OnboardingWizard() {
|
|||||||
const [step, setStep] = useState<Step>(1);
|
const [step, setStep] = useState<Step>(1);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [modelOpen, setModelOpen] = useState(false);
|
||||||
|
|
||||||
// Step 1
|
// Step 1
|
||||||
const [companyName, setCompanyName] = useState("");
|
const [companyName, setCompanyName] = useState("");
|
||||||
@@ -59,6 +67,14 @@ export function OnboardingWizard() {
|
|||||||
const [createdCompanyId, setCreatedCompanyId] = useState<string | null>(null);
|
const [createdCompanyId, setCreatedCompanyId] = useState<string | null>(null);
|
||||||
const [createdAgentId, setCreatedAgentId] = useState<string | null>(null);
|
const [createdAgentId, setCreatedAgentId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const { data: adapterModels } = useQuery({
|
||||||
|
queryKey: ["adapter-models", adapterType],
|
||||||
|
queryFn: () => agentsApi.adapterModels(adapterType),
|
||||||
|
enabled: onboardingOpen && step === 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedModel = (adapterModels ?? []).find((m) => m.id === model);
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
setStep(1);
|
setStep(1);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -377,23 +393,69 @@ export function OnboardingWizard() {
|
|||||||
<label className="text-xs text-muted-foreground mb-1 block">
|
<label className="text-xs text-muted-foreground mb-1 block">
|
||||||
Working directory
|
Working directory
|
||||||
</label>
|
</label>
|
||||||
<input
|
<div className="flex items-center gap-2 rounded-md border border-border px-2.5 py-1.5">
|
||||||
className="w-full rounded-md border border-border bg-transparent px-3 py-2 text-sm font-mono outline-none focus:ring-1 focus:ring-ring placeholder:text-muted-foreground/50"
|
<FolderOpen className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||||
placeholder="/path/to/project"
|
<input
|
||||||
value={cwd}
|
className="w-full bg-transparent outline-none text-sm font-mono placeholder:text-muted-foreground/50"
|
||||||
onChange={(e) => setCwd(e.target.value)}
|
placeholder="/path/to/project"
|
||||||
/>
|
value={cwd}
|
||||||
|
onChange={(e) => setCwd(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="inline-flex items-center rounded-md border border-border px-2 py-0.5 text-xs text-muted-foreground hover:bg-accent/50 transition-colors shrink-0"
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
// @ts-expect-error -- showDirectoryPicker is not in all TS lib defs yet
|
||||||
|
const handle = await window.showDirectoryPicker({ mode: "read" });
|
||||||
|
setCwd(handle.name);
|
||||||
|
} catch {
|
||||||
|
// user cancelled or API unsupported
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Choose
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 block">
|
<label className="text-xs text-muted-foreground mb-1 block">
|
||||||
Model
|
Model
|
||||||
</label>
|
</label>
|
||||||
<input
|
<Popover open={modelOpen} onOpenChange={setModelOpen}>
|
||||||
className="w-full rounded-md border border-border bg-transparent px-3 py-2 text-sm font-mono outline-none focus:ring-1 focus:ring-ring placeholder:text-muted-foreground/50"
|
<PopoverTrigger asChild>
|
||||||
placeholder="claude-sonnet-4-5-20250929"
|
<button className="inline-flex items-center gap-1.5 rounded-md border border-border px-2.5 py-1.5 text-sm hover:bg-accent/50 transition-colors w-full justify-between">
|
||||||
value={model}
|
<span className={cn(!model && "text-muted-foreground")}>
|
||||||
onChange={(e) => setModel(e.target.value)}
|
{selectedModel ? selectedModel.label : model || "Default"}
|
||||||
/>
|
</span>
|
||||||
|
<ChevronDown className="h-3 w-3 text-muted-foreground" />
|
||||||
|
</button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-1" align="start">
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-2 w-full px-2 py-1.5 text-sm rounded hover:bg-accent/50",
|
||||||
|
!model && "bg-accent"
|
||||||
|
)}
|
||||||
|
onClick={() => { setModel(""); setModelOpen(false); }}
|
||||||
|
>
|
||||||
|
Default
|
||||||
|
</button>
|
||||||
|
{(adapterModels ?? []).map((m) => (
|
||||||
|
<button
|
||||||
|
key={m.id}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center justify-between w-full px-2 py-1.5 text-sm rounded hover:bg-accent/50",
|
||||||
|
m.id === model && "bg-accent"
|
||||||
|
)}
|
||||||
|
onClick={() => { setModel(m.id); setModelOpen(false); }}
|
||||||
|
>
|
||||||
|
<span>{m.label}</span>
|
||||||
|
<span className="text-xs text-muted-foreground font-mono">{m.id}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -553,9 +615,6 @@ export function OnboardingWizard() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{step < 4 && "Cmd+Enter to continue"}
|
|
||||||
</span>
|
|
||||||
{step === 1 && (
|
{step === 1 && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
Reference in New Issue
Block a user