From 3befbc1d68ccd692f438a2d065665c76eb71949f Mon Sep 17 00:00:00 2001
From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com>
Date: Sun, 26 Jan 2025 15:12:05 +0800
Subject: [PATCH] feat: docx image preview (#13057)
---
web/app/components/base/markdown.tsx | 4 +-
.../detail/completed/SegmentCard.tsx | 259 ------------------
.../detail/completed/common/chunk-content.tsx | 12 +-
.../completed/segment-card/chunk-content.tsx | 56 ++++
.../index.tsx} | 60 ++--
.../detail/completed/segment-detail.tsx | 4 +-
.../skeleton/general-list-skeleton.tsx | 2 +-
.../documents/detail/new-segment-modal.tsx | 156 -----------
.../components/chunk-detail-modal.tsx | 10 +-
.../hit-testing/components/result-item.tsx | 13 +-
.../components/datasets/hit-testing/index.tsx | 10 +-
web/models/datasets.ts | 8 +-
12 files changed, 115 insertions(+), 479 deletions(-)
delete mode 100644 web/app/components/datasets/documents/detail/completed/SegmentCard.tsx
create mode 100644 web/app/components/datasets/documents/detail/completed/segment-card/chunk-content.tsx
rename web/app/components/datasets/documents/detail/completed/{segment-card.tsx => segment-card/index.tsx} (85%)
delete mode 100644 web/app/components/datasets/documents/detail/new-segment-modal.tsx
diff --git a/web/app/components/base/markdown.tsx b/web/app/components/base/markdown.tsx
index abb9a546ca..978a34a60a 100644
--- a/web/app/components/base/markdown.tsx
+++ b/web/app/components/base/markdown.tsx
@@ -9,7 +9,7 @@ import RemarkGfm from 'remark-gfm'
import RehypeRaw from 'rehype-raw'
import SyntaxHighlighter from 'react-syntax-highlighter'
import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs'
-import { Component, createContext, memo, useContext, useEffect, useMemo, useRef, useState } from 'react'
+import { Component, createContext, memo, useContext, useMemo, useRef, useState } from 'react'
import cn from '@/utils/classnames'
import CopyBtn from '@/app/components/base/copy-btn'
import SVGBtn from '@/app/components/base/svg'
@@ -241,7 +241,7 @@ const Link: Components['a'] = ({ node, ...props }) => {
export function Markdown(props: { content: string; className?: string }) {
const latexContent = preprocessLaTeX(props.content)
return (
-
+
= ({ percent, loading }) => {
- return (
-
-
-
{loading ? null : percent.toFixed(2)}
-
- )
-}
-
-type DocumentTitleProps = {
- extension?: string
- name?: string
- iconCls?: string
- textCls?: string
- wrapperCls?: string
-}
-
-const DocumentTitle: FC = ({ extension, name, iconCls, textCls, wrapperCls }) => {
- const localExtension = extension?.toLowerCase() || name?.split('.')?.pop()?.toLowerCase()
- return
-}
-
-export type UsageScene = 'doc' | 'hitTesting'
-
-type ISegmentCardProps = {
- loading: boolean
- detail?: SegmentDetailModel & { document: { name: string } }
- contentExternal?: string
- refSource?: {
- title: string
- uri: string
- }
- isExternal?: boolean
- score?: number
- onClick?: () => void
- onChangeSwitch?: (segId: string, enabled: boolean) => Promise
- onDelete?: (segId: string) => Promise
- scene?: UsageScene
- className?: string
- archived?: boolean
- embeddingAvailable?: boolean
-}
-
-const SegmentCard: FC = ({
- detail = {},
- contentExternal,
- isExternal,
- refSource,
- score,
- onClick,
- onChangeSwitch,
- onDelete,
- loading = true,
- scene = 'doc',
- className = '',
- archived,
- embeddingAvailable,
-}) => {
- const { t } = useTranslation()
- const {
- id,
- position,
- enabled,
- content,
- word_count,
- hit_count,
- index_node_hash,
- answer,
- } = detail as Required['detail']
- const isDocScene = scene === 'doc'
- const [showModal, setShowModal] = useState(false)
-
- const renderContent = () => {
- if (answer) {
- return (
- <>
-
-
- >
- )
- }
-
- if (contentExternal)
- return contentExternal
-
- return content
- }
-
- return (
- onClick?.()}
- >
-
- {isDocScene
- ? <>
-
-
- {loading
- ? (
-
- )
- : (
- <>
-
- {embeddingAvailable && (
-
-
-
) =>
- e.stopPropagation()
- }
- className="inline-flex items-center"
- >
- {
- await onChangeSwitch?.(id, val)
- }}
- />
-
-
- )}
- >
- )}
-
- >
- : (
- score !== null
- ? (
-
- )
- : null
- )}
-
- {loading
- ? (
-
- )
- : (
- isDocScene
- ? <>
-
- {renderContent()}
-
-
-
-
-
{formatNumber(word_count)}
-
-
-
-
{formatNumber(hit_count)}
-
-
- {!archived && embeddingAvailable && (
-
{
- e.stopPropagation()
- setShowModal(true)
- }}>
-
-
- )}
-
- >
- : <>
-
- {renderContent()}
-
-
-
-
-
-
- {isExternal ? t('datasetHitTesting.viewDetail') : t('datasetHitTesting.viewChart')}
-
-
-
-
- >
- )}
- {showModal
- &&
{ await onDelete?.(id) }}
- onCancel={() => setShowModal(false)}
- />
- }
-
- )
-}
-
-export default SegmentCard
diff --git a/web/app/components/datasets/documents/detail/completed/common/chunk-content.tsx b/web/app/components/datasets/documents/detail/completed/common/chunk-content.tsx
index e6403fa12f..af2989b188 100644
--- a/web/app/components/datasets/documents/detail/completed/common/chunk-content.tsx
+++ b/web/app/components/datasets/documents/detail/completed/common/chunk-content.tsx
@@ -3,6 +3,7 @@ import type { ComponentProps, FC } from 'react'
import { useTranslation } from 'react-i18next'
import { ChunkingMode } from '@/models/datasets'
import classNames from '@/utils/classnames'
+import { Markdown } from '@/app/components/base/markdown'
type IContentProps = ComponentProps<'textarea'>
@@ -52,7 +53,7 @@ const AutoResizeTextArea: FC = React.memo(({
if (!textarea)
return
textarea.style.height = 'auto'
- const lineHeight = parseInt(getComputedStyle(textarea).lineHeight)
+ const lineHeight = Number.parseInt(getComputedStyle(textarea).lineHeight)
const textareaHeight = Math.max(textarea.scrollHeight, lineHeight)
textarea.style.height = `${textareaHeight}px`
}, [value])
@@ -175,6 +176,15 @@ const ChunkContent: FC = ({
/>
}
+ if (!isEditMode) {
+ return (
+
+ )
+ }
+
return (
-
- {renderContent()}
-
+
{isGeneralMode &&
{keywords?.map(keyword => )}
}
diff --git a/web/app/components/datasets/documents/detail/completed/segment-detail.tsx b/web/app/components/datasets/documents/detail/completed/segment-detail.tsx
index 307a5cfb80..8891ddee85 100644
--- a/web/app/components/datasets/documents/detail/completed/segment-detail.tsx
+++ b/web/app/components/datasets/documents/detail/completed/segment-detail.tsx
@@ -142,9 +142,9 @@ const SegmentDetail: FC
= ({
-
+
{
+export const CardSkelton = React.memo(() => {
return (
diff --git a/web/app/components/datasets/documents/detail/new-segment-modal.tsx b/web/app/components/datasets/documents/detail/new-segment-modal.tsx
deleted file mode 100644
index 4c51779f9c..0000000000
--- a/web/app/components/datasets/documents/detail/new-segment-modal.tsx
+++ /dev/null
@@ -1,156 +0,0 @@
-import { memo, useState } from 'react'
-import type { FC } from 'react'
-import { useTranslation } from 'react-i18next'
-import { useContext } from 'use-context-selector'
-import { useParams } from 'next/navigation'
-import { RiCloseLine } from '@remixicon/react'
-import Modal from '@/app/components/base/modal'
-import Button from '@/app/components/base/button'
-import AutoHeightTextarea from '@/app/components/base/auto-height-textarea/common'
-import { Hash02 } from '@/app/components/base/icons/src/vender/line/general'
-import { ToastContext } from '@/app/components/base/toast'
-import type { SegmentUpdater } from '@/models/datasets'
-import { addSegment } from '@/service/datasets'
-import TagInput from '@/app/components/base/tag-input'
-
-type NewSegmentModalProps = {
- isShow: boolean
- onCancel: () => void
- docForm: string
- onSave: () => void
-}
-
-const NewSegmentModal: FC = ({
- isShow,
- onCancel,
- docForm,
- onSave,
-}) => {
- const { t } = useTranslation()
- const { notify } = useContext(ToastContext)
- const [question, setQuestion] = useState('')
- const [answer, setAnswer] = useState('')
- const { datasetId, documentId } = useParams()
- const [keywords, setKeywords] = useState([])
- const [loading, setLoading] = useState(false)
-
- const handleCancel = () => {
- setQuestion('')
- setAnswer('')
- onCancel()
- setKeywords([])
- }
-
- const handleSave = async () => {
- const params: SegmentUpdater = { content: '' }
- if (docForm === 'qa_model') {
- if (!question.trim())
- return notify({ type: 'error', message: t('datasetDocuments.segment.questionEmpty') })
- if (!answer.trim())
- return notify({ type: 'error', message: t('datasetDocuments.segment.answerEmpty') })
-
- params.content = question
- params.answer = answer
- }
- else {
- if (!question.trim())
- return notify({ type: 'error', message: t('datasetDocuments.segment.contentEmpty') })
-
- params.content = question
- }
-
- if (keywords?.length)
- params.keywords = keywords
-
- setLoading(true)
- try {
- await addSegment({ datasetId, documentId, body: params })
- notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
- handleCancel()
- onSave()
- }
- finally {
- setLoading(false)
- }
- }
-
- const renderContent = () => {
- if (docForm === 'qa_model') {
- return (
- <>
- QUESTION
- setQuestion(e.target.value)}
- autoFocus
- />
- ANSWER
- setAnswer(e.target.value)}
- />
- >
- )
- }
-
- return (
- setQuestion(e.target.value)}
- autoFocus
- />
- )
- }
-
- return (
- { }} className='pt-8 px-8 pb-6 !max-w-[640px] !rounded-xl'>
-
-
-
-
-
-
- {
- docForm === 'qa_model'
- ? t('datasetDocuments.segment.newQaSegment')
- : t('datasetDocuments.segment.newTextSegment')
- }
-
-
-
-
{renderContent()}
-
{t('datasetDocuments.segment.keywords')}
-
- setKeywords(newKeywords)} />
-
-
-
-
-
-
-
- )
-}
-
-export default memo(NewSegmentModal)
diff --git a/web/app/components/datasets/hit-testing/components/chunk-detail-modal.tsx b/web/app/components/datasets/hit-testing/components/chunk-detail-modal.tsx
index fe2f2b8f36..463a32e4d0 100644
--- a/web/app/components/datasets/hit-testing/components/chunk-detail-modal.tsx
+++ b/web/app/components/datasets/hit-testing/components/chunk-detail-modal.tsx
@@ -12,6 +12,7 @@ import FileIcon from '@/app/components/base/file-uploader/file-type-icon'
import type { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types'
import cn from '@/utils/classnames'
import Tag from '@/app/components/datasets/documents/detail/completed/common/tag'
+import { Markdown } from '@/app/components/base/markdown'
const i18nPrefix = 'datasetHitTesting'
@@ -26,7 +27,7 @@ const ChunkDetailModal: FC = ({
}) => {
const { t } = useTranslation()
const { segment, score, child_chunks } = payload
- const { position, content, keywords, document } = segment
+ const { position, content, sign_content, keywords, document } = segment
const isParentChildRetrieval = !!(child_chunks && child_chunks.length > 0)
const extension = document.name.split('.').slice(-1)[0] as FileAppearanceTypeEnum
const heighClassName = isParentChildRetrieval ? 'h-[min(627px,_80vh)] overflow-y-auto' : 'h-[min(539px,_80vh)] overflow-y-auto'
@@ -56,9 +57,10 @@ const ChunkDetailModal: FC = ({
-
- {content}
-
+
{!isParentChildRetrieval && keywords && keywords.length > 0 && (
{t(`${i18nPrefix}.keyword`)}
diff --git a/web/app/components/datasets/hit-testing/components/result-item.tsx b/web/app/components/datasets/hit-testing/components/result-item.tsx
index 16bc40f67c..12cbceff9c 100644
--- a/web/app/components/datasets/hit-testing/components/result-item.tsx
+++ b/web/app/components/datasets/hit-testing/components/result-item.tsx
@@ -13,6 +13,7 @@ import cn from '@/utils/classnames'
import type { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types'
import Tag from '@/app/components/datasets/documents/detail/completed/common/tag'
import { extensionToFileType } from '@/app/components/datasets/hit-testing/utils/extension-to-file-type'
+import { Markdown } from '@/app/components/base/markdown'
const i18nPrefix = 'datasetHitTesting'
type Props = {
@@ -25,7 +26,7 @@ const ResultItem: FC
= ({
const { t } = useTranslation()
const { segment, score, child_chunks } = payload
const data = segment
- const { position, word_count, content, keywords, document } = data
+ const { position, word_count, content, sign_content, keywords, document } = data
const isParentChildRetrieval = !!(child_chunks && child_chunks.length > 0)
const extension = document.name.split('.').slice(-1)[0] as FileAppearanceTypeEnum
const fileType = extensionToFileType(extension)
@@ -46,10 +47,16 @@ const ResultItem: FC = ({
{/* Main */}
-
{content}
+
{isParentChildRetrieval && (
-
+
{
+ e.stopPropagation()
+ toggleFold()
+ }}
+ >
{t(`${i18nPrefix}.hitChunks`, { num: child_chunks.length })}
diff --git a/web/app/components/datasets/hit-testing/index.tsx b/web/app/components/datasets/hit-testing/index.tsx
index 33f2acc65b..983b700b5a 100644
--- a/web/app/components/datasets/hit-testing/index.tsx
+++ b/web/app/components/datasets/hit-testing/index.tsx
@@ -7,7 +7,6 @@ import { omit } from 'lodash-es'
import { useBoolean } from 'ahooks'
import { useContext } from 'use-context-selector'
import { RiApps2Line, RiFocus2Line } from '@remixicon/react'
-import SegmentCard from '../documents/detail/completed/SegmentCard'
import Textarea from './textarea'
import s from './style.module.css'
import ModifyRetrievalModal from './modify-retrieval-modal'
@@ -25,6 +24,7 @@ import type { RetrievalConfig } from '@/types/app'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import useTimestamp from '@/hooks/use-timestamp'
import docStyle from '@/app/components/datasets/documents/detail/completed/style.module.css'
+import { CardSkelton } from '../documents/detail/completed/skeleton/general-list-skeleton'
const limit = 10
@@ -180,11 +180,9 @@ const HitTestingPage: FC
= ({ datasetId }: Props) => {
{/* {renderHitResults(generalResultData)} */}
{submitLoading
- ?
+ ?
+
+
: (
(() => {
if (!hitResult?.records.length && !externalHitResult?.records.length)
diff --git a/web/models/datasets.ts b/web/models/datasets.ts
index 673fb5fb15..073312fae1 100644
--- a/web/models/datasets.ts
+++ b/web/models/datasets.ts
@@ -12,9 +12,9 @@ export enum DataSourceType {
export type DatasetPermission = 'only_me' | 'all_team_members' | 'partial_members'
export enum ChunkingMode {
- 'text' = 'text_model', // General text
- 'qa' = 'qa_model', // General QA
- 'parentChild' = 'hierarchical_model', // Parent-Child
+ text = 'text_model', // General text
+ qa = 'qa_model', // General QA
+ parentChild = 'hierarchical_model', // Parent-Child
}
export type DataSet = {
@@ -452,6 +452,7 @@ export type SegmentDetailModel = {
position: number
document_id: string
content: string
+ sign_content: string
word_count: number
tokens: number
keywords: string[]
@@ -520,6 +521,7 @@ export type Segment = {
id: string
document: Document
content: string
+ sign_content: string
position: number
word_count: number
tokens: number