refactor(logs): pass app identity into log inspectors (#43261)

This commit is contained in:
yyh authored and GitHub committed 2026-09-30 04:35:56 +00:00
1 parent d5c3fb9ecd
commit df016cd309
12 files changed
+234 -177

No files matched your search

-16
View File
@@ -593,17 +593,9 @@
"count": 2
}
},
"web/app/components/base/agent-log-modal/detail.tsx": {
"typescript/no-explicit-any": {
"count": 1
}
},
"web/app/components/base/agent-log-modal/index.stories.tsx": {
"no-console": {
"count": 1
},
"typescript/no-explicit-any": {
"count": 1
}
},
"web/app/components/base/agent-log-modal/index.tsx": {
@@ -1141,17 +1133,9 @@
"count": 4
}
},
"web/app/components/base/message-log-modal/__tests__/index.spec.tsx": {
"dify/require-disable-directive-description": {
"count": 1
}
},
"web/app/components/base/message-log-modal/index.stories.tsx": {
"no-console": {
"count": 1
},
"typescript/no-explicit-any": {
"count": 1
}
},
"web/app/components/base/new-audio-button/index.tsx": {
@@ -638,6 +638,7 @@ const Debug: FC<IDebug> = ({
{selectedLogItem &&
(selectedLogItem.agent_thoughts?.length ? (
<AgentLogModal
appId={appId}
width={logModalWidth}
currentLogItem={selectedLogItem}
onCancel={() => setSelectedLogItem(undefined)}
+2
View File
@@ -588,6 +588,7 @@ function DetailPanel({ appDetail, detail, onClose, onFeedback }: IDetailPanel) {
{logKind === 'workflow' && (
<WorkflowContextProvider>
<MessageLogModal
appId={appDetail.id}
width={width}
currentLogItem={selectedLogItem}
onCancel={() => setSelectedLogItem(undefined)}
@@ -596,6 +597,7 @@ function DetailPanel({ appDetail, detail, onClose, onFeedback }: IDetailPanel) {
)}
{logKind === 'agent' && (
<AgentLogModal
appId={appDetail.id}
floating
width={width}
currentLogItem={selectedLogItem}
@@ -2,7 +2,6 @@ import type { ComponentProps, ReactNode } from 'react'
import type { IChatItem } from '@/app/components/base/chat/chat/type'
import type { AgentLogDetailResponse } from '@/models/log'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { useStore as useAppStore } from '@/app/components/app/store'
import { fetchAgentLogDetail } from '@/service/log'
import AgentLogDetail from '../detail'
@@ -27,10 +26,6 @@ vi.mock('@/app/notifications', () => ({
toast: mockToast,
}))
vi.mock('@/app/components/app/store', () => ({
useStore: vi.fn((selector) => selector({ appDetail: { id: 'app-id' } })),
}))
vi.mock('@/app/components/workflow/run/status', () => ({
default: ({
status,
@@ -116,6 +111,7 @@ const createMockResponse = (
describe('AgentLogDetail', () => {
const renderComponent = (props: Partial<ComponentProps<typeof AgentLogDetail>> = {}) => {
const defaultProps: ComponentProps<typeof AgentLogDetail> = {
appId: 'app-id',
conversationID: 'conv-id',
messageID: 'msg-id',
log: createMockLog(),
@@ -228,20 +224,6 @@ describe('AgentLogDetail', () => {
})
describe('Edge Cases', () => {
it('should not fetch data when app detail is unavailable', async () => {
vi.mocked(useAppStore).mockImplementationOnce((selector) =>
selector({ appDetail: undefined } as never),
)
vi.mocked(fetchAgentLogDetail).mockResolvedValue(createMockResponse())
renderComponent()
await waitFor(() => {
expect(fetchAgentLogDetail).not.toHaveBeenCalled()
})
expect(screen.getByRole('progressbar')).toBeInTheDocument()
})
it('should notify on API error', async () => {
vi.mocked(fetchAgentLogDetail).mockRejectedValue(new Error('API Error'))
@@ -1,5 +1,5 @@
import type { IChatItem } from '@/app/components/base/chat/chat/type'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { useClickAway } from 'ahooks'
import { fetchAgentLogDetail } from '@/service/log'
import AgentLogModal from '../index'
@@ -25,10 +25,6 @@ vi.mock('@/app/notifications', () => ({
toast: mockToast,
}))
vi.mock('@/app/components/app/store', () => ({
useStore: vi.fn((selector) => selector({ appDetail: { id: 'app-id' } })),
}))
vi.mock('@/app/components/workflow/run/status', () => ({
default: ({
status,
@@ -122,19 +118,25 @@ describe('AgentLogModal', () => {
})
it('should return null if no currentLogItem', () => {
const { container } = render(<AgentLogModal {...mockProps} currentLogItem={undefined} />)
const { container } = render(
<AgentLogModal appId="app-id" {...mockProps} currentLogItem={undefined} />,
)
expect(container.firstChild).toBeNull()
})
it('should return null if no conversationId', () => {
const { container } = render(
<AgentLogModal {...mockProps} currentLogItem={{ id: '1' } as unknown as IChatItem} />,
<AgentLogModal
appId="app-id"
{...mockProps}
currentLogItem={{ id: '1' } as unknown as IChatItem}
/>,
)
expect(container.firstChild).toBeNull()
})
it('should render correctly when log item is provided', async () => {
render(<AgentLogModal {...mockProps} />)
render(<AgentLogModal appId="app-id" {...mockProps} />)
expect(screen.getByText('appLog.runDetail.workflowTitle')).toBeInTheDocument()
@@ -146,7 +148,7 @@ describe('AgentLogModal', () => {
it('should render the floating modal through a dialog portal', () => {
vi.mocked(fetchAgentLogDetail).mockReturnValue(new Promise(() => {}))
const { container } = render(<AgentLogModal {...mockProps} floating />)
const { container } = render(<AgentLogModal appId="app-id" {...mockProps} floating />)
const modal = screen.getByRole('dialog')
expect(container).not.toContainElement(modal)
@@ -157,7 +159,7 @@ describe('AgentLogModal', () => {
it('should call onCancel when close button is clicked', () => {
vi.mocked(fetchAgentLogDetail).mockReturnValue(new Promise(() => {}))
render(<AgentLogModal {...mockProps} />)
render(<AgentLogModal appId="app-id" {...mockProps} />)
const closeBtn = screen.getByRole('button', { name: 'common.operation.close' })
fireEvent.click(closeBtn)
@@ -173,7 +175,7 @@ describe('AgentLogModal', () => {
clickAwayHandler = callback
})
render(<AgentLogModal {...mockProps} />)
render(<AgentLogModal appId="app-id" {...mockProps} />)
clickAwayHandler(new Event('click'))
expect(mockProps.onCancel).toHaveBeenCalledTimes(1)
@@ -189,7 +191,7 @@ describe('AgentLogModal', () => {
}
})
render(<AgentLogModal {...mockProps} />)
render(<AgentLogModal appId="app-id" {...mockProps} />)
expect(mockProps.onCancel).not.toHaveBeenCalled()
})
@@ -202,9 +204,83 @@ describe('AgentLogModal', () => {
clickAwayHandler = callback
})
render(<AgentLogModal {...mockProps} floating />)
render(<AgentLogModal appId="app-id" {...mockProps} floating />)
clickAwayHandler(new Event('click'))
expect(mockProps.onCancel).not.toHaveBeenCalled()
})
it('keeps the new app log visible when the previous app response arrives late', async () => {
const response = {
meta: {
status: 'succeeded',
executor: 'New executor',
start_time: '2023-01-01',
elapsed_time: 1,
total_tokens: 1,
agent_mode: 'function_call',
iterations: 0,
},
iterations: [],
files: [],
}
let resolveOldResponse!: (value: Awaited<ReturnType<typeof fetchAgentLogDetail>>) => void
const pending = new Promise<Awaited<ReturnType<typeof fetchAgentLogDetail>>>((resolve) => {
resolveOldResponse = resolve
})
vi.mocked(fetchAgentLogDetail)
.mockResolvedValue(response)
.mockImplementationOnce(() => pending)
const { rerender } = render(<AgentLogModal appId="old-app" {...mockProps} />)
rerender(<AgentLogModal appId="new-app" {...mockProps} />)
await screen.findByText('New executor')
expect(fetchAgentLogDetail).toHaveBeenLastCalledWith({
appID: 'new-app',
params: { conversation_id: mockLog.conversationId, message_id: mockLog.id },
})
await act(async () => {
resolveOldResponse({ ...response, meta: { ...response.meta, executor: 'Old executor' } })
await pending
})
expect(screen.getByText('New executor')).toBeInTheDocument()
expect(screen.queryByText('Old executor')).not.toBeInTheDocument()
})
it('preserves the selected tab when another message replaces the open record', async () => {
const { rerender } = render(<AgentLogModal appId="app-id" {...mockProps} />)
await screen.findByText('User')
fireEvent.click(screen.getByRole('button', { name: 'runLog.tracing' }))
rerender(
<AgentLogModal
appId="app-id"
{...mockProps}
currentLogItem={{ ...mockLog, id: 'next-message' }}
/>,
)
expect(screen.getByRole('button', { name: 'runLog.tracing' })).toHaveAttribute(
'data-active',
'true',
)
expect(screen.getByRole('progressbar')).toBeInTheDocument()
await waitFor(() => expect(screen.queryByRole('progressbar')).not.toBeInTheDocument())
expect(fetchAgentLogDetail).toHaveBeenLastCalledWith({
appID: 'app-id',
params: { conversation_id: mockLog.conversationId, message_id: 'next-message' },
})
})
it('ignores a failed request after its record has been replaced', async () => {
let rejectOldResponse: (error: Error) => void = () => {}
const pending = new Promise<Awaited<ReturnType<typeof fetchAgentLogDetail>>>((_, reject) => {
rejectOldResponse = reject
})
vi.mocked(fetchAgentLogDetail).mockReturnValueOnce(pending)
const { rerender } = render(<AgentLogModal appId="old-app" {...mockProps} />)
rerender(<AgentLogModal appId="new-app" {...mockProps} />)
await screen.findByText('User')
await act(async () => {
rejectOldResponse(new Error('Old request failed'))
})
expect(mockToast.error).not.toHaveBeenCalled()
expect(screen.getByText('User')).toBeInTheDocument()
})
})
@@ -1,14 +1,12 @@
'use client'
import type { FC } from 'react'
import type { IChatItem } from '@/app/components/base/chat/chat/type'
import type { AgentIteration, AgentLogDetailResponse } from '@/models/log'
import type { AgentLogDetailResponse } from '@/models/log'
import { cn } from '@langgenius/dify-ui/cn'
import { uniq } from 'es-toolkit/array'
import { flatten } from 'es-toolkit/compat'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useStore as useAppStore } from '@/app/components/app/store'
import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder'
import { toast } from '@/app/notifications'
import { fetchAgentLogDetail } from '@/service/log'
@@ -16,63 +14,21 @@ import ResultPanel from './result'
import TracingPanel from './tracing'
type AgentLogDetailProps = Readonly<{
appId: string
activeTab?: 'DETAIL' | 'TRACING'
conversationID: string
log: IChatItem
messageID: string
}>
const AgentLogDetail: FC<AgentLogDetailProps> = ({
appId,
activeTab = 'DETAIL',
conversationID,
messageID,
log,
}) => {
const { t } = useTranslation(['runLog'])
const [currentTab, setCurrentTab] = useState<string>(activeTab)
const appDetail = useAppStore((s) => s.appDetail)
const [loading, setLoading] = useState<boolean>(true)
const [runDetail, setRunDetail] = useState<AgentLogDetailResponse>()
const [list, setList] = useState<AgentIteration[]>([])
const tools = useMemo(() => {
const res = uniq(
flatten(
runDetail?.iterations.map((iteration) => {
return iteration.tool_calls.map((tool: any) => tool.tool_name).filter(Boolean)
}),
).filter(Boolean),
)
return res
}, [runDetail])
const getLogDetail = useCallback(
async (appID: string, conversationID: string, messageID: string) => {
try {
const res = await fetchAgentLogDetail({
appID,
params: {
conversation_id: conversationID,
message_id: messageID,
},
})
setRunDetail(res)
setList(res.iterations)
} catch (err) {
toast.error(`${err}`)
}
},
[],
)
const getData = async (appID: string, conversationID: string, messageID: string) => {
setLoading(true)
await getLogDetail(appID, conversationID, messageID)
setLoading(false)
}
const switchTab = async (tab: string) => {
setCurrentTab(tab)
}
useEffect(() => {
// fetch data
if (appDetail) getData(appDetail.id, conversationID, messageID)
}, [appDetail, conversationID, messageID])
const [currentTab, setCurrentTab] = useState<'DETAIL' | 'TRACING'>(activeTab)
return (
<div className="relative flex grow flex-col">
{/* tab */}
@@ -84,7 +40,7 @@ const AgentLogDetail: FC<AgentLogDetailProps> = ({
currentTab === 'DETAIL' && 'border-[rgb(21,94,239)]! text-text-secondary',
)}
data-active={currentTab === 'DETAIL'}
onClick={() => switchTab('DETAIL')}
onClick={() => setCurrentTab('DETAIL')}
>
{t(($) => $.detail, { ns: 'runLog' })}
</button>
@@ -95,41 +51,99 @@ const AgentLogDetail: FC<AgentLogDetailProps> = ({
currentTab === 'TRACING' && 'border-[rgb(21,94,239)]! text-text-secondary',
)}
data-active={currentTab === 'TRACING'}
onClick={() => switchTab('TRACING')}
onClick={() => setCurrentTab('TRACING')}
>
{t(($) => $.tracing, { ns: 'runLog' })}
</button>
</div>
{/* panel detail */}
<div
className={cn(
'h-0 grow overflow-y-auto rounded-b-2xl bg-components-panel-bg',
currentTab !== 'DETAIL' && 'bg-background-section!',
)}
>
{loading && (
<div className="flex h-full items-center justify-center bg-components-panel-bg">
<LoadingPlaceholder />
</div>
)}
{!loading && currentTab === 'DETAIL' && runDetail && (
<ResultPanel
inputs={log.input}
outputs={log.content}
status={runDetail.meta.status}
error={runDetail.meta.error}
elapsed_time={runDetail.meta.elapsed_time}
total_tokens={runDetail.meta.total_tokens}
created_at={runDetail.meta.start_time}
created_by={runDetail.meta.executor}
agentMode={runDetail.meta.agent_mode}
tools={tools}
iterations={runDetail.iterations.length}
/>
)}
{!loading && currentTab === 'TRACING' && <TracingPanel list={list} />}
</div>
<AgentLogContent
key={JSON.stringify([appId, conversationID, messageID])}
appId={appId}
conversationID={conversationID}
messageID={messageID}
log={log}
currentTab={currentTab}
/>
</div>
)
}
type AgentLogContentProps = Omit<AgentLogDetailProps, 'activeTab'> & {
currentTab: 'DETAIL' | 'TRACING'
}
function AgentLogContent({
appId,
conversationID,
messageID,
log,
currentTab,
}: AgentLogContentProps) {
const [loading, setLoading] = useState(true)
const [runDetail, setRunDetail] = useState<AgentLogDetailResponse>()
const tools = useMemo(
() =>
uniq(
(runDetail?.iterations ?? []).flatMap((iteration) =>
iteration.tool_calls
.map((tool) => tool.tool_name)
.filter((name): name is string => !!name),
),
),
[runDetail],
)
useEffect(() => {
let active = true
fetchAgentLogDetail({
appID: appId,
params: { conversation_id: conversationID, message_id: messageID },
}).then(
(data) => {
if (!active) return
setRunDetail(data)
setLoading(false)
},
(error) => {
if (!active) return
toast.error(`${error}`)
setLoading(false)
},
)
return () => {
active = false
}
}, [appId, conversationID, messageID])
return (
<div
className={cn(
'h-0 grow overflow-y-auto rounded-b-2xl bg-components-panel-bg',
currentTab !== 'DETAIL' && 'bg-background-section!',
)}
>
{loading && (
<div className="flex h-full items-center justify-center bg-components-panel-bg">
<LoadingPlaceholder />
</div>
)}
{!loading && currentTab === 'DETAIL' && runDetail && (
<ResultPanel
inputs={log.input}
outputs={log.content}
status={runDetail.meta.status}
error={runDetail.meta.error}
elapsed_time={runDetail.meta.elapsed_time}
total_tokens={runDetail.meta.total_tokens}
created_at={runDetail.meta.start_time}
created_by={runDetail.meta.executor}
agentMode={runDetail.meta.agent_mode}
tools={tools}
iterations={runDetail.iterations.length}
/>
)}
{!loading && currentTab === 'TRACING' && <TracingPanel list={runDetail?.iterations ?? []} />}
</div>
)
}
export default AgentLogDetail
@@ -2,7 +2,6 @@ import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import type { IChatItem } from '@/app/components/base/chat/chat/type'
import type { AgentLogDetailResponse } from '@/models/log'
import { useEffect, useRef } from 'react'
import { useStore as useAppStore } from '@/app/components/app/store'
import { AppToastHost } from '@/app/notifications/host'
import AgentLogModal from '.'
@@ -66,15 +65,8 @@ const MOCK_CHAT_ITEM: IChatItem = {
const AgentLogModalDemo = ({ width = 960 }: { width?: number }) => {
const originalFetchRef = useRef<typeof globalThis.fetch>(null)
const setAppDetail = useAppStore((state) => state.setAppDetail)
useEffect(() => {
setAppDetail({
id: 'app-1',
name: 'Analytics Agent',
mode: 'agent-chat',
} as any)
originalFetchRef.current = globalThis.fetch?.bind(globalThis)
const handler = async (input: RequestInfo | URL, init?: RequestInit) => {
@@ -98,15 +90,15 @@ const AgentLogModalDemo = ({ width = 960 }: { width?: number }) => {
return () => {
if (originalFetchRef.current) globalThis.fetch = originalFetchRef.current
setAppDetail(undefined)
}
}, [setAppDetail])
}, [])
return (
<>
<AppToastHost />
<div className="relative min-h-135 w-full bg-background-default-subtle p-6">
<AgentLogModal
appId="app-1"
currentLogItem={MOCK_CHAT_ITEM}
width={width}
onCancel={() => {
@@ -9,12 +9,19 @@ import { useTranslation } from 'react-i18next'
import AgentLogDetail from './detail'
type AgentLogModalProps = Readonly<{
appId: string
currentLogItem?: IChatItem
width: number
floating?: boolean
onCancel: () => void
}>
const AgentLogModal: FC<AgentLogModalProps> = ({ currentLogItem, width, floating, onCancel }) => {
const AgentLogModal: FC<AgentLogModalProps> = ({
appId,
currentLogItem,
width,
floating,
onCancel,
}) => {
const { t } = useTranslation(['appLog', 'common'])
const ref = useRef(null)
const [mounted, setMounted] = useState(false)
@@ -32,6 +39,7 @@ const AgentLogModal: FC<AgentLogModalProps> = ({ currentLogItem, width, floating
const detailContent = (
<>
<AgentLogDetail
appId={appId}
conversationID={currentLogItem.conversationId}
messageID={currentLogItem.id}
log={currentLogItem}
@@ -1,6 +1,5 @@
import type { IChatItem } from '@/app/components/base/chat/chat/type'
import { fireEvent, render, screen } from '@testing-library/react'
import { useStore } from '@/app/components/app/store'
import MessageLogModal from '../index'
let clickAwayHandler: (() => void) | null = null
@@ -10,10 +9,6 @@ vi.mock('ahooks', () => ({
},
}))
vi.mock('@/app/components/app/store', () => ({
useStore: vi.fn(),
}))
vi.mock('@/app/components/workflow/run', () => ({
default: ({
activeTab,
@@ -46,23 +41,20 @@ describe('MessageLogModal', () => {
beforeEach(() => {
vi.clearAllMocks()
clickAwayHandler = null
// oxlint-disable-next-line typescript/no-explicit-any
vi.mocked(useStore).mockImplementation((selector: any) =>
selector({
appDetail: { id: 'app-1' },
}),
)
})
describe('Render', () => {
it('renders nothing if currentLogItem is missing', () => {
const { container } = render(<MessageLogModal width={800} onCancel={onCancel} />)
const { container } = render(
<MessageLogModal appId="app-1" width={800} onCancel={onCancel} />,
)
expect(container.firstChild).toBeNull()
})
it('renders nothing if currentLogItem.workflow_run_id is missing', () => {
const { container } = render(
<MessageLogModal
appId="app-1"
width={800}
onCancel={onCancel}
currentLogItem={{ id: '1' } as IChatItem}
@@ -72,7 +64,9 @@ describe('MessageLogModal', () => {
})
it('renders modal with correct title and Run component', () => {
render(<MessageLogModal width={800} onCancel={onCancel} currentLogItem={mockLog} />)
render(
<MessageLogModal appId="app-1" width={800} onCancel={onCancel} currentLogItem={mockLog} />,
)
expect(screen.getByText(/title/i))!.toBeInTheDocument()
expect(screen.getByTestId('workflow-run'))!.toBeInTheDocument()
})
@@ -82,6 +76,7 @@ describe('MessageLogModal', () => {
it('passes correct props to Run component', () => {
render(
<MessageLogModal
appId="inspected-app"
width={800}
onCancel={onCancel}
currentLogItem={mockLog}
@@ -91,16 +86,17 @@ describe('MessageLogModal', () => {
const runComponent = screen.getByTestId('workflow-run')
expect(runComponent.getAttribute('data-active-tab')).toBe('TRACING')
expect(runComponent.getAttribute('data-run-detail-url')).toBe(
'/apps/app-1/workflow-runs/run-1',
'/apps/inspected-app/workflow-runs/run-1',
)
expect(runComponent.getAttribute('data-tracing-list-url')).toBe(
'/apps/app-1/workflow-runs/run-1/node-executions',
'/apps/inspected-app/workflow-runs/run-1/node-executions',
)
})
it('sets fixed style when fixedWidth is false (floating)', () => {
const { container } = render(
<MessageLogModal
appId="app-1"
width={1000}
onCancel={onCancel}
currentLogItem={mockLog}
@@ -115,6 +111,7 @@ describe('MessageLogModal', () => {
it('sets fixed width when fixedWidth is true', () => {
const { container } = render(
<MessageLogModal
appId="app-1"
width={1000}
onCancel={onCancel}
currentLogItem={mockLog}
@@ -129,7 +126,9 @@ describe('MessageLogModal', () => {
describe('Interaction', () => {
it('calls onCancel when close icon is clicked', () => {
render(<MessageLogModal width={800} onCancel={onCancel} currentLogItem={mockLog} />)
render(
<MessageLogModal appId="app-1" width={800} onCancel={onCancel} currentLogItem={mockLog} />,
)
const closeButton = screen.getByRole('button', { name: 'common.operation.close' })
expect(closeButton)!.toBeInTheDocument()
fireEvent.click(closeButton)
@@ -138,7 +137,13 @@ describe('MessageLogModal', () => {
it('calls onCancel when clicked away', () => {
render(
<MessageLogModal width={800} onCancel={onCancel} currentLogItem={mockLog} fixedWidth />,
<MessageLogModal
appId="app-1"
width={800}
onCancel={onCancel}
currentLogItem={mockLog}
fixedWidth
/>,
)
expect(clickAwayHandler).toBeTruthy()
clickAwayHandler!()
@@ -146,7 +151,9 @@ describe('MessageLogModal', () => {
})
it('does not use click away to close the floating dialog', () => {
render(<MessageLogModal width={800} onCancel={onCancel} currentLogItem={mockLog} />)
render(
<MessageLogModal appId="app-1" width={800} onCancel={onCancel} currentLogItem={mockLog} />,
)
expect(clickAwayHandler).toBeTruthy()
clickAwayHandler!()
expect(onCancel).not.toHaveBeenCalled()
@@ -3,17 +3,10 @@ import type { IChatItem } from '@/app/components/base/chat/chat/type'
import type { WorkflowRunDetailResponse } from '@/models/log'
import type { NodeTracing, NodeTracingListResponse } from '@/types/workflow'
import { useEffect } from 'react'
import { useStore } from '@/app/components/app/store'
import { WorkflowContextProvider } from '@/app/components/workflow/context'
import { BlockEnum } from '@/app/components/workflow/types'
import MessageLogModal from '.'
const SAMPLE_APP_DETAIL = {
id: 'app-demo-1',
name: 'Support Assistant',
mode: 'chat',
} as any
const mockRunDetail: WorkflowRunDetailResponse = {
id: 'run-demo-1',
version: 'v1.0.0',
@@ -96,9 +89,6 @@ const mockCurrentLogItem: IChatItem = {
const useMessageLogMocks = () => {
useEffect(() => {
const store = useStore.getState()
store.setAppDetail(SAMPLE_APP_DETAIL)
const originalFetch = globalThis.fetch?.bind(globalThis) ?? null
const handle = async (input: RequestInfo | URL, init?: RequestInit) => {
@@ -128,7 +118,6 @@ const useMessageLogMocks = () => {
return () => {
globalThis.fetch = originalFetch || globalThis.fetch
useStore.getState().setAppDetail(undefined)
}
}, [])
}
@@ -160,6 +149,7 @@ const meta = {
},
},
args: {
appId: 'app-demo-1',
defaultTab: 'DETAIL',
width: 960,
fixedWidth: true,
@@ -5,7 +5,6 @@ import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { useClickAway } from 'ahooks'
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'
@@ -14,6 +13,7 @@ const isRunActiveTab = (tab: string): tab is RunActiveTab =>
tab === 'RESULT' || tab === 'DETAIL' || tab === 'TRACING'
type MessageLogModalProps = {
appId: string
currentLogItem?: IChatItem
defaultTab?: string
width: number
@@ -21,6 +21,7 @@ type MessageLogModalProps = {
onCancel: () => void
}
const MessageLogModal: FC<MessageLogModalProps> = ({
appId,
currentLogItem,
defaultTab = 'DETAIL',
width,
@@ -29,7 +30,6 @@ const MessageLogModal: FC<MessageLogModalProps> = ({
}) => {
const { t } = useTranslation(['appLog', 'common'])
const ref = useRef(null)
const appDetail = useStore((state) => state.appDetail)
useClickAway(() => {
if (fixedWidth) onCancel()
@@ -54,8 +54,8 @@ const MessageLogModal: FC<MessageLogModalProps> = ({
<Run
hideResult
activeTab={activeTab}
runDetailUrl={`/apps/${appDetail?.id}/workflow-runs/${currentLogItem.workflow_run_id}`}
tracingListUrl={`/apps/${appDetail?.id}/workflow-runs/${currentLogItem.workflow_run_id}/node-executions`}
runDetailUrl={`/apps/${appId}/workflow-runs/${currentLogItem.workflow_run_id}`}
tracingListUrl={`/apps/${appId}/workflow-runs/${currentLogItem.workflow_run_id}/node-executions`}
/>
</>
)
@@ -104,8 +104,8 @@ const MessageLogModal: FC<MessageLogModalProps> = ({
<Run
hideResult
activeTab={activeTab}
runDetailUrl={`/apps/${appDetail?.id}/workflow-runs/${currentLogItem.workflow_run_id}`}
tracingListUrl={`/apps/${appDetail?.id}/workflow-runs/${currentLogItem.workflow_run_id}/node-executions`}
runDetailUrl={`/apps/${appId}/workflow-runs/${currentLogItem.workflow_run_id}`}
tracingListUrl={`/apps/${appId}/workflow-runs/${currentLogItem.workflow_run_id}/node-executions`}
/>
</div>
)
@@ -35,13 +35,14 @@ const GlobalVariablePanel = dynamic(
},
)
const WorkflowPanelOnLeft = () => {
const WorkflowPanelOnLeft = ({ appId }: { appId: string }) => {
const messageLogItem = useStore((state) => state.messageLogItem)
const setMessageLogItem = useStore((state) => state.setMessageLogItem)
return (
<>
{messageLogItem && (
<MessageLogModal
appId={appId}
fixedWidth
width={400}
currentLogItem={messageLogItem}
@@ -88,12 +89,12 @@ const WorkflowPanel = () => {
const panelProps: PanelProps = useMemo(() => {
return {
components: {
left: <WorkflowPanelOnLeft />,
left: appDetail && <WorkflowPanelOnLeft appId={appDetail.id} />,
right: <WorkflowPanelOnRight />,
},
versionHistoryPanelProps,
}
}, [versionHistoryPanelProps])
}, [appDetail, versionHistoryPanelProps])
return <Panel {...panelProps} />
}