mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 09:28:04 +08:00
feat: most ui for create datasets
chore: upd
This commit is contained in:
@ -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'>
|
||||
|
||||
@ -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}
|
||||
|
||||
Reference in New Issue
Block a user