diff --git a/ui/desktop/src/components/GooseMessage.tsx b/ui/desktop/src/components/GooseMessage.tsx index de418306..c07168f0 100644 --- a/ui/desktop/src/components/GooseMessage.tsx +++ b/ui/desktop/src/components/GooseMessage.tsx @@ -105,13 +105,13 @@ export default function GooseMessage({
{textContent && (
-
+
{}
{/* Only show MessageCopyLink if there's text content and no tool requests/responses */} -
+
{toolRequests.length === 0 && ( -
+
{timestamp}
)} @@ -126,20 +126,22 @@ export default function GooseMessage({ {toolRequests.length > 0 && (
-
- {toolRequests.map((toolRequest) => ( + {toolRequests.map((toolRequest) => ( +
- ))} -
+
+ ))}
{timestamp}
diff --git a/ui/desktop/src/components/ToolCallWithResponse.tsx b/ui/desktop/src/components/ToolCallWithResponse.tsx index 327cf16d..9e6d2287 100644 --- a/ui/desktop/src/components/ToolCallWithResponse.tsx +++ b/ui/desktop/src/components/ToolCallWithResponse.tsx @@ -34,24 +34,24 @@ export default function ToolCallWithResponse({ interface ToolCallExpandableProps { label: string | React.ReactNode; - defaultExpanded?: boolean; - forceExpand?: boolean; + isStartExpanded?: boolean; + isForceExpand?: boolean; children: React.ReactNode; className?: string; } function ToolCallExpandable({ label, - defaultExpanded = false, - forceExpand, + isStartExpanded = false, + isForceExpand, children, className = '', }: ToolCallExpandableProps) { - const [isExpanded, setIsExpanded] = React.useState(defaultExpanded); + const [isExpanded, setIsExpanded] = React.useState(isStartExpanded); const toggleExpand = () => setIsExpanded((prev) => !prev); React.useEffect(() => { - if (forceExpand) setIsExpanded(true); - }, [forceExpand]); + if (isForceExpand) setIsExpanded(true); + }, [isForceExpand]); return (
@@ -74,12 +74,23 @@ interface ToolCallViewProps { } function ToolCallView({ isCancelledMessage, toolCall, toolResponse }: ToolCallViewProps) { + const responseStyle = localStorage.getItem('response_style'); + const isExpandToolDetails = (() => { + switch (responseStyle) { + case 'concise': + return false; + case 'detailed': + default: + return true; + } + })(); + const isToolDetails = Object.entries(toolCall?.arguments).length > 0; const loadingStatus: LoadingStatus = !toolResponse?.toolResult.status ? 'loading' : toolResponse?.toolResult.status; - const toolResults: { result: Content; defaultExpanded: boolean }[] = + const toolResults: { result: Content; isExpandToolResults: boolean }[] = loadingStatus === 'success' && Array.isArray(toolResponse?.toolResult.value) ? toolResponse.toolResult.value .filter((item) => { @@ -88,16 +99,16 @@ function ToolCallView({ isCancelledMessage, toolCall, toolResponse }: ToolCallVi }) .map((item) => ({ result: item, - defaultExpanded: ((item.annotations?.priority as number | undefined) ?? -1) >= 0.5, + isExpandToolResults: ((item.annotations?.priority as number | undefined) ?? -1) >= 0.5, })) : []; - const shouldExpand = toolResults.some((v) => v.defaultExpanded); + const isShouldExpand = isExpandToolDetails || toolResults.some((v) => v.isExpandToolResults); return ( @@ -110,21 +121,24 @@ function ToolCallView({ isCancelledMessage, toolCall, toolResponse }: ToolCallVi {/* Tool Details */} {isToolDetails && (
- +
)} {/* Tool Output */} {!isCancelledMessage && ( <> - {toolResults.map(({ result, defaultExpanded }, index) => { + {toolResults.map(({ result, isExpandToolResults }, index) => { const isLast = index === toolResults.length - 1; return (
0 ? '' : 'rounded-t'} + ${isLast ? 'rounded-b' : ''} + `} > - +
); })} @@ -139,11 +153,16 @@ interface ToolDetailsViewProps { name: string; arguments: Record; }; + isStartExpanded: boolean; } -function ToolDetailsView({ toolCall }: ToolDetailsViewProps) { +function ToolDetailsView({ toolCall, isStartExpanded }: ToolDetailsViewProps) { return ( - + {toolCall.arguments && } ); @@ -151,14 +170,14 @@ function ToolDetailsView({ toolCall }: ToolDetailsViewProps) { interface ToolResultViewProps { result: Content; - defaultExpanded: boolean; + isStartExpanded: boolean; } -function ToolResultView({ result, defaultExpanded }: ToolResultViewProps) { +function ToolResultView({ result, isStartExpanded }: ToolResultViewProps) { return ( Output} - defaultExpanded={defaultExpanded} + isStartExpanded={isStartExpanded} >
{result.type === 'text' && result.text && ( diff --git a/ui/desktop/src/components/settings_v2/SettingsView.tsx b/ui/desktop/src/components/settings_v2/SettingsView.tsx index 091959e3..f670cdba 100644 --- a/ui/desktop/src/components/settings_v2/SettingsView.tsx +++ b/ui/desktop/src/components/settings_v2/SettingsView.tsx @@ -5,6 +5,7 @@ import ExtensionsSection from './extensions/ExtensionsSection'; import ModelsSection from './models/ModelsSection'; import { ModeSection } from './mode/ModeSection'; import SessionSharingSection from './sessions/SessionSharingSection'; +import { ResponseStylesSection } from './response_styles/ResponseStylesSection'; import { ExtensionConfig } from '../../api'; import MoreMenuLayout from '../more_menu/MoreMenuLayout'; @@ -47,6 +48,8 @@ export default function SettingsView({ {/*Session sharing*/} + {/* Response Styles */} +
diff --git a/ui/desktop/src/components/settings_v2/response_styles/ResponseStyleSelectionItem.tsx b/ui/desktop/src/components/settings_v2/response_styles/ResponseStyleSelectionItem.tsx new file mode 100644 index 00000000..c5918ed4 --- /dev/null +++ b/ui/desktop/src/components/settings_v2/response_styles/ResponseStyleSelectionItem.tsx @@ -0,0 +1,75 @@ +import { useEffect, useState } from 'react'; + +export interface ResponseStyle { + key: string; + label: string; + description: string; +} + +export const all_response_styles: ResponseStyle[] = [ + { + key: 'detailed', + label: 'Detailed', + description: 'Tool calls are by default shown open to expose details', + }, + { + key: 'concise', + label: 'Concise', + description: 'Tool calls are by default closed and only show the tool used', + }, +]; + +interface ResponseStyleSelectionItemProps { + currentStyle: string; + style: ResponseStyle; + showDescription: boolean; + handleStyleChange: (newStyle: string) => void; +} + +export function ResponseStyleSelectionItem({ + currentStyle, + style, + showDescription, + handleStyleChange, +}: ResponseStyleSelectionItemProps) { + const [checked, setChecked] = useState(currentStyle === style.key); + + useEffect(() => { + setChecked(currentStyle === style.key); + }, [currentStyle, style.key]); + + return ( +
+
handleStyleChange(style.key)} + > +
+
+

{style.label}

+ {showDescription && ( +

{style.description}

+ )} +
+
+ +
+ handleStyleChange(style.key)} + className="peer sr-only" + /> +
+
+
+
+ ); +} diff --git a/ui/desktop/src/components/settings_v2/response_styles/ResponseStylesSection.tsx b/ui/desktop/src/components/settings_v2/response_styles/ResponseStylesSection.tsx new file mode 100644 index 00000000..756f7b70 --- /dev/null +++ b/ui/desktop/src/components/settings_v2/response_styles/ResponseStylesSection.tsx @@ -0,0 +1,46 @@ +import { useEffect, useState } from 'react'; +import { all_response_styles, ResponseStyleSelectionItem } from './ResponseStyleSelectionItem'; + +export const ResponseStylesSection = () => { + const [currentStyle, setCurrentStyle] = useState('detailed'); + + useEffect(() => { + const savedStyle = localStorage.getItem('response_style'); + if (savedStyle) { + try { + setCurrentStyle(savedStyle); + } catch (error) { + console.error('Error parsing response style:', error); + } + } + }, []); + + const handleStyleChange = async (newStyle: string) => { + setCurrentStyle(newStyle); + localStorage.setItem('response_style', newStyle); + }; + + return ( +
+
+

Response Styles

+
+
+

+ Choose how Goose should format and style its responses +

+
+ {all_response_styles.map((style) => ( + + ))} +
+
+
+ ); +}; diff --git a/ui/desktop/src/components/settings_v2/sessions/SessionSharingSection.tsx b/ui/desktop/src/components/settings_v2/sessions/SessionSharingSection.tsx index 13c524c3..916d0a56 100644 --- a/ui/desktop/src/components/settings_v2/sessions/SessionSharingSection.tsx +++ b/ui/desktop/src/components/settings_v2/sessions/SessionSharingSection.tsx @@ -80,13 +80,13 @@ export default function SessionSharingSection() { }; return ( -
+
{/*Title*/} -
+

Session sharing

-
+
{envBaseUrlShare ? (

Session sharing is configured but fully opt-in — your sessions are only shared when you