@ -1,5 +1,5 @@
'use client'
import { use State } from 'react'
import { use Callback, useRef , use State } from 'react'
import { useMount } from 'ahooks'
import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next'
@ -11,16 +11,16 @@ import RetrievalSettings from '../../external-knowledge-base/create/RetrievalSet
import { IndexingType } from '../../create/step-two'
import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
import { ToastContext } from '@/app/components/base/toast'
import Button from '@/app/components/base/button'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
import { updateDatasetSetting } from '@/service/datasets'
import type { IconInfo } from '@/models/datasets'
import { type DataSetListResponse , DatasetPermission } from '@/models/datasets'
import DatasetDetailContext from '@/context/dataset-detail'
import type { RetrievalConfig } from '@/types/app'
import { use AppContext } from '@/context/app-context'
import type { AppIconType, RetrievalConfig } from '@/types/app'
import { use Selector as use AppContextWithSelector } from '@/context/app-context'
import { isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
import {
@ -31,12 +31,16 @@ import type { DefaultModel } from '@/app/components/header/account-setting/model
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { fetchMembers } from '@/service/common'
import type { Member } from '@/models/common'
import AlertTriangle from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertTriangle'
import AppIcon from '@/app/components/base/app-icon'
import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
import AppIconPicker from '@/app/components/base/app-icon-picker'
import Divider from '@/app/components/base/divider'
import ChunkStructure from '../chunk-structure'
import Toast from '@/app/components/base/toast'
import { RiAlertFill } from '@remixicon/react'
const rowClass = 'flex'
const labelClass = `
flex items - center shrink - 0 w - [ 180 px ] h - 9
`
const labelClass = 'flex items-center shrink-0 w-[180px] h-7 pt-1'
const getKey = ( pageIndex : number , previousPageData : DataSetListResponse ) = > {
if ( ! pageIndex || previousPageData . has_more )
@ -44,16 +48,25 @@ const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => {
return null
}
const DEFAULT_APP_ICON : IconInfo = {
icon_type : 'emoji' ,
icon : '📙' ,
icon_background : '#FFF4ED' ,
icon_url : '' ,
}
const Form = ( ) = > {
const { t } = useTranslation ( )
const { notify } = useContext ( ToastContext )
const { mutate } = useSWRConfig ( )
const { isCurrentWorkspaceDatasetOperator } = useAppContext ( )
const isCurrentWorkspaceDatasetOperator = useAppContext WithSelector ( state = > state . isCurrentWorkspaceDatasetOperator )
const { dataset : currentDataset , mutateDatasetRes : mutateDatasets } = useContext ( DatasetDetailContext )
const [ loading , setLoading ] = useState ( false )
const [ name , setName ] = useState ( currentDataset ? . name ? ? '' )
const [ iconInfo , setIconInfo ] = useState ( currentDataset ? . icon_info || DEFAULT_APP_ICON )
const [ showAppIconPicker , setShowAppIconPicker ] = useState ( false )
const [ description , setDescription ] = useState ( currentDataset ? . description ? ? '' )
const [ permission , setPermission ] = useState ( currentDataset ? . permission )
const [ chunkStructure , setChunkStructure ] = useState ( currentDataset ? . doc_form )
const [ topK , setTopK ] = useState ( currentDataset ? . external_retrieval_model . top_k ? ? 2 )
const [ scoreThreshold , setScoreThreshold ] = useState ( currentDataset ? . external_retrieval_model . score_threshold ? ? 0.5 )
const [ scoreThresholdEnabled , setScoreThresholdEnabled ] = useState ( currentDataset ? . external_retrieval_model . score_threshold_enabled ? ? false )
@ -76,6 +89,7 @@ const Form = () => {
modelList : rerankModelList ,
} = useModelListAndDefaultModelAndCurrentProviderAndModel ( ModelTypeEnum . rerank )
const { data : embeddingModelList } = useModelList ( ModelTypeEnum . textEmbedding )
const previousAppIcon = useRef ( DEFAULT_APP_ICON )
const getMembers = async ( ) = > {
const { accounts } = await fetchMembers ( { url : '/workspaces/current/members' , params : { } } )
@ -85,14 +99,35 @@ const Form = () => {
setMemberList ( accounts )
}
const handleSettingsChange = ( data : { top_k? : number ; score_threshold? : number ; score_threshold_enabled? : boolean } ) = > {
const handleOpenAppIconPicker = useCallback ( ( ) = > {
setShowAppIconPicker ( true )
previousAppIcon . current = iconInfo
} , [ iconInfo ] )
const handleSelectAppIcon = useCallback ( ( icon : AppIconSelection ) = > {
const iconInfo : IconInfo = {
icon_type : icon.type ,
icon : icon.type === 'emoji' ? icon.icon : icon.fileId ,
icon_background : icon.type === 'emoji' ? icon.background : undefined ,
icon_url : icon.type === 'emoji' ? undefined : icon . url ,
}
setIconInfo ( iconInfo )
setShowAppIconPicker ( false )
} , [ ] )
const handleCloseAppIconPicker = useCallback ( ( ) = > {
setIconInfo ( previousAppIcon . current )
setShowAppIconPicker ( false )
} , [ ] )
const handleSettingsChange = useCallback ( ( data : { top_k? : number ; score_threshold? : number ; score_threshold_enabled? : boolean } ) = > {
if ( data . top_k !== undefined )
setTopK ( data . top_k )
if ( data . score_threshold !== undefined )
setScoreThreshold ( data . score_threshold )
if ( data . score_threshold_enabled !== undefined )
setScoreThresholdEnabled ( data . score_threshold_enabled )
}
} , [ ] )
useMount ( ( ) = > {
getMembers ( )
@ -102,7 +137,7 @@ const Form = () => {
if ( loading )
return
if ( ! name ? . trim ( ) ) {
notify( { type : 'error' , message : t ( 'datasetSettings.form.nameError' ) } )
Toast. notify( { type : 'error' , message : t ( 'datasetSettings.form.nameError' ) } )
return
}
if (
@ -112,7 +147,7 @@ const Form = () => {
indexMethod ,
} )
) {
notify( { type : 'error' , message : t ( 'appDebug.datasetConfig.rerankModelRequired' ) } )
Toast. notify( { type : 'error' , message : t ( 'appDebug.datasetConfig.rerankModelRequired' ) } )
return
}
if ( retrievalConfig . weights ) {
@ -125,6 +160,8 @@ const Form = () => {
datasetId : currentDataset ! . id ,
body : {
name ,
icon_info : iconInfo ,
doc_form : chunkStructure ,
description ,
permission ,
indexing_technique : indexMethod ,
@ -154,14 +191,14 @@ const Form = () => {
} )
}
await updateDatasetSetting ( requestParams )
notify( { type : 'success' , message : t ( 'common.actionMsg.modifiedSuccessfully' ) } )
Toast. notify( { type : 'success' , message : t ( 'common.actionMsg.modifiedSuccessfully' ) } )
if ( mutateDatasets ) {
await mutateDatasets ( )
mutate ( unstable_serialize ( getKey ) )
}
}
catch {
notify( { type : 'error' , message : t ( 'common.actionMsg.modifiedUnsuccessfully' ) } )
Toast. notify( { type : 'error' , message : t ( 'common.actionMsg.modifiedUnsuccessfully' ) } )
}
finally {
setLoading ( false )
@ -169,20 +206,31 @@ const Form = () => {
}
return (
< div className = 'flex w-full flex-col gap-y-4 px-14 py-8 sm:w-[880px]' >
< div className = 'flex w-full flex-col gap-y-4 px-20 py-8 sm:w-[960px]' >
{ /* Dataset name and icon */ }
< div className = { rowClass } >
< div className = { labelClass } >
< div className = 'system-sm-semibold text-text-secondary' > { t ( 'datasetSettings.form.name' ) } < / div >
< / div >
< div className = 'grow' >
< div className = 'system-sm-semibold text-text-secondary' > { t ( 'datasetSettings.form.nameAndIcon' ) } < / div >
< / div >
< div className = 'flex grow items-center gap-x-2' >
< AppIcon
size = 'small'
onClick = { handleOpenAppIconPicker }
className = 'cursor-pointer'
iconType = { iconInfo . icon_type as AppIconType }
icon = { iconInfo . icon }
background = { iconInfo . icon_background }
imageUrl = { iconInfo . icon_url }
showEditIcon
/ >
< Input
disabled = { ! currentDataset ? . embedding_available }
className = 'h-9'
value = { name }
onChange = { e = > setName ( e . target . value ) }
/ >
< / div >
< / div >
{ /* Dataset description */ }
< div className = { rowClass } >
< div className = { labelClass } >
< div className = 'system-sm-semibold text-text-secondary' > { t ( 'datasetSettings.form.desc' ) } < / div >
@ -190,13 +238,14 @@ const Form = () => {
< div className = 'grow' >
< Textarea
disabled = { ! currentDataset ? . embedding_available }
className = ' h-[120px] resize-none'
className = ' resize-none'
placeholder = { t ( 'datasetSettings.form.descPlaceholder' ) || '' }
value = { description }
onChange = { e = > setDescription ( e . target . value ) }
/ >
< / div >
< / div >
{ /* Permissions */ }
< div className = { rowClass } >
< div className = { labelClass } >
< div className = 'system-sm-semibold text-text-secondary' > { t ( 'datasetSettings.form.permissions' ) } < / div >
@ -212,9 +261,43 @@ const Form = () => {
/ >
< / div >
< / div >
< Divider
type = 'horizontal'
className = 'my-1 h-px bg-divider-subtle'
/ >
{ /* Chunk Structure */ }
< div className = { rowClass } >
< div >
< div className = 'flex w-[180px] shrink-0 flex-col' >
< div className = 'system-sm-semibold flex h-8 items-center text-text-secondary' >
{ t ( 'datasetSettings.form.chunkStructure.title' ) }
< / div >
< div className = 'body-xs-regular text-text-tertiary' >
< a
target = '_blank'
rel = 'noopener noreferrer'
href = 'https://example.com' // todo: replace link
className = 'text-text-accent'
>
{ t ( 'datasetSettings.form.chunkStructure.learnMore' ) }
< / a >
{ t ( 'datasetSettings.form.chunkStructure.description' ) }
< / div >
< / div >
< / div >
< div className = 'grow' >
< ChunkStructure
chunkStructure = { chunkStructure ! }
onChunkStructureChange = { setChunkStructure }
/ >
< / div >
< / div >
{ currentDataset && currentDataset . indexing_technique && (
< >
< div className = 'my-1 h-0 w-full border-b border-divider-subtle' / >
< Divider
type = 'horizontal'
className = 'my-1 h-px bg-divider-subtle'
/ >
< div className = { rowClass } >
< div className = { labelClass } >
< div className = 'system-sm-semibold text-text-secondary' > { t ( 'datasetSettings.form.indexMethod' ) } < / div >
@ -227,13 +310,17 @@ const Form = () => {
docForm = { currentDataset . doc_form }
currentValue = { currentDataset . indexing_technique }
/ >
{ currentDataset . indexing_technique === IndexingType . ECONOMICAL && indexMethod === IndexingType . QUALIFIED && < div className = 'mt-2 flex h-10 items-center gap-x-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-xs backdrop-blur-[5px]' >
< div className = 'absolute bottom-0 left-0 right-0 top-0 bg-[linear-gradient(92deg,rgba(247,144,9,0.25)_0%,rgba(255,255,255,0.00)_100%)] opacity-40' > < / div >
{ currentDataset . indexing_technique === IndexingType . ECONOMICAL && indexMethod === IndexingType . QUALIFIED && (
< div className = 'mt-2 flex h-10 items-center gap-x-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur px-2 shadow-xs shadow-shadow-shadow-3' >
< div className = 'flex items-center bg-toast-warning-bg' / >
< div className = 'p-1' >
< AlertTriangle className = 'size-4 text-text-warning-secondary' / >
< RiAlertFill className = 'size-4 text-text-warning-secondary' / >
< / div >
< span className = 'system-xs-medium' > { t ( 'datasetSettings.form.upgradeHighQualityTip' ) } < / span >
< / div > }
< span className = 'system-xs-medium text-text-primary' >
{ t ( 'datasetSettings.form.upgradeHighQualityTip' ) }
< / span >
< / div >
) }
< / div >
< / div >
< / >
@ -347,6 +434,12 @@ const Form = () => {
< / Button >
< / div >
< / div >
{ showAppIconPicker && (
< AppIconPicker
onSelect = { handleSelectAppIcon }
onClose = { handleCloseAppIconPicker }
/ >
) }
< / div >
)
}