dify
This commit is contained in:
188
dify/web/app/components/base/message-log-modal/index.stories.tsx
Normal file
188
dify/web/app/components/base/message-log-modal/index.stories.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import { useEffect } from 'react'
|
||||
import MessageLogModal from '.'
|
||||
import type { IChatItem } from '@/app/components/base/chat/chat/type'
|
||||
import { useStore } from '@/app/components/app/store'
|
||||
import type { WorkflowRunDetailResponse } from '@/models/log'
|
||||
import type { NodeTracing, NodeTracingListResponse } from '@/types/workflow'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { WorkflowContextProvider } from '@/app/components/workflow/context'
|
||||
|
||||
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',
|
||||
graph: {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
},
|
||||
inputs: JSON.stringify({ question: 'How do I reset my password?' }, null, 2),
|
||||
inputs_truncated: false,
|
||||
status: 'succeeded',
|
||||
outputs: JSON.stringify({ answer: 'Follow the reset link we just emailed you.' }, null, 2),
|
||||
outputs_truncated: false,
|
||||
total_steps: 3,
|
||||
created_by_role: 'account',
|
||||
created_by_account: {
|
||||
id: 'account-1',
|
||||
name: 'Demo Admin',
|
||||
email: 'demo@example.com',
|
||||
},
|
||||
created_at: 1700000000,
|
||||
finished_at: 1700000006,
|
||||
elapsed_time: 5.2,
|
||||
total_tokens: 864,
|
||||
}
|
||||
|
||||
const buildNode = (override: Partial<NodeTracing>): NodeTracing => ({
|
||||
id: 'node-start',
|
||||
index: 0,
|
||||
predecessor_node_id: '',
|
||||
node_id: 'node-start',
|
||||
node_type: BlockEnum.Start,
|
||||
title: 'Start',
|
||||
inputs: {},
|
||||
inputs_truncated: false,
|
||||
process_data: {},
|
||||
process_data_truncated: false,
|
||||
outputs: {},
|
||||
outputs_truncated: false,
|
||||
status: 'succeeded',
|
||||
metadata: {
|
||||
iterator_length: 1,
|
||||
iterator_index: 0,
|
||||
loop_length: 1,
|
||||
loop_index: 0,
|
||||
},
|
||||
created_at: 1700000000,
|
||||
created_by: {
|
||||
id: 'account-1',
|
||||
name: 'Demo Admin',
|
||||
email: 'demo@example.com',
|
||||
},
|
||||
finished_at: 1700000001,
|
||||
elapsed_time: 1.1,
|
||||
extras: {},
|
||||
...override,
|
||||
})
|
||||
|
||||
const mockTracingList: NodeTracingListResponse = {
|
||||
data: [
|
||||
buildNode({}),
|
||||
buildNode({
|
||||
id: 'node-answer',
|
||||
node_id: 'node-answer',
|
||||
node_type: BlockEnum.Answer,
|
||||
title: 'Answer',
|
||||
inputs: { prompt: 'How do I reset my password?' },
|
||||
outputs: { output: 'Follow the reset link we just emailed you.' },
|
||||
finished_at: 1700000005,
|
||||
elapsed_time: 2.6,
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
const mockCurrentLogItem: IChatItem = {
|
||||
id: 'message-1',
|
||||
content: 'Follow the reset link we just emailed you.',
|
||||
isAnswer: true,
|
||||
workflow_run_id: 'run-demo-1',
|
||||
}
|
||||
|
||||
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) => {
|
||||
const url = typeof input === 'string'
|
||||
? input
|
||||
: input instanceof URL
|
||||
? input.toString()
|
||||
: input.url
|
||||
|
||||
if (url.includes('/workflow-runs/run-demo-1/') && url.endsWith('/node-executions')) {
|
||||
return new Response(
|
||||
JSON.stringify(mockTracingList),
|
||||
{ headers: { 'Content-Type': 'application/json' }, status: 200 },
|
||||
)
|
||||
}
|
||||
|
||||
if (url.endsWith('/workflow-runs/run-demo-1')) {
|
||||
return new Response(
|
||||
JSON.stringify(mockRunDetail),
|
||||
{ headers: { 'Content-Type': 'application/json' }, status: 200 },
|
||||
)
|
||||
}
|
||||
|
||||
if (originalFetch)
|
||||
return originalFetch(input, init)
|
||||
|
||||
throw new Error(`Unmocked fetch call for ${url}`)
|
||||
}
|
||||
|
||||
globalThis.fetch = handle as typeof globalThis.fetch
|
||||
|
||||
return () => {
|
||||
globalThis.fetch = originalFetch || globalThis.fetch
|
||||
useStore.getState().setAppDetail(undefined)
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
|
||||
type MessageLogModalProps = React.ComponentProps<typeof MessageLogModal>
|
||||
|
||||
const MessageLogPreview = (props: MessageLogModalProps) => {
|
||||
useMessageLogMocks()
|
||||
|
||||
return (
|
||||
<div className="relative min-h-[640px] w-full bg-background-default-subtle p-6">
|
||||
<WorkflowContextProvider>
|
||||
<MessageLogModal
|
||||
{...props}
|
||||
currentLogItem={mockCurrentLogItem}
|
||||
/>
|
||||
</WorkflowContextProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Feedback/MessageLogModal',
|
||||
component: MessageLogPreview,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Workflow run inspector presented alongside chat transcripts. This Storybook mock provides canned run details and tracing metadata.',
|
||||
},
|
||||
},
|
||||
},
|
||||
args: {
|
||||
defaultTab: 'DETAIL',
|
||||
width: 960,
|
||||
fixedWidth: true,
|
||||
onCancel: () => {
|
||||
console.log('Modal closed')
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof MessageLogPreview>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const FixedPanel: Story = {}
|
||||
|
||||
export const FloatingPanel: Story = {
|
||||
args: {
|
||||
fixedWidth: false,
|
||||
},
|
||||
}
|
||||
74
dify/web/app/components/base/message-log-modal/index.tsx
Normal file
74
dify/web/app/components/base/message-log-modal/index.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useClickAway } from 'ahooks'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { IChatItem } from '@/app/components/base/chat/chat/type'
|
||||
import Run from '@/app/components/workflow/run'
|
||||
import { useStore } from '@/app/components/app/store'
|
||||
|
||||
type MessageLogModalProps = {
|
||||
currentLogItem?: IChatItem
|
||||
defaultTab?: string
|
||||
width: number
|
||||
fixedWidth?: boolean
|
||||
onCancel: () => void
|
||||
}
|
||||
const MessageLogModal: FC<MessageLogModalProps> = ({
|
||||
currentLogItem,
|
||||
defaultTab = 'DETAIL',
|
||||
width,
|
||||
fixedWidth,
|
||||
onCancel,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const ref = useRef(null)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const appDetail = useStore(state => state.appDetail)
|
||||
|
||||
useClickAway(() => {
|
||||
if (mounted)
|
||||
onCancel()
|
||||
}, ref)
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
if (!currentLogItem || !currentLogItem.workflow_run_id)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('relative z-10 flex flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg pt-3 shadow-xl')}
|
||||
style={{
|
||||
width: fixedWidth ? width : 480,
|
||||
...(!fixedWidth
|
||||
? {
|
||||
position: 'fixed',
|
||||
top: 56 + 8,
|
||||
left: 8 + (width - 480),
|
||||
bottom: 16,
|
||||
}
|
||||
: {
|
||||
marginRight: 8,
|
||||
}),
|
||||
}}
|
||||
ref={ref}
|
||||
>
|
||||
<h1 className='system-xl-semibold shrink-0 px-4 py-1 text-text-primary'>{t('appLog.runDetail.title')}</h1>
|
||||
<span className='absolute right-3 top-4 z-20 cursor-pointer p-1' onClick={onCancel}>
|
||||
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
|
||||
</span>
|
||||
<Run
|
||||
hideResult
|
||||
activeTab={defaultTab as any}
|
||||
runDetailUrl={`/apps/${appDetail?.id}/workflow-runs/${currentLogItem.workflow_run_id}`}
|
||||
tracingListUrl={`/apps/${appDetail?.id}/workflow-runs/${currentLogItem.workflow_run_id}/node-executions`}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MessageLogModal
|
||||
Reference in New Issue
Block a user