Skip to content

Commit 5005eb3

Browse files
committed
feat(chat): add pending assistant response indicator
- detect when last message is from user and response is pending - show "Starting..." indicator while awaiting assistant response - include pending state in overall isWorking condition - simplify hasServerAcknowledgedLocalDispatch to check only completedAt
1 parent 26c242c commit 5005eb3

4 files changed

Lines changed: 24 additions & 11 deletions

File tree

apps/web/src/components/ChatView.logic.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,12 +350,13 @@ export function hasServerAcknowledgedLocalDispatch(input: {
350350
}
351351

352352
const latestTurn = input.latestTurn ?? null;
353-
353+
const latestTurnCompletedAt = latestTurn?.completedAt ?? null;
354+
if (latestTurnCompletedAt === null) {
355+
return false;
356+
}
354357
return (
355358
input.localDispatch.latestTurnTurnId !== (latestTurn?.turnId ?? null) ||
356-
input.localDispatch.latestTurnRequestedAt !== (latestTurn?.requestedAt ?? null) ||
357-
input.localDispatch.latestTurnStartedAt !== (latestTurn?.startedAt ?? null) ||
358-
input.localDispatch.latestTurnCompletedAt !== (latestTurn?.completedAt ?? null)
359+
input.localDispatch.latestTurnCompletedAt !== latestTurnCompletedAt
359360
);
360361
}
361362

apps/web/src/components/ChatView.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,21 +1396,28 @@ export default function ChatView({ threadId, environmentId: environmentIdProp }:
13961396
});
13971397
const hasInFlightTurn = Boolean(activeLatestTurn && !activeLatestTurn.completedAt);
13981398
const isSessionStarting = activeThread?.session?.orchestrationStatus === "starting";
1399+
const lastActiveMessage = activeThread?.messages[activeThread.messages.length - 1];
1400+
const hasPendingAssistantResponse = Boolean(
1401+
lastActiveMessage?.role === "user" &&
1402+
(!activeLatestTurn || Boolean(activeLatestTurn.completedAt)),
1403+
);
13991404
const isWorking =
14001405
phase === "running" ||
14011406
isSendBusy ||
14021407
isConnecting ||
14031408
isRevertingCheckpoint ||
14041409
hasInFlightTurn ||
1405-
isSessionStarting;
1410+
isSessionStarting ||
1411+
hasPendingAssistantResponse;
14061412
const isCompacting = activeThread?.session?.compacting === true;
14071413
const isThreadHydrating = activeThread !== undefined && !isThreadHydrated(activeThread);
14081414
const nowIso = new Date(nowTick).toISOString();
1409-
const activeWorkStartedAt = deriveActiveWorkStartedAt(
1410-
activeLatestTurn,
1411-
activeThread?.session ?? null,
1412-
localDispatchStartedAt,
1413-
);
1415+
const activeWorkStartedAt =
1416+
deriveActiveWorkStartedAt(
1417+
activeLatestTurn,
1418+
activeThread?.session ?? null,
1419+
localDispatchStartedAt,
1420+
) ?? (hasPendingAssistantResponse ? (lastActiveMessage?.createdAt ?? null) : null);
14141421
const isComposerApprovalState = activePendingApproval !== null;
14151422
const hasComposerHeader =
14161423
isComposerApprovalState ||
@@ -4870,6 +4877,7 @@ export default function ChatView({ threadId, environmentId: environmentIdProp }:
48704877
workspaceRoot={activeWorkspaceRoot}
48714878
isSendBusy={isSendBusy}
48724879
isSessionStarting={isSessionStarting}
4880+
hasPendingAssistantResponse={hasPendingAssistantResponse}
48734881
isPreparingWorktree={isPreparingWorktree}
48744882
isCompacting={isCompacting}
48754883
onSubagentSelect={onSubagentSelect}

apps/web/src/components/chat/MessagesTimeline.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ describe("MessagesTimeline", () => {
102102
workspaceRoot={undefined}
103103
isSendBusy={false}
104104
isSessionStarting={false}
105+
hasPendingAssistantResponse={false}
105106
isPreparingWorktree={false}
106107
isCompacting={false}
107108
onSubagentSelect={() => {}}
@@ -167,6 +168,7 @@ describe("MessagesTimeline", () => {
167168
workspaceRoot={undefined}
168169
isSendBusy={false}
169170
isSessionStarting={false}
171+
hasPendingAssistantResponse={false}
170172
isPreparingWorktree={false}
171173
isCompacting={false}
172174
onSubagentSelect={() => {}}

apps/web/src/components/chat/MessagesTimeline.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ interface MessagesTimelineProps {
154154
workspaceRoot: string | undefined;
155155
isSendBusy: boolean;
156156
isSessionStarting: boolean;
157+
hasPendingAssistantResponse: boolean;
157158
isPreparingWorktree: boolean;
158159
isCompacting: boolean;
159160
onSubagentSelect: (taskId: string) => void;
@@ -211,6 +212,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({
211212
workspaceRoot,
212213
isSendBusy,
213214
isSessionStarting,
215+
hasPendingAssistantResponse,
214216
isPreparingWorktree,
215217
isCompacting,
216218
onSubagentSelect,
@@ -619,7 +621,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({
619621
<span className="shiny-text pt-1 text-xs font-semibold text-primary/60">
620622
{isPreparingWorktree
621623
? "Preparing worktree\u2026"
622-
: isSendBusy || isSessionStarting
624+
: isSendBusy || isSessionStarting || hasPendingAssistantResponse
623625
? "Starting\u2026"
624626
: isCompacting
625627
? "Compacting\u2026"

0 commit comments

Comments
 (0)