mirror of https://github.com/langgenius/dify.git
refactor(test-run, preparation): restructure test run components, enhance data handling, and improve user experience with new loading states and error handling
This commit is contained in:
parent
8ab3f1212b
commit
449755ada4
|
|
@ -196,7 +196,7 @@ export const useOnlineDrive = () => {
|
|||
const dataSourceStore = useDataSourceStore()
|
||||
|
||||
const selectedOnlineDriveFileList = useMemo(() => {
|
||||
return selectedFileIds.map(key => fileList.find(item => item.key === key)!)
|
||||
return selectedFileIds.map(key => fileList.find(item => item.id === key)!)
|
||||
}, [fileList, selectedFileIds])
|
||||
|
||||
const clearOnlineDriveData = useCallback(() => {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ const CreateFormPipeline = () => {
|
|||
clearOnlineDriveData,
|
||||
} = useOnlineDrive()
|
||||
|
||||
const datasourceType = datasource?.nodeData.provider_type
|
||||
const datasourceType = useMemo(() => datasource?.nodeData.provider_type, [datasource])
|
||||
const isVectorSpaceFull = plan.usage.vectorSpace >= plan.total.vectorSpace
|
||||
const isShowVectorSpaceFull = useMemo(() => {
|
||||
if (!datasource)
|
||||
|
|
@ -234,10 +234,13 @@ const CreateFormPipeline = () => {
|
|||
const handleProcess = useCallback(async (data: Record<string, any>) => {
|
||||
if (!datasource)
|
||||
return
|
||||
const { bucket, currentCredentialId, fileList: onlineDriveFileList } = dataSourceStore.getState()
|
||||
const { currentCredentialId } = dataSourceStore.getState()
|
||||
const datasourceInfoList: Record<string, any>[] = []
|
||||
if (datasourceType === DatasourceType.localFile) {
|
||||
fileList.forEach((file) => {
|
||||
const {
|
||||
localFileList,
|
||||
} = dataSourceStore.getState()
|
||||
localFileList.forEach((file) => {
|
||||
const { id, name, type, size, extension, mime_type } = file.file
|
||||
const documentInfo = {
|
||||
related_id: id,
|
||||
|
|
@ -254,6 +257,9 @@ const CreateFormPipeline = () => {
|
|||
})
|
||||
}
|
||||
if (datasourceType === DatasourceType.onlineDocument) {
|
||||
const {
|
||||
onlineDocuments,
|
||||
} = dataSourceStore.getState()
|
||||
onlineDocuments.forEach((page) => {
|
||||
const { workspace_id, ...rest } = page
|
||||
const documentInfo = {
|
||||
|
|
@ -265,6 +271,9 @@ const CreateFormPipeline = () => {
|
|||
})
|
||||
}
|
||||
if (datasourceType === DatasourceType.websiteCrawl) {
|
||||
const {
|
||||
websitePages,
|
||||
} = dataSourceStore.getState()
|
||||
websitePages.forEach((websitePage) => {
|
||||
datasourceInfoList.push({
|
||||
...websitePage,
|
||||
|
|
@ -273,17 +282,20 @@ const CreateFormPipeline = () => {
|
|||
})
|
||||
}
|
||||
if (datasourceType === DatasourceType.onlineDrive) {
|
||||
if (datasourceType === DatasourceType.onlineDrive) {
|
||||
selectedFileIds.forEach((id) => {
|
||||
const file = onlineDriveFileList.find(file => file.id === id)
|
||||
datasourceInfoList.push({
|
||||
bucket,
|
||||
id: file?.id,
|
||||
type: file?.type,
|
||||
credential_id: currentCredentialId,
|
||||
})
|
||||
const {
|
||||
bucket,
|
||||
selectedFileIds,
|
||||
fileList: onlineDriveFileList,
|
||||
} = dataSourceStore.getState()
|
||||
selectedFileIds.forEach((id) => {
|
||||
const file = onlineDriveFileList.find(file => file.id === id)
|
||||
datasourceInfoList.push({
|
||||
bucket,
|
||||
id: file?.id,
|
||||
type: file?.type,
|
||||
credential_id: currentCredentialId,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
await runPublishedPipeline({
|
||||
pipeline_id: pipelineId!,
|
||||
|
|
@ -299,7 +311,7 @@ const CreateFormPipeline = () => {
|
|||
handleNextStep()
|
||||
},
|
||||
})
|
||||
}, [dataSourceStore, datasource, datasourceType, fileList, handleNextStep, onlineDocuments, pipelineId, runPublishedPipeline, selectedFileIds, websitePages])
|
||||
}, [dataSourceStore, datasource, datasourceType, handleNextStep, pipelineId, runPublishedPipeline])
|
||||
|
||||
const onClickProcess = useCallback(() => {
|
||||
isPreview.current = false
|
||||
|
|
|
|||
|
|
@ -4,138 +4,154 @@ 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',
|
||||
Question = 'question',
|
||||
Answer = 'answer',
|
||||
}
|
||||
|
||||
type QAItemProps = {
|
||||
type: QAItemType
|
||||
text: string
|
||||
type: QAItemType
|
||||
text: string
|
||||
}
|
||||
|
||||
const QAItem = (props: QAItemProps) => {
|
||||
const { type, text } = props
|
||||
return <div className="inline-flex items-start justify-start gap-1 self-stretch">
|
||||
<div className="w-4 text-[13px] font-medium leading-5 text-text-tertiary">{type === QAItemType.Question ? 'Q' : 'A'}</div>
|
||||
<div className="body-md-regular flex-1 text-text-secondary">{text}</div>
|
||||
</div>
|
||||
const { type, text } = props
|
||||
return <div className='inline-flex items-start justify-start gap-1 self-stretch'>
|
||||
<div className='w-4 text-[13px] font-medium leading-5 text-text-tertiary'>{type === QAItemType.Question ? 'Q' : 'A'}</div>
|
||||
<div className='body-md-regular flex-1 text-text-secondary'>{text}</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
enum ChunkType {
|
||||
General = 'genaral',
|
||||
Paragraph = 'paragraph',
|
||||
FullDoc = 'full-doc',
|
||||
QA = 'qa',
|
||||
General = 'general',
|
||||
Paragraph = 'paragraph',
|
||||
FullDoc = 'full-doc',
|
||||
QA = 'qa',
|
||||
}
|
||||
|
||||
type ChunkCardProps = {
|
||||
type: ChunkType
|
||||
content: string | string[] | QAChunk
|
||||
positionId?: string | number
|
||||
wordCount: number
|
||||
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 { 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 (
|
||||
<PreviewSlice
|
||||
key={child}
|
||||
label={`C-${indexForLabel}`}
|
||||
text={child}
|
||||
tooltip={`Child-chunk-${indexForLabel} · ${child.length} Characters`}
|
||||
labelInnerClassName='text-[10px] font-semibold align-bottom leading-7'
|
||||
dividerClassName='leading-7'
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// ChunkType.QA
|
||||
if (typeof content === 'object') {
|
||||
return <div className="flex flex-col gap-2">
|
||||
<QAItem type={QAItemType.Question} text={(content as QAChunk).question} />
|
||||
<QAItem type={QAItemType.Answer} text={(content as QAChunk).answer} />
|
||||
</div>
|
||||
}
|
||||
|
||||
// ChunkType.General
|
||||
return content
|
||||
const renderContent = () => {
|
||||
// ChunkType.Paragraph && ChunkType.FullDoc
|
||||
if (Array.isArray(content)) {
|
||||
return content.map((child, index) => {
|
||||
const indexForLabel = index + 1
|
||||
return (
|
||||
<PreviewSlice
|
||||
key={child}
|
||||
label={`C-${indexForLabel}`}
|
||||
text={child}
|
||||
tooltip={`Child-chunk-${indexForLabel} · ${child.length} Characters`}
|
||||
labelInnerClassName='text-[10px] font-semibold align-bottom leading-7'
|
||||
dividerClassName='leading-7'
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return <div className="inline-flex flex-col gap-1 self-stretch rounded-lg bg-components-panel-bg px-3 py-2.5">
|
||||
{type !== ChunkType.FullDoc && <div className="inline-flex items-center justify-start gap-2">
|
||||
<SegmentIndexTag
|
||||
positionId={positionId}
|
||||
labelPrefix={type === ChunkType.Paragraph ? 'Parent-Chunk' : 'Chunk'}
|
||||
/>
|
||||
<Dot />
|
||||
<div className='system-xs-medium text-text-tertiary'>{formatNumber(wordCount)} {t('datasetDocuments.segment.characters', { count: wordCount })}</div>
|
||||
</div>}
|
||||
<div className="body-md-regular text-text-secondary">{renderContent()}</div>
|
||||
// ChunkType.QA
|
||||
if (typeof content === 'object') {
|
||||
return <div className='flex flex-col gap-2'>
|
||||
<QAItem type={QAItemType.Question} text={(content as QAChunk).question} />
|
||||
<QAItem type={QAItemType.Answer} text={(content as QAChunk).answer} />
|
||||
</div>
|
||||
}
|
||||
|
||||
// ChunkType.General
|
||||
return content
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-1 rounded-lg bg-components-panel-bg px-3 py-2.5'>
|
||||
{type !== ChunkType.FullDoc && <div className='inline-flex items-center justify-start gap-2'>
|
||||
<SegmentIndexTag
|
||||
positionId={positionId}
|
||||
labelPrefix={type === ChunkType.Paragraph ? 'Parent-Chunk' : 'Chunk'}
|
||||
/>
|
||||
<Dot />
|
||||
<div className='system-xs-medium text-text-tertiary'>{formatNumber(wordCount)} {t('datasetDocuments.segment.characters', { count: wordCount })}</div>
|
||||
</div>}
|
||||
<div className='body-md-regular text-text-secondary'>{renderContent()}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export type ChunkInfo = {
|
||||
general_chunks?: string[]
|
||||
parent_child_chunks?: ParentChildChunk[]
|
||||
parent_mode?: string
|
||||
qa_chunks?: QAChunk[]
|
||||
general_chunks?: string[]
|
||||
parent_child_chunks?: ParentChildChunk[]
|
||||
parent_mode?: string
|
||||
qa_chunks?: QAChunk[]
|
||||
}
|
||||
|
||||
type ParentChildChunk = {
|
||||
child_contents: string[]
|
||||
parent_content: string
|
||||
parent_mode: string
|
||||
child_contents: string[]
|
||||
parent_content: string
|
||||
parent_mode: string
|
||||
}
|
||||
|
||||
type QAChunk = {
|
||||
question: string
|
||||
answer: string
|
||||
question: string
|
||||
answer: string
|
||||
}
|
||||
|
||||
type ChunkCardListProps = {
|
||||
chunkInfo: ChunkInfo
|
||||
chunkInfo: ChunkInfo
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const ChunkCardList = (props: ChunkCardListProps) => {
|
||||
const { chunkInfo } = props
|
||||
const { chunkInfo, className } = props
|
||||
|
||||
const chunkType = useMemo(() => {
|
||||
if (chunkInfo?.general_chunks)
|
||||
return ChunkType.General
|
||||
const chunkType = useMemo(() => {
|
||||
if (chunkInfo?.general_chunks)
|
||||
return ChunkType.General
|
||||
|
||||
if (chunkInfo?.parent_child_chunks)
|
||||
return chunkInfo.parent_mode as ChunkType
|
||||
if (chunkInfo?.parent_child_chunks)
|
||||
return chunkInfo.parent_mode as ChunkType
|
||||
|
||||
return ChunkType.QA
|
||||
}, [chunkInfo])
|
||||
return ChunkType.QA
|
||||
}, [chunkInfo])
|
||||
|
||||
return <div className='flex grow flex-col gap-1'>
|
||||
{(chunkInfo.general_chunks ?? chunkInfo.parent_child_chunks ?? chunkInfo?.qa_chunks ?? []).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
|
||||
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 ?? []
|
||||
}, [chunkInfo])
|
||||
|
||||
return <ChunkCard
|
||||
type={chunkType}
|
||||
content={isParentChildMode ? (seg as ParentChildChunk).child_contents : (seg as string | QAChunk)}
|
||||
wordCount={wordCount}
|
||||
positionId={index + 1}
|
||||
/>
|
||||
})}
|
||||
return (
|
||||
<div className={cn('flex w-full flex-col gap-y-1', className)}>
|
||||
{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
|
||||
|
||||
return (
|
||||
<ChunkCard
|
||||
type={chunkType}
|
||||
content={isParentChildMode ? (seg as ParentChildChunk).child_contents : (seg as string | QAChunk)}
|
||||
wordCount={wordCount}
|
||||
positionId={index + 1}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import DataSourceOptions from '../../test-run/data-source-options'
|
||||
import DataSourceOptions from '../../test-run/preparation/data-source-options'
|
||||
import Form from './form'
|
||||
import type { Datasource } from '../../test-run/types'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
import React from 'react'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
|
||||
type CloseButtonProps = {
|
||||
handleClose: () => void
|
||||
}
|
||||
|
||||
const CloseButton = ({
|
||||
handleClose,
|
||||
}: CloseButtonProps) => {
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
className='absolute right-2.5 top-2.5 flex size-8 items-center justify-center p-1.5'
|
||||
onClick={handleClose}
|
||||
>
|
||||
<RiCloseLine className='size-4 text-text-tertiary' />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(CloseButton)
|
||||
|
|
@ -1,24 +1,36 @@
|
|||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import StepIndicator from './step-indicator'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { useWorkflowInteractions } from '@/app/components/workflow/hooks'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
|
||||
type HeaderProps = {
|
||||
steps: { label: string; value: string }[]
|
||||
currentStep: number
|
||||
}
|
||||
const Header = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
const Header = ({
|
||||
steps,
|
||||
currentStep,
|
||||
}: HeaderProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
const {
|
||||
isPreparingDataSource,
|
||||
setIsPreparingDataSource,
|
||||
} = workflowStore.getState()
|
||||
isPreparingDataSource && setIsPreparingDataSource?.(false)
|
||||
handleCancelDebugAndPreviewPanel()
|
||||
}, [workflowStore])
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-y-0.5 px-3 pb-2 pt-3.5'>
|
||||
<div className='system-md-semibold-uppercase flex items-center gap-x-1 pl-1 pr-8 text-text-primary'>
|
||||
<div className='flex items-center gap-x-2 pl-4 pr-3 pt-4'>
|
||||
<div className='system-xl-semibold grow pl-1 pr-8 text-text-primary'>
|
||||
{t('datasetPipeline.testRun.title')}
|
||||
</div>
|
||||
<StepIndicator steps={steps} currentStep={currentStep} />
|
||||
<button
|
||||
type='button'
|
||||
className='flex size-8 shrink-0 items-center justify-center p-1.5'
|
||||
onClick={handleClose}
|
||||
>
|
||||
<RiCloseLine className='size-4 text-text-tertiary' />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,226 +1,26 @@
|
|||
import { useStore as useWorkflowStoreWithSelector } from '@/app/components/workflow/store'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import {
|
||||
useOnlineDocument,
|
||||
useOnlineDrive,
|
||||
useTestRunSteps,
|
||||
useWebsiteCrawl,
|
||||
} from './hooks'
|
||||
import DataSourceOptions from './data-source-options'
|
||||
import LocalFile from '@/app/components/datasets/documents/create-from-pipeline/data-source/local-file'
|
||||
import OnlineDocuments from '@/app/components/datasets/documents/create-from-pipeline/data-source/online-documents'
|
||||
import WebsiteCrawl from '@/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl'
|
||||
import OnlineDrive from '@/app/components/datasets/documents/create-from-pipeline/data-source/online-drive'
|
||||
import Actions from './actions'
|
||||
import DocumentProcessing from './document-processing'
|
||||
import { useWorkflowRun } from '@/app/components/workflow/hooks'
|
||||
import type { Datasource } from './types'
|
||||
import { DatasourceType } from '@/models/pipeline'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import CloseButton from './close-button'
|
||||
import Header from './header'
|
||||
import FooterTips from './footer-tips'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import DataSourceProvider from '@/app/components/datasets/documents/create-from-pipeline/data-source/store/provider'
|
||||
import { useDataSourceStore, useDataSourceStoreWithSelector } from '@/app/components/datasets/documents/create-from-pipeline/data-source/store'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import Preparation from './preparation'
|
||||
import Result from './result'
|
||||
import Header from './header'
|
||||
|
||||
const TestRunPanel = () => {
|
||||
const setShowDebugAndPreviewPanel = useWorkflowStoreWithSelector(state => state.setShowDebugAndPreviewPanel)
|
||||
const {
|
||||
localFileList: fileList,
|
||||
onlineDocuments,
|
||||
websitePages,
|
||||
selectedFileIds,
|
||||
} = useDataSourceStoreWithSelector(useShallow(state => ({
|
||||
localFileList: state.localFileList,
|
||||
onlineDocuments: state.onlineDocuments,
|
||||
websitePages: state.websitePages,
|
||||
selectedFileIds: state.selectedFileIds,
|
||||
})))
|
||||
const dataSourceStore = useDataSourceStore()
|
||||
const [datasource, setDatasource] = useState<Datasource>()
|
||||
|
||||
const {
|
||||
steps,
|
||||
currentStep,
|
||||
handleNextStep,
|
||||
handleBackStep,
|
||||
} = useTestRunSteps()
|
||||
|
||||
const { clearOnlineDocumentData } = useOnlineDocument()
|
||||
const { clearWebsiteCrawlData } = useWebsiteCrawl()
|
||||
const { clearOnlineDriveData } = useOnlineDrive()
|
||||
|
||||
const datasourceType = datasource?.nodeData.provider_type
|
||||
|
||||
const nextBtnDisabled = useMemo(() => {
|
||||
if (!datasource) return true
|
||||
if (datasourceType === DatasourceType.localFile)
|
||||
return !fileList.length || fileList.some(file => !file.file.id)
|
||||
if (datasourceType === DatasourceType.onlineDocument)
|
||||
return !onlineDocuments.length
|
||||
if (datasourceType === DatasourceType.websiteCrawl)
|
||||
return !websitePages.length
|
||||
if (datasourceType === DatasourceType.onlineDrive)
|
||||
return !selectedFileIds.length
|
||||
return false
|
||||
}, [datasource, datasourceType, fileList, onlineDocuments.length, selectedFileIds.length, websitePages.length])
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setShowDebugAndPreviewPanel(false)
|
||||
}, [setShowDebugAndPreviewPanel])
|
||||
|
||||
const { handleRun } = useWorkflowRun()
|
||||
|
||||
const handleProcess = useCallback((data: Record<string, any>) => {
|
||||
if (!datasource)
|
||||
return
|
||||
const datasourceInfoList: Record<string, any>[] = []
|
||||
const credentialId = dataSourceStore.getState().currentCredentialId
|
||||
if (datasourceType === DatasourceType.localFile) {
|
||||
const { id, name, type, size, extension, mime_type } = fileList[0].file
|
||||
const documentInfo = {
|
||||
related_id: id,
|
||||
name,
|
||||
type,
|
||||
size,
|
||||
extension,
|
||||
mime_type,
|
||||
url: '',
|
||||
transfer_method: TransferMethod.local_file,
|
||||
}
|
||||
datasourceInfoList.push(documentInfo)
|
||||
}
|
||||
if (datasourceType === DatasourceType.onlineDocument) {
|
||||
const { workspace_id, ...rest } = onlineDocuments[0]
|
||||
const documentInfo = {
|
||||
workspace_id,
|
||||
page: rest,
|
||||
credential_id: credentialId,
|
||||
}
|
||||
datasourceInfoList.push(documentInfo)
|
||||
}
|
||||
if (datasourceType === DatasourceType.websiteCrawl) {
|
||||
datasourceInfoList.push({
|
||||
...websitePages[0],
|
||||
credential_id: credentialId,
|
||||
})
|
||||
}
|
||||
if (datasourceType === DatasourceType.onlineDrive) {
|
||||
const { bucket, fileList } = dataSourceStore.getState()
|
||||
const file = fileList.find(file => file.id === selectedFileIds[0])
|
||||
datasourceInfoList.push({
|
||||
bucket,
|
||||
id: file?.id,
|
||||
type: file?.type,
|
||||
credential_id: credentialId,
|
||||
})
|
||||
}
|
||||
handleRun({
|
||||
inputs: data,
|
||||
start_node_id: datasource.nodeId,
|
||||
datasource_type: datasourceType,
|
||||
datasource_info_list: datasourceInfoList,
|
||||
})
|
||||
}, [dataSourceStore, datasource, datasourceType, fileList, handleRun, onlineDocuments, selectedFileIds, websitePages])
|
||||
|
||||
const clearDataSourceData = useCallback((dataSource: Datasource) => {
|
||||
if (dataSource.nodeData.provider_type === DatasourceType.onlineDocument)
|
||||
clearOnlineDocumentData()
|
||||
else if (dataSource.nodeData.provider_type === DatasourceType.websiteCrawl)
|
||||
clearWebsiteCrawlData()
|
||||
else if (dataSource.nodeData.provider_type === DatasourceType.onlineDrive)
|
||||
clearOnlineDriveData()
|
||||
}, [])
|
||||
|
||||
const handleSwitchDataSource = useCallback((dataSource: Datasource) => {
|
||||
const {
|
||||
setCurrentCredentialId,
|
||||
currentNodeIdRef,
|
||||
} = dataSourceStore.getState()
|
||||
clearDataSourceData(dataSource)
|
||||
setCurrentCredentialId('')
|
||||
currentNodeIdRef.current = dataSource.nodeId
|
||||
setDatasource(dataSource)
|
||||
}, [dataSourceStore])
|
||||
|
||||
const handleCredentialChange = useCallback((credentialId: string) => {
|
||||
const { setCurrentCredentialId } = dataSourceStore.getState()
|
||||
clearDataSourceData(datasource!)
|
||||
setCurrentCredentialId(credentialId)
|
||||
}, [dataSourceStore, datasource])
|
||||
const isPreparingDataSource = useStore(state => state.isPreparingDataSource)
|
||||
|
||||
return (
|
||||
<div
|
||||
className='relative flex h-full w-[480px] flex-col rounded-l-2xl border-y-[0.5px] border-l-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl shadow-shadow-shadow-1'
|
||||
>
|
||||
<CloseButton handleClose={handleClose} />
|
||||
<Header steps={steps} currentStep={currentStep} />
|
||||
<div className='flex grow flex-col overflow-y-auto'>
|
||||
{
|
||||
currentStep === 1 && (
|
||||
<>
|
||||
<div className='flex flex-col gap-y-4 px-4 py-2'>
|
||||
<DataSourceOptions
|
||||
dataSourceNodeId={datasource?.nodeId || ''}
|
||||
onSelect={handleSwitchDataSource}
|
||||
/>
|
||||
{datasourceType === DatasourceType.localFile && (
|
||||
<LocalFile
|
||||
allowedExtensions={datasource!.nodeData.fileExtensions || []}
|
||||
notSupportBatchUpload={false} // only support single file upload in test run
|
||||
/>
|
||||
)}
|
||||
{datasourceType === DatasourceType.onlineDocument && (
|
||||
<OnlineDocuments
|
||||
nodeId={datasource!.nodeId}
|
||||
nodeData={datasource!.nodeData}
|
||||
isInPipeline
|
||||
onCredentialChange={handleCredentialChange}
|
||||
/>
|
||||
)}
|
||||
{datasourceType === DatasourceType.websiteCrawl && (
|
||||
<WebsiteCrawl
|
||||
nodeId={datasource!.nodeId}
|
||||
nodeData={datasource!.nodeData}
|
||||
isInPipeline
|
||||
onCredentialChange={handleCredentialChange}
|
||||
/>
|
||||
)}
|
||||
{datasourceType === DatasourceType.onlineDrive && (
|
||||
<OnlineDrive
|
||||
nodeId={datasource!.nodeId}
|
||||
nodeData={datasource!.nodeData}
|
||||
isInPipeline
|
||||
onCredentialChange={handleCredentialChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Actions disabled={nextBtnDisabled} handleNextStep={handleNextStep} />
|
||||
<FooterTips />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
currentStep === 2 && (
|
||||
<DocumentProcessing
|
||||
dataSourceNodeId={datasource!.nodeId}
|
||||
onProcess={handleProcess}
|
||||
onBack={handleBackStep}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<Header />
|
||||
{isPreparingDataSource ? (
|
||||
<DataSourceProvider>
|
||||
<Preparation />
|
||||
</DataSourceProvider>
|
||||
) : (
|
||||
<Result />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const TestRunPanelWrapper = () => {
|
||||
return (
|
||||
<DataSourceProvider>
|
||||
<TestRunPanel />
|
||||
</DataSourceProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default TestRunPanelWrapper
|
||||
export default TestRunPanel
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useCallback, useEffect } from 'react'
|
||||
import { useDatasourceOptions } from '../hooks'
|
||||
import OptionCard from './option-card'
|
||||
import type { Datasource } from '../types'
|
||||
import type { Datasource } from '../../types'
|
||||
|
||||
type DataSourceOptionsProps = {
|
||||
dataSourceNodeId: string
|
||||
|
|
@ -28,7 +28,6 @@ const DataSourceOptions = ({
|
|||
useEffect(() => {
|
||||
if (options.length > 0 && !dataSourceNodeId)
|
||||
handelSelect(options[0].value)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import type { DataSourceOption } from './types'
|
||||
import { TestRunStep } from './types'
|
||||
import type { DataSourceOption } from '../types'
|
||||
import { TestRunStep } from '../types'
|
||||
import { useNodes } from 'reactflow'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import {
|
||||
useOnlineDocument,
|
||||
useOnlineDrive,
|
||||
useTestRunSteps,
|
||||
useWebsiteCrawl,
|
||||
} from './hooks'
|
||||
import DataSourceOptions from './data-source-options'
|
||||
import LocalFile from '@/app/components/datasets/documents/create-from-pipeline/data-source/local-file'
|
||||
import OnlineDocuments from '@/app/components/datasets/documents/create-from-pipeline/data-source/online-documents'
|
||||
import WebsiteCrawl from '@/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl'
|
||||
import OnlineDrive from '@/app/components/datasets/documents/create-from-pipeline/data-source/online-drive'
|
||||
import Actions from './actions'
|
||||
import DocumentProcessing from './document-processing'
|
||||
import { useWorkflowRun } from '@/app/components/workflow/hooks'
|
||||
import type { Datasource } from '../types'
|
||||
import { DatasourceType } from '@/models/pipeline'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import FooterTips from './footer-tips'
|
||||
import { useDataSourceStore, useDataSourceStoreWithSelector } from '@/app/components/datasets/documents/create-from-pipeline/data-source/store'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import StepIndicator from './step-indicator'
|
||||
|
||||
const Preparation = () => {
|
||||
const {
|
||||
localFileList: fileList,
|
||||
onlineDocuments,
|
||||
websitePages,
|
||||
selectedFileIds,
|
||||
} = useDataSourceStoreWithSelector(useShallow(state => ({
|
||||
localFileList: state.localFileList,
|
||||
onlineDocuments: state.onlineDocuments,
|
||||
websitePages: state.websitePages,
|
||||
selectedFileIds: state.selectedFileIds,
|
||||
})))
|
||||
const workflowStore = useWorkflowStore()
|
||||
const dataSourceStore = useDataSourceStore()
|
||||
const [datasource, setDatasource] = useState<Datasource>()
|
||||
|
||||
const {
|
||||
steps,
|
||||
currentStep,
|
||||
handleNextStep,
|
||||
handleBackStep,
|
||||
} = useTestRunSteps()
|
||||
|
||||
const { clearOnlineDocumentData } = useOnlineDocument()
|
||||
const { clearWebsiteCrawlData } = useWebsiteCrawl()
|
||||
const { clearOnlineDriveData } = useOnlineDrive()
|
||||
|
||||
const datasourceType = datasource?.nodeData.provider_type
|
||||
|
||||
const nextBtnDisabled = useMemo(() => {
|
||||
if (!datasource) return true
|
||||
if (datasourceType === DatasourceType.localFile)
|
||||
return !fileList.length || fileList.some(file => !file.file.id)
|
||||
if (datasourceType === DatasourceType.onlineDocument)
|
||||
return !onlineDocuments.length
|
||||
if (datasourceType === DatasourceType.websiteCrawl)
|
||||
return !websitePages.length
|
||||
if (datasourceType === DatasourceType.onlineDrive)
|
||||
return !selectedFileIds.length
|
||||
return false
|
||||
}, [datasource, datasourceType, fileList, onlineDocuments.length, selectedFileIds.length, websitePages.length])
|
||||
|
||||
const { handleRun } = useWorkflowRun()
|
||||
|
||||
const handleProcess = useCallback((data: Record<string, any>) => {
|
||||
if (!datasource)
|
||||
return
|
||||
const datasourceInfoList: Record<string, any>[] = []
|
||||
const credentialId = dataSourceStore.getState().currentCredentialId
|
||||
if (datasourceType === DatasourceType.localFile) {
|
||||
const { localFileList } = dataSourceStore.getState()
|
||||
const { id, name, type, size, extension, mime_type } = localFileList[0].file
|
||||
const documentInfo = {
|
||||
related_id: id,
|
||||
name,
|
||||
type,
|
||||
size,
|
||||
extension,
|
||||
mime_type,
|
||||
url: '',
|
||||
transfer_method: TransferMethod.local_file,
|
||||
}
|
||||
datasourceInfoList.push(documentInfo)
|
||||
}
|
||||
if (datasourceType === DatasourceType.onlineDocument) {
|
||||
const { onlineDocuments } = dataSourceStore.getState()
|
||||
const { workspace_id, ...rest } = onlineDocuments[0]
|
||||
const documentInfo = {
|
||||
workspace_id,
|
||||
page: rest,
|
||||
credential_id: credentialId,
|
||||
}
|
||||
datasourceInfoList.push(documentInfo)
|
||||
}
|
||||
if (datasourceType === DatasourceType.websiteCrawl) {
|
||||
const { websitePages } = dataSourceStore.getState()
|
||||
datasourceInfoList.push({
|
||||
...websitePages[0],
|
||||
credential_id: credentialId,
|
||||
})
|
||||
}
|
||||
if (datasourceType === DatasourceType.onlineDrive) {
|
||||
const { bucket, fileList, selectedFileIds } = dataSourceStore.getState()
|
||||
const file = fileList.find(file => file.id === selectedFileIds[0])
|
||||
datasourceInfoList.push({
|
||||
bucket,
|
||||
id: file?.id,
|
||||
type: file?.type,
|
||||
credential_id: credentialId,
|
||||
})
|
||||
}
|
||||
const { setIsPreparingDataSource } = workflowStore.getState()
|
||||
handleRun({
|
||||
inputs: data,
|
||||
start_node_id: datasource.nodeId,
|
||||
datasource_type: datasourceType,
|
||||
datasource_info_list: datasourceInfoList,
|
||||
})
|
||||
setIsPreparingDataSource?.(false)
|
||||
}, [dataSourceStore, datasource, datasourceType, handleRun, workflowStore])
|
||||
|
||||
const clearDataSourceData = useCallback((dataSource: Datasource) => {
|
||||
if (dataSource.nodeData.provider_type === DatasourceType.onlineDocument)
|
||||
clearOnlineDocumentData()
|
||||
else if (dataSource.nodeData.provider_type === DatasourceType.websiteCrawl)
|
||||
clearWebsiteCrawlData()
|
||||
else if (dataSource.nodeData.provider_type === DatasourceType.onlineDrive)
|
||||
clearOnlineDriveData()
|
||||
}, [])
|
||||
|
||||
const handleSwitchDataSource = useCallback((dataSource: Datasource) => {
|
||||
const {
|
||||
setCurrentCredentialId,
|
||||
currentNodeIdRef,
|
||||
} = dataSourceStore.getState()
|
||||
clearDataSourceData(dataSource)
|
||||
setCurrentCredentialId('')
|
||||
currentNodeIdRef.current = dataSource.nodeId
|
||||
setDatasource(dataSource)
|
||||
}, [dataSourceStore])
|
||||
|
||||
const handleCredentialChange = useCallback((credentialId: string) => {
|
||||
const { setCurrentCredentialId } = dataSourceStore.getState()
|
||||
clearDataSourceData(datasource!)
|
||||
setCurrentCredentialId(credentialId)
|
||||
}, [dataSourceStore, datasource])
|
||||
return (
|
||||
<>
|
||||
<StepIndicator steps={steps} currentStep={currentStep} />
|
||||
<div className='flex grow flex-col overflow-y-auto'>
|
||||
{
|
||||
currentStep === 1 && (
|
||||
<>
|
||||
<div className='flex flex-col gap-y-4 px-4 py-2'>
|
||||
<DataSourceOptions
|
||||
dataSourceNodeId={datasource?.nodeId || ''}
|
||||
onSelect={handleSwitchDataSource}
|
||||
/>
|
||||
{datasourceType === DatasourceType.localFile && (
|
||||
<LocalFile
|
||||
allowedExtensions={datasource!.nodeData.fileExtensions || []}
|
||||
notSupportBatchUpload={false} // only support single file upload in test run
|
||||
/>
|
||||
)}
|
||||
{datasourceType === DatasourceType.onlineDocument && (
|
||||
<OnlineDocuments
|
||||
nodeId={datasource!.nodeId}
|
||||
nodeData={datasource!.nodeData}
|
||||
isInPipeline
|
||||
onCredentialChange={handleCredentialChange}
|
||||
/>
|
||||
)}
|
||||
{datasourceType === DatasourceType.websiteCrawl && (
|
||||
<WebsiteCrawl
|
||||
nodeId={datasource!.nodeId}
|
||||
nodeData={datasource!.nodeData}
|
||||
isInPipeline
|
||||
onCredentialChange={handleCredentialChange}
|
||||
/>
|
||||
)}
|
||||
{datasourceType === DatasourceType.onlineDrive && (
|
||||
<OnlineDrive
|
||||
nodeId={datasource!.nodeId}
|
||||
nodeData={datasource!.nodeData}
|
||||
isInPipeline
|
||||
onCredentialChange={handleCredentialChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Actions disabled={nextBtnDisabled} handleNextStep={handleNextStep} />
|
||||
<FooterTips />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
currentStep === 2 && (
|
||||
<DocumentProcessing
|
||||
dataSourceNodeId={datasource!.nodeId}
|
||||
onProcess={handleProcess}
|
||||
onBack={handleBackStep}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Preparation)
|
||||
|
|
@ -17,7 +17,7 @@ const StepIndicator = ({
|
|||
steps,
|
||||
}: StepIndicatorProps) => {
|
||||
return (
|
||||
<div className='flex items-center gap-x-2 px-1'>
|
||||
<div className='flex items-center gap-x-2 px-4 pb-2'>
|
||||
{steps.map((step, index) => {
|
||||
const isCurrentStep = index === currentStep - 1
|
||||
const isLastStep = index === steps.length - 1
|
||||
|
|
@ -26,7 +26,7 @@ const StepIndicator = ({
|
|||
<div
|
||||
className={cn('flex items-center gap-x-1', isCurrentStep ? 'text-state-accent-solid' : 'text-text-tertiary')}
|
||||
>
|
||||
{isCurrentStep && <div className='size-1 rounded-full bg-state-accent-solid' />}
|
||||
{isCurrentStep && <div className='size-1 rounded-full bg-state-accent-solid' />}
|
||||
<span className='system-2xs-semibold-uppercase'>{step.label}</span>
|
||||
</div>
|
||||
{!isLastStep && (
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import {
|
||||
memo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
import TracingPanel from '@/app/components/workflow/run/tracing-panel'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import {
|
||||
WorkflowRunningStatus,
|
||||
} from '@/app/components/workflow/types'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Tabs from './tabs'
|
||||
import ResultPreview from './result-preview'
|
||||
|
||||
const Result = () => {
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
const [currentTab, setCurrentTab] = useState<string>('RESULT')
|
||||
|
||||
const switchTab = async (tab: string) => {
|
||||
setCurrentTab(tab)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex grow flex-col'>
|
||||
<Tabs currentTab={currentTab} workflowRunningData={workflowRunningData} switchTab={switchTab} />
|
||||
<div className='flex h-0 grow flex-col overflow-y-auto'>
|
||||
{currentTab === 'RESULT' && (
|
||||
<ResultPreview
|
||||
isRunning={!workflowRunningData?.result || workflowRunningData?.result.status === WorkflowRunningStatus.Running}
|
||||
outputs={workflowRunningData?.result?.outputs}
|
||||
error={workflowRunningData?.result?.error}
|
||||
tracing={workflowRunningData?.tracing}
|
||||
onSwitchToDetail={() => switchTab('DETAIL')}
|
||||
/>
|
||||
)}
|
||||
{currentTab === 'DETAIL' && (
|
||||
<ResultPanel
|
||||
inputs={workflowRunningData?.result?.inputs}
|
||||
outputs={workflowRunningData?.result?.outputs}
|
||||
status={workflowRunningData?.result?.status || ''}
|
||||
error={workflowRunningData?.result?.error}
|
||||
elapsed_time={workflowRunningData?.result?.elapsed_time}
|
||||
total_tokens={workflowRunningData?.result?.total_tokens}
|
||||
created_at={workflowRunningData?.result?.created_at}
|
||||
created_by={(workflowRunningData?.result?.created_by as any)?.name}
|
||||
steps={workflowRunningData?.result?.total_steps}
|
||||
exceptionCounts={workflowRunningData?.result?.exceptions_count}
|
||||
/>
|
||||
)}
|
||||
{currentTab === 'DETAIL' && !workflowRunningData?.result && (
|
||||
<div className='flex h-full items-center justify-center'>
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
{currentTab === 'TRACING' && (
|
||||
<TracingPanel
|
||||
className='bg-background-section-burn'
|
||||
list={workflowRunningData?.tracing || []}
|
||||
/>
|
||||
)}
|
||||
{currentTab === 'TRACING' && !workflowRunningData?.tracing?.length && (
|
||||
<div className='flex h-full items-center justify-center'>
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Result)
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
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'
|
||||
import { ChunkCardList } from '../../../../chunk-card-list'
|
||||
|
||||
type ResultTextProps = {
|
||||
isRunning?: boolean
|
||||
outputs?: any
|
||||
error?: string
|
||||
tracing?: NodeTracing[]
|
||||
onSwitchToDetail: () => void
|
||||
}
|
||||
|
||||
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])
|
||||
|
||||
return (
|
||||
<>
|
||||
{isRunning && !outputs && (
|
||||
<div className='flex grow flex-col items-center justify-center gap-y-2 pb-20'>
|
||||
<RiLoader2Line className='size-4 animate-spin' />
|
||||
<div className='system-sm-regular text-text-tertiary'>{t('pipeline.result.resultPreview.loading')}</div>
|
||||
</div>
|
||||
)}
|
||||
{!isRunning && error && (
|
||||
<div className='flex grow flex-col items-center justify-center gap-y-2 pb-20'>
|
||||
<div className='system-sm-regular text-text-tertiary'>{t('pipeline.result.resultPreview.error')}</div>
|
||||
<Button onClick={onSwitchToDetail}>
|
||||
{t('pipeline.result.resultPreview.viewDetails')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{outputs && (
|
||||
<div className='flex grow flex-col bg-background-body p-1'>
|
||||
{!!chunkInfo && <ChunkCardList chunkInfo={chunkInfo} />}
|
||||
<div className='system-xs-regular mt-1 flex items-center gap-x-2 text-text-tertiary'>
|
||||
<div className='h-px flex-1 bg-gradient-to-r from-background-gradient-mask-transparent to-divider-regular' />
|
||||
<span className='shrink-0truncate' title={t('pipeline.result.resultPreview.footerTip', { count: 20 })}>
|
||||
{t('pipeline.result.resultPreview.footerTip', { count: 20 })}
|
||||
</span>
|
||||
<div className='h-px flex-1 bg-gradient-to-l from-background-gradient-mask-transparent to-divider-regular' />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ResultPreview)
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { WorkflowRunningData } from '@/app/components/workflow/types'
|
||||
import Tab from './tab'
|
||||
|
||||
type TabsProps = {
|
||||
currentTab: string
|
||||
workflowRunningData?: WorkflowRunningData
|
||||
switchTab: (tab: string) => void
|
||||
}
|
||||
|
||||
const Tabs = ({
|
||||
currentTab,
|
||||
workflowRunningData,
|
||||
switchTab,
|
||||
}: TabsProps) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className='flex shrink-0 items-center gap-x-6 border-b-[0.5px] border-divider-subtle px-4'>
|
||||
<Tab
|
||||
isActive={currentTab === 'RESULT'}
|
||||
label={t('runLog.result')}
|
||||
value='RESULT'
|
||||
workflowRunningData={workflowRunningData}
|
||||
onClick={switchTab}
|
||||
/>
|
||||
<Tab
|
||||
isActive={currentTab === 'DETAIL'}
|
||||
label={t('runLog.detail')}
|
||||
value='DETAIL'
|
||||
workflowRunningData={workflowRunningData}
|
||||
onClick={switchTab}
|
||||
/>
|
||||
<Tab
|
||||
isActive={currentTab === 'TRACING'}
|
||||
label={t('runLog.tracing')}
|
||||
value='TRACING'
|
||||
workflowRunningData={workflowRunningData}
|
||||
onClick={switchTab}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Tabs)
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import React, { useCallback } from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { WorkflowRunningData } from '@/app/components/workflow/types'
|
||||
|
||||
type TabProps = {
|
||||
isActive: boolean
|
||||
label: string
|
||||
value: string
|
||||
workflowRunningData?: WorkflowRunningData
|
||||
onClick: (value: string) => void
|
||||
}
|
||||
|
||||
const Tab = ({ isActive, label, value, workflowRunningData, onClick }: TabProps) => {
|
||||
const handleClick = useCallback(() => {
|
||||
onClick(value)
|
||||
}, [value, onClick])
|
||||
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
className={cn(
|
||||
'system-sm-semibold-uppercase cursor-pointer border-b-2 border-transparent py-3 text-text-tertiary',
|
||||
isActive && 'border-util-colors-blue-brand-blue-brand-600 text-text-primary',
|
||||
!workflowRunningData && '!cursor-not-allowed opacity-30',
|
||||
)}
|
||||
onClick={handleClick}
|
||||
disabled={!workflowRunningData}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Tab)
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
|
@ -9,14 +8,13 @@ import Header from '@/app/components/workflow/header'
|
|||
import { fetchWorkflowRunHistory } from '@/service/workflow'
|
||||
import {
|
||||
useStore,
|
||||
useWorkflowStore,
|
||||
} from '@/app/components/workflow/store'
|
||||
import InputFieldButton from './input-field-button'
|
||||
import Publisher from './publisher'
|
||||
import RunMode from './run-mode'
|
||||
|
||||
const RagPipelineHeader = () => {
|
||||
const { t } = useTranslation()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel)
|
||||
|
||||
|
|
@ -27,11 +25,6 @@ const RagPipelineHeader = () => {
|
|||
}
|
||||
}, [pipelineId])
|
||||
|
||||
const handleStopRun = useCallback(() => {
|
||||
const { setShowDebugAndPreviewPanel } = workflowStore.getState()
|
||||
setShowDebugAndPreviewPanel(false)
|
||||
}, [workflowStore])
|
||||
|
||||
const headerProps: HeaderProps = useMemo(() => {
|
||||
return {
|
||||
normal: {
|
||||
|
|
@ -41,17 +34,17 @@ const RagPipelineHeader = () => {
|
|||
},
|
||||
runAndHistoryProps: {
|
||||
showRunButton: true,
|
||||
runButtonText: t('workflow.singleRun.testRun'),
|
||||
viewHistoryProps,
|
||||
isRunning: showDebugAndPreviewPanel,
|
||||
onStopRun: handleStopRun,
|
||||
components: {
|
||||
RunMode,
|
||||
},
|
||||
},
|
||||
},
|
||||
viewHistory: {
|
||||
viewHistoryProps,
|
||||
},
|
||||
}
|
||||
}, [viewHistoryProps, showDebugAndPreviewPanel, handleStopRun, t])
|
||||
}, [viewHistoryProps, showDebugAndPreviewPanel, t])
|
||||
|
||||
return (
|
||||
<Header {...headerProps} />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useWorkflowRun, useWorkflowStartRun } from '@/app/components/workflow/hooks'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
|
||||
import { getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils'
|
||||
import cn from '@/utils/classnames'
|
||||
import { RiCloseLine, RiDatabase2Line, RiLoader2Line, RiPlayLargeLine } from '@remixicon/react'
|
||||
import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
|
||||
type RunModeProps = {
|
||||
text?: string
|
||||
}
|
||||
|
||||
const RunMode = ({
|
||||
text,
|
||||
}: RunModeProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleWorkflowStartRunInWorkflow } = useWorkflowStartRun()
|
||||
const { handleStopRun } = useWorkflowRun()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
const isPreparingDataSource = useStore(s => s.isPreparingDataSource)
|
||||
|
||||
const isRunning = workflowRunningData?.result.status === WorkflowRunningStatus.Running
|
||||
const isDisabled = isPreparingDataSource || isRunning
|
||||
|
||||
const handleStop = useCallback(() => {
|
||||
handleStopRun(workflowRunningData?.task_id || '')
|
||||
}, [handleStopRun, workflowRunningData?.task_id])
|
||||
|
||||
const handleCancelPreparingDataSource = useCallback(() => {
|
||||
const { setIsPreparingDataSource, setShowDebugAndPreviewPanel } = workflowStore.getState()
|
||||
setIsPreparingDataSource?.(false)
|
||||
setShowDebugAndPreviewPanel(false)
|
||||
}, [workflowStore])
|
||||
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v.type === EVENT_WORKFLOW_STOP)
|
||||
handleStop()
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-x-px'>
|
||||
<button
|
||||
type='button'
|
||||
className={cn(
|
||||
'system-xs-medium flex h-7 items-center gap-x-1 px-1.5 text-text-accent hover:bg-state-accent-hover',
|
||||
isDisabled && 'cursor-not-allowed bg-state-accent-hover',
|
||||
isDisabled ? 'rounded-l-md' : 'rounded-md',
|
||||
)}
|
||||
onClick={() => {
|
||||
handleWorkflowStartRunInWorkflow()
|
||||
}}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{!isDisabled && (
|
||||
<>
|
||||
<RiPlayLargeLine className='mr-1 size-4' />
|
||||
{workflowRunningData ? t('pipeline.common.reRun') : (text ?? t('pipeline.common.testRun'))}
|
||||
</>
|
||||
)}
|
||||
{isRunning && (
|
||||
<>
|
||||
<RiLoader2Line className='mr-1 size-4 animate-spin' />
|
||||
{t('pipeline.common.processing')}
|
||||
</>
|
||||
)}
|
||||
{isPreparingDataSource && (
|
||||
<>
|
||||
<RiDatabase2Line className='mr-1 size-4' />
|
||||
{t('pipeline.common.preparingDataSource')}
|
||||
</>
|
||||
)}
|
||||
{
|
||||
!isDisabled && (
|
||||
<div className='system-kbd flex items-center gap-x-0.5 text-text-tertiary'>
|
||||
<div className='flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray'>
|
||||
{getKeyboardKeyNameBySystem('alt')}
|
||||
</div>
|
||||
<div className='flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray'>
|
||||
R
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</button>
|
||||
{isRunning && (
|
||||
<button
|
||||
type='button'
|
||||
className={cn(
|
||||
'flex size-7 items-center justify-center rounded-r-md bg-state-accent-active',
|
||||
)}
|
||||
onClick={handleStop}
|
||||
>
|
||||
<StopCircle className='size-4 text-text-accent' />
|
||||
</button>
|
||||
)}
|
||||
{isPreparingDataSource && (
|
||||
<button
|
||||
type='button'
|
||||
className={cn(
|
||||
'flex size-7 items-center justify-center rounded-r-md bg-state-accent-active',
|
||||
)}
|
||||
onClick={handleCancelPreparingDataSource}
|
||||
>
|
||||
<RiCloseLine className='size-4 text-text-accent' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(RunMode)
|
||||
|
|
@ -287,9 +287,10 @@ export const usePipelineRun = () => {
|
|||
)
|
||||
|
||||
const handleStopRun = useCallback((taskId: string) => {
|
||||
const { pipelineId } = workflowStore.getState()
|
||||
const { pipelineId, setShowDebugAndPreviewPanel } = workflowStore.getState()
|
||||
|
||||
stopWorkflowRun(`/rag/pipeline/${pipelineId}/workflow-runs/tasks/${taskId}/stop`)
|
||||
setShowDebugAndPreviewPanel(false)
|
||||
}, [workflowStore])
|
||||
|
||||
const handleRestoreFromPublishedWorkflow = useCallback((publishedWorkflow: VersionHistory) => {
|
||||
|
|
|
|||
|
|
@ -24,20 +24,32 @@ export const usePipelineStartRun = () => {
|
|||
return
|
||||
|
||||
const {
|
||||
isPreparingDataSource,
|
||||
setIsPreparingDataSource,
|
||||
showDebugAndPreviewPanel,
|
||||
setShowEnvPanel,
|
||||
setShowDebugAndPreviewPanel,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (!isPreparingDataSource && workflowRunningData) {
|
||||
workflowStore.setState({
|
||||
isPreparingDataSource: true,
|
||||
workflowRunningData: undefined,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
setShowEnvPanel(false)
|
||||
closeAllInputFieldPanels()
|
||||
|
||||
if (showDebugAndPreviewPanel) {
|
||||
setIsPreparingDataSource?.(false)
|
||||
handleCancelDebugAndPreviewPanel()
|
||||
return
|
||||
}
|
||||
|
||||
await doSyncWorkflowDraft()
|
||||
setIsPreparingDataSource?.(true)
|
||||
setShowDebugAndPreviewPanel(true)
|
||||
}, [workflowStore, handleCancelDebugAndPreviewPanel, doSyncWorkflowDraft])
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ export type RagPipelineSliceShape = {
|
|||
setRagPipelineVariables: (ragPipelineVariables: RAGPipelineVariables) => void
|
||||
dataSourceList: ToolWithProvider[]
|
||||
setDataSourceList: (dataSourceList: DataSourceItem[]) => void
|
||||
isPreparingDataSource: boolean
|
||||
setIsPreparingDataSource: (isPreparingDataSource: boolean) => void
|
||||
}
|
||||
|
||||
export type CreateRagPipelineSliceSlice = StateCreator<RagPipelineSliceShape>
|
||||
|
|
@ -45,4 +47,6 @@ export const createRagPipelineSliceSlice: StateCreator<RagPipelineSliceShape> =
|
|||
const formattedDataSourceList = dataSourceList.map(item => transformDataSourceToTool(item))
|
||||
set(() => ({ dataSourceList: formattedDataSourceList }))
|
||||
},
|
||||
isPreparingDataSource: false,
|
||||
setIsPreparingDataSource: isPreparingDataSource => set(() => ({ isPreparingDataSource })),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,97 +1,17 @@
|
|||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiLoader2Line,
|
||||
RiPlayLargeLine,
|
||||
} from '@remixicon/react'
|
||||
import { useStore } from '../store'
|
||||
import {
|
||||
useNodesReadOnly,
|
||||
useWorkflowRun,
|
||||
useWorkflowStartRun,
|
||||
} from '../hooks'
|
||||
import { WorkflowRunningStatus } from '../types'
|
||||
import type { ViewHistoryProps } from './view-history'
|
||||
import ViewHistory from './view-history'
|
||||
import Checklist from './checklist'
|
||||
import cn from '@/utils/classnames'
|
||||
import {
|
||||
StopCircle,
|
||||
} from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
|
||||
|
||||
type RunModeProps = {
|
||||
text?: string
|
||||
isRunning?: boolean
|
||||
onStopRun?: () => void
|
||||
}
|
||||
const RunMode = memo(({
|
||||
text,
|
||||
isRunning: running,
|
||||
onStopRun,
|
||||
}: RunModeProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleWorkflowStartRunInWorkflow } = useWorkflowStartRun()
|
||||
const { handleStopRun } = useWorkflowRun()
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
const isRunning = workflowRunningData?.result.status === WorkflowRunningStatus.Running
|
||||
const mergedRunning = isRunning || running
|
||||
|
||||
const handleStop = () => {
|
||||
handleStopRun(workflowRunningData?.task_id || '')
|
||||
}
|
||||
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v.type === EVENT_WORKFLOW_STOP)
|
||||
handleStop()
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-7 items-center px-2.5 text-[13px] font-medium text-components-button-secondary-accent-text',
|
||||
'cursor-pointer hover:bg-state-accent-hover',
|
||||
mergedRunning && 'cursor-not-allowed bg-state-accent-hover',
|
||||
mergedRunning ? 'rounded-l-md' : 'rounded-md',
|
||||
)}
|
||||
onClick={() => {
|
||||
handleWorkflowStartRunInWorkflow()
|
||||
}}
|
||||
>
|
||||
{
|
||||
mergedRunning
|
||||
? (
|
||||
<>
|
||||
<RiLoader2Line className='mr-1 h-4 w-4 animate-spin' />
|
||||
{t('workflow.common.running')}
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<RiPlayLargeLine className='mr-1 h-4 w-4' />
|
||||
{text ?? t('workflow.common.run')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
mergedRunning && (
|
||||
<div
|
||||
className={cn(
|
||||
'ml-[1px] flex h-7 w-7 cursor-pointer items-center justify-center rounded-r-md bg-state-accent-active',
|
||||
)}
|
||||
onClick={() => onStopRun ? onStopRun() : handleStopRun(workflowRunningData?.task_id || '')}
|
||||
>
|
||||
<StopCircle className='h-4 w-4 text-text-accent' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
})
|
||||
import RunMode from './run-mode'
|
||||
|
||||
const PreviewMode = memo(() => {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -115,24 +35,32 @@ export type RunAndHistoryProps = {
|
|||
showRunButton?: boolean
|
||||
runButtonText?: string
|
||||
isRunning?: boolean
|
||||
onStopRun?: () => void
|
||||
showPreviewButton?: boolean
|
||||
viewHistoryProps?: ViewHistoryProps
|
||||
components?: {
|
||||
RunMode?: React.ComponentType<
|
||||
{
|
||||
text?: string
|
||||
}
|
||||
>
|
||||
}
|
||||
}
|
||||
const RunAndHistory = ({
|
||||
showRunButton,
|
||||
runButtonText,
|
||||
isRunning,
|
||||
onStopRun,
|
||||
showPreviewButton,
|
||||
viewHistoryProps,
|
||||
components,
|
||||
}: RunAndHistoryProps) => {
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const { RunMode: CustomRunMode } = components || {}
|
||||
|
||||
return (
|
||||
<div className='flex h-8 items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-0.5 shadow-xs'>
|
||||
{
|
||||
showRunButton && <RunMode text={runButtonText} isRunning={isRunning} onStopRun={onStopRun} />
|
||||
showRunButton && (
|
||||
CustomRunMode ? <CustomRunMode text={runButtonText} /> : <RunMode text={runButtonText} />
|
||||
)
|
||||
}
|
||||
{
|
||||
showPreviewButton && <PreviewMode />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useWorkflowRun, useWorkflowStartRun } from '@/app/components/workflow/hooks'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
|
||||
import { getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils'
|
||||
import cn from '@/utils/classnames'
|
||||
import { RiLoader2Line, RiPlayLargeLine } from '@remixicon/react'
|
||||
import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
|
||||
type RunModeProps = {
|
||||
text?: string
|
||||
}
|
||||
|
||||
const RunMode = ({
|
||||
text,
|
||||
}: RunModeProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleWorkflowStartRunInWorkflow } = useWorkflowStartRun()
|
||||
const { handleStopRun } = useWorkflowRun()
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
|
||||
const isRunning = workflowRunningData?.result.status === WorkflowRunningStatus.Running
|
||||
|
||||
const handleStop = useCallback(() => {
|
||||
handleStopRun(workflowRunningData?.task_id || '')
|
||||
}, [handleStopRun, workflowRunningData?.task_id])
|
||||
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v.type === EVENT_WORKFLOW_STOP)
|
||||
handleStop()
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-x-px'>
|
||||
<button
|
||||
type='button'
|
||||
className={cn(
|
||||
'system-xs-medium flex h-7 items-center gap-x-1 px-1.5 text-text-accent hover:bg-state-accent-hover',
|
||||
isRunning && 'cursor-not-allowed bg-state-accent-hover',
|
||||
isRunning ? 'rounded-l-md' : 'rounded-md',
|
||||
)}
|
||||
onClick={() => {
|
||||
handleWorkflowStartRunInWorkflow()
|
||||
}}
|
||||
disabled={isRunning}
|
||||
>
|
||||
{
|
||||
isRunning
|
||||
? (
|
||||
<>
|
||||
<RiLoader2Line className='mr-1 size-4 animate-spin' />
|
||||
{t('workflow.common.running')}
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<RiPlayLargeLine className='mr-1 size-4' />
|
||||
{text ?? t('workflow.common.run')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
!isRunning && (
|
||||
<div className='system-kbd flex items-center gap-x-0.5 text-text-tertiary'>
|
||||
<div className='flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray'>
|
||||
{getKeyboardKeyNameBySystem('alt')}
|
||||
</div>
|
||||
<div className='flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray'>
|
||||
R
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</button>
|
||||
{
|
||||
isRunning && (
|
||||
<button
|
||||
type='button'
|
||||
className={cn(
|
||||
'flex size-7 items-center justify-center rounded-r-md bg-state-accent-active',
|
||||
)}
|
||||
onClick={handleStop}
|
||||
>
|
||||
<StopCircle className='size-4 text-text-accent' />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(RunMode)
|
||||
|
|
@ -72,7 +72,7 @@ const DisplayContent = (props: DisplayContentProps) => {
|
|||
btnClassName='!pl-1.5 !pr-0.5 gap-[3px]'
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-1 overflow-auto rounded-b-[10px] pb-1 pl-3 pr-1'>
|
||||
<div className='flex flex-1 overflow-auto rounded-b-[10px] pl-3 pr-1'>
|
||||
{viewMode === ViewMode.Code && (
|
||||
type === ContentType.Markdown
|
||||
? <Textarea
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ const translation = {
|
|||
description: 'Knowledge description',
|
||||
descriptionPlaceholder: 'Please enter the description of this Knowledge Pipeline. (Optional) ',
|
||||
},
|
||||
testRun: 'Test Run',
|
||||
preparingDataSource: 'Preparing Data Source',
|
||||
reRun: 'Re-run',
|
||||
processing: 'Processing',
|
||||
},
|
||||
inputField: {
|
||||
create: 'Create user input field',
|
||||
|
|
@ -19,6 +23,14 @@ const translation = {
|
|||
title: 'This pipeline has not yet been published',
|
||||
desc: 'When the pipeline is not published, you can modify the chunk structure in the knowledge base node, and the pipeline orchestration and changes will be automatically saved as a draft.',
|
||||
},
|
||||
result: {
|
||||
resultPreview: {
|
||||
loading: 'Processing...Please wait',
|
||||
error: 'Error occurred during execution',
|
||||
viewDetails: 'View details',
|
||||
footerTip: 'In test run mode, preview up to {{count}} chunks',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -212,8 +212,10 @@ const translation = {
|
|||
toolParameterRequired: '{{field}}: parameter [{{param}}] is required',
|
||||
},
|
||||
singleRun: {
|
||||
testRun: 'Test Run ',
|
||||
testRun: 'Test Run',
|
||||
startRun: 'Start Run',
|
||||
preparingDataSource: 'Preparing Data Source',
|
||||
reRun: 'Re-run',
|
||||
running: 'Running',
|
||||
testRunIteration: 'Test Run Iteration',
|
||||
back: 'Back',
|
||||
|
|
@ -974,7 +976,7 @@ const translation = {
|
|||
updateSuccess: 'Version updated',
|
||||
updateFailure: 'Failed to update version',
|
||||
copyIdSuccess: 'ID copied to clipboard',
|
||||
},
|
||||
},
|
||||
},
|
||||
debug: {
|
||||
settingsTab: 'Settings',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ const translation = {
|
|||
description: '知识流水线描述',
|
||||
descriptionPlaceholder: '请输入此知识流水线的描述。 (可选)',
|
||||
},
|
||||
testRun: '测试运行',
|
||||
preparingDataSource: '准备数据源',
|
||||
reRun: '重新运行',
|
||||
processing: '处理中',
|
||||
},
|
||||
inputField: {
|
||||
create: '创建用户输入字段',
|
||||
|
|
@ -19,6 +23,14 @@ const translation = {
|
|||
title: '此知识流水线尚未发布',
|
||||
desc: '当知识流水线未发布时,您可以修改知识库节点中的分块结构,知识流水线编排和更改将自动保存为草稿。',
|
||||
},
|
||||
result: {
|
||||
resultPreview: {
|
||||
loading: '处理中...请稍后',
|
||||
error: '执行过程中出现错误',
|
||||
viewDetails: '查看详情',
|
||||
footerTip: '在测试运行模式下,最多预览 {{count}} 个分段',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -212,8 +212,10 @@ const translation = {
|
|||
toolParameterRequired: '{{field}}: 参数 [{{param}}] 不能为空',
|
||||
},
|
||||
singleRun: {
|
||||
testRun: '测试运行 ',
|
||||
testRun: '测试运行',
|
||||
startRun: '开始运行',
|
||||
preparingDataSource: '准备数据源',
|
||||
reRun: '重新运行',
|
||||
running: '运行中',
|
||||
testRunIteration: '测试运行迭代',
|
||||
back: '返回',
|
||||
|
|
@ -937,20 +939,6 @@ const translation = {
|
|||
indexMethodIsRequired: '索引方法是必需的',
|
||||
retrievalSettingIsRequired: '检索设置是必需的',
|
||||
},
|
||||
dataSource: {
|
||||
supportedFileFormats: '支持的文件格式',
|
||||
supportedFileFormatsPlaceholder: '文件格式,例如:doc',
|
||||
add: '添加数据源',
|
||||
},
|
||||
knowledgeBase: {
|
||||
chunkStructure: '分段结构',
|
||||
chooseChunkStructure: '选择分段结构',
|
||||
changeChunkStructure: '更改分段结构',
|
||||
aboutRetrieval: '关于知识检索。',
|
||||
chunkIsRequired: '分段结构是必需的',
|
||||
indexMethodIsRequired: '索引方法是必需的',
|
||||
retrievalSettingIsRequired: '检索设置是必需的',
|
||||
},
|
||||
},
|
||||
tracing: {
|
||||
stopBy: '由{{user}}终止',
|
||||
|
|
|
|||
Loading…
Reference in New Issue