Spaces:
Paused
Paused
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; | |
import { fetchSSE } from './fetch-sse'; | |
interface llmParams { | |
model?: string; | |
temperature?: number; | |
max_tokens?: number; | |
top_p?: number; | |
frequency_penalty?: number; | |
presence_penalty?: number; | |
stop?: Array<string>; | |
} | |
type CursorStyle = | |
| 'line' | |
| 'block' | |
| 'underline' | |
| 'line-thin' | |
| 'block-outline' | |
| 'underline-thin'; | |
export interface Config { | |
llmKey?: string; | |
llmUrl?: string; | |
llmParams?: llmParams; | |
customCompletionFunction?: (code: string) => Promise<string>; | |
maxCodeLinesTollm?: number; | |
cursorStyleLoading?: CursorStyle; | |
cursorStyleNormal?: CursorStyle; | |
assistantMessage?: string; | |
} | |
export const defaultllmParams: llmParams = { | |
model: '', | |
temperature: 0, | |
max_tokens: 64, | |
top_p: 1.0, | |
frequency_penalty: 0.0, | |
presence_penalty: 0.0, | |
}; | |
export const defaultConfig: Config = { | |
llmKey: '', | |
llmUrl: 'https://matthoffner-ggml-coding-llm.hf.space/completion', | |
llmParams: defaultllmParams, | |
cursorStyleLoading: 'underline', | |
cursorStyleNormal: 'line', | |
assistantMessage: '', | |
}; | |
function minimizeWhitespace(code:string) { | |
return code | |
.split('\n') | |
.map((line:string) => line.trim()) | |
.join('\n'); | |
} | |
async function fetchCompletionFromllm( | |
code: string, | |
config: Config, | |
controller: AbortController, | |
handleInsertion: (text: string) => void | |
): Promise<void> { | |
const handleMessage = (message: string) => { | |
handleInsertion(message); | |
}; | |
let text = '' | |
return new Promise(async (resolve, reject) => { | |
await fetchSSE(config.llmUrl, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
authorization: `Bearer ${config.llmKey}`, | |
}, | |
body: JSON.stringify({ | |
prompt: code, | |
...config.llmParams, | |
}), | |
signal: controller.signal, | |
onMessage: (data) => { | |
let lastResponse; | |
if (data === "[DONE]") { | |
text = text.trim(); | |
return resolve(); | |
} | |
try { | |
const response = JSON.parse(data); | |
if ((lastResponse = response == null ? void 0 : response) == null ? void 0 : lastResponse.length) { | |
text += response || ''; | |
handleMessage == null ? void 0 : handleMessage(text); | |
} | |
} catch (err) { | |
console.warn("llm stream SEE event unexpected error", err); | |
return reject(err); | |
} | |
}, | |
onError: (error: any) => { | |
console.error(error); | |
} | |
}); | |
}) | |
} | |
const handleCompletion = async ( | |
editor: monaco.editor.IStandaloneCodeEditor, | |
config: Config, | |
controller: AbortController, | |
cursorStyleLoading: () => void, | |
cursorStyleNormal: () => void | |
) => { | |
const currentPosition = editor.getPosition(); | |
if (!currentPosition) { | |
return; | |
} | |
const currentLineNumber = currentPosition.lineNumber; | |
const startLineNumber = !config.maxCodeLinesTollm | |
? 1 | |
: Math.max(1, currentLineNumber - config.maxCodeLinesTollm); | |
const endLineNumber = currentLineNumber; | |
const code = editor | |
.getModel()! | |
.getLinesContent() | |
.slice(startLineNumber - 1, endLineNumber) | |
.join('\n'); | |
cursorStyleLoading(); | |
let lastText = '' | |
const handleInsertion = (text: string) => { | |
const position = editor.getPosition(); | |
if (!position) { | |
return; | |
} | |
const offset = editor.getModel()?.getOffsetAt(position); | |
if (!offset) { | |
return; | |
} | |
const edits = [ | |
{ | |
range: { | |
startLineNumber: position.lineNumber, | |
startColumn: position.column, | |
endLineNumber: position.lineNumber, | |
endColumn: position.column, | |
}, | |
text: text.slice(lastText.length), | |
}, | |
]; | |
lastText = text | |
editor.executeEdits('', edits); | |
}; | |
try { | |
let newCode = ''; | |
if (config.customCompletionFunction) { | |
newCode = await config.customCompletionFunction(code); | |
handleInsertion(newCode); | |
} else { | |
await fetchCompletionFromllm(code, config, controller, handleInsertion); | |
} | |
cursorStyleNormal(); | |
} catch (error) { | |
cursorStyleNormal(); | |
console.error('MonacoEditorCopilot error:', error); | |
} | |
}; | |
const MonacoEditorCopilot = ( | |
editor: monaco.editor.IStandaloneCodeEditor, | |
config: Config | |
) => { | |
const mergedConfig: Config = { | |
...defaultConfig, | |
...config, | |
llmParams: { ...defaultllmParams, ...config.llmParams }, | |
}; | |
const cursorStyleLoading = () => { | |
editor.updateOptions({ cursorStyle: mergedConfig.cursorStyleLoading }); | |
}; | |
const cursorStyleNormal = () => { | |
editor.updateOptions({ cursorStyle: mergedConfig.cursorStyleNormal }); | |
}; | |
cursorStyleNormal(); | |
let controller: AbortController | null = null; | |
const cancel = () => { | |
if (controller) { | |
controller.abort(); | |
} | |
cursorStyleNormal(); | |
} | |
const keyDownHandler = editor.onKeyDown(cancel); | |
const mouseDownHandler = editor.onMouseDown(cancel); | |
let copilotAction: monaco.editor.IActionDescriptor | null = { | |
id: 'copilot-completion', | |
label: 'Trigger Copilot Completion', | |
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyB], | |
contextMenuGroupId: 'navigation', | |
contextMenuOrder: 1.5, | |
run: async () => { | |
controller = new AbortController(); | |
await handleCompletion( | |
editor, | |
mergedConfig, | |
controller, | |
cursorStyleLoading, | |
cursorStyleNormal | |
); | |
}, | |
}; | |
editor.addAction(copilotAction); | |
const dispose = () => { | |
keyDownHandler.dispose(); | |
mouseDownHandler.dispose(); | |
}; | |
return dispose; | |
}; | |
export default MonacoEditorCopilot; |