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)
This commit is contained in:
chanx 2025-08-01 18:32:38 +08:00 committed by GitHub
parent 8fd12b670e
commit 01bf799a59
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 331 additions and 89 deletions

View File

@ -9,6 +9,7 @@
.messageItemContent {
display: inline-flex;
gap: 20px;
width: 100%;
}
.messageItemContentReverse {
flex-direction: row-reverse;

View File

@ -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 (
<div
className={classNames(styles.messageItem, {
@ -123,36 +137,89 @@ function MessageItem({
(item.role === MessageType.User ? (
<RAGFlowAvatar avatar={avatar ?? '/logo.svg'} />
) : avatarDialog || agentName ? (
<RAGFlowAvatar avatar={avatarDialog} name={agentName} isPerson />
<RAGFlowAvatar
avatar={avatarDialog as string}
name={agentName}
isPerson
/>
) : (
<AssistantIcon />
))}
<section className="flex-col gap-2 flex-1">
<div className="space-x-1">
{isAssistant ? (
<AssistantGroupButton
messageId={item.id}
content={item.content}
prompt={item.prompt}
showLikeButton={showLikeButton}
audioBinary={item.audio_binary}
showLoudspeaker={showLoudspeaker}
showLog={showLog}
></AssistantGroupButton>
) : (
<UserGroupButton
content={item.content}
messageId={item.id}
removeMessageById={removeMessageById}
regenerateMessage={
regenerateMessage && handleRegenerateMessage
}
sendLoading={sendLoading}
></UserGroupButton>
<div className="flex justify-between items-center">
{isShare && isAssistant && (
<Button
variant={'transparent'}
onClick={() => setShowThinking((think) => !think)}
>
<div className="flex items-center gap-1">
<div className="">
<Atom
className={startedNodeList(item) ? 'animate-spin' : ''}
/>
</div>
Thinking
{showThinking ? <ChevronUp /> : <ChevronDown />}
</div>
</Button>
)}
<div className="space-x-1">
{isAssistant ? (
<>
{isShare && !sendLoading && !isEmpty(item.content) && (
<AssistantGroupButton
messageId={item.id}
content={item.content}
prompt={item.prompt}
showLikeButton={showLikeButton}
audioBinary={item.audio_binary}
showLoudspeaker={showLoudspeaker}
showLog={showLog}
></AssistantGroupButton>
)}
{!isShare && (
<AssistantGroupButton
messageId={item.id}
content={item.content}
prompt={item.prompt}
showLikeButton={showLikeButton}
audioBinary={item.audio_binary}
showLoudspeaker={showLoudspeaker}
showLog={showLog}
></AssistantGroupButton>
)}
</>
) : (
<UserGroupButton
content={item.content}
messageId={item.id}
removeMessageById={removeMessageById}
regenerateMessage={
regenerateMessage && handleRegenerateMessage
}
sendLoading={sendLoading}
></UserGroupButton>
)}
{/* <b>{isAssistant ? '' : nickname}</b> */}
{/* <b>{isAssistant ? '' : nickname}</b> */}
</div>
</div>
{isAssistant &&
currentEventListWithoutMessageById &&
showThinking && (
<div className="mt-4 mb-4">
<WorkFlowTimeline
currentEventListWithoutMessage={currentEventListWithoutMessageById(
item.id,
)}
isShare={isShare}
currentMessageId={item.id}
canvasId={conversationId}
sendLoading={loading}
/>
</div>
)}
<div
className={cn({
[theme === 'dark'
@ -165,7 +232,7 @@ function MessageItem({
{item.data ? (
children
) : sendLoading && isEmpty(item.content) ? (
'searching...'
<>{!isShare && 'running...'}</>
) : (
<MarkdownContent
loading={loading}
@ -180,18 +247,7 @@ function MessageItem({
list={referenceDocuments}
></ReferenceDocumentList>
)}
{isAssistant && currentEventListWithoutMessageById && (
<div className="mt-4">
<WorkFlowTimeline
currentEventListWithoutMessage={currentEventListWithoutMessageById(
item.id,
)}
currentMessageId={item.id}
canvasId={conversationId}
sendLoading={loading}
/>
</div>
)}
{isUser && (
<UploadedMessageFiles files={item.files}></UploadedMessageFiles>
)}

View File

@ -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 ',

View File

@ -36,6 +36,7 @@ export interface INodeData {
error: null | string;
elapsed_time: number;
created_at: number;
thoughts: string;
}
export interface IInputData {

View File

@ -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',
},

View File

@ -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',

View File

@ -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<string, any>[];
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 = ({
<AccordionItem value={idx.toString()}>
<AccordionTrigger>
<div className="flex gap-2 items-center">
<span>
{parentName(tool.path) + ' '}
{capitalizeWords(tool.tool_name, '_')}
</span>
{!isShare && (
<span>
{parentName(tool.path) + ' '}
{capitalizeWords(tool.tool_name, '_')}
</span>
)}
{isShare && (
<span>
{
typeMap[
toLowerCaseStringAndDeleteChar(
tool.tool_name,
) as keyof typeof typeMap
]
}
</span>
)}
<span className="text-text-sub-title text-xs">
{/* 0:00
{x.data.elapsed_time?.toString().slice(0, 6)} */}
@ -129,10 +148,18 @@ const ToolTimelineItem = ({
</AccordionTrigger>
<AccordionContent>
<div className="space-y-2">
<JsonViewer
data={tool.result}
title="content"
></JsonViewer>
{!isShare && (
<JsonViewer
data={tool.result}
title="content"
></JsonViewer>
)}
{isShare && (
<JsonViewer
data={tool.result}
title={''}
></JsonViewer>
)}
</div>
</AccordionContent>
</AccordionItem>

View File

@ -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<typeof useCacheChatLog>,
'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"
/>
</section>
);
}
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 (
<Timeline>
{startedNodeList?.map((x, idx) => {
@ -213,7 +249,15 @@ export const WorkFlowTimeline = ({
<AccordionItem value={idx.toString()}>
<AccordionTrigger>
<div className="flex gap-2 items-center">
<span>{getNodeName(x.data?.component_name)}</span>
<span>
{!isShare && getNodeName(x.data?.component_name)}
{isShare &&
typeMap[
toLowerCaseStringAndDeleteChar(
nodeLabel,
) as keyof typeof typeMap
]}
</span>
<span className="text-text-sub-title text-xs">
{x.data.elapsed_time?.toString().slice(0, 6)}
</span>
@ -228,16 +272,36 @@ export const WorkFlowTimeline = ({
</span>
</div>
</AccordionTrigger>
<AccordionContent>
<div className="space-y-2">
<JsonViewer data={inputs} title="Input"></JsonViewer>
{!isShare && (
<AccordionContent>
<div className="space-y-2">
{!isShare && (
<>
<JsonViewer
data={inputs}
title="Input"
></JsonViewer>
<JsonViewer
data={outputs}
title={'Output'}
></JsonViewer>
</div>
</AccordionContent>
<JsonViewer
data={outputs}
title={'Output'}
></JsonViewer>
</>
)}
</div>
</AccordionContent>
)}
{isShare && x.data?.thoughts && (
<AccordionContent>
<div className="space-y-2">
<div className="w-full h-[200px] break-words overflow-auto scrollbar-auto p-2 bg-slate-800">
<HightLightMarkdown>
{x.data.thoughts || ''}
</HightLightMarkdown>
</div>
</div>
</AccordionContent>
)}
</AccordionItem>
</Accordion>
</section>
@ -248,6 +312,7 @@ export const WorkFlowTimeline = ({
key={'tool_' + idx}
tools={filterTrace(x.data.component_id)}
sendLoading={sendLoading}
isShare={isShare}
></ToolTimelineItem>
)}
</>

View File

@ -25,21 +25,25 @@ export const AgentLogDetailModal: React.FC<CustomModalProps> = ({
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<CustomModalProps> = ({
className="!w-[900px]"
>
<div className="flex items-start mb-4 flex-col gap-4 justify-start">
<div>
<div className="w-full">
{derivedMessages?.map((message, i) => {
return (
<MessageItem

View File

@ -147,7 +147,7 @@ const AgentLogPage: React.FC = () => {
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<IAgentLogResponse>();
const showLogDetail = (item: IAgentLogResponse) => {
setModalData(item);
setOpenModal(true);
if (item?.round) {
setModalData(item);
setOpenModal(true);
}
};
return (

View File

@ -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 (
<section>
<PageHeader>
@ -104,7 +108,7 @@ export default function AgentTemplates() {
<main className="flex-1 bg-text-title-invert/50 h-dvh">
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 max-h-[94vh] overflow-auto px-8 pt-8">
{templateList?.map((x, index) => {
{tempListFilter?.map((x, index) => {
return (
<TemplateCard
isCreate={index === 0}

View File

@ -29,7 +29,6 @@ import { ParameterDialog } from './parameter-dialog';
const ChatContainer = () => {
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 = () => {
</div>
<div className="flex flex-1 flex-col p-2.5 h-[90vh] m-3">
<div
className={cn('flex flex-1 flex-col overflow-auto m-auto w-5/6')}
className={cn(
'flex flex-1 flex-col overflow-auto scrollbar-auto m-auto w-5/6',
)}
>
<div>
{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}

View File

@ -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);
}
}