From ebcc1200a369b9b682664e53157d96ca56ac7228 Mon Sep 17 00:00:00 2001 From: Coding On Star <447357187@qq.com> Date: Thu, 14 May 2026 16:50:21 +0800 Subject: [PATCH 01/23] feat(MessageLogModal): refactor modal structure and improve tab handling (#36169) Co-authored-by: CodingOnStar Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- eslint-suppressions.json | 8 -- .../__tests__/index.spec.tsx | 23 +++--- .../base/message-log-modal/index.tsx | 78 +++++++++++++------ 3 files changed, 67 insertions(+), 42 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 1f7e82aee0..b17c435984 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1422,14 +1422,6 @@ "count": 1 } }, - "web/app/components/base/message-log-modal/index.tsx": { - "react/set-state-in-effect": { - "count": 1 - }, - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/components/base/new-audio-button/index.tsx": { "ts/no-explicit-any": { "count": 1 diff --git a/web/app/components/base/message-log-modal/__tests__/index.spec.tsx b/web/app/components/base/message-log-modal/__tests__/index.spec.tsx index 49f2970654..f7e0158896 100644 --- a/web/app/components/base/message-log-modal/__tests__/index.spec.tsx +++ b/web/app/components/base/message-log-modal/__tests__/index.spec.tsx @@ -4,11 +4,9 @@ import { useStore } from '@/app/components/app/store' import MessageLogModal from '../index' let clickAwayHandler: (() => void) | null = null -let clickAwayHandlers: (() => void)[] = [] vi.mock('ahooks', () => ({ useClickAway: (fn: () => void) => { clickAwayHandler = fn - clickAwayHandlers.push(fn) }, })) @@ -40,7 +38,6 @@ describe('MessageLogModal', () => { beforeEach(() => { vi.clearAllMocks() clickAwayHandler = null - clickAwayHandlers = [] // eslint-disable-next-line ts/no-explicit-any vi.mocked(useStore).mockImplementation((selector: any) => selector({ appDetail: { id: 'app-1' }, @@ -76,15 +73,17 @@ describe('MessageLogModal', () => { it('sets fixed style when fixedWidth is false (floating)', () => { const { container } = render() - const modal = container.firstChild as HTMLElement - expect(modal.style.position).toBe('fixed') - expect(modal.style.width).toBe('480px') + const modal = screen.getByRole('dialog') + expect(container).not.toContainElement(modal) + expect(document.body).toContainElement(modal) + expect(modal).toHaveClass('fixed', 'z-50', 'w-[480px]!', 'left-[max(8px,calc(100vw-1136px))]!') }) it('sets fixed width when fixedWidth is true', () => { const { container } = render() - const modal = container.firstChild as HTMLElement - expect(modal.style.width).toBe('1000px') + const panel = container.firstElementChild as HTMLElement + expect(panel).toHaveClass('relative', 'z-10') + expect(panel.style.width).toBe('1000px') }) }) @@ -98,16 +97,16 @@ describe('MessageLogModal', () => { }) it('calls onCancel when clicked away', () => { - render() + render() expect(clickAwayHandler).toBeTruthy() clickAwayHandler!() expect(onCancel).toHaveBeenCalledTimes(1) }) - it('does not call onCancel when clicked away if not mounted', () => { + it('does not use click away to close the floating dialog', () => { render() - expect(clickAwayHandlers.length).toBeGreaterThan(0) - clickAwayHandlers[0]!() // This is the closure from the initial render, where mounted is false + expect(clickAwayHandler).toBeTruthy() + clickAwayHandler!() expect(onCancel).not.toHaveBeenCalled() }) }) diff --git a/web/app/components/base/message-log-modal/index.tsx b/web/app/components/base/message-log-modal/index.tsx index 9a58a0213d..6b63ccdf3f 100644 --- a/web/app/components/base/message-log-modal/index.tsx +++ b/web/app/components/base/message-log-modal/index.tsx @@ -1,13 +1,18 @@ import type { FC } from 'react' import type { IChatItem } from '@/app/components/base/chat/chat/type' import { cn } from '@langgenius/dify-ui/cn' -import { RiCloseLine } from '@remixicon/react' +import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog' import { useClickAway } from 'ahooks' -import { useEffect, useRef, useState } from 'react' +import { useRef } from 'react' import { useTranslation } from 'react-i18next' import { useStore } from '@/app/components/app/store' import Run from '@/app/components/workflow/run' +type RunActiveTab = 'RESULT' | 'DETAIL' | 'TRACING' + +const isRunActiveTab = (tab: string): tab is RunActiveTab => + tab === 'RESULT' || tab === 'DETAIL' || tab === 'TRACING' + type MessageLogModalProps = { currentLogItem?: IChatItem defaultTab?: string @@ -24,36 +29,65 @@ const MessageLogModal: FC = ({ }) => { const { t } = useTranslation() const ref = useRef(null) - const [mounted, setMounted] = useState(false) const appDetail = useStore(state => state.appDetail) useClickAway(() => { - if (mounted) + if (fixedWidth) onCancel() }, ref) - useEffect(() => { - setMounted(true) - }, []) - if (!currentLogItem || !currentLogItem.workflow_run_id) return null + const activeTab = isRunActiveTab(defaultTab) ? defaultTab : 'DETAIL' + const modalContent = ( + <> + {t('runDetail.title', { ns: 'appLog' })} + + + + ) + + if (!fixedWidth) { + return ( + { + if (!open) + onCancel() + }} + > + + {modalContent} + + + ) + } + return (
@@ -64,11 +98,11 @@ const MessageLogModal: FC = ({ className="absolute top-4 right-3 z-20 cursor-pointer border-none bg-transparent p-1 focus-visible:ring-1 focus-visible:ring-components-input-border-active focus-visible:outline-hidden" onClick={onCancel} > -