From 01bf799a5942c8fe6b07036aa50f5d5c3221c622 Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Fri, 1 Aug 2025 18:32:38 +0800 Subject: [PATCH] Fix: Fixed share-log UI issues and log-template bugs (#9166) ### What problem does this PR solve? Fix: Fixed share-log UI issues and log-template bugs #3221 ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) --- .../components/next-message-item/index.less | 1 + .../components/next-message-item/index.tsx | 136 ++++++++++++------ web/src/components/ui/button.tsx | 1 + web/src/hooks/use-send-message.ts | 1 + web/src/locales/en.ts | 23 +++ web/src/locales/zh.ts | 22 +++ .../agent/log-sheet/toolTimelineItem.tsx | 45 ++++-- .../agent/log-sheet/workFlowTimeline.tsx | 91 ++++++++++-- .../pages/agents/agent-log-detail-modal.tsx | 26 ++-- web/src/pages/agents/agent-log-page.tsx | 8 +- web/src/pages/agents/agent-templates.tsx | 22 +-- web/src/pages/next-chats/share/index.tsx | 9 +- web/tailwind.css | 35 +++++ 13 files changed, 331 insertions(+), 89 deletions(-) diff --git a/web/src/components/next-message-item/index.less b/web/src/components/next-message-item/index.less index 448c7e7f1..17cb36165 100644 --- a/web/src/components/next-message-item/index.less +++ b/web/src/components/next-message-item/index.less @@ -9,6 +9,7 @@ .messageItemContent { display: inline-flex; gap: 20px; + width: 100%; } .messageItemContentReverse { flex-direction: row-reverse; diff --git a/web/src/components/next-message-item/index.tsx b/web/src/components/next-message-item/index.tsx index 5a7c38380..6aeca0c4f 100644 --- a/web/src/components/next-message-item/index.tsx +++ b/web/src/components/next-message-item/index.tsx @@ -14,16 +14,18 @@ import { } from 'react'; import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks'; -import { INodeEvent } from '@/hooks/use-send-message'; +import { INodeEvent, MessageEventType } 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'; +import { Atom, ChevronDown, ChevronUp } from 'lucide-react'; import IndentedTreeModal from '../indented-tree/modal'; import MarkdownContent from '../next-markdown-content'; import { RAGFlowAvatar } from '../ragflow-avatar'; import { useTheme } from '../theme-provider'; +import { Button } from '../ui/button'; import { AssistantGroupButton, UserGroupButton } from './group-button'; import styles from './index.less'; import { ReferenceDocumentList } from './reference-document-list'; @@ -50,6 +52,7 @@ interface IProps showLikeButton?: boolean; showLoudspeaker?: boolean; showLog?: boolean; + isShare?: boolean; } function MessageItem({ @@ -71,13 +74,14 @@ function MessageItem({ visibleAvatar = true, children, showLog, + isShare, }: IProps) { const { theme } = useTheme(); const isAssistant = item.role === MessageType.Assistant; const isUser = item.role === MessageType.User; - const { visible, hideModal, showModal } = useSetModalState(); - const [clickedDocumentId, setClickedDocumentId] = useState(''); - + const { visible, hideModal } = useSetModalState(); + const [clickedDocumentId] = useState(''); + const [showThinking, setShowThinking] = useState(false); const { setLastSendLoadingFunc } = useContext(AgentChatContext); useEffect(() => { @@ -101,6 +105,16 @@ function MessageItem({ setCurrentMessageId(item.id); } }, [item.id, setCurrentMessageId]); + + const startedNodeList = useCallback( + (item: IMessage) => { + const finish = currentEventListWithoutMessageById?.(item.id)?.some( + (item) => item.event === MessageEventType.WorkflowFinished, + ); + return !finish && loading; + }, + [currentEventListWithoutMessageById, loading], + ); return (
) : avatarDialog || agentName ? ( - + ) : ( ))}
-
- {isAssistant ? ( - - ) : ( - +
+ {isShare && isAssistant && ( + )} +
+ {isAssistant ? ( + <> + {isShare && !sendLoading && !isEmpty(item.content) && ( + + )} + {!isShare && ( + + )} + + ) : ( + + )} - {/* {isAssistant ? '' : nickname} */} + {/* {isAssistant ? '' : nickname} */} +
+ + {isAssistant && + currentEventListWithoutMessageById && + showThinking && ( +
+ +
+ )}
{!isShare && 'running...'} ) : ( )} - {isAssistant && currentEventListWithoutMessageById && ( -
- -
- )} + {isUser && ( )} diff --git a/web/src/components/ui/button.tsx b/web/src/components/ui/button.tsx index c8790d0a0..44eeb6458 100644 --- a/web/src/components/ui/button.tsx +++ b/web/src/components/ui/button.tsx @@ -23,6 +23,7 @@ const buttonVariants = cva( 'bg-colors-background-sentiment-solid-primary text-colors-text-persist-light hover:bg-colors-background-sentiment-solid-primary/80', icon: 'bg-colors-background-inverse-standard text-foreground hover:bg-colors-background-inverse-standard/80', dashed: 'border border-dashed border-input hover:bg-accent', + transparent: 'bg-transparent hover:bg-accent border', }, size: { default: 'h-8 px-2.5 py-1.5 ', diff --git a/web/src/hooks/use-send-message.ts b/web/src/hooks/use-send-message.ts index 3be9eab32..ecf961fdd 100644 --- a/web/src/hooks/use-send-message.ts +++ b/web/src/hooks/use-send-message.ts @@ -36,6 +36,7 @@ export interface INodeData { error: null | string; elapsed_time: number; created_at: number; + thoughts: string; } export interface IInputData { diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 79c299a6a..10469b125 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -1317,6 +1317,29 @@ This delimiter is used to split the input text into several text pieces echo of file: 'File upload', integer: 'Number', boolean: 'Boolean', + + logTimeline: { + begin: 'Ready to begin', + agent: 'Agent is thinking', + retrieval: 'Looking up knowledge', + message: 'Agent says', + awaitResponse: 'Waiting for you', + switch: 'Choosing the best path', + iteration: 'Batch processing', + categorize: 'Categorising info', + code: 'Running a quick script', + textProcessing: 'Tidying up text', + tavilySearch: 'Searching the web', + tavilyExtract: 'Reading the page', + exeSQL: 'Querying database', + google: 'Searching the web', + wikipedia: 'Searching Wikipedia', + googleScholar: 'Academic search', + gitHub: 'Searching GitHub', + email: 'Sending email', + httpRequest: 'Calling an API', + wenCai: 'Querying financial data', + }, goto: 'Fail Branch', comment: 'Default Value', }, diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index c6f43fe3e..dc91b1e44 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -1261,6 +1261,28 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 import: '导入', export: '导出', subject: '主题', + logTimeline: { + begin: '准备开始', + agent: '智能体正在思考', + retrieval: '查找知识', + message: '回复', + awaitResponse: '等你输入', + switch: '选择最佳路线', + iteration: '批量处理', + categorize: '信息归类', + code: '运行小段代码', + textProcessing: '整理文字', + tavilySearch: '正在网上搜索', + tavilyExtract: '读取网页内容', + exeSQL: '查询数据库', + google: '正在网上搜索', + wikipedia: '搜索维基百科', + googleScholar: '学术检索', + gitHub: '搜索', + email: '发送邮件', + httpRequest: '请求接口', + wenCai: '查询财务数据', + }, }, footer: { profile: 'All rights reserved @ React', diff --git a/web/src/pages/agent/log-sheet/toolTimelineItem.tsx b/web/src/pages/agent/log-sheet/toolTimelineItem.tsx index 66ff3cc8d..dac7c0817 100644 --- a/web/src/pages/agent/log-sheet/toolTimelineItem.tsx +++ b/web/src/pages/agent/log-sheet/toolTimelineItem.tsx @@ -14,14 +14,20 @@ import { import { cn } from '@/lib/utils'; import { Operator } from '../constant'; import OperatorIcon from '../operator-icon'; -import { JsonViewer } from './workFlowTimeline'; +import { + JsonViewer, + toLowerCaseStringAndDeleteChar, + typeMap, +} from './workFlowTimeline'; const ToolTimelineItem = ({ tools, sendLoading = false, + isShare = false, }: { tools: Record[]; sendLoading: boolean; + isShare?: boolean; }) => { if (!tools || tools.length === 0 || !Array.isArray(tools)) return null; const blackList = ['add_memory', 'gen_citations']; @@ -110,10 +116,23 @@ const ToolTimelineItem = ({
- - {parentName(tool.path) + ' '} - {capitalizeWords(tool.tool_name, '_')} - + {!isShare && ( + + {parentName(tool.path) + ' '} + {capitalizeWords(tool.tool_name, '_')} + + )} + {isShare && ( + + { + typeMap[ + toLowerCaseStringAndDeleteChar( + tool.tool_name, + ) as keyof typeof typeMap + ] + } + + )} {/* 0:00 {x.data.elapsed_time?.toString().slice(0, 6)} */} @@ -129,10 +148,18 @@ const ToolTimelineItem = ({
- + {!isShare && ( + + )} + {isShare && ( + + )}
diff --git a/web/src/pages/agent/log-sheet/workFlowTimeline.tsx b/web/src/pages/agent/log-sheet/workFlowTimeline.tsx index 008ae553e..84aa19d5d 100644 --- a/web/src/pages/agent/log-sheet/workFlowTimeline.tsx +++ b/web/src/pages/agent/log-sheet/workFlowTimeline.tsx @@ -1,3 +1,4 @@ +import HightLightMarkdown from '@/components/highlight-markdown'; import { Timeline, TimelineContent, @@ -31,7 +32,11 @@ import ToolTimelineItem from './toolTimelineItem'; type LogFlowTimelineProps = Pick< ReturnType, 'currentEventListWithoutMessage' | 'currentMessageId' -> & { canvasId?: string; sendLoading: boolean }; +> & { + canvasId?: string; + sendLoading: boolean; + isShare?: boolean; +}; export function JsonViewer({ data, title, @@ -46,12 +51,41 @@ export function JsonViewer({ src={data} displaySize collapseStringsAfterLength={100000000000} - className="w-full h-[200px] break-words overflow-auto p-2 bg-background-card" + className="w-full h-[200px] break-words overflow-auto scrollbar-auto p-2 bg-slate-800" />
); } - +export const typeMap = { + begin: t('flow.logTimeline.begin'), + agent: t('flow.logTimeline.agent'), + retrieval: t('flow.logTimeline.retrieval'), + message: t('flow.logTimeline.message'), + awaitResponse: t('flow.logTimeline.awaitResponse'), + switch: t('flow.logTimeline.switch'), + iteration: t('flow.logTimeline.iteration'), + categorize: t('flow.logTimeline.categorize'), + code: t('flow.logTimeline.code'), + textProcessing: t('flow.logTimeline.textProcessing'), + tavilySearch: t('flow.logTimeline.tavilySearch'), + tavilyExtract: t('flow.logTimeline.tavilyExtract'), + exeSQL: t('flow.logTimeline.exeSQL'), + google: t('flow.logTimeline.google'), + duckDuckGo: t('flow.logTimeline.google'), + wikipedia: t('flow.logTimeline.wikipedia'), + googleScholar: t('flow.logTimeline.googleScholar'), + arXiv: t('flow.logTimeline.googleScholar'), + pubMed: t('flow.logTimeline.googleScholar'), + gitHub: t('flow.logTimeline.gitHub'), + email: t('flow.logTimeline.email'), + httpRequest: t('flow.logTimeline.httpRequest'), + wenCai: t('flow.logTimeline.wenCai'), + yahooFinance: t('flow.logTimeline.yahooFinance'), +}; +export const toLowerCaseStringAndDeleteChar = ( + str: string, + char: string = '_', +) => str.toLowerCase().replace(/ /g, '').replaceAll(char, ''); function getInputsOrOutputs( nodeEventList: INodeData[], field: 'inputs' | 'outputs', @@ -69,6 +103,7 @@ export const WorkFlowTimeline = ({ currentMessageId, canvasId, sendLoading, + isShare, }: LogFlowTimelineProps) => { // const getNode = useGraphStore((state) => state.getNode); const [isStopFetchTrace, setISStopFetchTrace] = useState(false); @@ -146,6 +181,7 @@ export const WorkFlowTimeline = ({ }, [currentEventListWithoutMessage], ); + return ( {startedNodeList?.map((x, idx) => { @@ -213,7 +249,15 @@ export const WorkFlowTimeline = ({
- {getNodeName(x.data?.component_name)} + + {!isShare && getNodeName(x.data?.component_name)} + {isShare && + typeMap[ + toLowerCaseStringAndDeleteChar( + nodeLabel, + ) as keyof typeof typeMap + ]} + {x.data.elapsed_time?.toString().slice(0, 6)} @@ -228,16 +272,36 @@ export const WorkFlowTimeline = ({
- -
- + {!isShare && ( + +
+ {!isShare && ( + <> + - -
-
+ + + )} +
+
+ )} + {isShare && x.data?.thoughts && ( + +
+
+ + {x.data.thoughts || ''} + +
+
+
+ )}
@@ -248,6 +312,7 @@ export const WorkFlowTimeline = ({ key={'tool_' + idx} tools={filterTrace(x.data.component_id)} sendLoading={sendLoading} + isShare={isShare} > )} diff --git a/web/src/pages/agents/agent-log-detail-modal.tsx b/web/src/pages/agents/agent-log-detail-modal.tsx index fd184f736..1d8a32eb0 100644 --- a/web/src/pages/agents/agent-log-detail-modal.tsx +++ b/web/src/pages/agents/agent-log-detail-modal.tsx @@ -25,21 +25,25 @@ export const AgentLogDetailModal: React.FC = ({ const { data: canvasInfo } = useFetchAgent(); const shortMessage = useMemo(() => { - const content = derivedMessages[0]?.content || ''; + if (derivedMessages?.length) { + const content = derivedMessages[0]?.content || ''; - const chineseCharCount = (content.match(/[\u4e00-\u9fa5]/g) || []).length; - const totalLength = content.length; + const chineseCharCount = (content.match(/[\u4e00-\u9fa5]/g) || []).length; + const totalLength = content.length; - if (chineseCharCount > 0) { - if (totalLength > 15) { - return content.substring(0, 15) + '...'; + if (chineseCharCount > 0) { + if (totalLength > 15) { + return content.substring(0, 15) + '...'; + } + } else { + if (totalLength > 30) { + return content.substring(0, 30) + '...'; + } } + return content; } else { - if (totalLength > 30) { - return content.substring(0, 30) + '...'; - } + return ''; } - return content; }, [derivedMessages]); return ( @@ -52,7 +56,7 @@ export const AgentLogDetailModal: React.FC = ({ className="!w-[900px]" >
-
+
{derivedMessages?.map((message, i) => { return ( { setPagination((pre) => { return { ...pre, - current: current ?? pre.current, + current: current ?? pre.pageSize ? 1 : pre.current, pageSize: pageSize ?? pre.pageSize, }; }); @@ -196,8 +196,10 @@ const AgentLogPage: React.FC = () => { const [openModal, setOpenModal] = useState(false); const [modalData, setModalData] = useState(); const showLogDetail = (item: IAgentLogResponse) => { - setModalData(item); - setOpenModal(true); + if (item?.round) { + setModalData(item); + setOpenModal(true); + } }; return ( diff --git a/web/src/pages/agents/agent-templates.tsx b/web/src/pages/agents/agent-templates.tsx index 954feb258..e6d914bd1 100644 --- a/web/src/pages/agents/agent-templates.tsx +++ b/web/src/pages/agents/agent-templates.tsx @@ -11,7 +11,7 @@ import { useSetModalState } from '@/hooks/common-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useFetchAgentTemplates, useSetAgent } from '@/hooks/use-agent-request'; import { IFlowTemplate } from '@/interfaces/database/flow'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { CreateAgentDialog } from './create-agent-dialog'; import { TemplateCard } from './template-card'; @@ -70,15 +70,19 @@ export default function AgentTemplates() { ], ); const handleSiderBarChange = (keyword: string) => { - const tempList = list.filter( - (item, index) => - item.canvas_type - ?.toLocaleLowerCase() - .includes(keyword?.toLocaleLowerCase()) || index === 0, - ); - setTemplateList(tempList); setSelectMenuItem(keyword); }; + + const tempListFilter = useMemo(() => { + if (!selectMenuItem) { + return templateList; + } + return templateList.filter( + (item, index) => + item.canvas_type?.toLocaleLowerCase() === + selectMenuItem?.toLocaleLowerCase() || index === 0, + ); + }, [selectMenuItem, templateList]); return (
@@ -104,7 +108,7 @@ export default function AgentTemplates() {
- {templateList?.map((x, index) => { + {tempListFilter?.map((x, index) => { return ( { const { sharedId: conversationId, - from, locale, visibleAvatar, } = useGetSharedChatSearchParams(); @@ -61,7 +60,6 @@ const ChatContainer = () => { resetSession, } = useSendNextSharedMessage(addEventList); - // const { data } = useFetchExternalAgentInputs(); const sendDisabled = useSendButtonDisabled(value); const appConf = useFetchAppConf(); const { data: inputsData } = useFetchExternalAgentInputs(); @@ -95,7 +93,7 @@ const ChatContainer = () => { }, [inputsData, setAgentInfo]); React.useEffect(() => { - if (!isEmpty(inputsData)) { + if (inputsData && inputsData.inputs && !isEmpty(inputsData.inputs)) { showParameterDialog(); } }, [inputsData, showParameterDialog]); @@ -141,7 +139,9 @@ const ChatContainer = () => {
{derivedMessages?.map((message, i) => { @@ -162,6 +162,7 @@ const ChatContainer = () => { sendLoading && derivedMessages?.length - 1 === i } + isShare={true} avatarDialog={agentInfo.avatar} agentName={agentInfo.title} index={i} diff --git a/web/tailwind.css b/web/tailwind.css index c5e765e05..a488b7766 100644 --- a/web/tailwind.css +++ b/web/tailwind.css @@ -239,3 +239,38 @@ max-width: none; } } + +@layer utilities { + .scrollbar-auto { + /* hide scrollbar */ + scrollbar-width: thin; + scrollbar-color: transparent transparent; + } + + .scrollbar-auto::-webkit-scrollbar { + width: 6px; + height: 6px; + } + + .scrollbar-auto::-webkit-scrollbar-track { + background: transparent; + } + + .scrollbar-auto::-webkit-scrollbar-thumb { + background-color: transparent; + border-radius: 3px; + transition: background-color 0.2s ease; + } + + .scrollbar-auto:hover::-webkit-scrollbar-thumb, + .scrollbar-auto:focus::-webkit-scrollbar-thumb, + .scrollbar-auto:active::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.3); + } + + .dark .scrollbar-auto:hover::-webkit-scrollbar-thumb, + .dark .scrollbar-auto:focus::-webkit-scrollbar-thumb, + .dark .scrollbar-auto:active::-webkit-scrollbar-thumb { + background-color: rgba(255, 255, 255, 0.3); + } +}