Skip to content

Commit 38f495c

Browse files
fix(chat): stop newly provisioned thread runs
1 parent 56d64e3 commit 38f495c

3 files changed

Lines changed: 36 additions & 15 deletions

File tree

apps/chat/components/multimodal-input.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -622,10 +622,10 @@ function PureMultimodalInput({
622622

623623
const handleStop = useCallback(() => {
624624
if (session?.user && lastMessageId) {
625-
stopStreamMutation.mutate({ messageId: lastMessageId });
625+
stopStreamMutation.mutate({ chatId, messageId: lastMessageId });
626626
}
627627
stopHelper?.();
628-
}, [lastMessageId, session?.user, stopHelper, stopStreamMutation]);
628+
}, [chatId, lastMessageId, session?.user, stopHelper, stopStreamMutation]);
629629

630630
return (
631631
<div className="relative">

apps/chat/lib/db/queries.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,10 +1074,13 @@ export async function updateMessageCanceledAt({
10741074
canceledAt: Date | null;
10751075
}) {
10761076
try {
1077-
return await db
1077+
const updatedMessages = await db
10781078
.update(message)
10791079
.set({ canceledAt })
1080-
.where(eq(message.id, messageId));
1080+
.where(eq(message.id, messageId))
1081+
.returning({ id: message.id });
1082+
1083+
return updatedMessages.length > 0;
10811084
} catch (error) {
10821085
logger.error({ error, messageId }, "updateMessageCanceledAt failed");
10831086
throw error;

apps/chat/trpc/routers/chat.router.ts

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -188,30 +188,48 @@ export const chatRouter = createTRPCRouter({
188188
stopStream: protectedProcedure
189189
.input(
190190
z.object({
191+
chatId: z.string().uuid(),
191192
messageId: z.string().uuid(),
192193
})
193194
)
194195
.mutation(async ({ ctx, input }) => {
195-
const [msg] = await getMessageById({ id: input.messageId });
196-
if (!msg) {
197-
throw new TRPCError({
198-
code: "NOT_FOUND",
199-
message: "Message not found",
200-
});
196+
// A new chat can still be generating its title when the client stops.
197+
// Wait for its reserved assistant row so request ordering cannot lose cancellation.
198+
const deadline = Date.now() + 30_000;
199+
let retryDelay = 25;
200+
let chat = await getChatById({ id: input.chatId });
201+
202+
while (!chat && Date.now() < deadline) {
203+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
204+
retryDelay = Math.min(retryDelay * 2, 250);
205+
chat = await getChatById({ id: input.chatId });
201206
}
202207

203-
const chat = await getChatById({ id: msg.chatId });
204208
if (!chat || chat.userId !== ctx.user.id) {
205209
throw new TRPCError({
206210
code: "NOT_FOUND",
207211
message: "Chat not found or access denied",
208212
});
209213
}
210214

211-
await updateMessageCanceledAt({
212-
messageId: input.messageId,
213-
canceledAt: new Date(),
214-
});
215+
const canceledAt = new Date();
216+
217+
while (
218+
!(await updateMessageCanceledAt({
219+
messageId: input.messageId,
220+
canceledAt,
221+
}))
222+
) {
223+
if (Date.now() >= deadline) {
224+
throw new TRPCError({
225+
code: "NOT_FOUND",
226+
message: "Message not found",
227+
});
228+
}
229+
230+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
231+
retryDelay = Math.min(retryDelay * 2, 250);
232+
}
215233

216234
return { success: true };
217235
}),

0 commit comments

Comments
 (0)