Skip to content

Commit d532b4e

Browse files
committed
split text area into base, and standard
. .
1 parent bc72cff commit d532b4e

8 files changed

Lines changed: 181 additions & 138 deletions

File tree

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// This example is for an Editor with `ReactEditor` and `HistoryEditor`
2+
import { Descendant, Editor } from "slate";
3+
import { Editable, Slate } from "slate-react";
4+
import {
5+
useCallback,
6+
useEffect,
7+
useMemo,
8+
useState,
9+
TextareaHTMLAttributes,
10+
} from "react";
11+
import {
12+
AutosuggestionsBareFunction,
13+
useAutosuggestions,
14+
} from "../../../hooks/use-autosuggestions";
15+
import { AutosuggestionState } from "../../../types/autosuggestion-state";
16+
import { clearAutocompletionsFromEditor } from "../../../lib/slatejs-edits/clear-autocompletions";
17+
import { addAutocompletionsToEditor } from "../../../lib/slatejs-edits/add-autocompletions";
18+
import { useCopilotTextareaEditor } from "../../../hooks/use-copilot-textarea-editor";
19+
import { renderElement } from "./render-element";
20+
import {
21+
BaseAutosuggestionsConfig,
22+
defaultBaseAutosuggestionsConfig,
23+
} from "../../../types/autosuggestions-config";
24+
import { makeRenderPlaceholderFunction } from "./render-placeholder";
25+
import {
26+
getFullEditorTextWithNewlines,
27+
getTextAroundCursor,
28+
} from "../../../lib/get-text-around-cursor";
29+
import { replaceEditorText } from "../../../lib/slatejs-edits/replace-text";
30+
31+
export interface BaseCopilotTextareaProps
32+
extends TextareaHTMLAttributes<HTMLDivElement> {
33+
placeholderStyle?: React.CSSProperties;
34+
value?: string;
35+
onValueChange?: (value: string) => void;
36+
autosuggestionsConfig: Partial<BaseAutosuggestionsConfig>;
37+
}
38+
39+
export function BaseCopilotTextarea(
40+
props: BaseCopilotTextareaProps & {
41+
autosuggestionsFunction: AutosuggestionsBareFunction;
42+
}
43+
): JSX.Element {
44+
const autosuggestionsConfig: BaseAutosuggestionsConfig = {
45+
...defaultBaseAutosuggestionsConfig,
46+
...props.autosuggestionsConfig,
47+
};
48+
49+
// separate into TextareaHTMLAttributes<HTMLDivElement> and CopilotTextareaProps
50+
const {
51+
placeholderStyle,
52+
value,
53+
onValueChange,
54+
autosuggestionsConfig: autosuggestionsConfigFromProps,
55+
...textareaLikeProps
56+
} = props;
57+
58+
const valueOnInitialRender = useMemo(() => props.value ?? "", []);
59+
const [lastKnownFullEditorText, setLastKnownFullEditorText] =
60+
useState(valueOnInitialRender);
61+
62+
const initialValue: Descendant[] = useMemo(() => {
63+
return [
64+
{
65+
type: "paragraph",
66+
children: [{ text: valueOnInitialRender }],
67+
},
68+
];
69+
}, [valueOnInitialRender]);
70+
71+
const editor = useCopilotTextareaEditor();
72+
73+
const insertText = useCallback(
74+
(autosuggestion: AutosuggestionState) => {
75+
Editor.insertText(editor, autosuggestion.text, {
76+
at: autosuggestion.point,
77+
});
78+
},
79+
[editor]
80+
);
81+
82+
const {
83+
currentAutocompleteSuggestion,
84+
onChangeHandler: onChangeHandlerForAutocomplete,
85+
onKeyDownHandler: onKeyDownHandlerForAutocomplete,
86+
} = useAutosuggestions(
87+
autosuggestionsConfig.debounceTime,
88+
autosuggestionsConfig.acceptAutosuggestionKey,
89+
props.autosuggestionsFunction,
90+
insertText
91+
);
92+
93+
// sync autosuggestions state with the editor
94+
useEffect(() => {
95+
clearAutocompletionsFromEditor(editor);
96+
if (currentAutocompleteSuggestion) {
97+
addAutocompletionsToEditor(
98+
editor,
99+
currentAutocompleteSuggestion.text,
100+
currentAutocompleteSuggestion.point
101+
);
102+
}
103+
}, [currentAutocompleteSuggestion]);
104+
105+
const renderElementMemoized = useCallback(renderElement, []);
106+
const renderPlaceholderMemoized = useMemo(() => {
107+
// For some reason slateJS specifies a top value of 0, which makes for strange styling. We override this here.
108+
const placeholderStyleSlatejsOverrides: React.CSSProperties = {
109+
top: undefined,
110+
};
111+
112+
const placeholderStyleAugmented: React.CSSProperties = {
113+
...placeholderStyleSlatejsOverrides,
114+
...props.placeholderStyle,
115+
};
116+
117+
return makeRenderPlaceholderFunction(placeholderStyleAugmented);
118+
}, [props.placeholderStyle]);
119+
120+
// update the editor text, but only when the value changes from outside the component
121+
useEffect(() => {
122+
if (props.value === lastKnownFullEditorText) {
123+
return;
124+
}
125+
126+
setLastKnownFullEditorText(props.value ?? "");
127+
replaceEditorText(editor, props.value ?? "");
128+
}, [props.value]);
129+
130+
return (
131+
// Add the editable component inside the context.
132+
<Slate
133+
editor={editor}
134+
initialValue={initialValue}
135+
onChange={(value) => {
136+
const newEditorState = getTextAroundCursor(editor);
137+
138+
const fullEditorText = newEditorState
139+
? newEditorState.textBeforeCursor + newEditorState.textAfterCursor
140+
: getFullEditorTextWithNewlines(editor); // we don't double-parse the editor. When `newEditorState` is null, we didn't parse the editor yet.
141+
142+
setLastKnownFullEditorText(fullEditorText);
143+
onChangeHandlerForAutocomplete(newEditorState);
144+
props.onValueChange?.(fullEditorText);
145+
}}
146+
>
147+
<Editable
148+
renderElement={renderElementMemoized}
149+
renderPlaceholder={renderPlaceholderMemoized}
150+
onKeyDown={onKeyDownHandlerForAutocomplete}
151+
{...textareaLikeProps}
152+
/>
153+
</Slate>
154+
);
155+
}

