Files
urbanLifeline/dify/web/app/components/base/message-log-modal/index.stories.tsx

189 lines
4.8 KiB
TypeScript
Raw Normal View History

2025-12-01 17:21:38 +08:00
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,
},
}