Initial commit
This commit is contained in:
382
web/app/components/datasets/create/step-two/index.module.css
Normal file
382
web/app/components/datasets/create/step-two/index.module.css
Normal file
@@ -0,0 +1,382 @@
|
||||
.pageHeader {
|
||||
@apply px-16;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding-top: 42px;
|
||||
padding-bottom: 12px;
|
||||
background-color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
color: #101828;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.fixed {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-bottom: 0.5px solid #EAECF0;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.form {
|
||||
@apply px-16 pb-8;
|
||||
}
|
||||
|
||||
.form .label {
|
||||
@apply pt-6 pb-2;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #344054;
|
||||
}
|
||||
|
||||
.segmentationItem {
|
||||
min-height: 68px;
|
||||
}
|
||||
|
||||
.indexItem {
|
||||
min-height: 146px;
|
||||
}
|
||||
|
||||
.indexItem .disableMask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 12px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.indexItem .warningTip {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 8px 20px 8px 40px;
|
||||
background: #FFFAEB;
|
||||
border-top: 0.5px solid #FEF0C7;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #344054;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.indexItem .warningTip::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
left: 20px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: center no-repeat url(../assets/alert-triangle.svg);
|
||||
background-size: 12px;
|
||||
}
|
||||
|
||||
.indexItem .warningTip .click {
|
||||
color: #155EEF;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.indexItem.disabled:hover {
|
||||
background-color: #fcfcfd;
|
||||
border-color: #f2f4f7;
|
||||
box-shadow: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.indexItem.disabled:hover .radio {
|
||||
@apply w-4 h-4 border-[2px] border-gray-200 rounded-full;
|
||||
}
|
||||
|
||||
.radioItem {
|
||||
@apply relative mb-2 rounded-xl border border-gray-100 cursor-pointer;
|
||||
background-color: #fcfcfd;
|
||||
}
|
||||
|
||||
.radioItem.segmentationItem.custom {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.radioItem.segmentationItem.custom .typeHeader {
|
||||
/* height: 65px; */
|
||||
}
|
||||
|
||||
.radioItem.indexItem .typeHeader {
|
||||
@apply py-4 pr-5;
|
||||
}
|
||||
|
||||
.radioItem.indexItem.active .typeHeader {
|
||||
padding: 15.5px 19.5px 15.5px 63.5px;
|
||||
}
|
||||
|
||||
.radioItem.indexItem .radio {
|
||||
top: 16px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.radioItem.indexItem.active .radio {
|
||||
top: 16px;
|
||||
right: 19.5px;
|
||||
}
|
||||
|
||||
.radioItem.indexItem .typeHeader .title {
|
||||
@apply pb-1;
|
||||
}
|
||||
|
||||
.radioItem.indexItem .typeHeader .tip {
|
||||
@apply pb-3;
|
||||
}
|
||||
|
||||
.radioItem .typeIcon {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
left: 20px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: #EEF4FF center no-repeat;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.typeIcon.auto {
|
||||
background-color: #F5F3FF;
|
||||
background-image: url(../assets/zap-fast.svg);
|
||||
}
|
||||
|
||||
.typeIcon.customize {
|
||||
background-image: url(../assets/sliders-02.svg);
|
||||
}
|
||||
|
||||
.typeIcon.qualified {
|
||||
background-color: #FFF6ED;
|
||||
background-image: url(../assets/star-07.svg);
|
||||
}
|
||||
|
||||
.typeIcon.economical {
|
||||
background-image: url(../assets/piggy-bank-01.svg);
|
||||
}
|
||||
|
||||
.radioItem .radio {
|
||||
@apply w-4 h-4 border-[2px] border-gray-200 rounded-full;
|
||||
position: absolute;
|
||||
top: 26px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.radioItem:hover {
|
||||
background-color: #ffffff;
|
||||
border-color: #B2CCFF;
|
||||
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
|
||||
}
|
||||
|
||||
.radioItem:hover .radio {
|
||||
border-color: #155eef;
|
||||
}
|
||||
|
||||
.radioItem.active {
|
||||
border-width: 1.5px;
|
||||
border-color: #528BFF;
|
||||
box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06);
|
||||
}
|
||||
|
||||
.radioItem.active .radio {
|
||||
top: 25.5px;
|
||||
right: 19.5px;
|
||||
border-width: 5px;
|
||||
border-color: #155EEF;
|
||||
}
|
||||
|
||||
.radioItem.active:hover {
|
||||
border-width: 1.5px;
|
||||
border-color: #528BFF;
|
||||
box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06);
|
||||
}
|
||||
|
||||
.radioItem.active .typeIcon {
|
||||
top: 17.5px;
|
||||
left: 19.5px;
|
||||
}
|
||||
|
||||
.radioItem.active .typeHeader {
|
||||
padding: 11.5px 63.5px;
|
||||
}
|
||||
|
||||
.typeHeader {
|
||||
@apply flex flex-col px-16 py-3 justify-center;
|
||||
}
|
||||
|
||||
.typeHeader .title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 2px;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.typeHeader .tip {
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
.recommendTag {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 6px;
|
||||
margin-left: 4px;
|
||||
border: 1px solid #E0EAFF;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: #444CE7;
|
||||
}
|
||||
|
||||
.typeFormBody {
|
||||
@apply px-16;
|
||||
border-top: 1px solid #F2F4F7;
|
||||
}
|
||||
|
||||
.formRow {
|
||||
@apply flex justify-between mt-6;
|
||||
}
|
||||
|
||||
.formRow .label {
|
||||
@apply mb-2 p-0;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.ruleItem {
|
||||
@apply flex items-center h-7;
|
||||
}
|
||||
|
||||
.formFooter {
|
||||
padding: 16px 0 28px;
|
||||
}
|
||||
|
||||
.formFooter .button {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.input {
|
||||
@apply inline-flex h-9 w-full py-1 px-2 rounded-lg text-xs leading-normal;
|
||||
@apply bg-gray-100 caret-primary-600 hover:bg-gray-100 focus:ring-1 focus:ring-inset focus:ring-gray-200 focus-visible:outline-none focus:bg-white placeholder:text-gray-400;
|
||||
}
|
||||
|
||||
.file {
|
||||
@apply flex justify-between items-center mt-8 px-6 py-4 rounded-xl bg-gray-50;
|
||||
}
|
||||
|
||||
.file .divider {
|
||||
@apply shrink-0 mx-4 w-px bg-gray-200;
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
.fileIcon {
|
||||
@apply inline-flex mr-1 w-6 h-6 bg-center bg-no-repeat;
|
||||
background-image: url(../assets/pdf.svg);
|
||||
background-size: 24px;
|
||||
}
|
||||
|
||||
.fileIcon.pdf {
|
||||
background-image: url(../assets/pdf.svg);
|
||||
}
|
||||
|
||||
.fileIcon.html,
|
||||
.fileIcon.htm {
|
||||
background-image: url(../assets/html.svg);
|
||||
}
|
||||
|
||||
.fileIcon.md,
|
||||
.fileIcon.markdown {
|
||||
background-image: url(../assets/md.svg);
|
||||
}
|
||||
|
||||
.fileIcon.txt {
|
||||
background-image: url(../assets/txt.svg);
|
||||
}
|
||||
|
||||
.fileIcon.json {
|
||||
background-image: url(../assets/json.svg);
|
||||
}
|
||||
|
||||
.fileContent {
|
||||
flex: 1 1 50%;
|
||||
}
|
||||
|
||||
.divider {
|
||||
@apply mx-3 w-px h-4 bg-gray-200;
|
||||
}
|
||||
|
||||
.calculating {
|
||||
color: #98A2B3;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.sideTip {
|
||||
@apply flex flex-col items-center shrink-0;
|
||||
padding-top: 108px;
|
||||
width: 524px;
|
||||
border-left: 0.5px solid #F2F4F7;
|
||||
}
|
||||
|
||||
.tipCard {
|
||||
@apply flex flex-col items-start p-6;
|
||||
width: 320px;
|
||||
background-color: #F9FAFB;
|
||||
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.tipCard .icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #EAECF0;
|
||||
border-radius: 6px;
|
||||
background: center no-repeat url(../assets/book-open-01.svg);
|
||||
background-size: 16px;
|
||||
}
|
||||
|
||||
.tipCard .title {
|
||||
margin: 12px 0;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #344054;
|
||||
}
|
||||
|
||||
.tipCard .content {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #344054;
|
||||
}
|
||||
|
||||
.previewWrap {
|
||||
flex-shrink: 0;
|
||||
width: 524px;
|
||||
}
|
||||
|
||||
.previewHeader {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding-top: 42px;
|
||||
background-color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
color: #101828;
|
||||
z-index: 10;
|
||||
}
|
491
web/app/components/datasets/create/step-two/index.tsx
Normal file
491
web/app/components/datasets/create/step-two/index.tsx
Normal file
@@ -0,0 +1,491 @@
|
||||
'use client'
|
||||
import React, { useState, useRef, useEffect, useLayoutEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import type { File, PreProcessingRule, Rules, FileIndexingEstimateResponse as IndexingEstimateResponse } from '@/models/datasets'
|
||||
import {
|
||||
fetchDefaultProcessRule,
|
||||
createFirstDocument,
|
||||
createDocument,
|
||||
fetchFileIndexingEstimate as didFetchFileIndexingEstimate,
|
||||
} from '@/service/datasets'
|
||||
import type { CreateDocumentReq, createDocumentResponse } from '@/models/datasets'
|
||||
import Button from '@/app/components/base/button'
|
||||
import PreviewItem from './preview-item'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { XMarkIcon } from '@heroicons/react/20/solid'
|
||||
|
||||
import cn from 'classnames'
|
||||
import s from './index.module.css'
|
||||
import Link from 'next/link'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
|
||||
type StepTwoProps = {
|
||||
hasSetAPIKEY: boolean,
|
||||
onSetting: () => void,
|
||||
datasetId?: string,
|
||||
indexingType?: string,
|
||||
file?: File,
|
||||
onStepChange: (delta: number) => void,
|
||||
updateIndexingTypeCache: (type: string) => void,
|
||||
updateResultCache: (res: createDocumentResponse) => void
|
||||
}
|
||||
|
||||
enum SegmentType {
|
||||
AUTO = 'automatic',
|
||||
CUSTOM = 'custom',
|
||||
}
|
||||
enum IndexingType {
|
||||
QUALIFIED = 'high_quality',
|
||||
ECONOMICAL = 'economy',
|
||||
}
|
||||
|
||||
const StepTwo = ({
|
||||
hasSetAPIKEY,
|
||||
onSetting,
|
||||
datasetId,
|
||||
indexingType,
|
||||
file,
|
||||
onStepChange,
|
||||
updateIndexingTypeCache,
|
||||
updateResultCache,
|
||||
}: StepTwoProps) => {
|
||||
const { t } = useTranslation()
|
||||
const scrollRef = useRef<HTMLDivElement>(null)
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
const previewScrollRef = useRef<HTMLDivElement>(null)
|
||||
const [previewScrolled, setPreviewScrolled] = useState(false)
|
||||
const [segmentationType, setSegmentationType] = useState<SegmentType>(SegmentType.AUTO)
|
||||
const [segmentIdentifier, setSegmentIdentifier] = useState('\\n')
|
||||
const [max, setMax] = useState(1000)
|
||||
const [rules, setRules] = useState<PreProcessingRule[]>([])
|
||||
const [defaultConfig, setDefaultConfig] = useState<Rules>()
|
||||
const hasSetIndexType = !!indexingType
|
||||
const [indexType, setIndexType] = useState<IndexingType>(
|
||||
indexingType ||
|
||||
hasSetAPIKEY ? IndexingType.QUALIFIED : IndexingType.ECONOMICAL
|
||||
)
|
||||
const [showPreview, { setTrue: setShowPreview, setFalse: hidePreview }] = useBoolean()
|
||||
const [customFileIndexingEstimate, setCustomFileIndexingEstimate] = useState<IndexingEstimateResponse | null>(null)
|
||||
const [automaticFileIndexingEstimate, setAutomaticFileIndexingEstimate] = useState<IndexingEstimateResponse | null>(null)
|
||||
const fileIndexingEstimate = (() => {
|
||||
return segmentationType === SegmentType.AUTO ? automaticFileIndexingEstimate : customFileIndexingEstimate
|
||||
})()
|
||||
|
||||
const scrollHandle = (e: any) => {
|
||||
if (e.target.scrollTop > 0) {
|
||||
setScrolled(true)
|
||||
} else {
|
||||
setScrolled(false)
|
||||
}
|
||||
}
|
||||
|
||||
const previewScrollHandle = (e: any) => {
|
||||
if (e.target.scrollTop > 0) {
|
||||
setPreviewScrolled(true)
|
||||
} else {
|
||||
setPreviewScrolled(false)
|
||||
}
|
||||
}
|
||||
const getFileName = (name: string) => {
|
||||
const arr = name.split('.')
|
||||
return arr.slice(0, -1).join('.')
|
||||
}
|
||||
|
||||
const getRuleName = (key: string) => {
|
||||
if (key === 'remove_extra_spaces') {
|
||||
return t('datasetCreation.stepTwo.removeExtraSpaces')
|
||||
}
|
||||
if (key === 'remove_urls_emails') {
|
||||
return t('datasetCreation.stepTwo.removeUrlEmails')
|
||||
}
|
||||
if (key === 'remove_stopwords') {
|
||||
return t('datasetCreation.stepTwo.removeStopwords')
|
||||
}
|
||||
}
|
||||
const ruleChangeHandle = (id: string) => {
|
||||
const newRules = rules.map(rule => {
|
||||
if (rule.id === id) {
|
||||
return {
|
||||
id: rule.id,
|
||||
enabled: !rule.enabled,
|
||||
}
|
||||
}
|
||||
return rule
|
||||
})
|
||||
setRules(newRules)
|
||||
}
|
||||
const resetRules = () => {
|
||||
if (defaultConfig) {
|
||||
setSegmentIdentifier(defaultConfig.segmentation.separator === '\n' ? '\\n' : defaultConfig.segmentation.separator || '\\n')
|
||||
setMax(defaultConfig.segmentation.max_tokens)
|
||||
setRules(defaultConfig.pre_processing_rules)
|
||||
}
|
||||
}
|
||||
|
||||
const confirmChangeCustomConfig = async () => {
|
||||
setCustomFileIndexingEstimate(null)
|
||||
setShowPreview()
|
||||
await fetchFileIndexingEstimate()
|
||||
}
|
||||
|
||||
const getIndexing_technique = () => indexingType ? indexingType : indexType
|
||||
|
||||
const getProcessRule = () => {
|
||||
const processRule: any = {
|
||||
rules: {}, // api will check this. It will be removed after api refactored.
|
||||
mode: segmentationType,
|
||||
}
|
||||
if (segmentationType === SegmentType.CUSTOM) {
|
||||
const ruleObj = {
|
||||
pre_processing_rules: rules,
|
||||
segmentation: {
|
||||
separator: segmentIdentifier === '\\n' ? '\n' : segmentIdentifier,
|
||||
max_tokens: max,
|
||||
},
|
||||
}
|
||||
processRule.rules = ruleObj
|
||||
}
|
||||
return processRule
|
||||
}
|
||||
|
||||
const getFileIndexingEstimateParams = () => {
|
||||
const params = {
|
||||
file_id: file?.id,
|
||||
dataset_id: datasetId,
|
||||
indexing_technique: getIndexing_technique(),
|
||||
process_rule: getProcessRule(),
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
const fetchFileIndexingEstimate = async () => {
|
||||
const res = await didFetchFileIndexingEstimate(getFileIndexingEstimateParams())
|
||||
if (segmentationType === SegmentType.CUSTOM) {
|
||||
setCustomFileIndexingEstimate(res)
|
||||
}
|
||||
else {
|
||||
setAutomaticFileIndexingEstimate(res)
|
||||
}
|
||||
}
|
||||
|
||||
const getCreationParams = () => {
|
||||
const params = {
|
||||
data_source: {
|
||||
type: 'upload_file',
|
||||
info: file?.id,
|
||||
name: file?.name,
|
||||
},
|
||||
indexing_technique: getIndexing_technique(),
|
||||
process_rule: getProcessRule(),
|
||||
} as CreateDocumentReq
|
||||
return params
|
||||
}
|
||||
|
||||
const getRules = async () => {
|
||||
try {
|
||||
const res = await fetchDefaultProcessRule({ url: '/datasets/process-rule' })
|
||||
const separator = res.rules.segmentation.separator
|
||||
setSegmentIdentifier(separator === '\n' ? '\\n' : separator || '\\n')
|
||||
setMax(res.rules.segmentation.max_tokens)
|
||||
setRules(res.rules.pre_processing_rules)
|
||||
setDefaultConfig(res.rules)
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
const createHandle = async () => {
|
||||
try {
|
||||
let res;
|
||||
const params = getCreationParams()
|
||||
if (!datasetId) {
|
||||
res = await createFirstDocument({
|
||||
body: params
|
||||
})
|
||||
updateIndexingTypeCache(indexType)
|
||||
updateResultCache(res)
|
||||
} else {
|
||||
res = await createDocument({
|
||||
datasetId,
|
||||
body: params
|
||||
})
|
||||
updateIndexingTypeCache(indexType)
|
||||
updateResultCache({
|
||||
document: res,
|
||||
})
|
||||
}
|
||||
onStepChange(+1)
|
||||
}
|
||||
catch (err) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: err + '',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// fetch rules
|
||||
getRules()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
scrollRef.current?.addEventListener('scroll', scrollHandle);
|
||||
return () => {
|
||||
scrollRef.current?.removeEventListener('scroll', scrollHandle);
|
||||
}
|
||||
}, [])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (showPreview) {
|
||||
previewScrollRef.current?.addEventListener('scroll', previewScrollHandle);
|
||||
return () => {
|
||||
previewScrollRef.current?.removeEventListener('scroll', previewScrollHandle);
|
||||
}
|
||||
}
|
||||
}, [showPreview])
|
||||
|
||||
useEffect(() => {
|
||||
// get indexing type by props
|
||||
if (indexingType) {
|
||||
setIndexType(indexingType as IndexingType)
|
||||
} else {
|
||||
setIndexType(hasSetAPIKEY ? IndexingType.QUALIFIED : IndexingType.ECONOMICAL)
|
||||
}
|
||||
}, [hasSetAPIKEY, indexingType, datasetId])
|
||||
|
||||
useEffect(() => {
|
||||
if (segmentationType === SegmentType.AUTO) {
|
||||
setAutomaticFileIndexingEstimate(null)
|
||||
setShowPreview()
|
||||
fetchFileIndexingEstimate()
|
||||
} else {
|
||||
hidePreview()
|
||||
setCustomFileIndexingEstimate(null)
|
||||
}
|
||||
}, [segmentationType, indexType])
|
||||
|
||||
return (
|
||||
<div className='flex w-full h-full'>
|
||||
<div ref={scrollRef} className='relative h-full w-full overflow-y-scroll'>
|
||||
<div className={cn(s.pageHeader, scrolled && s.fixed)}>{t('datasetCreation.steps.two')}</div>
|
||||
<div className={cn(s.form)}>
|
||||
<div className={s.label}>{t('datasetCreation.stepTwo.segmentation')}</div>
|
||||
<div className='max-w-[640px]'>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
s.radioItem,
|
||||
s.segmentationItem,
|
||||
segmentationType === SegmentType.AUTO && s.active
|
||||
)}
|
||||
onClick={() => setSegmentationType(SegmentType.AUTO)}
|
||||
>
|
||||
<span className={cn(s.typeIcon, s.auto)} />
|
||||
<span className={cn(s.radio)} />
|
||||
<div className={s.typeHeader}>
|
||||
<div className={s.title}>{t('datasetCreation.stepTwo.auto')}</div>
|
||||
<div className={s.tip}>{t('datasetCreation.stepTwo.autoDescription')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
s.radioItem,
|
||||
s.segmentationItem,
|
||||
segmentationType === SegmentType.CUSTOM && s.active,
|
||||
segmentationType === SegmentType.CUSTOM && s.custom,
|
||||
)}
|
||||
onClick={() => setSegmentationType(SegmentType.CUSTOM)}
|
||||
>
|
||||
<span className={cn(s.typeIcon, s.customize)} />
|
||||
<span className={cn(s.radio)} />
|
||||
<div className={s.typeHeader}>
|
||||
<div className={s.title}>{t('datasetCreation.stepTwo.custom')}</div>
|
||||
<div className={s.tip}>{t('datasetCreation.stepTwo.customDescription')}</div>
|
||||
</div>
|
||||
{segmentationType === SegmentType.CUSTOM && (
|
||||
<div className={s.typeFormBody}>
|
||||
<div className={s.formRow}>
|
||||
<div className='w-full'>
|
||||
<div className={s.label}>{t('datasetCreation.stepTwo.separator')}</div>
|
||||
<input
|
||||
type="text"
|
||||
className={s.input}
|
||||
placeholder={t('datasetCreation.stepTwo.separatorPlaceholder') || ''} value={segmentIdentifier}
|
||||
onChange={(e) => setSegmentIdentifier(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.formRow}>
|
||||
<div className='w-full'>
|
||||
<div className={s.label}>{t('datasetCreation.stepTwo.maxLength')}</div>
|
||||
<input
|
||||
type="number"
|
||||
className={s.input}
|
||||
placeholder={t('datasetCreation.stepTwo.separatorPlaceholder') || ''} value={max}
|
||||
onChange={(e) => setMax(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.formRow}>
|
||||
<div className='w-full'>
|
||||
<div className={s.label}>{t('datasetCreation.stepTwo.rules')}</div>
|
||||
{rules.map(rule => (
|
||||
<div key={rule.id} className={s.ruleItem}>
|
||||
<input id={rule.id} type="checkbox" defaultChecked={rule.enabled} onChange={() => ruleChangeHandle(rule.id)} className="w-4 h-4 rounded border-gray-300 text-blue-700 focus:ring-blue-700" />
|
||||
<label htmlFor={rule.id} className="ml-2 text-sm font-normal cursor-pointer text-gray-800">{getRuleName(rule.id)}</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.formFooter}>
|
||||
<Button type="primary" className={cn(s.button, '!h-8 text-primary-600')} onClick={confirmChangeCustomConfig}>{t('datasetCreation.stepTwo.preview')}</Button>
|
||||
<Button className={cn(s.button, 'ml-2 !h-8')} onClick={resetRules}>{t('datasetCreation.stepTwo.reset')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.label}>{t('datasetCreation.stepTwo.indexMode')}</div>
|
||||
<div className='max-w-[640px]'>
|
||||
<div className='flex items-center gap-3'>
|
||||
{(!hasSetIndexType || (hasSetIndexType && indexingType === IndexingType.QUALIFIED)) && (
|
||||
<div
|
||||
className={cn(
|
||||
s.radioItem,
|
||||
s.indexItem,
|
||||
!hasSetAPIKEY && s.disabled,
|
||||
!hasSetIndexType && indexType === IndexingType.QUALIFIED && s.active,
|
||||
hasSetIndexType && s.disabled,
|
||||
hasSetIndexType && '!w-full',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (hasSetAPIKEY) {
|
||||
setIndexType(IndexingType.QUALIFIED)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className={cn(s.typeIcon, s.qualified)} />
|
||||
{!hasSetIndexType && <span className={cn(s.radio)} />}
|
||||
<div className={s.typeHeader}>
|
||||
<div className={s.title}>
|
||||
{t('datasetCreation.stepTwo.qualified')}
|
||||
{!hasSetIndexType && <span className={s.recommendTag}>{t('datasetCreation.stepTwo.recommend')}</span>}
|
||||
</div>
|
||||
<div className={s.tip}>{t('datasetCreation.stepTwo.qualifiedTip')}</div>
|
||||
<div className='pb-0.5 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.emstimateCost')}</div>
|
||||
{
|
||||
!!fileIndexingEstimate ? (
|
||||
<div className='text-xs font-medium text-gray-800'>{formatNumber(fileIndexingEstimate.tokens)} tokens(<span className='text-yellow-500'>${formatNumber(fileIndexingEstimate.total_price)}</span>)</div>
|
||||
) : (
|
||||
<div className={s.calculating}>{t('datasetCreation.stepTwo.calculating')}</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{!hasSetAPIKEY && (
|
||||
<div className={s.warningTip}>
|
||||
<span>{t('datasetCreation.stepTwo.warning')} </span>
|
||||
<span className={s.click} onClick={onSetting}>{t('datasetCreation.stepTwo.click')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{(!hasSetIndexType || (hasSetIndexType && indexingType === IndexingType.ECONOMICAL)) && (
|
||||
<div
|
||||
className={cn(
|
||||
s.radioItem,
|
||||
s.indexItem,
|
||||
!hasSetIndexType && indexType === IndexingType.ECONOMICAL && s.active,
|
||||
hasSetIndexType && s.disabled,
|
||||
hasSetIndexType && '!w-full',
|
||||
)}
|
||||
onClick={() => !hasSetIndexType && setIndexType(IndexingType.ECONOMICAL)}
|
||||
>
|
||||
<span className={cn(s.typeIcon, s.economical)} />
|
||||
{!hasSetIndexType && <span className={cn(s.radio)} />}
|
||||
<div className={s.typeHeader}>
|
||||
<div className={s.title}>{t('datasetCreation.stepTwo.economical')}</div>
|
||||
<div className={s.tip}>{t('datasetCreation.stepTwo.economicalTip')}</div>
|
||||
<div className='pb-0.5 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.emstimateCost')}</div>
|
||||
<div className='text-xs font-medium text-gray-800'>0 tokens</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{hasSetIndexType && (
|
||||
<div className='mt-2 text-xs text-gray-500 font-medium'>
|
||||
{t('datasetCreation.stepTwo.indexSettedTip')}
|
||||
<Link className='text-[#155EEF]' href={`/datasets/${datasetId}/settings`}>{t('datasetCreation.stepTwo.datasetSettingLink')}</Link>
|
||||
</div>
|
||||
)}
|
||||
<div className={s.file}>
|
||||
<div className={s.fileContent}>
|
||||
<div className='mb-2 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.fileName')}</div>
|
||||
<div className='flex items-center text-sm leading-6 font-medium text-gray-800'>
|
||||
<span className={cn(s.fileIcon, file && s[file.extension])} />
|
||||
{getFileName(file?.name || '')}
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.divider} />
|
||||
<div className={s.fileContent}>
|
||||
<div className='mb-2 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.emstimateSegment')}</div>
|
||||
<div className='flex items-center text-sm leading-6 font-medium text-gray-800'>
|
||||
{
|
||||
!!fileIndexingEstimate ? (
|
||||
<div className='text-xs font-medium text-gray-800'>{formatNumber(fileIndexingEstimate.total_segments)} </div>
|
||||
) : (
|
||||
<div className={s.calculating}>{t('datasetCreation.stepTwo.calculating')}</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-center mt-8 py-2'>
|
||||
<Button onClick={() => onStepChange(-1)}>{t('datasetCreation.stepTwo.lastStep')}</Button>
|
||||
<div className={s.divider} />
|
||||
<Button type='primary' onClick={createHandle}>{t('datasetCreation.stepTwo.nextStep')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{(showPreview) ? (
|
||||
<div ref={previewScrollRef} className={cn(s.previewWrap, 'relativeh-full overflow-y-scroll border-l border-[#F2F4F7]')}>
|
||||
<div className={cn(s.previewHeader, previewScrolled && `${s.fixed} pb-3`, ' flex items-center justify-between px-8')}>
|
||||
<span>{t('datasetCreation.stepTwo.previewTitle')}</span>
|
||||
<div className='flex items-center justify-center w-6 h-6 cursor-pointer' onClick={hidePreview}>
|
||||
<XMarkIcon className='h-4 w-4'></XMarkIcon>
|
||||
</div>
|
||||
</div>
|
||||
<div className='my-4 px-8 space-y-4'>
|
||||
{fileIndexingEstimate?.preview ? (
|
||||
<>
|
||||
{fileIndexingEstimate?.preview.map((item, index) => (
|
||||
<PreviewItem key={item} content={item} index={index + 1} />
|
||||
))}
|
||||
</>
|
||||
) : <div className='flex items-center justify-center h-[200px]'><Loading type='area'></Loading></div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
) :
|
||||
(<div className={cn(s.sideTip)}>
|
||||
<div className={s.tipCard}>
|
||||
<span className={s.icon} />
|
||||
<div className={s.title}>{t('datasetCreation.stepTwo.sideTipTitle')}</div>
|
||||
<div className={s.content}>
|
||||
<p className='mb-3'>{t('datasetCreation.stepTwo.sideTipP1')}</p>
|
||||
<p className='mb-3'>{t('datasetCreation.stepTwo.sideTipP2')}</p>
|
||||
<p className='mb-3'>{t('datasetCreation.stepTwo.sideTipP3')}</p>
|
||||
<p>{t('datasetCreation.stepTwo.sideTipP4')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default StepTwo
|
@@ -0,0 +1,49 @@
|
||||
'use client'
|
||||
import React, { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export interface IPreviewItemProps {
|
||||
index: number
|
||||
content: string
|
||||
}
|
||||
|
||||
const sharpIcon = (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.74999 1.5L3.24999 10.5M8.74998 1.5L7.24998 10.5M10.25 4H1.75M9.75 8H1.25" stroke="#98A2B3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const textIcon = (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 3.5H8M6 3.5V8.5M3.9 10.5H8.1C8.94008 10.5 9.36012 10.5 9.68099 10.3365C9.96323 10.1927 10.1927 9.96323 10.3365 9.68099C10.5 9.36012 10.5 8.94008 10.5 8.1V3.9C10.5 3.05992 10.5 2.63988 10.3365 2.31901C10.1927 2.03677 9.96323 1.8073 9.68099 1.66349C9.36012 1.5 8.94008 1.5 8.1 1.5H3.9C3.05992 1.5 2.63988 1.5 2.31901 1.66349C2.03677 1.8073 1.8073 2.03677 1.66349 2.31901C1.5 2.63988 1.5 3.05992 1.5 3.9V8.1C1.5 8.94008 1.5 9.36012 1.66349 9.68099C1.8073 9.96323 2.03677 10.1927 2.31901 10.3365C2.63988 10.5 3.05992 10.5 3.9 10.5Z" stroke="#667085" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
|
||||
)
|
||||
|
||||
const PreviewItem: FC<IPreviewItemProps> = ({
|
||||
index,
|
||||
content,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const charNums = (content || '').length
|
||||
const formatedIndex = (() => (index + '').padStart(3, '0'))()
|
||||
|
||||
return (
|
||||
<div className='p-4 rounded-xl bg-gray-50'>
|
||||
<div className='flex items-center justify-between h-5 text-xs text-gray-500'>
|
||||
<div className='flex items-center h-[18px] space-x-1 border border-gray-200 box-border rounded-md italic pl-1 pr-1.5 font-medium'>
|
||||
{sharpIcon}
|
||||
<span>{formatedIndex}</span>
|
||||
</div>
|
||||
<div className='flex items-center space-x-1'>
|
||||
{textIcon}
|
||||
<span>{charNums} {t('datasetCreation.stepTwo.characters')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-2 max-h-[120px] line-clamp-6 overflow-hidden text-sm text-gray-800'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(PreviewItem)
|
Reference in New Issue
Block a user