Skip to content

Commit 5a25419

Browse files
committed
ui(patches): remove apply and replace buttons and make the code revealable.
1 parent 2e7da6d commit 5a25419

File tree

3 files changed

+88
-99
lines changed

3 files changed

+88
-99
lines changed

refact-agent/gui/src/components/Tools/Textdoc.tsx

Lines changed: 42 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useMemo, useState } from "react";
1+
import React, { useCallback, useMemo } from "react";
22
import {
33
type CreateTextDocToolCall,
44
type RawTextDocTool,
@@ -12,20 +12,16 @@ import {
1212
isUpdateTextDocToolCall,
1313
parseRawTextDocToolCall,
1414
} from "./types";
15-
import { Box, Button, Card, Flex } from "@radix-ui/themes";
15+
import { Box, Card, Flex } from "@radix-ui/themes";
1616
import { TruncateLeft } from "../Text";
1717
import { Link } from "../Link";
1818
import { useEventsBusForIDE } from "../../hooks/useEventBusForIDE";
1919
import { Markdown } from "../Markdown";
2020
import { filename } from "../../utils/filename";
2121
import styles from "./Texdoc.module.css";
22-
import { createPatch } from "diff";
2322
import classNames from "classnames";
24-
import { useAppSelector } from "../../hooks";
25-
import { selectCanPaste } from "../../features/Chat";
26-
import { toolsApi } from "../../services/refact";
27-
import { ErrorCallout } from "../Callout";
28-
import { isRTKResponseErrorWithDetailMessage } from "../../utils";
23+
import { useCopyToClipboard } from "../../hooks/useCopyToClipboard";
24+
import { Reveal } from "../Reveal";
2925

3026
export const TextDocTool: React.FC<{ toolCall: RawTextDocTool }> = ({
3127
toolCall,
@@ -56,64 +52,14 @@ export const TextDocTool: React.FC<{ toolCall: RawTextDocTool }> = ({
5652
const TextDocHeader: React.FC<{
5753
toolCall: TextDocToolCall;
5854
}> = ({ toolCall }) => {
59-
const { openFile, diffPasteBack, sendToolEditToIde } = useEventsBusForIDE();
60-
const [requestDryRun, dryRunResult] = toolsApi.useDryRunForEditToolMutation();
61-
const [errorMessage, setErrorMessage] = useState<string>("");
62-
const canPaste = useAppSelector(selectCanPaste);
55+
const { openFile } = useEventsBusForIDE();
6356

64-
const clearErrorMessage = useCallback(() => setErrorMessage(""), []);
6557
// move this
6658
const handleOpenFile = useCallback(() => {
6759
if (!toolCall.function.arguments.path) return;
6860
openFile({ file_name: toolCall.function.arguments.path });
6961
}, [openFile, toolCall.function.arguments.path]);
7062

71-
const handleReplace = useCallback(
72-
(content: string) => {
73-
diffPasteBack(content);
74-
},
75-
[diffPasteBack],
76-
);
77-
78-
const replaceContent = useMemo(() => {
79-
if (isCreateTextDocToolCall(toolCall))
80-
return toolCall.function.arguments.content;
81-
if (isUpdateTextDocToolCall(toolCall))
82-
return toolCall.function.arguments.replacement;
83-
return null;
84-
}, [toolCall]);
85-
86-
const handleApplyToolResult = useCallback(() => {
87-
requestDryRun({
88-
toolName: toolCall.function.name,
89-
toolArgs: toolCall.function.arguments,
90-
})
91-
.then((results) => {
92-
if (results.data) {
93-
sendToolEditToIde(toolCall.function.arguments.path, results.data);
94-
} else if (isRTKResponseErrorWithDetailMessage(results)) {
95-
setErrorMessage(results.error.data.detail);
96-
}
97-
})
98-
.catch((error: unknown) => {
99-
if (
100-
error &&
101-
typeof error === "object" &&
102-
"message" in error &&
103-
typeof error.message === "string"
104-
) {
105-
setErrorMessage(error.message);
106-
} else {
107-
setErrorMessage("Error with patch: " + JSON.stringify(error));
108-
}
109-
});
110-
}, [
111-
requestDryRun,
112-
sendToolEditToIde,
113-
toolCall.function.arguments,
114-
toolCall.function.name,
115-
]);
116-
11763
return (
11864
<Card size="1" variant="surface" mt="4" className={styles.textdoc__header}>
11965
<Flex gap="2" py="2" pl="2" justify="between">
@@ -128,31 +74,7 @@ const TextDocHeader: React.FC<{
12874
{toolCall.function.arguments.path}
12975
</Link>
13076
</TruncateLeft>{" "}
131-
<div style={{ flexGrow: 1 }} />
132-
<Button
133-
size="1"
134-
onClick={handleApplyToolResult}
135-
disabled={dryRunResult.isLoading}
136-
title={`Apply`}
137-
>
138-
➕ Apply
139-
</Button>
140-
{replaceContent && (
141-
<Button
142-
size="1"
143-
onClick={() => handleReplace(replaceContent)}
144-
disabled={dryRunResult.isLoading || !canPaste}
145-
title="Replace the current selection in the ide."
146-
>
147-
➕ Replace Selection
148-
</Button>
149-
)}
15077
</Flex>
151-
{errorMessage && (
152-
<ErrorCallout onClick={clearErrorMessage} timeout={5000}>
153-
{errorMessage}
154-
</ErrorCallout>
155-
)}
15678
</Card>
15779
);
15880
};
@@ -166,11 +88,17 @@ const CreateTextDoc: React.FC<{
16688
"```" + extension + "\n" + toolCall.function.arguments.content + "\n```"
16789
);
16890
}, [toolCall.function.arguments.content, toolCall.function.arguments.path]);
91+
const handleCopy = useCopyToClipboard();
92+
93+
const lineCount = useMemo(() => code.split("\n").length, [code]);
94+
16995
return (
17096
// TODO: move this box up a bit, or make it generic
17197
<Box className={styles.textdoc}>
17298
<TextDocHeader toolCall={toolCall} />
173-
<Markdown>{code}</Markdown>
99+
<Reveal isRevealingCode defaultOpen={lineCount < 9}>
100+
<Markdown onCopyClick={handleCopy}>{code}</Markdown>
101+
</Reveal>
174102
</Box>
175103
);
176104
};
@@ -191,11 +119,20 @@ const ReplaceTextDoc: React.FC<{
191119
toolCall.function.arguments.path,
192120
toolCall.function.arguments.replacement,
193121
]);
122+
123+
const copyToClipBoard = useCopyToClipboard();
124+
const handleCopy = useCallback(() => {
125+
copyToClipBoard(toolCall.function.arguments.replacement);
126+
}, [copyToClipBoard, toolCall.function.arguments.replacement]);
127+
128+
const lineCount = useMemo(() => code.split("\n").length, [code]);
194129
return (
195130
// TODO: move this box up a bit, or make it generic
196131
<Box className={styles.textdoc}>
197132
<TextDocHeader toolCall={toolCall} />
198-
<Markdown>{code}</Markdown>
133+
<Reveal isRevealingCode defaultOpen={lineCount < 9}>
134+
<Markdown onCopyClick={handleCopy}>{code}</Markdown>
135+
</Reveal>
199136
</Box>
200137
);
201138
};
@@ -219,37 +156,43 @@ const UpdateRegexTextDoc: React.FC<{
219156
toolCall.function.arguments.replacement,
220157
]);
221158

