You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

create-from-scratch.tsx 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import React, { useCallback, useEffect, useRef, useState } from 'react'
  2. import AppIcon from '@/app/components/base/app-icon'
  3. import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
  4. import AppIconPicker from '@/app/components/base/app-icon-picker'
  5. import Input from '@/app/components/base/input'
  6. import Textarea from '@/app/components/base/textarea'
  7. import type { AppIconType } from '@/types/app'
  8. import { RiCloseLine } from '@remixicon/react'
  9. import PermissionSelector from '../../settings/permission-selector'
  10. import type { CreateDatasetReq } from '@/models/datasets'
  11. import { DatasetPermission } from '@/models/datasets'
  12. import { useMembers } from '@/service/use-common'
  13. import Button from '@/app/components/base/button'
  14. import { useTranslation } from 'react-i18next'
  15. import Toast from '@/app/components/base/toast'
  16. import { useCreateDataset } from '@/service/knowledge/use-create-dataset'
  17. import type { Member } from '@/models/common'
  18. type CreateFromScratchProps = {
  19. onClose?: () => void
  20. onCreate?: () => void
  21. }
  22. const DEFAULT_APP_ICON: AppIconSelection = {
  23. type: 'emoji',
  24. icon: '📙',
  25. background: '#FFF4ED',
  26. }
  27. const CreateFromScratch = ({
  28. onClose,
  29. onCreate,
  30. }: CreateFromScratchProps) => {
  31. const { t } = useTranslation()
  32. const [name, setName] = useState('')
  33. const [appIcon, setAppIcon] = useState<AppIconSelection>(DEFAULT_APP_ICON)
  34. const [description, setDescription] = useState('')
  35. const [permission, setPermission] = useState(DatasetPermission.onlyMe)
  36. const [showAppIconPicker, setShowAppIconPicker] = useState(false)
  37. const [selectedMemberIDs, setSelectedMemberIDs] = useState<string[]>([])
  38. const previousAppIcon = useRef<AppIconSelection>(DEFAULT_APP_ICON)
  39. const [memberList, setMemberList] = useState<Member[]>([])
  40. const { data: members } = useMembers()
  41. useEffect(() => {
  42. if (members?.accounts)
  43. setMemberList(members.accounts)
  44. }, [members])
  45. const handleAppNameChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
  46. const value = event.target.value
  47. setName(value)
  48. }, [])
  49. const handleOpenAppIconPicker = useCallback(() => {
  50. setShowAppIconPicker(true)
  51. previousAppIcon.current = appIcon
  52. }, [appIcon])
  53. const handleSelectAppIcon = useCallback((icon: AppIconSelection) => {
  54. setAppIcon(icon)
  55. setShowAppIconPicker(false)
  56. }, [])
  57. const handleCloseAppIconPicker = useCallback(() => {
  58. setAppIcon(previousAppIcon.current)
  59. setShowAppIconPicker(false)
  60. }, [])
  61. const handleDescriptionChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
  62. const value = event.target.value
  63. setDescription(value)
  64. }, [])
  65. const handlePermissionChange = useCallback((value?: DatasetPermission) => {
  66. setPermission(value!)
  67. }, [])
  68. const { mutateAsync: createEmptyDataset } = useCreateDataset()
  69. const handleCreate = useCallback(() => {
  70. if (!name) {
  71. Toast.notify({
  72. type: 'error',
  73. message: 'Please enter a name for the Knowledge Base.',
  74. })
  75. return
  76. }
  77. const request: CreateDatasetReq = {
  78. name,
  79. description,
  80. icon_info: {
  81. icon_type: appIcon.type,
  82. icon: appIcon.type === 'image' ? appIcon.fileId : appIcon.icon,
  83. icon_background: appIcon.type === 'image' ? undefined : appIcon.background,
  84. icon_url: appIcon.type === 'image' ? appIcon.url : undefined,
  85. },
  86. permission,
  87. }
  88. // Handle permission
  89. if (request.permission === DatasetPermission.partialMembers) {
  90. const selectedMemberList = selectedMemberIDs.map((id) => {
  91. return {
  92. user_id: id,
  93. role: memberList.find(member => member.id === id)?.role,
  94. }
  95. })
  96. request.partial_member_list = selectedMemberList
  97. }
  98. createEmptyDataset(request)
  99. onCreate?.()
  100. onClose?.()
  101. }, [name, permission, appIcon, description, createEmptyDataset, memberList, selectedMemberIDs, onCreate, onClose])
  102. return (
  103. <div className='relative flex flex-col'>
  104. {/* Header */}
  105. <div className='pb-3 pl-6 pr-14 pt-6'>
  106. <span className='title-2xl-semi-bold text-text-primary'>
  107. {t('datasetPipeline.creation.createKnowledge')}
  108. </span>
  109. </div>
  110. <button
  111. className='absolute right-5 top-5 flex size-8 items-center justify-center'
  112. onClick={onClose}
  113. >
  114. <RiCloseLine className='size-5 text-text-tertiary' />
  115. </button>
  116. {/* Form */}
  117. <div className='flex flex-col gap-y-5 px-6 py-3'>
  118. <div className='flex items-end gap-x-3 self-stretch'>
  119. <div className='flex grow flex-col gap-y-1 pb-1'>
  120. <label className='system-sm-medium flex h-6 items-center text-text-secondary'>
  121. {t('datasetPipeline.creation.knowledgeNameAndIcon')}
  122. </label>
  123. <Input
  124. onChange={handleAppNameChange}
  125. value={name}
  126. placeholder={t('datasetPipeline.creation.knowledgeNameAndIconPlaceholder')}
  127. />
  128. </div>
  129. <AppIcon
  130. size='xxl'
  131. onClick={handleOpenAppIconPicker}
  132. className='cursor-pointer'
  133. iconType={appIcon.type as AppIconType}
  134. icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon}
  135. background={appIcon.type === 'image' ? undefined : appIcon.background}
  136. imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
  137. showEditIcon
  138. />
  139. </div>
  140. <div className='flex flex-col gap-y-1'>
  141. <label className='system-sm-medium flex h-6 items-center text-text-secondary'>
  142. {t('datasetPipeline.creation.knowledgeDescription')}
  143. </label>
  144. <Textarea
  145. onChange={handleDescriptionChange}
  146. value={description}
  147. placeholder={t('datasetPipeline.creation.knowledgeDescriptionPlaceholder')}
  148. />
  149. </div>
  150. <div className='flex flex-col gap-y-1'>
  151. <label className='system-sm-medium flex h-6 items-center text-text-secondary'>
  152. {t('datasetPipeline.creation.knowledgePermissions')}
  153. </label>
  154. <PermissionSelector
  155. permission={permission}
  156. value={selectedMemberIDs}
  157. onChange={handlePermissionChange}
  158. onMemberSelect={setSelectedMemberIDs}
  159. memberList={memberList}
  160. />
  161. </div>
  162. </div>
  163. {/* Actions */}
  164. <div className='flex items-center justify-end gap-x-2 p-6 pt-5'>
  165. <Button
  166. variant='secondary'
  167. onClick={onClose}
  168. >
  169. {t('common.operation.cancel')}
  170. </Button>
  171. <Button
  172. variant='primary'
  173. onClick={handleCreate}
  174. >
  175. {t('common.operation.create')}
  176. </Button>
  177. </div>
  178. {showAppIconPicker && (
  179. <AppIconPicker
  180. onSelect={handleSelectAppIcon}
  181. onClose={handleCloseAppIconPicker}
  182. />
  183. )}
  184. </div>
  185. )
  186. }
  187. export default React.memo(CreateFromScratch)