Feat: support oceanbase monaco plugin (#1386)

This commit is contained in:
Xiao Kang 2024-04-13 23:55:33 +08:00 committed by GitHub
parent f3ece627d0
commit 53438a368b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 2423 additions and 62 deletions

View File

@ -1,10 +1,10 @@
import { ChangeEvent, Key, useEffect, useMemo, useState } from 'react';
import React, { ChangeEvent, Key, useEffect, useMemo, useState } from 'react';
import { useRequest } from 'ahooks';
import { Button, Select, Table, Tooltip } from 'antd';
import { Input, Tree } from 'antd';
import Icon from '@ant-design/icons';
import type { DataNode } from 'antd/es/tree';
import MonacoEditor from './monaco-editor';
import MonacoEditor, { ISession } from './monaco-editor';
import { sendGetRequest, sendSpacePostRequest } from '@/utils/request';
import { useSearchParams } from 'next/navigation';
import { OnChange } from '@monaco-editor/react';
@ -45,6 +45,7 @@ interface IProps {
chartData?: any;
tableData?: ITableData;
layout?: 'TB' | 'LR';
tables?: any;
handleChange: OnChange;
}
@ -58,7 +59,7 @@ interface ITableTreeItem {
children: Array<ITableTreeItem>;
}
function DbEditorContent({ layout = 'LR', editorValue, chartData, tableData, handleChange }: IProps) {
function DbEditorContent({ layout = 'LR', editorValue, chartData, tableData, tables, handleChange }: IProps) {
const chartWrapper = useMemo(() => {
if (!chartData) return null;
@ -86,9 +87,36 @@ function DbEditorContent({ layout = 'LR', editorValue, chartData, tableData, han
return {
columns: tbCols,
dataSource: tbDatas,
};
}, [tableData]);
const session: ISession = useMemo(() => {
const map: Record<string, { columnName: string; columnType: string; }[]> = {};
const db = tables?.data;
const tableList = db?.children;
tableList?.forEach((table: ITableTreeItem) => {
map[table.title] = table.children.map((column: ITableTreeItem) => {
return {
columnName: column.title,
columnType: column.type,
};
})
});
return {
async getTableList(schemaName) {
if (schemaName && schemaName!== db?.title) {
return [];
}
return tableList?.map((table: ITableTreeItem) => table.title) || [];
},
async getTableColumns(tableName) {
return map[tableName] || [];
},
async getSchemaList() {
return db?.title ? [db?.title] : [];
}
};
}, [tables])
return (
<div
className={classNames('flex w-full flex-1 h-full gap-2 overflow-hidden', {
@ -97,7 +125,7 @@ function DbEditorContent({ layout = 'LR', editorValue, chartData, tableData, han
})}
>
<div className="flex-1 flex overflow-hidden rounded">
<MonacoEditor value={editorValue?.sql || ''} language="mysql" onChange={handleChange} thoughts={editorValue?.thoughts || ''} />
<MonacoEditor value={editorValue?.sql || ''} language="mysql" onChange={handleChange} thoughts={editorValue?.thoughts || ''} session={session} />
</div>
<div className="flex-1 h-full overflow-auto bg-white dark:bg-theme-dark-container rounded p-4">
{!!tableData?.values.length ? (
@ -626,6 +654,7 @@ function DbEditor() {
}}
tableData={tableData}
chartData={undefined}
tables={tables}
/>
)}
</div>

View File

@ -1,25 +1,38 @@
import * as monaco from 'monaco-editor';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
import Editor, { OnChange, loader } from '@monaco-editor/react';
import classNames from 'classnames';
import { useContext, useMemo } from 'react';
import { format } from 'sql-formatter';
import { ChatContext } from '@/app/chat-context';
import { formatSql } from '@/utils';
import { getModelService } from './ob-editor/service';
import { useLatest } from 'ahooks';
import { ChatContext } from '@/app/chat-context';
import { github, githubDark } from './ob-editor/theme';
import { register } from './ob-editor/ob-plugin';
loader.config({ monaco });
export interface ISession {
getTableList: (schemaName?: string) => Promise<string[]>;
getTableColumns: (tableName: string) => Promise<{ columnName: string; columnType: string }[]>;
getSchemaList: () => Promise<string[]>;
}
interface MonacoEditorProps {
className?: string;
value: string;
language: string;
onChange?: OnChange;
thoughts?: string;
session?: ISession;
}
export default function MonacoEditor({ className, value, language = 'mysql', onChange, thoughts }: MonacoEditorProps) {
// merge value and thoughts
const { mode } = useContext(ChatContext);
let plugin = null;
monaco.editor.defineTheme('github', github as any);
monaco.editor.defineTheme('githubDark', githubDark as any);
export default function MonacoEditor({ className, value, language = 'mysql', onChange, thoughts, session }: MonacoEditorProps) {
// merge value and thoughts
const editorValue = useMemo(() => {
if (language !== 'mysql') {
return value;
@ -30,14 +43,34 @@ export default function MonacoEditor({ className, value, language = 'mysql', onC
return formatSql(value);
}, [value, thoughts]);
const sessionRef = useLatest(session);
const context = useContext(ChatContext);
async function pluginRegister(editor: monaco.editor.IStandaloneCodeEditor) {
const plugin = await register()
plugin.setModelOptions(
editor.getModel()?.id || '',
getModelService({
modelId: editor.getModel()?.id || '',
delimiter: ';',
}, () => sessionRef.current || null)
)
}
return (
<Editor
className={classNames(className)}
onMount={pluginRegister}
value={editorValue}
language={language}
defaultLanguage={language}
onChange={onChange}
theme={mode === 'dark' ? 'vs-dark' : 'light'}
theme={context?.mode !== "dark" ? "github" : "githubDark"}
options={{
minimap: {
enabled: false,
},
wordWrap: 'on',
}}
/>

View File

@ -0,0 +1,31 @@
import type Plugin from '@oceanbase-odc/monaco-plugin-ob';
let plugin: Plugin;
export async function register(): Promise<Plugin> {
window.obMonaco = {
getWorkerUrl: (type: string) => {
switch (type) {
case 'mysql': {
return location.origin + '/_next/static/ob-workers/mysql.js'
}
case 'obmysql': {
return location.origin + '/_next/static/ob-workers/obmysql.js'
}
case 'oboracle': {
return location.origin + '/_next/static/ob-workers/oracle.js'
}
}
return "";
}
}
const module = await import('@oceanbase-odc/monaco-plugin-ob')
const Plugin = module.default;
if (plugin) {
return plugin;
}
plugin = new Plugin();
plugin.setup(["mysql"]);
return plugin;
}

View File

@ -0,0 +1,21 @@
import type { IModelOptions } from '@oceanbase-odc/monaco-plugin-ob/dist/type';
import { ISession } from '../monaco-editor';
export function getModelService(
{ modelId, delimiter }: { modelId: string; delimiter: string },
session?: () => ISession | null
): IModelOptions {
return {
delimiter,
async getTableList(schemaName?: string) {
return session?.()?.getTableList(schemaName) || []
},
async getTableColumns(tableName: string, dbName?: string) {
return session?.()?.getTableColumns(tableName) || []
},
async getSchemaList() {
return session?.()?.getSchemaList() || []
},
};
}

View File

@ -0,0 +1,698 @@
export const github = {
base: 'vs',
inherit: true,
rules: [
{
background: 'ffffff',
token: '',
},
{
foreground: '6a737d',
token: 'comment',
},
{
foreground: '6a737d',
token: 'punctuation.definition.comment',
},
{
foreground: '6a737d',
token: 'string.comment',
},
{
foreground: '005cc5',
token: 'constant',
},
{
foreground: '005cc5',
token: 'entity.name.constant',
},
{
foreground: '005cc5',
token: 'variable.other.constant',
},
{
foreground: '005cc5',
token: 'variable.language',
},
{
foreground: '6f42c1',
token: 'entity',
},
{
foreground: '6f42c1',
token: 'entity.name',
},
{
foreground: '24292e',
token: 'variable.parameter.function',
},
{
foreground: '22863a',
token: 'entity.name.tag',
},
{
foreground: 'd73a49',
token: 'keyword',
},
{
foreground: 'd73a49',
token: 'storage',
},
{
foreground: 'd73a49',
token: 'storage.type',
},
{
foreground: '24292e',
token: 'storage.modifier.package',
},
{
foreground: '24292e',
token: 'storage.modifier.import',
},
{
foreground: '24292e',
token: 'storage.type.java',
},
{
foreground: '032f62',
token: 'string',
},
{
foreground: '032f62',
token: 'punctuation.definition.string',
},
{
foreground: '032f62',
token: 'string punctuation.section.embedded source',
},
{
foreground: '005cc5',
token: 'support',
},
{
foreground: '005cc5',
token: 'meta.property-name',
},
{
foreground: 'e36209',
token: 'variable',
},
{
foreground: '24292e',
token: 'variable.other',
},
{
foreground: 'b31d28',
fontStyle: 'bold italic underline',
token: 'invalid.broken',
},
{
foreground: 'b31d28',
fontStyle: 'bold italic underline',
token: 'invalid.deprecated',
},
{
foreground: 'fafbfc',
background: 'b31d28',
fontStyle: 'italic underline',
token: 'invalid.illegal',
},
{
foreground: 'fafbfc',
background: 'd73a49',
fontStyle: 'italic underline',
token: 'carriage-return',
},
{
foreground: 'b31d28',
fontStyle: 'bold italic underline',
token: 'invalid.unimplemented',
},
{
foreground: 'b31d28',
token: 'message.error',
},
{
foreground: '24292e',
token: 'string source',
},
{
foreground: '005cc5',
token: 'string variable',
},
{
foreground: '032f62',
token: 'source.regexp',
},
{
foreground: '032f62',
token: 'string.regexp',
},
{
foreground: '032f62',
token: 'string.regexp.character-class',
},
{
foreground: '032f62',
token: 'string.regexp constant.character.escape',
},
{
foreground: '032f62',
token: 'string.regexp source.ruby.embedded',
},
{
foreground: '032f62',
token: 'string.regexp string.regexp.arbitrary-repitition',
},
{
foreground: '22863a',
fontStyle: 'bold',
token: 'string.regexp constant.character.escape',
},
{
foreground: '005cc5',
token: 'support.constant',
},
{
foreground: '005cc5',
token: 'support.variable',
},
{
foreground: '005cc5',
token: 'meta.module-reference',
},
{
foreground: '735c0f',
token: 'markup.list',
},
{
foreground: '005cc5',
fontStyle: 'bold',
token: 'markup.heading',
},
{
foreground: '005cc5',
fontStyle: 'bold',
token: 'markup.heading entity.name',
},
{
foreground: '22863a',
token: 'markup.quote',
},
{
foreground: '24292e',
fontStyle: 'italic',
token: 'markup.italic',
},
{
foreground: '24292e',
fontStyle: 'bold',
token: 'markup.bold',
},
{
foreground: '005cc5',
token: 'markup.raw',
},
{
foreground: 'b31d28',
background: 'ffeef0',
token: 'markup.deleted',
},
{
foreground: 'b31d28',
background: 'ffeef0',
token: 'meta.diff.header.from-file',
},
{
foreground: 'b31d28',
background: 'ffeef0',
token: 'punctuation.definition.deleted',
},
{
foreground: '22863a',
background: 'f0fff4',
token: 'markup.inserted',
},
{
foreground: '22863a',
background: 'f0fff4',
token: 'meta.diff.header.to-file',
},
{
foreground: '22863a',
background: 'f0fff4',
token: 'punctuation.definition.inserted',
},
{
foreground: 'e36209',
background: 'ffebda',
token: 'markup.changed',
},
{
foreground: 'e36209',
background: 'ffebda',
token: 'punctuation.definition.changed',
},
{
foreground: 'f6f8fa',
background: '005cc5',
token: 'markup.ignored',
},
{
foreground: 'f6f8fa',
background: '005cc5',
token: 'markup.untracked',
},
{
foreground: '6f42c1',
fontStyle: 'bold',
token: 'meta.diff.range',
},
{
foreground: '005cc5',
token: 'meta.diff.header',
},
{
foreground: '005cc5',
fontStyle: 'bold',
token: 'meta.separator',
},
{
foreground: '005cc5',
token: 'meta.output',
},
{
foreground: '586069',
token: 'brackethighlighter.tag',
},
{
foreground: '586069',
token: 'brackethighlighter.curly',
},
{
foreground: '586069',
token: 'brackethighlighter.round',
},
{
foreground: '586069',
token: 'brackethighlighter.square',
},
{
foreground: '586069',
token: 'brackethighlighter.angle',
},
{
foreground: '586069',
token: 'brackethighlighter.quote',
},
{
foreground: 'b31d28',
token: 'brackethighlighter.unmatched',
},
{
foreground: 'b31d28',
token: 'sublimelinter.mark.error',
},
{
foreground: 'e36209',
token: 'sublimelinter.mark.warning',
},
{
foreground: '959da5',
token: 'sublimelinter.gutter-mark',
},
{
foreground: '032f62',
fontStyle: 'underline',
token: 'constant.other.reference.link',
},
{
foreground: '032f62',
fontStyle: 'underline',
token: 'string.other.link',
},
],
colors: {
'editor.foreground': '#24292e',
'editor.background': '#ffffff',
'editor.selectionBackground': '#c8c8fa',
'editor.inactiveSelectionBackground': '#fafbfc',
'editor.lineHighlightBackground': '#fafbfc',
'editorCursor.foreground': '#24292e',
'editorWhitespace.foreground': '#959da5',
'editorIndentGuide.background': '#959da5',
'editorIndentGuide.activeBackground': '#24292e',
'editor.selectionHighlightBorder': '#fafbfc',
},
};
export const githubDark = {
base: 'vs-dark',
inherit: true,
rules: [
{
background: '24292e',
token: '',
},
{
foreground: '959da5',
token: 'comment',
},
{
foreground: '959da5',
token: 'punctuation.definition.comment',
},
{
foreground: '959da5',
token: 'string.comment',
},
{
foreground: 'c8e1ff',
token: 'constant',
},
{
foreground: 'c8e1ff',
token: 'entity.name.constant',
},
{
foreground: 'c8e1ff',
token: 'variable.other.constant',
},
{
foreground: 'c8e1ff',
token: 'variable.language',
},
{
foreground: 'b392f0',
token: 'entity',
},
{
foreground: 'b392f0',
token: 'entity.name',
},
{
foreground: 'f6f8fa',
token: 'variable.parameter.function',
},
{
foreground: '7bcc72',
token: 'entity.name.tag',
},
{
foreground: 'ea4a5a',
token: 'keyword',
},
{
foreground: 'ea4a5a',
token: 'storage',
},
{
foreground: 'ea4a5a',
token: 'storage.type',
},
{
foreground: 'f6f8fa',
token: 'storage.modifier.package',
},
{
foreground: 'f6f8fa',
token: 'storage.modifier.import',
},
{
foreground: 'f6f8fa',
token: 'storage.type.java',
},
{
foreground: '79b8ff',
token: 'string',
},
{
foreground: '79b8ff',
token: 'punctuation.definition.string',
},
{
foreground: '79b8ff',
token: 'string punctuation.section.embedded source',
},
{
foreground: 'c8e1ff',
token: 'support',
},
{
foreground: 'c8e1ff',
token: 'meta.property-name',
},
{
foreground: 'fb8532',
token: 'variable',
},
{
foreground: 'f6f8fa',
token: 'variable.other',
},
{
foreground: 'd73a49',
fontStyle: 'bold italic underline',
token: 'invalid.broken',
},
{
foreground: 'd73a49',
fontStyle: 'bold italic underline',
token: 'invalid.deprecated',
},
{
foreground: 'fafbfc',
background: 'd73a49',
fontStyle: 'italic underline',
token: 'invalid.illegal',
},
{
foreground: 'fafbfc',
background: 'd73a49',
fontStyle: 'italic underline',
token: 'carriage-return',
},
{
foreground: 'd73a49',
fontStyle: 'bold italic underline',
token: 'invalid.unimplemented',
},
{
foreground: 'd73a49',
token: 'message.error',
},
{
foreground: 'f6f8fa',
token: 'string source',
},
{
foreground: 'c8e1ff',
token: 'string variable',
},
{
foreground: '79b8ff',
token: 'source.regexp',
},
{
foreground: '79b8ff',
token: 'string.regexp',
},
{
foreground: '79b8ff',
token: 'string.regexp.character-class',
},
{
foreground: '79b8ff',
token: 'string.regexp constant.character.escape',
},
{
foreground: '79b8ff',
token: 'string.regexp source.ruby.embedded',
},
{
foreground: '79b8ff',
token: 'string.regexp string.regexp.arbitrary-repitition',
},
{
foreground: '7bcc72',
fontStyle: 'bold',
token: 'string.regexp constant.character.escape',
},
{
foreground: 'c8e1ff',
token: 'support.constant',
},
{
foreground: 'c8e1ff',
token: 'support.variable',
},
{
foreground: 'c8e1ff',
token: 'meta.module-reference',
},
{
foreground: 'fb8532',
token: 'markup.list',
},
{
foreground: '0366d6',
fontStyle: 'bold',
token: 'markup.heading',
},
{
foreground: '0366d6',
fontStyle: 'bold',
token: 'markup.heading entity.name',
},
{
foreground: 'c8e1ff',
token: 'markup.quote',
},
{
foreground: 'f6f8fa',
fontStyle: 'italic',
token: 'markup.italic',
},
{
foreground: 'f6f8fa',
fontStyle: 'bold',
token: 'markup.bold',
},
{
foreground: 'c8e1ff',
token: 'markup.raw',
},
{
foreground: 'b31d28',
background: 'ffeef0',
token: 'markup.deleted',
},
{
foreground: 'b31d28',
background: 'ffeef0',
token: 'meta.diff.header.from-file',
},
{
foreground: 'b31d28',
background: 'ffeef0',
token: 'punctuation.definition.deleted',
},
{
foreground: '176f2c',
background: 'f0fff4',
token: 'markup.inserted',
},
{
foreground: '176f2c',
background: 'f0fff4',
token: 'meta.diff.header.to-file',
},
{
foreground: '176f2c',
background: 'f0fff4',
token: 'punctuation.definition.inserted',
},
{
foreground: 'b08800',
background: 'fffdef',
token: 'markup.changed',
},
{
foreground: 'b08800',
background: 'fffdef',
token: 'punctuation.definition.changed',
},
{
foreground: '2f363d',
background: '959da5',
token: 'markup.ignored',
},
{
foreground: '2f363d',
background: '959da5',
token: 'markup.untracked',
},
{
foreground: 'b392f0',
fontStyle: 'bold',
token: 'meta.diff.range',
},
{
foreground: 'c8e1ff',
token: 'meta.diff.header',
},
{
foreground: '0366d6',
fontStyle: 'bold',
token: 'meta.separator',
},
{
foreground: '0366d6',
token: 'meta.output',
},
{
foreground: 'ffeef0',
token: 'brackethighlighter.tag',
},
{
foreground: 'ffeef0',
token: 'brackethighlighter.curly',
},
{
foreground: 'ffeef0',
token: 'brackethighlighter.round',
},
{
foreground: 'ffeef0',
token: 'brackethighlighter.square',
},
{
foreground: 'ffeef0',
token: 'brackethighlighter.angle',
},
{
foreground: 'ffeef0',
token: 'brackethighlighter.quote',
},
{
foreground: 'd73a49',
token: 'brackethighlighter.unmatched',
},
{
foreground: 'd73a49',
token: 'sublimelinter.mark.error',
},
{
foreground: 'fb8532',
token: 'sublimelinter.mark.warning',
},
{
foreground: '6a737d',
token: 'sublimelinter.gutter-mark',
},
{
foreground: '79b8ff',
fontStyle: 'underline',
token: 'constant.other.reference.link',
},
{
foreground: '79b8ff',
fontStyle: 'underline',
token: 'string.other.link',
},
],
colors: {
'editor.foreground': '#f6f8fa',
'editor.background': '#24292e',
'editor.selectionBackground': '#4c2889',
'editor.inactiveSelectionBackground': '#444d56',
'editor.lineHighlightBackground': '#444d56',
'editorCursor.foreground': '#ffffff',
'editorWhitespace.foreground': '#6a737d',
'editorIndentGuide.background': '#6a737d',
'editorIndentGuide.activeBackground': '#f6f8fa',
'editor.selectionHighlightBorder': '#444d56',
},
};

View File

@ -1,4 +1,7 @@
/** @type {import('next').NextConfig} */
const CopyPlugin = require('copy-webpack-plugin');
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const path = require('path');
const nextConfig = {
output: 'export',
experimental: {
@ -12,6 +15,30 @@ const nextConfig = {
},
trailingSlash: true,
images: { unoptimized: true },
webpack: (config, { isServer }) => {
config.resolve.fallback = { fs: false };
if (!isServer) {
config.plugins.push(
new CopyPlugin({
patterns: [
{
from: path.join(__dirname, 'node_modules/@oceanbase-odc/monaco-plugin-ob/worker-dist/'),
to: 'static/ob-workers'
},
],
})
)
// 添加 monaco-editor-webpack-plugin 插件
config.plugins.push(
new MonacoWebpackPlugin({
// 你可以在这里配置插件的选项,例如:
languages: ['sql'],
filename: 'static/[name].worker.js'
})
);
}
return config;
}
};
module.exports = nextConfig;

1605
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,13 +3,13 @@
"version": "0.1.0",
"private": true,
"scripts": {
"start": "next start",
"dev": "next dev",
"build": "next build",
"build:prod": "APP_ENV=prod next build",
"start": "NODE_OPTIONS=--max_old_space_size=8192 next start",
"dev": "NODE_OPTIONS=--max_old_space_size=8192 next dev",
"build": "NODE_OPTIONS=--max_old_space_size=8192 next build",
"build:prod": "APP_ENV=prod NODE_OPTIONS=--max_old_space_size=8192 next build",
"lint": "next lint",
"export": "next export",
"compile": "next build && next export"
"compile": " NODE_OPTIONS=--max_old_space_size=8192 next build && next export"
},
"dependencies": {
"@ant-design/icons": "^5.2.5",
@ -21,6 +21,7 @@
"@monaco-editor/react": "^4.5.2",
"@mui/icons-material": "^5.11.16",
"@mui/joy": "5.0.0-beta.5",
"@oceanbase-odc/monaco-plugin-ob": "^1.0.3",
"ahooks": "^3.7.8",
"antd": "^5.6.2",
"axios": "^1.3.4",
@ -53,8 +54,10 @@
"@types/react-dom": "18.2.6",
"@types/react-syntax-highlighter": "^15.5.7",
"autoprefixer": "10.4.14",
"copy-webpack-plugin": "^12.0.2",
"eslint": "8.43.0",
"eslint-config-next": "13.4.7"
"eslint-config-next": "13.4.7",
"monaco-editor-webpack-plugin": "^7.1.0"
},
"description": "DB-GPT WebUI",
"main": "next.config.js",