Skip to content

Commit

Permalink
Merge pull request #3681 from udecode/ime/ai
Browse files Browse the repository at this point in the history
  • Loading branch information
felixfeng33 authored Oct 29, 2024
2 parents a90d071 + d563913 commit 1c34984
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 82 deletions.
6 changes: 6 additions & 0 deletions .changeset/big-lobsters-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@udecode/plate-ai': patch
---
Copilot:
- Fix the issue where `getNextWord` returns the entire sentence in Chinese/Korean/Japanese.
- When entering two characters using IME, the suggestion text should not be lost.
15 changes: 4 additions & 11 deletions packages/ai/src/react/copilot/CopilotPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type { CompleteOptions } from './utils/callCompletionApi';
import { renderCopilotBelowNodes } from './renderCopilotBelowNodes';
import { acceptCopilot } from './transforms/acceptCopilot';
import { acceptCopilotNextWord } from './transforms/acceptCopilotNextWord';
import { type GetNextWord, getNextWord } from './utils/getNextWord';
import { triggerCopilotSuggestion } from './utils/triggerCopilotSuggestion';
import { withCopilot } from './withCopilot';

Expand All @@ -44,11 +45,6 @@ type CompletionState = {
export type CopilotPluginConfig = PluginConfig<
'copilot',
CompletionState & {
/** Get the next word to be inserted. */
getNextWord?: (options: { text: string }) => {
firstWord: string;
remainingText: string;
};
/**
* Conditions to auto trigger copilot, used in addition to triggerQuery.
* Disabling defaults to:
Expand All @@ -69,6 +65,8 @@ export type CopilotPluginConfig = PluginConfig<
* @default 0
*/
debounceDelay?: number;
/** Get the next word to be inserted. */
getNextWord?: GetNextWord;
/**
* Get the prompt for AI completion.
*
Expand Down Expand Up @@ -139,12 +137,7 @@ export const CopilotPlugin = createTPlatePlugin<CopilotPluginConfig>({
completion: '',
debounceDelay: 0,
error: null,
getNextWord: ({ text }) => {
const firstWord = /^\s*\S+/.exec(text)?.[0] || '';
const remainingText = text.slice(firstWord.length);

return { firstWord, remainingText };
},
getNextWord: getNextWord,
getPrompt: ({ editor }) => {
const contextEntry = getAncestorNode(editor);

Expand Down
48 changes: 48 additions & 0 deletions packages/ai/src/react/copilot/utils/getNextWord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export type GetNextWord = (options: { text: string }) => {
firstWord: string;
remainingText: string;
};

export const getNextWord: GetNextWord = ({ text }) => {
if (!text) return { firstWord: '', remainingText: '' };

// Check if the first non-space character is a CJK character
const nonSpaceMatch = /^\s*(\S)/.exec(text);

if (!nonSpaceMatch) return { firstWord: '', remainingText: '' };

const firstNonSpaceChar = nonSpaceMatch[1];

// Regular expression for matching CJK characters
// 1. [\u4E00-\u9FA5] - Chinese Characters
// 2. [\u3040-\u309F] - Japanese Hiragana
// 3. [\u30A0-\u30FF] - Japanese Katakana
// 4. [\u3400-\u4DBF] - CJK Extension A
// 5. [\u4E00-\u9FFF] - CJK Unified Ideographs
// 6. [\uF900-\uFAFF] - CJK Compatibility Ideographs
// 7. [\uAC00-\uD7AF] - Korean Syllables
// 8. [\u1100-\u11FF] - Korean Jamo
const isCJKChar =
/[\u1100-\u11FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF]/.test(
firstNonSpaceChar
);

let firstWord, remainingText;

if (isCJKChar) {
// CJK characters: match leading spaces + first character + trailing spaces
const match =
// eslint-disable-next-line regexp/no-unused-capturing-group
/^(\s*[\u1100-\u11FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF]\s*)/.exec(
text
);
firstWord = match?.[0] || '';
remainingText = text.slice(firstWord.length);
} else {
// Other characters (e.g., English): use space-based word separation
firstWord = /^\s*\S+/.exec(text)?.[0] || '';
remainingText = text.slice(firstWord.length);
}

return { firstWord, remainingText };
};
5 changes: 3 additions & 2 deletions packages/ai/src/react/copilot/withCopilot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,11 @@ export const withCopilot: ExtendEditor<CopilotPluginConfig> = ({
editor.insertText = (text) => {
const suggestionText = getOptions().suggestionText;

if (suggestionText && text.length === 1 && text === suggestionText?.at(0)) {
// When using IME input, it’s possible to enter two characters at once.
if (suggestionText?.startsWith(text)) {
withoutAbort(editor, () => {
withoutMergingHistory(editor, () => {
const newText = suggestionText?.slice(1);
const newText = suggestionText?.slice(text.length);
setOption('suggestionText', newText);
insertText(text);
});
Expand Down
Loading

0 comments on commit 1c34984

Please sign in to comment.