### What problem does this PR solve? Fix: Fixed the loss of Await Response function on the share page and other style issues #3221 ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)tags/v0.20.1
| function AccordionTrigger({ | function AccordionTrigger({ | ||||
| className, | className, | ||||
| children, | children, | ||||
| hideDownIcon = false, | |||||
| ...props | ...props | ||||
| }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) { | |||||
| }: React.ComponentProps<typeof AccordionPrimitive.Trigger> & { | |||||
| hideDownIcon?: boolean; | |||||
| }) { | |||||
| return ( | return ( | ||||
| <AccordionPrimitive.Header className="flex"> | <AccordionPrimitive.Header className="flex"> | ||||
| <AccordionPrimitive.Trigger | <AccordionPrimitive.Trigger | ||||
| {...props} | {...props} | ||||
| > | > | ||||
| {children} | {children} | ||||
| <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" /> | |||||
| {!hideDownIcon && ( | |||||
| <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" /> | |||||
| )} | |||||
| </AccordionPrimitive.Trigger> | </AccordionPrimitive.Trigger> | ||||
| </AccordionPrimitive.Header> | </AccordionPrimitive.Header> | ||||
| ); | ); |
| logTimeline: { | logTimeline: { | ||||
| begin: 'Ready to begin', | begin: 'Ready to begin', | ||||
| agent: 'Agent is thinking', | agent: 'Agent is thinking', | ||||
| userFillUp: 'Waiting for you', | |||||
| retrieval: 'Looking up knowledge', | retrieval: 'Looking up knowledge', | ||||
| message: 'Agent says', | message: 'Agent says', | ||||
| awaitResponse: 'Waiting for you', | awaitResponse: 'Waiting for you', |
| subject: '主题', | subject: '主题', | ||||
| logTimeline: { | logTimeline: { | ||||
| begin: '准备开始', | begin: '准备开始', | ||||
| userFillUp: '等你输入', | |||||
| agent: '智能体正在思考', | agent: '智能体正在思考', | ||||
| retrieval: '查找知识', | retrieval: '查找知识', | ||||
| message: '回复', | message: '回复', |
| useUploadCanvasFileWithProgress, | useUploadCanvasFileWithProgress, | ||||
| } from '@/hooks/use-agent-request'; | } from '@/hooks/use-agent-request'; | ||||
| import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | ||||
| import { Message } from '@/interfaces/database/chat'; | |||||
| import { buildMessageUuidWithRole } from '@/utils/chat'; | import { buildMessageUuidWithRole } from '@/utils/chat'; | ||||
| import { get } from 'lodash'; | |||||
| import { memo, useCallback, useMemo } from 'react'; | |||||
| import { memo, useCallback } from 'react'; | |||||
| import { useParams } from 'umi'; | import { useParams } from 'umi'; | ||||
| import DebugContent from '../debug-content'; | import DebugContent from '../debug-content'; | ||||
| import { BeginQuery } from '../interface'; | |||||
| import { buildBeginQueryWithObject } from '../utils'; | |||||
| import { useAwaitCompentData } from '../hooks/use-chat-logic'; | |||||
| function AgentChatBox() { | function AgentChatBox() { | ||||
| const { | const { | ||||
| const { data: canvasInfo } = useFetchAgent(); | const { data: canvasInfo } = useFetchAgent(); | ||||
| const { id: canvasId } = useParams(); | const { id: canvasId } = useParams(); | ||||
| const { uploadCanvasFile, loading } = useUploadCanvasFileWithProgress(); | const { uploadCanvasFile, loading } = useUploadCanvasFileWithProgress(); | ||||
| const getInputs = useCallback((message: Message) => { | |||||
| return get(message, 'data.inputs', {}) as Record<string, BeginQuery>; | |||||
| }, []); | |||||
| const buildInputList = useCallback( | |||||
| (message: Message) => { | |||||
| return Object.entries(getInputs(message)).map(([key, val]) => { | |||||
| return { | |||||
| ...val, | |||||
| key, | |||||
| }; | |||||
| }); | |||||
| }, | |||||
| [getInputs], | |||||
| ); | |||||
| const handleOk = useCallback( | |||||
| (message: Message) => (values: BeginQuery[]) => { | |||||
| const inputs = getInputs(message); | |||||
| const nextInputs = buildBeginQueryWithObject(inputs, values); | |||||
| sendFormMessage({ | |||||
| inputs: nextInputs, | |||||
| id: canvasId, | |||||
| }); | |||||
| }, | |||||
| [canvasId, getInputs, sendFormMessage], | |||||
| ); | |||||
| const { buildInputList, handleOk, isWaitting } = useAwaitCompentData({ | |||||
| derivedMessages, | |||||
| sendFormMessage, | |||||
| canvasId: canvasId as string, | |||||
| }); | |||||
| const handleUploadFile: NonNullable<FileUploadProps['onUpload']> = | const handleUploadFile: NonNullable<FileUploadProps['onUpload']> = | ||||
| useCallback( | useCallback( | ||||
| }, | }, | ||||
| [appendUploadResponseList, uploadCanvasFile], | [appendUploadResponseList, uploadCanvasFile], | ||||
| ); | ); | ||||
| const isWaitting = useMemo(() => { | |||||
| const temp = derivedMessages?.some((message, i) => { | |||||
| const flag = | |||||
| message.role === MessageType.Assistant && | |||||
| derivedMessages.length - 1 === i && | |||||
| message.data; | |||||
| return flag; | |||||
| }); | |||||
| return temp; | |||||
| }, [derivedMessages]); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <section className="flex flex-1 flex-col px-5 h-[90vh]"> | <section className="flex flex-1 flex-col px-5 h-[90vh]"> |
| const sendFormMessage = useCallback( | const sendFormMessage = useCallback( | ||||
| (body: { id?: string; inputs: Record<string, BeginQuery> }) => { | (body: { id?: string; inputs: Record<string, BeginQuery> }) => { | ||||
| send(body); | |||||
| send({ ...body, session_id: sessionId }); | |||||
| addNewestOneQuestion({ | addNewestOneQuestion({ | ||||
| content: Object.entries(body.inputs) | content: Object.entries(body.inputs) | ||||
| .map(([key, val]) => `${key}: ${val.value}`) | .map(([key, val]) => `${key}: ${val.value}`) | ||||
| role: MessageType.User, | role: MessageType.User, | ||||
| }); | }); | ||||
| }, | }, | ||||
| [addNewestOneQuestion, send], | |||||
| [addNewestOneQuestion, send, sessionId], | |||||
| ); | ); | ||||
| // reset session | // reset session |
| import { MessageType } from '@/constants/chat'; | |||||
| import { Message } from '@/interfaces/database/chat'; | |||||
| import { IMessage } from '@/pages/chat/interface'; | |||||
| import { get } from 'lodash'; | |||||
| import { useCallback, useMemo } from 'react'; | |||||
| import { BeginQuery } from '../interface'; | |||||
| import { buildBeginQueryWithObject } from '../utils'; | |||||
| type IAwaitCompentData = { | |||||
| derivedMessages: IMessage[]; | |||||
| sendFormMessage: (params: { | |||||
| inputs: Record<string, BeginQuery>; | |||||
| id: string; | |||||
| }) => void; | |||||
| canvasId: string; | |||||
| }; | |||||
| const useAwaitCompentData = (props: IAwaitCompentData) => { | |||||
| const { derivedMessages, sendFormMessage, canvasId } = props; | |||||
| const getInputs = useCallback((message: Message) => { | |||||
| return get(message, 'data.inputs', {}) as Record<string, BeginQuery>; | |||||
| }, []); | |||||
| const buildInputList = useCallback( | |||||
| (message: Message) => { | |||||
| return Object.entries(getInputs(message)).map(([key, val]) => { | |||||
| return { | |||||
| ...val, | |||||
| key, | |||||
| }; | |||||
| }); | |||||
| }, | |||||
| [getInputs], | |||||
| ); | |||||
| const handleOk = useCallback( | |||||
| (message: Message) => (values: BeginQuery[]) => { | |||||
| const inputs = getInputs(message); | |||||
| const nextInputs = buildBeginQueryWithObject(inputs, values); | |||||
| sendFormMessage({ | |||||
| inputs: nextInputs, | |||||
| id: canvasId, | |||||
| }); | |||||
| }, | |||||
| [getInputs, sendFormMessage, canvasId], | |||||
| ); | |||||
| const isWaitting = useMemo(() => { | |||||
| const temp = derivedMessages?.some((message, i) => { | |||||
| const flag = | |||||
| message.role === MessageType.Assistant && | |||||
| derivedMessages.length - 1 === i && | |||||
| message.data; | |||||
| return flag; | |||||
| }); | |||||
| return temp; | |||||
| }, [derivedMessages]); | |||||
| return { getInputs, buildInputList, handleOk, isWaitting }; | |||||
| }; | |||||
| export { useAwaitCompentData }; |
| import { cn } from '@/lib/utils'; | import { cn } from '@/lib/utils'; | ||||
| import { isEmpty } from 'lodash'; | import { isEmpty } from 'lodash'; | ||||
| import { Operator } from '../constant'; | import { Operator } from '../constant'; | ||||
| import OperatorIcon from '../operator-icon'; | |||||
| import OperatorIcon, { SVGIconMap } from '../operator-icon'; | |||||
| import { | import { | ||||
| JsonViewer, | JsonViewer, | ||||
| toLowerCaseStringAndDeleteChar, | toLowerCaseStringAndDeleteChar, | ||||
| typeMap, | typeMap, | ||||
| } from './workFlowTimeline'; | } from './workFlowTimeline'; | ||||
| const capitalizeWords = (str: string, separator: string = '_'): string => { | |||||
| if (!str) return ''; | |||||
| type IToolIcon = | |||||
| | Operator.ArXiv | |||||
| | Operator.GitHub | |||||
| | Operator.Bing | |||||
| | Operator.DuckDuckGo | |||||
| | Operator.Google | |||||
| | Operator.GoogleScholar | |||||
| | Operator.PubMed | |||||
| | Operator.TavilyExtract | |||||
| | Operator.TavilySearch | |||||
| | Operator.Wikipedia | |||||
| | Operator.YahooFinance | |||||
| | Operator.WenCai | |||||
| | Operator.Crawler; | |||||
| return str | |||||
| .split(separator) | |||||
| .map((word) => { | |||||
| return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); | |||||
| }) | |||||
| .join(' '); | |||||
| const capitalizeWords = (str: string, separator: string = '_'): string[] => { | |||||
| if (!str) return ['']; | |||||
| const resultStrArr = str.split(separator).map((word) => { | |||||
| return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); | |||||
| }); | |||||
| return resultStrArr; | |||||
| }; | }; | ||||
| const changeToolName = (toolName: any) => { | const changeToolName = (toolName: any) => { | ||||
| const name = 'Agent ' + capitalizeWords(toolName); | |||||
| const name = 'Agent ' + capitalizeWords(toolName).join(' '); | |||||
| return name; | return name; | ||||
| }; | }; | ||||
| const ToolTimelineItem = ({ | const ToolTimelineItem = ({ | ||||
| return ( | return ( | ||||
| <> | <> | ||||
| {filteredTools?.map((tool, idx) => { | {filteredTools?.map((tool, idx) => { | ||||
| const toolName = capitalizeWords(tool.tool_name, '_').join(''); | |||||
| return ( | return ( | ||||
| <TimelineItem | <TimelineItem | ||||
| key={'tool_' + idx} | key={'tool_' + idx} | ||||
| <div className="size-6 flex items-center justify-center"> | <div className="size-6 flex items-center justify-center"> | ||||
| <OperatorIcon | <OperatorIcon | ||||
| className="size-4" | className="size-4" | ||||
| name={'Agent' as Operator} | |||||
| name={ | |||||
| (SVGIconMap[toolName as IToolIcon] | |||||
| ? toolName | |||||
| : 'Agent') as Operator | |||||
| } | |||||
| ></OperatorIcon> | ></OperatorIcon> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| className="bg-background-card px-3" | className="bg-background-card px-3" | ||||
| > | > | ||||
| <AccordionItem value={idx.toString()}> | <AccordionItem value={idx.toString()}> | ||||
| <AccordionTrigger> | |||||
| <AccordionTrigger | |||||
| hideDownIcon={isShare && isEmpty(tool.arguments)} | |||||
| > | |||||
| <div className="flex gap-2 items-center"> | <div className="flex gap-2 items-center"> | ||||
| {!isShare && ( | {!isShare && ( | ||||
| <span> | <span> | ||||
| {parentName(tool.path) + ' '} | {parentName(tool.path) + ' '} | ||||
| {capitalizeWords(tool.tool_name, '_')} | |||||
| {capitalizeWords(tool.tool_name, '_').join(' ')} | |||||
| </span> | </span> | ||||
| )} | )} | ||||
| {isShare && ( | {isShare && ( | ||||
| </span> | </span> | ||||
| <span | <span | ||||
| className={cn( | className={cn( | ||||
| 'border-background -end-1 -top-1 size-2 rounded-full border-2 bg-dot-green', | |||||
| 'border-background -end-1 -top-1 size-2 rounded-full bg-dot-green', | |||||
| )} | )} | ||||
| > | > | ||||
| <span className="sr-only">Online</span> | <span className="sr-only">Online</span> | ||||
| )} | )} | ||||
| {isShare && !isEmpty(tool.arguments) && ( | {isShare && !isEmpty(tool.arguments) && ( | ||||
| <AccordionContent> | <AccordionContent> | ||||
| <div className="space-y-2"> | |||||
| <div className="space-y-2 bg-muted p-2"> | |||||
| {tool && | {tool && | ||||
| tool.arguments && | tool.arguments && | ||||
| Object.entries(tool.arguments).length && | Object.entries(tool.arguments).length && | ||||
| <div className="text-sm font-medium leading-none"> | <div className="text-sm font-medium leading-none"> | ||||
| {key} | {key} | ||||
| </div> | </div> | ||||
| <div className="text-sm text-muted-foreground"> | |||||
| {val || ''} | |||||
| <div className="text-sm text-muted-foreground mt-1"> | |||||
| {val as string} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| ); | ); |
| src={data} | src={data} | ||||
| displaySize | displaySize | ||||
| collapseStringsAfterLength={100000000000} | collapseStringsAfterLength={100000000000} | ||||
| className="w-full h-[200px] break-words overflow-auto scrollbar-auto p-2 bg-slate-800" | |||||
| className="w-full h-[200px] break-words overflow-auto scrollbar-auto p-2 bg-muted" | |||||
| /> | /> | ||||
| </section> | </section> | ||||
| ); | ); | ||||
| httpRequest: t('flow.logTimeline.httpRequest'), | httpRequest: t('flow.logTimeline.httpRequest'), | ||||
| wenCai: t('flow.logTimeline.wenCai'), | wenCai: t('flow.logTimeline.wenCai'), | ||||
| yahooFinance: t('flow.logTimeline.yahooFinance'), | yahooFinance: t('flow.logTimeline.yahooFinance'), | ||||
| userFillUp: t('flow.logTimeline.userFillUp'), | |||||
| }; | }; | ||||
| export const toLowerCaseStringAndDeleteChar = ( | export const toLowerCaseStringAndDeleteChar = ( | ||||
| str: string, | str: string, | ||||
| char: string = '_', | char: string = '_', | ||||
| ) => str.toLowerCase().replace(/ /g, '').replaceAll(char, ''); | ) => str.toLowerCase().replace(/ /g, '').replaceAll(char, ''); | ||||
| // Convert all keys in typeMap to lowercase and output the new typeMap | |||||
| export const typeMapLowerCase = Object.fromEntries( | |||||
| Object.entries(typeMap).map(([key, value]) => [ | |||||
| toLowerCaseStringAndDeleteChar(key), | |||||
| value, | |||||
| ]), | |||||
| ); | |||||
| function getInputsOrOutputs( | function getInputsOrOutputs( | ||||
| nodeEventList: INodeData[], | nodeEventList: INodeData[], | ||||
| field: 'inputs' | 'outputs', | field: 'inputs' | 'outputs', | ||||
| className="bg-background-card px-3" | className="bg-background-card px-3" | ||||
| > | > | ||||
| <AccordionItem value={idx.toString()}> | <AccordionItem value={idx.toString()}> | ||||
| <AccordionTrigger> | |||||
| <AccordionTrigger | |||||
| hideDownIcon={isShare && !x.data?.thoughts} | |||||
| > | |||||
| <div className="flex gap-2 items-center"> | <div className="flex gap-2 items-center"> | ||||
| <span> | <span> | ||||
| {!isShare && getNodeName(x.data?.component_name)} | {!isShare && getNodeName(x.data?.component_name)} | ||||
| {isShare && | {isShare && | ||||
| typeMap[ | |||||
| (typeMapLowerCase[ | |||||
| toLowerCaseStringAndDeleteChar( | toLowerCaseStringAndDeleteChar( | ||||
| nodeLabel, | nodeLabel, | ||||
| ) as keyof typeof typeMap | ) as keyof typeof typeMap | ||||
| ]} | |||||
| ] ?? | |||||
| nodeLabel)} | |||||
| </span> | </span> | ||||
| <span className="text-text-sub-title text-xs"> | <span className="text-text-sub-title text-xs"> | ||||
| {x.data.elapsed_time?.toString().slice(0, 6)} | {x.data.elapsed_time?.toString().slice(0, 6)} | ||||
| {isShare && x.data?.thoughts && ( | {isShare && x.data?.thoughts && ( | ||||
| <AccordionContent> | <AccordionContent> | ||||
| <div className="space-y-2"> | <div className="space-y-2"> | ||||
| <div className="w-full h-[200px] break-words overflow-auto scrollbar-auto p-2 bg-slate-800"> | |||||
| <div className="w-full h-[200px] break-words overflow-auto scrollbar-auto p-2 bg-muted"> | |||||
| <HightLightMarkdown> | <HightLightMarkdown> | ||||
| {x.data.thoughts || ''} | {x.data.thoughts || ''} | ||||
| </HightLightMarkdown> | </HightLightMarkdown> |
| [Operator.Email]: 'sendemail-0', | [Operator.Email]: 'sendemail-0', | ||||
| }; | }; | ||||
| const SVGIconMap = { | |||||
| export const SVGIconMap = { | |||||
| [Operator.ArXiv]: ArxivIcon, | [Operator.ArXiv]: ArxivIcon, | ||||
| [Operator.GitHub]: GithubIcon, | [Operator.GitHub]: GithubIcon, | ||||
| [Operator.Bing]: BingIcon, | [Operator.Bing]: BingIcon, |
| <div className="flex justify-between items-center"> | <div className="flex justify-between items-center"> | ||||
| <h1 className="text-2xl font-bold mb-4">Log</h1> | <h1 className="text-2xl font-bold mb-4">Log</h1> | ||||
| <div className="flex justify-end space-x-2 mb-4"> | |||||
| <div className="flex justify-end space-x-2 mb-4 text-foreground"> | |||||
| <div className="flex items-center space-x-2"> | <div className="flex items-center space-x-2"> | ||||
| <span>ID/Title</span> | <span>ID/Title</span> | ||||
| <SearchInput | <SearchInput |
| } from '@/hooks/use-agent-request'; | } from '@/hooks/use-agent-request'; | ||||
| import { cn } from '@/lib/utils'; | import { cn } from '@/lib/utils'; | ||||
| import i18n from '@/locales/config'; | import i18n from '@/locales/config'; | ||||
| import DebugContent from '@/pages/agent/debug-content'; | |||||
| import { useCacheChatLog } from '@/pages/agent/hooks/use-cache-chat-log'; | import { useCacheChatLog } from '@/pages/agent/hooks/use-cache-chat-log'; | ||||
| import { useAwaitCompentData } from '@/pages/agent/hooks/use-chat-logic'; | |||||
| import { IInputs } from '@/pages/agent/interface'; | import { IInputs } from '@/pages/agent/interface'; | ||||
| import { useSendButtonDisabled } from '@/pages/chat/hooks'; | import { useSendButtonDisabled } from '@/pages/chat/hooks'; | ||||
| import { buildMessageUuidWithRole } from '@/utils/chat'; | import { buildMessageUuidWithRole } from '@/utils/chat'; | ||||
| appendUploadResponseList, | appendUploadResponseList, | ||||
| parameterDialogVisible, | parameterDialogVisible, | ||||
| showParameterDialog, | showParameterDialog, | ||||
| sendFormMessage, | |||||
| ok, | ok, | ||||
| resetSession, | resetSession, | ||||
| } = useSendNextSharedMessage(addEventList); | } = useSendNextSharedMessage(addEventList); | ||||
| const { buildInputList, handleOk, isWaitting } = useAwaitCompentData({ | |||||
| derivedMessages, | |||||
| sendFormMessage, | |||||
| canvasId: conversationId as string, | |||||
| }); | |||||
| const sendDisabled = useSendButtonDisabled(value); | const sendDisabled = useSendButtonDisabled(value); | ||||
| const appConf = useFetchAppConf(); | const appConf = useFetchAppConf(); | ||||
| const { data: inputsData } = useFetchExternalAgentInputs(); | const { data: inputsData } = useFetchExternalAgentInputs(); | ||||
| showLoudspeaker={false} | showLoudspeaker={false} | ||||
| showLog={false} | showLog={false} | ||||
| sendLoading={sendLoading} | sendLoading={sendLoading} | ||||
| ></MessageItem> | |||||
| > | |||||
| {message.role === MessageType.Assistant && | |||||
| derivedMessages.length - 1 === i && ( | |||||
| <DebugContent | |||||
| parameters={buildInputList(message)} | |||||
| message={message} | |||||
| ok={handleOk(message)} | |||||
| isNext={false} | |||||
| btnText={'Submit'} | |||||
| ></DebugContent> | |||||
| )} | |||||
| {message.role === MessageType.Assistant && | |||||
| derivedMessages.length - 1 !== i && ( | |||||
| <div> | |||||
| <div>{message?.data?.tips}</div> | |||||
| <div> | |||||
| {buildInputList(message)?.map((item) => item.value)} | |||||
| </div> | |||||
| </div> | |||||
| )} | |||||
| </MessageItem> | |||||
| ); | ); | ||||
| })} | })} | ||||
| </div> | </div> | ||||
| <NextMessageInput | <NextMessageInput | ||||
| isShared | isShared | ||||
| value={value} | value={value} | ||||
| disabled={hasError} | |||||
| sendDisabled={sendDisabled} | |||||
| disabled={hasError || isWaitting} | |||||
| sendDisabled={sendDisabled || isWaitting} | |||||
| conversationId={conversationId} | conversationId={conversationId} | ||||
| onInputChange={handleInputChange} | onInputChange={handleInputChange} | ||||
| onPressEnter={handlePressEnter} | onPressEnter={handlePressEnter} | ||||
| sendLoading={sendLoading} | sendLoading={sendLoading} | ||||
| stopOutputMessage={stopOutputMessage} | stopOutputMessage={stopOutputMessage} | ||||
| onUpload={handleUploadFile} | onUpload={handleUploadFile} | ||||
| isUploading={loading} | |||||
| isUploading={loading || isWaitting} | |||||
| ></NextMessageInput> | ></NextMessageInput> | ||||
| </div> | </div> | ||||
| </div> | </div> |
| @layer utilities { | @layer utilities { | ||||
| .scrollbar-auto { | .scrollbar-auto { | ||||
| /* hide scrollbar */ | /* hide scrollbar */ | ||||
| scrollbar-width: thin; | |||||
| scrollbar-width: none; | |||||
| scrollbar-color: transparent transparent; | scrollbar-color: transparent transparent; | ||||
| } | } | ||||