159+
const lineCount = useMemo(() => code.split("\n").length, [code]);
160+
222161
return (
223162
<Box className={styles.textdoc}>
224163
<TextDocHeader toolCall={toolCall} />
225-
<Markdown>{code}</Markdown>
164+
<Reveal isRevealingCode defaultOpen={lineCount < 9}>
165+
<Markdown>{code}</Markdown>
166+
</Reveal>
226167
</Box>
227168
);
228169
};
229170

230171
const UpdateTextDoc: React.FC<{
231172
toolCall: UpdateTextDocToolCall;
232173
}> = ({ toolCall }) => {
233-
const diff = useMemo(() => {
234-
const patch = createPatch(
235-
toolCall.function.arguments.path,
236-
toolCall.function.arguments.old_str,
237-
toolCall.function.arguments.replacement,
174+
const code = useMemo(() => {
175+
const extension = getFileExtension(toolCall.function.arguments.path);
176+
return (
177+
"```" +
178+
extension +
179+
"\n" +
180+
toolCall.function.arguments.replacement +
181+
"\n```"
238182
);
239-
240-
return "```diff\n" + patch + "\n```";
241183
}, [
242-
toolCall.function.arguments.replacement,
243-
toolCall.function.arguments.old_str,
244184
toolCall.function.arguments.path,
185+
toolCall.function.arguments.replacement,
245186
]);
246-
// TODO: don't use markdown for this, it's two bright
187+
188+
const lineCount = useMemo(() => code.split("\n").length, [code]);
189+
247190
return (
248191
<Box className={classNames(styles.textdoc, styles.textdoc__update)}>
249192
<TextDocHeader toolCall={toolCall} />
250-
<Box className={classNames(styles.textdoc__diffbox)}>
251-
<Markdown useInlineStyles={false}>{diff}</Markdown>
252-
</Box>
193+
<Reveal isRevealingCode defaultOpen={lineCount < 9}>
194+
<Markdown useInlineStyles={false}>{code}</Markdown>
195+
</Reveal>
253196
</Box>
254197
);
255198
};

refact-agent/gui/src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ export * from "./useAgentUsage";
2828
export * from "./useOpenUrl";
2929
export * from "./useCapsForToolUse";
3030
export * from "./useCanUseTools";
31+
export * from "./useCopyToClipboard";
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { useCallback } from "react";
2+
import { telemetryApi } from "../services/refact";
3+
import { fallbackCopying } from "../utils/fallbackCopying";
4+
5+
export const useCopyToClipboard = () => {
6+
const [sendTelemetryEvent] =
7+
telemetryApi.useLazySendTelemetryChatEventQuery();
8+
9+
const handleCopy = useCallback(
10+
(text: string) => {
11+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
12+
if (window.navigator?.clipboard?.writeText) {
13+
void window.navigator.clipboard
14+
.writeText(text)
15+
.catch(() => {
16+
// eslint-disable-next-line no-console
17+
console.log("failed to copy to clipboard");
18+
void sendTelemetryEvent({
19+
scope: `codeBlockCopyToClipboard`,
20+
success: false,
21+
error_message:
22+
"window.navigator?.clipboard?.writeText: failed to copy to clipboard",
23+
});
24+
})
25+
.then(() => {
26+
void sendTelemetryEvent({
27+
scope: `codeBlockCopyToClipboard`,
28+
success: true,
29+
error_message: "",
30+
});
31+
});
32+
} else {
33+
fallbackCopying(text);
34+
void sendTelemetryEvent({
35+
scope: `codeBlockCopyToClipboard`,
36+
success: true,
37+
error_message: "",
38+
});
39+
}
40+
},
41+
[sendTelemetryEvent],
42+
);
43+
44+
return handleCopy;
45+
};

0 commit comments

Comments
 (0)