4.6.8 supplement (#831)

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-02-15 12:26:02 +08:00
committed by GitHub
parent 51bbdf26a3
commit 91bcf8c53e
200 changed files with 4387 additions and 2749 deletions

View File

@@ -1,17 +1,20 @@
import React, { useEffect } from 'react';
import Editor, { loader, useMonaco } from '@monaco-editor/react';
import { useCallback, useRef, useState } from 'react';
import React, { useEffect, useCallback, useRef, useState } from 'react';
import Editor, { Monaco, loader, useMonaco } from '@monaco-editor/react';
import { Box, BoxProps } from '@chakra-ui/react';
import MyIcon from '../../Icon';
import { EditorVariablePickerType } from '../PromptEditor/type';
import { useToast } from '../../../../hooks/useToast';
import { useTranslation } from 'next-i18next';
loader.config({
paths: { vs: 'https://cdn.staticfile.net/monaco-editor/0.43.0/min/vs' }
paths: { vs: '/js/monaco-editor.0.45.0/vs' }
});
type Props = Omit<BoxProps, 'onChange' | 'resize' | 'height'> & {
type EditorVariablePickerType = {
key: string;
label: string;
};
type Props = Omit<BoxProps, 'resize' | 'onChange'> & {
height?: number;
resize?: boolean;
defaultValue?: string;
@@ -42,43 +45,95 @@ const options = {
tabSize: 2
};
const JSONEditor = ({ defaultValue, value, onChange, resize, variables, ...props }: Props) => {
const JSONEditor = ({ defaultValue, value, onChange, resize, variables = [], ...props }: Props) => {
const { toast } = useToast();
const { t } = useTranslation();
const [height, setHeight] = useState(props.height || 100);
const initialY = useRef(0);
const completionRegisterRef = useRef<any>();
const monaco = useMonaco();
const triggerChar = useRef<string>();
useEffect(() => {
completionRegisterRef.current = monaco?.languages.registerCompletionItemProvider('json', {
triggerCharacters: ['"'],
provideCompletionItems: function (model, position) {
var word = model.getWordUntilPosition(position);
var range = {
if (!monaco) return;
// 自定义补全提供者
completionRegisterRef.current = monaco.languages.registerCompletionItemProvider('json', {
triggerCharacters: ['{'],
provideCompletionItems: function (model, position, context) {
const lineContent = model.getLineContent(position.lineNumber);
if (context.triggerCharacter) {
console.log(context.triggerCharacter);
triggerChar.current = context.triggerCharacter;
}
const word = model.getWordUntilPosition(position);
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn
};
const startText = lineContent.substring(0, position.column - 1); // 光标前的文本
const endText = lineContent.substring(position.column - 1); // 光标后的文本
const before2Char = startText[startText.length - 2];
const beforeChar = startText[startText.length - 1];
const afterChar = endText[0];
const after2Char = endText[1];
if (before2Char !== '{' && beforeChar !== '"') {
return {
suggestions: []
};
}
return {
suggestions:
variables?.map((item) => ({
label: `${item.label}`,
kind: monaco.languages.CompletionItemKind.Function,
documentation: item.label,
insertText: `{{${item.label}}}`,
range: range
})) || [],
dispose: () => {}
variables?.map((item) => {
let insertText = item.key;
if (before2Char !== '{') {
insertText = `{${insertText}`;
}
if (afterChar !== '}') {
insertText = `${insertText}}`;
}
if (after2Char !== '}') {
insertText = `${insertText}}`;
}
return {
label: item.key,
kind: monaco.languages.CompletionItemKind.Variable,
detail: item.label,
insertText: insertText,
range
};
}) || []
};
}
});
// 自定义语法高亮
monaco.languages.setMonarchTokensProvider('json', {
tokenizer: {
root: [
// 匹配variables里的变量
[new RegExp(`{{(${variables.map((item) => item.key).join('|')})}}`), 'variable'],
[/".*?"/, 'string'], // 匹配字符串
[/[{}\[\]]/, '@brackets'], // 匹配括号
[/[0-9]+/, 'number'], // 匹配数字
[/true|false/, 'keyword'], // 匹配布尔值
[/:/, 'delimiter'], // 匹配冒号
[/,/, 'delimiter.comma'] // 匹配逗号
]
}
});
return () => {
completionRegisterRef.current?.dispose();
};
}, [monaco, completionRegisterRef.current]);
}, [monaco, variables]);
const handleMouseDown = useCallback((e: React.MouseEvent) => {
initialY.current = e.clientY;
@@ -98,6 +153,48 @@ const JSONEditor = ({ defaultValue, value, onChange, resize, variables, ...props
document.addEventListener('mouseup', handleMouseUp);
}, []);
const onBlur = useCallback(() => {
if (!value) return;
// replace {{xx}} to true
const replaceValue = value?.replace(/{{(.*?)}}/g, 'true');
try {
JSON.parse(replaceValue);
} catch (error) {
toast({
status: 'warning',
title: t('common.jsonEditor.Parse error')
});
}
}, [value]);
const beforeMount = useCallback((monaco: Monaco) => {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: false,
allowComments: false,
schemas: [
{
uri: 'http://myserver/foo-schema.json', // 一个假设的 URI
fileMatch: ['*'], // 匹配所有文件
schema: {} // 空的 Schema
}
]
});
monaco.editor.defineTheme('JSONEditorTheme', {
base: 'vs', // 可以基于已有的主题进行定制
inherit: true, // 继承基础主题的设置
rules: [{ token: 'variable', foreground: '2B5FD9' }],
colors: {
'editor.background': '#ffffff00',
'editorLineNumber.foreground': '#aaa',
'editorOverviewRuler.border': '#ffffff00',
'editor.lineHighlightBackground': '#F7F8FA',
'scrollbarSlider.background': '#E8EAEC',
'editorIndentGuide.activeBackground': '#ddd',
'editorIndentGuide.background': '#eee'
}
});
}, []);
return (
<Box position={'relative'}>
{resize && (
@@ -105,7 +202,7 @@ const JSONEditor = ({ defaultValue, value, onChange, resize, variables, ...props
position={'absolute'}
right={'0'}
bottom={'0'}
zIndex={999}
zIndex={10}
cursor={'ns-resize'}
px={'4px'}
onMouseDown={handleMouseDown}
@@ -119,47 +216,20 @@ const JSONEditor = ({ defaultValue, value, onChange, resize, variables, ...props
borderRadius={'md'}
borderColor={'myGray.200'}
py={2}
{...props}
height={'auto'}
{...props}
>
<Editor
height={height}
defaultLanguage="json"
options={options as any}
theme={'JSONEditorTheme'}
beforeMount={(monaco) => {
monaco?.editor.defineTheme('JSONEditorTheme', {
base: 'vs',
inherit: true,
rules: [],
colors: {
'editor.background': '#ffffff00',
'editorLineNumber.foreground': '#aaa',
'editorOverviewRuler.border': '#ffffff00',
'editor.lineHighlightBackground': '#F7F8FA',
'scrollbarSlider.background': '#E8EAEC',
'editorIndentGuide.activeBackground': '#ddd',
'editorIndentGuide.background': '#eee'
}
});
}}
theme="JSONEditorTheme"
beforeMount={beforeMount}
defaultValue={defaultValue}
value={value}
onChange={(e) => onChange?.(e || '')}
wrapperProps={{
onBlur: () => {
if (!value) return;
try {
JSON.parse(value as string);
} catch (error: any) {
toast({
title: t('common.Invalid Json'),
description: error.message,
status: 'warning',
isClosable: true
});
}
}
onBlur
}}
/>
</Box>

View File

@@ -10,6 +10,7 @@ import { useCallback, useTransition } from 'react';
const PromptEditor = ({
showOpenModal = true,
showResize = true,
variables = [],
value,
onChange,
@@ -19,6 +20,7 @@ const PromptEditor = ({
title
}: {
showOpenModal?: boolean;
showResize?: boolean;
variables?: EditorVariablePickerType[];
value?: string;
onChange?: (text: string) => void;
@@ -48,7 +50,7 @@ const PromptEditor = ({
return (
<>
<Editor
showResize
showResize={showResize}
showOpenModal={showOpenModal}
onOpenModal={onOpen}
variables={variables}

View File

@@ -100,7 +100,7 @@ export default function VariablePickerPlugin({
p={2}
borderRadius={'md'}
position={'fixed'}
w={'200px'}
w={'auto'}
overflow={'hidden'}
zIndex={99999}
>
@@ -113,6 +113,8 @@ export default function VariablePickerPlugin({
py={2}
borderRadius={'sm'}
cursor={'pointer'}
maxH={'300px'}
overflow={'auto'}
_notLast={{
mb: 2
}}
@@ -133,8 +135,11 @@ export default function VariablePickerPlugin({
setHighlightedIndex(index);
}}
>
<MyIcon name={item.icon as any} w={'14px'} />
<Box ml={2} fontSize={'sm'}>{`${item.key}(${item.label})`}</Box>
<MyIcon name={(item.icon as any) || 'core/modules/variable'} w={'14px'} />
<Box ml={2} fontSize={'sm'}>
{item.key}
{item.key !== item.label && `(${item.label})`}
</Box>
</Flex>
))}
</Box>,