From 77b9a6a94ef55781deb7646bcb522b166491aeb7 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Sat, 7 Mar 2026 15:00:40 +0800 Subject: [PATCH] feat(web): prompt ui upgrade --- web/src/views/Prompt/History.tsx | 109 ------- web/src/views/Prompt/Prompt.tsx | 246 --------------- web/src/views/Prompt/components/Header.tsx | 16 + web/src/views/Prompt/index.module.css | 3 + web/src/views/Prompt/index.tsx | 336 ++++++++++++++++++--- web/src/views/Prompt/pages/History.tsx | 140 +++++++++ 6 files changed, 445 insertions(+), 405 deletions(-) delete mode 100644 web/src/views/Prompt/History.tsx delete mode 100644 web/src/views/Prompt/Prompt.tsx create mode 100644 web/src/views/Prompt/components/Header.tsx create mode 100644 web/src/views/Prompt/index.module.css create mode 100644 web/src/views/Prompt/pages/History.tsx diff --git a/web/src/views/Prompt/History.tsx b/web/src/views/Prompt/History.tsx deleted file mode 100644 index 55327e4f..00000000 --- a/web/src/views/Prompt/History.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * @Author: ZhaoYing - * @Date: 2026-02-03 17:44:04 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 17:44:04 - */ -/** - * Prompt History Component - * Displays saved prompts with view, edit, and delete actions - */ - -import React, { useRef, type MouseEvent } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Tooltip, Space, App } from 'antd'; -import { EyeOutlined } from '@ant-design/icons'; - -import type { HistoryQuery, HistoryItem, PromptDetailRef } from './types'; -import RbCard from '@/components/RbCard/Card' -import { getPromptReleaseListUrl, deletePrompt } from '@/api/prompt' -import Markdown from '@/components/Markdown'; -import { formatDateTime } from '@/utils/format' -import PromptDetail from './components/PromptDetail' -import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList' - -const History: React.FC<{ query: HistoryQuery; edit: (item: HistoryItem) => void; }> = ({ query, edit }) => { - const { t } = useTranslation(); - const scrollListRef = useRef(null) - const detailRef = useRef(null) - const { message, modal } = App.useApp() - - /** View prompt details */ - const handleView = (item: HistoryItem) => { - detailRef.current?.handleOpen(item) - } - /** Delete prompt */ - const handleDelete = (item: HistoryItem, e?: MouseEvent) => { - e?.preventDefault(); - e?.stopPropagation(); - modal.confirm({ - title: t('common.confirmDeleteDesc', { name: item.title }), - okText: t('common.delete'), - cancelText: t('common.cancel'), - okType: 'danger', - onOk: () => { - deletePrompt(item.id).then(() => { - message.success(t('common.deleteSuccess')) - scrollListRef.current?.refresh() - detailRef.current?.handleClose() - }) - } - }) - - } - /** Edit prompt */ - const handleEdit = (item: HistoryItem) => { - edit(item) - } - - return ( - <> - { - const historyItem = item as unknown as HistoryItem; - return ( - {historyItem.title}} - extra={
{formatDateTime(historyItem.created_at, 'YYYY/MM/DD HH:mm')}
} - onClick={() => handleView(historyItem)} - > -
- -
- -
- - handleView(historyItem)} /> -
handleEdit(historyItem)} - >
-
handleDelete(historyItem, e)} - >
-
-
-
- ); - }} - /> - - - - ); -}; - -export default History; \ No newline at end of file diff --git a/web/src/views/Prompt/Prompt.tsx b/web/src/views/Prompt/Prompt.tsx deleted file mode 100644 index 01fdb35b..00000000 --- a/web/src/views/Prompt/Prompt.tsx +++ /dev/null @@ -1,246 +0,0 @@ -/* - * @Author: ZhaoYing - * @Date: 2026-02-03 17:44:15 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 17:44:15 - */ -/** - * 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, Row, Col } from 'antd'; -import { useTranslation } from 'react-i18next'; -import clsx from 'clsx' -import copy from 'copy-to-clipboard'; - -import { updatePromptMessages, createPromptSessions } from '@/api/prompt' -import { getModelListUrl } from '@/api/models' -import type { PromptVariableModalRef, AiPromptForm, HistoryItem, PromptSaveModalRef } from './types' -import ChatContent from '@/components/Chat/ChatContent' -import Empty from '@/components/Empty' -import ChatSendIcon from '@/assets/images/application/chatSend.svg' -import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpty.svg' -import type { ChatItem } from '@/components/Chat/types' -import CustomSelect from '@/components/CustomSelect' -import PromptVariableModal from './components/PromptVariableModal' -import { type SSEMessage } from '@/utils/stream' -import Editor from '@/views/ApplicationConfig/components/Editor' -import PromptSaveModal from './components/PromptSaveModal' - -const Prompt: FC<{ editVo: HistoryItem | null; refresh: () => void; }> = ({ editVo, refresh }) => { - const { t } = useTranslation(); - 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 values = Form.useWatch([], form) - - 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) 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) - .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([]) - refresh() - updateSession() - } - - return ( - <> -
-
-
- - - - - } - data={chatList || []} - streamLoading={false} - labelPosition="top" - labelFormat={(item) => item.role === 'user' ? t('prompt.you') : t('prompt.ai')} - /> - -
- - - - -
-
- -
- - - - - - - - - - form.setFieldValue('current_prompt', value)} - /> - -
- - -
-
-
-
- - - - - - ); -}; - -export default Prompt; \ No newline at end of file diff --git a/web/src/views/Prompt/components/Header.tsx b/web/src/views/Prompt/components/Header.tsx new file mode 100644 index 00000000..c1b4bd57 --- /dev/null +++ b/web/src/views/Prompt/components/Header.tsx @@ -0,0 +1,16 @@ +import { type FC } from 'react'; + +const Header:FC<{ title: string; desc: string; className?: string; }> = ({ + title, + desc, + className, +}) => { + return ( +
+
{title}
+
{desc}
+
+ ) +} + +export default Header \ No newline at end of file diff --git a/web/src/views/Prompt/index.module.css b/web/src/views/Prompt/index.module.css new file mode 100644 index 00000000..20572214 --- /dev/null +++ b/web/src/views/Prompt/index.module.css @@ -0,0 +1,3 @@ +.select:global(.ant-select-outlined:not(.ant-select-customize-input) .ant-select-selector) { + border: none; +} \ No newline at end of file diff --git a/web/src/views/Prompt/index.tsx b/web/src/views/Prompt/index.tsx index a0af4a7c..30d139c4 100644 --- a/web/src/views/Prompt/index.tsx +++ b/web/src/views/Prompt/index.tsx @@ -1,73 +1,309 @@ /* * @Author: ZhaoYing - * @Date: 2026-02-03 17:44:09 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 17:44:09 + * @Date: 2026-02-03 17:44:15 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-02-10 16:35:47 */ /** - * Prompt Management Page - * Main page with editor and history tabs for prompt optimization + * Prompt Editor Component + * AI-powered prompt optimization with chat interface and variable support */ -import { type FC, useState } from 'react'; -import { type SegmentedProps, Flex } from 'antd'; +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 PageTabs from '@/components/PageTabs'; -import SearchInput from '@/components/SearchInput' -import PromptEditor from './Prompt'; -import History from './History' -import type { HistoryQuery, HistoryItem } from './types'; +import { updatePromptMessages, createPromptSessions } from '@/api/prompt' +import { getModelListUrl } from '@/api/models' +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 CustomSelect from '@/components/CustomSelect' +import PromptVariableModal from './components/PromptVariableModal' +import { type SSEMessage } from '@/utils/stream' +import Editor from '@/views/ApplicationConfig/components/Editor' +import PromptSaveModal from './components/PromptSaveModal' +import { getLogoUrl } from '@/views/ModelManagement/utils' +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' -/** Available tab keys */ -const tabs = ['editor', 'history'] const Prompt: FC = () => { const { t } = useTranslation(); - const [activeTab, setActiveTab] = useState(tabs[0]) - const [query, setQuery] = useState({}); + 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 values = Form.useWatch([], form) const [editVo, setEditVo] = useState(null) - /** Handle tab change */ - const handleChangeTab = (value: SegmentedProps['value']) => { - setActiveTab(value) + 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) 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) + .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) - setQuery({}) + updateSession() } - /** Handle search in history */ - const handleSearch = (value?: string) => { - setQuery(prev => ({ ...prev, keyword: value })) + const [isFocus, setIsFocus] = useState(false) + const handleFocus = () => { + setIsFocus(true) } - /** Handle edit history item */ - const handleEdit = (item: HistoryItem) => { - console.log('edit', item) - setEditVo(item) - setActiveTab('editor') + const handleBlur = () => { + setIsFocus(false) } - /** Refresh editor state */ - const refresh = () => { - setEditVo(null) + const handleJump = () => { + navigate('/prompt/history') } + return ( <> - - ({ label: t(`prompt.${key}`), value: key }))} - onChange={handleChangeTab} - /> - {activeTab === 'history' && - - } - - -
- {activeTab === 'editor' && } - {activeTab === 'history' && } -
+
+
+
+
+ + + } + data={chatList || []} + streamLoading={false} + labelPosition="top" + labelFormat={(item) => item.role === 'user' ? t(`prompt.you`) : t(`prompt.ai`)} + /> + + + + + {loading + ?
+ : !values || !values?.message || values?.message?.trim() === '' + ?
+ :
+ } +
+ +
+
+ +
+ + + { + return data.map(option => ({ + ...data, + value: option.id, + label: (
+ {getLogoUrl(option.logo as string) && } + {option.name as string} +
+ ) + })) + }} + className={`rb:w-75! ${styles.select}`} + /> +
+ +
+ +
} + disabled={!values?.current_prompt || loading} + onClick={handleSave} + >{t('common.save')} +
} + disabled={!values?.current_prompt || loading} + onClick={handleCopy} + >{t('common.copy')} + + } + > + + {values?.current_prompt + ? form.setFieldValue('current_prompt', value)} + /> + : + } + + + + +
+ + + + ); }; diff --git a/web/src/views/Prompt/pages/History.tsx b/web/src/views/Prompt/pages/History.tsx new file mode 100644 index 00000000..7953af0f --- /dev/null +++ b/web/src/views/Prompt/pages/History.tsx @@ -0,0 +1,140 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-02-03 17:44:04 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-02-09 12:18:09 + */ +/** + * Prompt History Component + * Displays saved prompts with view, edit, and delete actions + */ + +import React, { useRef, type MouseEvent } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { Tooltip, App, Flex, Form, Dropdown } from 'antd'; +import { DashOutlined } from '@ant-design/icons'; + +import type { HistoryQuery, HistoryItem, PromptDetailRef } from '../types'; +import RbCard from '@/components/RbCard/Card' +import { getPromptReleaseListUrl, deletePrompt } from '@/api/prompt' +import Markdown from '@/components/Markdown'; +import { formatDateTime } from '@/utils/format' +import PromptDetail from '../components/PromptDetail' +import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList' +import SearchInput from '@/components/SearchInput' +import Header from '../components/Header' + +const History: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const scrollListRef = useRef(null) + const detailRef = useRef(null) + const { message, modal } = App.useApp() + const [form] = Form.useForm() + const query = Form.useWatch([], form) + + /** View prompt details */ + const handleView = (item: HistoryItem) => { + detailRef.current?.handleOpen(item) + } + /** Delete prompt */ + const handleDelete = (item: HistoryItem, e?: MouseEvent) => { + e?.preventDefault(); + e?.stopPropagation(); + modal.confirm({ + title: t('common.confirmDeleteDesc', { name: item.title }), + okText: t('common.delete'), + cancelText: t('common.cancel'), + okType: 'danger', + onOk: () => { + deletePrompt(item.id).then(() => { + message.success(t('common.deleteSuccess')) + scrollListRef.current?.refresh() + detailRef.current?.handleClose() + }) + } + }) + + } + /** Edit prompt */ + const handleEdit = (item: HistoryItem) => { + // edit(item) + navigate('/prompt', { + replace: true, + state: { ...item } + }) + } + + const handleClick = (key: string, item: HistoryItem) => { + console.log('handleClick key', key) + switch(key) { + case 'detail': + handleView(item) + break + case 'edit': + handleEdit(item) + break + case 'delete': + handleDelete(item) + break + } + } + + return ( + <> + +
+ +
+ + + +
+ + + ref={scrollListRef} + url={getPromptReleaseListUrl} + query={query} + column={3} + needLoading={false} + renderItem={(item) => ( + {item.title}} + headerClassName='rb:h-[38px]! rb:pt-3!' + headerType="borderless" + > + handleClick(key, item) + }} + > + + +
{formatDateTime(item.created_at, 'YYYY/MM/DD HH:mm')}
+
+ +
+
+ )} + /> + + + + ); +}; + +export default History; \ No newline at end of file