From 2735be49840c620416cd382f5873ce44dabf6f24 Mon Sep 17 00:00:00 2001 From: Col0ring <1561999073@qq.com> Date: Mon, 10 Nov 2025 14:28:55 +0800 Subject: [PATCH 1/7] feat: add pro component `MonacoEditor` --- .../components/base/text/__init__.py | 4 +- .../components/pro/__init__.py | 3 + .../components/pro/components.py | 3 + .../components/pro/monaco_editor/__init__.py | 98 ++++++++++++++ .../pro/monaco_editor/diff_editor/__init__.py | 96 +++++++++++++ frontend/defineConfig.js | 2 +- frontend/package.json | 2 + frontend/plugin.js | 13 +- frontend/pro/monaco-editor/Index.svelte | 108 +++++++++++++++ .../monaco-editor/diff-editor/Index.svelte | 110 +++++++++++++++ .../diff-editor/gradio.config.js | 7 + .../diff-editor/monaco-editor.diff-editor.tsx | 127 ++++++++++++++++++ .../monaco-editor/diff-editor/package.json | 14 ++ frontend/pro/monaco-editor/gradio.config.js | 7 + frontend/pro/monaco-editor/loader.ts | 99 ++++++++++++++ frontend/pro/monaco-editor/monaco-editor.less | 6 + frontend/pro/monaco-editor/monaco-editor.tsx | 80 +++++++++++ frontend/pro/monaco-editor/package.json | 14 ++ frontend/svelte-preprocess-react/inject.ts | 15 ++- pnpm-lock.yaml | 55 +++++++- pyproject.toml | 1 + 21 files changed, 856 insertions(+), 8 deletions(-) create mode 100644 backend/modelscope_studio/components/pro/monaco_editor/__init__.py create mode 100644 backend/modelscope_studio/components/pro/monaco_editor/diff_editor/__init__.py create mode 100644 frontend/pro/monaco-editor/Index.svelte create mode 100644 frontend/pro/monaco-editor/diff-editor/Index.svelte create mode 100644 frontend/pro/monaco-editor/diff-editor/gradio.config.js create mode 100644 frontend/pro/monaco-editor/diff-editor/monaco-editor.diff-editor.tsx create mode 100644 frontend/pro/monaco-editor/diff-editor/package.json create mode 100644 frontend/pro/monaco-editor/gradio.config.js create mode 100644 frontend/pro/monaco-editor/loader.ts create mode 100644 frontend/pro/monaco-editor/monaco-editor.less create mode 100644 frontend/pro/monaco-editor/monaco-editor.tsx create mode 100644 frontend/pro/monaco-editor/package.json diff --git a/backend/modelscope_studio/components/base/text/__init__.py b/backend/modelscope_studio/components/base/text/__init__.py index 47137786..ba51d557 100644 --- a/backend/modelscope_studio/components/base/text/__init__.py +++ b/backend/modelscope_studio/components/base/text/__init__.py @@ -27,14 +27,14 @@ def __init__( elem_style: dict | None = None, render: bool = True, **kwargs): - super().__init__(visible=visible, + super().__init__(value=value, + visible=visible, elem_id=elem_id, elem_classes=elem_classes, render=render, as_item=as_item, elem_style=elem_style, **kwargs) - self.value = value FRONTEND_DIR = resolve_frontend_dir("text", type="base") diff --git a/backend/modelscope_studio/components/pro/__init__.py b/backend/modelscope_studio/components/pro/__init__.py index f8fbb5ca..15fbabe7 100644 --- a/backend/modelscope_studio/components/pro/__init__.py +++ b/backend/modelscope_studio/components/pro/__init__.py @@ -1,3 +1,6 @@ from .chatbot import ModelScopeProChatbot as Chatbot +from .monaco_editor import ModelScopeProMonacoEditor as MonacoEditor +from .monaco_editor.diff_editor import \ + ModelScopeProMonacoEditorDiffEditor as MonacoEditorDiffEditor from .multimodal_input import ModelScopeProMultimodalInput as MultimodalInput from .web_sandbox import ModelScopeProWebSandbox as WebSandbox diff --git a/backend/modelscope_studio/components/pro/components.py b/backend/modelscope_studio/components/pro/components.py index 5575305d..34c68c90 100644 --- a/backend/modelscope_studio/components/pro/components.py +++ b/backend/modelscope_studio/components/pro/components.py @@ -1,4 +1,7 @@ from .chatbot import ModelScopeProChatbot as ProChatbot +from .monaco_editor import ModelScopeProMonacoEditor as ProMonacoEditor +from .monaco_editor.diff_editor import \ + ModelScopeProMonacoEditorDiffEditor as ProMonacoEditorDiffEditor from .multimodal_input import \ ModelScopeProMultimodalInput as ProMultimodalInput from .web_sandbox import ModelScopeProWebSandbox as ProWebSandbox diff --git a/backend/modelscope_studio/components/pro/monaco_editor/__init__.py b/backend/modelscope_studio/components/pro/monaco_editor/__init__.py new file mode 100644 index 00000000..bcfa71a8 --- /dev/null +++ b/backend/modelscope_studio/components/pro/monaco_editor/__init__.py @@ -0,0 +1,98 @@ +from __future__ import annotations + +from typing import Any, Literal, TypedDict, Union + +from gradio.events import EventListener + +from ....utils.dev import ModelScopeDataLayoutComponent, resolve_frontend_dir +from .diff_editor import ModelScopeProMonacoEditorDiffEditor + + +class LoaderConfig(TypedDict): + mode: Literal['cdn', 'local'] | None = None + cdn_url: str | None = None + + +class ModelScopeProMonacoEditor(ModelScopeDataLayoutComponent): + """ + """ + DiffEditor = ModelScopeProMonacoEditorDiffEditor + + EVENTS = [ + EventListener("mount", + callback=lambda block: block._internal.update( + bind_mount_event=True)), + EventListener("change", + callback=lambda block: block._internal.update( + bind_change_event=True)), + EventListener("validate", + callback=lambda block: block._internal.update( + bind_validate_event=True)), + ] + + # supported slots + SLOTS = ["loading"] + + # or { "mode": "cdn" }, default cdn: https://cdn.jsdelivr.net/npm/monaco-editor@xxx/min/vs + LOADER: Union[LoaderConfig, dict, None] = {"mode": "local"} + + def __init__( + self, + value: str | None = None, + *, + # python, javascript etc. + language: str | None = None, + before_mount: str | None = None, + after_mount: str | None = None, + override_services: dict | None = None, + loading: str | None = None, + options: dict | None = None, + line: int | None = None, + height: str | int | float | None = 400, + _loader: None = None, + _internal: None = None, + # gradio properties + visible: bool = True, + elem_id: str | None = None, + elem_classes: list[str] | str | None = None, + elem_style: dict | None = None, + render: bool = True, + **kwargs): + super().__init__(visible=visible, + value=value, + elem_id=elem_id, + elem_classes=elem_classes, + render=render, + elem_style=elem_style, + **kwargs) + self.line = line + self.loading = loading + print('loader', ModelScopeProMonacoEditor.LOADER) + self._loader = ModelScopeProMonacoEditor.LOADER + self.override_services = override_services + self.options = options + self.before_mount = before_mount + self.after_mount = after_mount + self.language = language + self.height = height + + FRONTEND_DIR = resolve_frontend_dir("monaco-editor", type='pro') + + @property + def skip_api(self): + return False + + def api_info(self) -> dict[str, Any]: + return {"type": "string"} + + def preprocess(self, payload: None | str) -> None | str: + return payload + + def postprocess(self, value: None | str) -> None | str: + return value + + def example_payload(self) -> Any: + return None + + def example_value(self) -> Any: + return None diff --git a/backend/modelscope_studio/components/pro/monaco_editor/diff_editor/__init__.py b/backend/modelscope_studio/components/pro/monaco_editor/diff_editor/__init__.py new file mode 100644 index 00000000..a86000bb --- /dev/null +++ b/backend/modelscope_studio/components/pro/monaco_editor/diff_editor/__init__.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +from typing import Any + +from gradio.events import EventListener + +from .....utils.dev import ModelScopeDataLayoutComponent, resolve_frontend_dir + + +class ModelScopeProMonacoEditorDiffEditor(ModelScopeDataLayoutComponent): + """ + """ + + EVENTS = [ + EventListener("mount", + callback=lambda block: block._internal.update( + bind_mount_event=True)), + EventListener("change", + callback=lambda block: block._internal.update( + bind_change_event=True)), + EventListener("validate", + callback=lambda block: block._internal.update( + bind_validate_event=True)), + ] + + # supported slots + SLOTS = ["loading"] + + def __init__( + self, + value: str | None = None, + *, + original: str | None = None, + # python, javascript etc. + language: str | None = None, + original_language: str | None = None, + modified_language: str | None = None, + before_mount: str | None = None, + after_mount: str | None = None, + override_services: dict | None = None, + loading: str | None = None, + options: dict | None = None, + line: int | None = None, + height: str | int | float | None = 400, + _loader: None = None, + _internal: None = None, + # gradio properties + visible: bool = True, + elem_id: str | None = None, + elem_classes: list[str] | str | None = None, + elem_style: dict | None = None, + render: bool = True, + **kwargs): + super().__init__(value=value, + visible=visible, + elem_id=elem_id, + elem_classes=elem_classes, + render=render, + elem_style=elem_style, + **kwargs) + from .. import ModelScopeProMonacoEditor + self._loader = ModelScopeProMonacoEditor.LOADER + self.original = original + self.line = line + self.loading = loading + self.override_services = override_services + self.options = options + self.before_mount = before_mount + self.after_mount = after_mount + self.language = language + self.original_language = original_language + self.modified_language = modified_language + self.height = height + + FRONTEND_DIR = resolve_frontend_dir("monaco-editor", + 'diff-editor', + type='pro') + + @property + def skip_api(self): + return False + + def api_info(self) -> dict[str, Any]: + return {"type": "string"} + + def preprocess(self, payload: None | str) -> None | str: + return payload + + def postprocess(self, value: None | str) -> None | str: + return value + + def example_payload(self) -> Any: + return None + + def example_value(self) -> Any: + return None diff --git a/frontend/defineConfig.js b/frontend/defineConfig.js index e05a0aa1..c71fa52a 100644 --- a/frontend/defineConfig.js +++ b/frontend/defineConfig.js @@ -3,7 +3,7 @@ import react from '@vitejs/plugin-react-swc'; import { ModelScopeStudioVitePlugin } from './plugin.js'; /** - * @type {(options:{ external?:boolean; }) => any} + * @type {(options:{ external?: boolean | { excludes:string[] } }) => any} */ export default ({ external } = { external: true }) => { return { diff --git a/frontend/package.json b/frontend/package.json index 0f2d64a5..89520bfc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "@gradio/statustracker": "^0.7.6", "@gradio/upload": "0.11.2", "@gradio/utils": "0.9.0", + "@monaco-editor/react": "^4.7.0", "amuchina": "^1.0.12", "antd": "^5.28.0", "classnames": "^2.5.1", @@ -31,6 +32,7 @@ "marked-gfm-heading-id": "^4.1.2", "marked-highlight": "^2.2.2", "mermaid": "^11.12.1", + "monaco-editor": "^0.54.0", "path-browserify-esm": "^1.0.6", "prismjs": "^1.30.0", "react": "^18.3.1", diff --git a/frontend/plugin.js b/frontend/plugin.js index abb0238e..2b713c7a 100644 --- a/frontend/plugin.js +++ b/frontend/plugin.js @@ -3,7 +3,7 @@ import fg from 'fast-glob'; import path from 'node:path'; import url from 'node:url'; -const globals = { +const baseGlobals = { react: 'window.ms_globals.React', 'react-dom': 'window.ms_globals.ReactDOM', 'react-dom/client': 'window.ms_globals.ReactDOMClient', @@ -15,6 +15,7 @@ const globals = { dayjs: 'window.ms_globals.dayjs', '@utils/createItemsContext': 'window.ms_globals.createItemsContext', '@globals/components': 'window.ms_globals.components', + '@monaco-editor/loader': 'window.ms_globals.monacoLoader', }; const dirname = path.dirname(url.fileURLToPath(import.meta.url)); @@ -46,9 +47,17 @@ function generateSveltePreprocessReactAliases() { } /** - * @type {(options:{ external?:boolean }) => import('vite').Plugin} + * @type {(options:{ external?: { excludes:string[] } | boolean }) => import('vite').Plugin} */ export const ModelScopeStudioVitePlugin = ({ external = true } = {}) => { + const globals = external?.excludes + ? Object.keys(baseGlobals).reduce((aliases, name) => { + if (!external.excludes.includes(name)) { + aliases[name] = baseGlobals[name]; + } + return aliases; + }, {}) + : baseGlobals; return { name: 'modelscope-studio-vite-plugin', config(userConfig, { command }) { diff --git a/frontend/pro/monaco-editor/Index.svelte b/frontend/pro/monaco-editor/Index.svelte new file mode 100644 index 00000000..46d1b233 --- /dev/null +++ b/frontend/pro/monaco-editor/Index.svelte @@ -0,0 +1,108 @@ + + + + +{#if $mergedProps.visible} + {#if _loader?.mode === 'local'} + {#await initLocalLoader()} + + {:then} + {#await AwaitedMonacoEditor then MonacoEditor} + + + + {/await} + {/await} + {:else if _loader?.cdn_url} + {#await initCDNLoader(_loader.cdn_url)} + {#await AwaitedMonacoEditor then MonacoEditor} + + + + {/await} + {/await} + {:else} + {#await AwaitedMonacoEditor then MonacoEditor} + + + + {/await} + {/if} +{/if} diff --git a/frontend/pro/monaco-editor/diff-editor/Index.svelte b/frontend/pro/monaco-editor/diff-editor/Index.svelte new file mode 100644 index 00000000..07c498f6 --- /dev/null +++ b/frontend/pro/monaco-editor/diff-editor/Index.svelte @@ -0,0 +1,110 @@ + + + + +{#if $mergedProps.visible} + {#if _loader?.mode === 'local'} + {#await initLocalLoader()} + + {:then} + {#await AwaitedMonacoDiffEditor then MonacoDiffEditor} + + + + {/await} + {/await} + {:else if _loader?.cdn_url} + {#await initCDNLoader(_loader.cdn_url)} + {#await AwaitedMonacoDiffEditor then MonacoDiffEditor} + + + + {/await} + {/await} + {:else} + {#await AwaitedMonacoDiffEditor then MonacoDiffEditor} + + + + {/await} + {/if} +{/if} diff --git a/frontend/pro/monaco-editor/diff-editor/gradio.config.js b/frontend/pro/monaco-editor/diff-editor/gradio.config.js new file mode 100644 index 00000000..80740416 --- /dev/null +++ b/frontend/pro/monaco-editor/diff-editor/gradio.config.js @@ -0,0 +1,7 @@ +import config from '../../../defineConfig.js'; + +export default config({ + external: { + excludes: ['@monaco-editor/loader'], + }, +}); diff --git a/frontend/pro/monaco-editor/diff-editor/monaco-editor.diff-editor.tsx b/frontend/pro/monaco-editor/diff-editor/monaco-editor.diff-editor.tsx new file mode 100644 index 00000000..08c5678d --- /dev/null +++ b/frontend/pro/monaco-editor/diff-editor/monaco-editor.diff-editor.tsx @@ -0,0 +1,127 @@ +import { + DiffEditor, + type DiffEditorProps, + type OnValidate, +} from '@monaco-editor/react'; +import { ReactSlot } from '@svelte-preprocess-react/react-slot'; +import { sveltify } from '@svelte-preprocess-react/sveltify'; +import React, { useEffect } from 'react'; +import { useFunction } from '@utils/hooks/useFunction'; +import { Spin } from 'antd'; +import type { editor, IDisposable } from 'monaco-editor'; + +import '../monaco-editor.less'; + +export interface MonacoDiffEditorProps extends DiffEditorProps { + themeMode?: string; + height?: string | number; + children?: React.ReactNode; + value?: string; + onValueChange: (value: string | undefined) => void; + onChange?: ( + value: string | undefined, + ev: editor.IModelContentChangedEvent + ) => void; + onValidate?: OnValidate; + afterMount?: DiffEditorProps['onMount']; +} + +export const MonacoDiffEditor = sveltify( + ({ + height, + className, + style, + themeMode, + slots, + beforeMount, + afterMount, + children, + onMount, + onChange, + onValueChange, + onValidate, + value, + modified, + ...props + }) => { + const beforeMountFunction = useFunction(beforeMount); + const afterMountFunction = useFunction(afterMount); + const disposablesRef = React.useRef([]); + + const handleEditorMount: MonacoDiffEditorProps['onMount'] = ( + editor, + monaco + ) => { + const modifiedEditor = editor.getModifiedEditor(); + const mountDisposable = modifiedEditor.onDidChangeModelContent((e) => { + const newValue = modifiedEditor.getValue(); + onValueChange(newValue); + onChange?.(newValue, e); + }); + + const validateDisposable = monaco.editor.onDidChangeMarkers((uris) => { + const editorUri = modifiedEditor.getModel()?.uri; + if (editorUri) { + const currentEditorHasMarkerChanges = uris.find( + (uri) => uri.path === editorUri.path + ); + if (currentEditorHasMarkerChanges) { + const markers = monaco.editor.getModelMarkers({ + resource: editorUri, + }); + onValidate?.(markers); + } + } + }); + disposablesRef.current.push(mountDisposable, validateDisposable); + }; + + useEffect(() => { + return () => { + // eslint-disable-next-line react-hooks/exhaustive-deps + disposablesRef.current.forEach((disposable) => { + disposable.dispose(); + }); + }; + }, []); + return ( + <> +
{children}
+
+ { + handleEditorMount(editor, monaco); + onMount?.(editor, monaco); + afterMountFunction?.(editor, monaco); + }} + loading={ + slots.loading ? ( + + ) : ( + props.loading || ( + +
+ + ) + ) + } + theme={themeMode === 'dark' ? 'vs-dark' : 'light'} + /> +
+ + ); + } +); +export default MonacoDiffEditor; diff --git a/frontend/pro/monaco-editor/diff-editor/package.json b/frontend/pro/monaco-editor/diff-editor/package.json new file mode 100644 index 00000000..409afa1f --- /dev/null +++ b/frontend/pro/monaco-editor/diff-editor/package.json @@ -0,0 +1,14 @@ +{ + "name": "@modelscope-studio/monaco-diff-editor", + "type": "module", + "exports": { + ".": { + "default": "./Index.svelte", + "gradio": "./Index.svelte" + }, + "./package.json": { + "default": "./package.json", + "gradio": "./package.json" + } + } +} diff --git a/frontend/pro/monaco-editor/gradio.config.js b/frontend/pro/monaco-editor/gradio.config.js new file mode 100644 index 00000000..6641fe8e --- /dev/null +++ b/frontend/pro/monaco-editor/gradio.config.js @@ -0,0 +1,7 @@ +import config from '../../defineConfig.js'; + +export default config({ + external: { + excludes: ['@monaco-editor/loader'], + }, +}); diff --git a/frontend/pro/monaco-editor/loader.ts b/frontend/pro/monaco-editor/loader.ts new file mode 100644 index 00000000..e4524279 --- /dev/null +++ b/frontend/pro/monaco-editor/loader.ts @@ -0,0 +1,99 @@ +function getMonacoLoader() { + return new Promise<{ + loader: typeof window.ms_globals.monacoLoader; + done?: () => void; + }>((resolve) => { + if (!window.ms_globals?.monacoLoader) { + if (window.ms_globals?.monacoLoaderPromise) { + window.ms_globals.monacoLoaderPromise.then((loader) => { + resolve({ + loader, + }); + }); + } else { + window.ms_globals ??= {} as typeof window.ms_globals; + window.ms_globals.monacoLoaderPromise = new Promise((resolve2) => { + import('@monaco-editor/react').then((m) => { + window.ms_globals.monacoLoader = m.loader; + resolve({ + loader: m.loader, + done: () => resolve2(m.loader), + }); + }); + }); + } + } else { + resolve({ + loader: window.ms_globals.monacoLoader, + }); + } + }); +} +export async function initLocalLoader() { + const { loader: monacoLoader, done } = await getMonacoLoader(); + + if (!done) { + return; + } + const [monaco, editorWorker, cssWorker, htmlWorker, jsonWorker, tsWorker] = + await Promise.all([ + import('monaco-editor'), + import('monaco-editor/esm/vs/editor/editor.worker?worker').then( + (m) => m.default + ), + import('monaco-editor/esm/vs/language/css/css.worker?worker').then( + (m) => m.default + ), + import('monaco-editor/esm/vs/language/html/html.worker?worker').then( + (m) => m.default + ), + import('monaco-editor/esm/vs/language/json/json.worker?worker').then( + (m) => m.default + ), + import('monaco-editor/esm/vs/language/typescript/ts.worker?worker').then( + (m) => m.default + ), + ]); + + window.MonacoEnvironment = { + getWorker(_, label) { + if (label === 'json') { + return new jsonWorker(); + } + if (label === 'css' || label === 'scss' || label === 'less') { + return new cssWorker(); + } + if (label === 'html' || label === 'handlebars' || label === 'razor') { + return new htmlWorker(); + } + if (label === 'typescript' || label === 'javascript') { + return new tsWorker(); + } + return new editorWorker(); + }, + }; + + if (monacoLoader) { + monacoLoader.config({ + monaco, + }); + } + + done(); +} + +export async function initCDNLoader(cdn: string) { + const { loader: monacoLoader, done } = await getMonacoLoader(); + if (!done) { + return; + } + + if (monacoLoader) { + monacoLoader.config({ + paths: { + vs: cdn, + }, + }); + } + done(); +} diff --git a/frontend/pro/monaco-editor/monaco-editor.less b/frontend/pro/monaco-editor/monaco-editor.less new file mode 100644 index 00000000..23166046 --- /dev/null +++ b/frontend/pro/monaco-editor/monaco-editor.less @@ -0,0 +1,6 @@ +.ms-gr-pro-monaco-editor { + &-spin { + width: 100%; + height: 100%; + } +} diff --git a/frontend/pro/monaco-editor/monaco-editor.tsx b/frontend/pro/monaco-editor/monaco-editor.tsx new file mode 100644 index 00000000..bd67b2d8 --- /dev/null +++ b/frontend/pro/monaco-editor/monaco-editor.tsx @@ -0,0 +1,80 @@ +import { Editor, type EditorProps } from '@monaco-editor/react'; +import { ReactSlot } from '@svelte-preprocess-react/react-slot'; +import { sveltify } from '@svelte-preprocess-react/sveltify'; +import React from 'react'; +import { useFunction } from '@utils/hooks/useFunction'; +import { Spin } from 'antd'; + +import './monaco-editor.less'; + +export interface MonacoEditorProps extends EditorProps { + themeMode?: string; + height?: string | number; + onValueChange: (value: string | undefined) => void; + children?: React.ReactNode; + afterMount?: EditorProps['onMount']; +} + +export const MonacoEditor = sveltify( + ({ + height, + value, + className, + style, + themeMode, + onValueChange, + onChange, + slots, + beforeMount, + afterMount, + children, + onMount, + ...props + }) => { + const beforeMountFunction = useFunction(beforeMount); + const afterMountFunction = useFunction(afterMount); + + return ( + <> +
{children}
+
+ { + onMount?.(...args); + afterMountFunction?.(...args); + }} + loading={ + slots.loading ? ( + + ) : ( + props.loading || ( + +
+ + ) + ) + } + onChange={(v, ev) => { + onValueChange(v); + onChange?.(v, ev); + }} + theme={themeMode === 'dark' ? 'vs-dark' : 'light'} + /> +
+ + ); + } +); +export default MonacoEditor; diff --git a/frontend/pro/monaco-editor/package.json b/frontend/pro/monaco-editor/package.json new file mode 100644 index 00000000..2a005ee9 --- /dev/null +++ b/frontend/pro/monaco-editor/package.json @@ -0,0 +1,14 @@ +{ + "name": "@modelscope-studio/monaco-editor", + "type": "module", + "exports": { + ".": { + "default": "./Index.svelte", + "gradio": "./Index.svelte" + }, + "./package.json": { + "default": "./package.json", + "gradio": "./package.json" + } + } +} diff --git a/frontend/svelte-preprocess-react/inject.ts b/frontend/svelte-preprocess-react/inject.ts index ed58abac..5233e777 100644 --- a/frontend/svelte-preprocess-react/inject.ts +++ b/frontend/svelte-preprocess-react/inject.ts @@ -1,3 +1,4 @@ +import type { loader as monacoLoader } from '@monaco-editor/react'; import React from 'react'; import ReactDOM from 'react-dom'; import ReactDOMClient from 'react-dom/client'; @@ -8,6 +9,7 @@ import * as globalComponents from '@globals/components'; import * as createItemsContext from '@utils/createItemsContext'; import * as antd from 'antd'; import dayjs from 'dayjs'; +import { noop } from 'lodash-es'; import { type Readable, type Writable, writable } from 'svelte/store'; import './events-polyfills'; @@ -31,7 +33,7 @@ declare global { antdIcons: typeof antdIcons; dayjs: typeof dayjs; internalContext: typeof internalContext; - initializePromise: Promise; + initializePromise: Promise | null; initialize: () => void; tickPromise: Promise | null; // svelte-preprocess-react @@ -39,6 +41,8 @@ declare global { tree: TreeNode; autokey: number; loadingKey: number; + monacoLoader: typeof monacoLoader | null; + monacoLoaderPromise: Promise | null; rerender: (props: BridgeProps) => void; // render items createItemsContext: typeof createItemsContext; @@ -68,8 +72,12 @@ const rerender = (props: BridgeProps) => { }; window.ms_globals ??= {} as typeof window.ms_globals; + window.ms_globals = { - ...window.ms_globals, + dispatch: noop, + initialize: noop, + tickPromise: null, + initializePromise: null, React, ReactDOM, ReactDOMClient, @@ -95,10 +103,13 @@ window.ms_globals = { nodes: [], }, rerender, + monacoLoader: null, + monacoLoaderPromise: null, // render items createItemsContext, itemsContexts: {}, components: globalComponents, + ...(window.ms_globals as Partial), }; // register custom elements customElements.define('react-portal-target', class extends HTMLElement {}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 418b75f6..203961a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -220,6 +220,9 @@ importers: '@gradio/utils': specifier: 0.9.0 version: 0.9.0(svelte@4.2.20) + '@monaco-editor/react': + specifier: ^4.7.0 + version: 4.7.0(monaco-editor@0.54.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) amuchina: specifier: ^1.0.12 version: 1.0.12 @@ -259,6 +262,9 @@ importers: mermaid: specifier: ^11.12.1 version: 11.12.1 + monaco-editor: + specifier: ^0.54.0 + version: 0.54.0 path-browserify-esm: specifier: ^1.0.6 version: 1.0.6 @@ -1736,6 +1742,16 @@ packages: '@microsoft/tsdoc@0.15.1': resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + '@monaco-editor/loader@1.6.1': + resolution: {integrity: sha512-w3tEnj9HYEC73wtjdpR089AqkUPskFRcdkxsiSFt3SoUc3OHpmu+leP94CXBm4mHfefmhsdfI0ZQu6qJ0wgtPg==} + + '@monaco-editor/react@4.7.0': + resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==} + peerDependencies: + monaco-editor: '>= 0.25.0 < 1' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@mrmlnc/readdir-enhanced@2.2.1': resolution: {integrity: sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==} engines: {node: '>=4'} @@ -3785,6 +3801,9 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} + dompurify@3.1.7: + resolution: {integrity: sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==} + dompurify@3.2.5: resolution: {integrity: sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==} @@ -5534,6 +5553,11 @@ packages: peerDependencies: marked: '>=4 <17' + marked@14.0.0: + resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} + engines: {node: '>= 18'} + hasBin: true + marked@16.4.1: resolution: {integrity: sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg==} engines: {node: '>= 20'} @@ -5804,6 +5828,9 @@ packages: mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + monaco-editor@0.54.0: + resolution: {integrity: sha512-hx45SEUoLatgWxHKCmlLJH81xBo0uXP4sRkESUpmDQevfi+e7K1VuiSprK6UpQ8u4zOcKNiH0pMvHvlMWA/4cw==} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -7133,6 +7160,7 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions space-separated-tokens@1.1.5: resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==} @@ -7174,6 +7202,9 @@ packages: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} engines: {node: '>=12.0.0'} + state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + state-toggle@1.0.3: resolution: {integrity: sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==} @@ -9538,6 +9569,17 @@ snapshots: '@microsoft/tsdoc@0.15.1': optional: true + '@monaco-editor/loader@1.6.1': + dependencies: + state-local: 1.0.7 + + '@monaco-editor/react@4.7.0(monaco-editor@0.54.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@monaco-editor/loader': 1.6.1 + monaco-editor: 0.54.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@mrmlnc/readdir-enhanced@2.2.1': dependencies: call-me-maybe: 1.0.2 @@ -10250,7 +10292,7 @@ snapshots: '@types/dompurify@3.2.0': dependencies: - dompurify: 3.2.5 + dompurify: 3.3.0 '@types/eslint@9.6.1': dependencies: @@ -11642,6 +11684,8 @@ snapshots: dependencies: domelementtype: 2.3.0 + dompurify@3.1.7: {} + dompurify@3.2.5: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -13692,6 +13736,8 @@ snapshots: dependencies: marked: 16.4.1 + marked@14.0.0: {} + marked@16.4.1: {} math-intrinsics@1.1.0: {} @@ -14254,6 +14300,11 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.1 + monaco-editor@0.54.0: + dependencies: + dompurify: 3.1.7 + marked: 14.0.0 + mri@1.2.0: {} ms@2.0.0: {} @@ -15889,6 +15940,8 @@ snapshots: stable-hash-x@0.2.0: {} + state-local@1.0.7: {} + state-toggle@1.0.3: {} static-extend@0.1.2: diff --git a/pyproject.toml b/pyproject.toml index 3911d9bc..5516bc54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -230,6 +230,7 @@ artifacts = [ "/backend/modelscope_studio/components/pro/multimodal_input/templates", "/backend/modelscope_studio/components/pro/web_sandbox/templates", "/backend/modelscope_studio/components/antd/statistic/timer/templates", + "/backend/modelscope_studio/components/pro/monaco_editor/templates", ] [tool.yapfignore] From b908822c26df7f77c665836db392ac11783a3ebe Mon Sep 17 00:00:00 2001 From: Col0ring <1561999073@qq.com> Date: Mon, 10 Nov 2025 14:41:15 +0800 Subject: [PATCH 2/7] fix: types --- .../components/pro/monaco_editor/__init__.py | 1 - frontend/fixtures.d.ts | 7 +++++++ frontend/svelte-preprocess-react/sveltify.ts | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/backend/modelscope_studio/components/pro/monaco_editor/__init__.py b/backend/modelscope_studio/components/pro/monaco_editor/__init__.py index bcfa71a8..c662b098 100644 --- a/backend/modelscope_studio/components/pro/monaco_editor/__init__.py +++ b/backend/modelscope_studio/components/pro/monaco_editor/__init__.py @@ -67,7 +67,6 @@ def __init__( **kwargs) self.line = line self.loading = loading - print('loader', ModelScopeProMonacoEditor.LOADER) self._loader = ModelScopeProMonacoEditor.LOADER self.override_services = override_services self.options = options diff --git a/frontend/fixtures.d.ts b/frontend/fixtures.d.ts index ee80763b..32078f08 100644 --- a/frontend/fixtures.d.ts +++ b/frontend/fixtures.d.ts @@ -1,6 +1,13 @@ declare module 'virtual:component-loader' { export const load_component; } + +declare module '*?worker' { + const workerConstructor: { + new (options?: { name?: string }): Worker; + }; + export default workerConstructor; +} declare module '*?inline' { const string: string; export default string; diff --git a/frontend/svelte-preprocess-react/sveltify.ts b/frontend/svelte-preprocess-react/sveltify.ts index 1fe3ec21..27175e69 100644 --- a/frontend/svelte-preprocess-react/sveltify.ts +++ b/frontend/svelte-preprocess-react/sveltify.ts @@ -87,6 +87,13 @@ export function sveltify( } return new Promise((resolve) => { + if (!window.ms_globals.initializePromise) { + window.ms_globals.initializePromise = new Promise((r) => { + window.ms_globals.initialize = () => { + r(); + }; + }); + } window.ms_globals.initializePromise.then(() => { resolve(Sveltified as any); }); From 7ac243ad2adbc35d715728ca745db5d634ca0180 Mon Sep 17 00:00:00 2001 From: Col0ring <1561999073@qq.com> Date: Tue, 11 Nov 2025 13:57:27 +0800 Subject: [PATCH 3/7] docs: add docs & examples for MonacoEditor --- .../components/pro/monaco_editor/__init__.py | 25 ++++-- .../pro/monaco_editor/diff_editor/__init__.py | 23 +++-- docs/app.py | 3 + .../pro/monaco_editor/README-zh_CN.md | 88 +++++++++++++++++++ docs/components/pro/monaco_editor/README.md | 88 +++++++++++++++++++ docs/components/pro/monaco_editor/app.py | 6 ++ .../pro/monaco_editor/demos/basic.py | 33 +++++++ .../pro/monaco_editor/demos/diff_editor.py | 43 +++++++++ .../demos/javascript_customize.py | 35 ++++++++ .../demos/monaco_editor_options.py | 33 +++++++ 10 files changed, 362 insertions(+), 15 deletions(-) create mode 100644 docs/components/pro/monaco_editor/README-zh_CN.md create mode 100644 docs/components/pro/monaco_editor/README.md create mode 100644 docs/components/pro/monaco_editor/app.py create mode 100644 docs/components/pro/monaco_editor/demos/basic.py create mode 100644 docs/components/pro/monaco_editor/demos/diff_editor.py create mode 100644 docs/components/pro/monaco_editor/demos/javascript_customize.py create mode 100644 docs/components/pro/monaco_editor/demos/monaco_editor_options.py diff --git a/backend/modelscope_studio/components/pro/monaco_editor/__init__.py b/backend/modelscope_studio/components/pro/monaco_editor/__init__.py index c662b098..837d6db2 100644 --- a/backend/modelscope_studio/components/pro/monaco_editor/__init__.py +++ b/backend/modelscope_studio/components/pro/monaco_editor/__init__.py @@ -21,13 +21,20 @@ class ModelScopeProMonacoEditor(ModelScopeDataLayoutComponent): EVENTS = [ EventListener("mount", callback=lambda block: block._internal.update( - bind_mount_event=True)), - EventListener("change", - callback=lambda block: block._internal.update( - bind_change_event=True)), - EventListener("validate", - callback=lambda block: block._internal.update( - bind_validate_event=True)), + bind_mount_event=True), + doc="An event is emitted when the editor is mounted."), + EventListener( + "change", + callback=lambda block: block._internal.update(bind_change_event= + True), + doc="An event is emitted when the editor value is changed."), + EventListener( + "validate", + callback=lambda block: block._internal.update(bind_validate_event= + True), + doc= + "An event is emitted when the editor value is changed and the validation markers are ready." + ), ] # supported slots @@ -45,9 +52,10 @@ def __init__( before_mount: str | None = None, after_mount: str | None = None, override_services: dict | None = None, - loading: str | None = None, + loading: str | None = "Editor is loading...", options: dict | None = None, line: int | None = None, + read_only: bool | None = None, height: str | int | float | None = 400, _loader: None = None, _internal: None = None, @@ -70,6 +78,7 @@ def __init__( self._loader = ModelScopeProMonacoEditor.LOADER self.override_services = override_services self.options = options + self.read_only = read_only self.before_mount = before_mount self.after_mount = after_mount self.language = language diff --git a/backend/modelscope_studio/components/pro/monaco_editor/diff_editor/__init__.py b/backend/modelscope_studio/components/pro/monaco_editor/diff_editor/__init__.py index a86000bb..99482ea1 100644 --- a/backend/modelscope_studio/components/pro/monaco_editor/diff_editor/__init__.py +++ b/backend/modelscope_studio/components/pro/monaco_editor/diff_editor/__init__.py @@ -14,13 +14,20 @@ class ModelScopeProMonacoEditorDiffEditor(ModelScopeDataLayoutComponent): EVENTS = [ EventListener("mount", callback=lambda block: block._internal.update( - bind_mount_event=True)), - EventListener("change", - callback=lambda block: block._internal.update( - bind_change_event=True)), - EventListener("validate", - callback=lambda block: block._internal.update( - bind_validate_event=True)), + bind_mount_event=True), + doc="An event is emitted when the editor is mounted."), + EventListener( + "change", + callback=lambda block: block._internal.update(bind_change_event= + True), + doc="An event is emitted when the editor value is changed."), + EventListener( + "validate", + callback=lambda block: block._internal.update(bind_validate_event= + True), + doc= + "An event is emitted when the editor value is changed and the validation markers are ready." + ), ] # supported slots @@ -39,6 +46,7 @@ def __init__( after_mount: str | None = None, override_services: dict | None = None, loading: str | None = None, + read_only: bool | None = None, options: dict | None = None, line: int | None = None, height: str | int | float | None = 400, @@ -65,6 +73,7 @@ def __init__( self.loading = loading self.override_services = override_services self.options = options + self.read_only = read_only self.before_mount = before_mount self.after_mount = after_mount self.language = language diff --git a/docs/app.py b/docs/app.py index 3338b0d9..a185c0d3 100644 --- a/docs/app.py +++ b/docs/app.py @@ -181,6 +181,9 @@ def render(self): "children": [{ "label": get_text("WebSandbox", "WebSandbox 网页沙盒"), "key": "web_sandbox" + }, { + "label": get_text("MonacoEditor", "MonacoEditor 代码编辑器"), + "key": "monaco_editor" }] }] diff --git a/docs/components/pro/monaco_editor/README-zh_CN.md b/docs/components/pro/monaco_editor/README-zh_CN.md new file mode 100644 index 00000000..7e7891e3 --- /dev/null +++ b/docs/components/pro/monaco_editor/README-zh_CN.md @@ -0,0 +1,88 @@ +# MonacoEditor + +代码编辑器,[Monaco Editor](https://microsoft.github.io/monaco-editor/) 的 Gradio 实现,基于 [@monaco-editor/react](https://github.com/suren-atoyan/monaco-react) 集成。 + +## 示例 + +### 基本使用 + + + +### Diff 编辑器 + + + +### Monaco Editor 配置项 + +可以直接传入 Monaco Editor 的配置项 [IStandaloneEditorConstructionOptions](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IStandaloneEditorConstructionOptions.html)。 + +在这个示例中,我们关闭了编辑器的`minimap`与 `lineNumbers`。 + + + +### 通过 JavaScript 定制化配置 + +如果你还需要更进一步地操作 Monaco Editor,你可以通过传入 JavaScript 函数字符串来进一步定制化配置。 + + + +## API  + +### 属性 + +#### MonacoEditor + +| 属性 | 类型 | 默认值 | 描述 | +| ---------------- | --------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| value | `str\| None` | None | 编辑器的值。 | +| language | `str\| None` | None | 编辑器的语言(monaco-editor [支持](https://github.com/microsoft/monaco-editor/tree/main/src/basic-languages)的所有语言)。 | +| line | `number\| None` | None | 垂直滚动编辑器到指定行。 | +| read_only | `bool\| None` | None | 编辑器是否只读。 | +| loading | `str\| None` | 'Editor is loading...' | 编辑器初始化加载时的加载文案。 | +| options | `dict \| None` | None | [IStandaloneEditorConstructionOptions](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IStandaloneEditorConstructionOptions.html) | +| overrideServices | `dict \| None` | None | [IEditorOverrideServices](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IEditorOverrideServices.html) | +| height | `str \| float \| int` | 400 | 组件的高度,如果值为数字,则以像素为单位指定,如果传递的是字符串,则以 CSS 单位指定。 | +| before_mount | `str \| None` | None | 传入 JavaScript 函数字符串,在编辑器加载前执行,可以获取到 `monaco` 对象。 | +| after_mount | `str \| None` | None | 传入 JavaScript 函数字符串,在编辑器加载后执行,可以获取到`editor`对象与`monaco` 对象。 | + +#### MonacoEditor.DiffEditor + +| 属性 | 类型 | 默认值 | 描述 | +| ----------------- | --------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| value | `str\| None` | None | 修改后的源的值(右侧)。 | +| original | `str\| None` | None | 原始源的值(左侧)。 | +| language | `str\| None` | None | 编辑器的语言(monaco-editor [支持](https://github.com/microsoft/monaco-editor/tree/main/src/basic-languages)的所有语言)。 | +| original_language | `str\| None` | None | 单独指定原始源的语言。否则,它将获取 language 属性的值。 | +| modified_Language | `str\| None` | None | 单独指定修改后的源的语言。否则,它将获取 language 属性的值。 | +| line | `number\| None` | None | 垂直滚动编辑器到指定行。 | +| read_only | `bool\| None` | None | 编辑器是否只读。 | +| loading | `str\| None` | 'Editor is loading...' | 编辑器初始化加载时的加载文案。 | +| options | `dict \| None` | None | [IStandaloneEditorConstructionOptions](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IStandaloneEditorConstructionOptions.html) | +| overrideServices | `dict \| None` | None | [IEditorOverrideServices](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IEditorOverrideServices.html) | +| height | `str \| float \| int` | 400 | 组件的高度,如果值为数字,则以像素为单位指定,如果传递的是字符串,则以 CSS 单位指定。 | +| before_mount | `str \| None` | None | 传入 JavaScript 函数字符串,在编辑器加载前执行,可以获取到 `monaco` 对象。 | +| after_mount | `str \| None` | None | 传入 JavaScript 函数字符串,在编辑器加载后执行,可以获取到`editor`对象与`monaco` 对象。 | + +### 事件 + +| 事件 | 描述 | +| ----------------------------------------------------------------- | ---------------------------------------- | +| `pro.(MonacoEditor \| MonacoEditor.DiffEditor).mount(fn, ···)` | 当编辑器加载完成时触发。 | +| `pro.(MonacoEditor \| MonacoEditor.DiffEditor).change(fn, ···)` | 当编辑器值改变时触发。 | +| `pro.(MonacoEditor \| MonacoEditor.DiffEditor).validate(fn, ···)` | 当编辑器触发校验并且校错误记存在时触发。 | + +**注:** 根据 [monaco-editor](https://microsoft.github.io/monaco-editor/),只有具备丰富智能感知的语言才会触发 validate 事件。 + +- TypeScript +- JavaScript +- CSS +- LESS +- SCSS +- JSON +- HTML + +### 插槽 + +```python +SLOTS=['loading'] +``` diff --git a/docs/components/pro/monaco_editor/README.md b/docs/components/pro/monaco_editor/README.md new file mode 100644 index 00000000..314f286f --- /dev/null +++ b/docs/components/pro/monaco_editor/README.md @@ -0,0 +1,88 @@ +# MonacoEditor + +Code editor, Gradio implementation of [Monaco Editor](https://microsoft.github.io/monaco-editor/), integrated based on [@monaco-editor/react](https://github.com/suren-atoyan/monaco-react). + +## Examples + +### Basic Usage + + + +### Diff Editor + + + +### Monaco Editor Options + +You can directly pass in Monaco Editor configuration options [IStandaloneEditorConstructionOptions](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IStandaloneEditorConstructionOptions.html). + +In this example, we disable the `minimap` and `lineNumbers` of the editor. + + + +### Customization via JavaScript + +If you need further customization of Monaco Editor, you can pass in JavaScript function strings for more advanced configuration. + + + +## API + +### Props + +#### MonacoEditor + +| Attribute | Type | Default | Description | +| ---------------- | --------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| value | `str\| None` | None | The value of the editor. | +| language | `str\| None` | None | The language of the editor (all languages [supported](https://github.com/microsoft/monaco-editor/tree/main/src/basic-languages) by monaco-editor). | +| line | `number\| None` | None | Vertically scroll the editor to the specified line. | +| read_only | `bool\| None` | None | Whether the editor is read-only. | +| loading | `str\| None` | 'Editor is loading...' | The loading text when the editor is initializing. | +| options | `dict \| None` | None | [IStandaloneEditorConstructionOptions](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IStandaloneEditorConstructionOptions.html) | +| overrideServices | `dict \| None` | None | [IEditorOverrideServices](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IEditorOverrideServices.html) | +| height | `str \| float \| int` | 400 | The height of the component. If the value is a number, it is specified in pixels. If a string is passed, it is specified in CSS units. | +| before_mount | `str \| None` | None | Pass in a JavaScript function string to execute before the editor loads, which can access the `monaco` object. | +| after_mount | `str \| None` | None | Pass in a JavaScript function string to execute after the editor loads, which can access the `editor` object and `monaco` object. | + +#### MonacoEditor.DiffEditor + +| Property | Type | Default | Description | +| ----------------- | --------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| value | `str\| None` | None | The modified source (right one) value. | +| original | `str\| None` | None | The original source (left one) value. | +| language | `str\| None` | None | The language of the editor (all languages [supported](https://github.com/microsoft/monaco-editor/tree/main/src/basic-languages) by monaco-editor). | +| original_language | `str\| None` | None | Specifies the language of the original source separately. Otherwise, it will take the value of the language property. | +| modified_Language | `str\| None` | None | Specifies the language of the modified source separately. Otherwise, it will take the value of the language property. | +| line | `number\| None` | None | Vertically scroll the editor to the specified line. | +| read_only | `bool\| None` | None | Whether the editor is read-only. | +| loading | `str\| None` | 'Editor is loading...' | The loading text when the editor is initializing. | +| options | `dict \| None` | None | [IStandaloneEditorConstructionOptions](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IStandaloneEditorConstructionOptions.html) | +| overrideServices | `dict \| None` | None | [IEditorOverrideServices](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IEditorOverrideServices.html) | +| height | `str \| float \| int` | 400 | The height of the component. If the value is a number, it is specified in pixels. If a string is passed, it is specified in CSS units. | +| before_mount | `str \| None` | None | Pass in a JavaScript function string to execute before the editor loads, which can access the `monaco` object. | +| after_mount | `str \| None` | None | Pass in a JavaScript function string to execute after the editor loads, which can access the `editor` object and `monaco` object. | + +### Events + +| Event | Description | +| ----------------------------------------------------------------- | ---------------------------------------------------------------------- | +| `pro.(MonacoEditor \| MonacoEditor.DiffEditor).mount(fn, ···)` | Triggered when the editor is mounted. | +| `pro.(MonacoEditor \| MonacoEditor.DiffEditor).change(fn, ···)` | Triggered when the editor value changes. | +| `pro.(MonacoEditor \| MonacoEditor.DiffEditor).validate(fn, ···)` | Triggered when the editor triggers validation and error markers exist. | + +**Note:** According to [monaco-editor](https://microsoft.github.io/monaco-editor/), only languages with rich IntelliSense will trigger the validate event. + +- TypeScript +- JavaScript +- CSS +- LESS +- SCSS +- JSON +- HTML + +### Slots + +```python +SLOTS=['loading'] +``` diff --git a/docs/components/pro/monaco_editor/app.py b/docs/components/pro/monaco_editor/app.py new file mode 100644 index 00000000..0018b7c1 --- /dev/null +++ b/docs/components/pro/monaco_editor/app.py @@ -0,0 +1,6 @@ +from helper.Docs import Docs + +docs = Docs(__file__) + +if __name__ == "__main__": + docs.render().queue().launch() diff --git a/docs/components/pro/monaco_editor/demos/basic.py b/docs/components/pro/monaco_editor/demos/basic.py new file mode 100644 index 00000000..1a75f32b --- /dev/null +++ b/docs/components/pro/monaco_editor/demos/basic.py @@ -0,0 +1,33 @@ +import gradio as gr +import modelscope_studio.components.antd as antd +import modelscope_studio.components.base as ms +import modelscope_studio.components.pro as pro + + +def change(editor_value): + print(editor_value) + + +with gr.Blocks() as demo, ms.Application(), antd.ConfigProvider(): + editor = pro.MonacoEditor( + value="""import gradio as gr +import modelscope_studio.components.antd as antd +import modelscope_studio.components.base as ms +import modelscope_studio.components.pro as pro + +with gr.Blocks() as demo, ms.Application(), antd.ConfigProvider(): + pro.MonacoEditor( + value="Hello World", + height=400, + ) + +if __name__ == "__main__": + demo.queue().launch() +""", + language="python", + height=400, + ) + editor.change(fn=change, inputs=[editor]) + +if __name__ == "__main__": + demo.queue().launch() diff --git a/docs/components/pro/monaco_editor/demos/diff_editor.py b/docs/components/pro/monaco_editor/demos/diff_editor.py new file mode 100644 index 00000000..4a3f6693 --- /dev/null +++ b/docs/components/pro/monaco_editor/demos/diff_editor.py @@ -0,0 +1,43 @@ +import gradio as gr +import modelscope_studio.components.antd as antd +import modelscope_studio.components.base as ms +import modelscope_studio.components.pro as pro + + +def change(editor_value): + print(editor_value) + + +def readonly_change(switch_value): + return gr.update(read_only=switch_value) + + +with gr.Blocks() as demo, ms.Application(), antd.ConfigProvider( +), ms.AutoLoading(): + with antd.Flex(gap="small", elem_style=dict(marginBottom=10)): + ms.Text("Readonly") + switch = antd.Switch(value=False) + editor = pro.MonacoEditor.DiffEditor( + original="import gradio as gr", + value="""import gradio as gr +import modelscope_studio.components.antd as antd +import modelscope_studio.components.base as ms +import modelscope_studio.components.pro as pro + +with gr.Blocks() as demo, ms.Application(), antd.ConfigProvider(): + pro.MonacoEditor( + value="Hello World", + height=400, + ) + +if __name__ == "__main__": + demo.queue().launch() +""", + language="python", + height=400, + ) + editor.change(fn=change, inputs=[editor]) + switch.change(fn=readonly_change, inputs=[switch], outputs=[editor]) + +if __name__ == "__main__": + demo.queue().launch() diff --git a/docs/components/pro/monaco_editor/demos/javascript_customize.py b/docs/components/pro/monaco_editor/demos/javascript_customize.py new file mode 100644 index 00000000..429711d4 --- /dev/null +++ b/docs/components/pro/monaco_editor/demos/javascript_customize.py @@ -0,0 +1,35 @@ +import gradio as gr +import modelscope_studio.components.antd as antd +import modelscope_studio.components.base as ms +import modelscope_studio.components.pro as pro + +with gr.Blocks() as demo, ms.Application(), antd.ConfigProvider(): + pro.MonacoEditor( + value="""import gradio as gr +import modelscope_studio.components.antd as antd +import modelscope_studio.components.base as ms +import modelscope_studio.components.pro as pro + +with gr.Blocks() as demo, ms.Application(), antd.ConfigProvider(): + pro.MonacoEditor( + value="Hello World", + height=400, + ) + +if __name__ == "__main__": + demo.queue().launch() +""", + language="python", + height=400, + # pass a javascript function string to the after_mount prop + after_mount="""(editor) => { + editor.updateOptions({ + readOnly: true, + minimap: { + enabled: false, + }, + }) +}""") + +if __name__ == "__main__": + demo.queue().launch() diff --git a/docs/components/pro/monaco_editor/demos/monaco_editor_options.py b/docs/components/pro/monaco_editor/demos/monaco_editor_options.py new file mode 100644 index 00000000..2c3fe88b --- /dev/null +++ b/docs/components/pro/monaco_editor/demos/monaco_editor_options.py @@ -0,0 +1,33 @@ +import gradio as gr +import modelscope_studio.components.antd as antd +import modelscope_studio.components.base as ms +import modelscope_studio.components.pro as pro + +with gr.Blocks() as demo, ms.Application(), antd.ConfigProvider(): + pro.MonacoEditor( + value="""import gradio as gr +import modelscope_studio.components.antd as antd +import modelscope_studio.components.base as ms +import modelscope_studio.components.pro as pro + +with gr.Blocks() as demo, ms.Application(), antd.ConfigProvider(): + pro.MonacoEditor( + value="Hello World", + height=400, + ) + +if __name__ == "__main__": + demo.queue().launch() +""", + language="python", + height=400, + options={ + "minimap": { + "enabled": False + }, + "lineNumbers": False + }, + ) + +if __name__ == "__main__": + demo.queue().launch() From 540ecc98aa933a6b2e6febd7ca1af17123999b41 Mon Sep 17 00:00:00 2001 From: Col0ring <1561999073@qq.com> Date: Tue, 11 Nov 2025 13:59:21 +0800 Subject: [PATCH 4/7] fix: update of correction value --- .../diff-editor/monaco-editor.diff-editor.tsx | 56 ++++++++++++++----- frontend/pro/monaco-editor/monaco-editor.tsx | 36 ++++++++---- frontend/pro/monaco-editor/useValueChange.ts | 43 ++++++++++++++ 3 files changed, 111 insertions(+), 24 deletions(-) create mode 100644 frontend/pro/monaco-editor/useValueChange.ts diff --git a/frontend/pro/monaco-editor/diff-editor/monaco-editor.diff-editor.tsx b/frontend/pro/monaco-editor/diff-editor/monaco-editor.diff-editor.tsx index 08c5678d..7bb67d14 100644 --- a/frontend/pro/monaco-editor/diff-editor/monaco-editor.diff-editor.tsx +++ b/frontend/pro/monaco-editor/diff-editor/monaco-editor.diff-editor.tsx @@ -5,17 +5,21 @@ import { } from '@monaco-editor/react'; import { ReactSlot } from '@svelte-preprocess-react/react-slot'; import { sveltify } from '@svelte-preprocess-react/sveltify'; -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; import { useFunction } from '@utils/hooks/useFunction'; import { Spin } from 'antd'; +import { isNumber } from 'lodash-es'; import type { editor, IDisposable } from 'monaco-editor'; +import { useValueChange } from '../useValueChange'; + import '../monaco-editor.less'; export interface MonacoDiffEditorProps extends DiffEditorProps { themeMode?: string; height?: string | number; children?: React.ReactNode; + readOnly?: boolean; value?: string; onValueChange: (value: string | undefined) => void; onChange?: ( @@ -24,6 +28,7 @@ export interface MonacoDiffEditorProps extends DiffEditorProps { ) => void; onValidate?: OnValidate; afterMount?: DiffEditorProps['onMount']; + line?: number; } export const MonacoDiffEditor = sveltify( @@ -40,22 +45,35 @@ export const MonacoDiffEditor = sveltify( onChange, onValueChange, onValidate, - value, + value: valueProp, modified, + options, + readOnly, + line, ...props }) => { const beforeMountFunction = useFunction(beforeMount); const afterMountFunction = useFunction(afterMount); - const disposablesRef = React.useRef([]); - + const disposablesRef = useRef([]); + const editorRef = useRef(null); + const [isEditorReady, setIsEditorReady] = React.useState(false); + const [value, setValue] = useValueChange({ + onValueChange, + value: valueProp, + }); const handleEditorMount: MonacoDiffEditorProps['onMount'] = ( editor, monaco ) => { + editorRef.current = editor; + if (isNumber(line)) { + editor.revealLine(line); + } + setIsEditorReady(true); const modifiedEditor = editor.getModifiedEditor(); const mountDisposable = modifiedEditor.onDidChangeModelContent((e) => { const newValue = modifiedEditor.getValue(); - onValueChange(newValue); + setValue(newValue); onChange?.(newValue, e); }); @@ -84,6 +102,13 @@ export const MonacoDiffEditor = sveltify( }); }; }, []); + + useEffect(() => { + if (isEditorReady && isNumber(line)) { + editorRef.current?.revealLine(line); + } + }, [line, isEditorReady]); + return ( <>
{children}
@@ -96,6 +121,13 @@ export const MonacoDiffEditor = sveltify( > ({ + readOnly, + ...(options || {}), + }), + [options, readOnly] + )} modified={value || modified} beforeMount={beforeMountFunction} onMount={(editor, monaco) => { @@ -107,14 +139,12 @@ export const MonacoDiffEditor = sveltify( slots.loading ? ( ) : ( - props.loading || ( - -
- - ) + +
+ ) } theme={themeMode === 'dark' ? 'vs-dark' : 'light'} diff --git a/frontend/pro/monaco-editor/monaco-editor.tsx b/frontend/pro/monaco-editor/monaco-editor.tsx index bd67b2d8..6b0886ee 100644 --- a/frontend/pro/monaco-editor/monaco-editor.tsx +++ b/frontend/pro/monaco-editor/monaco-editor.tsx @@ -1,15 +1,18 @@ import { Editor, type EditorProps } from '@monaco-editor/react'; import { ReactSlot } from '@svelte-preprocess-react/react-slot'; import { sveltify } from '@svelte-preprocess-react/sveltify'; -import React from 'react'; +import React, { useMemo } from 'react'; import { useFunction } from '@utils/hooks/useFunction'; import { Spin } from 'antd'; +import { useValueChange } from './useValueChange'; + import './monaco-editor.less'; export interface MonacoEditorProps extends EditorProps { themeMode?: string; height?: string | number; + readOnly?: boolean; onValueChange: (value: string | undefined) => void; children?: React.ReactNode; afterMount?: EditorProps['onMount']; @@ -18,7 +21,7 @@ export interface MonacoEditorProps extends EditorProps { export const MonacoEditor = sveltify( ({ height, - value, + value: valueProp, className, style, themeMode, @@ -29,10 +32,16 @@ export const MonacoEditor = sveltify( afterMount, children, onMount, + options, + readOnly, ...props }) => { const beforeMountFunction = useFunction(beforeMount); const afterMountFunction = useFunction(afterMount); + const [value, setValue] = useValueChange({ + onValueChange, + value: valueProp, + }); return ( <> @@ -52,22 +61,27 @@ export const MonacoEditor = sveltify( onMount?.(...args); afterMountFunction?.(...args); }} + options={useMemo( + () => ({ + readOnly, + ...(options || {}), + }), + [options, readOnly] + )} loading={ slots.loading ? ( ) : ( - props.loading || ( - -
- - ) + +
+ ) } onChange={(v, ev) => { - onValueChange(v); + setValue(v); onChange?.(v, ev); }} theme={themeMode === 'dark' ? 'vs-dark' : 'light'} diff --git a/frontend/pro/monaco-editor/useValueChange.ts b/frontend/pro/monaco-editor/useValueChange.ts new file mode 100644 index 00000000..18996800 --- /dev/null +++ b/frontend/pro/monaco-editor/useValueChange.ts @@ -0,0 +1,43 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useMemoizedFn } from '@utils/hooks/useMemoizedFn'; + +export function useValueChange(options: { + value: string | undefined; + onValueChange: (value: string | undefined) => void; +}) { + const { value: valueProp, onValueChange } = options; + const [typing, setTyping] = useState(false); + const [displayValue, setDisplayValue] = useState(valueProp); + const typingTimerRef = useRef | null>(null); + const onValueChangeMemoized = useMemoizedFn(onValueChange); + + const setValue = useCallback( + (value: string | undefined) => { + typingTimerRef.current && clearTimeout(typingTimerRef.current); + setTyping(true); + typingTimerRef.current = setTimeout(() => { + setTyping(false); + }, 100); + onValueChangeMemoized(value); + }, + [onValueChangeMemoized] + ); + + useEffect(() => { + // if not typing, use the cache value + if (!typing) { + // eslint-disable-next-line react-hooks/set-state-in-effect + setDisplayValue(valueProp); + } + }, [typing, valueProp]); + + useEffect(() => { + return () => { + if (typingTimerRef.current) { + clearTimeout(typingTimerRef.current); + typingTimerRef.current = null; + } + }; + }, []); + return [displayValue, setValue] as const; +} From f70defaf9c84bdfdfee6fdfd3475d718c465cab8 Mon Sep 17 00:00:00 2001 From: Col0ring <1561999073@qq.com> Date: Tue, 11 Nov 2025 14:00:27 +0800 Subject: [PATCH 5/7] chore: add changeset --- .changeset/silly-bats-own.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/silly-bats-own.md diff --git a/.changeset/silly-bats-own.md b/.changeset/silly-bats-own.md new file mode 100644 index 00000000..a1c138d2 --- /dev/null +++ b/.changeset/silly-bats-own.md @@ -0,0 +1,7 @@ +--- +'@modelscope-studio/pro': minor +'@modelscope-studio/frontend': minor +'modelscope_studio': minor +--- + +feat: add pro component `MonacoEditor` From fe4b3a164e9cbdc7463ae80936d835cd050b577e Mon Sep 17 00:00:00 2001 From: Col0ring <1561999073@qq.com> Date: Tue, 11 Nov 2025 16:06:52 +0800 Subject: [PATCH 6/7] fix: inject the monaco loader from `ms.Application` --- .../pro/monaco_editor/diff_editor/__init__.py | 2 +- frontend/pro/monaco-editor/Index.svelte | 29 +++++---------- .../monaco-editor/diff-editor/Index.svelte | 29 +++++---------- .../diff-editor/gradio.config.js | 6 +-- frontend/pro/monaco-editor/gradio.config.js | 6 +-- frontend/pro/monaco-editor/loader.ts | 37 ++++++++----------- frontend/svelte-preprocess-react/inject.ts | 5 ++- pyproject.toml | 1 + 8 files changed, 41 insertions(+), 74 deletions(-) diff --git a/backend/modelscope_studio/components/pro/monaco_editor/diff_editor/__init__.py b/backend/modelscope_studio/components/pro/monaco_editor/diff_editor/__init__.py index 99482ea1..8d3d98c1 100644 --- a/backend/modelscope_studio/components/pro/monaco_editor/diff_editor/__init__.py +++ b/backend/modelscope_studio/components/pro/monaco_editor/diff_editor/__init__.py @@ -45,7 +45,7 @@ def __init__( before_mount: str | None = None, after_mount: str | None = None, override_services: dict | None = None, - loading: str | None = None, + loading: str | None = "Editor is loading...", read_only: bool | None = None, options: dict | None = None, line: int | None = None, diff --git a/frontend/pro/monaco-editor/Index.svelte b/frontend/pro/monaco-editor/Index.svelte index 46d1b233..6ac1300a 100644 --- a/frontend/pro/monaco-editor/Index.svelte +++ b/frontend/pro/monaco-editor/Index.svelte @@ -77,32 +77,21 @@ slots: $slots, themeMode: gradio.theme, }; + + $: awaitedLoader = + _loader?.mode === 'local' + ? initLocalLoader() + : _loader?.cdn_url + ? initCDNLoader(_loader.cdn_url) + : undefined; {#if $mergedProps.visible} - {#if _loader?.mode === 'local'} - {#await initLocalLoader()} - - {:then} - {#await AwaitedMonacoEditor then MonacoEditor} - - - - {/await} - {/await} - {:else if _loader?.cdn_url} - {#await initCDNLoader(_loader.cdn_url)} - {#await AwaitedMonacoEditor then MonacoEditor} - - - - {/await} - {/await} - {:else} + {#await awaitedLoader then} {#await AwaitedMonacoEditor then MonacoEditor} {/await} - {/if} + {/await} {/if} diff --git a/frontend/pro/monaco-editor/diff-editor/Index.svelte b/frontend/pro/monaco-editor/diff-editor/Index.svelte index 07c498f6..f75e249d 100644 --- a/frontend/pro/monaco-editor/diff-editor/Index.svelte +++ b/frontend/pro/monaco-editor/diff-editor/Index.svelte @@ -79,32 +79,21 @@ slots: $slots, themeMode: gradio.theme, }; + + $: awaitedLoader = + _loader?.mode === 'local' + ? initLocalLoader() + : _loader?.cdn_url + ? initCDNLoader(_loader.cdn_url) + : undefined; {#if $mergedProps.visible} - {#if _loader?.mode === 'local'} - {#await initLocalLoader()} - - {:then} - {#await AwaitedMonacoDiffEditor then MonacoDiffEditor} - - - - {/await} - {/await} - {:else if _loader?.cdn_url} - {#await initCDNLoader(_loader.cdn_url)} - {#await AwaitedMonacoDiffEditor then MonacoDiffEditor} - - - - {/await} - {/await} - {:else} + {#await awaitedLoader then} {#await AwaitedMonacoDiffEditor then MonacoDiffEditor} {/await} - {/if} + {/await} {/if} diff --git a/frontend/pro/monaco-editor/diff-editor/gradio.config.js b/frontend/pro/monaco-editor/diff-editor/gradio.config.js index 80740416..1992403d 100644 --- a/frontend/pro/monaco-editor/diff-editor/gradio.config.js +++ b/frontend/pro/monaco-editor/diff-editor/gradio.config.js @@ -1,7 +1,3 @@ import config from '../../../defineConfig.js'; -export default config({ - external: { - excludes: ['@monaco-editor/loader'], - }, -}); +export default config(); diff --git a/frontend/pro/monaco-editor/gradio.config.js b/frontend/pro/monaco-editor/gradio.config.js index 6641fe8e..916857c0 100644 --- a/frontend/pro/monaco-editor/gradio.config.js +++ b/frontend/pro/monaco-editor/gradio.config.js @@ -1,7 +1,3 @@ import config from '../../defineConfig.js'; -export default config({ - external: { - excludes: ['@monaco-editor/loader'], - }, -}); +export default config(); diff --git a/frontend/pro/monaco-editor/loader.ts b/frontend/pro/monaco-editor/loader.ts index e4524279..13a9cb61 100644 --- a/frontend/pro/monaco-editor/loader.ts +++ b/frontend/pro/monaco-editor/loader.ts @@ -1,30 +1,25 @@ -function getMonacoLoader() { +import { initialize } from '@svelte-preprocess-react/component'; + +async function getMonacoLoader() { + await initialize(); return new Promise<{ loader: typeof window.ms_globals.monacoLoader; done?: () => void; }>((resolve) => { - if (!window.ms_globals?.monacoLoader) { - if (window.ms_globals?.monacoLoaderPromise) { - window.ms_globals.monacoLoaderPromise.then((loader) => { - resolve({ - loader, - }); - }); - } else { - window.ms_globals ??= {} as typeof window.ms_globals; - window.ms_globals.monacoLoaderPromise = new Promise((resolve2) => { - import('@monaco-editor/react').then((m) => { - window.ms_globals.monacoLoader = m.loader; - resolve({ - loader: m.loader, - done: () => resolve2(m.loader), - }); - }); + if (window.ms_globals?.monacoLoaderPromise) { + window.ms_globals.monacoLoaderPromise.then(() => { + resolve({ + loader: window.ms_globals.monacoLoader, }); - } + }); } else { - resolve({ - loader: window.ms_globals.monacoLoader, + window.ms_globals.monacoLoaderPromise = new Promise((resolve2) => { + resolve({ + loader: window.ms_globals.monacoLoader, + done: () => { + resolve2(); + }, + }); }); } }); diff --git a/frontend/svelte-preprocess-react/inject.ts b/frontend/svelte-preprocess-react/inject.ts index 5233e777..831c157b 100644 --- a/frontend/svelte-preprocess-react/inject.ts +++ b/frontend/svelte-preprocess-react/inject.ts @@ -1,4 +1,5 @@ import type { loader as monacoLoader } from '@monaco-editor/react'; +import { loader } from '@monaco-editor/react'; import React from 'react'; import ReactDOM from 'react-dom'; import ReactDOMClient from 'react-dom/client'; @@ -42,7 +43,7 @@ declare global { autokey: number; loadingKey: number; monacoLoader: typeof monacoLoader | null; - monacoLoaderPromise: Promise | null; + monacoLoaderPromise: Promise | null; rerender: (props: BridgeProps) => void; // render items createItemsContext: typeof createItemsContext; @@ -103,7 +104,7 @@ window.ms_globals = { nodes: [], }, rerender, - monacoLoader: null, + monacoLoader: loader, monacoLoaderPromise: null, // render items createItemsContext, diff --git a/pyproject.toml b/pyproject.toml index 5516bc54..0bca7918 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -231,6 +231,7 @@ artifacts = [ "/backend/modelscope_studio/components/pro/web_sandbox/templates", "/backend/modelscope_studio/components/antd/statistic/timer/templates", "/backend/modelscope_studio/components/pro/monaco_editor/templates", + "/backend/modelscope_studio/components/pro/monaco_editor/diff_editor/templates", ] [tool.yapfignore] From dc9c77beaf1d1223adc47646987866dcd2996c9b Mon Sep 17 00:00:00 2001 From: Col0ring <1561999073@qq.com> Date: Tue, 11 Nov 2025 16:11:56 +0800 Subject: [PATCH 7/7] fix: typo --- docs/components/pro/monaco_editor/README-zh_CN.md | 2 +- docs/components/pro/monaco_editor/README.md | 2 +- .../diff-editor/monaco-editor.diff-editor.tsx | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/components/pro/monaco_editor/README-zh_CN.md b/docs/components/pro/monaco_editor/README-zh_CN.md index 7e7891e3..c0ff766a 100644 --- a/docs/components/pro/monaco_editor/README-zh_CN.md +++ b/docs/components/pro/monaco_editor/README-zh_CN.md @@ -53,7 +53,7 @@ | original | `str\| None` | None | 原始源的值(左侧)。 | | language | `str\| None` | None | 编辑器的语言(monaco-editor [支持](https://github.com/microsoft/monaco-editor/tree/main/src/basic-languages)的所有语言)。 | | original_language | `str\| None` | None | 单独指定原始源的语言。否则,它将获取 language 属性的值。 | -| modified_Language | `str\| None` | None | 单独指定修改后的源的语言。否则,它将获取 language 属性的值。 | +| modified_language | `str\| None` | None | 单独指定修改后的源的语言。否则,它将获取 language 属性的值。 | | line | `number\| None` | None | 垂直滚动编辑器到指定行。 | | read_only | `bool\| None` | None | 编辑器是否只读。 | | loading | `str\| None` | 'Editor is loading...' | 编辑器初始化加载时的加载文案。 | diff --git a/docs/components/pro/monaco_editor/README.md b/docs/components/pro/monaco_editor/README.md index 314f286f..05af360b 100644 --- a/docs/components/pro/monaco_editor/README.md +++ b/docs/components/pro/monaco_editor/README.md @@ -53,7 +53,7 @@ If you need further customization of Monaco Editor, you can pass in JavaScript f | original | `str\| None` | None | The original source (left one) value. | | language | `str\| None` | None | The language of the editor (all languages [supported](https://github.com/microsoft/monaco-editor/tree/main/src/basic-languages) by monaco-editor). | | original_language | `str\| None` | None | Specifies the language of the original source separately. Otherwise, it will take the value of the language property. | -| modified_Language | `str\| None` | None | Specifies the language of the modified source separately. Otherwise, it will take the value of the language property. | +| modified_language | `str\| None` | None | Specifies the language of the modified source separately. Otherwise, it will take the value of the language property. | | line | `number\| None` | None | Vertically scroll the editor to the specified line. | | read_only | `bool\| None` | None | Whether the editor is read-only. | | loading | `str\| None` | 'Editor is loading...' | The loading text when the editor is initializing. | diff --git a/frontend/pro/monaco-editor/diff-editor/monaco-editor.diff-editor.tsx b/frontend/pro/monaco-editor/diff-editor/monaco-editor.diff-editor.tsx index 7bb67d14..4cd7e3ea 100644 --- a/frontend/pro/monaco-editor/diff-editor/monaco-editor.diff-editor.tsx +++ b/frontend/pro/monaco-editor/diff-editor/monaco-editor.diff-editor.tsx @@ -7,6 +7,7 @@ import { ReactSlot } from '@svelte-preprocess-react/react-slot'; import { sveltify } from '@svelte-preprocess-react/sveltify'; import React, { useEffect, useMemo, useRef } from 'react'; import { useFunction } from '@utils/hooks/useFunction'; +import { useMemoizedFn } from '@utils/hooks/useMemoizedFn'; import { Spin } from 'antd'; import { isNumber } from 'lodash-es'; import type { editor, IDisposable } from 'monaco-editor'; @@ -61,6 +62,8 @@ export const MonacoDiffEditor = sveltify( onValueChange, value: valueProp, }); + const onChangeMemoized = useMemoizedFn(onChange); + const onValidateMemoized = useMemoizedFn(onValidate); const handleEditorMount: MonacoDiffEditorProps['onMount'] = ( editor, monaco @@ -74,7 +77,7 @@ export const MonacoDiffEditor = sveltify( const mountDisposable = modifiedEditor.onDidChangeModelContent((e) => { const newValue = modifiedEditor.getValue(); setValue(newValue); - onChange?.(newValue, e); + onChangeMemoized(newValue, e); }); const validateDisposable = monaco.editor.onDidChangeMarkers((uris) => { @@ -87,7 +90,7 @@ export const MonacoDiffEditor = sveltify( const markers = monaco.editor.getModelMarkers({ resource: editorUri, }); - onValidate?.(markers); + onValidateMemoized(markers); } } });