index.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useState } from 'react'
  4. import useSWR from 'swr'
  5. import { usePathname } from 'next/navigation'
  6. import { Pagination } from 'react-headless-pagination'
  7. import { omit } from 'lodash-es'
  8. import dayjs from 'dayjs'
  9. import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline'
  10. import { Trans, useTranslation } from 'react-i18next'
  11. import Link from 'next/link'
  12. import List from './list'
  13. import Filter from './filter'
  14. import s from './style.module.css'
  15. import Loading from '@/app/components/base/loading'
  16. import { fetchChatConversations, fetchCompletionConversations } from '@/service/log'
  17. import { fetchAppDetail } from '@/service/apps'
  18. export type ILogsProps = {
  19. appId: string
  20. }
  21. export type QueryParam = {
  22. period?: number | string
  23. annotation_status?: string
  24. keyword?: string
  25. }
  26. // Custom page count is not currently supported.
  27. const limit = 10
  28. const ThreeDotsIcon: FC<{ className?: string }> = ({ className }) => {
  29. return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
  30. <path d="M5 6.5V5M8.93934 7.56066L10 6.5M10.0103 11.5H11.5103" stroke="#374151" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
  31. </svg>
  32. }
  33. const EmptyElement: FC<{ appUrl: string }> = ({ appUrl }) => {
  34. const { t } = useTranslation()
  35. const pathname = usePathname()
  36. const pathSegments = pathname.split('/')
  37. pathSegments.pop()
  38. return <div className='flex items-center justify-center h-full'>
  39. <div className='bg-gray-50 w-[560px] h-fit box-border px-5 py-4 rounded-2xl'>
  40. <span className='text-gray-700 font-semibold'>{t('appLog.table.empty.element.title')}<ThreeDotsIcon className='inline relative -top-3 -left-1.5' /></span>
  41. <div className='mt-2 text-gray-500 text-sm font-normal'>
  42. <Trans
  43. i18nKey="appLog.table.empty.element.content"
  44. components={{ shareLink: <Link href={`${pathSegments.join('/')}/overview`} className='text-primary-600' />, testLink: <Link href={appUrl} className='text-primary-600' target='_blank' /> }}
  45. />
  46. </div>
  47. </div>
  48. </div>
  49. }
  50. const Logs: FC<ILogsProps> = ({ appId }) => {
  51. const { t } = useTranslation()
  52. const [queryParams, setQueryParams] = useState<QueryParam>({ period: 7, annotation_status: 'all' })
  53. const [currPage, setCurrPage] = React.useState<number>(0)
  54. const query = {
  55. page: currPage + 1,
  56. limit,
  57. ...(queryParams.period !== 'all'
  58. ? {
  59. start: dayjs().subtract(queryParams.period as number, 'day').startOf('day').format('YYYY-MM-DD HH:mm'),
  60. end: dayjs().format('YYYY-MM-DD HH:mm'),
  61. }
  62. : {}),
  63. ...omit(queryParams, ['period']),
  64. }
  65. // Get the app type first
  66. const { data: appDetail } = useSWR({ url: '/apps', id: appId }, fetchAppDetail)
  67. const isChatMode = appDetail?.mode === 'chat'
  68. // When the details are obtained, proceed to the next request
  69. const { data: chatConversations, mutate: mutateChatList } = useSWR(() => isChatMode
  70. ? {
  71. url: `/apps/${appId}/chat-conversations`,
  72. params: query,
  73. }
  74. : null, fetchChatConversations)
  75. const { data: completionConversations, mutate: mutateCompletionList } = useSWR(() => !isChatMode
  76. ? {
  77. url: `/apps/${appId}/completion-conversations`,
  78. params: query,
  79. }
  80. : null, fetchCompletionConversations)
  81. const total = isChatMode ? chatConversations?.total : completionConversations?.total
  82. return (
  83. <div className='flex flex-col h-full'>
  84. <div className='flex flex-col justify-center px-6 pt-4'>
  85. <h1 className='flex text-xl font-medium text-gray-900'>{t('appLog.title')}</h1>
  86. <p className='flex text-sm font-normal text-gray-500'>{t('appLog.description')}</p>
  87. </div>
  88. <div className='flex flex-col px-6 py-4 flex-1'>
  89. <Filter appId={appId} queryParams={queryParams} setQueryParams={setQueryParams} />
  90. {total === undefined
  91. ? <Loading type='app' />
  92. : total > 0
  93. ? <List logs={isChatMode ? chatConversations : completionConversations} appDetail={appDetail} onRefresh={isChatMode ? mutateChatList : mutateCompletionList} />
  94. : <EmptyElement appUrl={`${appDetail?.site.app_base_url}/${appDetail?.mode}/${appDetail?.site.access_token}`} />
  95. }
  96. {/* Show Pagination only if the total is more than the limit */}
  97. {(total && total > limit)
  98. ? <Pagination
  99. className="flex items-center w-full h-10 text-sm select-none mt-8"
  100. currentPage={currPage}
  101. edgePageCount={2}
  102. middlePagesSiblingCount={1}
  103. setCurrentPage={setCurrPage}
  104. totalPages={Math.ceil(total / limit)}
  105. truncableClassName="w-8 px-0.5 text-center"
  106. truncableText="..."
  107. >
  108. <Pagination.PrevButton
  109. disabled={currPage === 0}
  110. className={`flex items-center mr-2 text-gray-500 focus:outline-none ${currPage === 0 ? 'cursor-not-allowed opacity-50' : 'cursor-pointer hover:text-gray-600 dark:hover:text-gray-200'}`} >
  111. <ArrowLeftIcon className="mr-3 h-3 w-3" />
  112. {t('appLog.table.pagination.previous')}
  113. </Pagination.PrevButton>
  114. <div className={`flex items-center justify-center flex-grow ${s.pagination}`}>
  115. <Pagination.PageButton
  116. activeClassName="bg-primary-50 dark:bg-opacity-0 text-primary-600 dark:text-white"
  117. className="flex items-center justify-center h-8 w-8 rounded-full cursor-pointer"
  118. inactiveClassName="text-gray-500"
  119. />
  120. </div>
  121. <Pagination.NextButton
  122. disabled={currPage === Math.ceil(total / limit) - 1}
  123. className={`flex items-center mr-2 text-gray-500 focus:outline-none ${currPage === Math.ceil(total / limit) - 1 ? 'cursor-not-allowed opacity-50' : 'cursor-pointer hover:text-gray-600 dark:hover:text-gray-200'}`} >
  124. {t('appLog.table.pagination.next')}
  125. <ArrowRightIcon className="ml-3 h-3 w-3" />
  126. </Pagination.NextButton>
  127. </Pagination>
  128. : null}
  129. </div>
  130. </div>
  131. )
  132. }
  133. export default Logs