diff --git a/ui/goose2/src/features/chat/ui/ChatInput.tsx b/ui/goose2/src/features/chat/ui/ChatInput.tsx index f46a6c65ff..9153a83a5b 100644 --- a/ui/goose2/src/features/chat/ui/ChatInput.tsx +++ b/ui/goose2/src/features/chat/ui/ChatInput.tsx @@ -38,7 +38,7 @@ interface ChatInputProps { // Personas personas?: Persona[]; selectedPersonaId?: string | null; - onPersonaChange?: (personaId: string) => void; + onPersonaChange?: (personaId: string | null) => void; onCreatePersona?: () => void; // Provider (secondary -- auto-set by persona but overridable) providers?: AcpProvider[]; @@ -97,20 +97,11 @@ export function ChatInput({ const textareaRef = useRef(null); const containerRef = useRef(null); - const defaultPersonaId = useMemo( - () => personas.find((persona) => persona.id === "builtin-solo")?.id ?? null, - [personas], - ); const activePersona = useMemo( () => personas.find((persona) => persona.id === selectedPersonaId) ?? null, [personas, selectedPersonaId], ); - const stickyPersona = - activePersona && - selectedPersonaId !== null && - selectedPersonaId !== defaultPersonaId - ? activePersona - : null; + const stickyPersona = activePersona; const canSend = text.trim().length > 0 && !isStreaming && !disabled; @@ -213,10 +204,8 @@ export function ChatInput({ : placeholder; const handleClearStickyPersona = useCallback(() => { - if (defaultPersonaId) { - onPersonaChange?.(defaultPersonaId); - } - }, [defaultPersonaId, onPersonaChange]); + onPersonaChange?.(null); + }, [onPersonaChange]); return ( diff --git a/ui/goose2/src/features/chat/ui/ChatInputToolbar.tsx b/ui/goose2/src/features/chat/ui/ChatInputToolbar.tsx index b79b363d14..00f88d8b26 100644 --- a/ui/goose2/src/features/chat/ui/ChatInputToolbar.tsx +++ b/ui/goose2/src/features/chat/ui/ChatInputToolbar.tsx @@ -52,7 +52,7 @@ interface ChatInputToolbarProps { // Personas personas: Persona[]; selectedPersonaId: string | null; - onPersonaChange?: (personaId: string) => void; + onPersonaChange?: (personaId: string | null) => void; onCreatePersona?: () => void; // Provider providers: AcpProvider[]; diff --git a/ui/goose2/src/features/chat/ui/ChatView.tsx b/ui/goose2/src/features/chat/ui/ChatView.tsx index 73604865cf..014b4b82ea 100644 --- a/ui/goose2/src/features/chat/ui/ChatView.tsx +++ b/ui/goose2/src/features/chat/ui/ChatView.tsx @@ -49,8 +49,8 @@ export function ChatView({ // Persona state const personas = useAgentStore((s) => s.personas); - const [selectedPersonaId, setSelectedPersonaId] = useState( - initialPersonaId ?? "builtin-solo", + const [selectedPersonaId, setSelectedPersonaId] = useState( + initialPersonaId ?? null, ); const session = useChatSessionStore((s) => s.sessions.find((candidate) => candidate.id === activeSessionId), @@ -171,7 +171,7 @@ export function ChatView({ // When persona changes, update the provider to match persona's default const handlePersonaChange = useCallback( - (personaId: string) => { + (personaId: string | null) => { setSelectedPersonaId(personaId); const persona = personas.find((p) => p.id === personaId); if (persona?.provider) { @@ -197,7 +197,7 @@ export function ChatView({ // Persist persona selection to session store useChatSessionStore .getState() - .updateSession(activeSessionId, { personaId }); + .updateSession(activeSessionId, { personaId: personaId ?? undefined }); }, [personas, providers, activeSessionId, handleProviderChange], ); @@ -205,19 +205,14 @@ export function ChatView({ // Validate persona still exists — fall back to default if deleted useEffect(() => { if ( + selectedPersonaId !== null && personas.length > 0 && !personas.find((p) => p.id === selectedPersonaId) ) { - const fallback = - personas.find((p) => p.id === "builtin-solo") ?? personas[0]; - if (fallback) { - setSelectedPersonaId(fallback.id); - useChatSessionStore - .getState() - .updateSession(activeSessionId, { personaId: fallback.id }); - } + // Selected persona was deleted — reset to no persona + setSelectedPersonaId(null); } - }, [personas, selectedPersonaId, activeSessionId]); + }, [personas, selectedPersonaId]); const displayAgentName = selectedPersona?.displayName ?? agentName; const personaAvatarSrc = useAvatarSrc(selectedPersona?.avatar); diff --git a/ui/goose2/src/features/chat/ui/PersonaPicker.tsx b/ui/goose2/src/features/chat/ui/PersonaPicker.tsx index 70e328ea74..fde73e2d87 100644 --- a/ui/goose2/src/features/chat/ui/PersonaPicker.tsx +++ b/ui/goose2/src/features/chat/ui/PersonaPicker.tsx @@ -16,7 +16,7 @@ import type { Persona } from "@/shared/types/agents"; interface PersonaPickerProps { personas: Persona[]; selectedPersonaId: string | null; - onPersonaChange: (personaId: string) => void; + onPersonaChange: (personaId: string | null) => void; onCreatePersona?: () => void; compact?: boolean; className?: string; @@ -33,7 +33,10 @@ export function PersonaPicker({ triggerVariant = "default", }: PersonaPickerProps) { const selected = useMemo( - () => personas.find((p) => p.id === selectedPersonaId) ?? personas[0], + () => + selectedPersonaId + ? personas.find((p) => p.id === selectedPersonaId) + : undefined, [personas, selectedPersonaId], ); @@ -46,7 +49,7 @@ export function PersonaPicker({ [personas], ); - const label = selected?.displayName ?? "Select persona"; + const label = selected?.displayName ?? "Goose"; return ( @@ -85,6 +88,24 @@ export function PersonaPicker({ align="start" className="max-h-[min(70vh,32rem)] w-[22rem] overflow-y-auto" > + onPersonaChange(null)} + className="flex items-start gap-2.5 py-2" + > +
+ +
+
+ Goose + + No persona — chat directly with the agent + +
+ {selectedPersonaId === null && ( + + )} +
+ {builtinPersonas.length > 0 && ( <> diff --git a/ui/goose2/src/features/chat/ui/__tests__/ChatInput.test.tsx b/ui/goose2/src/features/chat/ui/__tests__/ChatInput.test.tsx index 704f14b542..2ba6826880 100644 --- a/ui/goose2/src/features/chat/ui/__tests__/ChatInput.test.tsx +++ b/ui/goose2/src/features/chat/ui/__tests__/ChatInput.test.tsx @@ -29,7 +29,9 @@ function StatefulChatInput({ }: { onSend?: (text: string, personaId?: string) => void; }) { - const [selectedPersonaId, setSelectedPersonaId] = useState("builtin-solo"); + const [selectedPersonaId, setSelectedPersonaId] = useState( + "builtin-solo", + ); return ( { expect(screen.getByText("Good afternoon")).toBeInTheDocument(); }); - it("renders the chat input placeholder with persona name", () => { + it("renders the chat input placeholder with default agent name when no persona selected", () => { render(); expect( - screen.getByPlaceholderText("Message Solo... (type @ to mention)"), + screen.getByPlaceholderText("Message Goose... (type @ to mention)"), ).toBeInTheDocument(); }); diff --git a/ui/goose2/src/features/home/ui/HomeScreen.tsx b/ui/goose2/src/features/home/ui/HomeScreen.tsx index ceb34d2ab2..de0d63630c 100644 --- a/ui/goose2/src/features/home/ui/HomeScreen.tsx +++ b/ui/goose2/src/features/home/ui/HomeScreen.tsx @@ -61,7 +61,9 @@ export function HomeScreen({ const personas = useAgentStore((s) => s.personas); const projects = useProjectStore((s) => s.projects); - const [selectedPersonaId, setSelectedPersonaId] = useState("builtin-solo"); + const [selectedPersonaId, setSelectedPersonaId] = useState( + null, + ); const [providers, setProviders] = useState([]); const [selectedProvider, setSelectedProvider] = useState("goose"); const [selectedProjectId, setSelectedProjectId] = useState( @@ -96,7 +98,7 @@ export function HomeScreen({ const handleSend = useCallback( (message: string, personaId?: string) => { - const effectivePersonaId = personaId ?? selectedPersonaId; + const effectivePersonaId = personaId ?? selectedPersonaId ?? undefined; onStartChat?.( message,