Expanded ToolCall options (#2457)
This commit is contained in:
@@ -105,13 +105,13 @@ export default function GooseMessage({
|
|||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
{textContent && (
|
{textContent && (
|
||||||
<div className="flex flex-col group">
|
<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 ref={contentRef}>{<MarkdownContent content={textContent} />}</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Only show MessageCopyLink if there's text content and no tool requests/responses */}
|
{/* 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 && (
|
{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}
|
{timestamp}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -126,20 +126,22 @@ export default function GooseMessage({
|
|||||||
|
|
||||||
{toolRequests.length > 0 && (
|
{toolRequests.length > 0 && (
|
||||||
<div className="relative flex flex-col w-full">
|
<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
|
<ToolCallWithResponse
|
||||||
// If the message is resumed and not matched tool response, it means the tool is broken or cancelled.
|
// If the message is resumed and not matched tool response, it means the tool is broken or cancelled.
|
||||||
isCancelledMessage={
|
isCancelledMessage={
|
||||||
messageIndex < messageHistoryIndex &&
|
messageIndex < messageHistoryIndex &&
|
||||||
toolResponsesMap.get(toolRequest.id) == undefined
|
toolResponsesMap.get(toolRequest.id) == undefined
|
||||||
}
|
}
|
||||||
key={toolRequest.id}
|
|
||||||
toolRequest={toolRequest}
|
toolRequest={toolRequest}
|
||||||
toolResponse={toolResponsesMap.get(toolRequest.id)}
|
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">
|
<div className="text-xs text-textSubtle pt-1 transition-all duration-200 group-hover:-translate-y-4 group-hover:opacity-0">
|
||||||
{timestamp}
|
{timestamp}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -34,24 +34,24 @@ export default function ToolCallWithResponse({
|
|||||||
|
|
||||||
interface ToolCallExpandableProps {
|
interface ToolCallExpandableProps {
|
||||||
label: string | React.ReactNode;
|
label: string | React.ReactNode;
|
||||||
defaultExpanded?: boolean;
|
isStartExpanded?: boolean;
|
||||||
forceExpand?: boolean;
|
isForceExpand?: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToolCallExpandable({
|
function ToolCallExpandable({
|
||||||
label,
|
label,
|
||||||
defaultExpanded = false,
|
isStartExpanded = false,
|
||||||
forceExpand,
|
isForceExpand,
|
||||||
children,
|
children,
|
||||||
className = '',
|
className = '',
|
||||||
}: ToolCallExpandableProps) {
|
}: ToolCallExpandableProps) {
|
||||||
const [isExpanded, setIsExpanded] = React.useState(defaultExpanded);
|
const [isExpanded, setIsExpanded] = React.useState(isStartExpanded);
|
||||||
const toggleExpand = () => setIsExpanded((prev) => !prev);
|
const toggleExpand = () => setIsExpanded((prev) => !prev);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (forceExpand) setIsExpanded(true);
|
if (isForceExpand) setIsExpanded(true);
|
||||||
}, [forceExpand]);
|
}, [isForceExpand]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
@@ -74,12 +74,23 @@ interface ToolCallViewProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ToolCallView({ isCancelledMessage, toolCall, toolResponse }: 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 isToolDetails = Object.entries(toolCall?.arguments).length > 0;
|
||||||
const loadingStatus: LoadingStatus = !toolResponse?.toolResult.status
|
const loadingStatus: LoadingStatus = !toolResponse?.toolResult.status
|
||||||
? 'loading'
|
? 'loading'
|
||||||
: toolResponse?.toolResult.status;
|
: toolResponse?.toolResult.status;
|
||||||
|
|
||||||
const toolResults: { result: Content; defaultExpanded: boolean }[] =
|
const toolResults: { result: Content; isExpandToolResults: boolean }[] =
|
||||||
loadingStatus === 'success' && Array.isArray(toolResponse?.toolResult.value)
|
loadingStatus === 'success' && Array.isArray(toolResponse?.toolResult.value)
|
||||||
? toolResponse.toolResult.value
|
? toolResponse.toolResult.value
|
||||||
.filter((item) => {
|
.filter((item) => {
|
||||||
@@ -88,16 +99,16 @@ function ToolCallView({ isCancelledMessage, toolCall, toolResponse }: ToolCallVi
|
|||||||
})
|
})
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
result: 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 (
|
return (
|
||||||
<ToolCallExpandable
|
<ToolCallExpandable
|
||||||
defaultExpanded={shouldExpand}
|
isStartExpanded={isShouldExpand}
|
||||||
forceExpand={shouldExpand}
|
isForceExpand={isShouldExpand}
|
||||||
label={
|
label={
|
||||||
<>
|
<>
|
||||||
<Dot size={2} loadingStatus={loadingStatus} />
|
<Dot size={2} loadingStatus={loadingStatus} />
|
||||||
@@ -110,21 +121,24 @@ function ToolCallView({ isCancelledMessage, toolCall, toolResponse }: ToolCallVi
|
|||||||
{/* Tool Details */}
|
{/* Tool Details */}
|
||||||
{isToolDetails && (
|
{isToolDetails && (
|
||||||
<div className="bg-bgStandard rounded-t mt-1">
|
<div className="bg-bgStandard rounded-t mt-1">
|
||||||
<ToolDetailsView toolCall={toolCall} />
|
<ToolDetailsView toolCall={toolCall} isStartExpanded={isExpandToolDetails} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Tool Output */}
|
{/* Tool Output */}
|
||||||
{!isCancelledMessage && (
|
{!isCancelledMessage && (
|
||||||
<>
|
<>
|
||||||
{toolResults.map(({ result, defaultExpanded }, index) => {
|
{toolResults.map(({ result, isExpandToolResults }, index) => {
|
||||||
const isLast = index === toolResults.length - 1;
|
const isLast = index === toolResults.length - 1;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={index}
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -139,11 +153,16 @@ interface ToolDetailsViewProps {
|
|||||||
name: string;
|
name: string;
|
||||||
arguments: Record<string, unknown>;
|
arguments: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
isStartExpanded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToolDetailsView({ toolCall }: ToolDetailsViewProps) {
|
function ToolDetailsView({ toolCall, isStartExpanded }: ToolDetailsViewProps) {
|
||||||
return (
|
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} />}
|
{toolCall.arguments && <ToolCallArguments args={toolCall.arguments} />}
|
||||||
</ToolCallExpandable>
|
</ToolCallExpandable>
|
||||||
);
|
);
|
||||||
@@ -151,14 +170,14 @@ function ToolDetailsView({ toolCall }: ToolDetailsViewProps) {
|
|||||||
|
|
||||||
interface ToolResultViewProps {
|
interface ToolResultViewProps {
|
||||||
result: Content;
|
result: Content;
|
||||||
defaultExpanded: boolean;
|
isStartExpanded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToolResultView({ result, defaultExpanded }: ToolResultViewProps) {
|
function ToolResultView({ result, isStartExpanded }: ToolResultViewProps) {
|
||||||
return (
|
return (
|
||||||
<ToolCallExpandable
|
<ToolCallExpandable
|
||||||
label={<span className="pl-[19px] py-1">Output</span>}
|
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">
|
<div className="bg-bgApp rounded-b pl-[19px] pr-2 py-4">
|
||||||
{result.type === 'text' && result.text && (
|
{result.type === 'text' && result.text && (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import ExtensionsSection from './extensions/ExtensionsSection';
|
|||||||
import ModelsSection from './models/ModelsSection';
|
import ModelsSection from './models/ModelsSection';
|
||||||
import { ModeSection } from './mode/ModeSection';
|
import { ModeSection } from './mode/ModeSection';
|
||||||
import SessionSharingSection from './sessions/SessionSharingSection';
|
import SessionSharingSection from './sessions/SessionSharingSection';
|
||||||
|
import { ResponseStylesSection } from './response_styles/ResponseStylesSection';
|
||||||
import { ExtensionConfig } from '../../api';
|
import { ExtensionConfig } from '../../api';
|
||||||
import MoreMenuLayout from '../more_menu/MoreMenuLayout';
|
import MoreMenuLayout from '../more_menu/MoreMenuLayout';
|
||||||
|
|
||||||
@@ -47,6 +48,8 @@ export default function SettingsView({
|
|||||||
<ModeSection setView={setView} />
|
<ModeSection setView={setView} />
|
||||||
{/*Session sharing*/}
|
{/*Session sharing*/}
|
||||||
<SessionSharingSection />
|
<SessionSharingSection />
|
||||||
|
{/* Response Styles */}
|
||||||
|
<ResponseStylesSection />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 (
|
return (
|
||||||
<section id="session-sharing">
|
<section id="session-sharing" className="px-8">
|
||||||
{/*Title*/}
|
{/*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>
|
<h2 className="text-xl font-medium text-textStandard">Session sharing</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-8">
|
<div className="border-b border-borderSubtle pb-8">
|
||||||
{envBaseUrlShare ? (
|
{envBaseUrlShare ? (
|
||||||
<p className="text-sm text-textStandard mb-4">
|
<p className="text-sm text-textStandard mb-4">
|
||||||
Session sharing is configured but fully opt-in — your sessions are only shared when you
|
Session sharing is configured but fully opt-in — your sessions are only shared when you
|
||||||
|
|||||||
Reference in New Issue
Block a user