mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-10 18:05:48 +02:00
224 lines
6.2 KiB
TypeScript
224 lines
6.2 KiB
TypeScript
import type { Editor } from 'ace-builds'
|
|
import type { Point } from 'ace-builds-internal/document'
|
|
import openai from '@/api/openai'
|
|
import { debounce } from 'lodash'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
|
|
// eslint-disable-next-line ts/no-explicit-any
|
|
function debug(...args: any[]) {
|
|
if (import.meta.env.DEV) {
|
|
// eslint-disable-next-line no-console
|
|
console.debug(`[CodeEditor]`, ...args)
|
|
}
|
|
}
|
|
|
|
function useCodeCompletion() {
|
|
const editorRef = ref<Editor>()
|
|
const currentGhostText = ref<string>('')
|
|
|
|
const ws = openai.code_completion()
|
|
|
|
function getAISuggestions(code: string, context: string, position: Point, callback: (suggestion: string) => void, language: string = 'nginx', suffix: string = '', requestId: string) {
|
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
debug('WebSocket is not open')
|
|
return
|
|
}
|
|
|
|
if (!code.trim()) {
|
|
debug('Code is empty')
|
|
return
|
|
}
|
|
|
|
const message = {
|
|
context,
|
|
code,
|
|
suffix,
|
|
language,
|
|
position,
|
|
request_id: requestId,
|
|
}
|
|
|
|
debug('Sending message', message)
|
|
|
|
ws.send(JSON.stringify(message))
|
|
|
|
ws.onmessage = event => {
|
|
const data = JSON.parse(event.data)
|
|
debug(`Received message`, data, requestId)
|
|
if (data.request_id === requestId) {
|
|
callback(data.code)
|
|
}
|
|
}
|
|
}
|
|
|
|
function applyGhostText() {
|
|
if (!editorRef.value) {
|
|
debug('Editor instance not available yet')
|
|
return
|
|
}
|
|
|
|
try {
|
|
const currentText = editorRef.value.getValue()
|
|
const cursorPosition = editorRef.value.getCursorPosition()
|
|
|
|
// Get all text before the current cursor position as the code part for the request
|
|
const allLines = currentText.split('\n')
|
|
const currentLine = allLines[cursorPosition.row]
|
|
const textUpToCursor = allLines.slice(0, cursorPosition.row).join('\n')
|
|
+ (cursorPosition.row > 0 ? '\n' : '')
|
|
+ currentLine.substring(0, cursorPosition.column)
|
|
|
|
// Get text after cursor position as suffix
|
|
const textAfterCursor = currentLine.substring(cursorPosition.column)
|
|
+ (cursorPosition.row < allLines.length - 1 ? '\n' : '')
|
|
+ allLines.slice(cursorPosition.row + 1).join('\n')
|
|
|
|
// Generate new request ID
|
|
const requestId = uuidv4()
|
|
|
|
// Clear existing ghost text before making the request
|
|
clearGhostText()
|
|
|
|
// Get AI suggestions
|
|
getAISuggestions(
|
|
textUpToCursor,
|
|
currentText,
|
|
cursorPosition,
|
|
suggestion => {
|
|
debug(`AI suggestions applied: ${suggestion}`)
|
|
|
|
// If there's a suggestion, set ghost text
|
|
if (suggestion && typeof editorRef.value!.setGhostText === 'function') {
|
|
clearGhostText()
|
|
|
|
// Get current cursor position (may have changed during async process)
|
|
const newPosition = editorRef.value!.getCursorPosition()
|
|
|
|
editorRef.value!.setGhostText(suggestion, {
|
|
column: newPosition.column,
|
|
row: newPosition.row,
|
|
})
|
|
debug(`Ghost text set: ${suggestion}`)
|
|
currentGhostText.value = suggestion
|
|
}
|
|
else if (suggestion) {
|
|
debug('setGhostText method not available on editor instance')
|
|
}
|
|
},
|
|
editorRef.value.session.getMode()?.path?.split('/').pop() || 'text',
|
|
textAfterCursor, // Pass text after cursor as suffix
|
|
requestId, // Pass request ID
|
|
)
|
|
}
|
|
catch (error) {
|
|
debug(`Error in applyGhostText: ${error}`)
|
|
}
|
|
}
|
|
|
|
// Accept the ghost text suggestion with Tab key
|
|
function setupTabHandler(editor: Editor) {
|
|
if (!editor) {
|
|
debug('Editor not available in setupTabHandler')
|
|
return
|
|
}
|
|
|
|
debug('Setting up Tab key handler')
|
|
|
|
// Remove existing command to avoid conflicts
|
|
const existingCommand = editor.commands.byName.acceptGhostText
|
|
if (existingCommand) {
|
|
editor.commands.removeCommand(existingCommand)
|
|
}
|
|
|
|
// Register new Tab key handler command with highest priority
|
|
editor.commands.addCommand({
|
|
name: 'acceptGhostText',
|
|
bindKey: { win: 'Tab', mac: 'Tab' },
|
|
exec: (editor: Editor) => {
|
|
// Use our saved ghost text, not dependent on editor.ghostText
|
|
if (currentGhostText.value) {
|
|
debug(`Accepting ghost text: ${currentGhostText.value}`)
|
|
|
|
const position = editor.getCursorPosition()
|
|
const text = currentGhostText.value
|
|
|
|
// Insert text through session API
|
|
editor.session.insert(position, text)
|
|
|
|
clearGhostText()
|
|
|
|
debug('Ghost text inserted successfully')
|
|
return true // Prevent event propagation
|
|
}
|
|
|
|
debug('No ghost text to accept, allowing default tab behavior')
|
|
return false // Allow default Tab behavior
|
|
},
|
|
readOnly: false,
|
|
})
|
|
|
|
debug('Tab key handler set up successfully')
|
|
}
|
|
|
|
// Clear ghost text and reset state
|
|
function clearGhostText() {
|
|
if (!editorRef.value)
|
|
return
|
|
|
|
if (typeof editorRef.value.removeGhostText === 'function') {
|
|
editorRef.value.removeGhostText()
|
|
}
|
|
currentGhostText.value = ''
|
|
}
|
|
|
|
const debouncedApplyGhostText = debounce(applyGhostText, 1000, { leading: false, trailing: true })
|
|
|
|
debug('Editor initialized')
|
|
|
|
async function init(editor: Editor) {
|
|
const { enabled } = await openai.get_code_completion_enabled_status()
|
|
if (!enabled) {
|
|
debug('Code completion is not enabled')
|
|
return
|
|
}
|
|
|
|
editorRef.value = editor
|
|
|
|
// Set up Tab key handler
|
|
setupTabHandler(editor)
|
|
|
|
setTimeout(() => {
|
|
editor.on('change', (e: { action: string }) => {
|
|
debug(`Editor change event: ${e.action}`)
|
|
// If change is caused by user input, interrupt current completion
|
|
clearGhostText()
|
|
|
|
if (e.action === 'insert' || e.action === 'remove') {
|
|
// Clear current ghost text
|
|
debouncedApplyGhostText()
|
|
}
|
|
})
|
|
|
|
// Listen for cursor changes, using debounce
|
|
editor.selection.on('changeCursor', () => {
|
|
debug('Cursor changed')
|
|
clearGhostText()
|
|
debouncedApplyGhostText()
|
|
})
|
|
}, 2000)
|
|
}
|
|
|
|
function cleanUp() {
|
|
if (ws) {
|
|
ws.close()
|
|
}
|
|
debug('CodeCompletion unmounted')
|
|
}
|
|
|
|
return {
|
|
init,
|
|
cleanUp,
|
|
}
|
|
}
|
|
|
|
export default useCodeCompletion
|