feat(web): ai prompt api support stream
This commit is contained in:
91
web/src/views/ApplicationConfig/components/Editor/index.tsx
Normal file
91
web/src/views/ApplicationConfig/components/Editor/index.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import {forwardRef, useImperativeHandle } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
||||
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
|
||||
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
|
||||
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
|
||||
import { $getSelection } from 'lexical';
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
import InitialValuePlugin from './plugin/InitialValuePlugin'
|
||||
import LineBreakPlugin from './plugin/LineBreakPlugin';
|
||||
import InsertTextPlugin from './plugin/InsertTextPlugin';
|
||||
|
||||
export interface EditorRef {
|
||||
insertText: (text: string) => void;
|
||||
}
|
||||
|
||||
interface LexicalEditorProps {
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
const theme = {
|
||||
paragraph: 'editor-paragraph',
|
||||
text: {
|
||||
bold: 'editor-text-bold',
|
||||
italic: 'editor-text-italic',
|
||||
},
|
||||
};
|
||||
|
||||
const EditorContent = forwardRef<EditorRef, LexicalEditorProps>(({
|
||||
className = '',
|
||||
value,
|
||||
placeholder = "请输入内容...",
|
||||
onChange,
|
||||
}, ref) => {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
insertText: (text: string) => {
|
||||
editor.update(() => {
|
||||
const selection = $getSelection();
|
||||
if (selection) {
|
||||
selection.insertText(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
}), [editor]);
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<RichTextPlugin
|
||||
contentEditable={
|
||||
<ContentEditable
|
||||
className={clsx("rb:outline-none rb:resize-none rb:text-[14px] rb:leading-5 rb:px-4 rb:py-5 rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:overflow-auto", className)}
|
||||
/>
|
||||
}
|
||||
placeholder={
|
||||
<div className="rb:absolute rb:px-4 rb:py-5 rb:text-[14px] rb:text-[#5B6167] rb:leading-5 rb:pointer-none">
|
||||
{placeholder}
|
||||
</div>
|
||||
}
|
||||
ErrorBoundary={LexicalErrorBoundary}
|
||||
/>
|
||||
<LineBreakPlugin onChange={onChange} />
|
||||
<InitialValuePlugin value={value} />
|
||||
<InsertTextPlugin />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const Editor = forwardRef<EditorRef, LexicalEditorProps>((props, ref) => {
|
||||
const initialConfig = {
|
||||
namespace: 'Editor',
|
||||
theme,
|
||||
nodes: [],
|
||||
onError: (error: Error) => {
|
||||
console.error(error);
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<LexicalComposer initialConfig={initialConfig}>
|
||||
<EditorContent {...props} ref={ref} />
|
||||
</LexicalComposer>
|
||||
);
|
||||
});
|
||||
|
||||
export default Editor;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { type FC, useEffect } from 'react';
|
||||
import { $getRoot, $createParagraphNode, $createTextNode } from 'lexical';
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
|
||||
// 设置初始值的插件
|
||||
const InitialValuePlugin: FC<{ value?: string }> = ({ value }) => {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
editor.update(() => {
|
||||
const root = $getRoot();
|
||||
root.clear();
|
||||
const paragraph = $createParagraphNode();
|
||||
const textNode = $createTextNode(value);
|
||||
paragraph.append(textNode);
|
||||
root.append(paragraph);
|
||||
});
|
||||
}
|
||||
}, [editor, value]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default InitialValuePlugin
|
||||
@@ -0,0 +1,24 @@
|
||||
import { forwardRef, useImperativeHandle } from 'react';
|
||||
import { $getSelection } from 'lexical';
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
import type { EditorRef } from '../index'
|
||||
|
||||
// 插入文本的插件
|
||||
const InsertTextPlugin = forwardRef<EditorRef>((_, ref) => {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
insertText: (text: string) => {
|
||||
editor.update(() => {
|
||||
const selection = $getSelection();
|
||||
if (selection) {
|
||||
selection.insertText(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
}), [editor]);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
export default InsertTextPlugin;
|
||||
@@ -0,0 +1,24 @@
|
||||
import { type FC, useEffect } from 'react';
|
||||
import { $getRoot } from 'lexical';
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
|
||||
// 处理换行的插件
|
||||
const LineBreakPlugin: FC<{ onChange?: (value: string) => void }> = ({ onChange }) => {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
|
||||
useEffect(() => {
|
||||
return editor.registerUpdateListener(({ editorState }) => {
|
||||
editorState.read(() => {
|
||||
const root = $getRoot();
|
||||
const textContent = root.getTextContent();
|
||||
// 将\n转换为实际换行
|
||||
const processedContent = textContent.replace(/\\n/g, '\n');
|
||||
onChange?.(processedContent);
|
||||
});
|
||||
});
|
||||
}, [editor, onChange]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default LineBreakPlugin;
|
||||
Reference in New Issue
Block a user