mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 12:19:20 +00:00
Add basic mermaid rendering support in core
For #257761 Ports over extension sample + a few improvements to core
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
**/extensions/markdown-language-features/media/**
|
**/extensions/markdown-language-features/media/**
|
||||||
**/extensions/markdown-language-features/notebook-out/**
|
**/extensions/markdown-language-features/notebook-out/**
|
||||||
**/extensions/markdown-math/notebook-out/**
|
**/extensions/markdown-math/notebook-out/**
|
||||||
|
**/extensions/mermaid-chat-features/chat-webview-out/**
|
||||||
**/extensions/notebook-renderers/renderer-out/index.js
|
**/extensions/notebook-renderers/renderer-out/index.js
|
||||||
**/extensions/simple-browser/media/index.js
|
**/extensions/simple-browser/media/index.js
|
||||||
**/extensions/terminal-suggest/src/completions/upstream/**
|
**/extensions/terminal-suggest/src/completions/upstream/**
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ const compilations = [
|
|||||||
'extensions/markdown-math/tsconfig.json',
|
'extensions/markdown-math/tsconfig.json',
|
||||||
'extensions/media-preview/tsconfig.json',
|
'extensions/media-preview/tsconfig.json',
|
||||||
'extensions/merge-conflict/tsconfig.json',
|
'extensions/merge-conflict/tsconfig.json',
|
||||||
|
'extensions/mermaid-chat-features/tsconfig.json',
|
||||||
'extensions/terminal-suggest/tsconfig.json',
|
'extensions/terminal-suggest/tsconfig.json',
|
||||||
'extensions/microsoft-authentication/tsconfig.json',
|
'extensions/microsoft-authentication/tsconfig.json',
|
||||||
'extensions/notebook-renderers/tsconfig.json',
|
'extensions/notebook-renderers/tsconfig.json',
|
||||||
|
|||||||
@@ -559,11 +559,12 @@ const extensionsPath = path.join(root, 'extensions');
|
|||||||
|
|
||||||
// Additional projects to run esbuild on. These typically build code for webviews
|
// Additional projects to run esbuild on. These typically build code for webviews
|
||||||
const esbuildMediaScripts = [
|
const esbuildMediaScripts = [
|
||||||
|
'ipynb/esbuild.mjs',
|
||||||
'markdown-language-features/esbuild-notebook.mjs',
|
'markdown-language-features/esbuild-notebook.mjs',
|
||||||
'markdown-language-features/esbuild-preview.mjs',
|
'markdown-language-features/esbuild-preview.mjs',
|
||||||
'markdown-math/esbuild.mjs',
|
'markdown-math/esbuild.mjs',
|
||||||
|
'mermaid-chat-features/esbuild-chat-webview.mjs',
|
||||||
'notebook-renderers/esbuild.mjs',
|
'notebook-renderers/esbuild.mjs',
|
||||||
'ipynb/esbuild.mjs',
|
|
||||||
'simple-browser/esbuild-preview.mjs',
|
'simple-browser/esbuild-preview.mjs',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ const dirs = [
|
|||||||
'extensions/markdown-math',
|
'extensions/markdown-math',
|
||||||
'extensions/media-preview',
|
'extensions/media-preview',
|
||||||
'extensions/merge-conflict',
|
'extensions/merge-conflict',
|
||||||
|
'extensions/mermaid-chat-features',
|
||||||
'extensions/microsoft-authentication',
|
'extensions/microsoft-authentication',
|
||||||
'extensions/notebook-renderers',
|
'extensions/notebook-renderers',
|
||||||
'extensions/npm',
|
'extensions/npm',
|
||||||
|
|||||||
@@ -1410,6 +1410,7 @@ export default tseslint.config(
|
|||||||
{
|
{
|
||||||
files: [
|
files: [
|
||||||
'extensions/markdown-language-features/**/*.ts',
|
'extensions/markdown-language-features/**/*.ts',
|
||||||
|
'extensions/mermaid-chat-features/**/*.ts',
|
||||||
'extensions/media-preview/**/*.ts',
|
'extensions/media-preview/**/*.ts',
|
||||||
'extensions/simple-browser/**/*.ts',
|
'extensions/simple-browser/**/*.ts',
|
||||||
'extensions/typescript-language-features/**/*.ts',
|
'extensions/typescript-language-features/**/*.ts',
|
||||||
@@ -1430,6 +1431,10 @@ export default tseslint.config(
|
|||||||
'extensions/simple-browser/tsconfig.json',
|
'extensions/simple-browser/tsconfig.json',
|
||||||
'extensions/simple-browser/preview-src/tsconfig.json',
|
'extensions/simple-browser/preview-src/tsconfig.json',
|
||||||
|
|
||||||
|
// Mermaid chat features
|
||||||
|
'extensions/mermaid-chat-features/tsconfig.json',
|
||||||
|
'extensions/mermaid-chat-features/chat-webview-src/tsconfig.json',
|
||||||
|
|
||||||
// TypeScript
|
// TypeScript
|
||||||
'extensions/typescript-language-features/tsconfig.json',
|
'extensions/typescript-language-features/tsconfig.json',
|
||||||
'extensions/typescript-language-features/web/tsconfig.json',
|
'extensions/typescript-language-features/web/tsconfig.json',
|
||||||
|
|||||||
1
extensions/mermaid-chat-features/.gitignore
vendored
Normal file
1
extensions/mermaid-chat-features/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
chat-webview-out
|
||||||
2
extensions/mermaid-chat-features/.npmrc
Normal file
2
extensions/mermaid-chat-features/.npmrc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
legacy-peer-deps="true"
|
||||||
|
timeout=180000
|
||||||
8
extensions/mermaid-chat-features/.vscodeignore
Normal file
8
extensions/mermaid-chat-features/.vscodeignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
src/**
|
||||||
|
extension.webpack.config.js
|
||||||
|
esbuild.*
|
||||||
|
cgmanifest.json
|
||||||
|
package-lock.json
|
||||||
|
webpack.config.js
|
||||||
|
tsconfig*.json
|
||||||
|
.gitignore
|
||||||
5
extensions/mermaid-chat-features/README.md
Normal file
5
extensions/mermaid-chat-features/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Markdown Math
|
||||||
|
|
||||||
|
**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.
|
||||||
|
|
||||||
|
Adds math rendering using [KaTeX](https://katex.org) to VS Code's built-in markdown preview and markdown cells in notebooks.
|
||||||
4
extensions/mermaid-chat-features/cgmanifest.json
Normal file
4
extensions/mermaid-chat-features/cgmanifest.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"registrations": [],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
73
extensions/mermaid-chat-features/chat-webview-src/index.ts
Normal file
73
extensions/mermaid-chat-features/chat-webview-src/index.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import mermaid, { MermaidConfig } from 'mermaid';
|
||||||
|
|
||||||
|
function getMermaidTheme() {
|
||||||
|
return document.body.classList.contains('vscode-dark') || document.body.classList.contains('vscode-high-contrast')
|
||||||
|
? 'dark'
|
||||||
|
: 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
readonly diagramText: string;
|
||||||
|
readonly theme: 'dark' | 'default';
|
||||||
|
};
|
||||||
|
|
||||||
|
let state: State | undefined = undefined;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
const diagram = document.querySelector('.mermaid');
|
||||||
|
if (!diagram) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const theme = getMermaidTheme();
|
||||||
|
state = {
|
||||||
|
diagramText: diagram.textContent ?? '',
|
||||||
|
theme
|
||||||
|
};
|
||||||
|
|
||||||
|
const config: MermaidConfig = {
|
||||||
|
startOnLoad: true,
|
||||||
|
theme,
|
||||||
|
};
|
||||||
|
mermaid.initialize(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryUpdate() {
|
||||||
|
const newTheme = getMermaidTheme();
|
||||||
|
if (state?.theme === newTheme) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const diagramNode = document.querySelector('.mermaid');
|
||||||
|
if (!diagramNode || !(diagramNode instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
diagramText: state?.diagramText ?? '',
|
||||||
|
theme: newTheme
|
||||||
|
};
|
||||||
|
|
||||||
|
// Re-render
|
||||||
|
diagramNode.textContent = state?.diagramText ?? '';
|
||||||
|
delete diagramNode.dataset.processed;
|
||||||
|
|
||||||
|
mermaid.initialize({
|
||||||
|
theme: newTheme,
|
||||||
|
});
|
||||||
|
mermaid.run({
|
||||||
|
nodes: [diagramNode]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update when theme changes
|
||||||
|
new MutationObserver(() => {
|
||||||
|
tryUpdate();
|
||||||
|
}).observe(document.body, { attributes: true, attributeFilter: ['class'] });
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"jsx": "react",
|
||||||
|
"lib": [
|
||||||
|
"ES2024",
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
18
extensions/mermaid-chat-features/esbuild-chat-webview.mjs
Normal file
18
extensions/mermaid-chat-features/esbuild-chat-webview.mjs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
// @ts-check
|
||||||
|
import path from 'path';
|
||||||
|
import { run } from '../esbuild-webview-common.mjs';
|
||||||
|
|
||||||
|
const srcDir = path.join(import.meta.dirname, 'chat-webview-src');
|
||||||
|
const outDir = path.join(import.meta.dirname, 'chat-webview-out');
|
||||||
|
|
||||||
|
run({
|
||||||
|
entryPoints: [
|
||||||
|
path.join(srcDir, 'index.ts'),
|
||||||
|
],
|
||||||
|
srcDir,
|
||||||
|
outdir: outDir,
|
||||||
|
}, process.argv);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
// @ts-check
|
||||||
|
import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs';
|
||||||
|
|
||||||
|
export default withBrowserDefaults({
|
||||||
|
context: import.meta.dirname,
|
||||||
|
entry: {
|
||||||
|
extension: './src/extension.ts'
|
||||||
|
}
|
||||||
|
});
|
||||||
16
extensions/mermaid-chat-features/extension.webpack.config.js
Normal file
16
extensions/mermaid-chat-features/extension.webpack.config.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
// @ts-check
|
||||||
|
import withDefaults from '../shared.webpack.config.mjs';
|
||||||
|
|
||||||
|
export default withDefaults({
|
||||||
|
context: import.meta.dirname,
|
||||||
|
resolve: {
|
||||||
|
mainFields: ['module', 'main']
|
||||||
|
},
|
||||||
|
entry: {
|
||||||
|
extension: './src/extension.ts',
|
||||||
|
}
|
||||||
|
});
|
||||||
1857
extensions/mermaid-chat-features/package-lock.json
generated
Normal file
1857
extensions/mermaid-chat-features/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
75
extensions/mermaid-chat-features/package.json
Normal file
75
extensions/mermaid-chat-features/package.json
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
"name": "marmaid-chat-features",
|
||||||
|
"displayName": "%displayName%",
|
||||||
|
"description": "%description%",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"publisher": "vscode",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/microsoft/vscode.git"
|
||||||
|
},
|
||||||
|
"aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255",
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.104.0"
|
||||||
|
},
|
||||||
|
"enabledApiProposals": [
|
||||||
|
"chatOutputRenderer"
|
||||||
|
],
|
||||||
|
"capabilities": {
|
||||||
|
"virtualWorkspaces": true,
|
||||||
|
"untrustedWorkspaces": {
|
||||||
|
"supported": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"main": "./out/extension",
|
||||||
|
"browser": "./dist/browser/extension",
|
||||||
|
"activationEvents": [],
|
||||||
|
"contributes": {
|
||||||
|
"chatOutputRenderers": [
|
||||||
|
{
|
||||||
|
"viewType": "vscode.chatMermaidDiagram",
|
||||||
|
"mimeTypes": [
|
||||||
|
"text/vnd.mermaid"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"languageModelTools": [
|
||||||
|
{
|
||||||
|
"name": "renderMermaidDiagram",
|
||||||
|
"displayName": "Mermaid Renderer",
|
||||||
|
"toolReferenceName": "renderMermaidDiagram",
|
||||||
|
"canBeReferencedInPrompt": true,
|
||||||
|
"modelDescription": "Renders a Mermaid diagram from Mermaid.js markup.",
|
||||||
|
"userDescription": "Render a Mermaid.js diagrams from markup.",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"markup": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The mermaid diagram markup to render as a Mermaid diagram. This should only be the markup of the diagram. Do not include a wrapping code block."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"compile": "gulp compile-extension:mermaid-chat-features && npm run build-chat-webview",
|
||||||
|
"watch": "npm run build-chat-webview && gulp watch-extension:mermaid-chat-features",
|
||||||
|
"vscode:prepublish": "npm run build-ext && npm run build-chat-webview",
|
||||||
|
"build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:mermaid-chat-features ./tsconfig.json",
|
||||||
|
"build-chat-webview": "node ./esbuild-chat-webview.mjs",
|
||||||
|
"compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none",
|
||||||
|
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jsdom": "^21.1.7",
|
||||||
|
"@types/node": "^22"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"dompurify": "^3.2.6",
|
||||||
|
"jsdom": "^26.1.0",
|
||||||
|
"mermaid": "^11.11.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
extensions/mermaid-chat-features/package.nls.json
Normal file
6
extensions/mermaid-chat-features/package.nls.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"displayName": "Markdown Math",
|
||||||
|
"description": "Adds math support to Markdown in notebooks.",
|
||||||
|
"config.markdown.math.enabled": "Enable/disable rendering math in the built-in Markdown preview.",
|
||||||
|
"config.markdown.math.macros": "A collection of custom macros. Each macro is a key-value pair where the key is a new command name and the value is the expansion of the macro."
|
||||||
|
}
|
||||||
244
extensions/mermaid-chat-features/src/extension.ts
Normal file
244
extensions/mermaid-chat-features/src/extension.ts
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
import { JSDOM } from 'jsdom';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View type that uniquely identifies the Mermaid chat output renderer.
|
||||||
|
*/
|
||||||
|
const viewType = 'vscode.chatMermaidDiagram';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mime type used to identify Mermaid diagram data in chat output.
|
||||||
|
*/
|
||||||
|
const mime = 'text/vnd.mermaid';
|
||||||
|
|
||||||
|
const maxFixAttempts = 3;
|
||||||
|
|
||||||
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
|
|
||||||
|
// Register tools
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.lm.registerTool<{ markup: string }>('renderMermaidDiagram', {
|
||||||
|
invoke: async (options, token) => {
|
||||||
|
let sourceCode = options.input.markup;
|
||||||
|
sourceCode = await runMermaidMarkupFixLoop(sourceCode, token);
|
||||||
|
return writeMermaidToolOutput(sourceCode);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register the chat output renderer for Mermaid diagrams.
|
||||||
|
// This will be invoked with the data generated by the tools.
|
||||||
|
// It can also be invoked when rendering old Mermaid diagrams in the chat history.
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.chat.registerChatOutputRenderer(viewType, {
|
||||||
|
async renderChatOutput({ value }, webview, _ctx, _token) {
|
||||||
|
const mermaidSource = new TextDecoder().decode(value);
|
||||||
|
|
||||||
|
// Set the options for the webview
|
||||||
|
const mediaRoot = vscode.Uri.joinPath(context.extensionUri, 'chat-webview-out');
|
||||||
|
webview.options = {
|
||||||
|
enableScripts: true,
|
||||||
|
localResourceRoots: [mediaRoot],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set the HTML content for the webview
|
||||||
|
const nonce = getNonce();
|
||||||
|
const mermaidScript = vscode.Uri.joinPath(mediaRoot, 'index.js');
|
||||||
|
|
||||||
|
webview.html = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Mermaid Diagram</title>
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src ${webview.cspSource} 'nonce-${nonce}'; style-src 'self' 'unsafe-inline';" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<pre class="mermaid">
|
||||||
|
${escapeHtmlText(mermaidSource)}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<script type="module" nonce="${nonce}" src="${webview.asWebviewUri(mermaidScript)}"></script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily load mermaid
|
||||||
|
*/
|
||||||
|
const getMermaidInstance = (() => {
|
||||||
|
const createMermaidInstance = async () => {
|
||||||
|
// Patch the global window object for mermaid
|
||||||
|
|
||||||
|
const { window } = new JSDOM('');
|
||||||
|
(global as any).window = window;
|
||||||
|
(global as any).DOMPurify = DOMPurify(window);
|
||||||
|
return import('mermaid');
|
||||||
|
};
|
||||||
|
|
||||||
|
let cached: Promise<typeof import('mermaid')> | undefined;
|
||||||
|
return async (): Promise<typeof import('mermaid').default> => {
|
||||||
|
cached ??= createMermaidInstance();
|
||||||
|
return (await cached).default;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to fix mermaid syntax errors in a set number of attempts.
|
||||||
|
*
|
||||||
|
* @returns The best effort to fix the Mermaid markup.
|
||||||
|
*/
|
||||||
|
async function runMermaidMarkupFixLoop(sourceCode: string, token: vscode.CancellationToken): Promise<string> {
|
||||||
|
let attempt = 0;
|
||||||
|
while (attempt < maxFixAttempts) {
|
||||||
|
const result = await validateMermaidMarkup(sourceCode);
|
||||||
|
if (token.isCancellationRequested) {
|
||||||
|
throw new Error('Operation cancelled');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.type === 'success') {
|
||||||
|
return sourceCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
attempt++;
|
||||||
|
|
||||||
|
sourceCode = await tryFixingUpMermaidMarkup(sourceCode, result.message, token);
|
||||||
|
if (token.isCancellationRequested) {
|
||||||
|
throw new Error('Operation cancelled');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return whatever we have after max attempts
|
||||||
|
return sourceCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the syntax of the provided Mermaid markup.
|
||||||
|
*/
|
||||||
|
async function validateMermaidMarkup(sourceCode: string): Promise<{ type: 'success' } | { type: 'error'; message: string }> {
|
||||||
|
try {
|
||||||
|
const mermaid = await getMermaidInstance();
|
||||||
|
await mermaid.parse(sourceCode);
|
||||||
|
return { type: 'success' };
|
||||||
|
} catch (error) {
|
||||||
|
if (!(error instanceof Error)) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: 'error', message: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses a language model to try to fix Mermaid markup based on an error message.
|
||||||
|
*/
|
||||||
|
async function tryFixingUpMermaidMarkup(sourceCode: string, errorMessage: string, token: vscode.CancellationToken): Promise<string> {
|
||||||
|
const model = await getPreferredLm();
|
||||||
|
if (!model) {
|
||||||
|
console.warn('No suitable model found for fixing Mermaid markup');
|
||||||
|
return sourceCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.isCancellationRequested) {
|
||||||
|
throw new Error('Operation cancelled');
|
||||||
|
}
|
||||||
|
|
||||||
|
const completion = await model.sendRequest([
|
||||||
|
vscode.LanguageModelChatMessage.Assistant(joinLines(
|
||||||
|
`The user will provide you with the source code for the Mermaid diagram and an error message.`,
|
||||||
|
`Your task is to fix the Mermaid source code based on the error message.`,
|
||||||
|
`Please return the fixed Mermaid source code inside a \`mermaid\` fenced code block. Do not add any comments or explanation.`,
|
||||||
|
`Make sure to return the entire source code.`
|
||||||
|
)),
|
||||||
|
vscode.LanguageModelChatMessage.User(joinLines(
|
||||||
|
`Here is my Mermaid source code:`,
|
||||||
|
``,
|
||||||
|
`\`\`\`mermaid`,
|
||||||
|
`${sourceCode}`,
|
||||||
|
`\`\`\``,
|
||||||
|
``,
|
||||||
|
`And here is the mermaid error message:`,
|
||||||
|
``,
|
||||||
|
errorMessage,
|
||||||
|
)),
|
||||||
|
], {}, token);
|
||||||
|
|
||||||
|
return await parseMermaidMarkupFromChatResponse(completion, token) ?? sourceCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function parseMermaidMarkupFromChatResponse(chatResponse: vscode.LanguageModelChatResponse, token: vscode.CancellationToken): Promise<string | undefined> {
|
||||||
|
const parts: string[] = [];
|
||||||
|
for await (const line of chatResponse.text) {
|
||||||
|
if (token.isCancellationRequested) {
|
||||||
|
throw new Error('Operation cancelled');
|
||||||
|
}
|
||||||
|
|
||||||
|
parts.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = parts.join('');
|
||||||
|
const lines = response.split('\n');
|
||||||
|
if (!lines.at(0)?.startsWith('```') || !lines.at(-1)?.endsWith('```')) {
|
||||||
|
console.warn('Invalid response format from model, expected fenced code block');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.slice(1, -1).join('\n').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function getPreferredLm(): Promise<vscode.LanguageModelChat | undefined> {
|
||||||
|
return (await vscode.lm.selectChatModels({ family: 'gpt-4o-mini' })).at(0)
|
||||||
|
?? (await vscode.lm.selectChatModels({ family: 'gpt-4o' })).at(0)
|
||||||
|
?? (await vscode.lm.selectChatModels({})).at(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeMermaidToolOutput(sourceCode: string): vscode.LanguageModelToolResult {
|
||||||
|
// Expose the source code as a tool result for the LM
|
||||||
|
const result = new vscode.LanguageModelToolResult([
|
||||||
|
new vscode.LanguageModelTextPart(sourceCode)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// And store custom data in the tool result details to indicate that a custom renderer should be used for it.
|
||||||
|
// In this case we just store the source code as binary data.
|
||||||
|
|
||||||
|
// Add cast to use proposed API
|
||||||
|
(result as vscode.ExtendedLanguageModelToolResult2).toolResultDetails2 = {
|
||||||
|
mime,
|
||||||
|
value: new TextEncoder().encode(sourceCode),
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function joinLines(...lines: string[]): string {
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtmlText(str: string): string {
|
||||||
|
return str
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNonce() {
|
||||||
|
let text = '';
|
||||||
|
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
for (let i = 0; i < 64; i++) {
|
||||||
|
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
15
extensions/mermaid-chat-features/tsconfig.json
Normal file
15
extensions/mermaid-chat-features/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out",
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*",
|
||||||
|
"../../src/vscode-dts/vscode.d.ts",
|
||||||
|
"../../src/vscode-dts/vscode.proposed.chatOutputRenderer.d.ts",
|
||||||
|
"../../src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts",
|
||||||
|
"../../src/vscode-dts/vscode.proposed.languageModelThinkingPart.d.ts",
|
||||||
|
"../../src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -57,10 +57,10 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "gulp compile-extension:markdown-language-features && npm run build-preview",
|
"compile": "gulp compile-extension:simple-browser && npm run build-preview",
|
||||||
"watch": "npm run build-preview && gulp watch-extension:markdown-language-features",
|
"watch": "npm run build-preview && gulp watch-extension:simple-browser",
|
||||||
"vscode:prepublish": "npm run build-ext && npm run build-preview",
|
"vscode:prepublish": "npm run build-ext && npm run build-preview",
|
||||||
"build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json",
|
"build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:simple-browser ./tsconfig.json",
|
||||||
"build-preview": "node ./esbuild-preview.mjs",
|
"build-preview": "node ./esbuild-preview.mjs",
|
||||||
"compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none",
|
"compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none",
|
||||||
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
|
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
|
||||||
|
|||||||
Reference in New Issue
Block a user