Skip to content

Commit b11505a

Browse files
committed
fix useEffect, and have switch
1 parent 6def016 commit b11505a

File tree

4 files changed

+64
-31
lines changed

4 files changed

+64
-31
lines changed

frontend/src/components/editor/ai/ai-completion-editor.tsx

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { getCodes } from "@/core/codemirror/copilot/getCodes";
2222
import type { LanguageAdapterType } from "@/core/codemirror/language/types";
2323
import { selectAllText } from "@/core/codemirror/utils";
2424
import { useRuntimeManager } from "@/core/runtime/config";
25+
import { useEvent } from "@/hooks/useEvent";
2526
import { useTheme } from "@/theme/useTheme";
2627
import { cn } from "@/utils/cn";
2728
import { prettyError } from "@/utils/errors";
@@ -45,7 +46,7 @@ interface Props {
4546
declineChange: () => void;
4647
acceptChange: (rightHandCode: string) => void;
4748
enabled: boolean;
48-
initialTrigger?: boolean;
49+
triggerImmediately?: boolean;
4950
/**
5051
* Children shown when there is no completion
5152
*/
@@ -68,13 +69,10 @@ export const AiCompletionEditor: React.FC<Props> = ({
6869
declineChange,
6970
acceptChange,
7071
enabled,
71-
initialTrigger,
72+
triggerImmediately,
7273
children,
7374
}) => {
74-
const [hasTriggered, setHasTriggered] = useState(false);
75-
const [completionBody, setCompletionBody] = useState<object>(
76-
initialPrompt ? getAICompletionBody({ input: initialPrompt }) : {},
77-
);
75+
const [completionBody, setCompletionBody] = useState<object>({});
7876

7977
const [includeOtherCells, setIncludeOtherCells] = useAtom(
8078
includeOtherCellsAtom,
@@ -99,7 +97,11 @@ export const AiCompletionEditor: React.FC<Props> = ({
9997
// Throttle the messages and data updates to 100ms
10098
experimental_throttle: 100,
10199
body: {
102-
...completionBody,
100+
...(Object.keys(completionBody).length > 0
101+
? completionBody
102+
: initialPrompt
103+
? getAICompletionBody({ input: initialPrompt })
104+
: {}),
103105
includeOtherCode: includeOtherCells ? getCodes(currentCode) : "",
104106
code: currentCode,
105107
language: currentLanguageAdapter,
@@ -119,6 +121,12 @@ export const AiCompletionEditor: React.FC<Props> = ({
119121
const inputRef = React.useRef<ReactCodeMirrorRef>(null);
120122
const completion = untrimmedCompletion.trimEnd();
121123

124+
const initialSubmit = useEvent(() => {
125+
if (triggerImmediately && !isLoading && initialPrompt) {
126+
handleSubmit();
127+
}
128+
});
129+
122130
// Focus the input
123131
useEffect(() => {
124132
if (enabled) {
@@ -127,6 +135,7 @@ export const AiCompletionEditor: React.FC<Props> = ({
127135
const input = inputRef.current;
128136
if (input?.view) {
129137
input.view.focus();
138+
initialSubmit();
130139
return true;
131140
}
132141
return false;
@@ -136,7 +145,7 @@ export const AiCompletionEditor: React.FC<Props> = ({
136145

137146
selectAllText(inputRef.current?.view);
138147
}
139-
}, [enabled]);
148+
}, [enabled, initialSubmit]);
140149

141150
// Reset the input when the prompt changes
142151
useEffect(() => {
@@ -145,18 +154,6 @@ export const AiCompletionEditor: React.FC<Props> = ({
145154
}
146155
}, [enabled, initialPrompt, setInput]);
147156

148-
// TODO: Does not work properly
149-
if (!hasTriggered && initialTrigger) {
150-
setHasTriggered(true);
151-
// Use requestAnimationFrame for better timing
152-
requestAnimationFrame(() => {
153-
if (inputRef.current?.view) {
154-
storePrompt(inputRef.current.view);
155-
}
156-
handleSubmit();
157-
});
158-
}
159-
160157
const { theme } = useTheme();
161158

162159
const handleAcceptCompletion = () => {

frontend/src/components/editor/cell/code/cell-editor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ const CellEditorInternal = ({
406406
<AiCompletionEditor
407407
enabled={aiCompletionCell?.cellId === cellId}
408408
initialPrompt={aiCompletionCell?.initialPrompt}
409-
initialTrigger={aiCompletionCell?.triggerImmediately}
409+
triggerImmediately={aiCompletionCell?.triggerImmediately}
410410
currentCode={editorViewRef.current?.state.doc.toString() ?? code}
411411
currentLanguageAdapter={languageAdapter}
412412
declineChange={useEvent(() => {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/* Copyright 2024 Marimo. All rights reserved. */
2+
3+
import { useAtom } from "jotai";
4+
import { atomWithStorage } from "jotai/utils";
5+
import { store } from "@/core/state/jotai";
6+
7+
const BASE_KEY = "marimo:instant-ai-fix";
8+
9+
const instantAIFixAtom = atomWithStorage<boolean>(BASE_KEY, false);
10+
11+
export function useInstantAIFix() {
12+
const [instantAIFix, setInstantAIFix] = useAtom(instantAIFixAtom);
13+
return { instantAIFix, setInstantAIFix };
14+
}
15+
16+
export function getInstantAIFix() {
17+
return store.get(instantAIFixAtom);
18+
}

frontend/src/components/editor/errors/auto-fix.tsx

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/* Copyright 2024 Marimo. All rights reserved. */
22

33
import { useAtomValue, useSetAtom } from "jotai";
4-
import { WrenchIcon, ZapIcon } from "lucide-react";
4+
import { WrenchIcon, ZapIcon, ZapOffIcon } from "lucide-react";
55
import { Button } from "@/components/ui/button";
6+
import { Switch } from "@/components/ui/switch";
67
import { Tooltip } from "@/components/ui/tooltip";
78
import { aiCompletionCellAtom } from "@/core/ai/state";
89
import { notebookAtom, useCellActions } from "@/core/cells/cells";
@@ -12,6 +13,7 @@ import { getAutoFixes } from "@/core/errors/errors";
1213
import type { MarimoError } from "@/core/kernel/messages";
1314
import { store } from "@/core/state/jotai";
1415
import { cn } from "@/utils/cn";
16+
import { useInstantAIFix } from "./auto-fix-atom";
1517

1618
export const AutoFixButton = ({
1719
errors,
@@ -22,6 +24,7 @@ export const AutoFixButton = ({
2224
cellId: CellId;
2325
className?: string;
2426
}) => {
27+
const { instantAIFix, setInstantAIFix } = useInstantAIFix();
2528
const { createNewCell } = useCellActions();
2629
const aiEnabled = useAtomValue(aiEnabledAtom);
2730
const autoFixes = errors.flatMap((error) =>
@@ -37,7 +40,7 @@ export const AutoFixButton = ({
3740
// multiple fixes.
3841
const firstFix = autoFixes[0];
3942

40-
const handleFix = (aiInstantFix = false) => {
43+
const handleFix = () => {
4144
const editorView =
4245
store.get(notebookAtom).cellHandles[cellId].current?.editorView;
4346
firstFix.onFix({
@@ -53,33 +56,48 @@ export const AutoFixButton = ({
5356
cellId: cellId,
5457
aiFix: {
5558
setAiCompletionCell,
56-
instantFix: aiInstantFix,
59+
instantFix: instantAIFix,
5760
},
5861
});
5962
// Focus the editor
6063
editorView?.focus();
6164
};
6265

6366
return (
64-
<div className={cn("flex gap-2 my-2", className)}>
67+
<div className={cn("flex gap-2 my-2 items-center", className)}>
6568
<Tooltip content={firstFix.description} align="start">
6669
<Button
6770
size="xs"
6871
variant="outline"
6972
className="font-normal"
70-
onClick={() => handleFix(false)}
73+
onClick={handleFix}
7174
>
7275
<WrenchIcon className="h-3 w-3 mr-2" />
7376
{firstFix.title}
7477
</Button>
7578
</Tooltip>
7679

7780
{firstFix.fixType === "ai" && (
78-
<Tooltip content="Instant fix" align="start">
79-
<Button size="xs" variant="ghost" onClick={() => handleFix(true)}>
80-
<ZapIcon className="h-3 w-3" />
81-
</Button>
82-
</Tooltip>
81+
<div className="flex items-center gap-2">
82+
<Switch
83+
checked={instantAIFix}
84+
onCheckedChange={() => setInstantAIFix(!instantAIFix)}
85+
size="sm"
86+
className="h-4 w-8"
87+
title="Toggle instant AI fix mode"
88+
/>
89+
<Tooltip
90+
content={
91+
instantAIFix ? "Instant fix enabled" : "Instant fix disabled"
92+
}
93+
>
94+
{instantAIFix ? (
95+
<ZapIcon className="h-3 w-3 text-amber-500" />
96+
) : (
97+
<ZapOffIcon className="h-3 w-3 text-muted-foreground" />
98+
)}
99+
</Tooltip>
100+
</div>
83101
)}
84102
</div>
85103
);

0 commit comments

Comments
 (0)