-
Notifications
You must be signed in to change notification settings - Fork 3.8k
fix: 🐛 Fix the cancel button #5662
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,7 @@ import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; | |
| import { selectCurrentToolCall } from "../../../redux/selectors/selectCurrentToolCall"; | ||
| import { callCurrentTool } from "../../../redux/thunks/callCurrentTool"; | ||
| import { cancelCurrentToolCall } from "../../../redux/thunks/cancelCurrentToolCall"; | ||
| import { cancelStream } from "../../../redux/thunks/cancelStream"; | ||
| import { cancelButton } from "../../../redux/thunks/cancelButton"; | ||
| import { | ||
| getAltKeyLabel, | ||
| getFontSize, | ||
|
|
@@ -96,7 +96,7 @@ export function LumpToolbar() { | |
| <StopButton | ||
| className="text-gray-400" | ||
| onClick={() => { | ||
| dispatch(cancelStream()); | ||
| dispatch(cancelButton()); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. semantic nitpick, thinking cancelStream makes more sense as the action performed, and since dispatched on keyboard events not just button click. also reduces file changes |
||
| }} | ||
| > | ||
| {/* JetBrains overrides cmd+backspace, so we have to use another shortcut */} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,8 @@ import { renderChatMessage } from "core/util/messageContent"; | |
| import { findUriInDirs, getUriPathBasename } from "core/util/uri"; | ||
| import { v4 as uuidv4 } from "uuid"; | ||
| import { RootState } from "../store"; | ||
| import { cancelToolCallAndAddResult } from "../thunks/cancelToolCallAndAddResult"; | ||
| import { clearLastEmptyResponse } from "../thunks/clearLastEmptyResponse"; | ||
| import { streamResponseThunk } from "../thunks/streamResponse"; | ||
| import { findCurrentToolCall, findToolCall } from "../util"; | ||
|
|
||
|
|
@@ -103,21 +105,6 @@ export const sessionSlice = createSlice({ | |
| curMessage.isGatheringContext = payload; | ||
| } | ||
| }, | ||
| clearLastEmptyResponse: (state) => { | ||
| if (state.history.length < 2) { | ||
| return; | ||
| } | ||
| const lastMessage = state.history[state.history.length - 1]; | ||
|
|
||
| // Only clear in the case of an empty message | ||
| if (!lastMessage.message.content.length) { | ||
| state.mainEditorContentTrigger = | ||
| state.history[state.history.length - 2].editorState; | ||
| state.history = state.history.slice(0, -2); | ||
| // TODO is this logic correct for tool use conversations? | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This TODO was on the mark. I've followed up with an implementation that follows this line of thinking and peformed extensive testing to validate it. |
||
| // Maybe slice at last index of "user" role message? | ||
| } | ||
| }, | ||
| // Trigger value picked up by editor with isMainInput to set its content | ||
| setMainEditorContentTrigger: ( | ||
| state, | ||
|
|
@@ -227,6 +214,12 @@ export const sessionSlice = createSlice({ | |
| ...updates, | ||
| }; | ||
| }, | ||
| removeLastHistoryItem: (state) => { | ||
| state.history.pop(); | ||
| }, | ||
| removeHistoryItemById: (state, action: PayloadAction<string>) => { | ||
| state.history = state.history.filter(item => item.message.id !== action.payload); | ||
| }, | ||
| addContextItemsAtIndex: ( | ||
| state, | ||
| { | ||
|
|
@@ -651,8 +644,36 @@ export const sessionSlice = createSlice({ | |
| }, | ||
| }, | ||
| extraReducers: (builder) => { | ||
| // Keep existing cases | ||
| addPassthroughCases(builder, [streamResponseThunk]); | ||
| }, | ||
|
|
||
| // Add handlers for cancelToolCallAndAddResult | ||
| builder.addCase(cancelToolCallAndAddResult.fulfilled, (state, action) => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be moved to the thunk so that it's all in one place? I'm not super familiar with the builder paradigm, could you explain the advantage of it vs dispatching a new message/tool call status from the thunk? Stylistic opinion but feels a bit disjointed. Testability related? |
||
| if (action.payload) { | ||
| const { toolCallId, toolResultMessage } = action.payload; | ||
|
|
||
| // First update the tool call status | ||
| const toolCallState = findToolCall(state.history, toolCallId); | ||
| if (toolCallState) { | ||
| toolCallState.status = "canceled"; | ||
| } | ||
|
|
||
| // Then add the tool result message to history | ||
| // Ensure the message has the required structure with id | ||
| if (toolResultMessage && toolResultMessage.message && toolResultMessage.message.id) { | ||
| state.history = [...state.history, toolResultMessage]; | ||
| } else { | ||
| console.error("Cannot push tool result message to history: invalid message structure", toolResultMessage); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| // Add handlers for clearLastEmptyResponseThunk | ||
| builder.addCase(clearLastEmptyResponse.fulfilled, (state, action) => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If no action can remove |
||
| // All actions are handled directly by the thunk through dispatching | ||
| // other actions, so we don't need additional handling here | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| function addPassthroughCases( | ||
|
|
@@ -707,7 +728,8 @@ export const { | |
| setActive, | ||
| submitEditorAndInitAtIndex, | ||
| updateHistoryItemAtIndex, | ||
| clearLastEmptyResponse, | ||
| removeLastHistoryItem, | ||
| removeHistoryItemById, | ||
| setMainEditorContentTrigger, | ||
| deleteMessage, | ||
| setIsGatheringContext, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { createAsyncThunk } from "@reduxjs/toolkit"; | ||
| import { | ||
| abortStream, | ||
| setInactive, | ||
| } from "../slices/sessionSlice"; | ||
| import { ThunkApiType } from "../store"; | ||
| import { streamAssistantMessage } from "./streamAssistantMessage"; | ||
| import { clearLastEmptyResponse } from "./clearLastEmptyResponse"; | ||
|
|
||
| export const cancelButton = createAsyncThunk<void, undefined, ThunkApiType>( | ||
| "chat/cancelButton", | ||
| async (messages, { dispatch, extra, getState }) => { | ||
| await dispatch(setInactive()); | ||
| await dispatch(abortStream()); | ||
| await dispatch(clearLastEmptyResponse()); | ||
| await dispatch(streamAssistantMessage({ content: "The request has been cancelled." })); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In chat mode this should just terminate, shouldn't stream an assistant response |
||
| } | ||
| ); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
toolCallId shouldn't be on user chat message since one user message can have multiple tool calls, should already be within each toolcalldelta