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
9 changes: 7 additions & 2 deletions apps/web/src/components/chat/MessagesTimeline.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import {
formatSearchToolLabel,
} from "@t3tools/shared/toolActivity";
import { isWindowsAbsolutePath } from "@t3tools/shared/path";
import { displayModeRows } from "../../fork/displayMode/displayModeTimeline";

function timelineEntryRunId(entry: TimelineEntry): RunId | null {
if (entry.kind === "message") {
Expand Down Expand Up @@ -1508,6 +1509,7 @@ export function deriveMessagesTimelineRows(input: {
expandedAttemptIds?: ReadonlySet<RunAttemptId>;
expandedWorkGroupIds?: ReadonlySet<string>;
isWorking: boolean;
repliesChosen?: boolean;
/** Native subagent work has no app run; runless entries form the live response. */
runlessWorkActive?: boolean;
activeTurnStartedAt?: string | null;
Expand Down Expand Up @@ -2261,8 +2263,11 @@ export function deriveMessagesTimelineRows(input: {
}
}

const result = attachTrailingToolGroupsToAssistant(
attachCreatedThreadSummaries(nextRows, timelineEntries, collapsedFoldMemberIds),
const result = displayModeRows(
attachTrailingToolGroupsToAssistant(
attachCreatedThreadSummaries(nextRows, timelineEntries, collapsedFoldMemberIds),
),
input.repliesChosen === true,
);
return result.map((row, index) => {
if (timelineRowIsWorkLog(row) && timelineRowIsWorkLog(result[index + 1])) {
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/components/chat/MessagesTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ interface MessagesTimelineProps {
findQuery?: string;
activeFindMatch?: ThreadFindMatch | null;
findNavigationId?: number;
/** The rows already hold only the replies to show, so zen keeps every one of them. */
/** Rows hold only chosen replies, so zen keeps them all. No Working or Thinking row is drawn. */
repliesChosen?: boolean;
}

Expand Down Expand Up @@ -665,6 +665,7 @@ const ConversationTimeline = memo(function ConversationTimeline({
expandedAttemptIds: visibleExpandedAttemptIds,
expandedWorkGroupIds,
isWorking,
repliesChosen,
runlessWorkActive,
activeTurnStartedAt,
turnDiffSummaries,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import { LegendListRef } from "@legendapp/list/react";
import { MESSAGES_ONLY_DISPLAY_MODE, NORMAL_DISPLAY_MODE } from "@t3tools/shared/displayModes";
import * as DateTime from "effect/DateTime";
import { createRef, type ComponentProps } from "react";
import { describe, expect, it } from "vite-plus/test";
import { describe, expect, it, vi } from "vite-plus/test";

import "~/index.css";

import { render } from "../../../test/render";
import { MessagesTimeline } from "../../components/chat/MessagesTimeline";
import { deriveMessagesTimelineRows } from "../../components/chat/MessagesTimeline.logic";
import { __setClientSettingsForTests } from "../../hooks/useSettings";
import { deriveTimelineEntriesFromVisibleTurnItems } from "../../session-logic";
import { applyDisplayMode } from "./displayModeTimeline";
Expand Down Expand Up @@ -132,12 +133,14 @@ function props(): Omit<TimelineProps, "timelineEntries"> {
async function renderTimeline(
timelineEntries: TimelineProps["timelineEntries"],
repliesChosen = false,
extra: Partial<TimelineProps> = {},
) {
const container = document.createElement("div");
container.style.cssText = "height: 900px; width: 800px; display: flex; flex-direction: column;";
return render(
<MessagesTimeline
{...props()}
{...extra}
timelineEntries={timelineEntries}
repliesChosen={repliesChosen}
/>,
Expand Down Expand Up @@ -180,6 +183,52 @@ describe("Messages only timeline", () => {
expect(text).not.toContain("to the user");
});

describe("while the agent works", () => {
const working = { isWorking: true, activeTurnStartedAt: time(0) };
const asked = entriesOf([userMessage("work-ask", 0, "Please ship it.")]);
const liveRow = (container: HTMLElement, kind: "working" | "thinking") =>
container.querySelector(`[data-timeline-row-kind="${kind}"]`);

it("draws the Working and Thinking rows in Normal", async () => {
await using screen = await renderTimeline(
[...applyDisplayMode(NORMAL_DISPLAY_MODE, asked)],
false,
working,
);
await vi.waitFor(() => {
expect(liveRow(screen.container, "working")).not.toBeNull();
expect(liveRow(screen.container, "thinking")).not.toBeNull();
});
});

it("draws neither in a mode that chooses its replies", async () => {
await using screen = await renderTimeline(
[...applyDisplayMode(MESSAGES_ONLY_DISPLAY_MODE, asked)],
true,
working,
);
await expect.element(screen.getByText("Please ship it.")).toBeVisible();
expect(liveRow(screen.container, "working")).toBeNull();
expect(liveRow(screen.container, "thinking")).toBeNull();
});

it("groups a steer with the message before it once the Working row is gone", () => {
const rows = deriveMessagesTimelineRows({
timelineEntries: entriesOf([
userMessage("steer-ask", 0, "Please ship it."),
userMessage("steer-more", 1, "Run the tests first.", { inputIntent: "steer" }),
]),
isWorking: true,
repliesChosen: true,
turnDiffSummaries: [],
supportsConversationRollback: false,
});
expect(rows.map((row) => row.kind)).toEqual(["message", "message"]);
expect(rows[0]).toMatchObject({ groupedWithNext: true });
expect(rows[1]).toMatchObject({ groupedWithPrevious: true });
});
});

describe("in a finished turn with two sends", () => {
const send = (id: string, second: number, text: string): OrchestrationV2TurnItem => ({
...base(id, second),
Expand Down
11 changes: 11 additions & 0 deletions apps/web/src/fork/displayMode/displayModeTimeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
type VisibleCore,
} from "@t3tools/shared/displayModes";

import type { MessagesTimelineRow } from "../../components/chat/MessagesTimeline.logic";
import type { TimelineEntry } from "../../session-logic";
import type { SubThreadSummaries, SubThreadSummary } from "../subThreads/subThreadsState";

Expand Down Expand Up @@ -214,3 +215,13 @@ export function applyDisplayMode(
? entries
: rows;
}

/** A mode that chooses its replies draws no Working or Thinking row. */
export function displayModeRows(
rows: MessagesTimelineRow[],
repliesChosen: boolean,
): MessagesTimelineRow[] {
return repliesChosen
? rows.filter((row) => row.kind !== "working" && row.kind !== "thinking")
: rows;
}
Loading