Path: blob/main/extensions/copilot/src/extension/prompts/node/inline/inlineChatNotebookFixPrompt.tsx
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { BasePromptElementProps, PromptElement, PromptSizing, SystemMessage, UserMessage } from '@vscode/prompt-tsx';6import type * as vscode from 'vscode';7import { TextDocumentSnapshot } from '../../../../platform/editing/common/textDocumentSnapshot';8import { IIgnoreService } from '../../../../platform/ignore/common/ignoreService';9import { ILanguageDiagnosticsService, rangeSpanningDiagnostics } from '../../../../platform/languages/common/languageDiagnosticsService';10import { IParserService } from '../../../../platform/parser/node/parserService';11import { ITabsAndEditorsService } from '../../../../platform/tabs/common/tabsAndEditorsService';12import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';13import { ILanguage } from '../../../../util/common/languages';14import { isJupyterNotebookUri, isNotebookCellOrNotebookChatInput } from '../../../../util/common/notebooks';15import { illegalArgument } from '../../../../util/vs/base/common/errors';16import { Schemas } from '../../../../util/vs/base/common/network';17import { StringEdit } from '../../../../util/vs/editor/common/core/edits/stringEdit';18import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';19import { Range, Uri } from '../../../../vscodeTypes';20import { findDiagnosticForSelectionAndPrompt, findFixRangeOfInterest, generateFixContext } from '../../../context/node/resolvers/fixSelection';21import { generateNotebookCellContext } from '../../../context/node/resolvers/inlineChatSelection';22import { InlineFixProps } from '../../../context/node/resolvers/inlineFixIntentInvocation';23import { getStructure } from '../../../context/node/resolvers/selectionContextHelpers';24import { CodeContextRegion } from '../../../inlineChat/node/codeContextRegion';25import { IDocumentContext } from '../../../prompt/node/documentContext';26import { ReplyInterpreterMetaData } from '../../../prompt/node/intents';27import { CompositeElement } from '../base/common';28import { InstructionMessage } from '../base/instructionMessage';29import { IPromptEndpoint } from '../base/promptRenderer';30import { LegacySafetyRules } from '../base/safetyRules';31import { Tag } from '../base/tag';32import { PatchEditExamplePatch, PatchEditInputCodeBlock, PatchEditRules } from '../codeMapper/patchEditGeneration';33import { JupyterNotebookRules } from '../notebook/commonPrompts';34import { ChatToolReferences, ChatVariables, UserQuery } from '../panel/chatVariables';35import { HistoryWithInstructions } from '../panel/conversationHistory';36import { CustomInstructions } from '../panel/customInstructions';37import { CodeBlock } from '../panel/safeElements';38import { PatchEditFixReplyInterpreter } from './inlineChatFix3Prompt';39import { NotebookPromptPriority, promptPriorities } from './inlineChatNotebookCommon';40import { InlineChatCellSelectionProps, InlineChatCustomNotebookCellsContextRenderer, InlineChatCustomNotebookInfoRenderer, InlineChatNotebookSelectionCommonProps, InlineChatNotebookVariables, NotebookCellList } from './inlineChatNotebookCommonPromptElements';41import { ProjectedDocument } from './summarizedDocument/summarizeDocument';42import { summarizeDocumentSync } from './summarizedDocument/summarizeDocumentHelpers';4344const FIX_SELECTION_LENGTH_THRESHOLD = 15;45interface InlineChatNotebookFixPromptState {46isIgnored: boolean;47priorities: NotebookPromptPriority;48}495051export class InlineFixNotebookPrompt extends PromptElement<InlineFixProps, InlineChatNotebookFixPromptState> {5253constructor(54props: InlineFixProps,55@IIgnoreService private readonly _ignoreService: IIgnoreService,56@IInstantiationService private readonly _instantiationService: IInstantiationService,57@ILanguageDiagnosticsService private readonly _languageDiagnosticsService: ILanguageDiagnosticsService,58@IParserService private readonly _parserService: IParserService,59@ITabsAndEditorsService private readonly _tabsAndEditorsService: ITabsAndEditorsService,60@IWorkspaceService private readonly _workspaceService: IWorkspaceService,61@IPromptEndpoint private readonly _promptEndpoint: IPromptEndpoint62) {63super(props);64}6566override async prepare(sizing: PromptSizing): Promise<InlineChatNotebookFixPromptState> {67const { documentContext: context } = this.props;68const isIgnored = await this._ignoreService.isCopilotIgnored(context.document.uri);6970return {71isIgnored,72priorities: promptPriorities73};74}7576async render(state: InlineChatNotebookFixPromptState, sizing: PromptSizing) {77const documentContext = this.props.documentContext;7879if (!isNotebookCellOrNotebookChatInput(documentContext.document.uri)) {80throw illegalArgument('InlineFixNotebookPrompt should not be used with a non-notebook!');81}8283if (state.isIgnored) {84return <ignoredFiles value={[documentContext.document.uri]} />;85}8687const { query, history, chatVariables } = this.props.promptContext;88const selection = documentContext.selection;8990// find the diagnostics of interest and the selection of interest surrounding the diagnostics91const diagnostics = findDiagnosticForSelectionAndPrompt(this._languageDiagnosticsService, documentContext.document.uri, documentContext.selection, query);92const range = diagnostics.length > 0 ? rangeSpanningDiagnostics(diagnostics) : documentContext.selection;9394const treeSitterAST = this._parserService.getTreeSitterAST(documentContext.document);95const rangeOfInterest = treeSitterAST ? await findFixRangeOfInterest(treeSitterAST, range, FIX_SELECTION_LENGTH_THRESHOLD) : range;9697const fixContext = generateFixContext(this._promptEndpoint, documentContext, range, rangeOfInterest);98const inputDocCharLimit = (sizing.endpoint.modelMaxPromptTokens / 3) * 4; // consume one 3rd of the model window, estimating roughly 4 chars per token;99let projectedDocument: ProjectedDocument;100let isSummarized = false;101if (documentContext.document.getText().length > inputDocCharLimit) {102// only compute the summarized document if needed103const structure = await getStructure(this._parserService, documentContext.document, documentContext.fileIndentInfo);104projectedDocument = summarizeDocumentSync(inputDocCharLimit, documentContext.document, documentContext.wholeRange, structure, { tryPreserveTypeChecking: true });105isSummarized = true;106} else {107projectedDocument = new ProjectedDocument(documentContext.document.getText(), StringEdit.empty, documentContext.document.languageId);108}109110const adjustedSelection = projectedDocument.projectRange(selection);111const selectedLinesContent = documentContext.document.getText(new Range(selection.start.line, 0, selection.end.line + 1, 0)).trimEnd();112113const contextInfo = generateNotebookCellContext(this._tabsAndEditorsService, this._workspaceService, documentContext, fixContext.contextInfo, fixContext.tracker);114const replyInterpreter = this._instantiationService.createInstance(PatchEditFixReplyInterpreter, projectedDocument, documentContext.document.uri, adjustedSelection);115116// const replyInterpreter = this._instantiationService.createInstance(FixNotebookReplyInterpreter, range, contextInfo, documentContext);117const exampleUri = Uri.file('/someFolder/myFile.ts');118const priorities = state.priorities;119return (120<>121<meta value={new ReplyInterpreterMetaData(replyInterpreter)} />122<SystemMessage priority={priorities.core}>123You are an AI programming assistant.<br />124When asked for your name, you must respond with "GitHub Copilot".<br />125You are a world class expert in programming, and especially good at {documentContext.language.languageId}.<br />126Source code is always contained in ``` blocks.<br />127</SystemMessage>128<HistoryWithInstructions inline={true} passPriority historyPriority={priorities.history ?? 700} history={history}>129<InstructionMessage priority={priorities.core}>130The user needs help to write some new code.<br />131<JupyterNotebookRules />132When dealing with Jupyter Notebook, do not generate CELL INDEX in the code blocks in your answer, it is only used to help you understand the context.<br />133If you suggest to run a terminal command, use a code block that starts with ```bash.<br />134When fixing "ModuleNotFoundError" or "Import could not be resolved" errors, always use magic command "%pip install" to add the missing packages. The imports MUST be inserted at the top of the code block and it should not replace existing code.<br />135You should not import the same module twice.<br />136<PatchEditRules />137<LegacySafetyRules />138<Tag name='example' priority={100}>139<Tag name='user'>140I have the following code open in the editor.<br />141<PatchEditInputCodeBlock142uri={exampleUri}143languageId='csharp'144code={['// This is my class', 'class C { }', '', 'new C().Field = 9;']}145/>146</Tag>147<Tag name='assistant'>148The problem is that the class 'C' does not have a field or property named 'Field'. To fix this, you need to add a 'Field' property to the 'C' class.<br />149<br />150<PatchEditExamplePatch151changes={152[153{154uri: exampleUri,155find: ['// This is my class', 'class C { }'],156replace: ['// This is my class', 'class C {', 'public int Field { get; set; }', '}']157},158{159uri: exampleUri,160find: ['new C().Field = 9;'],161replace: ['// set the field to 9', 'new C().Field = 9;']162}163]164}165/>166</Tag>167</Tag>168</InstructionMessage>169</HistoryWithInstructions>170<UserMessage priority={priorities.context}>171<CustomInstructions languageId={documentContext.language.languageId} chatVariables={chatVariables} />172</UserMessage>173<ChatToolReferences priority={priorities.context} promptContext={this.props.promptContext} flexGrow={1} embeddedInsideUserMessage={false} />174<ChatVariables priority={priorities.context} chatVariables={chatVariables} embeddedInsideUserMessage={false} />175<InlineChatFixNotebookSelectionRenderer176priority={priorities.core}177documentContext={documentContext}178aboveCells={contextInfo.aboveCells}179belowCells={contextInfo.belowCells}180document={documentContext.document}181projectedDocument={projectedDocument}182language={documentContext.language}183diagnostics={diagnostics}184selection={documentContext.selection}185adjustedSelection={adjustedSelection}186isSummarized={isSummarized}187selectedLinesContent={selectedLinesContent}188/>189<InlineChatNotebookVariables notebookURI={this.props.documentContext.document.uri} priority={priorities.runtimeCore} priorities={priorities} query={query} />190<UserMessage priority={priorities.core}>191{/* <Diagnostics documentContext={documentContext} diagnostics={diagnostics} /> */}192{/* Describe in a single sentence how you would solve the problem. After that sentence, add an empty line. Then add a code block with the fix. */}193Please find a fix for my code so that the result is without any errors.<br />194<UserQuery chatVariables={chatVariables} query={query} /><br />195</UserMessage>196</>197);198}199}200201interface InlineChatNotebookSelectionRendererProps extends InlineChatNotebookSelectionCommonProps {202aboveCells?: CodeContextRegion[];203belowCells?: CodeContextRegion[];204205readonly document: TextDocumentSnapshot;206readonly projectedDocument: ProjectedDocument;207readonly language: ILanguage;208readonly diagnostics: vscode.Diagnostic[];209readonly selection: vscode.Selection;210readonly adjustedSelection: Range;211readonly isSummarized: boolean;212readonly selectedLinesContent: string;213}214215class InlineChatFixNotebookSelectionRenderer extends PromptElement<InlineChatNotebookSelectionRendererProps> {216217render(state: void, sizing: PromptSizing) {218if (this.props.documentContext.document.uri.scheme !== Schemas.vscodeNotebookCell) {219throw illegalArgument('InlineChatNotebookSelectionRenderer should be used only with a notebook!');220}221222const jupyterNotebook = isJupyterNotebookUri(this.props.documentContext.document.uri);223const { projectedDocument, aboveCells, belowCells } = this.props;224const aboveCellsInfo = aboveCells || [];225const belowCellsInfo = belowCells || [];226const lang = this.props.documentContext.language;227228return (229<>230{231jupyterNotebook232? <>233<InlineChatFixNotebookCellsContextRenderer documentContext={this.props.documentContext} aboveCells={aboveCellsInfo} belowCells={belowCellsInfo} />234</>235: <>236<InlineChatCustomNotebookInfoRenderer documentContext={this.props.documentContext} />237<InlineChatCustomNotebookCellsContextRenderer documentContext={this.props.documentContext} aboveCells={aboveCellsInfo} belowCells={belowCellsInfo} />238</>239}240<NotebookCellSelection cellIndex={aboveCellsInfo.length} document={this.props.document} projectedDocument={projectedDocument} language={lang} diagnostics={this.props.diagnostics} selection={this.props.selection} adjustedSelection={this.props.adjustedSelection} isSummarized={this.props.isSummarized} selectedLinesContent={this.props.selectedLinesContent} />241</>242);243}244}245246class NotebookCellSelection extends PromptElement<InlineChatCellSelectionProps> {247override render() {248const { cellIndex, document, projectedDocument, diagnostics, language, selection, adjustedSelection, isSummarized, selectedLinesContent } = this.props;249const notebookType = isNotebookCellOrNotebookChatInput(document.uri) ? 'Jupyter' : 'custom';250const isMarkdown = language.languageId === 'markdown';251252return <>253<UserMessage>254Now I create a new cell in this {notebookType} Notebook document at index {this.props.cellIndex}.<br />255{isMarkdown && <>This is a markdown cell. Markdown cell is used to describe and document your workflow.<br /></>}256<NotebookCellRenderer cellIndex={cellIndex} document={document} projectedDocument={projectedDocument} diagnostics={diagnostics} language={language} selection={selection} adjustedSelection={adjustedSelection} isSummarized={isSummarized} selectedLinesContent={selectedLinesContent} />257</UserMessage>258</>;259}260}261262class NotebookCellRenderer extends PromptElement<InlineChatCellSelectionProps> {263constructor(264props: InlineChatCellSelectionProps265) {266super(props);267}268269override render() {270const { document, projectedDocument, diagnostics, language, selection, adjustedSelection, isSummarized, selectedLinesContent } = this.props;271const isMarkdown = language.languageId === 'markdown';272273return <>274<CompositeElement>275{276projectedDocument.text.length > 0 ?277<>278{279isMarkdown ?280<>I have the following markdown content in this cell, starting from line 1 to line {projectedDocument.lineCount}.<br /></> :281<>I have the following code in this cell, starting from line 1 to line {projectedDocument.lineCount}.<br /></>282}283284<PatchEditInputCodeBlock uri={document.uri} languageId={language.languageId} code={projectedDocument.text} shouldTrim={false} isSummarized={isSummarized} /><br />285</> :286<>287I am in an empty file:288<PatchEditInputCodeBlock uri={document.uri} languageId={language.languageId} code={projectedDocument.text} shouldTrim={false} isSummarized={isSummarized} /><br />289</>290}291</CompositeElement >292<CompositeElement>293{294selection.isEmpty ?295<>296I have the selection at line {adjustedSelection.start.line + 1}, column {adjustedSelection.start.character + 1}<br />297</> :298<>299I have currently selected from line {adjustedSelection.start.line + 1}, column {adjustedSelection.start.character + 1} to line {adjustedSelection.end.line + 1} column {adjustedSelection.end.character + 1}.<br />300</>301}302</CompositeElement >303<CompositeElement>304{305selectedLinesContent.length && !diagnostics.some(d => d.range.contains(selection)) &&306<>307The content of the lines at the selection is308<CodeBlock uri={document.uri} languageId={language.languageId} code={selectedLinesContent} shouldTrim={false} /><br />309</>310}311</CompositeElement >312</>;313}314}315316interface InlineChatFixNotebookCellsContextRendererProps extends BasePromptElementProps {317documentContext: IDocumentContext;318aboveCells: CodeContextRegion[];319belowCells: CodeContextRegion[];320}321322/**323* Notebook cell context renderer. Used by Fix intents.324* It's using following example for llm response:325* ---FILEPATH Untitled-1<br />326---FIND<br />327---REPLACE<br />328```python<br />329df.plot(x='Name', y='Age', kind='bar')<br />330```<br />331---COMPLETE<br />332* However, we don't use this in Generate and Edit intents yet.333*/334class InlineChatFixNotebookCellsContextRenderer extends PromptElement<InlineChatFixNotebookCellsContextRendererProps> {335render(state: void, sizing: PromptSizing) {336if (!isNotebookCellOrNotebookChatInput(this.props.documentContext.document.uri)) {337throw illegalArgument('InlineChatNotebookSelectionRenderer should be used only with a notebook!');338}339340const { aboveCells: aboveCellsInfo, belowCells: belowCellsInfo } = this.props;341const lang = this.props.documentContext.language;342343return (344<>345{346(aboveCellsInfo.length > 0 || belowCellsInfo.length > 0) &&347<UserMessage>348I am working on a Jupyter notebook.<br />349This Jupyter Notebook already contains multiple cells.<br />350The content of cells are listed below, each cell starts with CELL INDEX and a code block started with ```{lang.languageId}<br />351Each cell is a block of code that can be executed independently.<br />352Since it is Jupyter Notebook, if a module is already imported in a cell, it can be used in other cells as well.<br />353For the same reason, if a variable is defined in a cell, it can be used in other cells as well.<br />354We should not repeat the same import or variable definition in multiple cells, unless we want to overwrite the previous definition.<br />355Do not generate CELL INDEX in your answer, it is only used to help you understand the context.<br />356<br />357<>Below you will find a set of examples of what you should respond with. Please follow the exmaples on how to avoid repeating code.<br />358## Examples starts here<br />359Here are the cells in this Jupyter Notebook:<br />360`CELL INDEX: 0<br />361```python<br />362import pandas as pd<br />363<br />364# create a dataframe with sample data<br />365df = pd.DataFrame({'Name': ['Alice', 'Bob', 'Charlie'], 'Age': [25, 30, 35], 'Gender': ['F', 'M', 'M']})<br />366print(df)<br />367```<br />368---------------------------------<br />369USER:<br />370Now I create a new cell in this Jupyter Notebook document at index 1.<br />371I have the following code open in this cell, starting from line 1 to line 1.<br />372```python<br />373```<br />374---------------------------------<br />375USER:<br />376plot the data frame<br />377<br />378---------------------------------<br />379Assistant Answer<br />380---------------------------------<br />381To plot the dataframe, we can use the `plot()` method of pandas dataframe.<br />382<br />383---FILEPATH Untitled-1<br />384---FIND<br />385---REPLACE<br />386```python<br />387df.plot(x='Name', y='Age', kind='bar')<br />388```<br />389---COMPLETE<br />390## Example ends here<br />391</>392393{aboveCellsInfo.length > 0 && <NotebookCellList cells={aboveCellsInfo} title={'Here are the cells in this Jupyter Notebook:\n'} />}394{belowCellsInfo.length > 0 && <NotebookCellList cells={belowCellsInfo} cellIndexDelta={aboveCellsInfo.length + 1} title={'Here are the cells below the current cell that I am editing in this Jupyter Notebook:\n'} />}395</UserMessage>396}397</>398);399}400}401402