diff --git a/web/src/components/Chat/PromptChatPanel.tsx b/web/src/components/Chat/PromptChatPanel.tsx deleted file mode 100644 index 51343ae1..00000000 --- a/web/src/components/Chat/PromptChatPanel.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { forwardRef, useImperativeHandle, useState } from 'react' -import ChatContent from './ChatContent' -import type { ChatItem } from './types' -import type { ReactNode } from 'react' - -export interface PromptChatPanelRef { - append: (item: ChatItem) => void - clear: () => void -} - -interface PromptChatPanelProps { - classNames?: string - contentClassNames?: string - empty: ReactNode - labelFormat: (item: ChatItem) => any -} - -const PromptChatPanel = forwardRef((props, ref) => { - const [chatList, setChatList] = useState([]) - - useImperativeHandle(ref, () => ({ - append: (item) => setChatList(prev => [...prev, item]), - clear: () => setChatList([]), - })) - - return ( - - ) -}) - -export default PromptChatPanel diff --git a/web/src/views/ApplicationConfig/components/AiPromptModal.tsx b/web/src/views/ApplicationConfig/components/AiPromptModal.tsx index fd2dc595..1666e075 100644 --- a/web/src/views/ApplicationConfig/components/AiPromptModal.tsx +++ b/web/src/views/ApplicationConfig/components/AiPromptModal.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:26:44 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-04-15 14:21:55 + * @Last Modified time: 2026-03-20 13:53:05 */ /** * AI Prompt Assistant Modal @@ -20,9 +20,10 @@ import { updatePromptMessages, createPromptSessions } from '@/api/prompt' import type { AiPromptModalRef, AiPromptVariableModalRef, AiPromptForm } from '../types' import RbModal from '@/components/RbModal' import type { Model } from '@/views/ModelManagement/types' +import ChatContent from '@/components/Chat/ChatContent' import Empty from '@/components/Empty' import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpty.svg' -import PromptChatPanel, { type PromptChatPanelRef } from '@/components/Chat/PromptChatPanel' +import type { ChatItem } from '@/components/Chat/types' import ModelSelect from '@/components/ModelSelect' import AiPromptVariableModal from './AiPromptVariableModal' import { type SSEMessage } from '@/utils/stream' @@ -54,14 +55,12 @@ const AiPromptModal = forwardRef(({ const [visible, setVisible] = useState(false); const [loading, setLoading] = useState(false) const [form] = Form.useForm() + const [chatList, setChatList] = useState([]) const [variables, setVariables] = useState([]) const [promptSession, setPromptSession] = useState(null) - const [hasPrompt, setHasPrompt] = useState(false) const aiPromptVariableModalRef = useRef(null) - const chatPanelRef = useRef(null) const editorRef = useRef(null) const currentPromptValueRef = useRef('') - const isStreamingRef = useRef(false) const values = Form.useWatch([], form) @@ -69,13 +68,12 @@ const AiPromptModal = forwardRef(({ const handleClose = () => { setVisible(false); setLoading(false) - chatPanelRef.current?.clear() + setChatList([]) setVariables([]) form.setFieldsValue({ message: undefined, current_prompt: undefined, }) - setHasPrompt(false) }; /** Open modal and create new prompt session */ @@ -104,7 +102,9 @@ const AiPromptModal = forwardRef(({ } const messageContent = values.message setLoading(true) - chatPanelRef.current?.append({ role: 'user', content: messageContent }) + setChatList(prev => { + return [...prev, { role: 'user', content: messageContent}] + }) form.setFieldsValue({ message: undefined, current_prompt: undefined }) const handleStreamMessage = (data: SSEMessage[]) => { @@ -114,8 +114,6 @@ const AiPromptModal = forwardRef(({ switch (item.event) { case 'start': currentPromptValueRef.current = '' - isStreamingRef.current = true - setHasPrompt(true) if (editorRef.current?.clear) { editorRef.current.clear(); } @@ -125,12 +123,15 @@ const AiPromptModal = forwardRef(({ currentPromptValueRef.current += content; if (editorRef.current?.appendText) { editorRef.current.appendText(content); + editorRef.current.scrollToBottom(); } else { form.setFieldsValue({ current_prompt: currentPromptValueRef.current }) } } if (desc) { - chatPanelRef.current?.append({ role: 'assistant', content: desc }) + setChatList(prev => { + return [...prev, { role: 'assistant', content: desc }] + }) } if (variables) { setVariables(variables) @@ -138,7 +139,6 @@ const AiPromptModal = forwardRef(({ break; case 'end': setLoading(false) - isStreamingRef.current = false // Sync form value when stream ends form.setFieldsValue({ current_prompt: currentPromptValueRef.current }) break @@ -193,6 +193,7 @@ const AiPromptModal = forwardRef(({ setIsFocus(false) } + console.log(values) return ( (({ body: 'rb:p-0! rb:border-t rb:border-t-[#EBEBEB]' }} > -
+
(({ /> - } + data={chatList || []} + streamLoading={false} + labelPosition="top" labelFormat={(item) => item.role === 'user' ? t(`${source}.you`) : t(`${source}.ai`)} /> (({ - {hasPrompt - ? - + {values?.current_prompt + ? { - if (!isStreamingRef.current) { - form.setFieldValue('current_prompt', value) - } - }} + className="rb:h-119 rb:bg-white! rb:border-none! rb:p-0!" + onChange={(value) => form.setFieldValue('current_prompt', value)} /> - - : - } + : + } +
diff --git a/web/src/views/ApplicationConfig/components/Editor/index.tsx b/web/src/views/ApplicationConfig/components/Editor/index.tsx index c3edc1fc..ab89a610 100644 --- a/web/src/views/ApplicationConfig/components/Editor/index.tsx +++ b/web/src/views/ApplicationConfig/components/Editor/index.tsx @@ -2,14 +2,14 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:25:17 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-04-15 14:00:07 + * @Last Modified time: 2026-02-26 11:18:04 */ /** * Rich text editor component using Lexical framework * Provides text editing with insert, append, clear, and scroll capabilities */ -import {forwardRef, useEffect, useImperativeHandle, useRef } from 'react'; +import {forwardRef, useImperativeHandle } from 'react'; import clsx from 'clsx'; import { LexicalComposer } from '@lexical/react/LexicalComposer'; import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; @@ -50,7 +50,7 @@ interface LexicalEditorProps { /** Callback when content changes */ onChange?: (value: string) => void; /** Editor height in pixels */ - height?: string; + height?: number; disabled?: boolean; } @@ -73,42 +73,9 @@ const EditorContent = forwardRef(({ value, placeholder = "Please enter content...", onChange, - disabled, - height + disabled }, ref) => { const [editor] = useLexicalComposerContext(); - const scrollRef = useRef(null); - const pendingTextRef = useRef(''); - const rafRef = useRef(null); - const isAppendingRef = useRef(false); - const scrollTopRef = useRef(0); - - useEffect(() => { - const el = scrollRef.current; - if (!el) return; - const onPointerDown = () => { - if (!isAppendingRef.current) scrollTopRef.current = el.scrollTop; - }; - el.addEventListener('pointerdown', onPointerDown); - return () => el.removeEventListener('pointerdown', onPointerDown); - }, []); - - useEffect(() => { - return editor.registerUpdateListener(({ tags }) => { - if (!scrollRef.current) return; - if (tags.has('append-text')) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - } else { - scrollRef.current.scrollTop = scrollTopRef.current; - } - }); - }, [editor]); - - const scrollToBottom = () => { - if (scrollRef.current) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - } - }; /** * Expose editor methods to parent component @@ -127,33 +94,24 @@ const EditorContent = forwardRef(({ }); }, appendText: (text: string) => { - pendingTextRef.current += text; - if (rafRef.current !== null) return; - rafRef.current = requestAnimationFrame(() => { - rafRef.current = null; - const batch = pendingTextRef.current; - pendingTextRef.current = ''; - if (scrollRef.current) scrollTopRef.current = scrollRef.current.scrollTop; - isAppendingRef.current = true; - editor.update(() => { - const root = $getRoot(); - const lastChild = root.getLastChild(); - if (lastChild && $isParagraphNode(lastChild)) { - const lastTextNode = lastChild.getLastChild(); - if (lastTextNode && $isTextNode(lastTextNode)) { - lastTextNode.setTextContent(lastTextNode.getTextContent() + batch); - } else { - lastChild.append($createTextNode(batch)); - } + editor.update(() => { + const root = $getRoot(); + const lastChild = root.getLastChild(); + if (lastChild && $isParagraphNode(lastChild)) { + const lastTextNode = lastChild.getLastChild(); + if (lastTextNode && $isTextNode(lastTextNode)) { + const currentText = lastTextNode.getTextContent(); + lastTextNode.setTextContent(currentText + text); } else { - const paragraph = $createParagraphNode(); - paragraph.append($createTextNode(batch)); - root.append(paragraph); + const textNode = $createTextNode(text); + lastChild.append(textNode); } - }, { - tag: 'append-text', - onUpdate: () => { isAppendingRef.current = false; } - }); + } else { + const paragraph = $createParagraphNode(); + const textNode = $createTextNode(text); + paragraph.append(textNode); + root.append(paragraph); + } }); }, clear: () => { @@ -164,16 +122,21 @@ const EditorContent = forwardRef(({ root.append(paragraph); }); }, - scrollToBottom, + scrollToBottom: () => { + const editorElement = editor.getRootElement(); + if (editorElement) { + editorElement.scrollTop = editorElement.scrollHeight; + } + } }), [editor]); return ( -
+
{ const { message } = App.useApp() const [loading, setLoading] = useState(false) const [form] = Form.useForm() + const [chatList, setChatList] = useState([]) const [variables, setVariables] = useState([]) const [promptSession, setPromptSession] = useState(null) const aiPromptVariableModalRef = useRef(null) const promptSaveModalRef = useRef(null) - const chatPanelRef = useRef(null) const editorRef = useRef(null) const currentPromptValueRef = useRef(undefined) - const isStreamingRef = useRef(false) - const [hasPrompt, setHasPrompt] = useState(false) const values = Form.useWatch([], form) const [editVo, setEditVo] = useState(null) @@ -57,14 +56,14 @@ const Prompt: FC = () => { useEffect(() => { if (editVo?.id) { form.setFieldValue('current_prompt', editVo.prompt) - setHasPrompt(true) - chatPanelRef.current?.clear() + setChatList([]) } updateSession() }, [editVo]) /** Update session ID */ const updateSession = () => { + console.log('updateSession') createPromptSessions().then(res => { const response = res as { id: string } setPromptSession(response.id) @@ -84,7 +83,9 @@ const Prompt: FC = () => { } const messageContent = values.message setLoading(true) - chatPanelRef.current?.append({ role: 'user', content: messageContent }) + setChatList(prev => { + return [...prev, { role: 'user', content: messageContent}] + }) form.setFieldsValue({ message: undefined, current_prompt: undefined }) const handleStreamMessage = (data: SSEMessage[]) => { @@ -94,35 +95,33 @@ const Prompt: FC = () => { switch (item.event) { case 'start': currentPromptValueRef.current = '' - isStreamingRef.current = true - setHasPrompt(true) if (editorRef.current?.clear) { editorRef.current.clear(); } break; case 'message': - if (content) { + if (typeof content === 'string') { currentPromptValueRef.current += content; if (editorRef.current?.appendText) { editorRef.current.appendText(content); + editorRef.current.scrollToBottom(); } else { form.setFieldsValue({ current_prompt: currentPromptValueRef.current }) } } if (desc) { - chatPanelRef.current?.append({ role: 'assistant', content: desc }) + setChatList(prev => { + return [...prev, { role: 'assistant', content: desc }] + }) } if (variables) { setVariables(variables) } - console.log('currentPromptValueRef.current', currentPromptValueRef.current) break; case 'end': setLoading(false) - isStreamingRef.current = false // Sync form values when stream ends form.setFieldsValue({ current_prompt: currentPromptValueRef.current }) - console.log('currentPromptValueRef.current', currentPromptValueRef.current) break } }) @@ -165,8 +164,7 @@ const Prompt: FC = () => { const handleRefresh = () => { form.setFieldValue('current_prompt', undefined) currentPromptValueRef.current = undefined; - setHasPrompt(false) - chatPanelRef.current?.clear() + setChatList([]) setEditVo(null) updateSession() } @@ -195,11 +193,13 @@ const Prompt: FC = () => { headerType="borderless" bodyClassName="rb:px-4! rb:pt-0! rb:pb-3!" > - } + data={chatList || []} + streamLoading={false} + labelPosition="top" labelFormat={(item) => item.role === 'user' ? t(`prompt.you`) : t(`prompt.ai`)} /> { > } > - {hasPrompt - ? - + {values?.current_prompt + ? { - if (!isStreamingRef.current) { - form.setFieldValue('current_prompt', value) - } - }} + className="rb:h-[calc(100vh-193px)] rb:bg-white! rb:border-none! rb:p-0! rb:text-[#212332] rb:leading-5" + onChange={(value) => form.setFieldValue('current_prompt', value)} /> - - : - } + : + } +