feat: economy hover effect & upload page

pull/12097/head
AkaraChen 1 year ago
parent f22c608c89
commit 3f6aee6c51

@ -1,6 +1,6 @@
'use client' 'use client'
import type { FC, PropsWithChildren } from 'react' import type { FC, PropsWithChildren } from 'react'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import { import {
@ -10,6 +10,7 @@ import {
} from '@remixicon/react' } from '@remixicon/react'
import Link from 'next/link' import Link from 'next/link'
import Image from 'next/image' import Image from 'next/image'
import { useHover } from 'ahooks'
import SettingCog from '../assets/setting-gear-mod.svg' import SettingCog from '../assets/setting-gear-mod.svg'
import OrangeEffect from '../assets/option-card-effect-orange.svg' import OrangeEffect from '../assets/option-card-effect-orange.svg'
import FamilyMod from '../assets/family-mod.svg' import FamilyMod from '../assets/family-mod.svg'
@ -59,6 +60,7 @@ import Badge from '@/app/components/base/badge'
import { SkeletonContanier, SkeletonPoint, SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton' import { SkeletonContanier, SkeletonPoint, SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import CustomDialog from '@/app/components/base/dialog' import CustomDialog from '@/app/components/base/dialog'
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
const TextLabel: FC<PropsWithChildren> = (props) => { const TextLabel: FC<PropsWithChildren> = (props) => {
return <label className='text-text-secondary text-xs font-semibold leading-none'>{props.children}</label> return <label className='text-text-secondary text-xs font-semibold leading-none'>{props.children}</label>
@ -559,6 +561,9 @@ const StepTwo = ({
score_threshold: 0.5, score_threshold: 0.5,
} as RetrievalConfig) } as RetrievalConfig)
const economyDomRef = useRef<HTMLDivElement>(null)
const isHoveringEconomy = useHover(economyDomRef)
return ( return (
<div className='flex w-full max-h-full h-full overflow-y-auto'> <div className='flex w-full max-h-full h-full overflow-y-auto'>
<div className='relative h-full w-full overflow-y-scroll'> <div className='relative h-full w-full overflow-y-scroll'>
@ -566,226 +571,231 @@ const StepTwo = ({
<div className={s.label}>{t('datasetCreation.stepTwo.segmentation')}</div> <div className={s.label}>{t('datasetCreation.stepTwo.segmentation')}</div>
<div className='max-w-[640px]'> <div className='max-w-[640px]'>
<div className='space-y-4'> <div className='space-y-4'>
<OptionCard {(!datasetId || [ChuckingMode.text, ChuckingMode.qa].includes(docForm))
title={t('datasetCreation.stepTwo.general')} && <OptionCard
icon={<Image src={SettingCog} alt={t('datasetCreation.stepTwo.general')} />} title={t('datasetCreation.stepTwo.general')}
activeHeaderClassName='bg-gradient-to-r from-[#EFF0F9] to-[#F9FAFB]' icon={<Image src={SettingCog} alt={t('datasetCreation.stepTwo.general')} />}
description={t('datasetCreation.stepTwo.generalTip')} activeHeaderClassName='bg-gradient-to-r from-[#EFF0F9] to-[#F9FAFB]'
isActive={ description={t('datasetCreation.stepTwo.generalTip')}
[ChuckingMode.text, ChuckingMode.qa].includes(docForm) isActive={
} [ChuckingMode.text, ChuckingMode.qa].includes(docForm)
onSwitched={() => }
handleChangeDocform(ChuckingMode.text) onSwitched={() =>
} handleChangeDocform(ChuckingMode.text)
actions={ }
<> actions={
<Button variant={'secondary-accent'} onClick={() => updatePreview()}> <>
<RiSearchEyeLine className='h-4 w-4 mr-1.5' /> <Button variant={'secondary-accent'} onClick={() => updatePreview()}>
{t('datasetCreation.stepTwo.previewChunk')} <RiSearchEyeLine className='h-4 w-4 mr-1.5' />
</Button> {t('datasetCreation.stepTwo.previewChunk')}
<Button variant={'ghost'} onClick={resetRules}> </Button>
{t('datasetCreation.stepTwo.reset')} <Button variant={'ghost'} onClick={resetRules}>
</Button> {t('datasetCreation.stepTwo.reset')}
</> </Button>
} </>
> }
<div className='space-y-4'> noHighlight={Boolean(datasetId)}
<div className='flex gap-3'> >
<DelimiterInput <div className='space-y-4'>
value={segmentIdentifier} <div className='flex gap-3'>
onChange={e => setSegmentIdentifier(e.target.value)} <DelimiterInput
/> value={segmentIdentifier}
<MaxLengthInput onChange={e => setSegmentIdentifier(e.target.value)}
value={maxChunkLength}
onChange={setMaxChunkLength}
/>
<OverlapInput
value={overlap}
min={1}
onChange={setOverlap}
/>
</div>
<div className='space-y-2'>
<div className='w-full flex flex-col'>
<TextLabel>{t('datasetCreation.stepTwo.rules')}</TextLabel>
<div className='mt-4 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>
</div>
{IS_CE_EDITION && <>
<div className='flex items-center'>
<Checkbox
checked={docForm === ChuckingMode.qa}
onCheck={() => {
if (docForm === ChuckingMode.qa)
handleChangeDocform(ChuckingMode.text)
else
handleChangeDocform(ChuckingMode.qa)
}}
className='mr-2'
/> />
<div className='flex items-center gap-1'> <MaxLengthInput
<TextLabel> value={maxChunkLength}
{t('datasetCreation.stepTwo.QALanguage')} onChange={setMaxChunkLength}
</TextLabel> />
<div className='z-50 relative'> <OverlapInput
<LanguageSelect value={overlap}
currentLanguage={docLanguage || locale} min={1}
onSelect={setDocLanguage} onChange={setOverlap}
disabled={isLanguageSelectDisabled} />
/> </div>
<div className='space-y-2'>
<div className='w-full flex flex-col'>
<TextLabel>{t('datasetCreation.stepTwo.rules')}</TextLabel>
<div className='mt-4 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>
<Tooltip popupContent={t('datasetCreation.stepTwo.QATip')} />
</div> </div>
</div> </div>
{docForm === ChuckingMode.qa && ( {IS_CE_EDITION && <>
<div <div className='flex items-center'>
style={{ <Checkbox
background: 'linear-gradient(92deg, rgba(247, 144, 9, 0.1) 0%, rgba(255, 255, 255, 0.00) 100%)', checked={docForm === ChuckingMode.qa}
}} onCheck={() => {
className='h-10 flex items-center gap-2 rounded-xl border-components-panel-border border shadow-shadow-shadow-3 px-3 text-xs' if (docForm === ChuckingMode.qa)
> handleChangeDocform(ChuckingMode.text)
<RiAlertFill className='size-4 text-text-warning-secondary' /> else
<span className='text-sm font-medium text-text-primary'> handleChangeDocform(ChuckingMode.qa)
{t('datasetCreation.stepTwo.QATip')} }}
</span> className='mr-2'
/>
<div className='flex items-center gap-1'>
<TextLabel>
{t('datasetCreation.stepTwo.QALanguage')}
</TextLabel>
<div className='z-50 relative'>
<LanguageSelect
currentLanguage={docLanguage || locale}
onSelect={setDocLanguage}
disabled={isLanguageSelectDisabled}
/>
</div>
<Tooltip popupContent={t('datasetCreation.stepTwo.QATip')} />
</div>
</div> </div>
)} {docForm === ChuckingMode.qa && (
</>} <div
</div> style={{
</OptionCard> background: 'linear-gradient(92deg, rgba(247, 144, 9, 0.1) 0%, rgba(255, 255, 255, 0.00) 100%)',
<OptionCard }}
title={t('datasetCreation.stepTwo.parentChild')} className='h-10 flex items-center gap-2 rounded-xl border-components-panel-border border shadow-shadow-shadow-3 px-3 text-xs'
icon={<Image src={FamilyMod} alt={t('datasetCreation.stepTwo.parentChild')} />} >
effectImg={OrangeEffect.src} <RiAlertFill className='size-4 text-text-warning-secondary' />
activeHeaderClassName='bg-gradient-to-r from-[#F9F1EE] to-[#F9FAFB]' <span className='text-sm font-medium text-text-primary'>
description={t('datasetCreation.stepTwo.parentChildTip')} {t('datasetCreation.stepTwo.QATip')}
isActive={docForm === ChuckingMode.parentChild} </span>
onSwitched={() => handleChangeDocform(ChuckingMode.parentChild)}
actions={
<>
<Button variant={'secondary-accent'} onClick={() => updatePreview()}>
<RiSearchEyeLine className='h-4 w-4 mr-1.5' />
{t('datasetCreation.stepTwo.previewChunk')}
</Button>
<Button variant={'ghost'} onClick={resetRules}>
{t('datasetCreation.stepTwo.reset')}
</Button>
</>
}
>
<div className='space-y-4'>
<div className='space-y-2'>
<TextLabel>
{t('datasetCreation.stepTwo.parentChunkForContext')}
</TextLabel>
<RadioCard
icon={<Image src={Note} alt='' />}
title={t('datasetCreation.stepTwo.paragraph')}
description={t('datasetCreation.stepTwo.paragraphTip')}
isChosen={parentChildConfig.chunkForContext === 'paragraph'}
onChosen={() => setParentChildConfig(
{
...parentChildConfig,
chunkForContext: 'paragraph',
},
)}
chosenConfig={
<div className='flex gap-2'>
<DelimiterInput
value={parentChildConfig.parent.delimiter}
onChange={e => setParentChildConfig({
...parentChildConfig,
parent: {
...parentChildConfig.parent,
delimiter: e.target.value,
},
})}
/>
<MaxLengthInput
value={parentChildConfig.parent.maxLength}
onChange={value => setParentChildConfig({
...parentChildConfig,
parent: {
...parentChildConfig.parent,
maxLength: value,
},
})}
/>
</div> </div>
}
/>
<RadioCard
icon={<Image src={FileList} alt='' />}
title={t('datasetCreation.stepTwo.fullDoc')}
description={t('datasetCreation.stepTwo.fullDocTip')}
onChosen={() => setParentChildConfig(
{
...parentChildConfig,
chunkForContext: 'full-doc',
},
)} )}
isChosen={parentChildConfig.chunkForContext === 'full-doc'} </>}
/>
</div> </div>
</OptionCard>}
{
(!datasetId || docForm === ChuckingMode.parentChild)
&& <OptionCard
title={t('datasetCreation.stepTwo.parentChild')}
icon={<Image src={FamilyMod} alt={t('datasetCreation.stepTwo.parentChild')} />}
effectImg={OrangeEffect.src}
activeHeaderClassName='bg-gradient-to-r from-[#F9F1EE] to-[#F9FAFB]'
description={t('datasetCreation.stepTwo.parentChildTip')}
isActive={docForm === ChuckingMode.parentChild}
onSwitched={() => handleChangeDocform(ChuckingMode.parentChild)}
actions={
<>
<Button variant={'secondary-accent'} onClick={() => updatePreview()}>
<RiSearchEyeLine className='h-4 w-4 mr-1.5' />
{t('datasetCreation.stepTwo.previewChunk')}
</Button>
<Button variant={'ghost'} onClick={resetRules}>
{t('datasetCreation.stepTwo.reset')}
</Button>
</>
}
noHighlight={Boolean(datasetId)}
>
<div className='space-y-4'> <div className='space-y-4'>
<TextLabel> <div className='space-y-2'>
{t('datasetCreation.stepTwo.childChunkForRetrieval')} <TextLabel>
</TextLabel> {t('datasetCreation.stepTwo.parentChunkForContext')}
<div className='flex gap-3 mt-2'> </TextLabel>
<DelimiterInput <RadioCard
value={parentChildConfig.child.delimiter} icon={<Image src={Note} alt='' />}
onChange={e => setParentChildConfig({ title={t('datasetCreation.stepTwo.paragraph')}
...parentChildConfig, description={t('datasetCreation.stepTwo.paragraphTip')}
child: { isChosen={parentChildConfig.chunkForContext === 'paragraph'}
...parentChildConfig.child, onChosen={() => setParentChildConfig(
delimiter: e.target.value, {
...parentChildConfig,
chunkForContext: 'paragraph',
}, },
})} )}
chosenConfig={
<div className='flex gap-2'>
<DelimiterInput
value={parentChildConfig.parent.delimiter}
onChange={e => setParentChildConfig({
...parentChildConfig,
parent: {
...parentChildConfig.parent,
delimiter: e.target.value,
},
})}
/>
<MaxLengthInput
value={parentChildConfig.parent.maxLength}
onChange={value => setParentChildConfig({
...parentChildConfig,
parent: {
...parentChildConfig.parent,
maxLength: value,
},
})}
/>
</div>
}
/> />
<MaxLengthInput <RadioCard
value={parentChildConfig.child.maxLength} icon={<Image src={FileList} alt='' />}
onChange={value => setParentChildConfig({ title={t('datasetCreation.stepTwo.fullDoc')}
...parentChildConfig, description={t('datasetCreation.stepTwo.fullDocTip')}
child: { onChosen={() => setParentChildConfig(
...parentChildConfig.child, {
maxLength: value, ...parentChildConfig,
chunkForContext: 'full-doc',
}, },
})} )}
isChosen={parentChildConfig.chunkForContext === 'full-doc'}
/> />
</div> </div>
<div className='space-y-2'> <div className='space-y-4'>
<TextLabel> <TextLabel>
{t('datasetCreation.stepTwo.rules')} {t('datasetCreation.stepTwo.childChunkForRetrieval')}
</TextLabel> </TextLabel>
<div className='space-y-2 mt-2'> <div className='flex gap-3 mt-2'>
{rules.map(rule => ( <DelimiterInput
<div key={rule.id} className={s.ruleItem} onClick={() => { value={parentChildConfig.child.delimiter}
ruleChangeHandle(rule.id) onChange={e => setParentChildConfig({
}}> ...parentChildConfig,
<Checkbox child: {
checked={rule.enabled} ...parentChildConfig.child,
/> delimiter: e.target.value,
<label className="ml-2 text-sm font-normal cursor-pointer text-gray-800">{getRuleName(rule.id)}</label> },
</div> })}
))} />
<MaxLengthInput
value={parentChildConfig.child.maxLength}
onChange={value => setParentChildConfig({
...parentChildConfig,
child: {
...parentChildConfig.child,
maxLength: value,
},
})}
/>
</div>
<div className='space-y-2'>
<TextLabel>
{t('datasetCreation.stepTwo.rules')}
</TextLabel>
<div className='space-y-2 mt-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> </div>
</div> </div>
</div> </div>
</div> </OptionCard>}
</OptionCard>
</div> </div>
</div> </div>
<Divider className='my-5' /> <Divider className='my-5' />
@ -828,50 +838,69 @@ const StepTwo = ({
)} )}
{(!hasSetIndexType || (hasSetIndexType && indexingType === IndexingType.ECONOMICAL)) && ( {(!hasSetIndexType || (hasSetIndexType && indexingType === IndexingType.ECONOMICAL)) && (
<div <PortalToFollowElem
className={cn( open={
s.radioItem, isHoveringEconomy && docForm !== ChuckingMode.text
s.indexItem, }
!hasSetIndexType && indexType === IndexingType.ECONOMICAL && s.active, placement={'top'}
hasSetIndexType && s.disabled,
hasSetIndexType && '!w-full !min-h-[96px]',
docForm !== ChuckingMode.text && s.disabled,
)}
onClick={changeToEconomicalType}
> >
<CustomDialog show={isQAConfirmDialogOpen} onClose={() => setIsQAConfirmDialogOpen(false)} className='w-[432px]'> <PortalToFollowElemTrigger>
<header className='pt-6 mb-4'> <div
<h2 className='text-lg font-semibold'> className={cn(
{t('datasetCreation.stepTwo.qaSwitchHighQualityTipTitle')} s.radioItem,
</h2> s.indexItem,
<p className='font-normal text-sm mt-2'> !hasSetIndexType && indexType === IndexingType.ECONOMICAL && s.active,
{t('datasetCreation.stepTwo.qaSwitchHighQualityTipContent')} hasSetIndexType && s.disabled,
</p> hasSetIndexType && '!w-full !min-h-[96px]',
</header> docForm !== ChuckingMode.text && s.disabled,
<div className='flex gap-2 pb-6'> )}
<Button className='ml-auto' onClick={() => { onClick={changeToEconomicalType}
setIsQAConfirmDialogOpen(false) ref={economyDomRef}
}}> >
{t('datasetCreation.stepTwo.cancel')} <CustomDialog show={isQAConfirmDialogOpen} onClose={() => setIsQAConfirmDialogOpen(false)} className='w-[432px]'>
</Button> <header className='pt-6 mb-4'>
<Button variant={'primary'} onClick={() => { <h2 className='text-lg font-semibold'>
setIsQAConfirmDialogOpen(false) {t('datasetCreation.stepTwo.qaSwitchHighQualityTipTitle')}
setIndexType(IndexingType.QUALIFIED) </h2>
setDocForm(ChuckingMode.qa) <p className='font-normal text-sm mt-2'>
}}> {t('datasetCreation.stepTwo.qaSwitchHighQualityTipContent')}
{t('datasetCreation.stepTwo.switch')} </p>
</Button> </header>
<div className='flex gap-2 pb-6'>
<Button className='ml-auto' onClick={() => {
setIsQAConfirmDialogOpen(false)
}}>
{t('datasetCreation.stepTwo.cancel')}
</Button>
<Button variant={'primary'} onClick={() => {
setIsQAConfirmDialogOpen(false)
setIndexType(IndexingType.QUALIFIED)
setDocForm(ChuckingMode.qa)
}}>
{t('datasetCreation.stepTwo.switch')}
</Button>
</div>
</CustomDialog>
<div className='h-8 p-1.5 bg-white rounded-lg border border-components-panel-border-subtle justify-center items-center inline-flex absolute left-5 top-[18px]'>
<Image src={indexMethodIcon.economical} 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>
<div className={s.tip}>{t('datasetCreation.stepTwo.economicalTip')}</div>
</div>
</div> </div>
</CustomDialog> </PortalToFollowElemTrigger>
<div className='h-8 p-1.5 bg-white rounded-lg border border-components-panel-border-subtle justify-center items-center inline-flex absolute left-5 top-[18px]'> <PortalToFollowElemContent>
<Image src={indexMethodIcon.economical} alt='Economical Icon' width={20} height={20} /> <div className='p-3 bg-white text-xs font-medium text-text-secondary rounded-lg shadow-lg'>
</div> {
{!hasSetIndexType && <span className={cn(s.radio)} />} docForm === ChuckingMode.qa
<div className={s.typeHeader}> ? t('datasetCreation.stepTwo.notAvailableForQA')
<div className={s.title}>{t('datasetCreation.stepTwo.economical')}</div> : t('datasetCreation.stepTwo.notAvailableForParentChild')
<div className={s.tip}>{t('datasetCreation.stepTwo.economicalTip')}</div> }
</div> </div>
</div> </PortalToFollowElemContent>
</PortalToFollowElem>
)} )}
</div> </div>
{hasSetIndexType && indexType === IndexingType.ECONOMICAL && ( {hasSetIndexType && indexType === IndexingType.ECONOMICAL && (

@ -51,14 +51,15 @@ type OptionCardProps = {
actions?: ReactNode actions?: ReactNode
effectImg?: string effectImg?: string
onSwitched?: () => void onSwitched?: () => void
noHighlight?: boolean
} & Omit<ComponentProps<'div'>, 'title'> } & Omit<ComponentProps<'div'>, 'title'>
export const OptionCard: FC<OptionCardProps> = (props) => { export const OptionCard: FC<OptionCardProps> = (props) => {
const { icon, className, title, description, isActive, children, actions, activeHeaderClassName, style, effectImg, onSwitched, onClick, ...rest } = props const { icon, className, title, description, isActive, children, actions, activeHeaderClassName, style, effectImg, onSwitched, onClick, noHighlight, ...rest } = props
return <div return <div
className={classNames( className={classNames(
'rounded-xl', 'rounded-xl',
isActive ? 'border-components-option-card-option-selected-border bg-components-panel-bg' : 'border-components-option-card-option-border bg-components-option-card-option-bg', (isActive && !noHighlight) ? 'border-components-option-card-option-selected-border bg-components-panel-bg' : 'border-components-option-card-option-border bg-components-option-card-option-bg',
className, className,
)} )}
style={{ style={{
@ -75,7 +76,7 @@ export const OptionCard: FC<OptionCardProps> = (props) => {
icon={icon} icon={icon}
title={title} title={title}
description={description} description={description}
isActive={isActive} isActive={isActive && !noHighlight}
activeClassName={activeHeaderClassName} activeClassName={activeHeaderClassName}
effectImg={effectImg} effectImg={effectImg}
/> />

@ -169,6 +169,8 @@ const translation = {
switch: 'Switch', switch: 'Switch',
qaSwitchHighQualityTipTitle: 'Q&A Format Requires High-quality Indexing Method', qaSwitchHighQualityTipTitle: 'Q&A Format Requires High-quality Indexing Method',
qaSwitchHighQualityTipContent: 'Currently, only high-quality index method supports Q&A format chunking. Would you like to switch to high-quality mode?', qaSwitchHighQualityTipContent: 'Currently, only high-quality index method supports Q&A format chunking. Would you like to switch to high-quality mode?',
notAvailableForParentChild: 'Not available for Parent-child Index',
notAvailableForQA: 'Not available for Q&A Index',
}, },
stepThree: { stepThree: {
creationTitle: '🎉 Knowledge created', creationTitle: '🎉 Knowledge created',

@ -169,6 +169,8 @@ const translation = {
switch: '切换', switch: '切换',
qaSwitchHighQualityTipTitle: 'Q&A 格式需要高质量的索引方法', qaSwitchHighQualityTipTitle: 'Q&A 格式需要高质量的索引方法',
qaSwitchHighQualityTipContent: '目前,只有高质量的索引方法支持 Q&A 格式分块。您要切换到高质量模式吗?', qaSwitchHighQualityTipContent: '目前,只有高质量的索引方法支持 Q&A 格式分块。您要切换到高质量模式吗?',
notAvailableForParentChild: '不支持父子索引',
notAvailableForQA: '不支持 Q&A 索引',
}, },
stepThree: { stepThree: {
creationTitle: '🎉 知识库已创建', creationTitle: '🎉 知识库已创建',

Loading…
Cancel
Save