Skip to content

Commit

Permalink
ActionsDetails: finer grained action state (#8940)
Browse files Browse the repository at this point in the history
* ActionsDetails: finer grained action state

* nit

* memoize the spinner

* nit/comment

* tweak
  • Loading branch information
spolu authored Nov 27, 2024
1 parent 3cadd72 commit 9fd096c
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 24 deletions.
17 changes: 17 additions & 0 deletions front/components/assistant/conversation/AgentMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ interface AgentMessageProps {
size: ConversationMessageSizeType;
}

export type AgentStateClassification = "thinking" | "acting" | "done";

/**
*
* @param isInModal is the conversation happening in a side modal, i.e. when
Expand Down Expand Up @@ -136,10 +138,18 @@ export function AgentMessage({
}
})();

const [lastAgentStateClassification, setLastAgentStateClassification] =
useState<AgentStateClassification>(shouldStream ? "thinking" : "done");
const [lastTokenClassification, setLastTokenClassification] = useState<
null | "tokens" | "chain_of_thought"
>(null);

useEffect(() => {
if (message.status !== "created") {
setLastAgentStateClassification("done");
}
}, [message.status]);

const buildEventSourceURL = useCallback(
(lastEvent: string | null) => {
const esURL = `/api/w/${owner.sId}/assistant/conversations/${conversationId}/messages/${message.sId}/events`;
Expand Down Expand Up @@ -187,6 +197,7 @@ export function AgentMessage({
setStreamedAgentMessage((m) => {
return { ...updateMessageWithAction(m, event.action) };
});
setLastAgentStateClassification("thinking");
break;
case "retrieval_params":
case "dust_app_run_params":
Expand All @@ -201,17 +212,20 @@ export function AgentMessage({
setStreamedAgentMessage((m) => {
return updateMessageWithAction(m, event.action);
});
setLastAgentStateClassification("acting");
break;
case "agent_error":
setStreamedAgentMessage((m) => {
return { ...m, status: "failed", error: event.error };
});
setLastAgentStateClassification("done");
break;

case "agent_generation_cancelled":
setStreamedAgentMessage((m) => {
return { ...m, status: "cancelled" };
});
setLastAgentStateClassification("done");
break;
case "agent_message_success": {
setStreamedAgentMessage((m) => {
Expand All @@ -220,6 +234,7 @@ export function AgentMessage({
...event.message,
};
});
setLastAgentStateClassification("done");
break;
}

Expand Down Expand Up @@ -249,6 +264,7 @@ export function AgentMessage({
default:
assertNever(event);
}
setLastAgentStateClassification("thinking");
break;
}

Expand Down Expand Up @@ -528,6 +544,7 @@ export function AgentMessage({
<div className="flex flex-col gap-y-4">
<AgentMessageActions
agentMessage={agentMessage}
lastAgentStateClassification={lastAgentStateClassification}
size={size}
owner={owner}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,60 @@ import type {
AgentMessageType,
LightWorkspaceType,
} from "@dust-tt/types";
import { assertNever } from "@dust-tt/types";
import { useEffect, useMemo, useState } from "react";

import { getActionSpecification } from "@app/components/actions/types";
import { AgentMessageActionsDrawer } from "@app/components/assistant/conversation/actions/AgentMessageActionsDrawer";
import type { AgentStateClassification } from "@app/components/assistant/conversation/AgentMessage";
import { classNames } from "@app/lib/utils";

interface AgentMessageActionsProps {
agentMessage: AgentMessageType;
lastAgentStateClassification: AgentStateClassification;
size?: ConversationMessageSizeType;
owner: LightWorkspaceType;
}

export function AgentMessageActions({
agentMessage,
lastAgentStateClassification,
owner,
size = "normal",
}: AgentMessageActionsProps) {
const [chipLabel, setChipLabel] = useState<string | undefined>("Thinking");
const [isActionDrawerOpened, setIsActionDrawerOpened] = useState(false);

// We're thinking or acting if the message status is still "created" and we don't have content
// yet. Despite our work on chain of thoughts events, it's still possible for content to be
// emitted before actions in which case we will think we're not thinking or acting until an action
// gets emitted in which case the content will get requalified as chain of thoughts and this will
// switch back to true.
useEffect(() => {
switch (lastAgentStateClassification) {
case "thinking":
setChipLabel("Thinking");
break;
case "acting":
if (agentMessage.actions.length > 0) {
setChipLabel(renderActionName(agentMessage.actions));
}
break;
case "done":
setChipLabel(undefined);
break;
default:
assertNever(lastAgentStateClassification);
}
}, [lastAgentStateClassification, agentMessage.actions]);

const isThinkingOrActing = useMemo(
() => agentMessage.status === "created",
[agentMessage.status]
);

useEffect(() => {
if (isThinkingOrActing) {
if (agentMessage.actions.length === 0) {
setChipLabel("Thinking");
} else {
setChipLabel(renderActionName(agentMessage.actions));
}
} else {
setChipLabel(undefined);
}
}, [isThinkingOrActing, agentMessage.actions]);

return (
<div className="flex flex-col items-start gap-y-4">
<AgentMessageActionsDrawer
actions={agentMessage.actions}
isOpened={isActionDrawerOpened}
onClose={() => setIsActionDrawerOpened(false)}
isStreaming={isThinkingOrActing || agentMessage.actions.length === 0}
isActing={lastAgentStateClassification === "acting"}
owner={owner}
/>
<ActionDetails
Expand All @@ -79,12 +85,19 @@ function ActionDetails({
onClick: () => void;
size: ConversationMessageSizeType;
}) {
// We memoize the spinner as otherwise its state gets resetted on each token emission (despite
// memoization of label in the parent component).
const MemoizedSpinner = useMemo(
() => <Spinner variant="dark" size="xs" />,
[]
);

if (!label && (!isActionStepDone || !hasActions)) {
return null;
}

return label ? (
<div key={label} className="animate-fadeIn duration-1000 fade-out">
<div key={label}>
<Chip size="sm" color="slate" isBusy>
<div
className={classNames(
Expand All @@ -93,8 +106,14 @@ function ActionDetails({
)}
onClick={hasActions ? onClick : undefined}
>
<Spinner variant="dark" size="xs" />
{label}
{MemoizedSpinner}
{label === "Thinking" ? (
<span>{label}</span>
) : (
<>
Thinking <span className="text-regular">{label}</span>
</>
)}
</div>
</Chip>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import { getActionSpecification } from "@app/components/actions/types";
interface AgentMessageActionsDrawerProps {
actions: AgentActionType[];
isOpened: boolean;
isStreaming: boolean;
isActing: boolean;
onClose: () => void;
owner: LightWorkspaceType;
}

export function AgentMessageActionsDrawer({
actions,
isOpened,
isStreaming,
isActing,
onClose,
owner,
}: AgentMessageActionsDrawerProps) {
Expand Down Expand Up @@ -71,7 +71,7 @@ export function AgentMessageActionsDrawer({
</div>
);
})}
{isStreaming && (
{isActing && (
<div className="flex justify-center">
<Spinner variant="color" />
</div>
Expand Down

0 comments on commit 9fd096c

Please sign in to comment.