From bad29c6bdd01f33709a6e8439d16d751d12f00c7 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 21 Nov 2024 09:05:26 +0000 Subject: [PATCH 1/4] Adopt chat-extension-utils --- package-lock.json | 17 +- package.json | 13 +- src/chat/chatHelpers.ts | 70 ------- src/chat/chatParticipant.ts | 179 ++++++----------- src/chat/mermaidPrompt.ts | 108 ++++++++++ src/chat/mermaidPrompt.tsx | 382 ------------------------------------ src/chat/toolMetadata.ts | 22 --- 7 files changed, 185 insertions(+), 606 deletions(-) create mode 100644 src/chat/mermaidPrompt.ts delete mode 100644 src/chat/mermaidPrompt.tsx delete mode 100644 src/chat/toolMetadata.ts diff --git a/package-lock.json b/package-lock.json index 845132f..06433a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "copilot-mermaid-diagram", "version": "0.0.1", "dependencies": { + "@vscode/chat-extension-utils": "^0.0.0-alpha.1", "@vscode/codicons": "^0.0.36", "groq-sdk": "^0.7.0", "mermaid": "^11.3.0" @@ -743,6 +744,15 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@vscode/chat-extension-utils": { + "version": "0.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@vscode/chat-extension-utils/-/chat-extension-utils-0.0.0-alpha.1.tgz", + "integrity": "sha512-49eYur98d1iukPEQqMYQL4lJgaKnM0QFQB4/BFIFvuuKM+Kug2KNE/TSIJJQXrp5CrP0kDOmIIXvTnNRPtO2vg==", + "license": "MIT", + "dependencies": { + "@vscode/prompt-tsx": "^0.3.0-alpha.13" + } + }, "node_modules/@vscode/codicons": { "version": "0.0.36", "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.36.tgz", @@ -750,10 +760,9 @@ "license": "CC-BY-4.0" }, "node_modules/@vscode/prompt-tsx": { - "version": "0.3.0-alpha.12", - "resolved": "https://registry.npmjs.org/@vscode/prompt-tsx/-/prompt-tsx-0.3.0-alpha.12.tgz", - "integrity": "sha512-2ANm569UBXIzjPbaDFjzRkucelhsnlnmYIPdDo+USeFq2Do0Q70gKiiRWYrQf5rPqCxrChDvgU14nsdJLUSaOQ==", - "dev": true, + "version": "0.3.0-alpha.13", + "resolved": "https://registry.npmjs.org/@vscode/prompt-tsx/-/prompt-tsx-0.3.0-alpha.13.tgz", + "integrity": "sha512-0m9Hy2VqfGcFgXmY7xFV1nYngoq2zm2Wy/3YdesmR6bOwFrJed9xW87y43Ax7UFVHwtjZkpjn4M9HbFvxvzdWA==", "license": "MIT" }, "node_modules/@vscode/test-cli": { diff --git a/package.json b/package.json index 2e4773f..651f131 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "displayName": "vscode-mermAId", "description": "Exploration into Copilot Chat-powered Diagram Generation", "repository": { - "type": "git", - "url": "https://github.com/microsoft/vscode-mermAId.git" - }, + "type": "git", + "url": "https://github.com/microsoft/vscode-mermAId.git" + }, "version": "0.0.1", "icon": "assets/logo.png", "engines": { @@ -91,7 +91,8 @@ "name": "mermAId_get_symbol_definition", "tags": [ "code", - "symbols" + "symbols", + "mermaid" ], "displayName": "Symbol Definitions from File", "modelDescription": "Given a file path string and a list of symbols, this model returns the definitions of the specified symbols. For example, if the file 'x.py' is provided and the symbol 'abc' is requested, the model will find 'abc' in 'x.py' and return its definition from the file where it is actually defined, such as 'y.py'.", @@ -124,7 +125,8 @@ "name": "mermAId_gather_symbols", "tags": [ "code", - "symbols" + "symbols", + "mermaid" ], "displayName": "Gather Workspace Symbols", "modelDescription": "This tool will accept a list of symbols and attempt to gather information about those symbols from the entire workspace. It can provide the location, parent symbol, and the the symbol's content.", @@ -290,6 +292,7 @@ "webpack-cli": "^5.1.4" }, "dependencies": { + "@vscode/chat-extension-utils": "^0.0.0-alpha.1", "@vscode/codicons": "^0.0.36", "groq-sdk": "^0.7.0", "mermaid": "^11.3.0" diff --git a/src/chat/chatHelpers.ts b/src/chat/chatHelpers.ts index 84de4a9..c0e3810 100644 --- a/src/chat/chatHelpers.ts +++ b/src/chat/chatHelpers.ts @@ -1,77 +1,7 @@ import * as vscode from 'vscode'; -import { MermaidPrompt, MermaidProps } from './mermaidPrompt'; -import { ChatMessage, ChatRole, HTMLTracer, PromptRenderer } from '@vscode/prompt-tsx'; export interface IToolCall { tool: vscode.LanguageModelToolInformation; call: vscode.LanguageModelToolCallPart; result: Thenable; } - - -export async function getContextMessage(references: ReadonlyArray): Promise { - const contextParts = (await Promise.all(references.map(async ref => { - if (ref.value instanceof vscode.Uri) { - const fileContents = (await vscode.workspace.fs.readFile(ref.value)).toString(); - return `${ref.value.fsPath}:\n\`\`\`\n${fileContents}\n\`\`\``; - } else if (ref.value instanceof vscode.Location) { - const rangeText = (await vscode.workspace.openTextDocument(ref.value.uri)).getText(ref.value.range); - return `${ref.value.uri.fsPath}:${ref.value.range.start.line + 1}-${ref.value.range.end.line + 1}\n\`\`\`${rangeText}\`\`\``; - } else if (typeof ref.value === 'string') { - return ref.value; - } - return null; - }))).filter(part => part !== null) as string[]; - - const context = contextParts - .map(part => `\n${part}\n`) - .join('\n'); - return `The user has provided these references:\n${context}`; -} - -export async function getHistoryMessages(context: vscode.ChatContext): Promise { - const messages: vscode.LanguageModelChatMessage[] = []; - for (const message of context.history) { - if (message instanceof vscode.ChatRequestTurn) { - if (message.references.length) { - messages.push(vscode.LanguageModelChatMessage.User(await getContextMessage(message.references))); - } - messages.push(vscode.LanguageModelChatMessage.User(message.prompt)); - } else if (message instanceof vscode.ChatResponseTurn) { - const strResponse = message.response.map(part => { - if (part instanceof vscode.ChatResponseMarkdownPart) { - return part.value.value; - } else if (part instanceof vscode.ChatResponseAnchorPart) { - if (part.value instanceof vscode.Location) { - return ` ${part.value.uri.fsPath}:${part.value.range.start.line}-${part.value.range.end.line} `; - } else if (part.value instanceof vscode.Uri) { - return ` ${part.value.fsPath} `; - } - } - }).join(''); - messages.push(vscode.LanguageModelChatMessage.Assistant(strResponse)); - } - } - - return messages; -} - -export async function renderMessages(chat: vscode.LanguageModelChat, props: MermaidProps, stream: vscode.ChatResponseStream, serveTrace: boolean) { - const renderer = new PromptRenderer({ modelMaxPromptTokens: chat.maxInputTokens }, MermaidPrompt, props, { - tokenLength: async (text, _token) => { - return chat.countTokens(text); - }, - countMessageTokens: async (message: ChatMessage) => { - return chat.countTokens(message.content); - } - }); - const tracer = new HTMLTracer(); - renderer.tracer = tracer; - const result = await renderer.render(); - if (serveTrace) { - const server = await tracer.serveHTML(); - console.log('Server address:', server.address); - const serverUri = vscode.Uri.parse(server.address); - } - return result; -} \ No newline at end of file diff --git a/src/chat/chatParticipant.ts b/src/chat/chatParticipant.ts index c899cb7..cc196d4 100644 --- a/src/chat/chatParticipant.ts +++ b/src/chat/chatParticipant.ts @@ -1,12 +1,10 @@ +import { sendChatParticipantRequest } from '@vscode/chat-extension-utils'; import * as vscode from 'vscode'; -import { logMessage } from '../extension'; +import { COMMAND_OPEN_DIAGRAM_SVG } from '../commands'; import { Diagram } from '../diagram'; import { DiagramEditorPanel } from '../diagramEditorPanel'; -import { renderPrompt, toVsCodeChatMessages } from '@vscode/prompt-tsx'; -import { MermaidPrompt, ToolResultMetadata } from './mermaidPrompt'; -import { ToolCallRound } from './toolMetadata'; -import { COMMAND_OPEN_DIAGRAM_SVG, COMMAND_OPEN_MARKDOWN_FILE } from '../commands'; -import { renderMessages } from './chatHelpers'; +import { logMessage } from '../extension'; +import { makePrompt } from './mermaidPrompt'; let developmentMode = false; @@ -22,91 +20,48 @@ export function registerChatParticipant(context: vscode.ExtensionContext) { } async function chatRequestHandler(request: vscode.ChatRequest, chatContext: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) { - const models = await vscode.lm.selectChatModels({ - vendor: 'copilot', - family: 'gpt-4o' - }); - - const model = models[0]; - - const options: vscode.LanguageModelChatRequestOptions = { - justification: 'To collaborate on diagrams', - }; - - options.tools = vscode.lm.tools.map((tool): vscode.LanguageModelChatTool => { - return { - name: tool.name, - description: tool.description, - inputSchema: tool.inputSchema ?? {} - }; - }); - logMessage(`Available tools: ${options.tools.map(tool => tool.name).join(', ')}`); - - let { messages, references } = await renderMessages(model, { - context: chatContext, - request, - toolCallRounds: [], - toolCallResults: {}, - command: request.command, - validationError: undefined - }, stream, developmentMode); - - references.forEach(ref => { - if (ref.anchor instanceof vscode.Uri || ref.anchor instanceof vscode.Location) { - stream.reference(ref.anchor); - } - }); - - let retries = 0; - const accumulatedToolResults: Record = {}; - const toolCallRounds: ToolCallRound[] = []; - - const runWithFunctions = async (): Promise => { - if (token.isCancellationRequested) { + if (request.command === 'help') { + handleHelpCommand(stream); + return; + } else if (request.command === 'iterate') { + const diagram = DiagramEditorPanel.currentPanel?.diagram; + if (!diagram) { + stream.markdown('No diagram found in editor view. Please create a diagram first to iterate on it.'); return; } + } - if (request.command === 'help') { - stream.markdown(` -## Welcome to the Mermaid Diagram Generator! - -Mermaid is a diagramming and charting tool that extends markdown. Visit their [website](https://mermaid.js.org/) to learn more about the tool. - -This chat agent generates useful diagrams using Mermaid to help you better understand your code and communicate your ideas to others. You can chat just by typing or use a command for a more specific intent. - -### Available Commands: -- **\\uml**: Create Unified Modeling Language graph, or Class Diagram. -- **\\sequence**: Create a sequence Diagram. -- **\\iterate**: To be called when you already have a diagram up to refine, add, and change the existing diagram. - -Good luck and happy diagramming! - `); - return; - } - - if (request.command === 'iterate') { - const diagram = DiagramEditorPanel.currentPanel?.diagram; - if (!diagram) { - stream.markdown('No diagram found in editor view. Please create a diagram first to iterate on it.'); - return; - } - } - + let retries = 0; + let validationError = ''; + const runRequest = async () => { + const result = sendChatParticipantRequest( + request, + chatContext, + { + prompt: makePrompt(request.command, validationError), + tools: vscode.lm.tools.filter(tool => tool.tags.includes('mermaid')), + responseStreamOptions: { + stream, + references: true, + responseText: false + }, + requestJustification: 'To collaborate on diagrams', + }, + token); + let isMermaidDiagramStreamingIn = false; let mermaidDiagram = ''; - - const response = await model.sendRequest(toVsCodeChatMessages(messages), options, token); - const toolCalls: vscode.LanguageModelToolCallPart[] = []; - + let responseStr = ''; - for await (const part of response.stream) { + for await (const part of result.stream) { if (part instanceof vscode.LanguageModelTextPart) { if (!isMermaidDiagramStreamingIn && part.value.includes('```')) { // When we see a code block, assume it's a mermaid diagram stream.progress('Capturing mermaid diagram from the model...'); isMermaidDiagramStreamingIn = true; } - + + // TODO get multiple diagrams? need to handle the end? if (isMermaidDiagramStreamingIn) { // Gather the mermaid diagram so we can validate it mermaidDiagram += part.value; @@ -115,42 +70,16 @@ Good luck and happy diagramming! stream.markdown(part.value); responseStr += part.value; } - } else if (part instanceof vscode.LanguageModelToolCallPart) { - toolCalls.push(part); } - } - - if (toolCalls.length) { - toolCallRounds.push({ - response: responseStr, - toolCalls - }); - const result = await renderMessages(model, { - context: chatContext, - request, - toolCallRounds, - toolCallResults: accumulatedToolResults, - command: request.command, - validationError: undefined - }, stream, developmentMode); - messages = result.messages; - const toolResultMetadata = result.metadata.getAll(ToolResultMetadata); - if (toolResultMetadata?.length) { - toolResultMetadata.forEach(meta => accumulatedToolResults[meta.toolCallId] = meta.result); - } - - return runWithFunctions(); - } - + logMessage(mermaidDiagram); - isMermaidDiagramStreamingIn = false; - + // Validate stream.progress('Validating mermaid diagram'); const diagram = new Diagram(mermaidDiagram); const diagramResult = await DiagramEditorPanel.createOrShow(diagram); - + if (diagramResult.success) { const openMermaidDiagramCommand: vscode.Command = { command: COMMAND_OPEN_DIAGRAM_SVG, @@ -160,11 +89,10 @@ Good luck and happy diagramming! stream.button(openMermaidDiagramCommand); return; } - + // -- Handle parse error logMessage(`Not successful (on retry=${++retries})`); if (retries < 3) { - let validationError = ''; if (retries === 1 && mermaidDiagram.indexOf('classDiagram') !== -1) { stream.progress('Attempting to fix validation errors'); validationError = getValidationErrorMessage(diagramResult.error, mermaidDiagram, true); @@ -172,17 +100,7 @@ Good luck and happy diagramming! stream.progress('Attempting to fix validation errors'); validationError = getValidationErrorMessage(diagramResult.error, mermaidDiagram, false); } - // tool call results should all be cached, but we need to re-render the prompt with the error message - const result = await renderMessages(model, { - context: chatContext, - request, - toolCallRounds, - toolCallResults: accumulatedToolResults, - command: request.command, - validationError - }, stream, developmentMode); - messages = result.messages; - return runWithFunctions(); + return runRequest(); } else { if (diagramResult.error) { logMessage(diagramResult.error); @@ -190,10 +108,25 @@ Good luck and happy diagramming! stream.markdown('Failed to display your requested mermaid diagram. Check output log for details.\n\n'); return; } + }; + + await runRequest(); +} + +function handleHelpCommand(stream: vscode.ChatResponseStream) { + stream.markdown(` +## Welcome to the Mermaid Diagram Generator! + +Mermaid is a diagramming and charting tool that extends markdown. Visit their [website](https://mermaid.js.org/) to learn more about the tool. - }; // End runWithFunctions() +This chat agent generates useful diagrams using Mermaid to help you better understand your code and communicate your ideas to others. You can chat just by typing or use a command for a more specific intent. + +### Available Commands: +- **/uml**: Create Unified Modeling Language graph, or Class Diagram. +- **/sequence**: Create a sequence Diagram. +- **/iterate**: To be called when you already have a diagram up to refine, add, and change the existing diagram. - await runWithFunctions(); +Good luck and happy diagramming!`); } function getValidationErrorMessage(error: string, diagram: string, uml: boolean) { diff --git a/src/chat/mermaidPrompt.ts b/src/chat/mermaidPrompt.ts new file mode 100644 index 0000000..554241b --- /dev/null +++ b/src/chat/mermaidPrompt.ts @@ -0,0 +1,108 @@ +import * as vscode from 'vscode'; +import { DiagramEditorPanel } from '../diagramEditorPanel'; +import { logMessage } from '../extension'; +import { afterIterateCommandExampleDiagram, beforeIterateCommandExampleDiagram } from './chatExamples'; + + +export function makePrompt(command: string | undefined, validationError: string): string { + const doc = vscode.window.activeTextEditor?.document; + // full file contents are included through the prompt references, unless the user explicitly excludes them + const docRef = doc ? + `My focus is currently on the file ${doc.uri.fsPath}` : + `There is not a current file open, the root of the workspace is: ${vscode.workspace.workspaceFolders?.[0]?.uri.fsPath}`; + const currentDiagram = DiagramEditorPanel.currentPanel?.diagram; + const diagramRef = currentDiagram ? + `Refer to this if it sounds like I'm referring to an existing diagram:\n${currentDiagram.content}` : + `There isn't a diagram open that you created.`; + const clickableSyntax = 'click {ItemLabel} call linkCallback("{ItemFilePath}#L{LineNumber}")'; + const clickableSyntaxExample = `click A call linkCallback("myClass.ts#L42")`; + const requestCommand = getCommandPromptPart(command); + + return ` + +- You are a helpful chat assistant that creates diagrams using the +mermaid syntax. +- If you aren't sure which tool is relevant and feel like you are missing +context, start by searching the code base to find general information. +You can call tools repeatedly to gather as much context as needed as long +as you call the tool with different arguments each time. Don't give up +unless you are sure the request cannot be fulfilled with the tools you +have. +- If you find a relevant symbol in the code gather more information about +it with one of the symbols tools. +- Use symbol information to find the file path and line number of the +symbol so that they can be referenced in the diagram. +- The final segment of your response should always be a valid mermaid diagram +prefixed with a line containing \`\`\`mermaid and suffixed with a line +containing \`\`\`. +- If you have the location for an item in the diagram, make it clickable by +adding adding the following syntax to the end of the line: +${clickableSyntax} +where ItemLabel is the label in the diagram and ItemFilePath and LineNumber +are the location of the item, but leave off the line number if you are unsure. +For example: +${clickableSyntaxExample} +- Make sure to only use the \`/\` character as a path separator in the links. +- Do not add anything to the response past the closing \`\`\` delimiter or +we won't be able to parse the response correctly. +- The \`\`\` delimiter should only occur in the two places mentioned above. + + +${docRef} +${diagramRef} + + +${requestCommand} + +${validationError} +`; +} + +function getCommandPromptPart(commandName: string | undefined): string { + switch (commandName) { + case 'iterate': + // If diagram already exists + const diagram = DiagramEditorPanel.currentPanel?.diagram; + if (!diagram) { + logMessage('Iterate: No existing diagram.'); + return 'End this chat conversation after explaining that you cannot iterate on a diagram that does not exist.'; + } + logMessage('Iterating on existing diagram.'); + logMessage(diagram.content); + return ` +Please make changes to the currently open diagram. + +There will be following instructions on how to update the diagram. +Do not make any other edits except my directed suggestion. +It is much less likely you will need to use a tool, unless the question references the codebase. +For example, if the instructions are 'Change all int data types to doubles and change Duck to Bunny' in the following diagram: +${beforeIterateCommandExampleDiagram} +Then you should emit the following diagram: +${afterIterateCommandExampleDiagram}`; + case 'uml': + return ` +Please create UML diagram. Include all relevant classes in the file attached as context. You must use the tool mermAId_get_symbol_definition to get definitions of symbols +not defined in the current context. You should call it multiple times since you will likely need to get the definitions of multiple symbols. +Therefore for all classes you touch, explore their related classes using mermAId_get_symbol_definition to get their definitions and add them to the diagram. +All class relationships should be defined and correctly indicated using mermaid syntax. Also add the correct Cardinality / Multiplicity to associations like 1..n one to n where n is great than 1. + +Remember that all class associations/should be defined! The types of relationships that you can include, and their syntax in mermaid UML diagrams, are as follows: +Inheritance: <|-- : Represents a "is-a" relationship where a subclass inherits from a superclass. +Composition: *-- : Represents a "whole-part" relationship where the part cannot exist without the whole. +Aggregation: o-- : Represents a "whole-part" relationship where the part can exist independently of the whole. +Association: --> : Represents a general connection between two classes. +Dependency: ..> : Represents a "uses" relationship where one class depends on another. +Realization: ..|> : Represents an implementation relationship between an interface and a class. +Link; solid or dashed: -- : used when no other relationship fits.`; + case 'sequence': + return ` +Please create a mermaid sequence diagram. The diagram should include all relevant steps to describe the behaviors, actions, and steps in the user's code. +Sequence diagrams model the interactions between different parts of a system in a time-sequenced manner. There are participants which represent entities in +the system. These actors can have aliases, be group and be deactivated. +Mermaid sequence diagrams also support loops, alternative routes, parallel actions, breaks in flow, notes/comments and more. +Use all of these features to best represent the users code and add in notes and comments to provide explanation. +As always, end your message with the diagram.`; + default: + return `Pick an appropriate diagram type, for example: sequence, class, or flowchart.`; + } +} diff --git a/src/chat/mermaidPrompt.tsx b/src/chat/mermaidPrompt.tsx deleted file mode 100644 index 34d5357..0000000 --- a/src/chat/mermaidPrompt.tsx +++ /dev/null @@ -1,382 +0,0 @@ -import { - AssistantMessage, - BasePromptElementProps, - contentType as promptTsxContentType, - PrioritizedList, - PromptElement, - PromptElementProps, - PromptPiece, - PromptSizing, - UserMessage, - PromptMetadata, - ToolCall, - Chunk, - ToolMessage, - PromptReference, -} from '@vscode/prompt-tsx'; -import * as vscode from 'vscode'; -import { isTsxMermaidMetadata, ToolCallRound } from './toolMetadata'; -import { afterIterateCommandExampleDiagram, beforeIterateCommandExampleDiagram } from './chatExamples'; -import { logMessage } from '../extension'; -import { DiagramEditorPanel } from '../diagramEditorPanel'; -import { ToolResult } from '@vscode/prompt-tsx/dist/base/promptElements'; - -export interface MermaidProps extends BasePromptElementProps { - request: vscode.ChatRequest; - command: string | undefined; - validationError: string | undefined; - context: vscode.ChatContext; - toolCallRounds: ToolCallRound[]; - toolCallResults: Record; -} - -export class MermaidPrompt extends PromptElement { - render(state: void, sizing: PromptSizing) { - const doc = vscode.window.activeTextEditor?.document; - // full file contents are included through the prompt references, unless the user explicitly excludes them - const docRef = doc ? - `My focus is currently on the file ${doc.uri.fsPath}` : - `There is not a current file open, the root of the workspace is: ${vscode.workspace.workspaceFolders?.[0]?.uri.fsPath}`; - const currentDiagram = DiagramEditorPanel.currentPanel?.diagram; - const diagramRef = currentDiagram ? - `Refer to this if it sounds like I'm referring to an existing diagram:\n${currentDiagram.content}` : - `There isn't a diagram open that you created.`; - const clickableSyntax = 'click {ItemLabel} call linkCallback("{ItemFilePath}#L{LineNumber}")'; - const clickableSyntaxExample = `click A call linkCallback("myClass.ts#L42")`; - return ( - <> - - Instructions:
- - You are helpful chat assistant that creates diagrams using the - mermaid syntax.
- - If you aren't sure which tool is relevant and feel like you are missing - context, start by searching the code base to find general information. - You can call tools repeatedly to gather as much context as needed as long - as you call the tool with different arguments each time. Don't give up - unless you are sure the request cannot be fulfilled with the tools you - have.
- - Don't ask for confirmation to use tools, just use them.
- - If you find a relevant symbol in the code gather more information about - it with one of the symbols tools.
- - Use symbol information to find the file path and line number of the - symbol so that they can be referenced in the diagram.
- - The final segment of your response should always be a valid mermaid diagram - prefixed with a line containing \`\`\`mermaid and suffixed with a line - containing \`\`\`.
- - If you have the location for an item in the diagram, make it clickable by - adding adding the following syntax to the end of the line:
- {clickableSyntax}
- where ItemLabel is the label in the diagram and ItemFilePath and LineNumber - are the location of the item, but leave off the line number if you are unsure. - For example:
- {clickableSyntaxExample}
- - Make sure to only use the \`/\` character as a path separator in the links. -
- - Do not add anything to the response past the closing \`\`\` delimiter or - we won't be able to parse the response correctly.
- - The \`\`\` delimiter should only occur in the two places mentioned above. -
- {docRef} - {diagramRef} - - - {this.props.request.prompt} - - - {this.props.validationError} - - ); - } -} - -interface ToolCallsProps extends BasePromptElementProps { - toolCallRounds: ToolCallRound[]; - toolCallResults: Record; - toolInvocationToken: vscode.ChatParticipantToolToken | undefined; -} - -const agentSupportedContentTypes = [promptTsxContentType, 'text/plain']; -const dummyCancellationToken: vscode.CancellationToken = new vscode.CancellationTokenSource().token; - -class ToolCalls extends PromptElement { - async render(state: void, sizing: PromptSizing) { - if (!this.props.toolCallRounds.length) { - return undefined; - } - - // Note- the final prompt must end with a UserMessage - return <> - {this.props.toolCallRounds.map(round => this.renderOneToolCallRound(round))} - Above is the result of calling one or more tools. The user cannot see the results, so you should explain them to the user if referencing them in your answer. - - } - - private renderOneToolCallRound(round: ToolCallRound) { - const assistantToolCalls: ToolCall[] = round.toolCalls.map(tc => ({ type: 'function', function: { name: tc.name, arguments: JSON.stringify(tc.input) }, id: tc.callId })); - // TODO- just need to adopt prompt-tsx update in vscode-copilot - return ( - - {round.response} - {round.toolCalls.map(toolCall => - )} - ); - } -} - -interface ToolCallElementProps extends BasePromptElementProps { - toolCall: vscode.LanguageModelToolCallPart; - toolInvocationToken: vscode.ChatParticipantToolToken | undefined; - toolCallResult: vscode.LanguageModelToolResult | undefined; -} - -class ToolCallElement extends PromptElement { - async render(state: void, sizing: PromptSizing): Promise { - const tool = vscode.lm.tools.find(t => t.name === this.props.toolCall.name); - if (!tool) { - console.error(`Tool not found: ${this.props.toolCall.name}`); - return Tool not found; - } - - const tokenizationOptions: vscode.LanguageModelToolTokenizationOptions = { - tokenBudget: sizing.tokenBudget, - countTokens: async (content: string) => sizing.countTokens(content), - }; - - const toolResult = this.props.toolCallResult ?? - await vscode.lm.invokeTool(this.props.toolCall.name, { input: this.props.toolCall.input, toolInvocationToken: this.props.toolInvocationToken, tokenizationOptions }, dummyCancellationToken); - - - // Reduced priority for copilot_codebase tool call since the responses are so long and use up so many tokens. - const priority = this.props.toolCall.name === 'copilot_codebase' ? 800 : 1000; - - return( - - - - - ); - } -} - -export class ToolResultMetadata extends PromptMetadata { - constructor( - public toolCallId: string, - public result: vscode.LanguageModelToolResult, - ) { - super(); - } -} - -interface HistoryProps extends BasePromptElementProps { - priority: number; - context: vscode.ChatContext; -} - -class History extends PromptElement { - render(state: void, sizing: PromptSizing) { - return ( - - {this.props.context.history.map((message) => { - if (message instanceof vscode.ChatRequestTurn) { - return ( - <> - {} - {message.prompt} - - ); - } else if (message instanceof vscode.ChatResponseTurn) { - const metadata = message.result.metadata; - if (isTsxMermaidMetadata(metadata) && metadata.toolCallsMetadata.toolCallRounds.length > 0) { - return ; - } - - return {chatResponseToString(message)}; - } - })} - - ); - } -} - -function chatResponseToString(response: vscode.ChatResponseTurn): string { - return response.response - .map((r) => { - if (r instanceof vscode.ChatResponseMarkdownPart) { - return r.value.value; - } else if (r instanceof vscode.ChatResponseAnchorPart) { - if (r.value instanceof vscode.Uri) { - return r.value.fsPath; - } else { - return r.value.uri.fsPath; - } - } - - return ''; - })?.join(''); -} - -interface RequestCommandProps extends BasePromptElementProps { - commandName: string; -} - -class RequestCommand extends PromptElement { - render(state: void, sizing: PromptSizing) { - switch (this.props.commandName) { - case 'iterate': - // If diagram already exists - const diagram = DiagramEditorPanel.currentPanel?.diagram; - if (!diagram) { - logMessage('Iterate: No existing diagram.'); - return ( - <> - - End this chat conversation after explaining that you cannot iterate on a diagram that does not exist. - - - ) - } - logMessage('Iterating on existing diagram.'); - logMessage(diagram.content); - return ( - <> - - Please make changes to the currently open diagram. - - There will be following instructions on how to update the diagram. - Do not make any other edits except my directed suggestion. - It is much less likely you will need to use a tool, unless the question references the codebase. - For example, if the instructions are 'Change all int data types to doubles and change Duck to Bunny' in the following diagram: - ${beforeIterateCommandExampleDiagram} - Then you should emit the following diagram: - ${afterIterateCommandExampleDiagram} - - - ) - case 'uml': - return ( - <> - - Please create UML diagram. Include all relevant classes in the file attached as context. You must use the tool mermAId_get_symbol_definition to get definitions of symbols - not defined in the current context. You should call it multiple times since you will likely need to get the definitions of multiple symbols. - Therefore for all classes you touch, explore their related classes using mermAId_get_symbol_definition to get their definitions and add them to the diagram. - All class relationships should be defined and correctly indicated using mermaid syntax. Also add the correct Cardinality / Multiplicity to associations like 1..n one to n where n is great than 1. - - - Remember that all class associations/should be defined! The types of relationships that you can include, and their syntax in mermaid UML diagrams, are as follows: - Inheritance: <|-- : Represents a "is-a" relationship where a subclass inherits from a superclass. - Composition: *-- : Represents a "whole-part" relationship where the part cannot exist without the whole. - Aggregation: o-- : Represents a "whole-part" relationship where the part can exist independently of the whole. - Association: --> : Represents a general connection between two classes. - Dependency: ..> : Represents a "uses" relationship where one class depends on another. - Realization: ..|> : Represents an implementation relationship between an interface and a class. - Link; solid or dashed: -- : used when no other relationship fits. - - - ); - case 'sequence': - return ( - - Please create a mermaid sequence diagram. The diagram should include all relevant steps to describe the behaviors, actions, and steps in the user's code. - Sequence diagrams model the interactions between different parts of a system in a time-sequenced manner. There are participants which represent entities in - the system. These actors can have aliases, be group and be deactivated. - Mermaid sequence diagrams also support loops, alternative routes, parallel actions, breaks in flow, notes/comments and more. - Use all of these features to best represent the users code and add in notes and comments to provide explanation. - As always, end your message with the diagram. - - ); - default: - return ( - - Pick an appropriate diagram type, for example: sequence, class, or flowchart. - - ); - } - } -} - -interface PromptReferencesProps extends BasePromptElementProps { - references: ReadonlyArray; - excludeReferences?: boolean; -} - -class PromptReferences extends PromptElement { - render(state: void, sizing: PromptSizing): PromptPiece { - return ( - - {this.props.references.map((ref, index) => ( - - ))} - - ); - } -} - -interface PromptReferenceProps extends BasePromptElementProps { - ref: vscode.ChatPromptReference; - excludeReferences?: boolean; -} - -class PromptReferenceElement extends PromptElement { - async render(state: void, sizing: PromptSizing): Promise { - const value = this.props.ref.value; - // TODO make context a list of TextChunks so that it can be trimmed - if (value instanceof vscode.Uri) { - const fileContents = (await vscode.workspace.fs.readFile(value)).toString(); - return ( - - {!this.props.excludeReferences && } - {value.fsPath}:
- ```
- {fileContents}
- ```
-
- ); - } else if (value instanceof vscode.Location) { - const rangeText = (await vscode.workspace.openTextDocument(value.uri)).getText(value.range); - return ( - - {!this.props.excludeReferences && } - {value.uri.fsPath}:{value.range.start.line + 1}-$
- {value.range.end.line + 1}:
- ```
- {rangeText}
- ``` -
- ); - } else if (typeof value === 'string') { - return {value}; - } - } -} - -export type TagProps = PromptElementProps<{ - name: string; -}>; - -export class Tag extends PromptElement { - private static readonly _regex = /^[a-zA-Z_][\w\.\-]*$/; - - render() { - const { name } = this.props; - - if (!Tag._regex.test(name)) { - throw new Error(`Invalid tag name: ${this.props.name}`); - } - - return ( - <> - {'<' + name + '>'}
- <> - {this.props.children}
- - {''}
- - ); - } -} diff --git a/src/chat/toolMetadata.ts b/src/chat/toolMetadata.ts deleted file mode 100644 index 56544e7..0000000 --- a/src/chat/toolMetadata.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as vscode from 'vscode'; - -export interface ToolCallRound { - response: string; - toolCalls: vscode.LanguageModelToolCallPart[]; -} - -export interface MermaidChatMetadata { - toolCallsMetadata: ToolCallsMetadata; -} - -export interface ToolCallsMetadata { - toolCallRounds: ToolCallRound[]; - toolCallResults: Record; -} - -export function isTsxMermaidMetadata(obj: unknown): obj is MermaidChatMetadata { - // If you change the metadata format, you would have to make this stricter or handle old objects in old ChatRequest metadata - return !!obj && - !!(obj as MermaidChatMetadata).toolCallsMetadata && - Array.isArray((obj as MermaidChatMetadata).toolCallsMetadata.toolCallRounds); -} \ No newline at end of file From 9446bc2c1b6d78aa7a8bb79e33c65e31755c9532 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 21 Nov 2024 09:31:42 +0000 Subject: [PATCH 2/4] Tweak --- src/chat/mermaidPrompt.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/chat/mermaidPrompt.ts b/src/chat/mermaidPrompt.ts index 554241b..d40822c 100644 --- a/src/chat/mermaidPrompt.ts +++ b/src/chat/mermaidPrompt.ts @@ -19,7 +19,6 @@ export function makePrompt(command: string | undefined, validationError: string) const requestCommand = getCommandPromptPart(command); return ` - - You are a helpful chat assistant that creates diagrams using the mermaid syntax. - If you aren't sure which tool is relevant and feel like you are missing @@ -46,14 +45,13 @@ ${clickableSyntaxExample} - Do not add anything to the response past the closing \`\`\` delimiter or we won't be able to parse the response correctly. - The \`\`\` delimiter should only occur in the two places mentioned above. - + ${docRef} ${diagramRef} - + ${requestCommand} - ${validationError} `; } @@ -69,8 +67,7 @@ function getCommandPromptPart(commandName: string | undefined): string { } logMessage('Iterating on existing diagram.'); logMessage(diagram.content); - return ` -Please make changes to the currently open diagram. + return `Please make changes to the currently open diagram. There will be following instructions on how to update the diagram. Do not make any other edits except my directed suggestion. @@ -80,8 +77,7 @@ ${beforeIterateCommandExampleDiagram} Then you should emit the following diagram: ${afterIterateCommandExampleDiagram}`; case 'uml': - return ` -Please create UML diagram. Include all relevant classes in the file attached as context. You must use the tool mermAId_get_symbol_definition to get definitions of symbols + return `Please create UML diagram. Include all relevant classes in the file attached as context. You must use the tool mermAId_get_symbol_definition to get definitions of symbols not defined in the current context. You should call it multiple times since you will likely need to get the definitions of multiple symbols. Therefore for all classes you touch, explore their related classes using mermAId_get_symbol_definition to get their definitions and add them to the diagram. All class relationships should be defined and correctly indicated using mermaid syntax. Also add the correct Cardinality / Multiplicity to associations like 1..n one to n where n is great than 1. @@ -95,8 +91,7 @@ Dependency: ..> : Represents a "uses" relationship where one class depends on an Realization: ..|> : Represents an implementation relationship between an interface and a class. Link; solid or dashed: -- : used when no other relationship fits.`; case 'sequence': - return ` -Please create a mermaid sequence diagram. The diagram should include all relevant steps to describe the behaviors, actions, and steps in the user's code. + return `Please create a mermaid sequence diagram. The diagram should include all relevant steps to describe the behaviors, actions, and steps in the user's code. Sequence diagrams model the interactions between different parts of a system in a time-sequenced manner. There are participants which represent entities in the system. These actors can have aliases, be group and be deactivated. Mermaid sequence diagrams also support loops, alternative routes, parallel actions, breaks in flow, notes/comments and more. From 5e369f4968cf697b0c271779ac33a804d9afc67d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Nov 2024 11:29:47 +0000 Subject: [PATCH 3/4] Adopt tsx PromptElement --- src/chat/chatParticipant.ts | 206 +++++++++++++++++++----------------- src/chat/mermaidPrompt.ts | 103 ------------------ src/chat/mermaidPrompt.tsx | 148 ++++++++++++++++++++++++++ 3 files changed, 256 insertions(+), 201 deletions(-) delete mode 100644 src/chat/mermaidPrompt.ts create mode 100644 src/chat/mermaidPrompt.tsx diff --git a/src/chat/chatParticipant.ts b/src/chat/chatParticipant.ts index cc196d4..14ea9c5 100644 --- a/src/chat/chatParticipant.ts +++ b/src/chat/chatParticipant.ts @@ -1,17 +1,15 @@ import { sendChatParticipantRequest } from '@vscode/chat-extension-utils'; +import { PromptElementAndProps } from '@vscode/chat-extension-utils/dist/toolsPrompt'; import * as vscode from 'vscode'; import { COMMAND_OPEN_DIAGRAM_SVG } from '../commands'; import { Diagram } from '../diagram'; import { DiagramEditorPanel } from '../diagramEditorPanel'; import { logMessage } from '../extension'; -import { makePrompt } from './mermaidPrompt'; - -let developmentMode = false; +import { MermaidPrompt } from './mermaidPrompt'; export function registerChatParticipant(context: vscode.ExtensionContext) { - const handler: vscode.ChatRequestHandler = chatRequestHandler; - - developmentMode = context.extensionMode === vscode.ExtensionMode.Development; + const mermaid = new MermaidChatParticipant(context); + const handler: vscode.ChatRequestHandler = mermaid.chatRequestHandler.bind(mermaid); const participant = vscode.chat.createChatParticipant('copilot-diagram.mermAId', handler); participant.iconPath = new vscode.ThemeIcon('pie-chart'); @@ -19,102 +17,113 @@ export function registerChatParticipant(context: vscode.ExtensionContext) { DiagramEditorPanel.extensionUri = context.extensionUri; } -async function chatRequestHandler(request: vscode.ChatRequest, chatContext: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) { - if (request.command === 'help') { - handleHelpCommand(stream); - return; - } else if (request.command === 'iterate') { - const diagram = DiagramEditorPanel.currentPanel?.diagram; - if (!diagram) { - stream.markdown('No diagram found in editor view. Please create a diagram first to iterate on it.'); +class MermaidChatParticipant { + constructor( + private readonly extensionContext: vscode.ExtensionContext, + ) { } + + async chatRequestHandler(request: vscode.ChatRequest, chatContext: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) { + if (request.command === 'help') { + this.handleHelpCommand(stream); return; + } else if (request.command === 'iterate') { + const diagram = DiagramEditorPanel.currentPanel?.diagram; + if (!diagram) { + stream.markdown('No diagram found in editor view. Please create a diagram first to iterate on it.'); + return; + } } - } - let retries = 0; - let validationError = ''; - const runRequest = async () => { - const result = sendChatParticipantRequest( - request, - chatContext, - { - prompt: makePrompt(request.command, validationError), - tools: vscode.lm.tools.filter(tool => tool.tags.includes('mermaid')), - responseStreamOptions: { - stream, - references: true, - responseText: false + let retries = 0; + let validationError = ''; + const runRequest = async () => { + // I don't know how to configure the types so that this can be inlined + const prompt: PromptElementAndProps = { + promptElement: MermaidPrompt, + props: { validationError, request } + }; + const result = sendChatParticipantRequest( + request, + chatContext, + { + prompt, + tools: vscode.lm.tools.filter(tool => tool.tags.includes('mermaid')), + responseStreamOptions: { + stream, + references: true, + responseText: false + }, + requestJustification: 'To collaborate on diagrams', + extensionMode: this.extensionContext.extensionMode }, - requestJustification: 'To collaborate on diagrams', - }, - token); - - let isMermaidDiagramStreamingIn = false; - let mermaidDiagram = ''; - - let responseStr = ''; - for await (const part of result.stream) { - if (part instanceof vscode.LanguageModelTextPart) { - if (!isMermaidDiagramStreamingIn && part.value.includes('```')) { - // When we see a code block, assume it's a mermaid diagram - stream.progress('Capturing mermaid diagram from the model...'); - isMermaidDiagramStreamingIn = true; + token); + + let isMermaidDiagramStreamingIn = false; + let mermaidDiagram = ''; + + let responseStr = ''; + for await (const part of result.stream) { + if (part instanceof vscode.LanguageModelTextPart) { + if (!isMermaidDiagramStreamingIn && part.value.includes('```')) { + // When we see a code block, assume it's a mermaid diagram + stream.progress('Capturing mermaid diagram from the model...'); + isMermaidDiagramStreamingIn = true; + } + + // TODO get multiple diagrams? need to handle the end? + if (isMermaidDiagramStreamingIn) { + // Gather the mermaid diagram so we can validate it + mermaidDiagram += part.value; + } else { + // Otherwise, render the markdown normally + stream.markdown(part.value); + responseStr += part.value; + } } - - // TODO get multiple diagrams? need to handle the end? - if (isMermaidDiagramStreamingIn) { - // Gather the mermaid diagram so we can validate it - mermaidDiagram += part.value; + } + + logMessage(mermaidDiagram); + + // Validate + stream.progress('Validating mermaid diagram'); + const diagram = new Diagram(mermaidDiagram); + const diagramResult = await DiagramEditorPanel.createOrShow(diagram); + + if (diagramResult.success) { + const openMermaidDiagramCommand: vscode.Command = { + command: COMMAND_OPEN_DIAGRAM_SVG, + title: vscode.l10n.t('Open mermaid diagram'), + arguments: [diagram.content] + }; + stream.button(openMermaidDiagramCommand); + return await result.result; + } + + // -- Handle parse error + logMessage(`Not successful (on retry=${++retries})`); + if (retries < 3) { + if (retries === 1 && mermaidDiagram.indexOf('classDiagram') !== -1) { + stream.progress('Attempting to fix validation errors'); + validationError = this.getValidationErrorMessage(diagramResult.error, mermaidDiagram, true); } else { - // Otherwise, render the markdown normally - stream.markdown(part.value); - responseStr += part.value; + stream.progress('Attempting to fix validation errors'); + validationError = this.getValidationErrorMessage(diagramResult.error, mermaidDiagram, false); } - } - } - - logMessage(mermaidDiagram); - - // Validate - stream.progress('Validating mermaid diagram'); - const diagram = new Diagram(mermaidDiagram); - const diagramResult = await DiagramEditorPanel.createOrShow(diagram); - - if (diagramResult.success) { - const openMermaidDiagramCommand: vscode.Command = { - command: COMMAND_OPEN_DIAGRAM_SVG, - title: vscode.l10n.t('Open mermaid diagram'), - arguments: [diagram.content] - }; - stream.button(openMermaidDiagramCommand); - return; - } - - // -- Handle parse error - logMessage(`Not successful (on retry=${++retries})`); - if (retries < 3) { - if (retries === 1 && mermaidDiagram.indexOf('classDiagram') !== -1) { - stream.progress('Attempting to fix validation errors'); - validationError = getValidationErrorMessage(diagramResult.error, mermaidDiagram, true); + return runRequest(); } else { - stream.progress('Attempting to fix validation errors'); - validationError = getValidationErrorMessage(diagramResult.error, mermaidDiagram, false); - } - return runRequest(); - } else { - if (diagramResult.error) { - logMessage(diagramResult.error); + if (diagramResult.error) { + logMessage(diagramResult.error); + } + stream.markdown('Failed to display your requested mermaid diagram. Check output log for details.\n\n'); + return await result.result; } - stream.markdown('Failed to display your requested mermaid diagram. Check output log for details.\n\n'); - return; - } - }; + }; - await runRequest(); -} + return await runRequest(); + } -function handleHelpCommand(stream: vscode.ChatResponseStream) { - stream.markdown(` + private handleHelpCommand(stream: vscode.ChatResponseStream) { + stream.markdown(` ## Welcome to the Mermaid Diagram Generator! Mermaid is a diagramming and charting tool that extends markdown. Visit their [website](https://mermaid.js.org/) to learn more about the tool. @@ -127,14 +136,15 @@ This chat agent generates useful diagrams using Mermaid to help you better under - **/iterate**: To be called when you already have a diagram up to refine, add, and change the existing diagram. Good luck and happy diagramming!`); -} + } -function getValidationErrorMessage(error: string, diagram: string, uml: boolean) { - let message = `Please fix this mermaid parse error to make the diagram render correctly: ${error}.\n Here is the diagram you provided:\n${diagram}`; - if (uml) { - message += fixUmlMessage; + private getValidationErrorMessage(error: string, diagram: string, uml: boolean) { + let message = `Please fix this mermaid parse error to make the diagram render correctly: ${error}.\n Here is the diagram you provided:\n${diagram}`; + if (uml) { + message += fixUmlMessage; + } + return message; } - return message; } const fixUmlMessage = "\nRemember when creating the UML diagram in Mermaid, classes are represented as flat structures," + @@ -155,4 +165,4 @@ const fixUmlMessage = "\nRemember when creating the UML diagram in Mermaid, clas } House "1" --> "1" Kitchen : kitchen - `; + `; \ No newline at end of file diff --git a/src/chat/mermaidPrompt.ts b/src/chat/mermaidPrompt.ts deleted file mode 100644 index d40822c..0000000 --- a/src/chat/mermaidPrompt.ts +++ /dev/null @@ -1,103 +0,0 @@ -import * as vscode from 'vscode'; -import { DiagramEditorPanel } from '../diagramEditorPanel'; -import { logMessage } from '../extension'; -import { afterIterateCommandExampleDiagram, beforeIterateCommandExampleDiagram } from './chatExamples'; - - -export function makePrompt(command: string | undefined, validationError: string): string { - const doc = vscode.window.activeTextEditor?.document; - // full file contents are included through the prompt references, unless the user explicitly excludes them - const docRef = doc ? - `My focus is currently on the file ${doc.uri.fsPath}` : - `There is not a current file open, the root of the workspace is: ${vscode.workspace.workspaceFolders?.[0]?.uri.fsPath}`; - const currentDiagram = DiagramEditorPanel.currentPanel?.diagram; - const diagramRef = currentDiagram ? - `Refer to this if it sounds like I'm referring to an existing diagram:\n${currentDiagram.content}` : - `There isn't a diagram open that you created.`; - const clickableSyntax = 'click {ItemLabel} call linkCallback("{ItemFilePath}#L{LineNumber}")'; - const clickableSyntaxExample = `click A call linkCallback("myClass.ts#L42")`; - const requestCommand = getCommandPromptPart(command); - - return ` -- You are a helpful chat assistant that creates diagrams using the -mermaid syntax. -- If you aren't sure which tool is relevant and feel like you are missing -context, start by searching the code base to find general information. -You can call tools repeatedly to gather as much context as needed as long -as you call the tool with different arguments each time. Don't give up -unless you are sure the request cannot be fulfilled with the tools you -have. -- If you find a relevant symbol in the code gather more information about -it with one of the symbols tools. -- Use symbol information to find the file path and line number of the -symbol so that they can be referenced in the diagram. -- The final segment of your response should always be a valid mermaid diagram -prefixed with a line containing \`\`\`mermaid and suffixed with a line -containing \`\`\`. -- If you have the location for an item in the diagram, make it clickable by -adding adding the following syntax to the end of the line: -${clickableSyntax} -where ItemLabel is the label in the diagram and ItemFilePath and LineNumber -are the location of the item, but leave off the line number if you are unsure. -For example: -${clickableSyntaxExample} -- Make sure to only use the \`/\` character as a path separator in the links. -- Do not add anything to the response past the closing \`\`\` delimiter or -we won't be able to parse the response correctly. -- The \`\`\` delimiter should only occur in the two places mentioned above. - - -${docRef} -${diagramRef} - - -${requestCommand} -${validationError} -`; -} - -function getCommandPromptPart(commandName: string | undefined): string { - switch (commandName) { - case 'iterate': - // If diagram already exists - const diagram = DiagramEditorPanel.currentPanel?.diagram; - if (!diagram) { - logMessage('Iterate: No existing diagram.'); - return 'End this chat conversation after explaining that you cannot iterate on a diagram that does not exist.'; - } - logMessage('Iterating on existing diagram.'); - logMessage(diagram.content); - return `Please make changes to the currently open diagram. - -There will be following instructions on how to update the diagram. -Do not make any other edits except my directed suggestion. -It is much less likely you will need to use a tool, unless the question references the codebase. -For example, if the instructions are 'Change all int data types to doubles and change Duck to Bunny' in the following diagram: -${beforeIterateCommandExampleDiagram} -Then you should emit the following diagram: -${afterIterateCommandExampleDiagram}`; - case 'uml': - return `Please create UML diagram. Include all relevant classes in the file attached as context. You must use the tool mermAId_get_symbol_definition to get definitions of symbols -not defined in the current context. You should call it multiple times since you will likely need to get the definitions of multiple symbols. -Therefore for all classes you touch, explore their related classes using mermAId_get_symbol_definition to get their definitions and add them to the diagram. -All class relationships should be defined and correctly indicated using mermaid syntax. Also add the correct Cardinality / Multiplicity to associations like 1..n one to n where n is great than 1. - -Remember that all class associations/should be defined! The types of relationships that you can include, and their syntax in mermaid UML diagrams, are as follows: -Inheritance: <|-- : Represents a "is-a" relationship where a subclass inherits from a superclass. -Composition: *-- : Represents a "whole-part" relationship where the part cannot exist without the whole. -Aggregation: o-- : Represents a "whole-part" relationship where the part can exist independently of the whole. -Association: --> : Represents a general connection between two classes. -Dependency: ..> : Represents a "uses" relationship where one class depends on another. -Realization: ..|> : Represents an implementation relationship between an interface and a class. -Link; solid or dashed: -- : used when no other relationship fits.`; - case 'sequence': - return `Please create a mermaid sequence diagram. The diagram should include all relevant steps to describe the behaviors, actions, and steps in the user's code. -Sequence diagrams model the interactions between different parts of a system in a time-sequenced manner. There are participants which represent entities in -the system. These actors can have aliases, be group and be deactivated. -Mermaid sequence diagrams also support loops, alternative routes, parallel actions, breaks in flow, notes/comments and more. -Use all of these features to best represent the users code and add in notes and comments to provide explanation. -As always, end your message with the diagram.`; - default: - return `Pick an appropriate diagram type, for example: sequence, class, or flowchart.`; - } -} diff --git a/src/chat/mermaidPrompt.tsx b/src/chat/mermaidPrompt.tsx new file mode 100644 index 0000000..c05213b --- /dev/null +++ b/src/chat/mermaidPrompt.tsx @@ -0,0 +1,148 @@ +import { + BasePromptElementProps, + PromptElement, + PromptSizing, + UserMessage +} from '@vscode/chat-extension-utils/dist/promptTsx'; +import * as vscode from 'vscode'; +import { DiagramEditorPanel } from '../diagramEditorPanel'; +import { logMessage } from '../extension'; +import { afterIterateCommandExampleDiagram, beforeIterateCommandExampleDiagram } from './chatExamples'; + +export interface MermaidProps extends BasePromptElementProps { + request: vscode.ChatRequest; + validationError: string | undefined; +} + +export class MermaidPrompt extends PromptElement { + render(state: void, sizing: PromptSizing) { + const doc = vscode.window.activeTextEditor?.document; + // full file contents are included through the prompt references, unless the user explicitly excludes them + const docRef = doc ? + `My focus is currently on the file ${doc.uri.fsPath}` : + `There is not a current file open, the root of the workspace is: ${vscode.workspace.workspaceFolders?.[0]?.uri.fsPath}`; + const currentDiagram = DiagramEditorPanel.currentPanel?.diagram; + const diagramRef = currentDiagram ? + `Refer to this if it sounds like I'm referring to an existing diagram:\n${currentDiagram.content}` : + `There isn't a diagram open that you created.`; + const clickableSyntax = 'click {ItemLabel} call linkCallback("{ItemFilePath}#L{LineNumber}")'; + const clickableSyntaxExample = `click A call linkCallback("myClass.ts#L42")`; + return ( + <> + + Instructions:
+ - You are helpful chat assistant that creates diagrams using the + mermaid syntax. < br /> + - If you aren't sure which tool is relevant and feel like you are missing + context, start by searching the code base to find general information. + You can call tools repeatedly to gather as much context as needed as long + as you call the tool with different arguments each time.Don't give up + unless you are sure the request cannot be fulfilled with the tools you + have. < br /> + - If you find a relevant symbol in the code gather more information about + it with one of the symbols tools. < br /> + - Use symbol information to find the file path and line number of the + symbol so that they can be referenced in the diagram. < br /> + - The final segment of your response should always be a valid mermaid diagram + prefixed with a line containing \`\`\`mermaid and suffixed with a line + containing \`\`\`.
+ - If you have the location for an item in the diagram, make it clickable by + adding adding the following syntax to the end of the line:
+ {clickableSyntax}
+ where ItemLabel is the label in the diagram and ItemFilePath and LineNumber + are the location of the item, but leave off the line number if you are unsure. + For example:
+ {clickableSyntaxExample}
+ - Make sure to only use the \`/\` character as a path separator in the links. +
+ - Do not add anything to the response past the closing \`\`\` delimiter or + we won't be able to parse the response correctly.
+ - The \`\`\` delimiter should only occur in the two places mentioned above. +
+ {docRef} + {diagramRef} + + {this.props.validationError} + + ); + } +} + +interface RequestCommandProps extends BasePromptElementProps { + commandName: string; +} + +class RequestCommand extends PromptElement { + render(state: void, sizing: PromptSizing) { + switch (this.props.commandName) { + case 'iterate': + // If diagram already exists + const diagram = DiagramEditorPanel.currentPanel?.diagram; + if (!diagram) { + logMessage('Iterate: No existing diagram.'); + return ( + <> + + End this chat conversation after explaining that you cannot iterate on a diagram that does not exist. + + + ) + } + logMessage('Iterating on existing diagram.'); + logMessage(diagram.content); + return ( + <> + + Please make changes to the currently open diagram. + + There will be following instructions on how to update the diagram. + Do not make any other edits except my directed suggestion. + It is much less likely you will need to use a tool, unless the question references the codebase. + For example, if the instructions are 'Change all int data types to doubles and change Duck to Bunny' in the following diagram: + {beforeIterateCommandExampleDiagram} + Then you should emit the following diagram: + {afterIterateCommandExampleDiagram} + + + ) + case 'uml': + return ( + <> + + Please create UML diagram. Include all relevant classes in the file attached as context. You must use the tool mermAId_get_symbol_definition to get definitions of symbols + not defined in the current context. You should call it multiple times since you will likely need to get the definitions of multiple symbols. + Therefore for all classes you touch, explore their related classes using mermAId_get_symbol_definition to get their definitions and add them to the diagram. + All class relationships should be defined and correctly indicated using mermaid syntax. Also add the correct Cardinality / Multiplicity to associations like 1..n one to n where n is great than 1. + + + Remember that all class associations/should be defined! The types of relationships that you can include, and their syntax in mermaid UML diagrams, are as follows: + Inheritance: <|-- : Represents a "is-a" relationship where a subclass inherits from a superclass. + Composition: *-- : Represents a "whole-part" relationship where the part cannot exist without the whole. + Aggregation: o-- : Represents a "whole-part" relationship where the part can exist independently of the whole. + Association: --> : Represents a general connection between two classes. + Dependency: ..> : Represents a "uses" relationship where one class depends on another. + Realization: ..|> : Represents an implementation relationship between an interface and a class. + Link; solid or dashed: -- : used when no other relationship fits. + + + ); + case 'sequence': + return ( + + Please create a mermaid sequence diagram. The diagram should include all relevant steps to describe the behaviors, actions, and steps in the user's code. + Sequence diagrams model the interactions between different parts of a system in a time-sequenced manner. There are participants which represent entities in + the system. These actors can have aliases, be group and be deactivated. + Mermaid sequence diagrams also support loops, alternative routes, parallel actions, breaks in flow, notes/comments and more. + Use all of these features to best represent the users code and add in notes and comments to provide explanation. + As always, end your message with the diagram. + + ); + default: + return ( + + Pick an appropriate diagram type, for example: sequence, class, or flowchart. + + ); + } + } +} From eccbe5081eadfc6643adabe3a3ff04776d1a063d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 25 Nov 2024 11:31:02 +0000 Subject: [PATCH 4/4] Formatting --- src/chat/mermaidPrompt.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/chat/mermaidPrompt.tsx b/src/chat/mermaidPrompt.tsx index c05213b..f6dc793 100644 --- a/src/chat/mermaidPrompt.tsx +++ b/src/chat/mermaidPrompt.tsx @@ -32,17 +32,17 @@ export class MermaidPrompt extends PromptElement { Instructions:
- You are helpful chat assistant that creates diagrams using the - mermaid syntax. < br /> + mermaid syntax.
- If you aren't sure which tool is relevant and feel like you are missing context, start by searching the code base to find general information. You can call tools repeatedly to gather as much context as needed as long as you call the tool with different arguments each time.Don't give up unless you are sure the request cannot be fulfilled with the tools you - have. < br /> + have.
- If you find a relevant symbol in the code gather more information about - it with one of the symbols tools. < br /> + it with one of the symbols tools.
- Use symbol information to find the file path and line number of the - symbol so that they can be referenced in the diagram. < br /> + symbol so that they can be referenced in the diagram.
- The final segment of your response should always be a valid mermaid diagram prefixed with a line containing \`\`\`mermaid and suffixed with a line containing \`\`\`.