mirror of
https://github.com/open-webui/open-webui
synced 2024-12-28 23:02:25 +00:00
feat: ai autocompletion
This commit is contained in:
parent
8300fa85b0
commit
95000c7b15
@ -214,6 +214,13 @@ input[type='number'] {
|
|||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ai-autocompletion::after {
|
||||||
|
color: #a0a0a0;
|
||||||
|
|
||||||
|
content: attr(data-suggestion);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.tiptap > pre > code {
|
.tiptap > pre > code {
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
|
@ -10,16 +10,18 @@
|
|||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
const eventDispatch = createEventDispatcher();
|
const eventDispatch = createEventDispatcher();
|
||||||
|
|
||||||
import { EditorState, Plugin, TextSelection } from 'prosemirror-state';
|
import { EditorState, Plugin, PluginKey, TextSelection } from 'prosemirror-state';
|
||||||
|
import { Decoration, DecorationSet } from 'prosemirror-view';
|
||||||
|
|
||||||
import { Editor } from '@tiptap/core';
|
import { Editor } from '@tiptap/core';
|
||||||
|
|
||||||
|
import { AIAutocompletion } from './RichTextInput/AutoCompletion.js';
|
||||||
|
|
||||||
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
|
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
|
||||||
import Placeholder from '@tiptap/extension-placeholder';
|
import Placeholder from '@tiptap/extension-placeholder';
|
||||||
import Highlight from '@tiptap/extension-highlight';
|
import Highlight from '@tiptap/extension-highlight';
|
||||||
import Typography from '@tiptap/extension-typography';
|
import Typography from '@tiptap/extension-typography';
|
||||||
import StarterKit from '@tiptap/starter-kit';
|
import StarterKit from '@tiptap/starter-kit';
|
||||||
|
|
||||||
import { all, createLowlight } from 'lowlight';
|
import { all, createLowlight } from 'lowlight';
|
||||||
|
|
||||||
import { PASTED_TEXT_CHARACTER_LIMIT } from '$lib/constants';
|
import { PASTED_TEXT_CHARACTER_LIMIT } from '$lib/constants';
|
||||||
@ -32,6 +34,7 @@
|
|||||||
export let value = '';
|
export let value = '';
|
||||||
export let id = '';
|
export let id = '';
|
||||||
|
|
||||||
|
export let autocomplete = false;
|
||||||
export let messageInput = false;
|
export let messageInput = false;
|
||||||
export let shiftEnter = false;
|
export let shiftEnter = false;
|
||||||
export let largeTextAsFile = false;
|
export let largeTextAsFile = false;
|
||||||
@ -147,7 +150,16 @@
|
|||||||
}),
|
}),
|
||||||
Highlight,
|
Highlight,
|
||||||
Typography,
|
Typography,
|
||||||
Placeholder.configure({ placeholder })
|
Placeholder.configure({ placeholder }),
|
||||||
|
AIAutocompletion.configure({
|
||||||
|
generateCompletion: async (text) => {
|
||||||
|
// Implement your AI text generation logic here
|
||||||
|
// This should return a Promise that resolves to the suggested text
|
||||||
|
|
||||||
|
console.log(text);
|
||||||
|
return 'AI-generated suggestion';
|
||||||
|
}
|
||||||
|
})
|
||||||
],
|
],
|
||||||
content: content,
|
content: content,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
@ -292,3 +304,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={element} class="relative w-full min-w-full h-full min-h-fit {className}" />
|
<div bind:this={element} class="relative w-full min-w-full h-full min-h-fit {className}" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.ai-autocompletion::after {
|
||||||
|
content: attr(data-suggestion);
|
||||||
|
color: var(--gray-5);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
96
src/lib/components/common/RichTextInput/AutoCompletion.js
Normal file
96
src/lib/components/common/RichTextInput/AutoCompletion.js
Normal file
@ -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
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user