list.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import DetailPanel from './detail'
  6. import type { WorkflowAppLogDetail, WorkflowLogsResponse } from '@/models/log'
  7. import type { App } from '@/types/app'
  8. import Loading from '@/app/components/base/loading'
  9. import Drawer from '@/app/components/base/drawer'
  10. import Indicator from '@/app/components/header/indicator'
  11. import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
  12. import useTimestamp from '@/hooks/use-timestamp'
  13. import cn from '@/utils/classnames'
  14. type ILogs = {
  15. logs?: WorkflowLogsResponse
  16. appDetail?: App
  17. onRefresh: () => void
  18. }
  19. const defaultValue = 'N/A'
  20. const WorkflowAppLogList: FC<ILogs> = ({ logs, appDetail, onRefresh }) => {
  21. const { t } = useTranslation()
  22. const { formatTime } = useTimestamp()
  23. const media = useBreakpoints()
  24. const isMobile = media === MediaType.mobile
  25. const [showDrawer, setShowDrawer] = useState<boolean>(false)
  26. const [currentLog, setCurrentLog] = useState<WorkflowAppLogDetail | undefined>()
  27. const statusTdRender = (status: string) => {
  28. if (status === 'succeeded') {
  29. return (
  30. <div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'>
  31. <Indicator color={'green'} />
  32. <span className='text-util-colors-green-green-600'>Success</span>
  33. </div>
  34. )
  35. }
  36. if (status === 'failed') {
  37. return (
  38. <div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'>
  39. <Indicator color={'red'} />
  40. <span className='text-util-colors-red-red-600'>Fail</span>
  41. </div>
  42. )
  43. }
  44. if (status === 'stopped') {
  45. return (
  46. <div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'>
  47. <Indicator color={'yellow'} />
  48. <span className='text-util-colors-warning-warning-600'>Stop</span>
  49. </div>
  50. )
  51. }
  52. if (status === 'running') {
  53. return (
  54. <div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'>
  55. <Indicator color={'blue'} />
  56. <span className='text-util-colors-blue-light-blue-light-600'>Running</span>
  57. </div>
  58. )
  59. }
  60. if (status === 'partial-succeeded') {
  61. return (
  62. <div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'>
  63. <Indicator color={'green'} />
  64. <span className='text-util-colors-green-green-600'>Partial Success</span>
  65. </div>
  66. )
  67. }
  68. }
  69. const onCloseDrawer = () => {
  70. onRefresh()
  71. setShowDrawer(false)
  72. setCurrentLog(undefined)
  73. }
  74. if (!logs || !appDetail)
  75. return <Loading />
  76. return (
  77. <div className='overflow-x-auto'>
  78. <table className={cn('mt-2 w-full min-w-[440px] border-collapse border-0')}>
  79. <thead className='system-xs-medium-uppercase text-text-tertiary'>
  80. <tr>
  81. <td className='pl-2 pr-1 w-5 rounded-l-lg bg-background-section-burn whitespace-nowrap'></td>
  82. <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.startTime')}</td>
  83. <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.status')}</td>
  84. <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.runtime')}</td>
  85. <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.tokens')}</td>
  86. <td className='pl-3 py-1.5 rounded-r-lg bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.user')}</td>
  87. </tr>
  88. </thead>
  89. <tbody className="text-text-secondary system-sm-regular">
  90. {logs.data.map((log: WorkflowAppLogDetail) => {
  91. const endUser = log.created_by_end_user ? log.created_by_end_user.session_id : log.created_by_account ? log.created_by_account.name : defaultValue
  92. return <tr
  93. key={log.id}
  94. className={cn('border-b border-divider-subtle hover:bg-background-default-hover cursor-pointer', currentLog?.id !== log.id ? '' : 'bg-background-default-hover')}
  95. onClick={() => {
  96. setCurrentLog(log)
  97. setShowDrawer(true)
  98. }}>
  99. <td className='h-4'>
  100. {!log.read_at && (
  101. <div className='p-3 pr-0.5 flex items-center'>
  102. <span className='inline-block bg-util-colors-blue-blue-500 h-1.5 w-1.5 rounded'></span>
  103. </div>
  104. )}
  105. </td>
  106. <td className='p-3 pr-2 w-[160px]'>{formatTime(log.created_at, t('appLog.dateTimeFormat') as string)}</td>
  107. <td className='p-3 pr-2'>{statusTdRender(log.workflow_run.status)}</td>
  108. <td className='p-3 pr-2'>
  109. <div className={cn(
  110. log.workflow_run.elapsed_time === 0 && 'text-text-quaternary',
  111. )}>{`${log.workflow_run.elapsed_time.toFixed(3)}s`}</div>
  112. </td>
  113. <td className='p-3 pr-2'>{log.workflow_run.total_tokens}</td>
  114. <td className='p-3 pr-2'>
  115. <div className={cn(endUser === defaultValue ? 'text-text-quaternary' : 'text-text-secondary', 'overflow-hidden text-ellipsis whitespace-nowrap')}>
  116. {endUser}
  117. </div>
  118. </td>
  119. </tr>
  120. })}
  121. </tbody>
  122. </table>
  123. <Drawer
  124. isOpen={showDrawer}
  125. onClose={onCloseDrawer}
  126. mask={isMobile}
  127. footer={null}
  128. panelClassname='mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[600px] rounded-xl border border-components-panel-border'
  129. >
  130. <DetailPanel onClose={onCloseDrawer} runID={currentLog?.workflow_run.id || ''} />
  131. </Drawer>
  132. </div>
  133. )
  134. }
  135. export default WorkflowAppLogList