From 26042343d8da0668d1720e047dcb72ce6cf3fab6 Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Thu, 31 Jul 2025 16:09:45 +0800 Subject: [PATCH] Fix: Improve Agent templates functionality and fix some UI style issues (#9129) ### What problem does this PR solve? Fix: Improve Agent templates functionality and fix some UI style issues #3221 ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) --- web/src/components/message-input/next.tsx | 2 +- .../components/next-message-item/index.tsx | 12 ++- web/src/locales/en.ts | 9 +++ web/src/pages/agent/canvas/index.tsx | 16 +++- .../agent/chat/use-send-agent-message.ts | 2 +- web/src/pages/agent/context.ts | 2 +- web/src/pages/agent/form/begin-form/index.tsx | 3 +- .../form/begin-form/parameter-dialog.tsx | 15 +++- .../agent/form/begin-form/query-table.tsx | 8 +- web/src/pages/agent/log-sheet/index.tsx | 4 +- .../agent/log-sheet/toolTimelineItem.tsx | 26 +++++- .../agent/log-sheet/workFlowTimeline.tsx | 46 +++++------ web/src/pages/agents/agent-templates.tsx | 19 +++-- web/src/pages/agents/template-sidebar.tsx | 79 ++++++++++++++----- 14 files changed, 173 insertions(+), 70 deletions(-) diff --git a/web/src/components/message-input/next.tsx b/web/src/components/message-input/next.tsx index 2e612836b..dcbed3694 100644 --- a/web/src/components/message-input/next.tsx +++ b/web/src/components/message-input/next.tsx @@ -132,7 +132,7 @@ export function NextMessageInput({ onChange={onInputChange} placeholder="Type your message here..." className="field-sizing-content min-h-10 w-full resize-none border-0 bg-transparent p-0 shadow-none focus-visible:ring-0 dark:bg-transparent" - disabled={isUploading || disabled} + disabled={isUploading || disabled || sendLoading} onKeyDown={handleKeyDown} />
diff --git a/web/src/components/next-message-item/index.tsx b/web/src/components/next-message-item/index.tsx index 1379560c7..735cd8eae 100644 --- a/web/src/components/next-message-item/index.tsx +++ b/web/src/components/next-message-item/index.tsx @@ -7,6 +7,7 @@ import { PropsWithChildren, memo, useCallback, + useContext, useEffect, useMemo, useState, @@ -15,6 +16,7 @@ import { import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks'; import { INodeEvent } from '@/hooks/use-send-message'; import { cn } from '@/lib/utils'; +import { AgentChatContext } from '@/pages/agent/context'; import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workFlowTimeline'; import { IMessage } from '@/pages/chat/interface'; import { isEmpty } from 'lodash'; @@ -74,6 +76,14 @@ function MessageItem({ const { visible, hideModal, showModal } = useSetModalState(); const [clickedDocumentId, setClickedDocumentId] = useState(''); + const { setLastSendLoadingFunc } = useContext(AgentChatContext); + + useEffect(() => { + if (typeof setLastSendLoadingFunc === 'function') { + setLastSendLoadingFunc(loading, item.id); + } + }, [loading, setLastSendLoadingFunc, item.id]); + const referenceDocuments = useMemo(() => { const docs = reference?.doc_aggs ?? {}; @@ -115,7 +125,6 @@ function MessageItem({ ) : ( ))} -
{isAssistant ? ( @@ -177,6 +186,7 @@ function MessageItem({ )} currentMessageId={item.id} canvasId={conversationId} + sendLoading={loading} />
)} diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 49869683b..7536452f7 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -1308,6 +1308,15 @@ This delimiter is used to split the input text into several text pieces echo of export: 'Export', seconds: 'Seconds', subject: 'Subject', + tag: 'Tag', + tagPlaceholder: 'Please enter tag', + descriptionPlaceholder: 'Please enter description', + line: 'Single-line text', + paragraph: 'Paragraph text', + options: 'Dropdown options', + file: 'File upload', + integer: 'Number', + boolean: 'Boolean', }, llmTools: { bad_calculator: { diff --git a/web/src/pages/agent/canvas/index.tsx b/web/src/pages/agent/canvas/index.tsx index 5c43f3a52..95cd4a0ab 100644 --- a/web/src/pages/agent/canvas/index.tsx +++ b/web/src/pages/agent/canvas/index.tsx @@ -13,7 +13,7 @@ import { } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import { NotebookPen } from 'lucide-react'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ChatSheet } from '../chat/chat-sheet'; import { AgentBackground } from '../components/background'; @@ -132,6 +132,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet({ setCurrentMessageId, }); + const [lastSendLoading, setLastSendLoading] = useState(false); const { handleBeforeDelete } = useBeforeDelete(); @@ -152,7 +153,13 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { clearEventList(); } }, [chatVisible, clearEventList]); - + const setLastSendLoadingFunc = (loading: boolean, messageId: string) => { + if (messageId === currentMessageId) { + setLastSendLoading(loading); + } else { + setLastSendLoading(false); + } + }; return (
)} {chatVisible && ( - + @@ -264,6 +273,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { currentEventListWithoutMessageById } currentMessageId={currentMessageId} + sendLoading={lastSendLoading} > )}
diff --git a/web/src/pages/agent/chat/use-send-agent-message.ts b/web/src/pages/agent/chat/use-send-agent-message.ts index cb1bc0e2c..0a8a045aa 100644 --- a/web/src/pages/agent/chat/use-send-agent-message.ts +++ b/web/src/pages/agent/chat/use-send-agent-message.ts @@ -251,12 +251,12 @@ export const useSendAgentMessage = ( }, [ agentId, + sessionId, send, clearUploadResponseList, inputs, beginParams, uploadResponseList, - sessionId, setValue, removeLatestMessage, ], diff --git a/web/src/pages/agent/context.ts b/web/src/pages/agent/context.ts index 9620e60c1..99da271cd 100644 --- a/web/src/pages/agent/context.ts +++ b/web/src/pages/agent/context.ts @@ -22,7 +22,7 @@ export const AgentInstanceContext = createContext( type AgentChatContextType = Pick< ReturnType, 'showLogSheet' ->; +> & { setLastSendLoadingFunc: (loading: boolean, messageId: string) => void }; export const AgentChatContext = createContext( {} as AgentChatContextType, diff --git a/web/src/pages/agent/form/begin-form/index.tsx b/web/src/pages/agent/form/begin-form/index.tsx index 6e918fc5d..97494d87c 100644 --- a/web/src/pages/agent/form/begin-form/index.tsx +++ b/web/src/pages/agent/form/begin-form/index.tsx @@ -123,7 +123,7 @@ function BeginForm({ node }: INextOperatorForm) { )} /> )} - {enablePrologue && ( + {mode === AgentDialogueMode.Conversational && enablePrologue && ( - {visible && ( - {cur} + {t(cur.toLowerCase())}
), value: cur, @@ -116,6 +118,13 @@ function ParameterForm({ submit(values); } + const handleKeyChange = (e: ChangeEvent) => { + const name = form.getValues().name || ''; + form.setValue('key', e.target.value.trim()); + if (!name) { + form.setValue('name', e.target.value.trim()); + } + }; return (
Key - + diff --git a/web/src/pages/agent/form/begin-form/query-table.tsx b/web/src/pages/agent/form/begin-form/query-table.tsx index b92c43fe2..28333c3c7 100644 --- a/web/src/pages/agent/form/begin-form/query-table.tsx +++ b/web/src/pages/agent/form/begin-form/query-table.tsx @@ -53,7 +53,7 @@ export function QueryTable({ data = [], deleteRecord, showModal }: IProps) { const columns: ColumnDef[] = [ { accessorKey: 'key', - header: 'key', + header: 'Key', meta: { cellClassName: 'max-w-30' }, cell: ({ row }) => { const key: string = row.getValue('key'); @@ -90,7 +90,11 @@ export function QueryTable({ data = [], deleteRecord, showModal }: IProps) { { accessorKey: 'type', header: t('flow.type'), - cell: ({ row }) =>
{row.getValue('type')}
, + cell: ({ row }) => ( +
+ {t(`flow.${(row.getValue('type')?.toString() || '').toLowerCase()}`)} +
+ ), }, { accessorKey: 'optional', diff --git a/web/src/pages/agent/log-sheet/index.tsx b/web/src/pages/agent/log-sheet/index.tsx index 90e50c410..deb9b3aa3 100644 --- a/web/src/pages/agent/log-sheet/index.tsx +++ b/web/src/pages/agent/log-sheet/index.tsx @@ -14,12 +14,13 @@ type LogSheetProps = IModalProps & Pick< ReturnType, 'currentEventListWithoutMessageById' | 'currentMessageId' - >; + > & { sendLoading: boolean }; export function LogSheet({ hideModal, currentEventListWithoutMessageById, currentMessageId, + sendLoading, }: LogSheetProps) { return ( @@ -36,6 +37,7 @@ export function LogSheet({ currentMessageId, )} currentMessageId={currentMessageId} + sendLoading={sendLoading} /> diff --git a/web/src/pages/agent/log-sheet/toolTimelineItem.tsx b/web/src/pages/agent/log-sheet/toolTimelineItem.tsx index 7c3a00184..66ff3cc8d 100644 --- a/web/src/pages/agent/log-sheet/toolTimelineItem.tsx +++ b/web/src/pages/agent/log-sheet/toolTimelineItem.tsx @@ -16,7 +16,13 @@ import { Operator } from '../constant'; import OperatorIcon from '../operator-icon'; import { JsonViewer } from './workFlowTimeline'; -const ToolTimelineItem = ({ tools }: { tools: Record[] }) => { +const ToolTimelineItem = ({ + tools, + sendLoading = false, +}: { + tools: Record[]; + sendLoading: boolean; +}) => { if (!tools || tools.length === 0 || !Array.isArray(tools)) return null; const blackList = ['add_memory', 'gen_citations']; const filteredTools = tools.filter( @@ -32,6 +38,15 @@ const ToolTimelineItem = ({ tools }: { tools: Record[] }) => { }) .join(' '); }; + const parentName = (str: string, separator: string = '-->') => { + if (!str) return ''; + const strs = str.split(separator); + if (strs.length > 1) { + return strs[strs.length - 1]; + } else { + return str; + } + }; return ( <> {filteredTools?.map((tool, idx) => { @@ -58,7 +73,9 @@ const ToolTimelineItem = ({ tools }: { tools: Record[] }) => { 'group-data-completed/timeline-item:bg-primary group-data-completed/timeline-item:text-primary-foreground flex size-6 p-1 items-center justify-center group-data-[orientation=vertical]/timeline:-left-7', { 'border border-blue-500': !( - idx >= filteredTools.length - 1 && tool.result === '...' + idx >= filteredTools.length - 1 && + tool.result === '...' && + sendLoading ), }, )} @@ -69,7 +86,8 @@ const ToolTimelineItem = ({ tools }: { tools: Record[] }) => { className={cn('rounded-full w-6 h-6', { ' border-muted-foreground border-2 border-t-transparent animate-spin ': idx >= filteredTools.length - 1 && - tool.result === '...', + tool.result === '...' && + sendLoading, })} > @@ -93,7 +111,7 @@ const ToolTimelineItem = ({ tools }: { tools: Record[] }) => {
- {tool.path + ' '} + {parentName(tool.path) + ' '} {capitalizeWords(tool.tool_name, '_')} diff --git a/web/src/pages/agent/log-sheet/workFlowTimeline.tsx b/web/src/pages/agent/log-sheet/workFlowTimeline.tsx index bf1d34475..fe773af91 100644 --- a/web/src/pages/agent/log-sheet/workFlowTimeline.tsx +++ b/web/src/pages/agent/log-sheet/workFlowTimeline.tsx @@ -20,6 +20,7 @@ import { } from '@/hooks/use-send-message'; import { ITraceData } from '@/interfaces/database/agent'; import { cn } from '@/lib/utils'; +import { t } from 'i18next'; import { get } from 'lodash'; import { useCallback, useEffect, useMemo, useState } from 'react'; import JsonView from 'react18-json-view'; @@ -30,7 +31,7 @@ import ToolTimelineItem from './toolTimelineItem'; type LogFlowTimelineProps = Pick< ReturnType, 'currentEventListWithoutMessage' | 'currentMessageId' -> & { canvasId?: string }; +> & { canvasId?: string; sendLoading: boolean }; export function JsonViewer({ data, title, @@ -67,6 +68,7 @@ export const WorkFlowTimeline = ({ currentEventListWithoutMessage, currentMessageId, canvasId, + sendLoading, }: LogFlowTimelineProps) => { // const getNode = useGraphStore((state) => state.getNode); const [isStopFetchTrace, setISStopFetchTrace] = useState(false); @@ -79,31 +81,20 @@ export const WorkFlowTimeline = ({ useEffect(() => { setMessageId(currentMessageId); }, [currentMessageId, setMessageId]); - // const getNodeName = useCallback( - // (nodeId: string) => { - // if ('begin' === nodeId) return t('flow.begin'); - // return getNode(nodeId)?.data.name; - // }, - // [getNode], - // ); - // const getNodeById = useCallback( - // (nodeId: string) => { - // const data = currentEventListWithoutMessage - // .map((x) => x.data) - // .filter((x) => x.component_id === nodeId); - // if ('begin' === nodeId) return t('flow.begin'); - // if (data && data.length) { - // return data[0]; - // } - // return {}; - // }, - // [currentEventListWithoutMessage], - // ); + const getNodeName = (nodeId: string) => { + if ('begin' === nodeId) return t('flow.begin'); + return nodeId; + }; + + useEffect(() => { + setISStopFetchTrace(!sendLoading); + }, [sendLoading]); + const startedNodeList = useMemo(() => { const finish = currentEventListWithoutMessage?.some( (item) => item.event === MessageEventType.WorkflowFinished, ); - setISStopFetchTrace(finish); + setISStopFetchTrace(finish || !sendLoading); const duplicateList = currentEventListWithoutMessage?.filter( (x) => x.event === MessageEventType.NodeStarted, ) as INodeEvent[]; @@ -115,7 +106,7 @@ export const WorkFlowTimeline = ({ } return pre; }, []); - }, [currentEventListWithoutMessage]); + }, [currentEventListWithoutMessage, sendLoading]); const hasTrace = useCallback( (componentId: string) => { @@ -198,7 +189,8 @@ export const WorkFlowTimeline = ({
@@ -212,7 +204,7 @@ export const WorkFlowTimeline = ({ -
+
- {x.data?.component_name} + {getNodeName(x.data?.component_name)} {x.data.elapsed_time?.toString().slice(0, 6)} @@ -253,7 +245,9 @@ export const WorkFlowTimeline = ({ {hasTrace(x.data.component_id) && ( )} diff --git a/web/src/pages/agents/agent-templates.tsx b/web/src/pages/agents/agent-templates.tsx index 0c924ea1e..954feb258 100644 --- a/web/src/pages/agents/agent-templates.tsx +++ b/web/src/pages/agents/agent-templates.tsx @@ -15,7 +15,7 @@ import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { CreateAgentDialog } from './create-agent-dialog'; import { TemplateCard } from './template-card'; -import { SideBar } from './template-sidebar'; +import { MenuItemKey, SideBar } from './template-sidebar'; export default function AgentTemplates() { const { navigateToAgentList } = useNavigatePage(); @@ -23,7 +23,9 @@ export default function AgentTemplates() { const list = useFetchAgentTemplates(); const { loading, setAgent } = useSetAgent(); const [templateList, setTemplateList] = useState([]); - + const [selectMenuItem, setSelectMenuItem] = useState( + MenuItemKey.Recommended, + ); useEffect(() => { setTemplateList(list); }, [list]); @@ -70,10 +72,12 @@ export default function AgentTemplates() { const handleSiderBarChange = (keyword: string) => { const tempList = list.filter( (item, index) => - item.title.toLocaleLowerCase().includes(keyword?.toLocaleLowerCase()) || - index === 0, + item.canvas_type + ?.toLocaleLowerCase() + .includes(keyword?.toLocaleLowerCase()) || index === 0, ); setTemplateList(tempList); + setSelectMenuItem(keyword); }; return (
@@ -93,9 +97,12 @@ export default function AgentTemplates() {
- + -
+
{templateList?.map((x, index) => { return ( diff --git a/web/src/pages/agents/template-sidebar.tsx b/web/src/pages/agents/template-sidebar.tsx index b361ef54e..77cb9a550 100644 --- a/web/src/pages/agents/template-sidebar.tsx +++ b/web/src/pages/agents/template-sidebar.tsx @@ -1,44 +1,85 @@ import { Button } from '@/components/ui/button'; -import { useSecondPathName } from '@/hooks/route-hook'; import { cn } from '@/lib/utils'; -import { Banknote, LayoutGrid, User } from 'lucide-react'; - +import { + Box, + ChartPie, + Component, + MessageCircleCode, + PencilRuler, + Sparkle, +} from 'lucide-react'; +export enum MenuItemKey { + Recommended = 'Recommended', + Agent = 'Agent', + CustomerSupport = 'Customer Support', + Marketing = 'Marketing', + ConsumerApp = 'Consumer App', + Other = 'Other', +} const menuItems = [ { - section: 'All Templates', + // section: 'All Templates', + section: '', items: [ - { icon: User, label: 'Assistant', key: 'Assistant' }, - { icon: LayoutGrid, label: 'chatbot', key: 'chatbot' }, - { icon: Banknote, label: 'generator', key: 'generator' }, - { icon: Banknote, label: 'Intel', key: 'Intel' }, + { + icon: Sparkle, + label: MenuItemKey.Recommended, + key: MenuItemKey.Recommended, + }, + { icon: Box, label: MenuItemKey.Agent, key: MenuItemKey.Agent }, + { + icon: MessageCircleCode, + label: MenuItemKey.CustomerSupport, + key: MenuItemKey.CustomerSupport, + }, + { + icon: ChartPie, + label: MenuItemKey.Marketing, + key: MenuItemKey.Marketing, + }, + { + icon: Component, + label: MenuItemKey.ConsumerApp, + key: MenuItemKey.ConsumerApp, + }, + { icon: PencilRuler, label: MenuItemKey.Other, key: MenuItemKey.Other }, ], }, ]; -export function SideBar({ change }: { change: (keyword: string) => void }) { - const pathName = useSecondPathName(); +export function SideBar({ + change, + selected = MenuItemKey.Recommended, +}: { + change: (keyword: string) => void; + selected?: string; +}) { const handleMenuClick = (key: string) => { change(key); }; return ( -