mirror of
https://github.com/aljazceru/goose.git
synced 2026-01-03 14:34:26 +01:00
Expanded ToolCall options (#2457)
This commit is contained in:
@@ -105,13 +105,13 @@ export default function GooseMessage({
|
||||
<div className="flex flex-col w-full">
|
||||
{textContent && (
|
||||
<div className="flex flex-col group">
|
||||
<div className={`goose-message-content py-2`}>
|
||||
<div className={`goose-message-content pt-2`}>
|
||||
<div ref={contentRef}>{<MarkdownContent content={textContent} />}</div>
|
||||
</div>
|
||||
{/* Only show MessageCopyLink if there's text content and no tool requests/responses */}
|
||||
<div className="relative flex justify-end z-[-1]">
|
||||
<div className="relative flex justify-start z-[-1]">
|
||||
{toolRequests.length === 0 && (
|
||||
<div className="absolute left-0 text-xs text-textSubtle pt-1 transition-all duration-200 group-hover:-translate-y-4 group-hover:opacity-0">
|
||||
<div className="text-xs text-textSubtle pt-1 transition-all duration-200 group-hover:-translate-y-4 group-hover:opacity-0">
|
||||
{timestamp}
|
||||
</div>
|
||||
)}
|
||||
@@ -126,20 +126,22 @@ export default function GooseMessage({
|
||||
|
||||
{toolRequests.length > 0 && (
|
||||
<div className="relative flex flex-col w-full">
|
||||
<div className={`goose-message-tool bg-bgSubtle rounded px-2 py-2 mt-2`}>
|
||||
{toolRequests.map((toolRequest) => (
|
||||
{toolRequests.map((toolRequest) => (
|
||||
<div
|
||||
className={`goose-message-tool bg-bgSubtle rounded px-2 py-2 mb-2`}
|
||||
key={toolRequest.id}
|
||||
>
|
||||
<ToolCallWithResponse
|
||||
// If the message is resumed and not matched tool response, it means the tool is broken or cancelled.
|
||||
isCancelledMessage={
|
||||
messageIndex < messageHistoryIndex &&
|
||||
toolResponsesMap.get(toolRequest.id) == undefined
|
||||
}
|
||||
key={toolRequest.id}
|
||||
toolRequest={toolRequest}
|
||||
toolResponse={toolResponsesMap.get(toolRequest.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="text-xs text-textSubtle pt-1 transition-all duration-200 group-hover:-translate-y-4 group-hover:opacity-0">
|
||||
{timestamp}
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<div className={className}>
|
||||
@@ -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 (
|
||||
<ToolCallExpandable
|
||||
defaultExpanded={shouldExpand}
|
||||
forceExpand={shouldExpand}
|
||||
isStartExpanded={isShouldExpand}
|
||||
isForceExpand={isShouldExpand}
|
||||
label={
|
||||
<>
|
||||
<Dot size={2} loadingStatus={loadingStatus} />
|
||||
@@ -110,21 +121,24 @@ function ToolCallView({ isCancelledMessage, toolCall, toolResponse }: ToolCallVi
|
||||
{/* Tool Details */}
|
||||
{isToolDetails && (
|
||||
<div className="bg-bgStandard rounded-t mt-1">
|
||||
<ToolDetailsView toolCall={toolCall} />
|
||||
<ToolDetailsView toolCall={toolCall} isStartExpanded={isExpandToolDetails} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tool Output */}
|
||||
{!isCancelledMessage && (
|
||||
<>
|
||||
{toolResults.map(({ result, defaultExpanded }, index) => {
|
||||
{toolResults.map(({ result, isExpandToolResults }, index) => {
|
||||
const isLast = index === toolResults.length - 1;
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={`bg-bgStandard mt-1 ${isToolDetails ? 'rounded-t-none' : ''} ${isLast ? 'rounded-b' : ''}`}
|
||||
className={`bg-bgStandard mt-1
|
||||
${isToolDetails || index > 0 ? '' : 'rounded-t'}
|
||||
${isLast ? 'rounded-b' : ''}
|
||||
`}
|
||||
>
|
||||
<ToolResultView result={result} defaultExpanded={defaultExpanded} />
|
||||
<ToolResultView result={result} isStartExpanded={isExpandToolResults} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -139,11 +153,16 @@ interface ToolDetailsViewProps {
|
||||
name: string;
|
||||
arguments: Record<string, unknown>;
|
||||
};
|
||||
isStartExpanded: boolean;
|
||||
}
|
||||
|
||||
function ToolDetailsView({ toolCall }: ToolDetailsViewProps) {
|
||||
function ToolDetailsView({ toolCall, isStartExpanded }: ToolDetailsViewProps) {
|
||||
return (
|
||||
<ToolCallExpandable label="Tool Details" className="pl-[19px] py-1">
|
||||
<ToolCallExpandable
|
||||
label="Tool Details"
|
||||
className="pl-[19px] py-1"
|
||||
isStartExpanded={isStartExpanded}
|
||||
>
|
||||
{toolCall.arguments && <ToolCallArguments args={toolCall.arguments} />}
|
||||
</ToolCallExpandable>
|
||||
);
|
||||
@@ -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 (
|
||||
<ToolCallExpandable
|
||||
label={<span className="pl-[19px] py-1">Output</span>}
|
||||
defaultExpanded={defaultExpanded}
|
||||
isStartExpanded={isStartExpanded}
|
||||
>
|
||||
<div className="bg-bgApp rounded-b pl-[19px] pr-2 py-4">
|
||||
{result.type === 'text' && result.text && (
|
||||
|
||||
@@ -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({
|
||||
<ModeSection setView={setView} />
|
||||
{/*Session sharing*/}
|
||||
<SessionSharingSection />
|
||||
{/* Response Styles */}
|
||||
<ResponseStylesSection />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<div className="group hover:cursor-pointer">
|
||||
<div
|
||||
className="flex items-center justify-between text-textStandard py-2 px-4 hover:bg-bgSubtle"
|
||||
onClick={() => handleStyleChange(style.key)}
|
||||
>
|
||||
<div className="flex">
|
||||
<div>
|
||||
<h3 className="text-textStandard">{style.label}</h3>
|
||||
{showDescription && (
|
||||
<p className="text-xs text-textSubtle max-w-md mt-[2px]">{style.description}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="responseStyles"
|
||||
value={style.key}
|
||||
checked={checked}
|
||||
onChange={() => handleStyleChange(style.key)}
|
||||
className="peer sr-only"
|
||||
/>
|
||||
<div
|
||||
className="h-4 w-4 rounded-full border border-borderStandard
|
||||
peer-checked:border-[6px] peer-checked:border-black dark:peer-checked:border-white
|
||||
peer-checked:bg-white dark:peer-checked:bg-black
|
||||
transition-all duration-200 ease-in-out group-hover:border-borderProminent"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<section id="responseStyles" className="px-8">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<h2 className="text-xl font-medium text-textStandard">Response Styles</h2>
|
||||
</div>
|
||||
<div className="pb-8">
|
||||
<p className="text-sm text-textStandard mb-6">
|
||||
Choose how Goose should format and style its responses
|
||||
</p>
|
||||
<div>
|
||||
{all_response_styles.map((style) => (
|
||||
<ResponseStyleSelectionItem
|
||||
key={style.key}
|
||||
style={style}
|
||||
currentStyle={currentStyle}
|
||||
showDescription={true}
|
||||
handleStyleChange={handleStyleChange}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -80,13 +80,13 @@ export default function SessionSharingSection() {
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="session-sharing">
|
||||
<section id="session-sharing" className="px-8">
|
||||
{/*Title*/}
|
||||
<div className="flex justify-between items-center mb-2 px-8">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<h2 className="text-xl font-medium text-textStandard">Session sharing</h2>
|
||||
</div>
|
||||
|
||||
<div className="px-8">
|
||||
<div className="border-b border-borderSubtle pb-8">
|
||||
{envBaseUrlShare ? (
|
||||
<p className="text-sm text-textStandard mb-4">
|
||||
Session sharing is configured but fully opt-in — your sessions are only shared when you
|
||||
|
||||
Reference in New Issue
Block a user