packages/react-textarea/src/components/copilot-textarea/render-element.tsx renamed to packages/react-textarea/src/components/copilot-textarea/base-copilot-textarea/render-element.tsx

File renamed without changes.

packages/react-textarea/src/components/copilot-textarea/render-placeholder.tsx renamed to packages/react-textarea/src/components/copilot-textarea/base-copilot-textarea/render-placeholder.tsx

File renamed without changes.
Lines changed: 11 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,15 @@
11
// This example is for an Editor with `ReactEditor` and `HistoryEditor`
2-
import { Descendant, Editor } from "slate";
3-
import { Editable, RenderPlaceholderProps, Slate } from "slate-react";
4-
import {
5-
useCallback,
6-
useEffect,
7-
useMemo,
8-
useRef,
9-
useState,
10-
TextareaHTMLAttributes,
11-
} from "react";
12-
import { useAutosuggestions } from "../../hooks/use-autosuggestions";
13-
import { AutosuggestionState } from "../../types/autosuggestion-state";
14-
import { clearAutocompletionsFromEditor } from "../../lib/slatejs-edits/clear-autocompletions";
15-
import { addAutocompletionsToEditor } from "../../lib/slatejs-edits/add-autocompletions";
16-
import { useCopilotTextareaEditor } from "../../hooks/use-copilot-textarea-editor";
17-
import { renderElement } from "./render-element";
18-
import { useMakeAutosuggestionFunction } from "../../hooks";
2+
import { useMakeAutosuggestionFunction as useMakeStandardAutosuggestionFunction } from "../../hooks";
193
import {
204
AutosuggestionsConfig,
215
defaultAutosuggestionsConfig,
226
} from "../../types/autosuggestions-config";
23-
import { makeRenderPlaceholderFunction } from "./render-placeholder";
247
import {
25-
getFullEditorTextWithNewlines,
26-
getTextAroundCursor,
27-
} from "../../lib/get-text-around-cursor";
28-
import { replaceEditorText } from "../../lib/slatejs-edits/replace-text";
8+
BaseCopilotTextarea,
9+
BaseCopilotTextareaProps,
10+
} from "./base-copilot-textarea/base-copilot-textarea";
2911

