Skip to content

Commit 32e7c1f

Browse files
authored
Add fix code button (#586)
* Change `useAddCell` to support source with all changes in a single transaction
1 parent be64772 commit 32e7c1f

File tree

4 files changed

+120
-2
lines changed

4 files changed

+120
-2
lines changed

src/components/notebook/cell/ExecutableCell.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,8 @@ export const ExecutableCell: React.FC<ExecutableCellProps> = ({
613613

614614
<ErrorBoundary FallbackComponent={OutputsErrorBoundary}>
615615
<MaybeCellOutputs
616+
cellId={cell.id}
617+
cellType={cell.cellType}
616618
isLoading={cell.executionState === "running" && !hasOutputs}
617619
outputs={hasOutputs ? outputs : staleOutputs}
618620
showOutput={showOutput}

src/components/outputs/MaybeCellOutputs.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { cn } from "@/lib/utils";
22
import { outputsDeltasQuery, processDeltas } from "@/queries/outputDeltas";
3-
import { OutputData, SAFE_MIME_TYPES } from "@runtimed/schema";
3+
import { CellType, OutputData, SAFE_MIME_TYPES } from "@runtimed/schema";
44
import { groupConsecutiveStreamOutputs } from "@/util/output-grouping";
55
import { useQuery } from "@livestore/react";
66
import { useMemo, useState } from "react";
@@ -9,17 +9,22 @@ import { SingleOutput } from "./shared-with-iframe/SingleOutput";
99
import { useDebounce } from "react-use";
1010
import { OutputsContainer } from "./shared-with-iframe/OutputsContainer";
1111
import { SuspenseSpinner } from "./shared-with-iframe/SuspenseSpinner";
12+
import { MaybeFixCodeButton } from "./MaybeFixCodeButton";
1213

1314
/**
1415
* TODO: consider renaming this component
1516
* By default, we want to be ready to render the outputs
1617
*/
1718
export const MaybeCellOutputs = ({
19+
cellId,
20+
cellType,
1821
outputs,
1922
shouldAlwaysUseIframe = false,
2023
isLoading,
2124
showOutput,
2225
}: {
26+
cellId: string;
27+
cellType: CellType;
2328
outputs: readonly OutputData[];
2429
shouldAlwaysUseIframe?: boolean;
2530
isLoading: boolean;
@@ -54,6 +59,13 @@ export const MaybeCellOutputs = ({
5459
isLoading && outputs.length ? "opacity-50" : "opacity-100"
5560
)}
5661
>
62+
{cellType === "code" && (
63+
<MaybeFixCodeButton
64+
isLoading={isLoading}
65+
cellId={cellId}
66+
outputs={outputs}
67+
/>
68+
)}
5769
{/* TODO: consider rendering an empty iframewhen we have a safe output currently rendered but cell input has changed */}
5870
{shouldUseIframe ? (
5971
<IframeOutput
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { useAddCell } from "@/hooks/useAddCell";
2+
import { OutputData } from "@runtimed/schema";
3+
import { Bug } from "lucide-react";
4+
import { useCallback } from "react";
5+
import { Button } from "../ui/button";
6+
import { OutputsContainer } from "./shared-with-iframe/OutputsContainer";
7+
8+
export function MaybeFixCodeButton({
9+
isLoading,
10+
cellId,
11+
outputs,
12+
}: {
13+
isLoading: boolean;
14+
cellId: string;
15+
outputs: readonly OutputData[];
16+
}) {
17+
const errorOutput = outputs.find((output) => output.outputType === "error");
18+
19+
if (
20+
!errorOutput ||
21+
!errorOutput.data ||
22+
typeof errorOutput.data !== "string"
23+
) {
24+
return null;
25+
}
26+
27+
return (
28+
<OutputsContainer>
29+
<FixCodeButton
30+
cellId={cellId}
31+
errorOutputData={errorOutput.data}
32+
isLoading={isLoading}
33+
/>
34+
</OutputsContainer>
35+
);
36+
}
37+
38+
const FixCodeButton = ({
39+
cellId,
40+
errorOutputData,
41+
isLoading,
42+
}: {
43+
cellId: string;
44+
errorOutputData: string;
45+
isLoading: boolean;
46+
}) => {
47+
const { addCell } = useAddCell();
48+
49+
const handleFixCode = useCallback(() => {
50+
const formattedErrorOutputData = formatErrorOutputData(errorOutputData);
51+
const message = formatAiMessage(formattedErrorOutputData);
52+
addCell(cellId, "ai", "after", message);
53+
}, [addCell, cellId, errorOutputData]);
54+
55+
return (
56+
<Button
57+
variant="destructive"
58+
size="sm"
59+
onClick={handleFixCode}
60+
disabled={isLoading}
61+
>
62+
<Bug /> Fix Code with AI{" "}
63+
</Button>
64+
);
65+
};
66+
67+
/** Add code block to the error message */
68+
function formatAiMessage(errorString: string) {
69+
return `Fix the error:\n \`\`\`json\n${errorString}\n\`\`\`\n`;
70+
}
71+
72+
/** Formats the error for AI model to fix */
73+
function formatErrorOutputData(errorOutputData: string) {
74+
return JSON.stringify(JSON.parse(errorOutputData), null, 2);
75+
}

src/hooks/useAddCell.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ export const useAddCell = () => {
1818
(
1919
cellId?: string,
2020
cellType: CellType = "code",
21-
position: "before" | "after" = "after"
21+
position: "before" | "after" = "after",
22+
source?: string
2223
) => {
2324
const cellReferences = store.query(queries.cellsWithIndices$);
2425
const newCellId = `cell-${Date.now()}-${Math.random()
@@ -99,6 +100,34 @@ export const useAddCell = () => {
99100
);
100101
}
101102

103+
if (source) {
104+
const cellEvents = [
105+
// set cell source
106+
events.cellSourceChanged({
107+
id: newCellId,
108+
source,
109+
modifiedBy: userId,
110+
}),
111+
// hide cell input
112+
events.cellSourceVisibilityToggled({
113+
id: newCellId,
114+
sourceVisible: false,
115+
actorId: userId,
116+
}),
117+
// run cell
118+
events.executionRequested({
119+
cellId: newCellId,
120+
actorId: userId,
121+
requestedBy: userId,
122+
queueId: `exec-${Date.now()}-${Math.random().toString(36).slice(2)}`,
123+
executionCount:
124+
(store.query(queries.cellQuery.byId(newCellId))?.executionCount ||
125+
0) + 1,
126+
}),
127+
];
128+
store.commit(...cellEvents);
129+
}
130+
102131
// Focus the new cell after creation
103132
setTimeout(() => store.setSignal(focusedCellSignal$, newCellId), 0);
104133

0 commit comments

Comments
 (0)