diff --git a/apps/web/app/components/workspace/profile-switcher.tsx b/apps/web/app/components/workspace/profile-switcher.tsx index 47a884a5b17..cafad52ca08 100644 --- a/apps/web/app/components/workspace/profile-switcher.tsx +++ b/apps/web/app/components/workspace/profile-switcher.tsx @@ -19,6 +19,7 @@ export type ProfileSwitcherTriggerProps = { type ProfileSwitcherProps = { onProfileSwitch?: () => void; + onWorkspaceDelete?: (profileName: string) => void; onCreateWorkspace?: () => void; /** Parent-tracked active profile -- triggers a re-fetch when it changes (e.g. after workspace creation). */ activeProfileHint?: string | null; @@ -26,11 +27,26 @@ type ProfileSwitcherProps = { trigger?: (props: ProfileSwitcherTriggerProps) => React.ReactNode; }; -export function ProfileSwitcher({ onProfileSwitch, onCreateWorkspace, activeProfileHint, trigger }: ProfileSwitcherProps) { +function shortenPath(p: string): string { + return p + .replace(/^\/Users\/[^/]+/, "~") + .replace(/^\/home\/[^/]+/, "~") + .replace(/^[A-Za-z]:[/\\]Users[/\\][^/\\]+/, "~"); +} + +export function ProfileSwitcher({ + onProfileSwitch, + onWorkspaceDelete, + onCreateWorkspace, + activeProfileHint, + trigger, +}: ProfileSwitcherProps) { const [profiles, setProfiles] = useState([]); const [activeProfile, setActiveProfile] = useState("default"); const [isOpen, setIsOpen] = useState(false); const [switching, setSwitching] = useState(false); + const [deletingProfile, setDeletingProfile] = useState(null); + const [actionError, setActionError] = useState(null); const dropdownRef = useRef(null); const fetchProfiles = useCallback(async () => { @@ -66,6 +82,7 @@ export function ProfileSwitcher({ onProfileSwitch, onCreateWorkspace, activeProf setIsOpen(false); return; } + setActionError(null); setSwitching(true); try { const res = await fetch("/api/profiles/switch", { @@ -78,15 +95,55 @@ export function ProfileSwitcher({ onProfileSwitch, onCreateWorkspace, activeProf setActiveProfile(data.activeProfile ?? "default"); onProfileSwitch?.(); void fetchProfiles(); + } else { + const data = (await res.json().catch(() => ({}))) as { error?: string }; + setActionError(data.error ?? "Failed to switch profile."); } } catch { - // ignore + setActionError("Failed to switch profile."); } finally { setSwitching(false); setIsOpen(false); } }; + const handleDeleteWorkspace = async (profileName: string) => { + const target = profiles.find((p) => p.name === profileName); + if (!target?.workspaceDir) { + return; + } + const confirmed = window.confirm( + `Delete workspace for profile "${profileName}"?\n\nThis runs openclaw --profile ${profileName} workspace delete.`, + ); + if (!confirmed) { + return; + } + + setActionError(null); + setDeletingProfile(profileName); + try { + const res = await fetch("/api/workspace/delete", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ profile: profileName }), + }); + if (!res.ok) { + const data = (await res.json().catch(() => ({}))) as { error?: string }; + setActionError(data.error ?? `Failed to delete workspace for profile '${profileName}'.`); + return; + } + if (profileName === activeProfile) { + onProfileSwitch?.(); + } + onWorkspaceDelete?.(profileName); + await fetchProfiles(); + } catch { + setActionError(`Failed to delete workspace for profile '${profileName}'.`); + } finally { + setDeletingProfile(null); + } + }; + // Don't show the switcher if there's only one profile and no way to create more const showSwitcher = profiles.length > 0; const handleToggle = () => { @@ -158,53 +215,98 @@ export function ProfileSwitcher({ onProfileSwitch, onCreateWorkspace, activeProf {profiles.map((p) => { const isCurrent = p.name === activeProfile; return ( - + + {p.workspaceDir && ( + )} - + ); })} + {actionError && ( +

+ {actionError} +

+ )} + {/* Create new */}