Files
openchamber/packages/ui/src/components/multirun/AgentSelector.tsx
T
Bohdan Triapitsyn 53c2a0d919 feat: instant draft-first worktree creation and multi-run launcher redesign (#741)
## Summary

- **Instant worktree creation from chat draft**: selecting "+ New worktree" in the draft branch selector immediately creates a session draft and bootstraps the worktree in the background — no modal interruption
- **Redesigned multi-run launcher**: compact 2-column grid layout in a right-sized dialog with scroll shadow, sticky footer, tooltips replacing verbose descriptions, and project icons in the selector
- **Branch selector aligned across surfaces**: multi-run and agent manager branch pickers now use the shared git store and match NewWorktreeDialog behavior (same default resolution cascade, no synthetic HEAD option, all branches shown)
- **Opaque model multi-select dropdown**: fixes text bleed-through on translucent backgrounds by compositing `--surface-elevated` over `--surface-background`
- **"+ New" inline button in sidebar worktree headers** for faster worktree creation

## Why

Worktree creation was behind modal flow that interrupted the user's train of thought. The draft-first approach lets users start typing immediately while the worktree bootstraps. The multi-run launcher had an oversized form layout with redundant explanations, and its branch picker behaved differently from the main worktree dialog - causing confusion about which branches were available and what the default was.
2026-03-22 22:31:29 +02:00

118 lines
3.3 KiB
TypeScript

import React from 'react';
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { cn } from '@/lib/utils';
import { useConfigStore } from '@/stores/useConfigStore';
export interface AgentSelectorProps {
/** Currently selected agent name (empty string for no agent) */
value: string;
/** Called when agent selection changes */
onChange: (agentName: string) => void;
/** Optional className for the trigger */
className?: string;
/** Whether the selector is disabled */
disabled?: boolean;
/** ID for accessibility */
id?: string;
}
/**
* Agent selector dropdown for selecting an agent for multi-run sessions.
* Uses getVisibleAgents from useConfigStore to show available agents.
*/
export const AgentSelector: React.FC<AgentSelectorProps> = ({
value,
onChange,
className,
disabled,
id,
}) => {
const getVisibleAgents = useConfigStore((state) => state.getVisibleAgents);
const loadAgents = useConfigStore((state) => state.loadAgents);
const defaultAgentName = useConfigStore((state) => state.currentAgentName);
const agents = getVisibleAgents();
const selectableAgents = React.useMemo(
() => agents.filter((agent) => agent.mode !== 'subagent'),
[agents]
);
// Load agents on mount
React.useEffect(() => {
loadAgents();
}, [loadAgents]);
// Ensure we always have a valid selection (defaults to current default agent, then first selectable agent).
React.useEffect(() => {
if (disabled) {
return;
}
const trimmedValue = value.trim();
if (trimmedValue.length > 0 && selectableAgents.some((agent) => agent.name === trimmedValue)) {
return;
}
const candidateDefault =
typeof defaultAgentName === 'string' && defaultAgentName.trim().length > 0
? defaultAgentName.trim()
: null;
if (candidateDefault && selectableAgents.some((agent) => agent.name === candidateDefault)) {
onChange(candidateDefault);
return;
}
const firstAgent = selectableAgents[0]?.name;
if (firstAgent) {
onChange(firstAgent);
}
}, [defaultAgentName, disabled, onChange, selectableAgents, value]);
const selectValue = value.trim().length > 0 ? value : undefined;
return (
<Select
value={selectValue}
onValueChange={onChange}
disabled={disabled}
>
<SelectTrigger
id={id}
size="lg"
className={cn(
'max-w-full typography-meta text-foreground !border-border/80 !bg-[var(--surface-subtle)]/95 !backdrop-blur-sm hover:!bg-[var(--interactive-hover)]/70 data-[state=open]:!bg-[var(--interactive-active)]/70',
className,
)}
style={{
backdropFilter: 'blur(10px)',
WebkitBackdropFilter: 'blur(10px)',
}}
>
<SelectValue placeholder="Select an agent" />
</SelectTrigger>
<SelectContent fitContent>
{selectableAgents.length > 0 && (
<SelectGroup>
{selectableAgents.map((agent) => (
<SelectItem
key={agent.name}
value={agent.name}
className="w-auto whitespace-nowrap"
>
{agent.name}
</SelectItem>
))}
</SelectGroup>
)}
</SelectContent>
</Select>
);
};