popup.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import { Fragment, useState } from 'react'
  2. import type { FC } from 'react'
  3. import Link from 'next/link'
  4. import { useTranslation } from 'react-i18next'
  5. import Tooltip from './tooltip'
  6. import ProgressTooltip from './progress-tooltip'
  7. import type { Resources } from './index'
  8. import {
  9. PortalToFollowElem,
  10. PortalToFollowElemContent,
  11. PortalToFollowElemTrigger,
  12. } from '@/app/components/base/portal-to-follow-elem'
  13. import FileIcon from '@/app/components/base/file-icon'
  14. import {
  15. Hash02,
  16. Target04,
  17. } from '@/app/components/base/icons/src/vender/line/general'
  18. import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
  19. import {
  20. BezierCurve03,
  21. TypeSquare,
  22. } from '@/app/components/base/icons/src/vender/line/editor'
  23. type PopupProps = {
  24. data: Resources
  25. showHitInfo?: boolean
  26. }
  27. const Popup: FC<PopupProps> = ({
  28. data,
  29. showHitInfo = false,
  30. }) => {
  31. const { t } = useTranslation()
  32. const [open, setOpen] = useState(false)
  33. const fileType = data.dataSourceType !== 'notion'
  34. ? (/\.([^.]*)$/g.exec(data.documentName)?.[1] || '')
  35. : 'notion'
  36. return (
  37. <PortalToFollowElem
  38. open={open}
  39. onOpenChange={setOpen}
  40. placement='top-start'
  41. offset={{
  42. mainAxis: 8,
  43. crossAxis: -2,
  44. }}
  45. >
  46. <PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
  47. <div className='flex items-center px-2 max-w-[240px] h-7 bg-white rounded-lg'>
  48. <FileIcon type={fileType} className='shrink-0 mr-1 w-4 h-4' />
  49. <div className='text-xs text-gray-600 truncate'>{data.documentName}</div>
  50. </div>
  51. </PortalToFollowElemTrigger>
  52. <PortalToFollowElemContent style={{ zIndex: 1000 }}>
  53. <div className='max-w-[360px] bg-gray-50 rounded-xl shadow-lg'>
  54. <div className='px-4 pt-3 pb-2'>
  55. <div className='flex items-center h-[18px]'>
  56. <FileIcon type={fileType} className='shrink-0 mr-1 w-4 h-4' />
  57. <div className='text-xs font-medium text-gray-600 truncate'>{data.documentName}</div>
  58. </div>
  59. </div>
  60. <div className='px-4 py-0.5 max-h-[450px] bg-white rounded-lg overflow-y-auto'>
  61. <div className='w-full'>
  62. {
  63. data.sources.map((source, index) => (
  64. <Fragment key={index}>
  65. <div className='group py-3'>
  66. <div className='flex items-center justify-between mb-2'>
  67. <div className='flex items-center px-1.5 h-5 border border-gray-200 rounded-md'>
  68. <Hash02 className='mr-0.5 w-3 h-3 text-gray-400' />
  69. <div className='text-[11px] font-medium text-gray-500'>
  70. {source.segment_position || index + 1}
  71. </div>
  72. </div>
  73. {
  74. showHitInfo && (
  75. <Link
  76. href={`/datasets/${source.dataset_id}/documents/${source.document_id}`}
  77. className='hidden items-center h-[18px] text-xs text-primary-600 group-hover:flex'>
  78. {t('common.chat.citation.linkToDataset')}
  79. <ArrowUpRight className='ml-1 w-3 h-3' />
  80. </Link>
  81. )
  82. }
  83. </div>
  84. <div className='text-[13px] text-gray-800 break-words'>{source.content}</div>
  85. {
  86. showHitInfo && (
  87. <div className='flex items-center mt-2 text-xs font-medium text-gray-500 flex-wrap'>
  88. <Tooltip
  89. text={t('common.chat.citation.characters')}
  90. data={source.word_count}
  91. icon={<TypeSquare className='mr-1 w-3 h-3' />}
  92. />
  93. <Tooltip
  94. text={t('common.chat.citation.hitCount')}
  95. data={source.hit_count}
  96. icon={<Target04 className='mr-1 w-3 h-3' />}
  97. />
  98. <Tooltip
  99. text={t('common.chat.citation.vectorHash')}
  100. data={source.index_node_hash.substring(0, 7)}
  101. icon={<BezierCurve03 className='mr-1 w-3 h-3' />}
  102. />
  103. {
  104. source.score && (
  105. <ProgressTooltip data={Number(source.score.toFixed(2))} />
  106. )
  107. }
  108. </div>
  109. )
  110. }
  111. </div>
  112. {
  113. index !== data.sources.length - 1 && (
  114. <div className='my-1 h-[1px] bg-black/5' />
  115. )
  116. }
  117. </Fragment>
  118. ))
  119. }
  120. </div>
  121. </div>
  122. </div>
  123. </PortalToFollowElemContent>
  124. </PortalToFollowElem>
  125. )
  126. }
  127. export default Popup