From 6e40a92315199ecfb40aff45a8ec17ab5e34f01f Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 30 Dec 2024 15:20:55 -0800 Subject: [PATCH] add FIM streaming + instructions --- .../void/browser/inlineDiffsService.ts | 60 +++++----- .../contrib/void/browser/prompt/prompts.ts | 112 ++++++++++++++++-- .../browser/react/src/ctrl-k-tsx/CtrlK.tsx | 5 + .../react/src/ctrl-k-tsx/CtrlKChat.tsx | 14 ++- .../browser/react/src/ctrl-k-tsx/index.tsx | 4 + 5 files changed, 151 insertions(+), 44 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index 4ca990e1..8268c320 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -27,13 +27,14 @@ import * as dom from '../../../../base/browser/dom.js'; import { Widget } from '../../../../base/browser/ui/widget.js'; import { URI } from '../../../../base/common/uri.js'; import { IConsistentItemService } from './helperServices/consistentItemService.js'; -import { inlineDiff_systemMessage } from './prompt/prompts.js'; +import { ctrlKStream_prefixAndSuffix, ctrlKStream_prompt, ctrlKStream_systemMessage, ctrlLStream_prompt, ctrlLStream_systemMessage } from './prompt/prompts.js'; import { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js'; import { IPosition } from '../../../../editor/common/core/position.js'; import { mountCtrlK } from '../browser/react/out/ctrl-k-tsx/index.js' import { QuickEditPropsType } from './quickEditActions.js'; import { InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js'; +import { LLMMessage } from '../../../../platform/void/common/llmMessageTypes.js'; const configOfBG = (color: Color) => { return { dark: color, light: color, hcDark: color, hcLight: color, } @@ -302,6 +303,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const viewZone: IViewZone = { afterLineNumber: ctrlKZone.startLine - 1, domNode: domNode, + heightInPx: 0, suppressMouseDown: false, }; @@ -640,7 +642,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { // changes the start/line locations of all DiffAreas on the page (adjust their start/end based on the change) based on the change that was recently made private _realignAllDiffAreasLines(uri: URI, text: string, recentChange: { startLineNumber: number; endLineNumber: number }) { - console.log('recent change', recentChange) + // console.log('recent change', recentChange) const model = this._getModel(uri) if (!model) return @@ -930,9 +932,9 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const adding: Omit = { type: 'DiffZone', - originalCode: originalCode, - startLine: startLine, - endLine: endLine, // starts out the same as the current file + originalCode, + startLine, + endLine, _URI: uri, _streamState: { isStreaming: true, @@ -944,40 +946,38 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { } const diffZone = this._addDiffArea(adding) - // actually call the LLM - const userContent = featureName === 'Ctrl+L' ? `\ -ORIGINAL_CODE -\`\`\` -${originalCode} -\`\`\` - -DIFF -\`\`\` -${userMessage} -\`\`\` - -INSTRUCTIONS -Please finish writing the new file by applying the diff to the original file. Return ONLY the completion of the file, without any explanation. -` - : `\ -CTRL K MESSAGE GOES HERE __TODO__! -${userMessage} -` + let messages: LLMMessage[] + if (featureName === 'Ctrl+L') { + const userContent = ctrlLStream_prompt({ originalCode, userMessage }) + messages = [ + // TODO include more context too + { role: 'system', content: ctrlLStream_systemMessage, }, + { role: 'user', content: userContent, } + ] + } + else if (featureName === 'Ctrl+K') { + const { prefix, suffix } = ctrlKStream_prefixAndSuffix({ fullFileStr: currentFileStr, startLine, endLine }) + const userContent = ctrlKStream_prompt({ selection: originalCode, userMessage, prefix, suffix }) + console.log('PREFIX:\n', prefix) + console.log('SUFFIX:\n', suffix) + console.log('USER CONTENT:\n', userContent) + messages = [ + // TODO include more context too (LSP, file history, etc) + { role: 'system', content: ctrlKStream_systemMessage, }, + { role: 'user', content: userContent, } + ] + } + else { throw new Error(`featureName ${featureName} is invalid`) } // __TODO__ make these only move forward - const latestCurrentFileEnd: IPosition = { lineNumber: 1, column: 1 } const latestOriginalFileStart: IPosition = { lineNumber: 1, column: 1 } streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({ featureName, logging: { loggingName: 'streamChunk' }, - messages: [ - { role: 'system', content: inlineDiff_systemMessage, }, - // TODO include more context too - { role: 'user', content: userContent, } - ], + messages, onText: ({ newText, fullText }) => { this._writeDiffZoneLLMText(diffZone, fullText, latestCurrentFileEnd, latestOriginalFileStart) this._refreshStylesAndDiffsInURI(uri) diff --git a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts index 49493bbd..f95c0165 100644 --- a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts @@ -121,10 +121,7 @@ export const chat_prompt = (instructions: string, selections: CodeSelection[] | - - - -export const inlineDiff_systemMessage = ` +export const ctrlLStream_systemMessage = ` You are a coding assistant that applies a diff to a file. You are given the original file \`original_file\`, a diff \`diff\`, and a new file that you are applying the diff to \`new_file\`. Please finish writing the new file \`new_file\`, according to the diff \`diff\`. You must completely re-write the whole file, using the diff. @@ -255,14 +252,106 @@ export default Sidebar;\`\`\` -export const generateCtrlKPrompt = ({ selection, prefix, suffix, instructions, }: { selection: string, prefix: string, suffix: string, instructions: string, }) => `\ +export const ctrlLStream_prompt = ({ originalCode, userMessage }: { originalCode: string, userMessage: string }) => { + return `\ +ORIGINAL_CODE +\`\`\` +${originalCode} +\`\`\` + +DIFF +\`\`\` +${userMessage} +\`\`\` + +INSTRUCTIONS +Please finish writing the new file by applying the diff to the original file. Return ONLY the completion of the file, without any explanation. +` +} + + + +export const ctrlKStream_systemMessage = `\ +` + + +export const ctrlKStream_prefixAndSuffix = ({ fullFileStr, startLine, endLine }: { fullFileStr: string, startLine: number, endLine: number }) => { + + const fullFileLines = fullFileStr.split('\n') + + // we can optimize this later + const MAX_CHARS = 1024 + /* + + a + a + a <-- final i (prefix = a\na\n) + a + |b <-- startLine-1 (middle = b\nc\nd\n) <-- initial i (moves up) + c + d| <-- endLine-1 <-- initial j (moves down) + e + e <-- final j (suffix = e\ne\n) + e + e + */ + + let prefix = '' + let i = startLine - 1 // 0-indexed exclusive + // we'll include fullFileLines[i...(startLine-1)-1].join('\n') in the prefix. + while (i !== 0) { + const newLine = fullFileLines[i - 1] + if (newLine.length + 1 + prefix.length <= MAX_CHARS) { // +1 to include the \n + prefix = `${newLine}\n${prefix}` + i -= 1 + } + else break + } + + let suffix = '' + let j = endLine - 1 + while (j !== fullFileLines.length - 1) { + const newLine = fullFileLines[j + 1] + if (newLine.length + 1 + suffix.length <= MAX_CHARS) { // +1 to include the \n + suffix = `${suffix}\n${newLine}` + j += 1 + } + else break + } + + return { prefix, suffix } + +} + +export const ctrlKStream_prompt = ({ selection, prefix, suffix, userMessage }: { selection: string, prefix: string, suffix: string, userMessage: string, }) => { + const onlySpeaksFIM = false + + if (onlySpeaksFIM) { + const preTag = 'PRE' + const sufTag = 'SUF' + const midTag = 'MID' + return `\ +<${preTag}> +/* Original Selection: +${selection}*/ +/* Instructions: ${userMessage}*/ +${prefix} +<${sufTag}>${suffix} +<${midTag}>` + } + // prompt the model on how to do FIM + else { + const preTag = 'PRE' + const sufTag = 'SUF' + const midTag = 'MID' + return `\ Here is the user's original selection: \`\`\` -${selection} +<${midTag}>${selection} \`\`\` The user wants to apply the following instructions to the selection: -${instructions} +${userMessage} Please rewrite the selection following the user's instructions. @@ -273,10 +362,11 @@ Instructions to follow: 3. Be careful not to duplicate or remove variables, comments, or other syntax by mistake Complete the following: -\`\`\` -
${prefix}
-${suffix} -`; +<${preTag}>${prefix} +<${sufTag}>${suffix} +<${midTag}>` + } +}; diff --git a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlK.tsx b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlK.tsx index 6f79e10d..f10557fe 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlK.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlK.tsx @@ -1,3 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ + import { useEffect, useState } from 'react' import { useIsDark, useSidebarState } from '../util/services.js' import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js' diff --git a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx index 97dc79a7..114fb1f1 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ import React, { FormEvent, useCallback, useEffect, useRef, useState } from 'react'; import { useSettingsState, useSidebarState, useThreadsState, useQuickEditState, useAccessor } from '../util/services.js'; @@ -20,9 +24,13 @@ export const CtrlKChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onChang const inputContainer = sizerRef.current if (!inputContainer) return; + + // inputBoxRef.current?.onDidHeightChange(height => { + // console.log('NEW HEIGHT',height) + // onChangeHeight(height + 40) + // }) // only observing 1 element let resizeObserver: ResizeObserver | undefined - resizeObserver = new ResizeObserver((entries) => { const height = entries[0].borderBoxSize[0].blockSize console.log('NEW HEIGHT', height) @@ -105,7 +113,7 @@ export const CtrlKChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onChang >
{/* left (input) */} @@ -126,7 +134,7 @@ export const CtrlKChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onChang
{/* right (button) */} -
+
{/* submit / stop button */} {isStreaming ? // stop button diff --git a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/index.tsx b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/index.tsx index 3b4882d5..4d59dbca 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/index.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/index.tsx @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ import { mountFnGenerator } from '../util/mountFnGenerator.js' import { CtrlK } from './CtrlK.js'