index.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. 'use client'
  2. import type { FC, SVGProps } from 'react'
  3. import React, { useState } from 'react'
  4. import useSWR from 'swr'
  5. import Link from 'next/link'
  6. import { usePathname } from 'next/navigation'
  7. import { useDebounce } from 'ahooks'
  8. import { omit } from 'lodash-es'
  9. import dayjs from 'dayjs'
  10. import { Trans, useTranslation } from 'react-i18next'
  11. import List from './list'
  12. import Filter, { TIME_PERIOD_MAPPING } from './filter'
  13. import Pagination from '@/app/components/base/pagination'
  14. import Loading from '@/app/components/base/loading'
  15. import { fetchChatConversations, fetchCompletionConversations } from '@/service/log'
  16. import { APP_PAGE_LIMIT } from '@/config'
  17. import type { App, AppMode } from '@/types/app'
  18. export type ILogsProps = {
  19. appDetail: App
  20. }
  21. export type QueryParam = {
  22. period: string
  23. annotation_status?: string
  24. keyword?: string
  25. sort_by?: string
  26. }
  27. const ThreeDotsIcon = ({ className }: SVGProps<SVGElement>) => {
  28. return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
  29. <path d="M5 6.5V5M8.93934 7.56066L10 6.5M10.0103 11.5H11.5103" stroke="#374151" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
  30. </svg>
  31. }
  32. const EmptyElement: FC<{ appUrl: string }> = ({ appUrl }) => {
  33. const { t } = useTranslation()
  34. const pathname = usePathname()
  35. const pathSegments = pathname.split('/')
  36. pathSegments.pop()
  37. return <div className='flex items-center justify-center h-full'>
  38. <div className='bg-background-section-burn w-[560px] h-fit box-border px-5 py-4 rounded-2xl'>
  39. <span className='text-text-secondary system-md-semibold'>{t('appLog.table.empty.element.title')}<ThreeDotsIcon className='inline relative -top-3 -left-1.5' /></span>
  40. <div className='mt-2 text-text-tertiary system-sm-regular'>
  41. <Trans
  42. i18nKey="appLog.table.empty.element.content"
  43. components={{ shareLink: <Link href={`${pathSegments.join('/')}/overview`} className='text-util-colors-blue-blue-600' />, testLink: <Link href={appUrl} className='text-util-colors-blue-blue-600' target='_blank' rel='noopener noreferrer' /> }}
  44. />
  45. </div>
  46. </div>
  47. </div>
  48. }
  49. const Logs: FC<ILogsProps> = ({ appDetail }) => {
  50. const { t } = useTranslation()
  51. const [queryParams, setQueryParams] = useState<QueryParam>({
  52. period: '2',
  53. annotation_status: 'all',
  54. sort_by: '-created_at',
  55. })
  56. const [currPage, setCurrPage] = React.useState<number>(0)
  57. const [limit, setLimit] = React.useState<number>(APP_PAGE_LIMIT)
  58. const debouncedQueryParams = useDebounce(queryParams, { wait: 500 })
  59. // Get the app type first
  60. const isChatMode = appDetail.mode !== 'completion'
  61. const query = {
  62. page: currPage + 1,
  63. limit,
  64. ...((debouncedQueryParams.period !== '9')
  65. ? {
  66. start: dayjs().subtract(TIME_PERIOD_MAPPING[debouncedQueryParams.period].value, 'day').startOf('day').format('YYYY-MM-DD HH:mm'),
  67. end: dayjs().endOf('day').format('YYYY-MM-DD HH:mm'),
  68. }
  69. : {}),
  70. ...(isChatMode ? { sort_by: debouncedQueryParams.sort_by } : {}),
  71. ...omit(debouncedQueryParams, ['period']),
  72. }
  73. const getWebAppType = (appType: AppMode) => {
  74. if (appType !== 'completion' && appType !== 'workflow')
  75. return 'chat'
  76. return appType
  77. }
  78. // When the details are obtained, proceed to the next request
  79. const { data: chatConversations, mutate: mutateChatList } = useSWR(() => isChatMode
  80. ? {
  81. url: `/apps/${appDetail.id}/chat-conversations`,
  82. params: query,
  83. }
  84. : null, fetchChatConversations)
  85. const { data: completionConversations, mutate: mutateCompletionList } = useSWR(() => !isChatMode
  86. ? {
  87. url: `/apps/${appDetail.id}/completion-conversations`,
  88. params: query,
  89. }
  90. : null, fetchCompletionConversations)
  91. const total = isChatMode ? chatConversations?.total : completionConversations?.total
  92. return (
  93. <div className='grow flex flex-col h-full'>
  94. <p className='shrink-0 text-text-tertiary system-sm-regular'>{t('appLog.description')}</p>
  95. <div className='grow max-h-[calc(100%-16px)] flex flex-col py-4 flex-1'>
  96. <Filter isChatMode={isChatMode} appId={appDetail.id} queryParams={queryParams} setQueryParams={setQueryParams} />
  97. {total === undefined
  98. ? <Loading type='app' />
  99. : total > 0
  100. ? <List logs={isChatMode ? chatConversations : completionConversations} appDetail={appDetail} onRefresh={isChatMode ? mutateChatList : mutateCompletionList} />
  101. : <EmptyElement appUrl={`${appDetail.site.app_base_url}/${getWebAppType(appDetail.mode)}/${appDetail.site.access_token}`} />
  102. }
  103. {/* Show Pagination only if the total is more than the limit */}
  104. {(total && total > APP_PAGE_LIMIT)
  105. ? <Pagination
  106. current={currPage}
  107. onChange={setCurrPage}
  108. total={total}
  109. limit={limit}
  110. onLimitChange={setLimit}
  111. />
  112. : null}
  113. </div>
  114. </div>
  115. )
  116. }
  117. export default Logs