Files
FastGPT/packages/web/components/common/Textarea/CodeEditor/Editor.tsx
T
Archer c93c3937e1 S3 sdk (#6215)
* refactor: fastgpt object storage & global proxy (#6155)

* feat: migrate to fastgpt storage sdk

* chore: rename env variable

* chore: move to sdk dir

* docs: object storage

* CHORE

* chore: storage mocks

* chore: update docker-compose

* fix: global proxy agent

* fix: update COS proxy

* refactor: use fetch instead of http.request

* fix: axios request base url

* fix: axios proxy request behavior

* fix: bumps axios

* fix: patch axios for proxy

* fix: replace axios with proxied axios

* fix: upload txt file encoding

* clean code

* fix: use "minio" for minio adapter (#6205)

* fix: use minio client to delete files when using minio vendor (#6206)

* doc

* feat: filter citations and add response button control (#6170)

* feat: filter citations and add response button control

* i18n

* fix

* fix test

* perf: chat api code

* fix: workflow edge overlap and auto-align in folded loop nodes (#6204)

* fix: workflow edge overlap and auto-align in folded loop nodes

* sort

* fix

* fix edge

* fix icon

* perf: s3 file name

* perf: admin get app api

* perf: catch user error

* fix: refactor useOrg hook to use debounced search key (#6180)

* chore: comment minio adapter (#6207)

* chore: filename with suffix random id

* perf: s3 storage code

* fix: encode filename when copy object

---------

Co-authored-by: archer <545436317@qq.com>

* fix: node card link

* json

* perf: chat index;

* index

* chat item soft delete (#6216)

* chat item soft delete

* temp

* fix

* remove code

* perf: delete chat item

---------

Co-authored-by: archer <545436317@qq.com>

* feat: select wheather filter sensitive info when export apps (#6222)

* fix some bugs (#6210)

* fix v4.14.5 bugs

* type

* fix

* fix

* custom feedback

* fix

* code

* fix

* remove invalid function

---------

Co-authored-by: archer <545436317@qq.com>

* perf: test

* fix file default local upload (#6223)

* docs: improve object storage introduction (#6224)

* doc

---------

Co-authored-by: roy <whoeverimf5@gmail.com>
Co-authored-by: heheer <heheer@sealos.io>
Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
2026-01-09 18:25:02 +08:00

189 lines
5.1 KiB
TypeScript

import React, { useCallback, useRef, useState, useEffect } from 'react';
import Editor, { type Monaco, loader } from '@monaco-editor/react';
import { Box, type BoxProps } from '@chakra-ui/react';
import MyIcon from '../../Icon';
import { getWebReqUrl } from '../../../../common/system/utils';
import usePythonCompletion from './usePythonCompletion';
loader.config({
paths: { vs: getWebReqUrl('/js/monaco-editor.0.45.0/vs') }
});
type EditorVariablePickerType = {
key: string;
label: string;
};
export type Props = Omit<BoxProps, 'resize' | 'onChange'> & {
resize?: boolean;
defaultValue?: string;
value?: string;
onChange?: (e: string) => void;
onOpenModal?: () => void;
variables?: EditorVariablePickerType[];
defaultHeight?: number;
language?: string;
};
const options = {
lineNumbers: 'on',
guides: {
indentation: false
},
automaticLayout: true,
minimap: {
enabled: false
},
scrollbar: {
verticalScrollbarSize: 4,
horizontalScrollbarSize: 8,
alwaysConsumeMouseWheel: false
},
lineNumbersMinChars: 4,
fontSize: 14,
scrollBeyondLastLine: false,
folding: true,
overviewRulerBorder: false,
tabSize: 2
};
const MyEditor = ({
defaultValue,
value,
onChange,
resize,
variables = [],
defaultHeight = 200,
onOpenModal,
language = 'typescript',
...props
}: Props) => {
const [height, setHeight] = useState(defaultHeight);
const initialY = useRef(0);
const registerPythonCompletion = usePythonCompletion();
const handleMouseDown = useCallback((e: React.MouseEvent) => {
initialY.current = e.clientY;
const handleMouseMove = (e: MouseEvent) => {
const deltaY = e.clientY - initialY.current;
initialY.current = e.clientY;
setHeight((prevHeight) => (prevHeight + deltaY < 100 ? 100 : prevHeight + deltaY));
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}, []);
const editorRef = useRef<any>(null);
const monacoRef = useRef<Monaco | null>(null);
const handleEditorDidMount = useCallback((editor: any, monaco: Monaco) => {
editorRef.current = editor;
monacoRef.current = monaco;
// Prevent browser autofill from causing getModifierState errors
const editorDom = editor.getDomNode();
if (editorDom) {
const textarea = editorDom.querySelector('textarea');
if (textarea) {
textarea.setAttribute('autocomplete', 'off');
textarea.setAttribute('autocorrect', 'off');
textarea.setAttribute('autocapitalize', 'off');
textarea.setAttribute('spellcheck', 'false');
}
}
}, []);
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'
}
});
registerPythonCompletion(monaco);
},
[registerPythonCompletion]
);
return (
<Box
borderWidth={'1px'}
borderRadius={'md'}
borderColor={'myGray.200'}
py={1}
height={height}
position={'relative'}
{...props}
>
<Editor
height={'100%'}
language={language}
options={options as any}
theme="JSONEditorTheme"
beforeMount={beforeMount}
defaultValue={defaultValue}
value={value}
onChange={(e) => {
onChange?.(e || '');
}}
onMount={handleEditorDidMount}
/>
{resize && (
<Box
position={'absolute'}
right={'-2.5'}
bottom={'-3.5'}
zIndex={10}
cursor={'ns-resize'}
px={'4px'}
onMouseDown={handleMouseDown}
>
<MyIcon name={'common/editor/resizer'} width={'16px'} height={'16px'} />
</Box>
)}
{!!onOpenModal && (
<Box
zIndex={10}
position={'absolute'}
bottom={0}
right={2}
cursor={'pointer'}
onClick={onOpenModal}
>
<MyIcon name={'common/fullScreenLight'} w={'14px'} color={'myGray.600'} />
</Box>
)}
</Box>
);
};
export default React.memo(MyEditor);