From bc547d87cbdba60c0ba1511c8258be8b04d644a8 Mon Sep 17 00:00:00 2001 From: Arshad Yaseen Date: Fri, 17 May 2024 06:02:32 +0530 Subject: [PATCH] Improve prompt --- docs/components/editor-demo.tsx | 138 +++++--------------------- docs/components/features.tsx | 17 ++-- src/classes/copilot.ts | 4 +- src/constants/common.ts | 2 +- src/constants/contextual-filter.ts | 2 +- src/helpers/copilot.ts | 68 +++++++------ src/hooks/use-typing-debounce-fn.ts | 20 ++-- src/types/completion.ts | 2 +- src/types/editor-props.ts | 4 +- src/utils/completion/syntax-parser.ts | 28 +++--- 10 files changed, 103 insertions(+), 182 deletions(-) diff --git a/docs/components/editor-demo.tsx b/docs/components/editor-demo.tsx index 568b4b3a..1d7c708c 100644 --- a/docs/components/editor-demo.tsx +++ b/docs/components/editor-demo.tsx @@ -1,130 +1,42 @@ -import {useState} from 'react'; - -import {Tabs, TabsList, TabsTrigger} from '@/components/ui/tabs'; -import {TabsContent} from '@radix-ui/react-tabs'; import {motion} from 'framer-motion'; -import {Editor, EditorProps, Theme} from 'monacopilot'; +import {Editor, Theme} from 'monacopilot'; import {useTheme} from 'next-themes'; -interface File { - filename: string; - language: string; - path: string; - content: string; -} - -const COPILOT_ENDPOINT = '/api/copilot'; - -const EDITOR_OPTIONS: EditorProps['options'] = { - padding: {top: 60, bottom: 16}, - overviewRulerBorder: false, - overviewRulerLanes: 0, - scrollBeyondLastLine: false, - fontFamily: 'var(--font-mono)', - fontSize: 15, - renderLineHighlightOnlyWhenFocus: true, - lineDecorationsWidth: 0, +const EDITOR_DEFAULTS = { + value: `// Start coding here to see the autocompletions in action!`, + language: 'javascript', + options: { + padding: {top: 16, bottom: 16}, + overviewRulerBorder: false, + overviewRulerLanes: 0, + scrollBeyondLastLine: false, + fontFamily: 'var(--font-mono)', + fontSize: 15, + scrollbar: {alwaysConsumeMouseWheel: false}, + }, }; const EditorDemo = () => { - const [files, setFiles] = useState>([ - { - filename: 'index.js', - language: 'javascript', - path: './index.js', - content: `import { add } from './utils';\n\nconst result = add(1, 2);`, - }, - { - filename: 'utils.js', - language: 'javascript', - path: './utils.js', - content: - 'export function add(a, b) { return a + b; }\nexport function subtract(a, b) { return a - b; }', - }, - { - filename: 'constants.js', - language: 'javascript', - path: './constants.js', - content: 'export const PI = 3.14159;', - }, - ]); + const {resolvedTheme} = useTheme(); + const theme: Theme = + resolvedTheme === 'dark' ? 'codesandbox-dark' : 'github-light'; return ( - - - {files.map(file => ( - - {file.filename} - - ))} - - {files.map(file => ( - - f.filename !== file.filename) - .map(f => ({path: f.path, content: f.content}))} - setValue={(filename, value) => - setFiles(prevFiles => - prevFiles.map(f => - f.filename === filename ? {...f, content: value} : f, - ), - ) - } - /> - - ))} - + className="rounded-xl my-8 overflow-hidden bg-background border shadow-md shadow-neutral-50 dark:shadow-neutral-900 md:w-[700px] w-full h-[400px]"> + ); }; -interface RenderEditorProps extends EditorProps { - setValue: (filename: string, value: string) => void; -} - -const RenderEditor = ({ - setValue, - filename, - value, - language, - externalContext, -}: RenderEditorProps) => { - const {resolvedTheme} = useTheme(); - const theme: Theme = - resolvedTheme === 'dark' ? 'codesandbox-dark' : 'github-light'; - - return ( - { - if (filename && newValue) { - setValue(filename, newValue); - } - }} - value={value} - language={language} - className="w-full z-10" - loading="" - externalContext={externalContext} - /> - ); -}; - export default EditorDemo; diff --git a/docs/components/features.tsx b/docs/components/features.tsx index d705cec7..3f027a7f 100644 --- a/docs/components/features.tsx +++ b/docs/components/features.tsx @@ -4,11 +4,7 @@ import { CardHeader, CardTitle, } from '@/components/ui/card'; -import { - DoubleArrowRightIcon, - LightningBoltIcon, - ShadowIcon, -} from '@radix-ui/react-icons'; +import {FileIcon, LightningBoltIcon, ShadowIcon} from '@radix-ui/react-icons'; const FEATURES = [ { @@ -16,17 +12,18 @@ const FEATURES = [ title: 'Framework Specific Completions', description: 'Get completions based on the framework you are using.', }, + { + icon: FileIcon, + title: 'Multi-file Completions', + description: + "You can provide other files' code or content and get completions that are relevant to that context.", + }, { icon: ShadowIcon, title: '20+ Popular Themes', description: 'Choose from a wide range of themes to customize your editor easily.', }, - { - icon: DoubleArrowRightIcon, - title: 'Fast completions', - description: 'Get completions in fast and efficient way.', - }, ]; const Features = () => { diff --git a/src/classes/copilot.ts b/src/classes/copilot.ts index 55560751..676915c6 100644 --- a/src/classes/copilot.ts +++ b/src/classes/copilot.ts @@ -19,12 +19,12 @@ import Config from './config'; * * @param {string} apiKey - The Groq API key. * @param {CopilotOptions} [options] - Optional parameters to configure the completion model, - * such as the model ID. Defaults to `llama3-70b-8192` if not specified. + * such as the model ID. Defaults to `llama` if not specified. * * @example * ```typescript * const copilot = new Copilot(process.env.GROQ_API_KEY, { - * model: 'llama3-70b-8192', + * model: 'llama', * }); * ``` */ diff --git a/src/constants/common.ts b/src/constants/common.ts index f39b4eff..8390a062 100644 --- a/src/constants/common.ts +++ b/src/constants/common.ts @@ -16,7 +16,7 @@ export const EDITOR_DEFAULT_OPTIONS: EditorOptionsType = { automaticLayout: true, }; -export const EDITOR_BUILT_IN_THEMES: Array = [ +export const EDITOR_BUILT_IN_THEMES: EditorBuiltInTheme[] = [ 'light', 'vs-dark', ]; diff --git a/src/constants/contextual-filter.ts b/src/constants/contextual-filter.ts index a6917bbc..b9bcfbbb 100644 --- a/src/constants/contextual-filter.ts +++ b/src/constants/contextual-filter.ts @@ -191,7 +191,7 @@ const FILTER_WEIGHTS: number[] = [ const FILTER_INTERCEPT: number = -0.3043572714994554; // 15% acceptance threshold -const CONTEXTUAL_FILTER_ACCEPT_THRESHOLD: number = 0.13; +const CONTEXTUAL_FILTER_ACCEPT_THRESHOLD: number = 0.15; export { FILTER_LANGUAGE_MAP, diff --git a/src/helpers/copilot.ts b/src/helpers/copilot.ts index d8ef88c9..88a4a3ec 100644 --- a/src/helpers/copilot.ts +++ b/src/helpers/copilot.ts @@ -1,59 +1,71 @@ import {CompletionMetadata, CompletionMode} from '../types/completion'; -const CURSOR_PLACEHOLDER = '<|cursor|>'; +const CURSOR_PLACEHOLDER = '<>'; + +const getProperLanguageName = (language?: string): string | undefined => { + return language === 'javascript' ? 'latest JavaScript' : language; +}; const getDescriptionForMode = (mode: CompletionMode): string => { switch (mode) { case 'fill-in-the-middle': - return 'filling in the middle of code'; - case 'continuation': - return 'continuing the code'; - default: - return 'unknown mode'; + return 'filling in the middle of the code'; + case 'completion': + return 'completing the code'; } }; export const generateSystemPrompt = (metadata: CompletionMetadata): string => { + const language = getProperLanguageName(metadata.language); + const description = getDescriptionForMode( + metadata.editorState.completionMode, + ); + const langText = language || ''; + return `You are an expert ${langText} code completion assistant known for exceptional skill in ${description}.`; +}; + +export const generateUserPrompt = (metadata: CompletionMetadata): string => { const { - language = 'the language', filename, framework, editorState, + codeBeforeCursor, + codeAfterCursor, + externalContext, } = metadata; - let prompt = `As an expert ${language} code completion assistant known for high accuracy in ${getDescriptionForMode(editorState.completionMode)}, could you assist with the code at the cursor location marked '${CURSOR_PLACEHOLDER}'? This code is part of ${filename ? `the ${filename} file` : 'a larger project'}. Please `; + const language = getProperLanguageName(metadata.language); + const modeDescription = getDescriptionForMode(editorState.completionMode); + const fileNameText = filename + ? `the file named ${filename}` + : 'a larger project'; + + const frameworkText = framework + ? ` The code utilizes the ${framework} framework in ${language}.` + : ` The code is implemented in ${language}.`; + + let prompt = `You will be presented with a code snippet where the cursor location is marked with '${CURSOR_PLACEHOLDER}'. Your task is to assist with ${modeDescription}. This code is part of ${fileNameText}. Please `; switch (editorState.completionMode) { case 'fill-in-the-middle': - prompt += `generate a completion to fill the middle of the code surrounding '${CURSOR_PLACEHOLDER}'. Ensure the completion precisely replaces '${CURSOR_PLACEHOLDER}', maintaining consistency, semantic accuracy, and relevance to the context.`; + prompt += `generate a completion to fill the middle of the code around '${CURSOR_PLACEHOLDER}'. Ensure the completion replaces '${CURSOR_PLACEHOLDER}' precisely, maintaining consistency, semantic accuracy, and relevance to the context. The completion must start exactly from the cursor position without any preceding or following characters, and it should not introduce any syntactical or semantic errors to the existing code.`; break; - case 'continuation': - prompt += `provide a continuation from '${CURSOR_PLACEHOLDER}'. The completion should fluidly extend the existing code, precisely replacing '${CURSOR_PLACEHOLDER}' while adhering to ${language} standards and ensuring semantic correctness and contextual appropriateness.`; + case 'completion': + prompt += `provide the necessary completion for '${CURSOR_PLACEHOLDER}' while ensuring consistency, semantic accuracy, and relevance to the context. The completion must start exactly from the cursor position without any preceding or following characters, and it should not introduce any syntactical or semantic errors to the existing code.`; break; } - prompt += ` Output only the necessary completion code, without additional explanations or content.`; + prompt += ` Output only the necessary completion code, without additional explanations or content.${frameworkText}`; - if (framework) { - prompt += ` The code utilizes the ${framework} framework in ${language}.`; - } else { - prompt += ` The code is implemented in ${language}.`; - } - - return prompt.endsWith('.') ? prompt : prompt + '.'; -}; + let codeForCompletion = `${codeBeforeCursor}${CURSOR_PLACEHOLDER}${codeAfterCursor}\n\n`; -export const generateUserPrompt = (metadata: CompletionMetadata): string => { - const {codeBeforeCursor, codeAfterCursor, externalContext} = metadata; - - let prompt = `${codeBeforeCursor}${CURSOR_PLACEHOLDER}${codeAfterCursor}\n\n`; - - // Append external context information if available if (externalContext && externalContext.length > 0) { - prompt += externalContext + codeForCompletion += externalContext .map(context => `// Path: ${context.path}\n${context.content}\n`) .join('\n'); } - return prompt; + prompt += `\n\n\n${codeForCompletion}\n`; + + return prompt.endsWith('.') ? prompt : `${prompt}.`; }; diff --git a/src/hooks/use-typing-debounce-fn.ts b/src/hooks/use-typing-debounce-fn.ts index b5ee45ad..25cab7a8 100644 --- a/src/hooks/use-typing-debounce-fn.ts +++ b/src/hooks/use-typing-debounce-fn.ts @@ -11,36 +11,32 @@ const useTypingDebounceFn = Promise>( func: T, delay: number = 1000, ): ((...funcArgs: Parameters) => Promise>) => { - const [timer, setTimer] = React.useState | null>(null); + const timerRef = React.useRef | null>(null); const debouncedFunc = React.useCallback( (...args: Parameters): Promise> => { - if (timer) { - clearTimeout(timer); + if (timerRef.current) { + clearTimeout(timerRef.current); } return new Promise>((resolve, reject) => { - const newTimer = setTimeout(() => { + timerRef.current = setTimeout(() => { func(...args) .then(resolve) .catch(reject); }, delay); - - setTimer(newTimer); }); }, - [func, delay, timer], + [func, delay], ); React.useEffect(() => { return () => { - if (timer) { - clearTimeout(timer); + if (timerRef.current) { + clearTimeout(timerRef.current); } }; - }, [timer]); + }, []); return debouncedFunc; }; diff --git a/src/types/completion.ts b/src/types/completion.ts index 673b9767..e7f92508 100644 --- a/src/types/completion.ts +++ b/src/types/completion.ts @@ -20,7 +20,7 @@ export interface CompletionRequestParams { completionMetadata: CompletionMetadata; } -export type CompletionMode = 'fill-in-the-middle' | 'continuation'; +export type CompletionMode = 'fill-in-the-middle' | 'completion'; export interface CompletionMetadata { language: string | undefined; diff --git a/src/types/editor-props.ts b/src/types/editor-props.ts index 246a711a..a218af60 100644 --- a/src/types/editor-props.ts +++ b/src/types/editor-props.ts @@ -44,7 +44,7 @@ export type FrameworkType = | 'ant design'; export type CompletionSpeedType = 'little-faster' | 'normal'; -export type ExternalContextType = Array<{ +export type ExternalContextType = { /** * The relative path from the current editing code in the editor to an external file. * @@ -59,7 +59,7 @@ export type ExternalContextType = Array<{ * The content of the external file as a string. */ content: string; -}>; +}[]; /** * Themes available for the Rich Monaco Editor. diff --git a/src/utils/completion/syntax-parser.ts b/src/utils/completion/syntax-parser.ts index 8304a0c4..367507e1 100644 --- a/src/utils/completion/syntax-parser.ts +++ b/src/utils/completion/syntax-parser.ts @@ -79,23 +79,27 @@ export const isCursorAtStartWithCodeAround = ( /** * Determines the most suitable completion mode based on the cursor position and surrounding code context. * - * @returns The completion mode ('fill-in-the-middle' | 'continuation'). + * @returns The completion mode ('fill-in-the-middle' | 'completion'). */ export const determineCompletionMode = ( position: EditorPositionType, model: EditorModelType, ): CompletionMode => { - const {codeAfterCursor} = getCodeBeforeAndAfterCursor(position, model); - const significantCharacterCount = 3; + const {lineNumber, column} = position; - // Calculate the number of non-whitespace characters in the code after the cursor - const nonWhitespaceCount = [...codeAfterCursor].reduce((count, char) => { - return char.trim() !== '' && count < significantCharacterCount - ? count + 1 - : count; - }, 0); + const currentLine = model.getLineContent(lineNumber); - return nonWhitespaceCount >= significantCharacterCount - ? 'fill-in-the-middle' - : 'continuation'; + const codeBeforeCursorInLine = currentLine.slice(0, column - 1).trim(); + const codeAfterCursorInLine = currentLine.slice(column - 1).trim(); + + // Determine if there is content before or after the cursor in the current line + const hasCodeBeforeCursor = codeBeforeCursorInLine.length > 0; + const hasCodeAfterCursor = codeAfterCursorInLine.length > 0; + + // Determine fill-in-the-middle based on code presence + if (hasCodeBeforeCursor && hasCodeAfterCursor) { + return 'fill-in-the-middle'; + } + + return 'completion'; };