This commit is contained in:
2025-12-01 17:21:38 +08:00
parent 32fee2b8ab
commit fab8c13cb3
7511 changed files with 996300 additions and 0 deletions

View 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,
},
}

View 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