|
@@ -0,0 +1,96 @@
|
|
|
+import { Extension } from '@tiptap/core'
|
|
|
+import { Plugin, PluginKey } from 'prosemirror-state'
|
|
|
+
|
|
|
+export const AIAutocompletion = Extension.create({
|
|
|
+ name: 'aiAutocompletion',
|
|
|
+
|
|
|
+ addOptions() {
|
|
|
+ return {
|
|
|
+ generateCompletion: () => Promise.resolve(''),
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ addGlobalAttributes() {
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ types: ['paragraph'],
|
|
|
+ attributes: {
|
|
|
+ class: {
|
|
|
+ default: null,
|
|
|
+ parseHTML: element => element.getAttribute('class'),
|
|
|
+ renderHTML: attributes => {
|
|
|
+ if (!attributes.class) return {}
|
|
|
+ return { class: attributes.class }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ 'data-prompt': {
|
|
|
+ default: null,
|
|
|
+ parseHTML: element => element.getAttribute('data-prompt'),
|
|
|
+ renderHTML: attributes => {
|
|
|
+ if (!attributes['data-prompt']) return {}
|
|
|
+ return { 'data-prompt': attributes['data-prompt'] }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ 'data-suggestion': {
|
|
|
+ default: null,
|
|
|
+ parseHTML: element => element.getAttribute('data-suggestion'),
|
|
|
+ renderHTML: attributes => {
|
|
|
+ if (!attributes['data-suggestion']) return {}
|
|
|
+ return { 'data-suggestion': attributes['data-suggestion'] }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ]
|
|
|
+ },
|
|
|
+
|
|
|
+ addProseMirrorPlugins() {
|
|
|
+ return [
|
|
|
+ new Plugin({
|
|
|
+ key: new PluginKey('aiAutocompletion'),
|
|
|
+ props: {
|
|
|
+ handleKeyDown: (view, event) => {
|
|
|
+ if (event.key !== 'Tab') return false
|
|
|
+
|
|
|
+ const { state, dispatch } = view
|
|
|
+ const { selection } = state
|
|
|
+ const { $head } = selection
|
|
|
+
|
|
|
+ if ($head.parent.type.name !== 'paragraph') return false
|
|
|
+
|
|
|
+ const node = $head.parent
|
|
|
+ const prompt = node.textContent
|
|
|
+
|
|
|
+ if (!node.attrs['data-suggestion']) {
|
|
|
+ // Generate completion
|
|
|
+ this.options.generateCompletion(prompt).then(suggestion => {
|
|
|
+ if (suggestion && suggestion.trim() !== '') {
|
|
|
+ dispatch(state.tr.setNodeMarkup($head.before(), null, {
|
|
|
+ ...node.attrs,
|
|
|
+ class: 'ai-autocompletion',
|
|
|
+ 'data-prompt': prompt,
|
|
|
+ 'data-suggestion': suggestion,
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ // Accept suggestion
|
|
|
+ const suggestion = node.attrs['data-suggestion']
|
|
|
+ dispatch(state.tr
|
|
|
+ .insertText(suggestion, $head.pos)
|
|
|
+ .setNodeMarkup($head.before(), null, {
|
|
|
+ ...node.attrs,
|
|
|
+ class: null,
|
|
|
+ 'data-prompt': null,
|
|
|
+ 'data-suggestion': null,
|
|
|
+ })
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ return true
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ ]
|
|
|
+ },
|
|
|
+})
|