2025-05-07 11:30:13 +08:00
|
|
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
2025-05-03 13:43:37 +08:00
|
|
|
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 Input from '@/app/components/base/input'
|
|
|
|
import Textarea from '@/app/components/base/textarea'
|
|
|
|
import type { AppIconType } from '@/types/app'
|
|
|
|
import { RiCloseLine } from '@remixicon/react'
|
|
|
|
import PermissionSelector from '../../settings/permission-selector'
|
2025-05-07 11:30:13 +08:00
|
|
|
import type { CreateDatasetReq } from '@/models/datasets'
|
2025-05-08 09:42:02 +08:00
|
|
|
import { ChunkingMode, DatasetPermission } from '@/models/datasets'
|
2025-05-03 13:43:37 +08:00
|
|
|
import { useMembers } from '@/service/use-common'
|
|
|
|
import Button from '@/app/components/base/button'
|
|
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
import Toast from '@/app/components/base/toast'
|
2025-05-08 09:42:02 +08:00
|
|
|
import { useCreatePipelineDataset } from '@/service/knowledge/use-create-dataset'
|
2025-05-07 11:30:13 +08:00
|
|
|
import type { Member } from '@/models/common'
|
2025-05-03 13:43:37 +08:00
|
|
|
|
|
|
|
type CreateFromScratchProps = {
|
2025-05-07 14:29:01 +08:00
|
|
|
onClose: () => void
|
2025-05-03 13:43:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
const DEFAULT_APP_ICON: AppIconSelection = {
|
|
|
|
type: 'emoji',
|
|
|
|
icon: '📙',
|
|
|
|
background: '#FFF4ED',
|
|
|
|
}
|
|
|
|
|
|
|
|
const CreateFromScratch = ({
|
|
|
|
onClose,
|
|
|
|
}: CreateFromScratchProps) => {
|
|
|
|
const { t } = useTranslation()
|
|
|
|
const [name, setName] = useState('')
|
|
|
|
const [appIcon, setAppIcon] = useState<AppIconSelection>(DEFAULT_APP_ICON)
|
|
|
|
const [description, setDescription] = useState('')
|
|
|
|
const [permission, setPermission] = useState(DatasetPermission.onlyMe)
|
|
|
|
const [showAppIconPicker, setShowAppIconPicker] = useState(false)
|
|
|
|
const [selectedMemberIDs, setSelectedMemberIDs] = useState<string[]>([])
|
|
|
|
const previousAppIcon = useRef<AppIconSelection>(DEFAULT_APP_ICON)
|
2025-05-07 11:30:13 +08:00
|
|
|
const [memberList, setMemberList] = useState<Member[]>([])
|
2025-05-03 13:43:37 +08:00
|
|
|
|
2025-05-07 11:30:13 +08:00
|
|
|
const { data: members } = useMembers()
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (members?.accounts)
|
|
|
|
setMemberList(members.accounts)
|
|
|
|
}, [members])
|
2025-05-03 13:43:37 +08:00
|
|
|
|
|
|
|
const handleAppNameChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
const value = event.target.value
|
|
|
|
setName(value)
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
const handleOpenAppIconPicker = useCallback(() => {
|
|
|
|
setShowAppIconPicker(true)
|
|
|
|
previousAppIcon.current = appIcon
|
|
|
|
}, [appIcon])
|
|
|
|
|
|
|
|
const handleSelectAppIcon = useCallback((icon: AppIconSelection) => {
|
|
|
|
setAppIcon(icon)
|
|
|
|
setShowAppIconPicker(false)
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
const handleCloseAppIconPicker = useCallback(() => {
|
|
|
|
setAppIcon(previousAppIcon.current)
|
|
|
|
setShowAppIconPicker(false)
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
const handleDescriptionChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
|
|
const value = event.target.value
|
|
|
|
setDescription(value)
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
const handlePermissionChange = useCallback((value?: DatasetPermission) => {
|
|
|
|
setPermission(value!)
|
|
|
|
}, [])
|
|
|
|
|
2025-05-08 09:42:02 +08:00
|
|
|
const { mutateAsync: createEmptyDataset } = useCreatePipelineDataset()
|
2025-05-07 11:30:13 +08:00
|
|
|
|
2025-05-07 14:29:01 +08:00
|
|
|
const handleCreate = useCallback(async () => {
|
2025-05-03 13:43:37 +08:00
|
|
|
if (!name) {
|
|
|
|
Toast.notify({
|
|
|
|
type: 'error',
|
|
|
|
message: 'Please enter a name for the Knowledge Base.',
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
2025-05-07 11:30:13 +08:00
|
|
|
const request: CreateDatasetReq = {
|
|
|
|
name,
|
|
|
|
description,
|
|
|
|
icon_info: {
|
|
|
|
icon_type: appIcon.type,
|
|
|
|
icon: appIcon.type === 'image' ? appIcon.fileId : appIcon.icon,
|
|
|
|
icon_background: appIcon.type === 'image' ? undefined : appIcon.background,
|
|
|
|
icon_url: appIcon.type === 'image' ? appIcon.url : undefined,
|
|
|
|
},
|
2025-05-08 09:42:02 +08:00
|
|
|
doc_form: ChunkingMode.text,
|
2025-05-07 11:30:13 +08:00
|
|
|
permission,
|
|
|
|
}
|
|
|
|
// Handle permission
|
|
|
|
if (request.permission === DatasetPermission.partialMembers) {
|
|
|
|
const selectedMemberList = selectedMemberIDs.map((id) => {
|
|
|
|
return {
|
|
|
|
user_id: id,
|
|
|
|
role: memberList.find(member => member.id === id)?.role,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
request.partial_member_list = selectedMemberList
|
|
|
|
}
|
2025-05-07 14:29:01 +08:00
|
|
|
await createEmptyDataset(request, {
|
|
|
|
onSettled: () => {
|
|
|
|
onClose?.()
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}, [name, permission, appIcon, description, createEmptyDataset, memberList, selectedMemberIDs, onClose])
|
2025-05-03 13:43:37 +08:00
|
|
|
|
|
|
|
return (
|
|
|
|
<div className='relative flex flex-col'>
|
|
|
|
{/* Header */}
|
|
|
|
<div className='pb-3 pl-6 pr-14 pt-6'>
|
|
|
|
<span className='title-2xl-semi-bold text-text-primary'>
|
2025-05-07 11:30:13 +08:00
|
|
|
{t('datasetPipeline.creation.createKnowledge')}
|
2025-05-03 13:43:37 +08:00
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
<button
|
|
|
|
className='absolute right-5 top-5 flex size-8 items-center justify-center'
|
|
|
|
onClick={onClose}
|
|
|
|
>
|
|
|
|
<RiCloseLine className='size-5 text-text-tertiary' />
|
|
|
|
</button>
|
|
|
|
{/* Form */}
|
|
|
|
<div className='flex flex-col gap-y-5 px-6 py-3'>
|
|
|
|
<div className='flex items-end gap-x-3 self-stretch'>
|
|
|
|
<div className='flex grow flex-col gap-y-1 pb-1'>
|
2025-05-07 11:30:13 +08:00
|
|
|
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>
|
2025-05-07 14:29:01 +08:00
|
|
|
{t('datasetPipeline.knowledgeNameAndIcon')}
|
2025-05-07 11:30:13 +08:00
|
|
|
</label>
|
2025-05-03 13:43:37 +08:00
|
|
|
<Input
|
|
|
|
onChange={handleAppNameChange}
|
|
|
|
value={name}
|
2025-05-07 14:29:01 +08:00
|
|
|
placeholder={t('datasetPipeline.knowledgeNameAndIconPlaceholder')}
|
2025-05-03 13:43:37 +08:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<AppIcon
|
|
|
|
size='xxl'
|
|
|
|
onClick={handleOpenAppIconPicker}
|
|
|
|
className='cursor-pointer'
|
|
|
|
iconType={appIcon.type as AppIconType}
|
|
|
|
icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon}
|
|
|
|
background={appIcon.type === 'image' ? undefined : appIcon.background}
|
|
|
|
imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
|
2025-05-03 17:16:00 +08:00
|
|
|
showEditIcon
|
2025-05-03 13:43:37 +08:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className='flex flex-col gap-y-1'>
|
2025-05-07 11:30:13 +08:00
|
|
|
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>
|
2025-05-07 14:29:01 +08:00
|
|
|
{t('datasetPipeline.knowledgeDescription')}
|
2025-05-07 11:30:13 +08:00
|
|
|
</label>
|
2025-05-03 13:43:37 +08:00
|
|
|
<Textarea
|
|
|
|
onChange={handleDescriptionChange}
|
|
|
|
value={description}
|
2025-05-07 14:29:01 +08:00
|
|
|
placeholder={t('datasetPipeline.knowledgeDescriptionPlaceholder')}
|
2025-05-03 13:43:37 +08:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className='flex flex-col gap-y-1'>
|
2025-05-07 11:30:13 +08:00
|
|
|
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>
|
2025-05-07 14:29:01 +08:00
|
|
|
{t('datasetPipeline.knowledgePermissions')}
|
2025-05-07 11:30:13 +08:00
|
|
|
</label>
|
|
|
|
<PermissionSelector
|
|
|
|
permission={permission}
|
|
|
|
value={selectedMemberIDs}
|
|
|
|
onChange={handlePermissionChange}
|
|
|
|
onMemberSelect={setSelectedMemberIDs}
|
|
|
|
memberList={memberList}
|
|
|
|
/>
|
2025-05-03 13:43:37 +08:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
|
|
<div className='flex items-center justify-end gap-x-2 p-6 pt-5'>
|
|
|
|
<Button
|
|
|
|
variant='secondary'
|
|
|
|
onClick={onClose}
|
|
|
|
>
|
|
|
|
{t('common.operation.cancel')}
|
|
|
|
</Button>
|
|
|
|
<Button
|
|
|
|
variant='primary'
|
|
|
|
onClick={handleCreate}
|
|
|
|
>
|
|
|
|
{t('common.operation.create')}
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
{showAppIconPicker && (
|
|
|
|
<AppIconPicker
|
|
|
|
onSelect={handleSelectAppIcon}
|
|
|
|
onClose={handleCloseAppIconPicker}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export default React.memo(CreateFromScratch)
|