dify/web/app/components/workflow/header/view-history.tsx
Stephen Zhou a84c2d36a3
style: format with vp fmt (#38803)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-12 15:57:46 +00:00

183 lines
8.0 KiB
TypeScript

import { cn } from '@langgenius/dify-ui/cn'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { memo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Loading from '@/app/components/base/loading'
import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks'
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import { useWorkflowRunHistory } from '@/service/use-workflow'
import {
useIsChatMode,
useNodesInteractions,
useWorkflowInteractions,
useWorkflowRun,
} from '../hooks'
import { ControlMode, WorkflowRunningStatus } from '../types'
import { formatWorkflowRunIdentifier } from '../utils'
export type ViewHistoryProps = {
withText?: boolean
onClearLogAndMessageModal?: () => void
historyUrl?: string
}
const ViewHistory = ({ withText, onClearLogAndMessageModal, historyUrl }: ViewHistoryProps) => {
const { t } = useTranslation()
const isChatMode = useIsChatMode()
const [open, setOpen] = useState(false)
const { formatTimeFromNow } = useFormatTimeFromNow()
const { handleNodesCancelSelected } = useNodesInteractions()
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
const workflowStore = useWorkflowStore()
const setControlMode = useStore((s) => s.setControlMode)
const historyWorkflowData = useStore((s) => s.historyWorkflowData)
const { handleBackupDraft } = useWorkflowRun()
const { closeAllInputFieldPanels } = useInputFieldPanel()
const shouldFetchHistory = open && !!historyUrl
const { data, isLoading } = useWorkflowRunHistory(historyUrl, shouldFetchHistory)
return (
<Popover open={open} onOpenChange={setOpen}>
{withText ? (
<PopoverTrigger
render={
<button
type="button"
aria-label={t(($) => $['common.showRunHistory'], { ns: 'workflow' })}
className={cn(
'flex h-8 items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-3 shadow-xs',
'cursor-pointer text-[13px] font-medium text-components-button-secondary-text hover:bg-components-button-secondary-bg-hover',
'data-popup-open:bg-components-button-secondary-bg-hover',
)}
>
<span className="mr-1 i-custom-vender-line-time-clock-play size-4" />
{t(($) => $['common.showRunHistory'], { ns: 'workflow' })}
</button>
}
/>
) : (
<Tooltip>
<TooltipTrigger render={<div className="flex" />}>
<PopoverTrigger
render={
<button
type="button"
aria-label={t(($) => $['common.viewRunHistory'], { ns: 'workflow' })}
className="group flex size-7 cursor-pointer items-center justify-center rounded-md hover:bg-state-accent-hover data-popup-open:bg-state-accent-hover"
onClick={() => {
onClearLogAndMessageModal?.()
}}
>
<span className="i-custom-vender-line-time-clock-play size-4 text-components-button-ghost-text group-hover:text-components-button-secondary-accent-text group-data-popup-open:text-components-button-secondary-accent-text" />
</button>
}
/>
</TooltipTrigger>
<TooltipContent>
{t(($) => $['common.viewRunHistory'], { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
)}
<PopoverContent
placement={withText ? 'bottom-start' : 'bottom-end'}
sideOffset={4}
alignOffset={withText ? -8 : 10}
popupClassName="border-none bg-transparent shadow-none"
>
<div
className="ml-2 flex w-[240px] flex-col overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl"
style={{
maxHeight: 'calc(2 / 3 * 100vh)',
}}
>
<div className="sticky top-0 flex items-center justify-between bg-components-panel-bg px-4 pt-3 text-base font-semibold text-text-primary">
<div className="grow">{t(($) => $['common.runHistory'], { ns: 'workflow' })}</div>
<button
type="button"
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
className="flex size-6 shrink-0 cursor-pointer items-center justify-center"
onClick={() => {
onClearLogAndMessageModal?.()
setOpen(false)
}}
>
<span className="i-ri-close-line size-4 text-text-tertiary" />
</button>
</div>
{isLoading && (
<div className="flex h-10 items-center justify-center">
<Loading />
</div>
)}
{!isLoading && (
<div className="p-2">
{!data?.data.length && (
<div className="py-12">
<span className="mx-auto mb-2 i-custom-vender-line-time-clock-play-slim size-8 text-text-quaternary" />
<div className="text-center text-[13px] text-text-quaternary">
{t(($) => $['common.notRunning'], { ns: 'workflow' })}
</div>
</div>
)}
{data?.data.map((item) => (
<div
key={item.id}
className={cn(
'mb-0.5 flex cursor-pointer rounded-lg px-2 py-[7px] hover:bg-state-base-hover',
item.id === historyWorkflowData?.id &&
'bg-state-accent-hover hover:bg-state-accent-hover',
)}
onClick={() => {
workflowStore.setState({
historyWorkflowData: item,
showInputsPanel: false,
showEnvPanel: false,
})
closeAllInputFieldPanels()
handleBackupDraft()
setOpen(false)
handleNodesCancelSelected()
handleCancelDebugAndPreviewPanel()
setControlMode(ControlMode.Hand)
}}
>
{!isChatMode &&
[WorkflowRunningStatus.Stopped, WorkflowRunningStatus.Paused].includes(
item.status,
) && (
<span className="mt-0.5 mr-1.5 i-custom-vender-line-alertsAndFeedback-alert-triangle h-3.5 w-3.5 text-[#F79009]" />
)}
{!isChatMode && item.status === WorkflowRunningStatus.Failed && (
<span className="mt-0.5 mr-1.5 i-ri-error-warning-line h-3.5 w-3.5 text-[#F04438]" />
)}
{!isChatMode && item.status === WorkflowRunningStatus.Succeeded && (
<span className="mt-0.5 mr-1.5 i-ri-checkbox-circle-line h-3.5 w-3.5 text-[#12B76A]" />
)}
<div>
<div
className={cn(
'flex items-center text-[13px] leading-[18px] font-medium text-text-primary',
item.id === historyWorkflowData?.id && 'text-text-accent',
)}
>
{`Test ${isChatMode ? 'Chat' : 'Run'}${formatWorkflowRunIdentifier(item.finished_at, item.status)}`}
</div>
<div className="flex items-center text-xs leading-[18px] text-text-tertiary">
{item.created_by_account?.name} ·
{formatTimeFromNow((item.finished_at || item.created_at) * 1000)}
</div>
</div>
</div>
))}
</div>
)}
</div>
</PopoverContent>
</Popover>
)
}
export default memo(ViewHistory)