feat: most ui for create datasets

chore: upd
This commit is contained in:
AkaraChen
2024-11-20 14:55:59 +08:00
parent ca4d0fb4cc
commit 27ece2fb52
14 changed files with 237 additions and 89 deletions

View File

@ -1,20 +1,25 @@
'use client'
import type { ComponentProps, FC, PropsWithChildren, ReactNode } from 'react'
import type { FC, PropsWithChildren, ReactNode } from 'react'
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { useBoolean } from 'ahooks'
import { MagnifyingGlassCircleIcon, XMarkIcon } from '@heroicons/react/20/solid'
import { XMarkIcon } from '@heroicons/react/20/solid'
import { RocketLaunchIcon } from '@heroicons/react/24/outline'
import {
RiCloseLine,
RiSearchEyeLine,
} from '@remixicon/react'
import Link from 'next/link'
import { groupBy } from 'lodash-es'
import Image from 'next/image'
import SettingCog from '../assets/setting-gear-mod.svg'
import OrangeEffect from '../assets/option-card-effect-orange.svg'
import FamilyMod from '../assets/family-mod.svg'
import GoldIcon from '../assets/gold.svg'
import Piggybank from '../assets/piggy-bank-mod.svg'
import Note from '../assets/note-mod.svg'
import FileList from '../assets/file-list-3-fill.svg'
import PreviewItem, { PreviewType } from './preview-item'
import LanguageSelect from './language-select'
import s from './index.module.css'
import unescape from './unescape'
import escape from './escape'
@ -39,11 +44,8 @@ import Toast from '@/app/components/base/toast'
import type { NotionPage } from '@/models/common'
import { DataSourceProvider } from '@/models/common'
import { DataSourceType, DocForm } from '@/models/datasets'
import Switch from '@/app/components/base/switch'
import { MessageChatSquare } from '@/app/components/base/icons/src/public/common'
import { useDatasetDetailContext } from '@/context/dataset-detail'
import I18n from '@/context/i18n'
import { IS_CE_EDITION } from '@/config'
import { RETRIEVE_METHOD } from '@/types/app'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import Tooltip from '@/app/components/base/tooltip'
@ -53,6 +55,18 @@ import ModelSelector from '@/app/components/header/account-setting/model-provide
import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import Checkbox from '@/app/components/base/checkbox'
import RadioCard from '@/app/components/base/radio-card'
const TextLabel: FC<PropsWithChildren> = (props) => {
return <label className='text-[#354052] text-xs font-semibold leading-none'>{props.children}</label>
}
const FormField: FC<PropsWithChildren<{ label: ReactNode }>> = (props) => {
return <div className='space-y-2 flex-1'>
<TextLabel>{props.label}</TextLabel>
{props.children}
</div>
}
type ValueOf<T> = T[keyof T]
type StepTwoProps = {
@ -579,26 +593,6 @@ const StepTwo = ({
}
}, [segmentationType, indexType])
const Label: FC<PropsWithChildren> = (props) => {
return <label className='text-[#354052] text-xs font-semibold leading-none'>{props.children}</label>
}
const FormItem: FC<PropsWithChildren<{ label: ReactNode }>> = (props) => {
return <div className='space-y-2 flex-1'>
<Label>{props.label}</Label>
{props.children}
</div>
}
const CheckboxWithLabel: FC<PropsWithChildren<ComponentProps<typeof Checkbox> & {
label: string
}>> = (props) => {
return <div className='flex items-center gap-2'>
<Checkbox />
<Label>{props.label}</Label>
</div>
}
const [retrievalConfig, setRetrievalConfig] = useState(currentDataset?.retrieval_model_dict || {
search_method: RETRIEVE_METHOD.semantic,
reranking_enable: false,
@ -637,14 +631,14 @@ const StepTwo = ({
<OptionCard
title={'General'}
icon={<Image src={SettingCog} alt='General' />}
activeHeaderClassName='bg-gradient-to-r from-blue-50/40 to-[#ffffff]'
activeHeaderClassName='bg-gradient-to-r from-[#EFF0F9] to-[#F9FAFB]'
description={'General text chunking mode, the chunks retrieved and recalled are the same.'}
isActive={SegmentType.AUTO === segmentationType}
onClick={() => setSegmentationType(SegmentType.AUTO)}
actions={
<>
<Button variant={'secondary-accent'}>
<MagnifyingGlassCircleIcon className='size-4 mr-2' />
<RiSearchEyeLine className='h-4 w-4 mr-1.5' />
Preview Chunk
</Button>
<Button variant={'ghost'} disabled>Reset</Button>
@ -653,7 +647,7 @@ const StepTwo = ({
>
<div className='space-y-4'>
<div className='flex gap-2'>
<FormItem label={<div className='flex'>
<FormField label={<div className='flex'>
{t('datasetCreation.stepTwo.separator')}
<Tooltip
popupContent={
@ -669,8 +663,8 @@ const StepTwo = ({
placeholder={t('datasetCreation.stepTwo.separatorPlaceholder') || ''} value={segmentIdentifier}
onChange={e => setSegmentIdentifier(e.target.value)}
/>
</FormItem>
<FormItem label={<div>
</FormField>
<FormField label={<div>
{t('datasetCreation.stepTwo.maxLength')}
</div>}>
<Input
@ -682,8 +676,8 @@ const StepTwo = ({
min={1}
onChange={e => setMax(parseInt(e.target.value.replace(/^0+/, ''), 10))}
/>
</FormItem>
<FormItem label={<div className='flex'>
</FormField>
<FormField label={<div className='flex'>
{t('datasetCreation.stepTwo.overlap')}
<Tooltip
popupContent={
@ -700,11 +694,11 @@ const StepTwo = ({
value={overlap}
min={1}
onChange={e => setOverlap(parseInt(e.target.value.replace(/^0+/, ''), 10))} />
</FormItem>
</FormField>
</div>
<div className='space-y-2'>
<div className='w-full flex flex-col'>
<Label>{t('datasetCreation.stepTwo.rules')}</Label>
<TextLabel>{t('datasetCreation.stepTwo.rules')}</TextLabel>
<div className='mt-4 space-y-2'>
{rules.map(rule => (
<div key={rule.id} className={s.ruleItem} onClick={() => {
@ -723,16 +717,76 @@ const StepTwo = ({
</OptionCard>
<OptionCard
title={'Parent-child'}
icon={undefined}
activeHeaderClassName='bg-gradient-to-r from-red-50/40 to-[#ffffff]'
icon={<Image src={FamilyMod} alt='Parent-child' />}
effectImg={OrangeEffect.src}
activeHeaderClassName='bg-gradient-to-r from-[#F9F1EE] to-[#F9FAFB]'
description={'When using the parent-child mode, the child-chunk is used for retrieval and the parent-chunk is used for recall as context.'}
isActive={SegmentType.CUSTOM === segmentationType}
onClick={() => setSegmentationType(SegmentType.CUSTOM)}
actions={
<>
<Button variant={'secondary-accent'}>
<RiSearchEyeLine className='h-4 w-4 mr-1.5' />
Preview Chunk
</Button>
<Button variant={'ghost'} onClick={resetRules}>Reset</Button>
</>
}
>
<div className='space-y-4'>
<Label>
Parent-chunk for Context
</Label>
<TextLabel>
Parent-chunk for Context
</TextLabel>
<RadioCard
icon={<Image src={Note} alt='' />}
title={'Paragraph'}
description={'This mode splits the text in to paragraphs based on delimiters and the maximum chunk length, using the split text as the parent chunk for retrieval.'}
isChosen={true}
chosenConfig={
<div className='flex gap-2'>
<FormField label={'Delimiter'}>
<Input type="text" placeholder={'\n\n'} value={segmentIdentifier} onChange={e => setSegmentIdentifier(e.target.value)} />
</FormField>
<FormField label={'Maximum chunk length'}>
<Input type="number" placeholder={'\n\n'} value={segmentIdentifier} onChange={e => setSegmentIdentifier(e.target.value)} />
</FormField>
</div>
}
/>
<RadioCard
icon={<Image src={FileList} alt='' />}
title={'Full Doc'}
description={'The entire document is used as the parent chunk and retrieved directly. Please note that for performance reasons, text exceeding 10000 tokens will be automatically truncated.'}
isChosen={true}
/>
<TextLabel>
Child-chunk for Retrieval
</TextLabel>
<div className='flex gap-2'>
<FormField label={'Delimiter'}>
<Input type="text" placeholder={'\n'} value={segmentIdentifier} onChange={e => setSegmentIdentifier(e.target.value)} />
</FormField>
<FormField label={'Maximum chunk length'}>
<Input type="number" placeholder={'\n'} value={segmentIdentifier} onChange={e => setSegmentIdentifier(e.target.value)} />
</FormField>
</div>
<TextLabel>
Text Pre-processing Rules
</TextLabel>
<div className='space-y-2'>
{rules.map(rule => (
<div key={rule.id} className={s.ruleItem} onClick={() => {
ruleChangeHandle(rule.id)
}}>
<Checkbox
checked={rule.enabled}
/>
<label className="ml-2 text-sm font-normal cursor-pointer text-gray-800">{getRuleName(rule.id)}</label>
</div>
))}
</div>
</div>
</OptionCard>
</div>
@ -755,7 +809,9 @@ const StepTwo = ({
setIndexType(IndexingType.QUALIFIED)
}}
>
<span className={cn(s.typeIcon, s.qualified)} />
<div className='h-8 p-1.5 bg-white rounded-lg border border-[#101828]/10 justify-center items-center inline-flex absolute left-5 top-[18px]'>
<Image src={GoldIcon} alt='Gold Icon' width={20} height={20} />
</div>
{!hasSetIndexType && <span className={cn(s.radio)} />}
<div className={s.typeHeader}>
<div className={s.title}>
@ -784,7 +840,9 @@ const StepTwo = ({
)}
onClick={changeToEconomicalType}
>
<span className={cn(s.typeIcon, s.economical)} />
<div className='h-8 p-1.5 bg-white rounded-lg border border-[#101828]/10 justify-center items-center inline-flex absolute left-5 top-[18px]'>
<Image src={Piggybank} alt='Economical Icon' width={20} height={20} />
</div>
{!hasSetIndexType && <span className={cn(s.radio)} />}
<div className={s.typeHeader}>
<div className={s.title}>{t('datasetCreation.stepTwo.economical')}</div>
@ -799,35 +857,6 @@ const StepTwo = ({
<Link className='text-[#155EEF]' href={`/datasets/${datasetId}/settings`}>{t('datasetCreation.stepTwo.datasetSettingLink')}</Link>
</div>
)}
{IS_CE_EDITION && indexType === IndexingType.QUALIFIED && (
<div className='mt-3 rounded-xl bg-gray-50 border border-gray-100'>
<div className='flex justify-between items-center px-5 py-4'>
<div className='flex justify-center items-center w-8 h-8 rounded-lg bg-indigo-50'>
<MessageChatSquare className='w-4 h-4' />
</div>
<div className='grow mx-3'>
<div className='mb-[2px] text-md font-medium text-gray-900'>{t('datasetCreation.stepTwo.QATitle')}</div>
<div className='inline-flex items-center text-[13px] leading-[18px] text-gray-500'>
<span className='pr-1'>{t('datasetCreation.stepTwo.QALanguage')}</span>
<LanguageSelect currentLanguage={docLanguage} onSelect={handleSelect} disabled={isLanguageSelectDisabled} />
</div>
</div>
<div className='shrink-0'>
<Switch
defaultValue={docForm === DocForm.QA}
onChange={handleSwitch}
size='md'
/>
</div>
</div>
{docForm === DocForm.QA && !QATipHide && (
<div className='flex justify-between items-center px-5 py-2 bg-orange-50 border-t border-amber-100 rounded-b-xl text-[13px] leading-[18px] text-medium text-amber-500'>
{t('datasetCreation.stepTwo.QATip')}
<RiCloseLine className='w-4 h-4 text-gray-500 cursor-pointer' onClick={() => setQATipHide(true)} />
</div>
)}
</div>
)}
{/* Embedding model */}
{indexType === IndexingType.QUALIFIED && (
<div className='mb-2'>

View File

@ -1,10 +1,11 @@
import { type ComponentProps, type FC, type ReactNode } from 'react'
import Image from 'next/image'
import piggyBank from '../assets/piggy-bank-01.svg'
import Effect from '../assets/option-card-effect-blue.svg'
import classNames from '@/utils/classnames'
const TriangleArrow = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="11" viewBox="0 0 24 11" fill="none">
const TriangleArrow: FC<ComponentProps<'svg'>> = props => (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="11" viewBox="0 0 24 11" fill="none" {...props}>
<path d="M9.87868 1.12132C11.0503 -0.0502525 12.9497 -0.0502525 14.1213 1.12132L23.3137 10.3137H0.686292L9.87868 1.12132Z" fill="white"/>
</svg>
)
@ -15,18 +16,25 @@ type OptionCardHeaderProps = {
description: string
isActive?: boolean
activeClassName?: string
effectImg?: string
}
export const OptionCardHeader: FC<OptionCardHeaderProps> = (props) => {
const { icon, title, description, isActive, activeClassName } = props
const { icon, title, description, isActive, activeClassName, effectImg } = props
return <div className={classNames(
'h-20 flex py-3 px-4 items-center gap-4',
'flex h-full overflow-hidden relative',
isActive && activeClassName,
)}>
<div className='size-8 rounded-lg border p-1.5 shadow border-[#101828]/10 justify-center flex'>
{icon || <Image src={piggyBank.src} className='size-5' alt={description} width={20} height={20} />}
<div className='size-14 flex items-center justify-center relative overflow-hidden'>
{isActive && <Image src={effectImg || Effect.src} className='absolute top-0 left-0 w-full h-full' alt='' width={56} height={56} />}
<div className='size-8 rounded-lg border p-1.5 shadow border-[#101828]/10 justify-center flex bg-white'>
{icon || <Image src={piggyBank.src} className='size-5' alt={description} width={20} height={20} />}
</div>
</div>
<div className='flex-1 space-y-1'>
<TriangleArrow
className='absolute left-4 -bottom-1.5'
/>
<div className='flex-1 space-y-1 py-3'>
<div className='text-[#354052] text-sm font-semibold leading-tight'>{title}</div>
<div className='text-[#676f83] text-xs font-normal leading-none'>{description}</div>
</div>
@ -41,10 +49,11 @@ type OptionCardProps = {
description: string
isActive?: boolean
actions?: ReactNode
effectImg?: string
} & Omit<ComponentProps<'div'>, 'title'>
export const OptionCard: FC<OptionCardProps> = (props) => {
const { icon, className, title, description, isActive, children, actions, activeHeaderClassName, style, ...rest } = props
const { icon, className, title, description, isActive, children, actions, activeHeaderClassName, style, effectImg, ...rest } = props
return <div
className={classNames(
'rounded-xl overflow-hidden',
@ -62,6 +71,7 @@ export const OptionCard: FC<OptionCardProps> = (props) => {
description={description}
isActive={isActive}
activeClassName={activeHeaderClassName}
effectImg={effectImg}
/>
{/** Body */}
{isActive && <div className='p-3'>{children}