@@ -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<PromptChatPanelRef, PromptChatPanelProps>((props, ref) => {
|
|
||||||
const [chatList, setChatList] = useState<ChatItem[]>([])
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
|
||||||
append: (item) => setChatList(prev => [...prev, item]),
|
|
||||||
clear: () => setChatList([]),
|
|
||||||
}))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ChatContent
|
|
||||||
classNames={props.classNames}
|
|
||||||
contentClassNames={props.contentClassNames}
|
|
||||||
empty={props.empty}
|
|
||||||
data={chatList}
|
|
||||||
streamLoading={false}
|
|
||||||
labelPosition="top"
|
|
||||||
labelFormat={props.labelFormat}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default PromptChatPanel
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 16:26:44
|
* @Date: 2026-02-03 16:26:44
|
||||||
* @Last Modified by: ZhaoYing
|
* @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
|
* AI Prompt Assistant Modal
|
||||||
@@ -20,9 +20,10 @@ import { updatePromptMessages, createPromptSessions } from '@/api/prompt'
|
|||||||
import type { AiPromptModalRef, AiPromptVariableModalRef, AiPromptForm } from '../types'
|
import type { AiPromptModalRef, AiPromptVariableModalRef, AiPromptForm } from '../types'
|
||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
import type { Model } from '@/views/ModelManagement/types'
|
import type { Model } from '@/views/ModelManagement/types'
|
||||||
|
import ChatContent from '@/components/Chat/ChatContent'
|
||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpty.svg'
|
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 ModelSelect from '@/components/ModelSelect'
|
||||||
import AiPromptVariableModal from './AiPromptVariableModal'
|
import AiPromptVariableModal from './AiPromptVariableModal'
|
||||||
import { type SSEMessage } from '@/utils/stream'
|
import { type SSEMessage } from '@/utils/stream'
|
||||||
@@ -54,14 +55,12 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [form] = Form.useForm<AiPromptForm>()
|
const [form] = Form.useForm<AiPromptForm>()
|
||||||
|
const [chatList, setChatList] = useState<ChatItem[]>([])
|
||||||
const [variables, setVariables] = useState<string[]>([])
|
const [variables, setVariables] = useState<string[]>([])
|
||||||
const [promptSession, setPromptSession] = useState<string | null>(null)
|
const [promptSession, setPromptSession] = useState<string | null>(null)
|
||||||
const [hasPrompt, setHasPrompt] = useState(false)
|
|
||||||
const aiPromptVariableModalRef = useRef<AiPromptVariableModalRef>(null)
|
const aiPromptVariableModalRef = useRef<AiPromptVariableModalRef>(null)
|
||||||
const chatPanelRef = useRef<PromptChatPanelRef>(null)
|
|
||||||
const editorRef = useRef<any>(null)
|
const editorRef = useRef<any>(null)
|
||||||
const currentPromptValueRef = useRef<string>('')
|
const currentPromptValueRef = useRef<string>('')
|
||||||
const isStreamingRef = useRef(false)
|
|
||||||
|
|
||||||
const values = Form.useWatch([], form)
|
const values = Form.useWatch([], form)
|
||||||
|
|
||||||
@@ -69,13 +68,12 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
chatPanelRef.current?.clear()
|
setChatList([])
|
||||||
setVariables([])
|
setVariables([])
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
message: undefined,
|
message: undefined,
|
||||||
current_prompt: undefined,
|
current_prompt: undefined,
|
||||||
})
|
})
|
||||||
setHasPrompt(false)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Open modal and create new prompt session */
|
/** Open modal and create new prompt session */
|
||||||
@@ -104,7 +102,9 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
}
|
}
|
||||||
const messageContent = values.message
|
const messageContent = values.message
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
chatPanelRef.current?.append({ role: 'user', content: messageContent })
|
setChatList(prev => {
|
||||||
|
return [...prev, { role: 'user', content: messageContent}]
|
||||||
|
})
|
||||||
form.setFieldsValue({ message: undefined, current_prompt: undefined })
|
form.setFieldsValue({ message: undefined, current_prompt: undefined })
|
||||||
|
|
||||||
const handleStreamMessage = (data: SSEMessage[]) => {
|
const handleStreamMessage = (data: SSEMessage[]) => {
|
||||||
@@ -114,8 +114,6 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
switch (item.event) {
|
switch (item.event) {
|
||||||
case 'start':
|
case 'start':
|
||||||
currentPromptValueRef.current = ''
|
currentPromptValueRef.current = ''
|
||||||
isStreamingRef.current = true
|
|
||||||
setHasPrompt(true)
|
|
||||||
if (editorRef.current?.clear) {
|
if (editorRef.current?.clear) {
|
||||||
editorRef.current.clear();
|
editorRef.current.clear();
|
||||||
}
|
}
|
||||||
@@ -125,12 +123,15 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
currentPromptValueRef.current += content;
|
currentPromptValueRef.current += content;
|
||||||
if (editorRef.current?.appendText) {
|
if (editorRef.current?.appendText) {
|
||||||
editorRef.current.appendText(content);
|
editorRef.current.appendText(content);
|
||||||
|
editorRef.current.scrollToBottom();
|
||||||
} else {
|
} else {
|
||||||
form.setFieldsValue({ current_prompt: currentPromptValueRef.current })
|
form.setFieldsValue({ current_prompt: currentPromptValueRef.current })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (desc) {
|
if (desc) {
|
||||||
chatPanelRef.current?.append({ role: 'assistant', content: desc })
|
setChatList(prev => {
|
||||||
|
return [...prev, { role: 'assistant', content: desc }]
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (variables) {
|
if (variables) {
|
||||||
setVariables(variables)
|
setVariables(variables)
|
||||||
@@ -138,7 +139,6 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
isStreamingRef.current = false
|
|
||||||
// Sync form value when stream ends
|
// Sync form value when stream ends
|
||||||
form.setFieldsValue({ current_prompt: currentPromptValueRef.current })
|
form.setFieldsValue({ current_prompt: currentPromptValueRef.current })
|
||||||
break
|
break
|
||||||
@@ -193,6 +193,7 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
setIsFocus(false)
|
setIsFocus(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(values)
|
||||||
return (
|
return (
|
||||||
<RbModal
|
<RbModal
|
||||||
title={t(`${source}.AIPromptAssistant`)}
|
title={t(`${source}.AIPromptAssistant`)}
|
||||||
@@ -206,7 +207,7 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
body: 'rb:p-0! rb:border-t rb:border-t-[#EBEBEB]'
|
body: 'rb:p-0! rb:border-t rb:border-t-[#EBEBEB]'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form form={form} className="rb:mx-4! rb:h-[calc(100vh-202px)]">
|
<Form form={form} className="rb:mx-4!">
|
||||||
<div className="rb:grid rb:grid-cols-2">
|
<div className="rb:grid rb:grid-cols-2">
|
||||||
<div className="rb:border-r rb:border-r-[#EBEBEB] rb:pr-4 rb:pt-3 rb:pb-4">
|
<div className="rb:border-r rb:border-r-[#EBEBEB] rb:pr-4 rb:pt-3 rb:pb-4">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -219,11 +220,13 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<PromptChatPanel
|
<ChatContent
|
||||||
ref={chatPanelRef}
|
classNames="rb:h-105.5 rb:pb-[15px]!"
|
||||||
classNames="rb:h-[calc(100vh-330px)] rb:pb-[15px]!"
|
|
||||||
contentClassNames="rb:max-w-75!"
|
contentClassNames="rb:max-w-75!"
|
||||||
empty={<Empty url={ConversationEmptyIcon} title={t(`${source}.promptChatEmpty`)} isNeedSubTitle={false} size={[140, 100]} className="rb:h-full" />}
|
empty={<Empty url={ConversationEmptyIcon} title={t(`${source}.promptChatEmpty`)} isNeedSubTitle={false} size={[140, 100]} className="rb:h-full" />}
|
||||||
|
data={chatList || []}
|
||||||
|
streamLoading={false}
|
||||||
|
labelPosition="top"
|
||||||
labelFormat={(item) => item.role === 'user' ? t(`${source}.you`) : t(`${source}.ai`)}
|
labelFormat={(item) => item.role === 'user' ? t(`${source}.you`) : t(`${source}.ai`)}
|
||||||
/>
|
/>
|
||||||
<Flex align="center" gap={12} justify="space-between"
|
<Flex align="center" gap={12} justify="space-between"
|
||||||
@@ -285,21 +288,16 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{hasPrompt
|
<Form.Item name="current_prompt" noStyle>
|
||||||
? <Form.Item name="current_prompt" noStyle>
|
{values?.current_prompt
|
||||||
<Editor
|
? <Editor
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
height="rb:h-[calc(100vh-276px)]"
|
className="rb:h-119 rb:bg-white! rb:border-none! rb:p-0!"
|
||||||
className={clsx('rb:bg-white! rb:border-none! rb:p-0!')}
|
onChange={(value) => form.setFieldValue('current_prompt', value)}
|
||||||
onChange={(value) => {
|
|
||||||
if (!isStreamingRef.current) {
|
|
||||||
form.setFieldValue('current_prompt', value)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
: <Empty url={analysisEmptyIcon} title={t(`${source}.promptOptimizationEmpty`)} isNeedSubTitle={false} size={[270, 170]} className="rb:h-119 rb:w-70 rb:mx-auto! rb:text-center! rb:text-[12px]! rb:leading-4!" />
|
||||||
: <Empty url={analysisEmptyIcon} title={t(`${source}.promptOptimizationEmpty`)} isNeedSubTitle={false} size={[270, 170]} className="rb:h-119 rb:w-70 rb:mx-auto! rb:text-center! rb:text-[12px]! rb:leading-4!" />
|
}
|
||||||
}
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 16:25:17
|
* @Date: 2026-02-03 16:25:17
|
||||||
* @Last Modified by: ZhaoYing
|
* @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
|
* Rich text editor component using Lexical framework
|
||||||
* Provides text editing with insert, append, clear, and scroll capabilities
|
* 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 clsx from 'clsx';
|
||||||
import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
||||||
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
|
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
|
||||||
@@ -50,7 +50,7 @@ interface LexicalEditorProps {
|
|||||||
/** Callback when content changes */
|
/** Callback when content changes */
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
/** Editor height in pixels */
|
/** Editor height in pixels */
|
||||||
height?: string;
|
height?: number;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,42 +73,9 @@ const EditorContent = forwardRef<EditorRef, LexicalEditorProps>(({
|
|||||||
value,
|
value,
|
||||||
placeholder = "Please enter content...",
|
placeholder = "Please enter content...",
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled
|
||||||
height
|
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
|
||||||
const pendingTextRef = useRef<string>('');
|
|
||||||
const rafRef = useRef<number | null>(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
|
* Expose editor methods to parent component
|
||||||
@@ -127,33 +94,24 @@ const EditorContent = forwardRef<EditorRef, LexicalEditorProps>(({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
appendText: (text: string) => {
|
appendText: (text: string) => {
|
||||||
pendingTextRef.current += text;
|
editor.update(() => {
|
||||||
if (rafRef.current !== null) return;
|
const root = $getRoot();
|
||||||
rafRef.current = requestAnimationFrame(() => {
|
const lastChild = root.getLastChild();
|
||||||
rafRef.current = null;
|
if (lastChild && $isParagraphNode(lastChild)) {
|
||||||
const batch = pendingTextRef.current;
|
const lastTextNode = lastChild.getLastChild();
|
||||||
pendingTextRef.current = '';
|
if (lastTextNode && $isTextNode(lastTextNode)) {
|
||||||
if (scrollRef.current) scrollTopRef.current = scrollRef.current.scrollTop;
|
const currentText = lastTextNode.getTextContent();
|
||||||
isAppendingRef.current = true;
|
lastTextNode.setTextContent(currentText + text);
|
||||||
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));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const paragraph = $createParagraphNode();
|
const textNode = $createTextNode(text);
|
||||||
paragraph.append($createTextNode(batch));
|
lastChild.append(textNode);
|
||||||
root.append(paragraph);
|
|
||||||
}
|
}
|
||||||
}, {
|
} else {
|
||||||
tag: 'append-text',
|
const paragraph = $createParagraphNode();
|
||||||
onUpdate: () => { isAppendingRef.current = false; }
|
const textNode = $createTextNode(text);
|
||||||
});
|
paragraph.append(textNode);
|
||||||
|
root.append(paragraph);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
clear: () => {
|
clear: () => {
|
||||||
@@ -164,16 +122,21 @@ const EditorContent = forwardRef<EditorRef, LexicalEditorProps>(({
|
|||||||
root.append(paragraph);
|
root.append(paragraph);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
scrollToBottom,
|
scrollToBottom: () => {
|
||||||
|
const editorElement = editor.getRootElement();
|
||||||
|
if (editorElement) {
|
||||||
|
editorElement.scrollTop = editorElement.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
}), [editor]);
|
}), [editor]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={scrollRef} style={{ position: 'relative' }} className={height ? `${height} rb:overflow-y-auto` : ''}>
|
<div style={{ position: 'relative' }}>
|
||||||
<RichTextPlugin
|
<RichTextPlugin
|
||||||
contentEditable={
|
contentEditable={
|
||||||
<ContentEditable
|
<ContentEditable
|
||||||
className={clsx(
|
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:rounded-lg",
|
"rb:outline-none rb:resize-none rb:text-[14px] rb:leading-5 rb:px-4 rb:py-5 rb:bg-[#FBFDFF] rb-border rb:rounded-lg rb:overflow-auto",
|
||||||
disabled && "rb:cursor-not-allowed rb:bg-[#F6F8FC] rb:text-[#5B6167]",
|
disabled && "rb:cursor-not-allowed rb:bg-[#F6F8FC] rb:text-[#5B6167]",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 17:44:15
|
* @Date: 2026-02-03 17:44:15
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-04-15 14:25:17
|
* @Last Modified time: 2026-03-27 15:14:58
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Prompt Editor Component
|
* Prompt Editor Component
|
||||||
@@ -18,9 +18,10 @@ import { useNavigate, useLocation } from 'react-router-dom';
|
|||||||
|
|
||||||
import { updatePromptMessages, createPromptSessions } from '@/api/prompt'
|
import { updatePromptMessages, createPromptSessions } from '@/api/prompt'
|
||||||
import type { PromptVariableModalRef, AiPromptForm, HistoryItem, PromptSaveModalRef } from './types'
|
import type { PromptVariableModalRef, AiPromptForm, HistoryItem, PromptSaveModalRef } from './types'
|
||||||
|
import ChatContent from '@/components/Chat/ChatContent'
|
||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpty.svg'
|
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 ModelSelect from '@/components/ModelSelect'
|
||||||
import PromptVariableModal from './components/PromptVariableModal'
|
import PromptVariableModal from './components/PromptVariableModal'
|
||||||
import { type SSEMessage } from '@/utils/stream'
|
import { type SSEMessage } from '@/utils/stream'
|
||||||
@@ -38,15 +39,13 @@ const Prompt: FC = () => {
|
|||||||
const { message } = App.useApp()
|
const { message } = App.useApp()
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [form] = Form.useForm<AiPromptForm>()
|
const [form] = Form.useForm<AiPromptForm>()
|
||||||
|
const [chatList, setChatList] = useState<ChatItem[]>([])
|
||||||
const [variables, setVariables] = useState<string[]>([])
|
const [variables, setVariables] = useState<string[]>([])
|
||||||
const [promptSession, setPromptSession] = useState<string | null>(null)
|
const [promptSession, setPromptSession] = useState<string | null>(null)
|
||||||
const aiPromptVariableModalRef = useRef<PromptVariableModalRef>(null)
|
const aiPromptVariableModalRef = useRef<PromptVariableModalRef>(null)
|
||||||
const promptSaveModalRef = useRef<PromptSaveModalRef>(null)
|
const promptSaveModalRef = useRef<PromptSaveModalRef>(null)
|
||||||
const chatPanelRef = useRef<PromptChatPanelRef>(null)
|
|
||||||
const editorRef = useRef<any>(null)
|
const editorRef = useRef<any>(null)
|
||||||
const currentPromptValueRef = useRef<string>(undefined)
|
const currentPromptValueRef = useRef<string>(undefined)
|
||||||
const isStreamingRef = useRef(false)
|
|
||||||
const [hasPrompt, setHasPrompt] = useState(false)
|
|
||||||
const values = Form.useWatch([], form)
|
const values = Form.useWatch([], form)
|
||||||
const [editVo, setEditVo] = useState<HistoryItem | null>(null)
|
const [editVo, setEditVo] = useState<HistoryItem | null>(null)
|
||||||
|
|
||||||
@@ -57,14 +56,14 @@ const Prompt: FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editVo?.id) {
|
if (editVo?.id) {
|
||||||
form.setFieldValue('current_prompt', editVo.prompt)
|
form.setFieldValue('current_prompt', editVo.prompt)
|
||||||
setHasPrompt(true)
|
setChatList([])
|
||||||
chatPanelRef.current?.clear()
|
|
||||||
}
|
}
|
||||||
updateSession()
|
updateSession()
|
||||||
}, [editVo])
|
}, [editVo])
|
||||||
|
|
||||||
/** Update session ID */
|
/** Update session ID */
|
||||||
const updateSession = () => {
|
const updateSession = () => {
|
||||||
|
console.log('updateSession')
|
||||||
createPromptSessions().then(res => {
|
createPromptSessions().then(res => {
|
||||||
const response = res as { id: string }
|
const response = res as { id: string }
|
||||||
setPromptSession(response.id)
|
setPromptSession(response.id)
|
||||||
@@ -84,7 +83,9 @@ const Prompt: FC = () => {
|
|||||||
}
|
}
|
||||||
const messageContent = values.message
|
const messageContent = values.message
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
chatPanelRef.current?.append({ role: 'user', content: messageContent })
|
setChatList(prev => {
|
||||||
|
return [...prev, { role: 'user', content: messageContent}]
|
||||||
|
})
|
||||||
form.setFieldsValue({ message: undefined, current_prompt: undefined })
|
form.setFieldsValue({ message: undefined, current_prompt: undefined })
|
||||||
|
|
||||||
const handleStreamMessage = (data: SSEMessage[]) => {
|
const handleStreamMessage = (data: SSEMessage[]) => {
|
||||||
@@ -94,35 +95,33 @@ const Prompt: FC = () => {
|
|||||||
switch (item.event) {
|
switch (item.event) {
|
||||||
case 'start':
|
case 'start':
|
||||||
currentPromptValueRef.current = ''
|
currentPromptValueRef.current = ''
|
||||||
isStreamingRef.current = true
|
|
||||||
setHasPrompt(true)
|
|
||||||
if (editorRef.current?.clear) {
|
if (editorRef.current?.clear) {
|
||||||
editorRef.current.clear();
|
editorRef.current.clear();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'message':
|
case 'message':
|
||||||
if (content) {
|
if (typeof content === 'string') {
|
||||||
currentPromptValueRef.current += content;
|
currentPromptValueRef.current += content;
|
||||||
if (editorRef.current?.appendText) {
|
if (editorRef.current?.appendText) {
|
||||||
editorRef.current.appendText(content);
|
editorRef.current.appendText(content);
|
||||||
|
editorRef.current.scrollToBottom();
|
||||||
} else {
|
} else {
|
||||||
form.setFieldsValue({ current_prompt: currentPromptValueRef.current })
|
form.setFieldsValue({ current_prompt: currentPromptValueRef.current })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (desc) {
|
if (desc) {
|
||||||
chatPanelRef.current?.append({ role: 'assistant', content: desc })
|
setChatList(prev => {
|
||||||
|
return [...prev, { role: 'assistant', content: desc }]
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (variables) {
|
if (variables) {
|
||||||
setVariables(variables)
|
setVariables(variables)
|
||||||
}
|
}
|
||||||
console.log('currentPromptValueRef.current', currentPromptValueRef.current)
|
|
||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
isStreamingRef.current = false
|
|
||||||
// Sync form values when stream ends
|
// Sync form values when stream ends
|
||||||
form.setFieldsValue({ current_prompt: currentPromptValueRef.current })
|
form.setFieldsValue({ current_prompt: currentPromptValueRef.current })
|
||||||
console.log('currentPromptValueRef.current', currentPromptValueRef.current)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -165,8 +164,7 @@ const Prompt: FC = () => {
|
|||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
form.setFieldValue('current_prompt', undefined)
|
form.setFieldValue('current_prompt', undefined)
|
||||||
currentPromptValueRef.current = undefined;
|
currentPromptValueRef.current = undefined;
|
||||||
setHasPrompt(false)
|
setChatList([])
|
||||||
chatPanelRef.current?.clear()
|
|
||||||
setEditVo(null)
|
setEditVo(null)
|
||||||
updateSession()
|
updateSession()
|
||||||
}
|
}
|
||||||
@@ -195,11 +193,13 @@ const Prompt: FC = () => {
|
|||||||
headerType="borderless"
|
headerType="borderless"
|
||||||
bodyClassName="rb:px-4! rb:pt-0! rb:pb-3!"
|
bodyClassName="rb:px-4! rb:pt-0! rb:pb-3!"
|
||||||
>
|
>
|
||||||
<PromptChatPanel
|
<ChatContent
|
||||||
ref={chatPanelRef}
|
|
||||||
classNames="rb:h-[calc(100vh-257px)] rb:mb-[12px]!"
|
classNames="rb:h-[calc(100vh-257px)] rb:mb-[12px]!"
|
||||||
contentClassNames="rb:max-w-75!"
|
contentClassNames="rb:max-w-75!"
|
||||||
empty={<Empty url={ConversationEmptyIcon} title={t(`prompt.promptChatEmpty`)} isNeedSubTitle={false} size={[140, 100]} className="rb:h-full" />}
|
empty={<Empty url={ConversationEmptyIcon} title={t(`prompt.promptChatEmpty`)} isNeedSubTitle={false} size={[140, 100]} className="rb:h-full" />}
|
||||||
|
data={chatList || []}
|
||||||
|
streamLoading={false}
|
||||||
|
labelPosition="top"
|
||||||
labelFormat={(item) => item.role === 'user' ? t(`prompt.you`) : t(`prompt.ai`)}
|
labelFormat={(item) => item.role === 'user' ? t(`prompt.you`) : t(`prompt.ai`)}
|
||||||
/>
|
/>
|
||||||
<Flex align="center" gap={12} justify="space-between"
|
<Flex align="center" gap={12} justify="space-between"
|
||||||
@@ -275,21 +275,16 @@ const Prompt: FC = () => {
|
|||||||
></Button>
|
></Button>
|
||||||
</Space>}
|
</Space>}
|
||||||
>
|
>
|
||||||
{hasPrompt
|
<Form.Item name="current_prompt" noStyle>
|
||||||
? <Form.Item name="current_prompt" noStyle>
|
{values?.current_prompt
|
||||||
<Editor
|
? <Editor
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
height="rb:h-[calc(100vh-193px)]"
|
className="rb:h-[calc(100vh-193px)] rb:bg-white! rb:border-none! rb:p-0! rb:text-[#212332] rb:leading-5"
|
||||||
className="rb:bg-white! rb:border-none! rb:p-0! rb:text-[#212332] rb:leading-5"
|
onChange={(value) => form.setFieldValue('current_prompt', value)}
|
||||||
onChange={(value) => {
|
|
||||||
if (!isStreamingRef.current) {
|
|
||||||
form.setFieldValue('current_prompt', value)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
: <Empty url={analysisEmptyIcon} title={t(`prompt.promptPlaceholder`)} isNeedSubTitle={false} size={[270, 170]} className="rb:h-[calc(100vh-193px)] rb:mx-auto! rb:text-center! rb:text-[12px]! rb:leading-4!" />
|
||||||
: <Empty url={analysisEmptyIcon} title={t(`prompt.promptPlaceholder`)} isNeedSubTitle={false} size={[270, 170]} className="rb:h-[calc(100vh-193px)] rb:mx-auto! rb:text-center! rb:text-[12px]! rb:leading-4!" />
|
}
|
||||||
}
|
</Form.Item>
|
||||||
</RbCard>
|
</RbCard>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user