From 1e9bfd8872118bd012822e646696428b7e031d73 Mon Sep 17 00:00:00 2001 From: twwu Date: Fri, 29 Aug 2025 23:28:43 +0800 Subject: [PATCH] feat(chunk-card-list): implement ChunkCard and QAItem components, refactor ChunkCardList to utilize new structure and types --- .../components/chunk-card-list/chunk-card.tsx | 78 +++++++++ .../components/chunk-card-list/index.tsx | 155 +++--------------- .../components/chunk-card-list/q-a-item.tsx | 19 +++ .../components/chunk-card-list/types.ts | 28 ++++ .../panel/test-run/result/index.tsx | 1 - .../test-run/result/result-preview/index.tsx | 17 +- .../test-run/result/result-preview/utils.ts | 53 +++--- .../variable-inspect/value-content.tsx | 10 +- 8 files changed, 181 insertions(+), 180 deletions(-) create mode 100644 web/app/components/rag-pipeline/components/chunk-card-list/chunk-card.tsx create mode 100644 web/app/components/rag-pipeline/components/chunk-card-list/q-a-item.tsx create mode 100644 web/app/components/rag-pipeline/components/chunk-card-list/types.ts diff --git a/web/app/components/rag-pipeline/components/chunk-card-list/chunk-card.tsx b/web/app/components/rag-pipeline/components/chunk-card-list/chunk-card.tsx new file mode 100644 index 0000000000..15ac117b3b --- /dev/null +++ b/web/app/components/rag-pipeline/components/chunk-card-list/chunk-card.tsx @@ -0,0 +1,78 @@ +import React, { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import type { QAChunk } from './types' +import { QAItemType } from './types' +import { PreviewSlice } from '@/app/components/datasets/formatted-text/flavours/preview-slice' +import SegmentIndexTag from '@/app/components/datasets/documents/detail/completed/common/segment-index-tag' +import Dot from '@/app/components/datasets/documents/detail/completed/common/dot' +import { formatNumber } from '@/utils/format' +import QAItem from './q-a-item' +import { ChunkingMode, type ParentMode } from '@/models/datasets' + +type ChunkCardProps = { + chunkType: ChunkingMode + parentMode?: ParentMode + content: string | string[] | QAChunk + positionId?: string | number + wordCount: number +} + +const ChunkCard = (props: ChunkCardProps) => { + const { chunkType, parentMode, content, positionId, wordCount } = props + const { t } = useTranslation() + + const isFullDoc = useMemo(() => { + return chunkType === ChunkingMode.parentChild && parentMode === 'full-doc' + }, [chunkType, parentMode]) + + const isParagraph = useMemo(() => { + return chunkType === ChunkingMode.parentChild && parentMode === 'paragraph' + }, [chunkType, parentMode]) + + const contentElement = useMemo(() => { + if (chunkType === ChunkingMode.parentChild) { + return (content as string[]).map((child, index) => { + const indexForLabel = index + 1 + return ( + + ) + }) + } + + if (chunkType === ChunkingMode.qa) { + return ( +
+ + +
+ ) + } + + return content as string + }, [content, chunkType]) + + return ( +
+ {!isFullDoc && ( +
+ + +
{`${formatNumber(wordCount)} ${t('datasetDocuments.segment.characters', { count: wordCount })}`}
+
+ )} +
{contentElement}
+
+ ) +} + +export default React.memo(ChunkCard) diff --git a/web/app/components/rag-pipeline/components/chunk-card-list/index.tsx b/web/app/components/rag-pipeline/components/chunk-card-list/index.tsx index 268530a16d..d7b83d8375 100644 --- a/web/app/components/rag-pipeline/components/chunk-card-list/index.tsx +++ b/web/app/components/rag-pipeline/components/chunk-card-list/index.tsx @@ -1,153 +1,46 @@ import { useMemo } from 'react' -import SegmentIndexTag from '@/app/components/datasets/documents/detail/completed/common/segment-index-tag' -import Dot from '@/app/components/datasets/documents/detail/completed/common/dot' -import { PreviewSlice } from '@/app/components/datasets/formatted-text/flavours/preview-slice' -import { useTranslation } from 'react-i18next' -import { formatNumber } from '@/utils/format' import cn from '@/utils/classnames' - -enum QAItemType { - Question = 'question', - Answer = 'answer', -} - -type QAItemProps = { - type: QAItemType - text: string -} - -const QAItem = (props: QAItemProps) => { - const { type, text } = props - return
-
{type === QAItemType.Question ? 'Q' : 'A'}
-
{text}
-
-} - -export enum ChunkType { - General = 'general', - Paragraph = 'paragraph', - FullDoc = 'full-doc', - QA = 'qa', -} - -type ChunkCardProps = { - type: ChunkType - content: string | string[] | QAChunk - positionId?: string | number - wordCount: number -} - -const ChunkCard = (props: ChunkCardProps) => { - const { type, content, positionId, wordCount } = props - const { t } = useTranslation() - - const renderContent = () => { - // ChunkType.Paragraph && ChunkType.FullDoc - if (Array.isArray(content)) { - return content.map((child, index) => { - const indexForLabel = index + 1 - return ( - - ) - }) - } - - // ChunkType.QA - if (typeof content === 'object') { - return
- - -
- } - - // ChunkType.General - return content - } - - return ( -
- {type !== ChunkType.FullDoc &&
- - -
{formatNumber(wordCount)} {t('datasetDocuments.segment.characters', { count: wordCount })}
-
} -
{renderContent()}
-
- ) -} - -export type ChunkInfo = { - general_chunks?: string[] - parent_child_chunks?: ParentChildChunk[] - parent_mode?: string - qa_chunks?: QAChunk[] -} - -type ParentChildChunk = { - child_contents: string[] - parent_content: string - parent_mode: string -} - -type QAChunk = { - question: string - answer: string -} +import type { ChunkInfo, GeneralChunks, ParentChildChunk, ParentChildChunks, QAChunk, QAChunks } from './types' +import { ChunkingMode, type ParentMode } from '@/models/datasets' +import ChunkCard from './chunk-card' type ChunkCardListProps = { + chunkType: ChunkingMode + parentMode?: ParentMode chunkInfo: ChunkInfo className?: string } export const ChunkCardList = (props: ChunkCardListProps) => { - const { chunkInfo, className } = props - - const chunkType = useMemo(() => { - if (chunkInfo?.general_chunks) - return ChunkType.General - - if (chunkInfo?.parent_child_chunks) - return chunkInfo.parent_mode as ChunkType - - return ChunkType.QA - }, [chunkInfo]) + const { chunkType, parentMode, chunkInfo, className } = props const chunkList = useMemo(() => { - if (chunkInfo?.general_chunks) - return chunkInfo.general_chunks - if (chunkInfo?.parent_child_chunks) - return chunkInfo.parent_child_chunks - return chunkInfo?.qa_chunks ?? [] + if (chunkType === ChunkingMode.text) + return chunkInfo as GeneralChunks + if (chunkType === ChunkingMode.parentChild) + return (chunkInfo as ParentChildChunks).parent_child_chunks + return (chunkInfo as QAChunks).qa_chunks }, [chunkInfo]) + const getWordCount = (seg: string | ParentChildChunk | QAChunk) => { + if (chunkType === ChunkingMode.parentChild) + return (seg as ParentChildChunk).parent_content.length + if (chunkType === ChunkingMode.text) + return (seg as string).length + return (seg as QAChunk).question.length + (seg as QAChunk).answer.length + } + return (
- {chunkList.map((seg: string | ParentChildChunk | QAChunk, index: number) => { - const isParentChildMode = [ChunkType.Paragraph, ChunkType.FullDoc].includes(chunkType!) - let wordCount = 0 - if (isParentChildMode) - wordCount = (seg as ParentChildChunk)?.parent_content?.length - else if (typeof seg === 'string') - wordCount = seg.length - else - wordCount = (seg as QAChunk)?.question?.length + (seg as QAChunk)?.answer?.length + {chunkList.map((seg, index: number) => { + const wordCount = getWordCount(seg) return ( diff --git a/web/app/components/rag-pipeline/components/chunk-card-list/q-a-item.tsx b/web/app/components/rag-pipeline/components/chunk-card-list/q-a-item.tsx new file mode 100644 index 0000000000..d256c9203a --- /dev/null +++ b/web/app/components/rag-pipeline/components/chunk-card-list/q-a-item.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import { QAItemType } from './types' + +type QAItemProps = { + type: QAItemType + text: string +} + +const QAItem = (props: QAItemProps) => { + const { type, text } = props + return ( +
+
{type === QAItemType.Question ? 'Q' : 'A'}
+
{text}
+
+ ) +} + +export default React.memo(QAItem) diff --git a/web/app/components/rag-pipeline/components/chunk-card-list/types.ts b/web/app/components/rag-pipeline/components/chunk-card-list/types.ts new file mode 100644 index 0000000000..0a5e594b47 --- /dev/null +++ b/web/app/components/rag-pipeline/components/chunk-card-list/types.ts @@ -0,0 +1,28 @@ +export type GeneralChunks = string[] + +export type ParentChildChunk = { + child_contents: string[] + parent_content: string + parent_mode: string +} + +export type ParentChildChunks = { + parent_child_chunks: ParentChildChunk[] + parent_mode: string +} + +export type QAChunk = { + question: string + answer: string +} + +export type QAChunks = { + qa_chunks: QAChunk[] +} + +export type ChunkInfo = GeneralChunks | ParentChildChunks | QAChunks + +export enum QAItemType { + Question = 'question', + Answer = 'answer', +} diff --git a/web/app/components/rag-pipeline/components/panel/test-run/result/index.tsx b/web/app/components/rag-pipeline/components/panel/test-run/result/index.tsx index b196df1eb2..51a36c6f3c 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/result/index.tsx +++ b/web/app/components/rag-pipeline/components/panel/test-run/result/index.tsx @@ -29,7 +29,6 @@ const Result = () => { isRunning={!workflowRunningData?.result || workflowRunningData?.result.status === WorkflowRunningStatus.Running} outputs={workflowRunningData?.result?.outputs} error={workflowRunningData?.result?.error} - tracing={workflowRunningData?.tracing} onSwitchToDetail={() => switchTab('DETAIL')} /> )} diff --git a/web/app/components/rag-pipeline/components/panel/test-run/result/result-preview/index.tsx b/web/app/components/rag-pipeline/components/panel/test-run/result/result-preview/index.tsx index 8ff091ba1a..d7a66c57dd 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/result/result-preview/index.tsx +++ b/web/app/components/rag-pipeline/components/panel/test-run/result/result-preview/index.tsx @@ -1,6 +1,4 @@ import Button from '@/app/components/base/button' -import { BlockEnum } from '@/app/components/workflow/types' -import type { NodeTracing } from '@/types/workflow' import { RiLoader2Line } from '@remixicon/react' import React, { useMemo } from 'react' import { useTranslation } from 'react-i18next' @@ -12,7 +10,6 @@ type ResultTextProps = { isRunning?: boolean outputs?: any error?: string - tracing?: NodeTracing[] onSwitchToDetail: () => void } @@ -20,21 +17,13 @@ const ResultPreview = ({ isRunning, outputs, error, - tracing, onSwitchToDetail, }: ResultTextProps) => { const { t } = useTranslation() - const chunkInfo = useMemo(() => { - if (!outputs || !tracing) - return undefined - const knowledgeIndexNode = tracing.find(node => node.node_type === BlockEnum.KnowledgeBase) - return knowledgeIndexNode?.inputs?.chunks - }, [outputs, tracing]) - const previewChunks = useMemo(() => { - return formatPreviewChunks(chunkInfo, outputs) - }, [chunkInfo, outputs]) + return formatPreviewChunks(outputs) + }, [outputs]) return ( <> @@ -54,7 +43,7 @@ const ResultPreview = ({ )} {outputs && previewChunks && (
- +
diff --git a/web/app/components/rag-pipeline/components/panel/test-run/result/result-preview/utils.ts b/web/app/components/rag-pipeline/components/panel/test-run/result/result-preview/utils.ts index 5f20a09067..2efe5f452f 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/result/result-preview/utils.ts +++ b/web/app/components/rag-pipeline/components/panel/test-run/result/result-preview/utils.ts @@ -1,18 +1,17 @@ import { RAG_PIPELINE_PREVIEW_CHUNK_NUM } from '@/config' -import { type ChunkInfo, ChunkType } from '../../../../chunk-card-list' +import type { ChunkInfo, GeneralChunks, ParentChildChunks, QAChunks } from '../../../../chunk-card-list/types' +import type { ParentMode } from '@/models/datasets' +import { ChunkingMode } from '@/models/datasets' type GeneralChunkPreview = { content: string } const formatGeneralChunks = (outputs: any) => { - if (!outputs) return undefined - const chunkInfo: ChunkInfo = { - general_chunks: [], - } + const chunkInfo: GeneralChunks = [] const chunks = outputs.preview as GeneralChunkPreview[] chunks.slice(0, RAG_PIPELINE_PREVIEW_CHUNK_NUM).forEach((chunk) => { - chunkInfo.general_chunks?.push(chunk.content) + chunkInfo.push(chunk.content) }) return chunkInfo @@ -23,29 +22,27 @@ type ParentChildChunkPreview = { child_chunks: string[] } -const formatParentChildChunks = (outputs: any, chunkType: ChunkType) => { - if (!outputs) return undefined - const chunkInfo: ChunkInfo = { +const formatParentChildChunks = (outputs: any, parentMode: ParentMode) => { + const chunkInfo: ParentChildChunks = { parent_child_chunks: [], - parent_mode: chunkType, + parent_mode: parentMode, } const chunks = outputs.preview as ParentChildChunkPreview[] - if (chunkType === ChunkType.Paragraph) { + if (parentMode === 'paragraph') { chunks.slice(0, RAG_PIPELINE_PREVIEW_CHUNK_NUM).forEach((chunk) => { chunkInfo.parent_child_chunks?.push({ parent_content: chunk.content, child_contents: chunk.child_chunks, - parent_mode: chunkType, + parent_mode: parentMode, }) }) - return chunkInfo } - else { + if (parentMode === 'full-doc') { chunks.forEach((chunk) => { chunkInfo.parent_child_chunks?.push({ parent_content: chunk.content, child_contents: chunk.child_chunks.slice(0, RAG_PIPELINE_PREVIEW_CHUNK_NUM), - parent_mode: chunkType, + parent_mode: parentMode, }) }) } @@ -59,8 +56,7 @@ type QAChunkPreview = { } const formatQAChunks = (outputs: any) => { - if (!outputs) return undefined - const chunkInfo: ChunkInfo = { + const chunkInfo: QAChunks = { qa_chunks: [], } const chunks = outputs.qa_preview as QAChunkPreview[] @@ -73,26 +69,19 @@ const formatQAChunks = (outputs: any) => { return chunkInfo } -export const formatPreviewChunks = (chunkInfo: ChunkInfo, outputs: any): ChunkInfo | undefined => { - if (!chunkInfo) return undefined +export const formatPreviewChunks = (outputs: any): ChunkInfo | undefined => { + if (!outputs) return undefined - let chunkType = ChunkType.General - if (chunkInfo?.general_chunks) - chunkType = ChunkType.General + const chunkingMode = outputs.chunk_structure + const parentMode = outputs.parent_mode - if (chunkInfo?.parent_child_chunks) - chunkType = chunkInfo.parent_mode as ChunkType - - if (chunkInfo?.qa_chunks) - chunkType = ChunkType.QA - - if (chunkType === ChunkType.General) + if (chunkingMode === ChunkingMode.text) return formatGeneralChunks(outputs) - if (chunkType === ChunkType.Paragraph || chunkType === ChunkType.FullDoc) - return formatParentChildChunks(outputs, chunkType) + if (chunkingMode === ChunkingMode.parentChild) + return formatParentChildChunks(outputs, parentMode) - if (chunkType === ChunkType.QA) + if (chunkingMode === ChunkingMode.qa) return formatQAChunks(outputs) return undefined diff --git a/web/app/components/workflow/variable-inspect/value-content.tsx b/web/app/components/workflow/variable-inspect/value-content.tsx index 04d57bfe37..089cb06424 100644 --- a/web/app/components/workflow/variable-inspect/value-content.tsx +++ b/web/app/components/workflow/variable-inspect/value-content.tsx @@ -26,8 +26,10 @@ import { VarInInspectType } from '@/types/workflow' import cn from '@/utils/classnames' import BoolValue from '../panel/chat-variable-panel/components/bool-value' import { useStore } from '@/app/components/workflow/store' -import { ChunkCardList, type ChunkInfo } from '@/app/components/rag-pipeline/components/chunk-card-list' +import { ChunkCardList } from '@/app/components/rag-pipeline/components/chunk-card-list' +import type { ChunkInfo } from '@/app/components/rag-pipeline/components/chunk-card-list/types' import { PreviewMode } from '../../base/features/types' +import { ChunkingMode } from '@/models/datasets' enum ViewMode { Code = 'code', @@ -98,7 +100,11 @@ const DisplayContent = (props: DisplayContentProps) => { {viewMode === ViewMode.Preview && ( type === ContentType.Markdown ? - : + : )}