Skip to content

Commit 72f7133

Browse files
authored
Merge pull request #5347 from continuedev/dallin/apply-state
Edit tool fixes
2 parents cbfc6a2 + 71aa9b0 commit 72f7133

File tree

18 files changed

+171
-136
lines changed

18 files changed

+171
-136
lines changed

core/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,7 @@ export type EditStatus =
12161216
| "done";
12171217

12181218
export type ApplyStateStatus =
1219+
| "not-started" // Apply state created but not necessarily streaming
12191220
| "streaming" // Changes are being applied to the file
12201221
| "done" // All changes have been applied, awaiting user to accept/reject
12211222
| "closed"; // All changes have been applied. Note that for new files, we immediately set the status to "closed"

gui/src/components/StyledMarkdownPreview/StepContainerPreToolbar/ApplyActions.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ export function ApplyActions(props: ApplyActionsProps) {
4848
);
4949

5050
switch (props.applyState ? props.applyState.status : null) {
51+
case "not-started":
52+
return (
53+
<div className="flex select-none items-center rounded bg-zinc-700 pl-2 pr-1">
54+
<span className="text-lightgray inline-flex w-min items-center gap-2 text-center text-xs">
55+
Pending
56+
</span>
57+
</div>
58+
);
5159
case "streaming":
5260
return (
5361
<div className="flex select-none items-center rounded bg-zinc-700 pl-2 pr-1">
@@ -84,18 +92,15 @@ export function ApplyActions(props: ApplyActionsProps) {
8492
</div>
8593
);
8694
case "closed":
87-
if (!hasRejected && props.applyState?.numDiffs === 0) {
88-
if (showApplied) {
95+
if (isSuccessful) {
96+
if (showApplied || props.disableManualApply) {
8997
return (
9098
<span className="flex select-none items-center rounded bg-zinc-700 text-slate-400 max-sm:px-0.5 sm:pl-2">
9199
<span className="max-sm:hidden">Applied</span>
92100
<CheckIcon className="h-3.5 w-3.5 hover:brightness-125 sm:px-1" />
93101
</span>
94102
);
95103
}
96-
if (props.disableManualApply) {
97-
return null;
98-
}
99104
return applyButton("Reapply");
100105
}
101106
default:

gui/src/components/StyledMarkdownPreview/StepContainerPreToolbar/GeneratingCodeLoader.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,24 @@ import Spinner from "../../gui/Spinner";
33
export interface GeneratingCodeLoaderProps {
44
showLineCount: boolean;
55
codeBlockContent: string;
6+
isPending: boolean;
67
}
78

89
export function GeneratingCodeLoader({
910
showLineCount,
1011
codeBlockContent,
12+
isPending,
1113
}: GeneratingCodeLoaderProps) {
1214
const numLinesCodeBlock = codeBlockContent.split("\n").length;
1315
const linesGeneratedText =
1416
numLinesCodeBlock === 1
1517
? `1 line generated`
16-
: `${numLinesCodeBlock} lines generated`;
18+
: `${numLinesCodeBlock} lines ${isPending ? "pending" : "generated"}`;
1719

1820
return (
1921
<span className="text-lightgray inline-flex items-center gap-2">
2022
{showLineCount ? linesGeneratedText : "Generating"}
21-
<Spinner />
23+
{!isPending && <Spinner />}
2224
</span>
2325
);
2426
}

gui/src/components/StyledMarkdownPreview/StepContainerPreToolbar/index.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export interface StepContainerPreToolbarProps {
5252
codeBlockContent: string;
5353
language: string | null;
5454
relativeFilepath?: string;
55-
isGeneratingCodeBlock: boolean;
55+
isFinalCodeblock: boolean;
5656
codeBlockIndex: number; // To track which codeblock we are applying
5757
codeBlockStreamId: string;
5858
range?: string;
@@ -65,7 +65,7 @@ export function StepContainerPreToolbar({
6565
codeBlockContent,
6666
language,
6767
relativeFilepath,
68-
isGeneratingCodeBlock,
68+
isFinalCodeblock,
6969
codeBlockIndex,
7070
codeBlockStreamId,
7171
range,
@@ -115,6 +115,9 @@ export function StepContainerPreToolbar({
115115
const hasFileExtension =
116116
relativeFilepath && /\.[0-9a-z]+$/i.test(relativeFilepath);
117117

118+
const isStreaming = useAppSelector((store) => store.session.isStreaming);
119+
const isGeneratingCodeBlock = isFinalCodeblock && isStreaming;
120+
118121
// If we are creating a file, we already render that in the button
119122
// so we don't want to dispaly it twice here
120123
const displayFilepath = relativeFilepath ?? appliedFileUri;
@@ -272,20 +275,23 @@ export function StepContainerPreToolbar({
272275
</div>
273276

274277
<div className="flex items-center gap-2.5">
275-
{isGeneratingCodeBlock ? (
278+
{!isGeneratingCodeBlock && (
279+
<div className="xs:flex hidden items-center gap-2.5">
280+
<InsertButton onInsert={onClickInsertAtCursor} />
281+
<CopyButton text={codeBlockContent} />
282+
</div>
283+
)}
284+
285+
{isGeneratingCodeBlock || applyState?.status === "not-started" ? (
276286
<GeneratingCodeLoader
277287
showLineCount={!isExpanded}
278288
codeBlockContent={codeBlockContent}
289+
isPending={
290+
applyState?.status === "not-started" && !isGeneratingCodeBlock
291+
}
279292
/>
280293
) : (
281-
<>
282-
<div className="xs:flex hidden items-center gap-2.5">
283-
<InsertButton onInsert={onClickInsertAtCursor} />
284-
<CopyButton text={codeBlockContent} />
285-
</div>
286-
287-
{renderActionButtons()}
288-
</>
294+
renderActionButtons()
289295
)}
290296
</div>
291297
</ToolbarDiv>

gui/src/components/StyledMarkdownPreview/index.tsx

Lines changed: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ interface StyledMarkdownPreviewProps {
126126
itemIndex?: number;
127127
useParentBackgroundColor?: boolean;
128128
disableManualApply?: boolean;
129-
singleCodeblockStreamId?: string;
129+
forceStreamId?: string;
130130
expandCodeblocks?: boolean;
131131
}
132132

@@ -195,27 +195,16 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview(
195195
const pastFileInfoRef = useUpdatingRef(pastFileInfo);
196196

197197
const isLastItem = useMemo(() => {
198-
return props.itemIndex && props.itemIndex === history.length - 1;
198+
return props.itemIndex === history.length - 1;
199199
}, [history.length, props.itemIndex]);
200200
const isLastItemRef = useUpdatingRef(isLastItem);
201201

202-
const isStreaming = useAppSelector((store) => store.session.isStreaming);
203-
const isStreamingRef = useUpdatingRef(isStreaming);
204-
205-
const codeblockState = useRef<{ streamId: string; isGenerating: boolean }[]>(
206-
[],
207-
);
208-
202+
const codeblockStreamIds = useRef<string[]>([]);
209203
useEffect(() => {
210-
if (props.singleCodeblockStreamId) {
211-
codeblockState.current = [
212-
{
213-
streamId: props.singleCodeblockStreamId,
214-
isGenerating: false,
215-
},
216-
];
204+
if (props.forceStreamId) {
205+
codeblockStreamIds.current = [props.forceStreamId];
217206
}
218-
}, [props.singleCodeblockStreamId]);
207+
}, [props.forceStreamId, codeblockStreamIds]);
219208

220209
const [reactContent, setMarkdownSource] = useRemark({
221210
remarkPlugins: [
@@ -310,19 +299,12 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview(
310299

311300
const language = getLanguageFromClassName(className);
312301

313-
const isGeneratingCodeBlock =
314-
preChildProps["data-islastcodeblock"] &&
315-
isLastItemRef.current &&
316-
isStreamingRef.current;
317-
318-
if (codeblockState.current[codeBlockIndex] === undefined) {
319-
codeblockState.current[codeBlockIndex] = {
320-
streamId: uuidv4(),
321-
isGenerating: isGeneratingCodeBlock,
322-
};
323-
} else {
324-
codeblockState.current[codeBlockIndex].isGenerating =
325-
isGeneratingCodeBlock;
302+
const isFinalCodeblock =
303+
preChildProps["data-islastcodeblock"] && isLastItemRef.current;
304+
305+
if (codeblockStreamIds.current[codeBlockIndex] === undefined) {
306+
codeblockStreamIds.current[codeBlockIndex] =
307+
props.forceStreamId ?? uuidv4();
326308
}
327309

328310
return (
@@ -331,11 +313,9 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview(
331313
codeBlockIndex={codeBlockIndex}
332314
language={language}
333315
relativeFilepath={relativeFilePath}
334-
isGeneratingCodeBlock={isGeneratingCodeBlock}
316+
isFinalCodeblock={isFinalCodeblock}
335317
range={range}
336-
codeBlockStreamId={
337-
codeblockState.current[codeBlockIndex].streamId
338-
}
318+
codeBlockStreamId={codeblockStreamIds.current[codeBlockIndex]}
339319
expanded={props.expandCodeblocks}
340320
disableManualApply={props.disableManualApply}
341321
>
@@ -381,7 +361,7 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview(
381361
const codeWrapState = uiConfig?.codeWrap ? "pre-wrap" : "pre";
382362
return (
383363
<StyledMarkdown
384-
contentEditable='false'
364+
contentEditable="false"
385365
fontSize={getFontSize()}
386366
whiteSpace={codeWrapState}
387367
bgColor={props.useParentBackgroundColor ? "" : vscBackground}

gui/src/components/mainInput/InputToolbar.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { modelSupportsImages, modelSupportsTools } from "core/llm/autodetect";
44
import { useRef } from "react";
55
import { useAppDispatch, useAppSelector } from "../../redux/hooks";
66
import { selectUseActiveFile } from "../../redux/selectors";
7-
import { selectCurrentToolCall } from "../../redux/selectors/selectCurrentToolCall";
7+
import {
8+
selectCurrentToolCall,
9+
selectCurrentToolCallApplyState,
10+
} from "../../redux/selectors/selectCurrentToolCall";
811
import { selectSelectedChatModel } from "../../redux/slices/configSlice";
912
import {
1013
selectHasCodeToEdit,
@@ -53,15 +56,16 @@ function InputToolbar(props: InputToolbarProps) {
5356
const hasCodeToEdit = useAppSelector(selectHasCodeToEdit);
5457
const toolCallState = useAppSelector(selectCurrentToolCall);
5558
const isEditModeAndNoCodeToEdit = isInEditMode && !hasCodeToEdit;
56-
const activeToolCallStreamId = useAppSelector(
57-
(store) => store.session.activeToolStreamId,
59+
const currentToolCallApplyState = useAppSelector(
60+
selectCurrentToolCallApplyState,
5861
);
5962

6063
const isEnterDisabled =
6164
props.disabled ||
6265
isEditModeAndNoCodeToEdit ||
6366
toolCallState?.status === "generated" ||
64-
!!activeToolCallStreamId;
67+
(currentToolCallApplyState &&
68+
currentToolCallApplyState.status !== "closed");
6569

6670
const toolsSupported = defaultModel && modelSupportsTools(defaultModel);
6771

gui/src/components/mainInput/Lump/LumpToolbar.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ export function LumpToolbar() {
109109
if (toolCallState?.status === "generated") {
110110
return (
111111
<Container>
112-
<GeneratingIndicator />
112+
<div className="flex flex-row items-center pb-0.5 pr-1 text-xs text-gray-400">
113+
<span className="hidden sm:flex">Pending tool call</span>
114+
</div>
113115

114116
<div className="flex gap-2 pb-0.5">
115117
<StopButton

gui/src/hooks/useSetup.ts

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
setSelectedProfile,
1111
} from "../redux";
1212
import { useAppDispatch, useAppSelector } from "../redux/hooks";
13+
import { selectCurrentToolCallApplyState } from "../redux/selectors/selectCurrentToolCall";
1314
import {
1415
selectSelectedChatModel,
1516
setConfigResult,
@@ -252,41 +253,39 @@ function useSetup() {
252253
dispatch(updateIndexingStatus(data));
253254
});
254255

255-
const activeToolStreamId = useAppSelector(
256-
(store) => store.session.activeToolStreamId,
256+
const currentToolCallApplyState = useAppSelector(
257+
selectCurrentToolCallApplyState,
257258
);
258259
useWebviewListener(
259260
"updateApplyState",
260261
async (state) => {
261262
dispatch(updateApplyState(state));
262-
const lastHistoryMsg = history.at(-1);
263-
const [streamId, toolCallId] = activeToolStreamId ?? [];
263+
264+
// Handle apply status updates that are associated with current tool call
264265
if (
265-
toolCallId &&
266266
state.status === "closed" &&
267-
lastHistoryMsg?.toolCallState?.toolCallId === toolCallId
267+
currentToolCallApplyState &&
268+
currentToolCallApplyState.streamId === state.streamId
268269
) {
269-
if (state.streamId === streamId) {
270-
// const output: ContextItem = {
271-
// name: "Edit tool output",
272-
// content: "Completed edit",
273-
// description: "",
274-
// };
275-
dispatch(
276-
acceptToolCall({
277-
toolCallId,
278-
}),
279-
);
280-
// dispatch(setToolCallOutput([]));
281-
dispatch(
282-
streamResponseAfterToolCall({
283-
toolCallId,
284-
}),
285-
);
286-
}
270+
// const output: ContextItem = {
271+
// name: "Edit tool output",
272+
// content: "Completed edit",
273+
// description: "",
274+
// };
275+
dispatch(
276+
acceptToolCall({
277+
toolCallId: currentToolCallApplyState.toolCallId!,
278+
}),
279+
);
280+
// dispatch(setToolCallOutput([]));
281+
dispatch(
282+
streamResponseAfterToolCall({
283+
toolCallId: currentToolCallApplyState.toolCallId!,
284+
}),
285+
);
287286
}
288287
},
289-
[activeToolStreamId, history],
288+
[currentToolCallApplyState, history],
290289
);
291290
}
292291

gui/src/pages/gui/Chat.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ import { IdeMessengerContext } from "../../context/IdeMessenger";
3030
import { useWebviewListener } from "../../hooks/useWebviewListener";
3131
import { useAppDispatch, useAppSelector } from "../../redux/hooks";
3232
import { selectUseHub } from "../../redux/selectors";
33-
import { selectCurrentToolCall } from "../../redux/selectors/selectCurrentToolCall";
33+
import {
34+
selectCurrentToolCall,
35+
selectCurrentToolCallApplyState,
36+
} from "../../redux/selectors/selectCurrentToolCall";
3437
import { selectSelectedChatModel } from "../../redux/slices/configSlice";
3538
import { submitEdit } from "../../redux/slices/editModeState";
3639
import {
@@ -152,6 +155,10 @@ export function Chat() {
152155

153156
const { widget, highlights } = useFindWidget(stepsDivRef);
154157

158+
const currentToolCallApplyState = useAppSelector(
159+
selectCurrentToolCallApplyState,
160+
);
161+
155162
const sendInput = useCallback(
156163
(
157164
editorState: JSONContent,
@@ -164,6 +171,14 @@ export function Chat() {
164171
"Cannot submit message while awaiting tool confirmation",
165172
);
166173
}
174+
if (
175+
currentToolCallApplyState &&
176+
currentToolCallApplyState.status !== "closed"
177+
) {
178+
return console.error(
179+
"Cannot submit message while awaiting tool call apply",
180+
);
181+
}
167182
if (selectedChatModel?.provider === "free-trial") {
168183
const newCount = incrementFreeTrialCount();
169184

@@ -352,6 +367,7 @@ export function Chat() {
352367
toolCallState={item.toolCallState!}
353368
toolCall={toolCall}
354369
output={history[index + 1]?.contextItems}
370+
historyIndex={index}
355371
/>
356372
</div>
357373
);

0 commit comments

Comments
 (0)