Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { expect, screen, waitFor, within } from "storybook/test";
import { StreamingOutput } from "./StreamingOutput";
import { AssistantOutput } from "./AssistantOutput";
import {
buildLiveStatus,
buildReconnectState,
buildRetryState,
buildStreamRenderState,
pinFixtureClock,
type StoryStreamRenderState,
} from "./storyFixtures";

// StreamingOutput renders inside a ConversationItem > Message > MessageContent
// chain, but it's self-contained enough to render standalone.
// Mirrors how ConversationTimeline normalizes a live row before handing it to
// AssistantOutput.
const LiveAssistantOutput = ({
streamState,
streamTools,
liveStatus,
}: StoryStreamRenderState) => (
<AssistantOutput
keyPrefix="stream"
blocks={streamState?.blocks ?? []}
tools={streamTools}
isStreaming={liveStatus.phase === "streaming"}
liveStatus={liveStatus}
/>
);

const meta: Meta<typeof StreamingOutput> = {
title: "pages/AgentsPage/ChatConversation/StreamingOutput",
component: StreamingOutput,
const meta: Meta<typeof LiveAssistantOutput> = {
title: "pages/AgentsPage/ChatConversation/AssistantOutput",
component: LiveAssistantOutput,
beforeEach: pinFixtureClock,
};
export default meta;
type Story = StoryObj<typeof StreamingOutput>;
type Story = StoryObj<typeof LiveAssistantOutput>;

/** Transport reconnects render a non-terminal reconnecting callout. */
export const ReconnectingAfterDisconnect: Story = {
Expand Down Expand Up @@ -245,11 +259,7 @@ export const StartingShowsThinkingActivity: Story = {
};

export const ResponseDoesNotRenderActivitySlot: Story = {
args: {
streamState: responseStreamState.streamState,
streamTools: responseStreamState.streamTools,
liveStatus: responseStreamState.liveStatus,
},
args: responseStreamState,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.queryByTestId("live-activity-slot")).not.toBeInTheDocument();
Expand All @@ -258,22 +268,20 @@ export const ResponseDoesNotRenderActivitySlot: Story = {

/** Tool-only streams use running tool affordances instead of generic thinking. */
export const RunningToolsSuppressThinkingActivity: Story = {
args: {
...buildStreamRenderState([
{
type: "tool-call",
tool_name: "execute",
tool_call_id: "tc-1",
args: { command: "ls -la" },
},
{
type: "tool-call",
tool_name: "read_file",
tool_call_id: "tc-2",
args: { path: "README.md" },
},
]),
},
args: buildStreamRenderState([
{
type: "tool-call",
tool_name: "execute",
tool_call_id: "tc-1",
args: { command: "ls -la" },
},
{
type: "tool-call",
tool_name: "read_file",
tool_call_id: "tc-2",
args: { path: "README.md" },
},
]),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.queryByTestId("live-activity-slot")).not.toBeInTheDocument();
Expand Down Expand Up @@ -334,18 +342,10 @@ export const EditFilesEmptyDeltaKeepsRunningHeight: Story = {
return (
<div className="flex flex-col gap-2">
<div data-testid="running-edit-files">
<StreamingOutput
streamState={editFilesRunningState.streamState}
streamTools={editFilesRunningState.streamTools}
liveStatus={editFilesRunningState.liveStatus}
/>
<LiveAssistantOutput {...editFilesRunningState} />
</div>
<div data-testid="empty-delta-edit-files">
<StreamingOutput
streamState={editFilesEmptyDeltaState.streamState}
streamTools={editFilesEmptyDeltaState.streamTools}
liveStatus={editFilesEmptyDeltaState.liveStatus}
/>
<LiveAssistantOutput {...editFilesEmptyDeltaState} />
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { FC } from "react";
import { Shimmer } from "../ChatElements";
import { ToolIcon } from "../ChatElements/tools/ToolIcon";
import { ChatStatusCallout } from "./ChatStatusCallout";
import type { LiveStatusModel } from "./liveStatusModel";
import { BlockList, type BlockListProps } from "./MessageBlocks";
import { shouldShowGenericThinking } from "./streamingActivity";

const LiveActivitySlot: FC = () => (
<div
data-testid="live-activity-slot"
className="flex h-6 items-center gap-2 text-content-secondary"
>
<ToolIcon name="thinking" />
<Shimmer as="span" className="text-[13px] leading-6">
Thinking
</Shimmer>
</div>
);

type AssistantOutputProps = BlockListProps & {
// Present only while the turn is still live. Drives the retry/reconnect
// callout and the generic thinking indicator.
liveStatus?: LiveStatusModel;
};

/**
* Renders assistant output from already-normalized blocks and tools, so a live
* turn and the durable message that replaces it render through the same path.
*/
export const AssistantOutput: FC<AssistantOutputProps> = ({
liveStatus,
...blockProps
}) => {
const { blocks, tools } = blockProps;
const callout =
liveStatus?.phase === "retrying" || liveStatus?.phase === "reconnecting"
? liveStatus
: undefined;

return (
<div className="relative flex flex-col gap-2 overflow-visible">
<BlockList {...blockProps} />
{callout && <ChatStatusCallout status={callout} />}
{liveStatus &&
shouldShowGenericThinking({ liveStatus, blocks, tools }) && (
<LiveActivitySlot />
)}
</div>
);
};
Loading
Loading