feat(web): ai prompt api support stream

This commit is contained in:
zhaoying
2026-01-06 15:03:40 +08:00
parent 8d3ec8c047
commit 35a06c3cbe
7 changed files with 218 additions and 26 deletions

View 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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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;