30-
export interface CopilotTextareaProps
31-
extends TextareaHTMLAttributes<HTMLDivElement> {
32-
placeholderStyle?: React.CSSProperties;
33-
value?: string;
34-
onValueChange?: (value: string) => void;
12+
export interface CopilotTextareaProps extends BaseCopilotTextareaProps {
3513
autosuggestionsConfig: Partial<AutosuggestionsConfig>;
3614
}
3715

@@ -41,30 +19,7 @@ export function CopilotTextarea(props: CopilotTextareaProps): JSX.Element {
4119
...props.autosuggestionsConfig,
4220
};
4321

44-
// separate into TextareaHTMLAttributes<HTMLDivElement> and CopilotTextareaProps
45-
const {
46-
placeholderStyle,
47-
value,
48-
onValueChange,
49-
autosuggestionsConfig: autosuggestionsConfigFromProps,
50-
...textareaLikeProps
51-
} = props;
52-
53-
const valueOnInitialRender = useMemo(() => props.value ?? "", []);
54-
const [lastKnownFullEditorText, setLastKnownFullEditorText] =
55-
useState(valueOnInitialRender);
56-
57-
const initialValue: Descendant[] = useMemo(() => {
58-
return [
59-
{
60-
type: "paragraph",
61-
children: [{ text: valueOnInitialRender }],
62-
},
63-
];
64-
}, [valueOnInitialRender]);
65-
66-
const editor = useCopilotTextareaEditor();
67-
const autosuggestionsFunction = useMakeAutosuggestionFunction(
22+
const autosuggestionsFunction = useMakeStandardAutosuggestionFunction(
6823
autosuggestionsConfig.textareaPurpose,
6924
autosuggestionsConfig.apiEndpoint,
7025
autosuggestionsConfig.makeSystemMessage,
@@ -73,85 +28,11 @@ export function CopilotTextarea(props: CopilotTextareaProps): JSX.Element {
7328
autosuggestionsConfig.disableWhenEmpty
7429
);
7530

76-
const insertText = useCallback(
77-
(autosuggestion: AutosuggestionState) => {
78-
Editor.insertText(editor, autosuggestion.text, {
79-
at: autosuggestion.point,
80-
});
81-
},
82-
[editor]
83-
);
84-
const {
85-
currentAutocompleteSuggestion,
86-
onChangeHandler: onChangeHandlerForAutocomplete,
87-
onKeyDownHandler: onKeyDownHandlerForAutocomplete,
88-
} = useAutosuggestions(
89-
autosuggestionsConfig.debounceTime,
90-
autosuggestionsConfig.acceptAutosuggestionKey,
91-
autosuggestionsFunction,
92-
insertText
93-
);
94-
95-
// sync autosuggestions state with the editor
96-
useEffect(() => {
97-
clearAutocompletionsFromEditor(editor);
98-
if (currentAutocompleteSuggestion) {
99-
addAutocompletionsToEditor(
100-
editor,
101-
currentAutocompleteSuggestion.text,
102-
currentAutocompleteSuggestion.point
103-
);
104-
}
105-
}, [currentAutocompleteSuggestion]);
106-
107-
const renderElementMemoized = useCallback(renderElement, []);
108-
const renderPlaceholderMemoized = useMemo(() => {
109-
// For some reason slateJS specifies a top value of 0, which makes for strange styling. We override this here.
110-
const placeholderStyleSlatejsOverrides: React.CSSProperties = {
111-
top: undefined,
112-
};
113-
114-
const placeholderStyleAugmented: React.CSSProperties = {
115-
...placeholderStyleSlatejsOverrides,
116-
...props.placeholderStyle,
117-
};
118-
119-
return makeRenderPlaceholderFunction(placeholderStyleAugmented);
120-
}, [props.placeholderStyle]);
121-
122-
// update the editor text, but only when the value changes from outside the component
123-
useEffect(() => {
124-
if (props.value === lastKnownFullEditorText) {
125-
return;
126-
}
127-
128-
setLastKnownFullEditorText(props.value ?? "");
129-
replaceEditorText(editor, props.value ?? "");
130-
}, [props.value]);
131-
13231
return (
133-
// Add the editable component inside the context.
134-
<Slate
135-
editor={editor}
136-
initialValue={initialValue}
137-
onChange={(value) => {
138-
const newEditorState = getTextAroundCursor(editor);
139-
140-
const fullEditorText = newEditorState
141-
? newEditorState.textBeforeCursor + newEditorState.textAfterCursor
142-
: getFullEditorTextWithNewlines(editor); // we don't double-parse the editor. When `newEditorState` is null, we didn't parse the editor yet.
143-
144-
setLastKnownFullEditorText(fullEditorText);
145-
onChangeHandlerForAutocomplete(newEditorState);
146-
props.onValueChange?.(fullEditorText);
147-
}}
148-
>
149-
<Editable
150-
renderElement={renderElementMemoized}
151-
renderPlaceholder={renderPlaceholderMemoized}
152-
onKeyDown={onKeyDownHandlerForAutocomplete}
153-
{...textareaLikeProps}
154-
/>
155-
</Slate>
32+
<BaseCopilotTextarea
33+
{...props}
34+
autosuggestionsConfig={autosuggestionsConfig}
35+
autosuggestionsFunction={autosuggestionsFunction}
36+
/>
15637
);
15738
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export { BaseCopilotTextarea } from "./copilot-textarea/base-copilot-textarea/base-copilot-textarea";
12
export { CopilotTextarea } from "./copilot-textarea/copilot-textarea";
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { useMakeAutosuggestionFunction } from "./use-make-autosuggestions-function";
1+
export { useMakeAutosuggestionFunction } from "./make-autosuggestions-function/use-make-standard-autosuggestions-function";

packages/react-textarea/src/hooks/use-make-autosuggestions-function.tsx renamed to packages/react-textarea/src/hooks/make-autosuggestions-function/use-make-standard-autosuggestions-function.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useCallback, useState } from "react";
22
import { useContext } from "react";
33
import { CopilotContext } from "@copilotkit/react-core";
4-
import { AutosuggestionsBareFunction } from "./use-autosuggestions";
5-
import { MakeSystemMessage } from "../types";
4+
import { AutosuggestionsBareFunction } from "../use-autosuggestions";
5+
import { MakeSystemMessage } from "../../types";
66

77
export interface MinimalChatGPTMessage {
88
role: string;

packages/react-textarea/src/types/autosuggestions-config.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
1-
import { MinimalChatGPTMessage } from "../hooks/use-make-autosuggestions-function";
1+
import { MinimalChatGPTMessage } from "../hooks/make-autosuggestions-function/use-make-standard-autosuggestions-function";
22

33
export type MakeSystemMessage = (
44
textareaPurpose: string,
55
contextString: string
66
) => string;
77

8-
export interface AutosuggestionsConfig {
8+
export interface BaseAutosuggestionsConfig {
99
textareaPurpose: string;
1010
debounceTime: number;
1111
acceptAutosuggestionKey: string;
12+
}
1213

14+
export const defaultBaseAutosuggestionsConfig: BaseAutosuggestionsConfig = {
15+
textareaPurpose: "A generic textbox",
16+
debounceTime: 500,
17+
acceptAutosuggestionKey: "Tab",
18+
};
19+
20+
export interface AutosuggestionsConfig extends BaseAutosuggestionsConfig {
1321
apiEndpoint: string;
1422
contextCategories: string[] | undefined;
1523
makeSystemMessage: MakeSystemMessage;
@@ -80,9 +88,7 @@ export const defaultFewShotMessages: MinimalChatGPTMessage[] = [
8088
},
8189
];
8290
export const defaultAutosuggestionsConfig: AutosuggestionsConfig = {
83-
textareaPurpose: "A generic textbox",
84-
debounceTime: 500,
85-
acceptAutosuggestionKey: "Tab",
91+
...defaultBaseAutosuggestionsConfig,
8692

8793
apiEndpoint: "api/autosuggestions",
8894
makeSystemMessage: defaultMakeSystemMessage,

0 commit comments

Comments
 (0)