/* * @Author: ZhaoYing * @Date: 2026-02-03 17:44:15 * @Last Modified by: ZhaoYing * @Last Modified time: 2026-04-21 16:30:26 */ /** * Prompt Editor Component * AI-powered prompt optimization with chat interface and variable support */ import { type FC, useState, useRef, useEffect } from 'react'; import { Button, Form, Input, App, Flex, Space } from 'antd'; import { useTranslation } from 'react-i18next'; import clsx from 'clsx' import copy from 'copy-to-clipboard'; import { useNavigate, useLocation } from 'react-router-dom'; import { updatePromptMessages, createPromptSessions } from '@/api/prompt' import type { PromptVariableModalRef, AiPromptForm, HistoryItem, PromptSaveModalRef } from './types' import ChatContent from '@/components/Chat/ChatContent' import Empty from '@/components/Empty' import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpty.svg' import type { ChatItem } from '@/components/Chat/types' import ModelSelect from '@/components/ModelSelect' import PromptVariableModal from './components/PromptVariableModal' import { type SSEMessage } from '@/utils/stream' import Editor from '@/views/ApplicationConfig/components/Editor' import PromptSaveModal from './components/PromptSaveModal' import analysisEmptyIcon from '@/assets/images/conversation/analysisEmpty.png' import Header from './components/Header'; import RbCard from '@/components/RbCard/Card'; import styles from './index.module.css' const Prompt: FC = () => { const { t } = useTranslation(); const navigate = useNavigate(); const { state } = useLocation() 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 editorRef = useRef(null) const currentPromptValueRef = useRef(undefined) const abortRef = useRef<(() => void) | null>(null) const values = Form.useWatch([], form) const [editVo, setEditVo] = useState(null) useEffect(() => { return () => { abortRef.current?.() abortRef.current = null } }, []) useEffect(() => { setEditVo(state) }, [state]) useEffect(() => { if (editVo?.id) { form.setFieldValue('current_prompt', editVo.prompt) setChatList([]) } updateSession() }, [editVo]) /** Update session ID */ const updateSession = () => { console.log('updateSession') createPromptSessions().then(res => { const response = res as { id: string } setPromptSession(response.id) }) } /** Send message to AI for prompt optimization */ const handleSend = () => { if (!promptSession || loading || !values.message || values.message.trim() == '') return if (!values.model_id) { message.warning(t('common.selectPlaceholder', { title: t('prompt.model') })) return } if (!values.message) { message.warning(t('prompt.promptChatPlaceholder')) return } const messageContent = values.message setLoading(true) setChatList(prev => { return [...prev, { role: 'user', content: messageContent}] }) form.setFieldsValue({ message: undefined, current_prompt: undefined }) const handleStreamMessage = (data: SSEMessage[]) => { data.map(item => { const { content, desc, variables } = item.data as { content: string; desc: string; variables: string[] }; switch (item.event) { case 'start': currentPromptValueRef.current = '' if (editorRef.current?.clear) { editorRef.current.clear(); } break; case 'message': 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) { setChatList(prev => { return [...prev, { role: 'assistant', content: desc }] }) } if (variables) { setVariables(variables) } break; case 'end': setLoading(false) // Sync form values when stream ends form.setFieldsValue({ current_prompt: currentPromptValueRef.current }) break } }) }; updatePromptMessages((promptSession) as string, values, handleStreamMessage, undefined, (abort) => { abortRef.current = abort }) .finally(() => { setLoading(false) }) } /** Copy prompt to clipboard */ const handleCopy = () => { if (!values.current_prompt || values?.current_prompt?.trim() === '') return copy(values.current_prompt) message.success(t('common.copySuccess')) } /** Open variable modal */ const handleAdd = () => { aiPromptVariableModalRef.current?.handleOpen() } /** Apply variable to editor */ const handleVariableApply = (value: string) => { if (editorRef.current?.insertText) { editorRef.current.insertText(value) } else { form.setFieldValue('current_prompt', (values.current_prompt || '') + value) } } /** Save prompt */ const handleSave = () => { if (!values.current_prompt || !promptSession) { return } promptSaveModalRef.current?.handleOpen({ session_id: promptSession, prompt: values.current_prompt }) } /** Refresh editor and clear state */ const handleRefresh = () => { form.setFieldValue('current_prompt', undefined) currentPromptValueRef.current = undefined; setChatList([]) setEditVo(null) updateSession() } const [isFocus, setIsFocus] = useState(false) const [isComposing, setIsComposing] = useState(false) const handleFocus = () => { setIsFocus(true) } const handleBlur = () => { setIsFocus(false) } const handleJump = () => { navigate('/prompt/history') } return ( <>
} data={chatList || []} streamLoading={false} labelPosition="top" labelFormat={(item) => item.role === 'user' ? t(`prompt.you`) : t(`prompt.ai`)} /> setIsComposing(true)} onCompositionEnd={() => setIsComposing(false)} onKeyDown={(e) => { if (e.key === 'Enter' && !isComposing) handleSend() }} variant="borderless" className="rb:p-0!" onFocus={handleFocus} onBlur={handleBlur} />
} disabled={!values?.current_prompt || loading} onClick={handleSave} >{t('common.save')}
} disabled={!values?.current_prompt || loading} onClick={handleCopy} >{t('common.copy')} } > {values?.current_prompt ? { if (loading) return form.setFieldValue('current_prompt', value) }} /> : }
); }; export default Prompt;