Browse Source

fix: add dataset edit permissions (#13223)

Wu Tianwei 2 months ago
parent
commit
49b4144ffd

+ 11 - 7
web/app/components/app/configuration/dataset-config/card-item/item.tsx

@@ -23,12 +23,14 @@ type ItemProps = {
   onRemove: (id: string) => void
   onRemove: (id: string) => void
   readonly?: boolean
   readonly?: boolean
   onSave: (newDataset: DataSet) => void
   onSave: (newDataset: DataSet) => void
+  editable?: boolean
 }
 }
 
 
 const Item: FC<ItemProps> = ({
 const Item: FC<ItemProps> = ({
   config,
   config,
   onSave,
   onSave,
   onRemove,
   onRemove,
+  editable = true,
 }) => {
 }) => {
   const media = useBreakpoints()
   const media = useBreakpoints()
   const isMobile = media === MediaType.mobile
   const isMobile = media === MediaType.mobile
@@ -68,19 +70,21 @@ const Item: FC<ItemProps> = ({
         <div className='flex items-center h-[18px]'>
         <div className='flex items-center h-[18px]'>
           <div className='grow text-[13px] font-medium text-gray-800 truncate' title={config.name}>{config.name}</div>
           <div className='grow text-[13px] font-medium text-gray-800 truncate' title={config.name}>{config.name}</div>
           {config.provider === 'external'
           {config.provider === 'external'
-            ? <Badge text={t('dataset.externalTag')}></Badge>
+            ? <Badge text={t('dataset.externalTag') as string} />
             : <Badge
             : <Badge
               text={formatIndexingTechniqueAndMethod(config.indexing_technique, config.retrieval_model_dict?.search_method)}
               text={formatIndexingTechniqueAndMethod(config.indexing_technique, config.retrieval_model_dict?.search_method)}
             />}
             />}
         </div>
         </div>
       </div>
       </div>
       <div className='hidden rounded-lg group-hover:flex items-center justify-end absolute right-0 top-0 bottom-0 pr-2 w-[124px] bg-gradient-to-r from-white/50 to-white to-50%'>
       <div className='hidden rounded-lg group-hover:flex items-center justify-end absolute right-0 top-0 bottom-0 pr-2 w-[124px] bg-gradient-to-r from-white/50 to-white to-50%'>
-        <div
-          className='flex items-center justify-center mr-1 w-6 h-6 hover:bg-black/5 rounded-md cursor-pointer'
-          onClick={() => setShowSettingsModal(true)}
-        >
-          <RiEditLine className='w-4 h-4 text-gray-500' />
-        </div>
+        {
+          editable && <div
+            className='flex items-center justify-center mr-1 w-6 h-6 hover:bg-black/5 rounded-md cursor-pointer'
+            onClick={() => setShowSettingsModal(true)}
+          >
+            <RiEditLine className='w-4 h-4 text-gray-500' />
+          </div>
+        }
         <div
         <div
           className='group/action flex items-center justify-center w-6 h-6 hover:bg-[#FEE4E2] rounded-md cursor-pointer'
           className='group/action flex items-center justify-center w-6 h-6 hover:bg-[#FEE4E2] rounded-md cursor-pointer'
           onClick={() => onRemove(config.id)}
           onClick={() => onRemove(config.id)}

+ 20 - 2
web/app/components/app/configuration/dataset-config/index.tsx

@@ -1,6 +1,6 @@
 'use client'
 'use client'
 import type { FC } from 'react'
 import type { FC } from 'react'
-import React from 'react'
+import React, { useMemo } 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 produce from 'immer'
 import produce from 'immer'
@@ -19,6 +19,8 @@ import {
 } from '@/app/components/workflow/nodes/knowledge-retrieval/utils'
 } from '@/app/components/workflow/nodes/knowledge-retrieval/utils'
 import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
 import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
 import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
 import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
+import { useSelector as useAppContextSelector } from '@/context/app-context'
+import { hasEditPermissionForDataset } from '@/utils/permission'
 
 
 const Icon = (
 const Icon = (
   <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
   <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -29,6 +31,7 @@ const Icon = (
 
 
 const DatasetConfig: FC = () => {
 const DatasetConfig: FC = () => {
   const { t } = useTranslation()
   const { t } = useTranslation()
+  const userProfile = useAppContextSelector(s => s.userProfile)
   const {
   const {
     mode,
     mode,
     dataSets: dataSet,
     dataSets: dataSet,
@@ -105,6 +108,20 @@ const DatasetConfig: FC = () => {
     setModelConfig(newModelConfig)
     setModelConfig(newModelConfig)
   }
   }
 
 
+  const formattedDataset = useMemo(() => {
+    return dataSet.map((item) => {
+      const datasetConfig = {
+        createdBy: item.created_by,
+        partialMemberList: item.partial_member_list || [],
+        permission: item.permission,
+      }
+      return {
+        ...item,
+        editable: hasEditPermissionForDataset(userProfile?.id || '', datasetConfig),
+      }
+    })
+  }, [dataSet, userProfile?.id])
+
   return (
   return (
     <FeaturePanel
     <FeaturePanel
       className='mt-2'
       className='mt-2'
@@ -122,12 +139,13 @@ const DatasetConfig: FC = () => {
       {hasData
       {hasData
         ? (
         ? (
           <div className='flex flex-wrap mt-1 px-3 pb-3 justify-between'>
           <div className='flex flex-wrap mt-1 px-3 pb-3 justify-between'>
-            {dataSet.map(item => (
+            {formattedDataset.map(item => (
               <CardItem
               <CardItem
                 key={item.id}
                 key={item.id}
                 config={item}
                 config={item}
                 onRemove={onRemove}
                 onRemove={onRemove}
                 onSave={handleSave}
                 onSave={handleSave}
+                editable={item.editable}
               />
               />
             ))}
             ))}
           </div>
           </div>

+ 2 - 2
web/app/components/app/configuration/dataset-config/settings-modal/index.tsx

@@ -12,7 +12,7 @@ import Divider from '@/app/components/base/divider'
 import Button from '@/app/components/base/button'
 import Button from '@/app/components/base/button'
 import Input from '@/app/components/base/input'
 import Input from '@/app/components/base/input'
 import Textarea from '@/app/components/base/textarea'
 import Textarea from '@/app/components/base/textarea'
-import { type DataSet } from '@/models/datasets'
+import { type DataSet, DatasetPermission } from '@/models/datasets'
 import { useToastContext } from '@/app/components/base/toast'
 import { useToastContext } from '@/app/components/base/toast'
 import { updateDatasetSetting } from '@/service/datasets'
 import { updateDatasetSetting } from '@/service/datasets'
 import { useAppContext } from '@/context/app-context'
 import { useAppContext } from '@/context/app-context'
@@ -134,7 +134,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
           }),
           }),
         },
         },
       } as any
       } as any
-      if (permission === 'partial_members') {
+      if (permission === DatasetPermission.partialMembers) {
         requestParams.body.partial_member_list = selectedMemberIDs.map((id) => {
         requestParams.body.partial_member_list = selectedMemberIDs.map((id) => {
           return {
           return {
             user_id: id,
             user_id: id,

+ 2 - 2
web/app/components/datasets/settings/form/index.tsx

@@ -17,7 +17,7 @@ import Input from '@/app/components/base/input'
 import Textarea from '@/app/components/base/textarea'
 import Textarea from '@/app/components/base/textarea'
 import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
 import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
 import { updateDatasetSetting } from '@/service/datasets'
 import { updateDatasetSetting } from '@/service/datasets'
-import { type DataSetListResponse } from '@/models/datasets'
+import { type DataSetListResponse, DatasetPermission } from '@/models/datasets'
 import DatasetDetailContext from '@/context/dataset-detail'
 import DatasetDetailContext from '@/context/dataset-detail'
 import { type RetrievalConfig } from '@/types/app'
 import { type RetrievalConfig } from '@/types/app'
 import { useAppContext } from '@/context/app-context'
 import { useAppContext } from '@/context/app-context'
@@ -145,7 +145,7 @@ const Form = () => {
           }),
           }),
         },
         },
       } as any
       } as any
-      if (permission === 'partial_members') {
+      if (permission === DatasetPermission.partialMembers) {
         requestParams.body.partial_member_list = selectedMemberIDs.map((id) => {
         requestParams.body.partial_member_list = selectedMemberIDs.map((id) => {
           return {
           return {
             user_id: id,
             user_id: id,

+ 17 - 13
web/app/components/datasets/settings/permission-selector/index.tsx

@@ -12,7 +12,7 @@ import Avatar from '@/app/components/base/avatar'
 import Input from '@/app/components/base/input'
 import Input from '@/app/components/base/input'
 import { Check } from '@/app/components/base/icons/src/vender/line/general'
 import { Check } from '@/app/components/base/icons/src/vender/line/general'
 import { Users01, UsersPlus } from '@/app/components/base/icons/src/vender/solid/users'
 import { Users01, UsersPlus } from '@/app/components/base/icons/src/vender/solid/users'
-import type { DatasetPermission } from '@/models/datasets'
+import { DatasetPermission } from '@/models/datasets'
 import { useAppContext } from '@/context/app-context'
 import { useAppContext } from '@/context/app-context'
 import type { Member } from '@/models/common'
 import type { Member } from '@/models/common'
 export type RoleSelectorProps = {
 export type RoleSelectorProps = {
@@ -60,6 +60,10 @@ const PermissionSelector = ({ disabled, permission, value, memberList, onChange,
     return memberList.filter(member => (member.name.includes(searchKeywords) || member.email.includes(searchKeywords)) && member.id !== userProfile.id && ['owner', 'admin', 'editor', 'dataset_operator'].includes(member.role))
     return memberList.filter(member => (member.name.includes(searchKeywords) || member.email.includes(searchKeywords)) && member.id !== userProfile.id && ['owner', 'admin', 'editor', 'dataset_operator'].includes(member.role))
   }, [memberList, searchKeywords, userProfile])
   }, [memberList, searchKeywords, userProfile])
 
 
+  const isOnlyMe = permission === DatasetPermission.onlyMe
+  const isAllTeamMembers = permission === DatasetPermission.allTeamMembers
+  const isPartialMembers = permission === DatasetPermission.partialMembers
+
   return (
   return (
     <PortalToFollowElem
     <PortalToFollowElem
       open={open}
       open={open}
@@ -72,14 +76,14 @@ const PermissionSelector = ({ disabled, permission, value, memberList, onChange,
           onClick={() => !disabled && setOpen(v => !v)}
           onClick={() => !disabled && setOpen(v => !v)}
           className='block'
           className='block'
         >
         >
-          {permission === 'only_me' && (
+          {isOnlyMe && (
             <div className={cn('flex items-center px-3 py-[6px] rounded-lg bg-gray-100 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200', disabled && 'hover:!bg-gray-100 !cursor-default')}>
             <div className={cn('flex items-center px-3 py-[6px] rounded-lg bg-gray-100 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200', disabled && 'hover:!bg-gray-100 !cursor-default')}>
               <Avatar avatar={userProfile.avatar_url} name={userProfile.name} className='shrink-0 mr-2' size={24} />
               <Avatar avatar={userProfile.avatar_url} name={userProfile.name} className='shrink-0 mr-2' size={24} />
               <div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('datasetSettings.form.permissionsOnlyMe')}</div>
               <div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('datasetSettings.form.permissionsOnlyMe')}</div>
               {!disabled && <RiArrowDownSLine className='shrink-0 w-4 h-4 text-gray-700' />}
               {!disabled && <RiArrowDownSLine className='shrink-0 w-4 h-4 text-gray-700' />}
             </div>
             </div>
           )}
           )}
-          {permission === 'all_team_members' && (
+          {isAllTeamMembers && (
             <div className={cn('flex items-center px-3 py-[6px] rounded-lg bg-gray-100 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200')}>
             <div className={cn('flex items-center px-3 py-[6px] rounded-lg bg-gray-100 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200')}>
               <div className='mr-2 flex items-center justify-center w-6 h-6 rounded-lg bg-[#EEF4FF]'>
               <div className='mr-2 flex items-center justify-center w-6 h-6 rounded-lg bg-[#EEF4FF]'>
                 <Users01 className='w-3.5 h-3.5 text-[#444CE7]' />
                 <Users01 className='w-3.5 h-3.5 text-[#444CE7]' />
@@ -88,7 +92,7 @@ const PermissionSelector = ({ disabled, permission, value, memberList, onChange,
               {!disabled && <RiArrowDownSLine className='shrink-0 w-4 h-4 text-gray-700' />}
               {!disabled && <RiArrowDownSLine className='shrink-0 w-4 h-4 text-gray-700' />}
             </div>
             </div>
           )}
           )}
-          {permission === 'partial_members' && (
+          {isPartialMembers && (
             <div className={cn('flex items-center px-3 py-[6px] rounded-lg bg-gray-100 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200')}>
             <div className={cn('flex items-center px-3 py-[6px] rounded-lg bg-gray-100 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200')}>
               <div className='mr-2 flex items-center justify-center w-6 h-6 rounded-lg bg-[#EEF4FF]'>
               <div className='mr-2 flex items-center justify-center w-6 h-6 rounded-lg bg-[#EEF4FF]'>
                 <Users01 className='w-3.5 h-3.5 text-[#444CE7]' />
                 <Users01 className='w-3.5 h-3.5 text-[#444CE7]' />
@@ -102,17 +106,17 @@ const PermissionSelector = ({ disabled, permission, value, memberList, onChange,
           <div className='relative w-[480px] rounded-lg border-[0.5px] bg-white shadow-lg'>
           <div className='relative w-[480px] rounded-lg border-[0.5px] bg-white shadow-lg'>
             <div className='p-1'>
             <div className='p-1'>
               <div className='pl-3 pr-2 py-1 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => {
               <div className='pl-3 pr-2 py-1 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => {
-                onChange('only_me')
+                onChange(DatasetPermission.onlyMe)
                 setOpen(false)
                 setOpen(false)
               }}>
               }}>
                 <div className='flex items-center gap-2'>
                 <div className='flex items-center gap-2'>
                   <Avatar avatar={userProfile.avatar_url} name={userProfile.name} className='shrink-0 mr-2' size={24} />
                   <Avatar avatar={userProfile.avatar_url} name={userProfile.name} className='shrink-0 mr-2' size={24} />
                   <div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('datasetSettings.form.permissionsOnlyMe')}</div>
                   <div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('datasetSettings.form.permissionsOnlyMe')}</div>
-                  {permission === 'only_me' && <Check className='w-4 h-4 text-primary-600' />}
+                  {isOnlyMe && <Check className='w-4 h-4 text-primary-600' />}
                 </div>
                 </div>
               </div>
               </div>
               <div className='pl-3 pr-2 py-1 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => {
               <div className='pl-3 pr-2 py-1 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => {
-                onChange('all_team_members')
+                onChange(DatasetPermission.allTeamMembers)
                 setOpen(false)
                 setOpen(false)
               }}>
               }}>
                 <div className='flex items-center gap-2'>
                 <div className='flex items-center gap-2'>
@@ -120,23 +124,23 @@ const PermissionSelector = ({ disabled, permission, value, memberList, onChange,
                     <Users01 className='w-3.5 h-3.5 text-[#444CE7]' />
                     <Users01 className='w-3.5 h-3.5 text-[#444CE7]' />
                   </div>
                   </div>
                   <div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('datasetSettings.form.permissionsAllMember')}</div>
                   <div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('datasetSettings.form.permissionsAllMember')}</div>
-                  {permission === 'all_team_members' && <Check className='w-4 h-4 text-primary-600' />}
+                  {isAllTeamMembers && <Check className='w-4 h-4 text-primary-600' />}
                 </div>
                 </div>
               </div>
               </div>
               <div className='pl-3 pr-2 py-1 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => {
               <div className='pl-3 pr-2 py-1 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => {
-                onChange('partial_members')
+                onChange(DatasetPermission.partialMembers)
                 onMemberSelect([userProfile.id])
                 onMemberSelect([userProfile.id])
               }}>
               }}>
                 <div className='flex items-center gap-2'>
                 <div className='flex items-center gap-2'>
-                  <div className={cn('mr-2 flex items-center justify-center w-6 h-6 rounded-lg bg-[#FFF6ED]', permission === 'partial_members' && '!bg-[#EEF4FF]')}>
-                    <UsersPlus className={cn('w-3.5 h-3.5 text-[#FB6514]', permission === 'partial_members' && '!text-[#444CE7]')} />
+                  <div className={cn('mr-2 flex items-center justify-center w-6 h-6 rounded-lg bg-[#FFF6ED]', isPartialMembers && '!bg-[#EEF4FF]')}>
+                    <UsersPlus className={cn('w-3.5 h-3.5 text-[#FB6514]', isPartialMembers && '!text-[#444CE7]')} />
                   </div>
                   </div>
                   <div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('datasetSettings.form.permissionsInvitedMembers')}</div>
                   <div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('datasetSettings.form.permissionsInvitedMembers')}</div>
-                  {permission === 'partial_members' && <Check className='w-4 h-4 text-primary-600' />}
+                  {isPartialMembers && <Check className='w-4 h-4 text-primary-600' />}
                 </div>
                 </div>
               </div>
               </div>
             </div>
             </div>
-            {permission === 'partial_members' && (
+            {isPartialMembers && (
               <div className='max-h-[360px] border-t-[1px] border-gray-100 p-1 overflow-y-auto'>
               <div className='max-h-[360px] border-t-[1px] border-gray-100 p-1 overflow-y-auto'>
                 <div className='sticky left-0 top-0 p-2 pb-1 bg-white'>
                 <div className='sticky left-0 top-0 p-2 pb-1 bg-white'>
                   <Input
                   <Input

+ 0 - 66
web/app/components/datasets/settings/permissions-radio/index.tsx

@@ -1,66 +0,0 @@
-'use client'
-import { useTranslation } from 'react-i18next'
-import s from './index.module.css'
-import classNames from '@/utils/classnames'
-import type { DataSet } from '@/models/datasets'
-
-const itemClass = `
-  flex items-center w-full sm:w-[234px] h-12 px-3 rounded-xl bg-gray-25 border border-gray-100 cursor-pointer
-`
-const radioClass = `
-  w-4 h-4 border-[2px] border-gray-200 rounded-full
-`
-type IPermissionsRadioProps = {
-  value?: DataSet['permission']
-  onChange: (v?: DataSet['permission']) => void
-  itemClassName?: string
-  disable?: boolean
-}
-
-const PermissionsRadio = ({
-  value,
-  onChange,
-  itemClassName,
-  disable,
-}: IPermissionsRadioProps) => {
-  const { t } = useTranslation()
-  const options = [
-    {
-      key: 'only_me',
-      text: t('datasetSettings.form.permissionsOnlyMe'),
-    },
-    {
-      key: 'all_team_members',
-      text: t('datasetSettings.form.permissionsAllMember'),
-    },
-  ]
-
-  return (
-    <div className={classNames(s.wrapper, 'flex justify-between w-full flex-wrap gap-y-2')}>
-      {
-        options.map(option => (
-          <div
-            key={option.key}
-            className={classNames(
-              itemClass,
-              itemClassName,
-              s.item,
-              option.key === value && s['item-active'],
-              disable && s.disable,
-            )}
-            onClick={() => {
-              if (!disable)
-                onChange(option.key as DataSet['permission'])
-            }}
-          >
-            <div className={classNames(s['user-icon'], 'mr-3')} />
-            <div className='grow text-sm text-gray-900'>{option.text}</div>
-            <div className={classNames(radioClass, s.radio)} />
-          </div>
-        ))
-      }
-    </div>
-  )
-}
-
-export default PermissionsRadio

+ 13 - 9
web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx

@@ -23,6 +23,7 @@ type Props = {
   onRemove: () => void
   onRemove: () => void
   onChange: (dataSet: DataSet) => void
   onChange: (dataSet: DataSet) => void
   readonly?: boolean
   readonly?: boolean
+  editable?: boolean
 }
 }
 
 
 const DatasetItem: FC<Props> = ({
 const DatasetItem: FC<Props> = ({
@@ -30,6 +31,7 @@ const DatasetItem: FC<Props> = ({
   onRemove,
   onRemove,
   onChange,
   onChange,
   readonly,
   readonly,
+  editable = true,
 }) => {
 }) => {
   const media = useBreakpoints()
   const media = useBreakpoints()
   const { t } = useTranslation()
   const { t } = useTranslation()
@@ -75,14 +77,16 @@ const DatasetItem: FC<Props> = ({
       </div>
       </div>
       {!readonly && (
       {!readonly && (
         <div className='hidden group-hover/dataset-item:flex shrink-0 ml-2  items-center space-x-1'>
         <div className='hidden group-hover/dataset-item:flex shrink-0 ml-2  items-center space-x-1'>
-          <ActionButton
-            onClick={(e) => {
-              e.stopPropagation()
-              showSettingsModal()
-            }}
-          >
-            <RiEditLine className='w-4 h-4 flex-shrink-0 text-text-tertiary' />
-          </ActionButton>
+          {
+            editable && <ActionButton
+              onClick={(e) => {
+                e.stopPropagation()
+                showSettingsModal()
+              }}
+            >
+              <RiEditLine className='w-4 h-4 flex-shrink-0 text-text-tertiary' />
+            </ActionButton>
+          }
           <ActionButton
           <ActionButton
             onClick={handleRemove}
             onClick={handleRemove}
             state={ActionButtonState.Destructive}
             state={ActionButtonState.Destructive}
@@ -102,7 +106,7 @@ const DatasetItem: FC<Props> = ({
       {
       {
         payload.provider === 'external' && <Badge
         payload.provider === 'external' && <Badge
           className='group-hover/dataset-item:hidden shrink-0'
           className='group-hover/dataset-item:hidden shrink-0'
-          text={t('dataset.externalTag')}
+          text={t('dataset.externalTag') as string}
         />
         />
       }
       }
 
 

+ 23 - 3
web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-list.tsx

@@ -1,10 +1,13 @@
 'use client'
 'use client'
 import type { FC } from 'react'
 import type { FC } from 'react'
-import React, { useCallback } from 'react'
+import React, { useCallback, useMemo } from 'react'
 import produce from 'immer'
 import produce from 'immer'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 import Item from './dataset-item'
 import Item from './dataset-item'
 import type { DataSet } from '@/models/datasets'
 import type { DataSet } from '@/models/datasets'
+import { useSelector as useAppContextSelector } from '@/context/app-context'
+import { hasEditPermissionForDataset } from '@/utils/permission'
+
 type Props = {
 type Props = {
   list: DataSet[]
   list: DataSet[]
   onChange: (list: DataSet[]) => void
   onChange: (list: DataSet[]) => void
@@ -17,6 +20,7 @@ const DatasetList: FC<Props> = ({
   readonly,
   readonly,
 }) => {
 }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
+  const userProfile = useAppContextSelector(s => s.userProfile)
 
 
   const handleRemove = useCallback((index: number) => {
   const handleRemove = useCallback((index: number) => {
     return () => {
     return () => {
@@ -35,10 +39,25 @@ const DatasetList: FC<Props> = ({
       onChange(newList)
       onChange(newList)
     }
     }
   }, [list, onChange])
   }, [list, onChange])
+
+  const formattedList = useMemo(() => {
+    return list.map((item) => {
+      const datasetConfig = {
+        createdBy: item.created_by,
+        partialMemberList: item.partial_member_list || [],
+        permission: item.permission,
+      }
+      return {
+        ...item,
+        editable: hasEditPermissionForDataset(userProfile?.id || '', datasetConfig),
+      }
+    })
+  }, [list, userProfile?.id])
+
   return (
   return (
     <div className='space-y-1'>
     <div className='space-y-1'>
-      {list.length
-        ? list.map((item, index) => {
+      {formattedList.length
+        ? formattedList.map((item, index) => {
           return (
           return (
             <Item
             <Item
               key={index}
               key={index}
@@ -46,6 +65,7 @@ const DatasetList: FC<Props> = ({
               onRemove={handleRemove(index)}
               onRemove={handleRemove(index)}
               onChange={handleChange(index)}
               onChange={handleChange(index)}
               readonly={readonly}
               readonly={readonly}
+              editable={item.editable}
             />
             />
           )
           )
         })
         })

+ 6 - 2
web/models/datasets.ts

@@ -9,7 +9,11 @@ export enum DataSourceType {
   WEB = 'website_crawl',
   WEB = 'website_crawl',
 }
 }
 
 
-export type DatasetPermission = 'only_me' | 'all_team_members' | 'partial_members'
+export enum DatasetPermission {
+  'onlyMe' = 'only_me',
+  'allTeamMembers' = 'all_team_members',
+  'partialMembers' = 'partial_members',
+}
 
 
 export enum ChunkingMode {
 export enum ChunkingMode {
   'text' = 'text_model', // General text
   'text' = 'text_model', // General text
@@ -40,7 +44,7 @@ export type DataSet = {
   retrieval_model_dict: RetrievalConfig
   retrieval_model_dict: RetrievalConfig
   retrieval_model: RetrievalConfig
   retrieval_model: RetrievalConfig
   tags: Tag[]
   tags: Tag[]
-  partial_member_list?: any[]
+  partial_member_list?: string[]
   external_knowledge_info: {
   external_knowledge_info: {
     external_knowledge_id: string
     external_knowledge_id: string
     external_knowledge_api_id: string
     external_knowledge_api_id: string

+ 18 - 0
web/utils/permission.ts

@@ -0,0 +1,18 @@
+import { DatasetPermission } from '@/models/datasets'
+
+type DatasetConfig = {
+  createdBy: string
+  partialMemberList: string[]
+  permission: DatasetPermission
+}
+
+export const hasEditPermissionForDataset = (userId: string, datasetConfig: DatasetConfig) => {
+  const { createdBy, partialMemberList, permission } = datasetConfig
+  if (permission === DatasetPermission.onlyMe)
+    return userId === createdBy
+  if (permission === DatasetPermission.allTeamMembers)
+    return true
+  if (permission === DatasetPermission.partialMembers)
+    return partialMemberList.includes(userId)
+  return false
+}