fix(electrobun): wizard 7 fixes — validation, GitLab, SSHFS, icons, model dropdown, keyboard nav
- Git Platform: validates repo via git.probe before enabling Next, supports GitHub + GitLab + any git URL - Template dir configurable in Advanced Settings (template_dir key) - SSHFS: checks sshfs availability, mountpoint selector when enabled - CustomDropdown: flip-up when insufficient space below - 50 Lucide icons (was 24) with categories (AI, Data, DevOps, Security, Media, Comms) - Model: CustomDropdown from live API, max_tokens as slider, effort only with adaptive thinking - Keyboard: Escape closes wizard, Tab navigation with :focus-visible rings, source cards navigable
This commit is contained in:
parent
41b8d46a19
commit
021feba3ed
11 changed files with 368 additions and 614 deletions
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { appRpc } from './rpc.ts';
|
||||
import { sanitize, sanitizePath, sanitizeUrl, isValidGitUrl, isValidGithubRepo } from './sanitize.ts';
|
||||
import WizardStep1 from './WizardStep1.svelte';
|
||||
|
|
@ -11,123 +12,87 @@
|
|||
useWorktrees?: boolean; shell?: string; icon?: string; color?: string;
|
||||
modelConfig?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
onCreated: (project: ProjectResult) => void;
|
||||
groupId: string;
|
||||
groups: Array<{ id: string; name: string }>;
|
||||
existingNames: string[];
|
||||
}
|
||||
|
||||
interface Props { onClose: () => void; onCreated: (project: ProjectResult) => void; groupId: string; groups: Array<{ id: string; name: string }>; existingNames: string[]; }
|
||||
let { onClose, onCreated, groupId, groups, existingNames }: Props = $props();
|
||||
|
||||
type SourceType = 'local' | 'git-clone' | 'github' | 'template' | 'remote';
|
||||
type AuthMethod = 'password' | 'key' | 'agent' | 'config';
|
||||
let step = $state(1);
|
||||
let sourceType = $state<SourceType>('local');
|
||||
type ProviderInfo = { id: string; available: boolean; hasApiKey: boolean; hasCli: boolean; cliPath: string | null; version: string | null };
|
||||
let step = $state(1); let sourceType = $state<SourceType>('local');
|
||||
let localPath = $state(''); let repoUrl = $state(''); let cloneTarget = $state('');
|
||||
let githubRepo = $state(''); let selectedTemplate = $state(''); let templateTargetDir = $state('~/projects');
|
||||
let githubRepo = $state(''); let selectedTemplate = $state(''); let templateTargetDir = $state('~/projects'); let templateOriginDir = $state('');
|
||||
let remoteHost = $state(''); let remoteUser = $state(''); let remotePath = $state('');
|
||||
let remoteAuthMethod = $state<AuthMethod>('agent'); let remotePassword = $state(''); let remoteKeyPath = $state('~/.ssh/id_ed25519');
|
||||
let remoteSshfs = $state(false); let isGitRepo = $state(false); let gitBranch = $state('');
|
||||
let pathValid = $state<'idle' | 'checking' | 'valid' | 'invalid' | 'not-dir'>('idle');
|
||||
let gitProbeStatus = $state<'idle' | 'probing' | 'ok' | 'error'>('idle');
|
||||
let gitProbeBranches = $state<string[]>([]); let githubLoading = $state(false); let cloning = $state(false);
|
||||
let remoteSshfs = $state(false); let remoteSshfsMountpoint = $state('');
|
||||
let isGitRepo = $state(false); let gitBranch = $state('');
|
||||
let pathValid = $state<'idle'|'checking'|'valid'|'invalid'|'not-dir'>('idle');
|
||||
let gitProbeStatus = $state<'idle'|'probing'|'ok'|'error'>('idle'); let gitProbeBranches = $state<string[]>([]);
|
||||
let githubLoading = $state(false); let cloning = $state(false);
|
||||
let githubInfo = $state<{ stars: number; description: string; defaultBranch: string } | null>(null);
|
||||
let githubProbeStatus = $state<'idle'|'probing'|'ok'|'error'>('idle');
|
||||
let templates = $state<Array<{ id: string; name: string; description: string; icon: string }>>([]);
|
||||
let projectName = $state(''); let nameError = $state(''); let selectedBranch = $state('');
|
||||
let branches = $state<string[]>([]); let useWorktrees = $state(false); let selectedGroupId = $state(groupId);
|
||||
let projectIcon = $state('Terminal'); let projectColor = $state('var(--ctp-blue)'); let shellChoice = $state('bash');
|
||||
let provider = $state<string>('claude'); let model = $state(''); let permissionMode = $state('default');
|
||||
let systemPrompt = $state(''); let autoStart = $state(false); let modelConfig = $state<Record<string, unknown>>({});
|
||||
type ProviderInfo = { id: string; available: boolean; hasApiKey: boolean; hasCli: boolean; cliPath: string | null; version: string | null };
|
||||
let detectedProviders = $state<ProviderInfo[]>([]); let providerModels = $state<Array<{ id: string; name: string; provider: string }>>([]); let modelsLoading = $state(false);
|
||||
let pathTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let probeTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let githubTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
$effect(() => {
|
||||
if (sourceType === 'template' && templates.length === 0) {
|
||||
appRpc.request['project.templates']({}).then(r => {
|
||||
if (r?.templates) templates = r.templates;
|
||||
}).catch(console.error);
|
||||
}
|
||||
let step1Ref = $state<WizardStep1 | null>(null);
|
||||
let step2Ref = $state<WizardStep2 | null>(null);
|
||||
|
||||
onMount(async () => {
|
||||
try { const r = await appRpc.request['settings.get']({ key: 'template_dir' }); if (r?.value) templateOriginDir = r.value; } catch { /* default */ }
|
||||
});
|
||||
|
||||
$effect(() => { if (sourceType === 'template' && templates.length === 0) { appRpc.request['project.templates']({ templateDir: templateOriginDir || undefined }).then(r => { if (r?.templates) templates = r.templates; }).catch(console.error); } });
|
||||
$effect(() => {
|
||||
if (sourceType === 'local') {
|
||||
if (pathTimer) clearTimeout(pathTimer);
|
||||
if (!localPath.trim()) { pathValid = 'idle'; isGitRepo = false; return; }
|
||||
pathValid = 'checking';
|
||||
pathTimer = setTimeout(async () => {
|
||||
try {
|
||||
const result = await appRpc.request['files.statEx']({ path: localPath });
|
||||
if (!result?.exists) { pathValid = 'invalid'; isGitRepo = false; }
|
||||
else if (!result.isDirectory) { pathValid = 'not-dir'; isGitRepo = false; }
|
||||
else { pathValid = 'valid'; isGitRepo = result.isGitRepo; gitBranch = result.gitBranch ?? ''; if (!projectName) { const parts = localPath.replace(/\/+$/, '').split('/'); projectName = parts[parts.length - 1] || ''; } }
|
||||
} catch { pathValid = 'invalid'; isGitRepo = false; }
|
||||
try { const result = await appRpc.request['files.statEx']({ path: localPath }); if (!result?.exists) { pathValid = 'invalid'; isGitRepo = false; } else if (!result.isDirectory) { pathValid = 'not-dir'; isGitRepo = false; } else { pathValid = 'valid'; isGitRepo = result.isGitRepo; gitBranch = result.gitBranch ?? ''; if (!projectName) { const parts = localPath.replace(/\/+$/, '').split('/'); projectName = parts[parts.length - 1] || ''; } } } catch { pathValid = 'invalid'; isGitRepo = false; }
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
$effect(() => {
|
||||
if (sourceType === 'git-clone' && repoUrl.trim()) {
|
||||
if (probeTimer) clearTimeout(probeTimer);
|
||||
gitProbeStatus = 'probing';
|
||||
if (probeTimer) clearTimeout(probeTimer); gitProbeStatus = 'probing';
|
||||
probeTimer = setTimeout(async () => {
|
||||
if (!isValidGitUrl(repoUrl)) { gitProbeStatus = 'error'; return; }
|
||||
try {
|
||||
const r = await appRpc.request['git.probe']({ url: repoUrl.trim() });
|
||||
if (r?.ok) { gitProbeStatus = 'ok'; gitProbeBranches = r.branches; } else { gitProbeStatus = 'error'; }
|
||||
} catch { gitProbeStatus = 'error'; }
|
||||
try { const r = await appRpc.request['git.probe']({ url: repoUrl.trim() }); if (r?.ok) { gitProbeStatus = 'ok'; gitProbeBranches = r.branches; } else { gitProbeStatus = 'error'; } } catch { gitProbeStatus = 'error'; }
|
||||
}, 600);
|
||||
} else if (sourceType === 'git-clone') { gitProbeStatus = 'idle'; }
|
||||
});
|
||||
$effect(() => {
|
||||
if (sourceType === 'github' && githubRepo.trim()) {
|
||||
if (githubTimer) clearTimeout(githubTimer);
|
||||
githubInfo = null;
|
||||
if (!isValidGithubRepo(githubRepo)) return;
|
||||
githubLoading = true;
|
||||
if (githubTimer) clearTimeout(githubTimer); githubInfo = null; githubProbeStatus = 'idle';
|
||||
const input = githubRepo.trim();
|
||||
let probeUrl: string;
|
||||
if (input.startsWith('http://') || input.startsWith('https://') || input.startsWith('git@')) { probeUrl = input; }
|
||||
else if (isValidGithubRepo(input)) { probeUrl = `https://github.com/${input}.git`; } else { return; }
|
||||
githubLoading = true; githubProbeStatus = 'probing';
|
||||
githubTimer = setTimeout(async () => {
|
||||
try {
|
||||
const res = await fetch(`https://api.github.com/repos/${githubRepo.trim()}`, { signal: AbortSignal.timeout(5000) });
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
githubInfo = { stars: data.stargazers_count ?? 0, description: data.description ?? '', defaultBranch: data.default_branch ?? 'main' };
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
githubLoading = false;
|
||||
const r = await appRpc.request['git.probe']({ url: probeUrl });
|
||||
if (r?.ok) { githubProbeStatus = 'ok'; if (isValidGithubRepo(input)) { try { const res = await fetch(`https://api.github.com/repos/${input}`, { signal: AbortSignal.timeout(5000) }); if (res.ok) { const d = await res.json(); githubInfo = { stars: d.stargazers_count ?? 0, description: d.description ?? '', defaultBranch: d.default_branch ?? 'main' }; } } catch { /* optional */ } } }
|
||||
else { githubProbeStatus = 'error'; }
|
||||
} catch { githubProbeStatus = 'error'; } githubLoading = false;
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
$effect(() => {
|
||||
const trimmed = projectName.trim().toLowerCase();
|
||||
if (!trimmed) { nameError = ''; return; }
|
||||
const lowerNames = existingNames.map(n => n.toLowerCase());
|
||||
nameError = lowerNames.includes(trimmed) ? 'A project with this name already exists' : '';
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (step === 3 && detectedProviders.length === 0) {
|
||||
appRpc.request['provider.scan']({}).then(r => {
|
||||
if (r?.providers) { detectedProviders = r.providers; const first = r.providers.find(p => p.available); if (first) provider = first.id; }
|
||||
}).catch(console.error);
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (step === 3 && provider) {
|
||||
providerModels = []; modelsLoading = true;
|
||||
appRpc.request['provider.models']({ provider }).then(r => {
|
||||
if (r?.models) providerModels = r.models;
|
||||
}).catch(console.error).finally(() => { modelsLoading = false; });
|
||||
}
|
||||
});
|
||||
$effect(() => { const t = projectName.trim().toLowerCase(); if (!t) { nameError = ''; return; } nameError = existingNames.map(n => n.toLowerCase()).includes(t) ? 'A project with this name already exists' : ''; });
|
||||
$effect(() => { if (step === 3 && detectedProviders.length === 0) { appRpc.request['provider.scan']({}).then(r => { if (r?.providers) { detectedProviders = r.providers; const first = r.providers.find(p => p.available); if (first) provider = first.id; } }).catch(console.error); } });
|
||||
$effect(() => { if (step === 3 && provider) { providerModels = []; modelsLoading = true; appRpc.request['provider.models']({ provider }).then(r => { if (r?.models) providerModels = r.models; }).catch(console.error).finally(() => { modelsLoading = false; }); } });
|
||||
|
||||
let step1Valid = $derived(() => {
|
||||
switch (sourceType) {
|
||||
case 'local': return pathValid === 'valid';
|
||||
case 'git-clone': return isValidGitUrl(repoUrl) && !!sanitizePath(cloneTarget);
|
||||
case 'github': return isValidGithubRepo(githubRepo);
|
||||
case 'git-clone': return isValidGitUrl(repoUrl) && !!sanitizePath(cloneTarget) && gitProbeStatus === 'ok';
|
||||
case 'github': return githubProbeStatus === 'ok';
|
||||
case 'template': return selectedTemplate !== '' && !!sanitizePath(templateTargetDir);
|
||||
case 'remote': return !!sanitize(remoteHost) && !!sanitize(remoteUser) && !!sanitize(remotePath);
|
||||
default: return false;
|
||||
|
|
@ -135,139 +100,73 @@
|
|||
});
|
||||
|
||||
async function goToStep2() {
|
||||
if (sourceType === 'git-clone') {
|
||||
const url = sanitizeUrl(repoUrl); if (!url) return;
|
||||
const target = sanitizePath(cloneTarget); if (!target) return;
|
||||
cloning = true;
|
||||
try {
|
||||
const result = await appRpc.request['git.clone']({ url, target });
|
||||
if (!result?.ok) { cloning = false; return; }
|
||||
localPath = target; isGitRepo = true;
|
||||
} catch { cloning = false; return; }
|
||||
cloning = false;
|
||||
} else if (sourceType === 'github') {
|
||||
const url = `https://github.com/${githubRepo.trim()}.git`;
|
||||
const target = `~/projects/${githubRepo.trim().split('/')[1] || 'project'}`;
|
||||
cloning = true;
|
||||
try {
|
||||
const result = await appRpc.request['git.clone']({ url, target });
|
||||
if (!result?.ok) { cloning = false; return; }
|
||||
localPath = target; isGitRepo = true;
|
||||
} catch { cloning = false; return; }
|
||||
cloning = false;
|
||||
} else if (sourceType === 'template') {
|
||||
const target = sanitizePath(templateTargetDir); if (!target) return;
|
||||
const name = sanitize(projectName) || selectedTemplate;
|
||||
cloning = true;
|
||||
try {
|
||||
const result = await appRpc.request['project.createFromTemplate']({ templateId: selectedTemplate, targetDir: target, projectName: name });
|
||||
if (!result?.ok) { cloning = false; return; }
|
||||
localPath = result.path; if (!projectName) projectName = name;
|
||||
} catch { cloning = false; return; }
|
||||
cloning = false;
|
||||
}
|
||||
if (sourceType === 'git-clone') { const url = sanitizeUrl(repoUrl); const target = sanitizePath(cloneTarget); if (!url || !target) return; cloning = true; try { const r = await appRpc.request['git.clone']({ url, target }); if (!r?.ok) { cloning = false; return; } localPath = target; isGitRepo = true; } catch { cloning = false; return; } cloning = false; }
|
||||
else if (sourceType === 'github') { const input = githubRepo.trim(); const url = (input.startsWith('http') || input.startsWith('git@')) ? input : `https://github.com/${input}.git`; const name = input.includes('/') ? input.split('/').pop()?.replace(/\.git$/, '') || 'project' : 'project'; const target = `~/projects/${name}`; cloning = true; try { const r = await appRpc.request['git.clone']({ url, target }); if (!r?.ok) { cloning = false; return; } localPath = target; isGitRepo = true; } catch { cloning = false; return; } cloning = false; }
|
||||
else if (sourceType === 'template') { const target = sanitizePath(templateTargetDir); if (!target) return; const name = sanitize(projectName) || selectedTemplate; cloning = true; try { const r = await appRpc.request['project.createFromTemplate']({ templateId: selectedTemplate, targetDir: target, projectName: name }); if (!r?.ok) { cloning = false; return; } localPath = r.path; if (!projectName) projectName = name; } catch { cloning = false; return; } cloning = false; }
|
||||
if (!projectName && localPath) { const parts = localPath.replace(/\/+$/, '').split('/'); projectName = parts[parts.length - 1] || ''; }
|
||||
if (isGitRepo && localPath) {
|
||||
try { const r = await appRpc.request['git.branches']({ path: localPath }); if (r?.branches) { branches = r.branches; selectedBranch = r.current || ''; } } catch { /* ignore */ }
|
||||
}
|
||||
step = 2;
|
||||
if (isGitRepo && localPath) { try { const r = await appRpc.request['git.branches']({ path: localPath }); if (r?.branches) { branches = r.branches; selectedBranch = r.current || ''; } } catch { /* ignore */ } }
|
||||
step = 2; requestAnimationFrame(() => step2Ref?.focusFirst());
|
||||
}
|
||||
|
||||
function goToStep3() { if (nameError) return; step = 3; }
|
||||
function goBack() { step = Math.max(1, step - 1); }
|
||||
|
||||
async function createProject() {
|
||||
const cwd = sourceType === 'remote' ? `ssh://${sanitize(remoteUser)}@${sanitize(remoteHost)}:${sanitize(remotePath)}` : (sanitizePath(localPath) || localPath.trim());
|
||||
const project: ProjectResult = {
|
||||
id: `p-${Date.now()}`, name: sanitize(projectName) || 'Untitled', cwd,
|
||||
provider: provider as string, model: model || undefined, systemPrompt: systemPrompt || undefined,
|
||||
autoStart, groupId: selectedGroupId, useWorktrees: useWorktrees || undefined,
|
||||
shell: shellChoice, icon: projectIcon, color: projectColor,
|
||||
modelConfig: Object.keys(modelConfig).length > 0 ? modelConfig : undefined,
|
||||
};
|
||||
onCreated(project);
|
||||
onCreated({ id: `p-${Date.now()}`, name: sanitize(projectName) || 'Untitled', cwd, provider, model: model || undefined, systemPrompt: systemPrompt || undefined, autoStart, groupId: selectedGroupId, useWorktrees: useWorktrees || undefined, shell: shellChoice, icon: projectIcon, color: projectColor, modelConfig: Object.keys(modelConfig).length > 0 ? modelConfig : undefined });
|
||||
resetState();
|
||||
}
|
||||
|
||||
function closeWizard() { resetState(); onClose(); }
|
||||
function handleOverlayKeydown(e: KeyboardEvent) { if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); closeWizard(); } }
|
||||
|
||||
function resetState() {
|
||||
step = 1; sourceType = 'local'; localPath = ''; repoUrl = ''; cloneTarget = '';
|
||||
githubRepo = ''; selectedTemplate = ''; templateTargetDir = '~/projects';
|
||||
remoteHost = ''; remoteUser = ''; remotePath = '';
|
||||
remoteAuthMethod = 'agent'; remotePassword = ''; remoteKeyPath = '~/.ssh/id_ed25519'; remoteSshfs = false;
|
||||
pathValid = 'idle'; isGitRepo = false; gitBranch = '';
|
||||
gitProbeStatus = 'idle'; gitProbeBranches = []; githubInfo = null; githubLoading = false;
|
||||
cloning = false; projectName = ''; nameError = ''; selectedBranch = '';
|
||||
branches = []; useWorktrees = false; selectedGroupId = groupId;
|
||||
projectIcon = 'Terminal'; projectColor = 'var(--ctp-blue)'; shellChoice = 'bash';
|
||||
provider = 'claude'; model = ''; permissionMode = 'default';
|
||||
step = 1; sourceType = 'local'; localPath = ''; repoUrl = ''; cloneTarget = ''; githubRepo = ''; selectedTemplate = ''; templateTargetDir = '~/projects';
|
||||
remoteHost = ''; remoteUser = ''; remotePath = ''; remoteAuthMethod = 'agent'; remotePassword = ''; remoteKeyPath = '~/.ssh/id_ed25519'; remoteSshfs = false; remoteSshfsMountpoint = '';
|
||||
pathValid = 'idle'; isGitRepo = false; gitBranch = ''; gitProbeStatus = 'idle'; gitProbeBranches = []; githubInfo = null; githubLoading = false; githubProbeStatus = 'idle';
|
||||
cloning = false; projectName = ''; nameError = ''; selectedBranch = ''; branches = []; useWorktrees = false; selectedGroupId = groupId;
|
||||
projectIcon = 'Terminal'; projectColor = 'var(--ctp-blue)'; shellChoice = 'bash'; provider = 'claude'; model = ''; permissionMode = 'default';
|
||||
systemPrompt = ''; autoStart = false; providerModels = []; modelsLoading = false; modelConfig = {};
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const SETTERS: Record<string, (v: any) => void> = {
|
||||
sourceType: v => sourceType = v, localPath: v => localPath = v,
|
||||
repoUrl: v => repoUrl = v, cloneTarget: v => cloneTarget = v,
|
||||
githubRepo: v => githubRepo = v, selectedTemplate: v => selectedTemplate = v,
|
||||
templateTargetDir: v => templateTargetDir = v, remoteHost: v => remoteHost = v,
|
||||
remoteUser: v => remoteUser = v, remotePath: v => remotePath = v,
|
||||
remoteAuthMethod: v => remoteAuthMethod = v, remotePassword: v => remotePassword = v,
|
||||
remoteKeyPath: v => remoteKeyPath = v, remoteSshfs: v => remoteSshfs = v,
|
||||
projectName: v => projectName = v, selectedBranch: v => selectedBranch = v,
|
||||
useWorktrees: v => useWorktrees = v, selectedGroupId: v => selectedGroupId = v,
|
||||
projectIcon: v => projectIcon = v, projectColor: v => projectColor = v,
|
||||
shellChoice: v => shellChoice = v, provider: v => provider = v,
|
||||
model: v => model = v, permissionMode: v => permissionMode = v,
|
||||
systemPrompt: v => systemPrompt = v, autoStart: v => autoStart = v,
|
||||
modelConfig: v => modelConfig = v,
|
||||
sourceType: v => sourceType = v, localPath: v => localPath = v, repoUrl: v => repoUrl = v, cloneTarget: v => cloneTarget = v,
|
||||
githubRepo: v => githubRepo = v, selectedTemplate: v => selectedTemplate = v, templateTargetDir: v => templateTargetDir = v,
|
||||
remoteHost: v => remoteHost = v, remoteUser: v => remoteUser = v, remotePath: v => remotePath = v,
|
||||
remoteAuthMethod: v => remoteAuthMethod = v, remotePassword: v => remotePassword = v, remoteKeyPath: v => remoteKeyPath = v,
|
||||
remoteSshfs: v => remoteSshfs = v, remoteSshfsMountpoint: v => remoteSshfsMountpoint = v,
|
||||
projectName: v => projectName = v, selectedBranch: v => selectedBranch = v, useWorktrees: v => useWorktrees = v,
|
||||
selectedGroupId: v => selectedGroupId = v, projectIcon: v => projectIcon = v, projectColor: v => projectColor = v,
|
||||
shellChoice: v => shellChoice = v, provider: v => provider = v, model: v => model = v,
|
||||
permissionMode: v => permissionMode = v, systemPrompt: v => systemPrompt = v, autoStart: v => autoStart = v, modelConfig: v => modelConfig = v,
|
||||
};
|
||||
function handleUpdate(field: string, value: unknown) { SETTERS[field]?.(value); }
|
||||
</script>
|
||||
|
||||
<div class="wizard-overlay" role="dialog" aria-label="New Project">
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="wizard-overlay" role="dialog" aria-label="New Project" tabindex={-1} onkeydown={handleOverlayKeydown}>
|
||||
<div class="wizard-panel">
|
||||
<div class="wz-header">
|
||||
<h2 class="wz-title">New Project</h2>
|
||||
<div class="wz-steps">
|
||||
{#each [1, 2, 3] as s}
|
||||
<span class="wz-step-dot" class:active={step === s} class:done={step > s}>{s}</span>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="wz-steps">{#each [1, 2, 3] as s}<span class="wz-step-dot" class:active={step === s} class:done={step > s}>{s}</span>{/each}</div>
|
||||
<button class="wz-close" onclick={closeWizard} aria-label="Close">×</button>
|
||||
</div>
|
||||
|
||||
<div class="wz-body" style:display={step === 1 ? 'flex' : 'none'}>
|
||||
<h3 class="wz-step-title">Source</h3>
|
||||
<WizardStep1 {sourceType} {localPath} {repoUrl} {cloneTarget} {githubRepo} {selectedTemplate} {remoteHost} {remoteUser} {remotePath} {remoteAuthMethod} {remotePassword} {remoteKeyPath} {remoteSshfs} {pathValid} {isGitRepo} {gitBranch} {gitProbeStatus} {gitProbeBranches} {githubInfo} {githubLoading} {cloning} {templates} {templateTargetDir} onUpdate={handleUpdate} />
|
||||
<WizardStep1 bind:this={step1Ref} {sourceType} {localPath} {repoUrl} {cloneTarget} {githubRepo} {selectedTemplate} {remoteHost} {remoteUser} {remotePath} {remoteAuthMethod} {remotePassword} {remoteKeyPath} {remoteSshfs} {remoteSshfsMountpoint} {pathValid} {isGitRepo} {gitBranch} {gitProbeStatus} {gitProbeBranches} {githubInfo} {githubProbeStatus} {githubLoading} {cloning} {templates} {templateTargetDir} {templateOriginDir} onUpdate={handleUpdate} />
|
||||
<div class="wz-footer">
|
||||
<button class="wz-btn secondary" onclick={closeWizard}>Cancel</button>
|
||||
<div class="wz-footer-right">
|
||||
{#if cloning}<span class="wz-cloning">Cloning…</span>{/if}
|
||||
<button class="wz-btn primary" disabled={!step1Valid() || cloning} onclick={goToStep2}>Next</button>
|
||||
</div>
|
||||
<div class="wz-footer-right">{#if cloning}<span class="wz-cloning">Cloning…</span>{/if}<button class="wz-btn primary" disabled={!step1Valid() || cloning} onclick={goToStep2}>Next</button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wz-body" style:display={step === 2 ? 'flex' : 'none'}>
|
||||
<h3 class="wz-step-title">Configure</h3>
|
||||
<WizardStep2 {projectName} {nameError} {selectedBranch} {branches} {useWorktrees} {selectedGroupId} {groups} {projectIcon} {projectColor} {shellChoice} {isGitRepo} onUpdate={handleUpdate} />
|
||||
<div class="wz-footer">
|
||||
<button class="wz-btn secondary" onclick={goBack}>Back</button>
|
||||
<button class="wz-btn primary" disabled={!!nameError} onclick={goToStep3}>Next</button>
|
||||
</div>
|
||||
<WizardStep2 bind:this={step2Ref} {projectName} {nameError} {selectedBranch} {branches} {useWorktrees} {selectedGroupId} {groups} {projectIcon} {projectColor} {shellChoice} {isGitRepo} onUpdate={handleUpdate} />
|
||||
<div class="wz-footer"><button class="wz-btn secondary" onclick={goBack}>Back</button><button class="wz-btn primary" disabled={!!nameError} onclick={goToStep3}>Next</button></div>
|
||||
</div>
|
||||
|
||||
<div class="wz-body" style:display={step === 3 ? 'flex' : 'none'}>
|
||||
<h3 class="wz-step-title">Agent</h3>
|
||||
<WizardStep3 {provider} {model} {permissionMode} {systemPrompt} {autoStart} {detectedProviders} {providerModels} {modelsLoading} {modelConfig} onUpdate={handleUpdate} />
|
||||
<div class="wz-footer">
|
||||
<button class="wz-btn secondary" onclick={goBack}>Back</button>
|
||||
<div class="wz-footer-right">
|
||||
<button class="wz-btn ghost" onclick={createProject}>Skip</button>
|
||||
<button class="wz-btn primary" onclick={createProject}>Create</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wz-footer"><button class="wz-btn secondary" onclick={goBack}>Back</button><div class="wz-footer-right"><button class="wz-btn ghost" onclick={createProject}>Skip</button><button class="wz-btn primary" onclick={createProject}>Create</button></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -283,12 +182,14 @@
|
|||
.wz-step-dot.done { background: color-mix(in srgb, var(--ctp-green) 20%, transparent); border-color: var(--ctp-green); color: var(--ctp-green); }
|
||||
.wz-close { background: none; border: none; color: var(--ctp-overlay1); cursor: pointer; font-size: 0.875rem; padding: 0.25rem; border-radius: 0.25rem; }
|
||||
.wz-close:hover { color: var(--ctp-text); background: var(--ctp-surface0); }
|
||||
.wz-close:focus-visible { outline: 2px solid var(--ctp-blue); outline-offset: -1px; }
|
||||
.wz-body { flex: 1; min-height: 0; overflow-y: auto; flex-direction: column; gap: 0.5rem; padding: 1rem; }
|
||||
.wz-step-title { font-size: 0.875rem; font-weight: 600; color: var(--ctp-text); margin: 0 0 0.25rem; }
|
||||
.wz-footer { display: flex; align-items: center; justify-content: space-between; padding-top: 0.75rem; margin-top: auto; border-top: 1px solid var(--ctp-surface0); }
|
||||
.wz-footer-right { display: flex; gap: 0.375rem; align-items: center; }
|
||||
.wz-cloning { font-size: 0.75rem; color: var(--ctp-blue); font-style: italic; }
|
||||
.wz-btn { padding: 0.375rem 0.75rem; border-radius: 0.25rem; font-size: 0.8125rem; font-weight: 500; cursor: pointer; font-family: var(--ui-font-family); border: 1px solid transparent; }
|
||||
.wz-btn:focus-visible { outline: 2px solid var(--ctp-blue); outline-offset: 1px; }
|
||||
.wz-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
.wz-btn.primary { background: color-mix(in srgb, var(--ctp-blue) 20%, transparent); border-color: var(--ctp-blue); color: var(--ctp-blue); }
|
||||
.wz-btn.primary:hover:not(:disabled) { background: color-mix(in srgb, var(--ctp-blue) 35%, transparent); }
|
||||
|
|
@ -296,5 +197,4 @@
|
|||
.wz-btn.secondary:hover { background: var(--ctp-surface0); color: var(--ctp-text); }
|
||||
.wz-btn.ghost { background: transparent; border: none; color: var(--ctp-subtext0); }
|
||||
.wz-btn.ghost:hover { color: var(--ctp-text); }
|
||||
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue